Skip to main content

Parameterize tests

You can parameterize tests at two levels: per test, by generating cases from a data set, and per project, by running the same specs across different platforms and devices.

Parameterized tests

Loop over a data array and call test() once per case. Give each a unique title so the report and --grep can address them individually.

tests/login.spec.ts
import { test, expect } from '@taqwright/taqwright';

const accounts = [
{ label: 'standard user', username: 'emma@demoapp.com', password: '10203040' },
{ label: 'admin user', username: 'admin@demoapp.com', password: 'admin123' },
];

for (const account of accounts) {
test(`login as ${account.label}`, async ({ mobile }) => {
await mobile.getByLabel('Username').fill(account.username);
await mobile.getByLabel('Password').fill(account.password);
await mobile.getByLabel('Login').click();
await expect(mobile.getByLabel('View All')).toBeVisible();
});
}

forEach works the same way; use whichever reads better:

accounts.forEach(({ label, username, password }) => {
test(`login as ${label}`, async ({ mobile }) => {
// ...
});
});

Before and after hooks

Hooks declared at file scope run once around the whole file, regardless of how many cases the loop generates. To scope setup to each parameter instead, wrap each iteration in its own test.describe so its beforeEach only applies to that group:

tests/search.spec.ts
import { test, expect } from '@taqwright/taqwright';

for (const term of ['boho', 'denim', 'linen']) {
test.describe(`search "${term}"`, () => {
test.beforeEach(async ({ mobile }) => {
await mobile.getByLabel('Username').fill('emma@demoapp.com');
await mobile.getByLabel('Password').fill('10203040');
await mobile.getByLabel('Login').click();
});

test('shows results', async ({ mobile }) => {
await mobile.getByLabel('View All').click();
await mobile.getById('Search dresses...').fill(term);
await expect(mobile.getByText(term, { exact: false })).toBeVisible();
});
});
}

Custom option fixtures

To reuse a parameter across files with a default value, define an option fixture with test.extend. Extend taqwright's test and mark the fixture { option: true }:

tests/fixtures.ts
import { test as base } from '@taqwright/taqwright';

export type TestOptions = {
/** Which seeded account the tests sign in as. */
account: string;
};

export const test = base.extend<TestOptions>({
account: ['emma@demoapp.com', { option: true }],
});

export { expect } from '@taqwright/taqwright';

Consume it like any other fixture, and override it per file or per group with test.use:

tests/profile.spec.ts
import { test, expect } from './fixtures';

test.use({ account: 'admin@demoapp.com' });

test('profile shows the signed-in account', async ({ mobile, account }) => {
await expect(mobile.getByText(account)).toBeVisible();
});
Set option values with test.use, not config

The use block in taqwright.config.ts accepts only the built-in mobile options (platform, device, buildPath, …) and is not forwarded to the Playwright runner, so you cannot carry custom option values in defineConfig().projects[].use. Override option fixtures with test.use({ ... }) in the spec or describe block instead. To vary behaviour per project, branch on the project itself (next section).

Parameterize by project and platform

Taqwright's equivalent of parameterized projects is the projects[] array: declare the same specs against different platforms and devices, then branch on the active project at run time. See the projects[] examples in Configuration.

taqwright.config.ts
import { defineConfig, Platform } from '@taqwright/taqwright';

export default defineConfig({
projects: [
{
name: 'android',
use: {
platform: Platform.ANDROID,
device: { provider: 'emulator', name: 'taqwright_api34' },
buildPath: './app/DemoApp-v1.0.0.apk',
appBundleId: 'com.taqelah.demo_app',
},
},
{
name: 'ios',
use: {
platform: Platform.IOS,
device: { provider: 'emulator', name: 'iPhone 17 Pro' },
buildPath: './app/DemoApp-v1.0.0.app',
appBundleId: 'com.taqelah.demoApp',
},
},
],
});

Read the active project from testInfo to adapt a single spec to each one (the same accessor used for conditional annotations):

tests/share.spec.ts
import { test, expect, Platform } from '@taqwright/taqwright';

test('share sheet opens', async ({ mobile }, testInfo) => {
await mobile.getByLabel('Share').click();
const target =
testInfo.project.use.platform === Platform.IOS ? 'Messages' : 'Android System';
await expect(mobile.getByText(target, { exact: false })).toBeVisible();
});

Run a single project with --project (see Command line):

npx taqwright test --project ios

Environment variables and .env

Pass values in through the environment and read them with process.env, in both your config and your tests. This keeps credentials out of source control.

STAGING=1 ACCOUNT=admin@demoapp.com npx taqwright test
taqwright.config.ts
import { defineConfig, Platform } from '@taqwright/taqwright';

export default defineConfig({
projects: [
{
name: 'android',
use: {
platform: Platform.ANDROID,
device: {
provider: process.env.STAGING ? 'browserstack' : 'emulator',
name: 'taqwright_api34',
},
buildPath: './app/DemoApp-v1.0.0.apk',
appBundleId: 'com.taqelah.demo_app',
},
},
],
});

To load variables from a .env file, add dotenv and import it at the top of your config (taqwright does not bundle it):

npm i -D dotenv
taqwright.config.ts
import 'dotenv/config';
import { defineConfig } from '@taqwright/taqwright';

// process.env is now populated from .env
export default defineConfig({
// ...
});

Create tests from a CSV file

For data that lives outside your code, read and parse a file at collection time, then loop as above. Any parser works; this uses csv-parse.

tests/from-csv.spec.ts
import fs from 'node:fs';
import path from 'node:path';
import { parse } from 'csv-parse/sync';
import { test, expect } from '@taqwright/taqwright';

const records: { label: string; term: string }[] = parse(
fs.readFileSync(path.join(__dirname, 'searches.csv')),
{ columns: true, skip_empty_lines: true },
);

for (const record of records) {
test(`search ${record.label}`, async ({ mobile }) => {
await mobile.getById('Search dresses...').fill(record.term);
await expect(mobile.getByText(record.term, { exact: false })).toBeVisible();
});
}

See Writing tests for the test and fixture basics this page builds on.