Two MoTacon attendees are on the left. The MoTaacon logo is in the center, and to the right a prompt to Get Your Ticket.

Test Data Types A Mental Model for Automation using playwright at Scale

06 Aug 2026

In this moment: Komal chowdhary
 
When we talk about test data management in automation for Playwright frameworks the strategy always starts with :
 
“Keep test data separate from test logic.”  That is good advice. 

But at enterprise scale, it is not enough.  Because once your Playwright test suite grows, you quickly realize something important: 

Not all test data behaves the same way. 

For example A product name, a user credential, a staging URL, an order ID generated during execution , Customer with saved card, Unique email, A Customer active across services are all test data. 

But they do not have the same lifecycle. They do not change for the same reason. They should not be handled in the same way. 

Earlier, our tests had values directly inside the flow: 

await loginPage.validLogin('maya.customer@example.com', 'Test@123'); 
await dashboardPage.searchProductAddCart('Blue Denim Jacket'); 
await ordersReviewPage.searchCountryAndSelect('ind', 'India'); 

This looks fine when you have a few tests. But at enterprise scale, it is not enough. 
When the suite grows, this becomes harder to maintain. 

So yes, we moved the data out of the test.  But more importantly, we started asking: 

 What type of data is this? 

That small question changes everything the way we design the data layer. 
That’s when we need a different data handling strategy. 
In this post, I’ll walk through the model that will help u think more clearly about test data: 

1. Static Data 

Static data is data that rarely changes. And the most common type data we use in our frameworks 

Examples:

  • Product names
  • Error messages
  • UI text
  • Country names
  • API route patterns

 
// src/data/products.data.ts 

export const products = {   
denimJacket: 'Blue Denim Jacket',
runningShoes: 'Road Runner Shoes',
wirelessHeadphones: 'Wave Sound Headphones', 
} as const; 
 
Usage: 

await dashboardPage.searchProductAddCart(products.denimJacket); 

What problem does this solve? 
If the product name changes, you update just one file. Simple and easy

2. User Data 

User data represents test users or personas. 

Examples:

  • Customer
  • Admin
  • Guest user
  • Support user
  • Seller
  • Buyer

// src/data/users.data.ts 

export const users = {  
 maya: { username: 'maya.customer@example.com', password: 'Test@123',   },  
 arjun: { username: 'arjun.customer@example.com', password: 'Test@123',   },   
admin: { username: 'admin.user@example.com', password: 'Admin@123',   },
 } as const; 

Usage: 

 const { username, password } = users.maya; await loginPage.validLogin(username, password); 

What problem does this solve? 

If a password changes, you update it in one place. Same concept as first one. 
But this way it also makes tests more readable because tests can talk in terms of personas, not raw credentials. 
 
3. Environment Data 

Environment data controls where your tests run. 

Examples:

  • Dev URL
  • QA URL
  • Staging URL
  • Production URL
  • API base URL

 
// src/config/env.config.ts
 const urls = {
 dev: 'https://dev.example.com',
 stage: 'https://stage.example.com',
 prod: 'https://example.com',
 } as const;
 
 const env = process.env.ENV ?? 'prod';
 export const baseUrl = urls[env as keyof typeof urls]; await page.goto(baseUrl); 

 
Run from command line: 
 
ENV=stage npx playwright test 

 
What problem does this solve? 
The same test can run across different environments without changing the test code. 
Especially useful in CI/CD pipelines. 

 
4. Runtime Data 
Runtime data is created while the test is running. 

Examples: 

  • Order ID
  • Booking reference
  • Invoice number
  • Ticket number
  • Customer ID generated by the system

 
test.describe.serial('Full order flow', () => { 

 let orderId: string;  

  test('place order', async ({ page }) => {  
  const loginPage = new LoginPage(page);    
  const dashboardPage = new DashboardPage(page);    
  const ordersPage = new OrdersPage(page);    
  await loginPage.validLogin( users.maya.username, users.maya.password     );  
  await dashboardPage.searchProductAddCart(products.denimJacket); 
  ordered = await ordersPage.placeOrderAndReturnOrderId();  
  });  

  test('verify order', async ({ page }) => {
  const ordersPage = new OrdersPage(page);     
  await ordersPage.verifyOrderIsDisplayed(orderId);   
   }); 

}); 

 
What problem does this solve? 

