Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@
"semver": "^7.8.1",
"string-width": "^8.2.1",
"tunnel": "^0.0.6",
"uuid": "^14.0.0"
"uuid": "^14.0.0",
"yaml": "^2.9.0"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
Expand Down
133 changes: 131 additions & 2 deletions packages/cli/src/services/__tests__/playwright-project-bundler.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { describe, it, expect } from 'vitest'

import { afterEach, describe, it, expect } from 'vitest'

import {
getAutoIncludes,
getPlaywrightVersionFromPackage,
PlaywrightProjectBundle,
PlaywrightProjectBundler,
resolvePlaywrightVersion,
} from '../playwright-project-bundler.js'
import { PackageManager } from '../check-parser/package-files/package-manager.js'
import { PackageManager, PNpmDetector } from '../check-parser/package-files/package-manager.js'
import { Package, Workspace } from '../check-parser/package-files/workspace.js'
import { Err, Ok } from '../check-parser/package-files/result.js'
import { Session } from '../../constructs/session.js'

// A promise we can resolve from the outside, to hold a bundle "in flight"
// while we issue concurrent calls — keeps the dedup test deterministic.
Expand Down Expand Up @@ -166,3 +173,125 @@ describe('getPlaywrightVersionFromPackage()', () => {
expect(version).toMatch(/^\d+\.\d+\.\d+/)
})
})

describe('resolvePlaywrightVersion()', () => {
afterEach(() => {
Session.reset()
})

// Builds a single-package project on disk with a pnpm lockfile pinning one
// version and an *installed* node_modules pinning a different one, then wires
// up Session as project-parser would. This reproduces a local install that
// has drifted from the lockfile (e.g. switching branches without
// reinstalling), where the lockfile must win over the stale install.
async function setupProject (lockfileVersion: string, installedVersion: string): Promise<string> {
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), 'checkly-pw-version-')),
)

await fs.writeFile(
path.join(root, 'package.json'),
JSON.stringify({
name: 'project',
version: '1.0.0',
devDependencies: { '@playwright/test': '^1.40.0' },
}),
)

await fs.writeFile(
path.join(root, 'pnpm-lock.yaml'),
`lockfileVersion: '9.0'\n`
+ `importers:\n`
+ ` .:\n`
+ ` devDependencies:\n`
+ ` '@playwright/test':\n`
+ ` specifier: ^1.40.0\n`
+ ` version: ${lockfileVersion}\n`,
)

const installedDir = path.join(root, 'node_modules', '@playwright', 'test')
await fs.mkdir(installedDir, { recursive: true })
await fs.writeFile(
path.join(installedDir, 'package.json'),
JSON.stringify({ name: '@playwright/test', version: installedVersion }),
)

const workspace = new Workspace({
root: new Package({ name: 'project', path: root }),
packages: [],
lockfile: Ok(path.join(root, 'pnpm-lock.yaml')),
configFile: Err(new Error('none')),
})

Session.packageManager = new PNpmDetector()
Session.workspace = Ok(workspace)

return root
}

it('prefers the lockfile version over the installed node_modules version', async () => {
const root = await setupProject('1.41.0', '1.40.0')
const version = await resolvePlaywrightVersion(root)
expect(version).toBe('1.41.0')
})

it('falls back to the installed package when no workspace lockfile is available', async () => {
// Session left at its default (no workspace), so the lockfile path is
// skipped and we read the installed package in the cwd.
const version = await resolvePlaywrightVersion(process.cwd())
expect(version).toMatch(/^\d+\.\d+\.\d+/)
})

it('resolves the enclosing member version for a nested non-declaring config package', async () => {
// Workspace where `other-package` (holding the Playwright config) is
// physically nested inside `some-package` and declares no @playwright/test.
// Node resolves the version from the enclosing `some-package` (1.41.0), not
// the root (1.40.0) — and so must we.
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), 'checkly-pw-nested-')),
)
const somePackage = path.join(root, 'packages', 'some-package')
const otherPackage = path.join(somePackage, 'more-packages', 'other-package')
await fs.mkdir(otherPackage, { recursive: true })

await fs.writeFile(path.join(root, 'package.json'),
JSON.stringify({ name: 'root', version: '1.0.0', devDependencies: { '@playwright/test': '1.40.0' } }))
await fs.writeFile(path.join(somePackage, 'package.json'),
JSON.stringify({ name: 'some-package', version: '1.0.0', dependencies: { '@playwright/test': '1.41.0' } }))
await fs.writeFile(path.join(otherPackage, 'package.json'),
JSON.stringify({ name: 'other-package', version: '1.0.0' }))

await fs.writeFile(
path.join(root, 'pnpm-lock.yaml'),
`lockfileVersion: '9.0'\n`
+ `importers:\n`
+ ` .:\n`
+ ` devDependencies:\n`
+ ` '@playwright/test':\n`
+ ` specifier: 1.40.0\n`
+ ` version: 1.40.0\n`
+ ` packages/some-package:\n`
+ ` dependencies:\n`
+ ` '@playwright/test':\n`
+ ` specifier: 1.41.0\n`
+ ` version: 1.41.0\n`
+ ` packages/some-package/more-packages/other-package: {}\n`,
)

const workspace = new Workspace({
root: new Package({ name: 'root', path: root }),
packages: [
new Package({ name: 'some-package', path: somePackage }),
new Package({ name: 'other-package', path: otherPackage }),
],
lockfile: Ok(path.join(root, 'pnpm-lock.yaml')),
configFile: Err(new Error('none')),
})

Session.packageManager = new PNpmDetector()
Session.workspace = Ok(workspace)

const version = await resolvePlaywrightVersion(otherPackage)
expect(version).toBe('1.41.0')
})
})
Loading
Loading