skip to content

In a Playwright project, how do `testDir`, `testMatch` and `testIgnore` decide which specs run?

level: middleimportance: should knowfreq 47%

answer

  1. Each project resolves its own file list
  2. Root, then keep, then drop
  3. Ignore wins over match
  4. Project values override the top-level ones
  5. Globs, regexes or arrays of both

basics

~20 s

Each project resolves its own file list: testDir is the root it scans, testMatch keeps files matching a glob or regex, and testIgnore drops files. Ignore beats match, and each key overrides the top-level value for that project only.

solid answer

~40 s

Every project resolves its own file list before any test runs. `testDir` sets the root that project scans, defaulting to the top-level `testDir`, which itself defaults to the config file's directory. `testMatch` keeps only files matching the pattern — the default matches `*.spec.*` and `*.test.*` files — and `testIgnore` drops files from what is left; a file caught by both is not run, because ignore wins. All three accept a glob string, a `RegExp`, or an array of either, and matching is done against the file path. Setting one on a project overrides the top-level value for that project alone, which is how a `smoke` project runs ten specs while a `regression` project in the same config runs the other four hundred.

code

typescript · 18 lines
typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  projects: [
    {
      name: 'smoke',
      testMatch: /.*\.smoke\.spec\.ts/,
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'full-admin',
      testDir: './tests/admin',
      testIgnore: '**/bulk-import.spec.ts',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

go deeper

for a junior

Know that testDir says where specs live and that files normally need a .spec or .test suffix to be picked up at all.

for a middle

Explain the three-step resolution per project — root, match, ignore — and that ignore wins when a file matches both patterns.

for a senior

Use per-project filters deliberately: carve a smoke slice or exclude one costly spec from one variant instead of branching inside tests or maintaining a second config.

for a principal

Decide the conventions — filename suffixes versus path globs, what may be excluded from a variant and who reviews it — so exclusions stay visible rather than accumulating unnoticed.

## Three keys, one file list per project Before Playwright runs anything, it computes a file list **separately for each project**. Three keys drive it, and each may appear at the config root, on a project, or both: - **`testDir`** — the directory scanned for spec files. A project's value overrides the top-level one; the top-level default is the directory holding the config file. - **`testMatch`** — only files matching this pattern are treated as test files. The default matches the usual `*.spec.ts` / `*.test.ts` shapes and their `js`, `mjs`, `cjs` and `tsx` variants. - **`testIgnore`** — files matching this pattern are dropped from the list. All three accept a glob string, a `RegExp`, or an array mixing them, and patterns are matched against the file's path — so `'**/admin/**'` and `/.*\.smoke\.spec\.ts/` are both valid. ## The order the rules apply in 1. Start from the project's `testDir` (or the inherited top-level one). 2. Keep the files under it that match `testMatch`. 3. Remove from that set anything matching `testIgnore`. **Ignore beats match.** A file caught by both patterns does not run — there is no "explicitly matched, so it wins" rule, which is what people usually assume when a file mysteriously disappears from a project. Two more consequences worth internalising: - A project whose filters match nothing simply contributes no tests. The run does not fail for that reason alone, so a typo in a glob shows up as a suspiciously fast green run. - Filters are resolved per project, so the same file can be in one project's list and out of another's — that asymmetry is the whole point of putting them on the project. ## What this buys you in a matrix On an internal admin console, the same config can carry: - a `smoke` project matching `/.*\.smoke\.spec\.ts/`, run on every push; - a `full-admin` project rooted at `./tests/admin` that ignores one long-running import spec; - a browser variant that runs everything, differing only in `use`. Without per-project filters, the alternatives are worse: a second config file (a separate run, a separate report), or tag filtering by title on the command line, which is invisible in the config and easy to get wrong. ## Config root versus project | Key | At the config root | On a project | |---|---|---| | `testDir` | The default root for every project | The root for that project only | | `testMatch` | The default file pattern | Replaces the default for that project | | `testIgnore` | Applies to every project | Adds an exclusion for that project only | Note the word **replaces**: a project's `testMatch` does not intersect with the top-level one, it takes its place for that project. If a project narrows `testMatch` to smoke specs, it no longer picks up ordinary specs even though the root pattern would have. ## Diagnosing an empty or surprising project 1. Run `npx playwright test --list` and read the project prefixes — it prints exactly what each project resolved to. 2. Check whether the project sets `testDir`: a relative path is resolved against the config file's directory, not the shell's working directory. 3. Check `testIgnore` at the root as well as on the project; a broad root-level ignore silently applies everywhere. 4. Remember the extension: a file named `checkout.ts` rather than `checkout.spec.ts` never matches the default pattern, in any project. ## Habits that keep the matrix honest - Prefer a filename convention (`*.smoke.spec.ts`) over sprawling path globs; it survives file moves. - Keep the root `testDir` broad and let projects narrow, rather than the reverse. - Give any project that filters a name that says so, so a reader of the report knows why a test is missing from it. - When a spec must not run in one variant, exclude it there with the project's `testIgnore` rather than branching inside the test.

  • A spec file matches both a project's `testMatch` and its `testIgnore` — does it run?
    No. The ignore pattern is applied after the match pattern and removes the file from the list, so an explicit match does not rescue it. If you want the file, narrow the ignore pattern rather than broadening the match.
  • How would you confirm what a project actually resolved without running the suite?
    `npx playwright test --list` prints every test that would run, prefixed with its project name, so you can see per project which files were picked up. Adding `--project=<name>` narrows the listing to the one you are debugging.

saying these in an interview costs you the question

  • Thinks testMatch on a project intersects with the root pattern
  • Believes an explicit testMatch overrides testIgnore
  • Expects a project with no matching files to fail the run
  • Resolves a project testDir against the shell working directory
  • Assumes filters are global and cannot differ per project