Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/tmp
/yarn.lock
node_modules
!packages/cli/src/services/__tests__/fixtures/playwright-json/**
.vscode
.checkly
.DS_Store
Expand All @@ -17,4 +18,4 @@ local
.tsbuildinfo
**/checkly-github-report.md
**/checkly-summary.md
**/e2e/__tests__/fixtures/empty-project/e2e-test-project-*
**/e2e/__tests__/fixtures/empty-project/e2e-test-project-*
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,4 @@
"optional": true
}
}
}
}
22 changes: 14 additions & 8 deletions packages/cli/src/commands/pw-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
import config from '../services/config'
import { parseProject } from '../services/project-parser'
import type { Runtime } from '../rest/runtimes'
import { Diagnostics, RuntimeCheck, Session } from '../constructs'
import { Diagnostics, PlaywrightCheck, RuntimeCheck, Session } from '../constructs'
import { Flags, ux } from '@oclif/core'

Check warning on line 16 in packages/cli/src/commands/pw-test.ts

View workflow job for this annotation

GitHub Actions / lint

'ux' is defined but never used
import { createReporters, ReporterType } from '../reporters/reporter'
import TestRunner from '../services/test-runner'
import { DEFAULT_CHECK_RUN_TIMEOUT_SECONDS, Events, SequenceId } from '../services/abstract-check-runner'
Expand All @@ -40,7 +40,6 @@
static flags = {
'location': Flags.string({
char: 'l',
default: DEFAULT_REGION,
description: 'The location to run the checks at.',
}),
'private-location': Flags.string({
Expand Down Expand Up @@ -112,7 +111,7 @@
} = await loadChecklyConfig(configDirectory, configFilenames, false)
const playwrightConfigPath = this.getConfigPath(playwrightFlags) ?? checklyConfig.checks?.playwrightConfigPath
const dir = path.dirname(playwrightConfigPath || '.')
const playwrightCheck = await PwTestCommand.createPlaywrightCheck(playwrightFlags, runLocation as keyof Region, dir)
const playwrightCheck = await PwTestCommand.createPlaywrightCheck(playwrightFlags, runLocation as keyof Region, privateRunLocation, dir)
if (createCheck) {
this.style.actionStart('Creating Checkly check from Playwright test')
await this.createPlaywrightCheck(playwrightCheck, playwrightConfigPath)
Expand All @@ -136,9 +135,6 @@
projectName: testSessionName ?? checklyConfig.projectName,
repoUrl: checklyConfig.repoUrl,
includeTestOnlyChecks: true,
checkMatch: checklyConfig.checks?.checkMatch,
ignoreDirectoriesMatch: checklyConfig.checks?.ignoreDirectoriesMatch,
checkDefaults: checklyConfig.checks,
availableRuntimes: availableRuntimes.reduce((acc, runtime) => {
acc[runtime.name] = runtime
return acc
Expand All @@ -150,6 +146,10 @@
include: checklyConfig.checks?.include,
playwrightChecks: [playwrightCheck],
checkFilter: check => {
// Skip non Playwright checks
if (!(check instanceof PlaywrightCheck)) {
return false
}
if (check instanceof RuntimeCheck) {
if (Object.keys(testEnvVars).length) {
check.environmentVariables = check.environmentVariables
Expand Down Expand Up @@ -287,7 +287,7 @@
await runner.run()
}

static async createPlaywrightCheck(args: string[], runLocation: keyof Region, dir: string): Promise<PlaywrightSlimmedProp> {
static async createPlaywrightCheck(args: string[], runLocation: keyof Region, privateRunLocation: string | undefined, dir: string): Promise<PlaywrightSlimmedProp> {
const parseArgs = args.map(arg => {
if (arg.includes(' ')) {
arg = `"${arg}"`
Expand All @@ -297,11 +297,17 @@
const input = parseArgs.join(' ') || ''
const inputLogicalId = cased(input, 'kebab-case').substring(0, 50)
const testCommand = await PwTestCommand.getTestCommand(dir, input)

// Use private location if provided, otherwise use public location (with default if neither is provided)
const locationConfig = privateRunLocation
? { privateLocations: [privateRunLocation] }
: { locations: [runLocation || DEFAULT_REGION] }

return {
logicalId: `playwright-check-${inputLogicalId}`,
name: `Playwright Test: ${input}`,
testCommand,
locations: [runLocation],
...locationConfig,
frequency: 10,
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/constructs/playwright-check-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface PlaywrightCheckBundleProps {
codeBundlePath?: string
browsers?: string[]
cacheHash?: string
playwrightVersion?: string
}

export class PlaywrightCheckBundle implements Bundle {
Expand All @@ -15,13 +16,15 @@ export class PlaywrightCheckBundle implements Bundle {
codeBundlePath?: string
browsers?: string[]
cacheHash?: string
playwrightVersion?: string

constructor (playwrightCheck: PlaywrightCheck, props: PlaywrightCheckBundleProps) {
this.playwrightCheck = playwrightCheck
this.groupId = props.groupId
this.codeBundlePath = props.codeBundlePath
this.browsers = props.browsers
this.cacheHash = props.cacheHash
this.playwrightVersion = props.playwrightVersion
}

synthesize () {
Expand All @@ -31,6 +34,7 @@ export class PlaywrightCheckBundle implements Bundle {
codeBundlePath: this.codeBundlePath,
browsers: this.browsers,
cacheHash: this.cacheHash,
playwrightVersion: this.playwrightVersion
}
}
}
6 changes: 4 additions & 2 deletions packages/cli/src/constructs/playwright-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ export class PlaywrightCheck extends RuntimeCheck {
let dir = ''
try {
const {
outputFile, browsers, relativePlaywrightConfigPath, cacheHash,
outputFile, browsers, relativePlaywrightConfigPath, cacheHash, playwrightVersion
} = await bundlePlayWrightProject(playwrightConfigPath, include)
dir = outputFile
const { data: { key } } = await PlaywrightCheck.uploadPlaywrightProject(dir)
return { key, browsers, relativePlaywrightConfigPath, cacheHash }
return { key, browsers, relativePlaywrightConfigPath, cacheHash, playwrightVersion }
} finally {
await cleanup(dir)
}
Expand Down Expand Up @@ -133,13 +133,15 @@ export class PlaywrightCheck extends RuntimeCheck {
key: codeBundlePath,
browsers,
cacheHash,
playwrightVersion,
} = await PlaywrightCheck.bundleProject(this.playwrightConfigPath, this.include ?? [])

return new PlaywrightCheckBundle(this, {
groupId,
codeBundlePath,
browsers,
cacheHash,
playwrightVersion,
})
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"@playwright/test": "1.1.1",
}
}
35 changes: 32 additions & 3 deletions packages/cli/src/services/__tests__/util.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import path from 'node:path'
import { describe, it, expect, vi, beforeEach, afterAll } from 'vitest'
import fs from 'node:fs/promises'
import fsSync from 'node:fs'

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

import { pathToPosix, isFileSync } from '../util'
import { pathToPosix, isFileSync, getPlaywrightVersion } from '../util'

describe('util', () => {
describe('pathToPosix()', () => {
Expand All @@ -24,4 +25,32 @@ describe('util', () => {
expect(isFileSync('some random string')).toBeFalsy()
})
})

describe('getPlaywrightVersion()', () => {
const fixturesDir = path.join(__dirname, '..', '__tests__', 'fixtures', 'playwright-json');
const emptyDir = path.join(__dirname, 'fixtures', 'empty');

// Create empty directory for testing the "not found" case
beforeEach(async () => {
if (!fsSync.existsSync(emptyDir)) {
await fs.mkdir(emptyDir, { recursive: true });
}
});

afterAll(async () => {
if (fsSync.existsSync(emptyDir)) {
await fs.rm(emptyDir, { recursive: true, force: true });
}
})

it('should find version using node_modules path', async () => {
const version = await getPlaywrightVersion(fixturesDir);
expect(version).toBe('1.1.1');
});

it('should return undefined if playwright is not found', async () => {
const version = await getPlaywrightVersion(emptyDir);
expect(version).toBeUndefined();
});
})
})
45 changes: 37 additions & 8 deletions packages/cli/src/services/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as fs from 'fs/promises'
import * as fsSync from 'fs'
import gitRepoInfo from 'git-repo-info'
import { parse } from 'dotenv'

// @ts-ignore
import { getProxyForUrl } from 'proxy-from-env'
import { httpOverHttp, httpsOverHttp, httpOverHttps, httpsOverHttps } from 'tunnel'
Expand All @@ -18,6 +19,7 @@ import { PlaywrightConfig } from './playwright-config'
import { access , readFile} from 'fs/promises'
import { createHash } from 'crypto';
import { Session } from '../constructs'
import semver from 'semver'

export interface GitInformation {
commitId: string
Expand Down Expand Up @@ -173,8 +175,15 @@ export function assignProxy (baseURL: string, axiosConfig: CreateAxiosDefaults)
return axiosConfig
}

export function normalizeVersion(v?: string | undefined): string | undefined {
const cleaned =
semver.valid(semver.clean(v ?? '') || '') ??
semver.coerce(v ?? '')?.version;
return cleaned && semver.valid(cleaned) ? cleaned : undefined;
}

export async function bundlePlayWrightProject (playwrightConfig: string, include: string[]):
Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: string, cacheHash: string}> {
Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: string, cacheHash: string, playwrightVersion: string | undefined}> {
const dir = path.resolve(path.dirname(playwrightConfig))
const filePath = path.resolve(dir, playwrightConfig)
const pwtConfig = await Session.loadFile(filePath)
Expand All @@ -191,9 +200,14 @@ Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: s
archive.pipe(output)

const pwConfigParsed = new PlaywrightConfig(filePath, pwtConfig)
const lockFile = await findLockFile(dir)
if (!lockFile) {
throw new Error('No lock file found')
}

const [cacheHash] = await Promise.all([
getCacheHash(dir),
const [cacheHash, playwrightVersion] = await Promise.all([
getCacheHash(lockFile),
getPlaywrightVersion(dir),
loadPlaywrightProjectFiles(dir, pwConfigParsed, include, archive)
])

Expand All @@ -203,6 +217,7 @@ Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: s
return resolve({
outputFile,
browsers: pwConfigParsed.getBrowsers(),
playwrightVersion,
relativePlaywrightConfigPath: path.relative(dir, filePath),
cacheHash
})
Expand All @@ -214,16 +229,30 @@ Promise<{outputFile: string, browsers: string[], relativePlaywrightConfigPath: s
})
}

export async function getCacheHash (dir: string): Promise<string> {
const lockFile = await findLockFile(dir)
if (!lockFile) {
throw new Error('No lock file found')
}
export async function getCacheHash (lockFile: string): Promise<string> {
const fileBuffer = await readFile(lockFile);
const hash = createHash('sha256');
hash.update(fileBuffer);
return hash.digest('hex');
}

export async function getPlaywrightVersion(projectDir: string): Promise<string | undefined> {
try {
const modulePath = path.join(projectDir, 'node_modules', '@playwright', 'test', 'package.json');
const packageJson = JSON.parse(await readFile(modulePath, 'utf-8'));
return normalizeVersion(packageJson.version);
} catch {
// If node_modules not found, fall back to checking the project's package.json
const packageJsonPath = path.join(projectDir, 'package.json');
try {
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
const version = packageJson.dependencies?.['@playwright/test'] ||
packageJson.devDependencies?.['@playwright/test'];
return normalizeVersion(version);
} catch {
return;
}
}
}

async function findLockFile(dir: string): Promise<string | null> {
Expand Down
Loading