Skip to content

fix(cli): detect @playwright/test version from lockfile#1342

Merged
sorccu merged 5 commits into
mainfrom
simo/sim-256-cli-detect-playwright-version-from-lock-files
Jun 5, 2026
Merged

fix(cli): detect @playwright/test version from lockfile#1342
sorccu merged 5 commits into
mainfrom
simo/sim-256-cli-detect-playwright-version-from-lock-files

Conversation

@sorccu
Copy link
Copy Markdown
Member

@sorccu sorccu commented Jun 5, 2026

Problem

When deploying a Playwright project, the CLI sends the detected @playwright/test version string to the Checkly cloud, which uses it to pick the matching Playwright version when running the checks. That version was read from the locally installed @playwright/test/package.json, which can drift from the project's lockfile — e.g. after switching branches without reinstalling. As a result the reported version (and so the version the cloud runs against) could differ from what CI and other developers resolve.

Change

Resolve the version string from the project's lockfile (the source of truth), scoped to the workspace member that owns the Playwright config. Falls back to the previous node_modules read when there's no usable lockfile answer (no/unsupported lockfile, or the package isn't pinned), preserving existing behavior. Warns when a package.json range no longer satisfies the resolved version (stale lockfile).

Resolution mirrors Node's upward module resolution across the full importer ancestry (config dir → workspace root), not just the nearest member + root. This matters in nested workspace layouts (e.g. a pnpm package physically nested inside another member's directory that declares no @playwright/test): Node resolves the version from the physically-enclosing member, and so do we.

A new parsePackageVersionFromLockfile on each PackageManagerDetector extracts the version for an ordered chain of candidate importers, backed by per-format parsers (lockfile-package-version.ts):

Package manager Format handling
npm / cnpm package-lock.json v2/v3 packages map; single physical node_modules-path walk (covers all ancestors). v1 → fall back
pnpm pnpm-lock.yaml importers; nearest declaring importer wins; scalar (v5) or {version} (v6/v9), peer-suffix stripped
bun bun.lock (JSONC); two-phase — every member-scoped key before the hoisted fallback (bun.lockb → fall back)
yarn classic custom line parser; per-ancestor declared-range match
yarn berry YAML descriptors, npm: protocol stripped; per-ancestor declared-range match

Adds the yaml dependency for the YAML-based formats. (Native commands like pnpm list --lockfile-only were rejected as primary: the flag is recent-pnpm-only and most PMs' equivalents read node_modules — the very drift being avoided.)

Why not snyk-nodejs-lockfile-parser

Too heavy for a CLI. These parsers do exactly one thing — return one package's version for one importer chain.

Testing

All formats verified against real generated lockfiles, including multi-member workspaces with conflicting versions and a member physically nested inside another that declares nothing. 42 unit/integration tests covering: the drift case (lockfile version wins over a differing installed version), the no-lockfile fallback, and the nested-importer resolution for npm, pnpm, and bun (the bun case is a regression guard against a naive first-hit lookup).

🤖 Generated with Claude Code

sorccu and others added 5 commits June 5, 2026 17:28
Add the ability to resolve a single package's version from a project's
lockfile, scoped to a workspace importer, for every supported package
manager. Each PackageManagerDetector gains parsePackageVersionFromLockfile,
backed by per-format parsers in lockfile-package-version.ts:

- npm/cnpm: package-lock.json v2/v3 packages map (node_modules path walk)
- pnpm: pnpm-lock.yaml importers (scalar v5 / map v6/v9, peer-suffix strip)
- bun: bun.lock (JSONC; member-name-scoped packages entries)
- yarn classic: custom line parser; yarn berry: YAML descriptors

Adds the `yaml` dependency for the YAML-based formats.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ules

The Playwright version uploaded to the cloud was read from the locally
installed @playwright/test, which can drift from the project's lockfile
(e.g. after switching branches without reinstalling). Resolve it from the
workspace lockfile instead — the version CI and other developers get —
scoped to the workspace member that owns the Playwright config.

resolvePlaywrightVersion prefers the lockfile and falls back to the old
node_modules read when no lockfile answer is available (no/unsupported
lockfile, package not pinned). Warns when a package.json range no longer
satisfies the lockfile version (stale lockfile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getPlaywrightVersionFromPackage now uses the shared PLAYWRIGHT_TEST constant
for the resolve specifier and the playwrightRange helper for the declared
range, instead of repeating the '@playwright/test' literal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lockfile version detection only checked the nearest workspace importer and
the workspace root. In a legal pnpm layout where a workspace package is
physically nested inside another member's directory and declares no
@playwright/test, Node resolves the version from the enclosing member, not
the root — so the previous logic reported the wrong version (a regression
versus the old require.resolve behavior).

Resolution now walks the full importer ancestry (config dir → workspace
root), mirroring Node's upward module resolution. The query carries an
ordered importer list and each format resolves accordingly: npm's physical
node_modules-path walk already covers ancestors; pnpm picks the nearest
declaring importer; bun probes every member-scoped key before the hoisted
fallback (two-phase); yarn tries each ancestor's declared range. The
orchestrator realpaths the config dir and workspace root, and anchors the
drift warning on the consuming package's range.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…geVersionFromLockfile

The method reads the lockfile and resolves a version across the importer
chain (mirroring Node's upward resolution), not merely parses — "resolve"
reflects that and reserves "parse" for the per-format helpers it delegates
to. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sorccu sorccu merged commit 8cbfafa into main Jun 5, 2026
8 checks passed
@sorccu sorccu deleted the simo/sim-256-cli-detect-playwright-version-from-lock-files branch June 5, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant