Skip to content

Playwright ​

This guide covers Playwright 1.42 or newer. You need Node.js 22.12 or newer to run test-inventory and an existing JavaScript or TypeScript test suite. The scanner reads your source without executing your tests or starting a browser. These are minimum supported versions; CI comparisons list tested versions.

Choose another framework · Use an AI client

Install ​

sh
npm install --save-dev test-inventory

Scan your suite ​

Run from the folder containing your project's package.json:

sh
npx test-inventory --framework playwright --pretty --output inventory.json

To select files explicitly, use quoted globs. This example matches the trading-web fixture used below; adjust the globs to your suite's layout:

sh
npx test-inventory "tests/**/*.spec.ts" --framework playwright --output inventory.json

The command replaces inventory.json once the new output is complete. It does not change your test files.

Check the result ​

Print the counts and diagnostics from the saved file:

sh
node -e "const i = require('./inventory.json'); console.log(i.summary); console.log(i.diagnostics)"

For matching tests, summary.fileCount and summary.testCount should be greater than zero. Review diagnostics even when the command exits with 0: errors can mean tests are missing. If the result is empty or unexpected, use troubleshooting.

The inventory describes source declarations, not execution results. Conditional skips are not proof that a run skipped the test. Loops and tables are one record by default; supported cases can be expanded with --expand-cases. Expected-to-fail tests execute and check for failure; they are not disabled tests.

Tests created by unreadable helpers can be missing without a diagnostic. A dependency hash does not prove complete coverage of runtime dependencies. Read the shared limits before using scans to make CI decisions.

Find skipped tests ​

sh
npx test-inventory --framework playwright --state skip --state fixme --fields fullName,state,condition,reason --pretty

This uses the same file selection as the first scan. If you supplied globs, --root or --framework-version, repeat those options here.

This selects source declarations marked skip or fixme; it is not a report of a test run. See query recipes for other inventory queries.

Example ​

trading-web tests the trading app's UI with page objects and fixtures. Its specs import test from a fixtures file that extends Playwright's:

Excerpt: ts
// fixtures/index.ts
export const test = base.extend<TradingFixtures>({
  signedInPage,
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
  // ...
});

export { expect } from '@playwright/test';

The scan follows that import, sees base.extend() on Playwright's test, and reads the specs that use it like any other:

Excerpt: ts
// tests/orders/order-lifecycle.spec.ts
import { test, expect } from '../../fixtures/index';

