Resource library

QA How-To

How to Fix Playwright "Error: No tests found"

Fix Playwright error no tests found by checking testDir, file patterns, filters, projects, and CI paths. Follow verified commands to restore test discovery.

18 min read | 3,208 words

TL;DR

Run npx playwright test --list from the correct project directory. If it is empty, fix testDir, filename patterns, ignore rules, or missing test registrations; if only a focused run is empty, remove path, grep, and project filters one at a time. Repeat the list check in CI or Docker before enabling the full run.

Key Takeaways

  • Use npx playwright test --list to separate discovery problems from browser or application failures.
  • Resolve testDir relative to the config file and check spec filenames against testMatch.
  • Compare unfiltered and filtered lists to locate path, grep, or project exclusions.
  • Verify that a matching file registers a real test from @playwright/test.
  • Inspect CI checkout, working directory, and Docker mounts when local discovery succeeds.
  • Keep required suites failing on zero tests instead of applying --pass-with-no-tests.

If you need to fix Playwright error no tests found, start when npx playwright test exits before opening a browser or running a test. The runner has collected zero eligible tests after applying its file search and filters. Find where candidates disappear before changing browser settings.

Error: No tests found

TL;DR

Run npx playwright test --list from the directory containing playwright.config.ts. If it lists zero tests, check the resolved testDir, the file's .spec.ts or .test.ts suffix, testMatch, testIgnore, and any project-specific overrides. If the full list contains your test but a focused command does not, remove its positional path, --grep, --grep-invert, or --project filter one at a time. Use --config=path/to/playwright.config.ts when launching from another directory. Keep a one-test discovery check in CI so an empty suite fails visibly.

What the Error Actually Means

Playwright Test discovers files, loads them, registers calls to test(...), then applies selection rules. A zero-test result can occur at any of those stages. A file outside testDir never enters the candidate set. A file named login.ts does not meet Playwright's default test-file pattern. A matching file with no registered test() calls adds no cases. A valid case can disappear after title or project filtering.

The default discovery pattern accepts JavaScript and TypeScript test files with .spec or .test before supported extensions. checkout.spec.ts, checkout.test.tsx, and checkout.spec.js are familiar examples. checkout.e2e.ts needs an explicit testMatch. The configuration's testDir is resolved relative to the config file, while command-line file arguments are regular expressions against full test file paths. Those are different mechanisms, and confusing them creates many false leads.

The message does not say whether Chromium is installed, whether a selector is correct, or whether an application server is ready. Those concerns appear only after a test is selected. If your runner lists a test and later reports a browser executable problem, use the browser executable repair guide. For a selected test that stalls, use the Playwright timeout guide.

Before changing files, capture the exact command, working directory, config path, and list output. The shortest useful diagnostic sequence is:

pwd
npx playwright test --list
npx playwright test --help

--list collects and reports cases without running their browser actions. A nonempty list proves discovery and selection for that invocation; it does not prove the app or browsers work.

Root-Cause Decision Table

Symptom Root cause Fix
A known spec exists, but the unfiltered list is empty testDir points at another directory Set testDir to the real spec directory relative to the config
Only *.e2e.ts or unsuffixed files are absent Default or custom testMatch excludes them Rename to .spec.ts or set a deliberate match pattern
Unfiltered --list works, focused invocation does not Positional file regex or grep removes the case Simplify the path argument and inspect the title filter
One browser project lists tests and another does not Project-specific directory, match, ignore, or grep Compare project settings and select the intended project
A matching file appears in search but registers no case No reachable test() call or wrong runner API Export a real Playwright Test case from the file
Ignored or generated directory vanishes from discovery testIgnore or relevant .gitignore behavior Move the spec or adjust only the exclusion that applies
Local lists cases, CI lists none Wrong working directory or files absent in the job Check out tests and use an explicit config path
Host lists cases, container lists none Container mount or WORKDIR hides config or specs Mount the project at the expected path and align working directory

1. Fix Playwright Error No Tests Found When testDir Points Elsewhere

Start with a tiny reference layout. The following playwright.config.ts and tests/health.spec.ts form a complete test project when @playwright/test is installed in the repository. Put both paths under the same project root. The test uses no application URL, so discovery and execution do not depend on a running server.

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
});
// tests/health.spec.ts
import { test, expect } from '@playwright/test';

test('arithmetic smoke', () => {
  expect(1 + 1).toBe(2);
});

