Skip to content

[Regression]: tsconfig "extends" bare specifier isn't resolved via node_modules walk-up like tsc (fatal since 1.62) #41989

Description

@tabcat

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions