Skip to content

Commit 426405e

Browse files
kiroushisorccu
authored andcommitted
feat: support per-entry workingDir on playwrightChecks
A single bundled `checkly test`/`deploy` session can contain several playwrightChecks, but they share one bundle root and one working directory, so each self-contained Playwright project (its own package manager and pinned @playwright/test) needs hand-written installCommand/testCommand shell surgery, and the CLI resolves a single @playwright/test version for all of them. Add an optional per-entry `workingDir` to PlaywrightCheck and playwrightChecks config entries. When set, the bundler resolves that entry's Playwright version from the working directory and stamps a per-entry working directory (plus a working-dir-relative playwright config path) into the check payload, so one session can carry projects on different Playwright versions without command surgery. Backward compatible: when `workingDir` is omitted, every computed value is identical to today (defaults to the project directory).
1 parent 69cf8a3 commit 426405e

4 files changed

Lines changed: 77 additions & 12 deletions

File tree

packages/cli/src/constructs/playwright-check.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ export interface PlaywrightCheckProps extends Omit<RuntimeCheckProps, 'retryStra
9494
*/
9595
include?: string | string[]
9696

97+
/**
98+
* Working directory for this check's install and test commands, relative to
99+
* the project (the directory that contains your Checkly config). Defaults to
100+
* the project directory.
101+
*
102+
* Set this when a `playwrightChecks` entry points at a self-contained
103+
* sub-project with its own package manager and pinned `@playwright/test`: the
104+
* CLI then resolves that entry's Playwright version from this directory and
105+
* the runner runs its commands from here. This lets one bundled session mix
106+
* several fixtures on different Playwright versions without hand-written
107+
* `installCommand`/`testCommand` shell surgery.
108+
*
109+
* @example "playwright-native/yarn-tests"
110+
*/
111+
workingDir?: string
112+
97113
/**
98114
* Name of the check group to assign this check to.
99115
* The group must exist in your project configuration.
@@ -150,6 +166,7 @@ export class PlaywrightCheck extends RuntimeCheck {
150166
pwProjects: string[]
151167
pwTags: string[]
152168
include: string[]
169+
workingDir?: string
153170
engine?: Engine
154171
/** @deprecated Use {@link groupId} instead. Kept for compatibility with earlier versions. */
155172
groupName?: string
@@ -170,6 +187,7 @@ export class PlaywrightCheck extends RuntimeCheck {
170187
this.include = config.include
171188
? (Array.isArray(config.include) ? config.include : [config.include])
172189
: []
190+
this.workingDir = config.workingDir
173191
this.testCommand = config.testCommand
174192
this.groupName = config.groupName
175193
this.playwrightConfigPath = this.resolveContentFilePath(config.playwrightConfigPath)
@@ -491,7 +509,11 @@ export class PlaywrightCheck extends RuntimeCheck {
491509
relativePlaywrightConfigPath,
492510
workingDir,
493511
files,
494-
} = await Session.getPlaywrightProjectBundler().bundle(this.playwrightConfigPath, this.include ?? [])
512+
} = await Session.getPlaywrightProjectBundler().bundle(
513+
this.playwrightConfigPath,
514+
this.include ?? [],
515+
this.workingDir,
516+
)
495517

496518
bundler.registerFiles(...files)
497519