The test does not know the orderId before the order is placed. 
Runtime data helps capture that generated orderId and reuse it later for verification, instead of hardcoding values or searching for unstable records. 

5. Seeded / Precondition Data 

Seeded data is data that must exist before the test begins. 

Examples:

  • A customer with saved address
  • A user with previous order history
  • A product already available in inventory
  • A customer with loyalty points
  • An account with a saved payment method

Instead of creating everything through the UI, you can create the required state through an API or test data service.
 
const customer = await testDataApi.createCustomer({   
type: 'premium', 
hasSavedAddress: true, 
hasPreviousOrders: true, 
});

 // customer returned by API:
  {
 email: 'premium_customer_123@example.com',
 password: 'Test@123',
 hasPreviousOrders: true
  } 

Then the UI test can start from a meaningful point: 

await loginPage.validLogin(customer.email, customer.password); 
await ordersPage.openPreviousOrders(); 
 
What problem does this solve? 

Here, the test does not create the customer through the UI. The API prepares a customer with previous orders and returns the email and password needed for login. 

6. Disposable / Isolated Data 

Disposable data is unique data created for a single test or a single worker. 

Examples 

  • A unique email address
  • A unique customer name
  • A unique order reference
  • A unique phone number

const productsToOrder = [
  products.denimJacket,
  products.runningShoes,
  products.wirelessHeadphones,
];

for (const product of productsToOrder) {
  test(`customer can place order for ${product}`, async ({ page }, testInfo) => {
    const orderNote = `order-${testInfo.parallelIndex}-${Date.now()}`;

    const loginPage = new LoginPage(page);
    const dashboardPage = new DashboardPage(page);
    const checkoutPage = new CheckoutPage(page);

    await loginPage.validLogin(
      users.maya.username,
      users.maya.password
    );
    await dashboardPage.searchProductAddCart(product);
    await checkoutPage.addOrderNote(orderNote);
    await checkoutPage.placeOrder();
    await checkoutPage.verifyOrderNote(orderNote);
  });
}
 
What problem does it solve?

When the same flow runs multiple times, each run needs its own unique data. 
The orderNote helps identify which order belongs to which test run, instead of all tests depending on the same shared value. 

7. Stateful / Cross-System Data
 

This is data whose state must be valid across multiple systems at once. 

Example

A single customer may exist in: 

  • the UI application
  • the customer service
  • the payment service
  • the email system
  • the loyalty service
  • the order service

Here, the test data is not just "the customer's email." The real question is:
 
Is this customer in the correct state across all of those systems?
 
const customer = await testDataService.createCustomer({
  status: 'ACTIVE',
  emailVerified: true,
  paymentStatus: 'VERIFIED',
  loyaltyStatus: 'ENROLLED',
  fraudCheckStatus: 'PASSED',
});
 
Usage
await loginPage.validLogin(customer.email, customer.password);
await checkoutPage.placeOrder();
await orderApi.verifyOrderCreated(customer.id);
await emailApi.verifyConfirmationEmailSent(customer.email);
await loyaltyApi.verifyPointsAdded(customer.id);
 
What problem does it solve? 
Enterprise applications are rarely one system. One UI action may affect multiple backend systems.
Stateful data helps you verify that the business flow is worked across the full ecosystem of your application, not just the screen.

 Summary

The value of this model is not the individual definitions. It is what happens when you stop treating "test data" as one undifferentiated thing and start asking which type you are dealing with. Each type then has a natural home and a natural way to be handled:

Once you can name the type of data you are working with, the next question is how your tests should actually consume it. Which calls for another post on  Data-Driven Testing patterns



Komal chowdhary profile image
Komal chowdhary
QA Specialist

I blend strategy, creativity, and a growth mindset, while engaging with the testing community

Open To
Write
Speak
Attending MoTaCon 🤝
Komal chowdhary
Link to connected post on Data-Driven Testing patterns : https://www.ministryoftesting.com/moments/data-driven-testing-patterns-to-keep-your-suite-clean-at-scale

Sign in to comment
Explore MoT
Influence, from the other side of the table image
What I learned about influence by becoming a stakeholder
MoT Software Testing Essentials Certificate image
Boost your career in software testing with the MoT Software Testing Essentials Certificate. Learn essential skills, from basic testing techniques to advanced risk analysis, crafted by industry experts.
This Week in Quality image
Debrief the week in Quality via a community radio show hosted by Simon Tomes and members of the community
Subscribe to our newsletter