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