packages/cli/src/services/__tests__/playwright-project-bundler.spec.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,20 @@ function deferred<T> () {
2929
// Subclass that stubs the real bundling so we can count how many times it runs
3030
// and control its timing, without needing a full Session/filesystem setup.
3131
class CountingBundler extends PlaywrightProjectBundler {
32-
calls: Array<{ config: string, include: string[] }> = []
32+
calls: Array<{ config: string, include: string[], workingDir?: string }> = []
3333
#gate?: Promise<void>
3434

3535
constructor (gate?: Promise<void>) {
3636
super()
3737
this.#gate = gate
3838
}
3939

40-
protected async bundleProject (config: string, include: string[]): Promise<PlaywrightProjectBundle> {
41-
this.calls.push({ config, include })
40+
protected async bundleProject (
41+
config: string,
42+
include: string[],
43+
workingDir?: string,
44+
): Promise<PlaywrightProjectBundle> {
45+
this.calls.push({ config, include, workingDir })
4246
if (this.#gate) {
4347
await this.#gate
4448
}
@@ -96,6 +100,25 @@ describe('PlaywrightProjectBundler cache', () => {
96100

97101
expect(bundler.calls).toHaveLength(2)
98102
})
103+
104+
it('bundles separately for different working directories', async () => {
105+
const bundler = new CountingBundler()
106+
107+
await bundler.bundle('pw.config.ts', ['a/**'], 'packages/foo')
108+
await bundler.bundle('pw.config.ts', ['a/**'], 'packages/bar')
109+
110+
expect(bundler.calls).toHaveLength(2)
111+
})
112+
113+
it('threads the working directory through and reuses the cache for the same key', async () => {
114+
const bundler = new CountingBundler()
115+
116+
await bundler.bundle('pw.config.ts', ['a/**'], 'packages/foo')
117+
await bundler.bundle('pw.config.ts', ['a/**'], 'packages/foo')
118+
119+
expect(bundler.calls).toHaveLength(1)
120+
expect(bundler.calls[0].workingDir).toBe('packages/foo')
121+
})
99122
})
100123

101124
describe('getAutoIncludes()', () => {

packages/cli/src/services/checkly-config-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export type PlaywrightSlimmedProp = Pick<PlaywrightCheckProps, 'name' | 'activat
3535
| 'muted' | 'shouldFail' | 'locations' | 'tags' | 'frequency' | 'environmentVariables'
3636
| 'alertChannels' | 'privateLocations' | 'alertEscalationPolicy'
3737
| 'pwProjects' | 'pwTags' | 'installCommand' | 'testCommand' | 'group' | 'groupName' | 'runParallel'
38-
| 'engine'> & { logicalId: string, playwrightConfigPath?: string }
38+
| 'engine' | 'workingDir'> & { logicalId: string, playwrightConfigPath?: string }
3939

4040
export type ChecklyConfig = {
4141
/**

packages/cli/src/services/playwright-project-bundler.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,45 @@ export class PlaywrightProjectBundler {
3131
// config path and the serialized include patterns from colliding.
3232
#cache = new Map<string, Promise<PlaywrightProjectBundle>>()
3333

34-
async bundle (playwrightConfig: string, include: string[]): Promise<PlaywrightProjectBundle> {
35-
const cacheKey = `${playwrightConfig}\0${JSON.stringify(include)}`
34+
async bundle (playwrightConfig: string, include: string[], workingDir?: string): Promise<PlaywrightProjectBundle> {
35+
const cacheKey = `${playwrightConfig}\0${JSON.stringify(include)}\0${workingDir ?? ''}`
3636
const cached = this.#cache.get(cacheKey)
3737
if (cached !== undefined) {
3838
return await cached
3939
}
40-
const promise = this.bundleProject(playwrightConfig, include)
40+
const promise = this.bundleProject(playwrightConfig, include, workingDir)
4141
this.#cache.set(cacheKey, promise)
4242
return await promise
4343
}
4444

4545
// The actual bundling, separated from the cache wrapper above so it can be
4646
// overridden in tests. Not part of the public surface.
47-
protected async bundleProject (playwrightConfig: string, include: string[]): Promise<PlaywrightProjectBundle> {
47+
protected async bundleProject (
48+
playwrightConfig: string,
49+
include: string[],
50+
workingDir?: string,
51+
): Promise<PlaywrightProjectBundle> {
4852
const dir = path.resolve(path.dirname(playwrightConfig))
4953
const filePath = path.resolve(dir, playwrightConfig)
5054

55+
// Per-entry working directory: where this check's install/test commands run,
56+
// and where its @playwright/test version is resolved. `workingDir` is authored
57+
// relative to the project context dir. When omitted it defaults to the context
58+
// dir, so everything below collapses to the legacy single-working-dir behaviour.
59+
// Setting it lets one bundled session carry several self-contained fixtures on
60+
// different Playwright versions without hand-written install/test shell surgery.
61+
const effectiveWorkingDir = workingDir
62+
? path.resolve(Session.contextPath!, workingDir)
63+
: Session.contextPath!
64+
5165
// No need of loading everything if there is no lockfile
5266
const pwtConfig = await Session.loadFile(filePath)
5367

5468
const pwConfigParsed = new PlaywrightConfig(filePath, pwtConfig)
5569

56-
const playwrightVersion = await resolvePlaywrightVersion(dir)
70+
// Resolve the version from the working dir when set (that's where the fixture
71+
// declares/installs its own @playwright/test); otherwise from the config dir.
72+
const playwrightVersion = await resolvePlaywrightVersion(workingDir ? effectiveWorkingDir : dir)
5773

5874
const parser = Session.getPlaywrightParser()
5975
const { files, errors } = await parser.getFilesAndDependencies(pwConfigParsed)
@@ -104,8 +120,12 @@ export class PlaywrightProjectBundler {
104120
return {
105121
browsers: pwConfigParsed.getBrowsers(),
106122
playwrightVersion,
107-
relativePlaywrightConfigPath: Session.contextRelativePosixPath(filePath),
108-
workingDir: Session.relativePosixPath(Session.contextPath!),
123+
// Both relative to the effective working dir: the runner runs the test
124+
// command from `workingDir`, so the config path must resolve from there.
125+
// With no per-entry workingDir these collapse to the legacy
126+
// contextPath-relative values.
127+
relativePlaywrightConfigPath: pathToPosix(path.relative(effectiveWorkingDir, filePath)),
128+
workingDir: Session.relativePosixPath(effectiveWorkingDir),
109129
files,
110130
}
111131
}

0 commit comments

Comments
 (0)