Verify the file first, then collect it:

ls tests/health.spec.ts
npx playwright test --list
npx playwright test tests/health.spec.ts

The list should contain arithmetic smoke; the focused run should pass without a browser because the test requests no page fixture. If a repo stores tests in e2e, set testDir: './e2e' in the config or move the file into tests.

A config in packages/web/playwright.config.ts with testDir: './tests' scans packages/web/tests. Launching from the repository root does not transform that path into root-level tests. Check which config Playwright loads by supplying it explicitly:

npx playwright test --config=packages/web/playwright.config.ts --list

Use this command only if that config path exists in your repository. When the explicit command finds cases and the short command does not, the launch location or config selection caused the mismatch. The Playwright TypeScript framework setup shows how to keep config and test folders predictable.

2. Fix Playwright Error No Tests Found When File Names Do Not Match

Playwright's default file pattern selects *.spec.* and *.test.* files with supported JavaScript or TypeScript extensions. health.ts, health.e2e.ts, and health.spec.txt do not qualify under that default. A custom testMatch can further narrow discovery, and a custom testIgnore can remove a file that otherwise matches.

For the reference project, keep the standard suffix. If your team's convention is *.e2e.ts, replace the config with this deliberate alternative:

// playwright.config.ts, alternative for the e2e naming convention
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  testMatch: '**/*.e2e.ts',
});

Rename the reference file to tests/health.e2e.ts before checking it. Do not keep the earlier health.spec.ts and expect this alternative config to list it: testMatch selects only the new suffix. testMatch strings are glob patterns evaluated against absolute file paths, so a pattern such as **/*.e2e.ts is more portable than a guessed relative path prefix.

mv tests/health.spec.ts tests/health.e2e.ts
npx playwright test --list
npx playwright test tests/health.e2e.ts

If a team needs both conventions, make the allowed patterns explicit with an array, or use the default and standardize filenames. Avoid broad matches such as **/*.ts; they may load helpers as tests and produce unrelated collection failures. When reviewing a no-tests incident, compare the actual basename and extension, including case on Linux, to the configured pattern. A case-sensitive CI filesystem can reveal a filename mismatch hidden by local tooling.

3. Check Positional Paths, Title Grep, and Shell Quoting

A positional argument to playwright test is a regular expression matched against the full test file path. It is not a literal filesystem lookup. In particular, regex punctuation has meaning: . matches any character and [ begins a character class. Quote a pattern containing shell metacharacters so the shell does not expand it before Playwright sees it. Start broad, then narrow only after you have seen the collected path.

npx playwright test --list
npx playwright test 'health' --list
npx playwright test 'tests/health.e2e.ts' --list

These commands assume the health.e2e.ts file created in section 2. If your repository retains the standard health.spec.ts variant, substitute that basename. A common failure is passing a path from the wrong root, such as src/tests/health.spec.ts, while Playwright reports tests/health.spec.ts. Since the expression does not match, no test survives.

--grep filters the combined test title information. The reference case is called arithmetic smoke, so --grep 'smoke' should include it and --grep 'checkout' should exclude it. --grep-invert removes matching cases. Project config may also define grep rules. Confirm with the exact selection you intend to run:

npx playwright test --grep 'smoke' --list
npx playwright test --grep-invert 'checkout' --list

When the full list has tests but a filtered list is empty, do not edit testDir. Inspect the command wrapper, npm script, CI arguments, and project-level grep before touching the spec. An empty --grep result is a selection problem, even when the failure banner resembles a discovery problem.

4. Repair Project-Specific Selection

A project may override top-level testDir, testMatch, testIgnore, or grep. This is useful for separating browser, API, or setup suites, but a selected project can contain zero matching cases while the default invocation finds other projects' tests. The project name passed to --project must correspond to a configured project. Inspect the resolved project entries, then list each one rather than assuming the entire suite has the same search path.

Here is a complete config for the reference file. It replaces the alternative e2e config in section 2 and expects tests/health.spec.ts, so rename the file back before using it:

// playwright.config.ts, project-selection example
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  projects: [
    { name: 'chromium' },
    { name: 'smoke', grep: /smoke/ },
  ],
});
mv tests/health.e2e.ts tests/health.spec.ts
npx playwright test --project=chromium --list
npx playwright test --project=smoke --list
npx playwright test --project=smoke

