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.
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:
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 }:
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:
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();
});
test.use, not configThe 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.
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):
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
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
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.
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.