// One order moves through every status, so each step depends on the one before.
test.describe('order lifecycle', { tag: '@orders' }, () => {
  test.describe.configure({ mode: 'serial' });

  test('places a limit order for 100 NVDA', async ({ orderTicket, blotter }) => {
    // ...
  test('shows a partial fill', async ({ blotter }) => {
    await expect(blotter.status('NVDA')).toHaveText(/Partially filled \(\d+\/100\)/);
  });
sh
npx test-inventory "tests/**/*.spec.ts" --framework playwright --output inventory.json

The describe's record has the mode set by describe.configure(), and its tests inherit the mode and the tag:

json
{
  "id": "2b2853e11e8f8699",
  "relativeFilePath": "tests/orders/order-lifecycle.spec.ts",
  "lineStart": 4,
  "title": "order lifecycle",
  "isSerial": true,
  "tags": ["@orders"]
}
json
{
  "fullName": "order lifecycle > shows a partial fill",
  "suiteId": "2b2853e11e8f8699",
  "isSerial": true,
  "tags": ["@orders"]
}

Gherkin features with playwright-bdd ​

playwright-bdd generates Playwright tests from .feature files. Generate the files first, then scan them:

sh
npx bddgen
npx test-inventory ".features-gen/**/*.spec.js" --framework playwright --output inventory.json

Records point at the generated files. The generated comments identify their source feature files.

Playwright projects and declaration counts ​

trading-web runs the same tests in Chromium and WebKit. Its config defines two projects:

Excerpt: ts
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // ...
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

Scan its declarations once:

sh
npx test-inventory "tests/**/*.spec.ts" --framework playwright --pretty

The output contains one record for this test, with both project names. This is an excerpt of that record:

json
{
  "fullName": "order lifecycle > shows a partial fill",
  "projects": ["chromium", "webkit"]
}

Running the test in two browsers does not double the inventory's testCount. The inventory counts source declarations; a runner report can contain a separate result for each project. A test's projects lists the projects whose file-selection settings include it. Read project configuration for the settings the scanner can resolve.

Configuration and limits ​

The scan finds playwright.config.* in the test file's folder and its ancestors. A config elsewhere that reaches the file, such as apps/web/playwright.config.ts with testDir: '../../e2e', is not found from it. It reads file and project selection, run settings, modes and forbidOnly. Other settings, such as workers and config use, are not interpreted as test record settings.

A config chosen with the runner's --config is not inferred from the files. A config built by a call the scan cannot follow, such as getPlaywrightConfig({ startCommand: 'pnpm start' }), cannot be worked out.

For a project's grep, the test's text contains its project, file, config tag, describe and test titles and tags. Option tags are placed at the end; Playwright places a describe's tags after its title. Only a pattern depending on that order can distinguish them. A test excluded by grep remains in the inventory, with that project missing from its projects.

Calls to testInfo.setTimeout() and testInfo.slow() are not read. Nor are calls in an imported function passed to a hook by name, such as test.beforeEach(slowOnCI).

A loop that registers the same title path twice gives a duplicate-title error and notLoaded tests, as Playwright refuses the file.

States set in helper functions ​

In Playwright, a test.skip(), test.fixme(), test.fail() or test.describe.configure() call inside a plain function applies to the tests, hooks and describes that call that function by name, also through helpers that call other helpers. A function called from the file or a describe may also be imported from another file:

ts
function skipWithoutMarketData() {
  test.skip(!process.env.MARKET_DATA_URL, 'needs the market data feed');
}

test('streams AAPL quotes', async ({ page }) => {
  skipWithoutMarketData(); // state: skip, isConditional: true
});

When the file exports the function, or nothing in the file calls it by name, the scan can't tell which tests it affects, and a state-in-helper warning says the call was not applied. A function the file declares and passes by name to a hook or a test, as in test.beforeEach(skipWithoutMarketData), is read as if its code were written there.

Fixtures, snapshots and dependency hashes ​

The hash does not follow webServer commands or teardown projects. Nondefault snapshot paths from snapshotPathTemplate, snapshotDir or a matcher's pathTemplate give snapshot tests a dependency-not-followed warning at that setting.

A test's fixtures are those requested by the test, surrounding hooks, conditions such as test.skip(({ account }) => ...) and test.use() values in reachable code, including helpers taking the test object, plus the fixtures those request. Taking the fixtures object whole, an unreadable fixture function (such as a hook returned by a factory or held in a let), or a fixture from an installed package includes every fixture of the test object. An automatic fixture, or an override of built-in fixtures or options such as page, context, storageState or baseURL, enters every test's hash. base.extend(fixtures) without an object literal takes all fixtures.

A screenshot without a name, or with a runtime name, takes all files in the spec's -snapshots folder. A known name takes every matching prefix, so holdings.png also takes holdings-by-sector-chromium-linux.png. These broader dependencies can change a hash even when the changed code does not affect that test.

Exact file selection

For Playwright: the files under each project's testDir that testMatch matches and testIgnore doesn't, matched against the full path in any case, without those that .gitignore files in the test folder leave out when no testDir is set or respectGitIgnore is on.

Supported syntax ​

Playwright 1.42 and newer.

Where test comes from ​

FormExample
Named import, also renamedimport { test } from '@playwright/test', import { test as base } from '@playwright/test'
Default importimport pwTest from '@playwright/test'
Namespace importimport * as pw from '@playwright/test' then pw.test(...)
CommonJSconst { test } = require('@playwright/test'), require('@playwright/test').test(...)
Other packagesplaywright/test, @playwright/experimental-ct-react and the other component testing packages, and playwright-bdd, whose generated files import test from it
Derivedbase.extend({...}), chained .extend().extend(), mergeTests(a, b)
Kept in a variableconst later = test.skip: keeps skip, fixme, fail, only or fail.only. Conditional and logical choices, also written inline, keep only the modifiers every possible test function shares.
Local fixtures fileimport { test } from '../fixtures', or a path alias, # import or workspace package, followed through re-exports, extend chains and functions that build it, across files
Returned by a functionbrokerTest('live')('sends an order', ...), also kept in a variable, where brokerTest is declared in the file or a local file and returns a test object or describe function
Destructured membersconst { describe, beforeEach } = test, also nested, as in const { describe: { serial } } = test: each works as test.describe or test.beforeEach does. A test object built with a destructured extend is not followed

Calls ​

CallRead as
test(title, body), test(title, details, body)A test. The body may be a function passed by name.
test.only, test.skip, test.fixme, test.fail with a title and bodyA test with that state.
test.fail.onlyExpected to fail, with isOnly. Playwright 1.49 and newer.
test.skip(), test.skip(condition, description), test.skip(({ browserName }) => condition, description); the same for fixme and failAt file or describe level, a state for every test in the scope. In a test body or hook, a state for that test or the tests the hook runs for.
test.skip(description) with a string, from Playwright 1.65; the same for fixme and failThe same, with the string as the reason; an empty string applies nothing. Before 1.65 the string is only a condition, with a skip-without-body warning.
test.describe(title, callback), with details, or anonymous test.describe(callback)A describe.
test.describe.only, .skip, .fixme, .serial, .serial.only, .parallel, .parallel.onlyA describe with that state or mode.
test.describe.configure({ mode, lock })The mode of the describe or file. lock, from Playwright 1.64, adds locks to every test in it.
test.info().annotations.push({ type, description })An annotation of the test.
test.use, test.slow, test.setTimeout, test.step, test.step.skip (1.50), test.expect, test.info, test.abort (1.60), hooksKnown; not tests.
Any other chain, such as test.only.skipAn invalid-chain error: Playwright fails to load the file and runs no tests at all.

Details ​

{ tag, annotation, lock }: tag is a string or an array of strings starting with @; annotation an object or an array of { type, description }; lock a string or an array of strings, from Playwright 1.63. Tags are also read from @words in titles.

The shared syntax rules also apply.

Next ​

Documentation for test-inventory 0.10.1. Released under the MIT License.