Both project lists should show the arithmetic smoke case. Change the smoke project's grep to /checkout/ and it will legitimately list none; restore /smoke/ rather than weakening the file pattern. Review Playwright projects configuration examples when several projects intentionally target different subsets.

One subtlety is that a setup project may be a dependency of another project. Filtering a primary project does not mean every setup file should appear under that project's direct list. Keep setup tests in their own project and verify each project's collection separately.

5. Register a Real Playwright Test in the Matching File

A filename can pass discovery yet contribute no runnable Playwright cases. A helper with only exported functions is not a test. A suite callback that never invokes test() is also empty. Tests written for Jest or Vitest need their own runner unless you convert their imports and fixtures to Playwright Test. Avoid importing test from one package while executing another package's runner.

For the reference project, use a file with a top-level Playwright registration. The test callback can be synchronous for a pure calculation; browser tests normally use an async callback and request a fixture such as { page }. The important discovery signal is the test('arithmetic smoke', ...) call reached while the file loads.

// tests/health.spec.ts
import { test, expect } from '@playwright/test';

test('arithmetic smoke', () => {
  expect(1 + 1).toBe(2);
});
npx playwright test tests/health.spec.ts --list
npx playwright test tests/health.spec.ts

The first command should name the case, and the second should pass it. If importing the file throws a module error, diagnose that error directly; it is different from a successful collection of zero cases. If test generation depends on an environment variable or a data array, log or inspect that input. An empty loop over data generates no tests even though the file and imports are valid. Prefer an explicit assertion that test data exists during module setup, so the failure names the missing fixture instead of silently removing the suite.

test.skip(...) still registers a case, which the list can display as skipped. Do not treat a fully skipped suite as identical to no tests found. Likewise, test bodies that fail immediately have already passed discovery.

6. Review testIgnore and Git Ignore Rules

