Last Good Version
1.61.0
First Bad Version
1.62.0 (current latest)
Steps to reproduce
Minimal reproduction: https://github.com/tabcat/pw-tsconfig-repro
git clone https://github.com/tabcat/pw-tsconfig-repro
cd pw-tsconfig-repro
npm install
npx playwright test
On 1.62.0 this exits 1 with Failed to resolve "extends" path "foo/tsconfig.json" followed by No tests found. Downgrade to confirm it is a regression:
npm i -D @playwright/test@1.61.0
npx playwright test # 1 passed
The layout: the spec imports a file: dependency dep (npm symlinks it into node_modules), whose tsconfig.json does "extends": "foo/tsconfig.json", where foo is a sibling package hoisted to the root node_modules and absent from dep's own node_modules.
Bisected to #41571 ("fix(tsconfig): fail loudly when extends/references can't be resolved", 86b70b6), first shipped in 1.62.0.
Expected behavior
npx playwright test collects and runs the spec, as on 1.61.0 (1 passed). tsc --showConfig -p dep-src/tsconfig.json resolves foo/tsconfig.json and exits 0: TypeScript resolves a bare-specifier extends via Node-style resolution (walking up node_modules), so Playwright's loader should too.
Actual behavior
Spec collection aborts (exit 1). The tsconfig load fires lazily at the bare require('foo') inside the symlinked dependency:
Error: Failed to load tsconfig file at .../dep-src/tsconfig.json:
Failed to resolve "extends" path "foo/tsconfig.json" referenced from .../dep-src/tsconfig.json
at dep-src/index.js:1
> 1 | require('foo')
| ^
2 | module.exports = 'dep'
at Object.<anonymous> (.../dep-src/index.js:1:1)
at Object.<anonymous> (.../x.spec.ts:2:1)
Error: No tests found
Additional context
resolveConfigFile in packages/playwright/src/transform/tsconfig-loader.ts resolves a bare-specifier extends with only two probes, the config's own dir and its immediate node_modules, and never walks up parent node_modules the way tsc/Node do. That gap was long-standing but harmless: 1.61 silently ignored the unresolved extends. #41571 turned it into a hard error, so a hoisted config package that tsc resolves fine now aborts spec collection.
The dependency is reached through a symlink (npm workspaces / file: / npm link); its realpath escapes /node_modules/, so belongsToNodeModules() treats it as user code and runs per-file tsconfig discovery on it. The failing tsconfig.json belongs to the dependency, so the consuming project cannot fix it by editing its own config. This affects any hoisted npm/yarn monorepo whose workspace packages ship a tsconfig extending a shared config package (aegir, @tsconfig/*, an internal config package).
Suggested fix: keep failing loudly for genuinely unresolvable paths (the #41571 goal), but resolve bare-specifier extends with Node-style resolution (walk up node_modules / require.resolve) before erroring, matching tsc. Alternatively, downgrade to a warning when the specifier is a bare package path.
Workaround: playwright test --tsconfig=<config>.json (single-tsconfig mode skips per-file discovery).
Environment
System:
OS: Linux 6.17 Ubuntu 24.04.4 LTS
CPU: (16) x64 AMD Ryzen 7 7840U w/ Radeon 780M Graphics
Memory: 17.72 GB / 30.66 GB
Binaries:
Node: 24.12.0
npm: 11.8.0
npmPackages:
@playwright/test: 1.62.0 => 1.62.0
Last Good Version
1.61.0
First Bad Version
1.62.0 (current latest)
Steps to reproduce
Minimal reproduction: https://github.com/tabcat/pw-tsconfig-repro
On 1.62.0 this exits 1 with
Failed to resolve "extends" path "foo/tsconfig.json"followed byNo tests found. Downgrade to confirm it is a regression:The layout: the spec imports a
file:dependencydep(npm symlinks it intonode_modules), whosetsconfig.jsondoes"extends": "foo/tsconfig.json", wherefoois a sibling package hoisted to the rootnode_modulesand absent fromdep's ownnode_modules.Bisected to #41571 ("fix(tsconfig): fail loudly when extends/references can't be resolved", 86b70b6), first shipped in 1.62.0.
Expected behavior
npx playwright testcollects and runs the spec, as on 1.61.0 (1 passed).tsc --showConfig -p dep-src/tsconfig.jsonresolvesfoo/tsconfig.jsonand exits 0: TypeScript resolves a bare-specifierextendsvia Node-style resolution (walking upnode_modules), so Playwright's loader should too.Actual behavior
Spec collection aborts (exit 1). The tsconfig load fires lazily at the bare
require('foo')inside the symlinked dependency:Additional context
resolveConfigFileinpackages/playwright/src/transform/tsconfig-loader.tsresolves a bare-specifierextendswith only two probes, the config's own dir and its immediatenode_modules, and never walks up parentnode_modulesthe way tsc/Node do. That gap was long-standing but harmless: 1.61 silently ignored the unresolvedextends. #41571 turned it into a hard error, so a hoisted config package that tsc resolves fine now aborts spec collection.The dependency is reached through a symlink (npm workspaces /
file:/npm link); its realpath escapes/node_modules/, sobelongsToNodeModules()treats it as user code and runs per-file tsconfig discovery on it. The failingtsconfig.jsonbelongs to the dependency, so the consuming project cannot fix it by editing its own config. This affects any hoisted npm/yarn monorepo whose workspace packages ship a tsconfig extending a shared config package (aegir,@tsconfig/*, an internal config package).Suggested fix: keep failing loudly for genuinely unresolvable paths (the #41571 goal), but resolve bare-specifier
extendswith Node-style resolution (walk upnode_modules/require.resolve) before erroring, matching tsc. Alternatively, downgrade to a warning when the specifier is a bare package path.Workaround:
playwright test --tsconfig=<config>.json(single-tsconfig mode skips per-file discovery).Environment