Skip to main content

Inline config — test() and test.describe() options

Source: tests/saucedemo/inline-config.cookbook.ts

What you can pass inline

Two distinct APIs that are often confused:

CallAcceptsFor
test('name', { ... }, body)tag, annotationMetadata: filtering and reporting
test.describe('name', { ... }, callback)tag, annotationSame — applied to all tests in the suite
test.describe.configure({ ... })mode, retries, timeoutRuntime behavior for the next describe block

timeout and retries are NOT in TestDetails — they go through configure() or test.setTimeout() inside the body.

TestDetails — tag and annotation

Filtering with tags

test('single tag', { tag: '@smoke' }, async ({ page }) => {
await page.goto('/');
});

test('multiple tags', { tag: ['@smoke', '@login'] }, async ({ page }) => {
await page.goto('/');
});

test.describe('Suite tag — every test inherits', { tag: '@full' }, () => {
test('inherits @full', async ({ page }) => { /* ... */ });
});

Filter via CLI or project grep:

npx playwright test --grep "@smoke"
npx playwright test --grep "@smoke|@critical" # OR
npx playwright test --grep-invert "@slow" # exclude

This project uses tags for the @smoke / @core / @full profiles — see Test Profiles.

Annotations — extra metadata in the report

test('annotated', {
annotation: [
{ type: 'issue', description: 'https://github.com/org/repo/issues/42' },
{ type: 'owner', description: 'auth-team' },
{ type: 'env', description: 'staging' },
],
}, async ({ page }) => {
await page.goto('/');
});

Annotations appear in the HTML report as rows under the test name. Use for: issue links, ownership, environment, JIRA tickets.

test.describe.configure() — mode, retries, timeout

Must be called inside a describe, before the tests.

mode: 'parallel' | 'serial'

test.describe('Parallel suite', () => {
test.describe.configure({ mode: 'parallel' });
test('runs concurrently A', async ({ page }) => { /* ... */ });
test('runs concurrently B', async ({ page }) => { /* ... */ });
});

test.describe('Serial suite', () => {
test.describe.configure({ mode: 'serial' }); // one at a time
test('step 1', async ({ page }) => { /* ... */ });
test('step 2 — skipped if step 1 fails', async ({ page }) => { /* ... */ });
});

retries and timeout

test.describe('Flaky suite — extra retries', () => {
test.describe.configure({ retries: 5 });
// ...
});

test.describe('Slow suite — extended timeout', () => {
test.describe.configure({ timeout: 90_000 });
// ...
});

test.describe('All three together', () => {
test.describe.configure({ mode: 'serial', retries: 2, timeout: 60_000 });
// ...
});

Inside the test body — runtime control

For per-test logic that depends on runtime conditions:

test('extended timeout', async ({ page }) => {
test.setTimeout(90_000); // this test only
// ...
});

test('triple timeout', async ({ page }) => {
test.slow(); // × 3
// ...
});

test('conditional skip', async ({ page, browserName }) => {
test.skip(browserName === 'firefox', 'Known issue on Firefox');
// ...
});

test('expected to fail', async ({ page }) => {
test.fail(); // passes if body throws
// ...
});

test.use() inside describe — change browser options

Not part of TestDetails — separate API.

test.describe('Mobile viewport', () => {
test.use({ viewport: { width: 390, height: 844 } });
test('login on mobile', async ({ page }) => { /* ... */ });
});

test.describe('German locale', () => {
test.use({ locale: 'de-DE', timezoneId: 'Europe/Berlin' });
test('login with German locale', async ({ page }) => { /* ... */ });
});

Cheat sheet

test('name', { ... TestDetails ... }, body)
test.describe('name', { ... TestDetails ... }, callback)
TestDetails = { tag?, annotation? }

test.describe.configure({ mode?, retries?, timeout? })
→ controls scheduling/retries/timeout for the NEXT describe block

Inside test body:
test.setTimeout(ms) test.slow()
test.skip(cond, reason) test.fail() test.fixme()

test.use({ ...BrowserContextOptions }) inside describe:
viewport, locale, timezoneId, geolocation, permissions,
storageState, baseURL, extraHTTPHeaders, colorScheme, ...