testIgnore excludes matching paths after discovery. A pattern added to keep fixtures or generated output out of the suite can accidentally cover a whole test directory. The path pattern is evaluated against absolute paths, so verify it with real paths. For example, **/tests/** excludes the reference test even though testDir: './tests' points there.

A narrow exclusion can leave specs visible. Use this complete alternative config if non-test assets live under tests/test-assets:

// playwright.config.ts, narrow ignore example
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  testIgnore: '**/test-assets/**',
});
npx playwright test --list
npx playwright test tests/health.spec.ts

The reference case remains listed because it is outside test-assets. Compare an empty list under your current config with a list after narrowing only the offending ignore pattern.

.gitignore needs more careful treatment. Current Playwright behavior ignores files matching .gitignore by default when neither top-level nor project-specific testDir is explicitly set. That conditional matters: adding testDir changes the situation, so do not claim .gitignore always controls collection. Check whether the file exists and is tracked in the environment first:

git status --short tests/health.spec.ts
git check-ignore -v tests/health.spec.ts
npx playwright test --list

git check-ignore can exit nonzero when the file is not ignored; that is useful evidence, not a Playwright failure. If the file is ignored, decide whether it belongs in source control or is generated intentionally. Set testDir explicitly to the authoritative test folder, and verify collection in both local and CI contexts. This avoids relying on an implicit repository-wide scan.

7. Fix CI Working Directory and Missing Checkout Files

CI jobs often run a different command from a developer's terminal. A monorepo script may execute in the root while the config lives under packages/web; a sparse checkout may omit specs; an install step may run in one directory and the test command in another. The same no-tests banner can follow any of these. Print the working directory and inspect both the config and a known spec inside the job before changing Playwright options.

The following GitHub Actions step is a diagnostic example for the reference root-level layout. It assumes an earlier actions/checkout step and dependency installation. If the project lives in a package, set working-directory and the config path to that package's real location.

- name: Check Playwright discovery
  run: |
    pwd
    ls playwright.config.ts tests/health.spec.ts
    npx playwright test --config=playwright.config.ts --list
- name: Run Playwright tests
  run: npx playwright test --config=playwright.config.ts

Verify the step by reading the list output in the CI log and confirming a positive test count before the run command begins. For a package layout, a concrete alternative is npx playwright test --config=packages/web/playwright.config.ts --list from the repository root, provided the config and specs are checked out there. The GitHub Actions for Playwright guide covers the rest of the job, including dependency and browser installation.

Do not add --pass-with-no-tests to a required CI gate. That real flag changes an empty selection into a successful exit, which can silently disable coverage. It is appropriate only for an intentionally optional subset, such as a shard or targeted run that is allowed to contain no matching cases. For the main suite, an empty list is a deployment signal worth failing on.

8. Align Docker Mounts, WORKDIR, and the Installed Package

The official Playwright image supplies browsers and system dependencies; your project still needs its Node dependencies and test files. If Docker starts in /work but the repository is mounted at /app, a short npx playwright test may load a different config or find no matching specs. A bind mount can also hide files that were copied into an image at build time. Inspect the container's visible tree before investigating browser binaries.

Here is a host command for the root-level reference project. Replace <your-playwright-version> with the exact installed @playwright/test version and use a matching official image tag. Keep the quoted image placeholder as an instruction, not a literal tag to execute.

node -p "require('./node_modules/@playwright/test/package.json').version"
docker run --rm -v "$PWD:/work" -w /work \
  'mcr.microsoft.com/playwright:v<your-playwright-version>-noble' \
  sh -lc 'pwd && ls playwright.config.ts tests/health.spec.ts && npx playwright test --list && npx playwright test tests/health.spec.ts'

This assumes the host project dependencies are visible through the bind mount. In a reproducible Docker build, copy package.json and the lockfile, run npm ci, copy the config and tests, and set WORKDIR consistently. Do not hard-code a package version from an article: the image tag and installed package should match the project's lockfile. The Docker for Playwright guide explains image and browser setup beyond discovery.

If ls fails inside the container, repair the mount or copy instructions. If --list shows the case but the run fails on a browser executable, discovery is already fixed; inspect package and image version alignment.

How to Verify the Fix

Use the same command that originally failed, then remove its filters to understand the difference. For the reference layout, first list all tests, then list the exact file, then execute that file. The expected result is a named arithmetic smoke case in both lists and a passing run. For a real suite, record the expected number of cases per project rather than accepting any nonzero total without checking coverage.

npx playwright test --list
npx playwright test tests/health.spec.ts --list
npx playwright test tests/health.spec.ts

Next, repeat the selection from the original working directory and inside the original CI or Docker environment. A local pass does not prove that a remote checkout contains the same specs. If the original command included --grep, --project, or a positional path, add those arguments back one at a time and observe which one drops the count to zero.

Finally, confirm that the corrected test actually runs. --list validates collection only. A green run of the pure reference case proves the runner can execute tests; a browser test must additionally prove browser installation and app readiness. Treat those as separate checks so a new execution failure does not obscure a successfully repaired discovery issue.

Prevent It From Coming Back

Keep spec names and test folders conventional. Put testDir in the config instead of depending on an implicit scan across a growing monorepo. Review changes to testMatch, testIgnore, project grep rules, package scripts, and CI working directories as changes to the suite's effective coverage. A one-line edit to any of them can exclude hundreds of cases without touching a spec.

Add a collection step to CI before the full run. npx playwright test --list exits with the same zero-test problem unless you opt into --pass-with-no-tests. For a large matrix, list at least one representative case under every required project, then run the suite. Preserve the collection output as a build log; it is the quickest evidence when a later refactor moves files or renames projects.

When generating test cases from data, validate that the data source is populated. When moving specs between packages, update both the config and job working directory in the same change. When narrowing a suite with grep, use a title convention that a reviewer can recognize from --list.

Interview Questions and Answers

Q: What does Error: No tests found prove?

It proves the current invocation selected zero Playwright Test cases. It does not prove that the repository has no tests. I would first compare an unfiltered --list with the failing command, then inspect file discovery and selection rules.

Q: How is testDir resolved?

It is resolved relative to the configuration file. I would inspect the config's location and the test tree before changing the path, especially in a monorepo with package-level configs.

Q: What is the difference between testMatch and --grep?

testMatch chooses files using path patterns. --grep chooses registered tests using their title information. If an unfiltered list contains the case but a grep list does not, changing the filename pattern is the wrong fix.

Q: Why can a positional path unexpectedly exclude a file?

Playwright treats the argument as a regular expression against the full test path. A wrong directory prefix or unquoted regex character can remove the intended file. I start with a short unique basename, inspect --list, and narrow from there.

Q: Can test.skip cause this error?

A skipped test is still registered for collection, so a listed skipped case is different from zero tests. I would check the list output and reporting state before attributing the error to skips.

Q: Why might only CI show the error?

The job may start in another directory, omit test files, select a different project, or apply an extra grep. I would log pwd, verify config and spec presence, and run --list in that job before editing application code.

Common Mistakes

  • Installing browsers before verifying --list; browser setup is irrelevant until a test is selected.
  • Passing a filename as though Playwright treated it as a literal path rather than a regex filter.
  • Broadening testMatch to every TypeScript file and accidentally loading helpers as tests.
  • Removing all testIgnore rules when a single pattern is too broad.
  • Assuming .gitignore always controls discovery, regardless of explicit testDir settings.
  • Using --pass-with-no-tests on a required CI suite and reporting an empty run as green.
  • Copying a Docker image tag from an article instead of matching the installed package version.
  • Treating a successful --list as proof that browsers and the application will run.

Conclusion

To fix Playwright error no tests found, identify the stage where the candidate count reaches zero: directory, filename pattern, test registration, or final selection. Use --list with progressively narrower arguments, make one targeted correction, and rerun the original command in its original environment. Keep a CI collection check so future config and path changes cannot quietly erase the suite.

Interview Questions and Answers

How would you debug Error: No tests found in Playwright?

I run the exact failing invocation with --list, then compare it with an unfiltered --list. If both are empty, I inspect testDir, testMatch, testIgnore, the file suffix, and whether the file registers a Playwright test. If only the focused list is empty, I remove positional, grep, and project filters one at a time.

How does Playwright resolve testDir?

Playwright resolves testDir relative to the configuration file. A package-level config with testDir set to ./tests scans that package's tests folder, even if I start the command from a monorepo root. I verify the config path explicitly before editing the directory setting.

What is the distinction between testMatch and a positional CLI argument?

testMatch is a file discovery pattern configured for the suite or project. A positional CLI argument is a regular expression applied to the full test file path for that invocation. Both must permit the file before its cases can run.

How can grep remove all tests while files are discovered?

Grep filters registered cases by combined title information, so a file can load successfully while no case title matches. I compare a full --list with the same command plus --grep and inspect config-level grep rules. Renaming a file is not a direct fix for a title mismatch.

What would you check in a CI-only no-tests incident?

I would print the job working directory, verify the config and a known spec exist after checkout, and run --list with --config. Then I would compare the local and CI project, grep, and positional arguments. An incomplete checkout or wrong package directory is often more plausible than a browser issue.

When is --pass-with-no-tests appropriate?

It is suitable for an intentionally optional subset that may contain no matching cases. I would not use it on the main required suite because it converts lost coverage into a green build. The CI log should show a meaningful collected count.

How do testIgnore and .gitignore differ for Playwright discovery?

testIgnore is an explicit Playwright path exclusion. Playwright also respects .gitignore by default when neither top-level nor project-specific testDir is set, so I check the resolved configuration before blaming Git ignore rules. A tracked spec can still be excluded by testIgnore.

How do you distinguish no tests found from a browser installation failure?

I run --list first. If it reports the case, discovery succeeded, and I investigate the later execution error, including browser binaries or image version alignment. If the list is empty, installing browsers cannot add a missing case.

Frequently Asked Questions

Why does Playwright say Error: No tests found when my spec file exists?

A file can exist outside the configured testDir or fail testMatch. It can also be removed by testIgnore, a project-specific rule, or the command-line path filter. Compare the unfiltered --list output with the focused command.

What file names does Playwright Test discover by default?

The default pattern includes supported JavaScript and TypeScript files whose names end in .spec or .test before the extension. A name such as checkout.e2e.ts needs a custom testMatch or a rename to checkout.spec.ts.

How do I check tests without running a browser?

Run npx playwright test --list with the same config and filters as the failing command. It collects and reports cases without executing browser steps, making it the quickest way to inspect selection.

Can --grep cause No tests found?

Yes. --grep matches test title information, and an expression matching none of the registered cases leaves an empty selection. Compare with an unfiltered --list and check any grep or grepInvert in project configuration.

Should I use --pass-with-no-tests in CI?

Only when an empty subset is expected and acceptable. For a required suite, the flag hides missing coverage by turning a zero-test run into a successful exit.

Why are tests found locally but absent in Docker?

The container may have a different working directory, an incomplete bind mount, or missing copied spec files. Inspect pwd and ls inside the container, then run --list there with an explicit config path.

Does a skipped Playwright test count as a found test?

Yes. test.skip registers a test case, which collection can display as skipped. A suite of skipped cases is distinct from a command that selects no cases at all.

Related Guides