Skip to main content
Version: 2x

Data-Driven Testing

Data-driven testing runs the same scenario logic against multiple sets of input data. qavajs supports several approaches through Cucumber's native constructs and the memory system.

Scenario Outline​

Scenario Outline combined with an Examples table is the most straightforward way to repeat a scenario with different data:

Feature: Wikipedia search

Scenario Outline: Search returns correct article for <term>
Given I open '$wikipedia' url
When I type '<term>' to 'Search Input'
And I click 'Search Button'
Then I expect text of 'Article Title' to equal '<term>'

Examples:
| term |
| JavaScript |
| TypeScript |
| Node.js |

Each row in Examples produces a separate scenario. Tags can be applied to individual Examples blocks:

Scenario Outline: Login attempt
Given I open '$loginUrl' url
When I type '<username>' to 'Username Input'
And I type '<password>' to 'Password Input'
And I click 'Login Button'
Then I expect '<result>' to be visible

@smoke
Examples: valid credentials
| username | password | result |
| admin | admin123 | Dashboard |

@regression
Examples: invalid credentials
| username | password | result |
| admin | wrong | Error Message |
| unknown | pass | Error Message |

Data Tables​

Use a DataTable to pass structured data to a single step. This is useful when the number of fields varies or when the data is more readable as a table than as step parameters.

Scenario: Fill registration form
When I fill following fields:
| First Name | John |
| Last Name | Doe |
| Email | john@doe.com |
| Password | Secret1! |

In a custom step definition:

import { When, DataTable } from '@qavajs/core';

When('I fill following fields:', async function(table: DataTable) {
const rows = table.rows();
for (const [field, value] of rows) {
const resolvedValue = await this.getValue(value);
await this.playwright.page.fill(`[placeholder="${field}"]`, resolvedValue);
}
});

Memory-Driven Data​

Store complex data structures in memory and reference them in steps. This is useful for data that is computed, shared across scenarios, or loaded from an external source.

Inline with $js()​

Generate values inline using the built-in JavaScript expression evaluator:

Scenario: Create user with random email
When I save '$js("user_" + Date.now() + "@example.com")' as 'email'
And I type '$email' to 'Email Input'
And I click 'Register Button'
Then I expect text of 'Confirmation' to contain '$email'

Computed functions in Memory​

Define generator functions in your Memory class:

// memory/index.ts
export class Memory {
randomEmail = () => `test+${Date.now()}@example.com`;
randomUsername = () => `user_${Math.floor(Math.random() * 9999)}`;
futureDate = (days: number) => {
const d = new Date();
d.setDate(d.getDate() + days);
return d.toISOString().split('T')[0];
};
}

Reference them in feature files:

Scenario: Register with generated email
When I type '$randomEmail()' to 'Email Input'
And I type '$randomUsername()' to 'Username Input'

Objects and nested access​

Save a full object to memory and access its properties with dot notation:

Scenario: Use saved order data
When I save json to memory as 'order':
"""
{
"product": "qavajs T-shirt",
"quantity": 2,
"size": "M"
}
"""
And I type '$order.product' to 'Product Search'
And I select '$order.size' option from 'Size Dropdown' dropdown

Parameterized Page Objects​

Combine Scenario Outline with template locators to drive both data and element selection from the table:

Feature: Product filter

Scenario Outline: Filter products by <category>
Given I open '$shopUrl' url
When I click 'Filter By Category (<category>)'
Then I expect number of elements in 'Product Cards' collection to be above '0'

Examples:
| category |
| Electronics |
| Clothing |
| Books |
// page_object/index.ts
import { locator } from '@qavajs/steps-playwright/po';

export class App {
FilterByCategory = locator.template((cat: string) =>
`[data-filter="${cat}"]`
);
ProductCards = locator('.product-card');
}

Loading Data from Files​

For large data sets, load them from JSON or CSV files in your Memory class:

// memory/index.ts
import { readFileSync } from 'fs';
import { join } from 'path';

const users = JSON.parse(
readFileSync(join(__dirname, 'data/users.json'), 'utf-8')
);

export class Memory {
testUsers = users;
adminUser = users.find((u: any) => u.role === 'admin');
}
Scenario: Admin can access settings
When I type '$adminUser.username' to 'Username Input'
And I type '$adminUser.password' to 'Password Input'
And I click 'Login Button'
Then I expect 'Settings Link' to be visible

Choosing an Approach​

ApproachBest for
Scenario Outline + ExamplesFixed, human-readable test matrix defined upfront
DataTableStructured multi-field input within one scenario
$js() inlineOne-off computed values (timestamps, randoms)
Memory computed functionsReusable generators shared across many scenarios
Memory loaded from filesLarge data sets or data maintained by the team