Skip to content

Commit 6c63cb2

Browse files
authored
Merge branch 'main' into document-ai-skill
2 parents 1f1c874 + 2b5c99c commit 6c63cb2

File tree

5 files changed

+105
-6
lines changed

5 files changed

+105
-6
lines changed

packages/cli/e2e/__tests__/pw-test.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,20 @@ describe('pw-test', { timeout: 45000 }, () => {
5454
expect(checklyConfig.config?.checks?.playwrightChecks.length).toBe(1)
5555
expect(checklyConfig.config?.checks?.playwrightChecks[0].name).toBe('Playwright Test: --grep @TAG-B')
5656
expect(checklyConfig.config?.checks?.playwrightChecks[0].testCommand).toBe('npx playwright test --grep @TAG-B')
57+
expect(checklyConfig.config?.checks?.playwrightChecks[0].frequency).toBe(10)
58+
})
59+
60+
it('Should add a Playwright test with custom frequency', async () => {
61+
const result = await runChecklyCli({
62+
args: ['pw-test', '--create-check', '--frequency', '5', '--', `--grep`, '@TAG-B'],
63+
apiKey: config.get('apiKey'),
64+
accountId: config.get('accountId'),
65+
directory: FIXTURE_TEST_PWT_NATIVE,
66+
timeout: 120000, // 2 minutes
67+
})
68+
expect(result.status).toBe(0)
69+
const configContent = fs.readFileSync(
70+
path.join(FIXTURE_TEST_PWT_NATIVE, 'checkly.config.ts'), 'utf-8')
71+
expect(configContent).toContain('frequency: 5')
5772
})
5873
})

packages/cli/src/commands/pw-test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
findPropertyByName,
3333
reWriteChecklyConfigFile,
3434
} from '../helpers/write-config-helpers'
35+
import * as acornParser from '../helpers/recast-acorn-parser'
3536
import * as JSON5 from 'json5'
3637
import { detectPackageManager } from '../services/check-parser/package-files/package-manager'
3738
import { DEFAULT_REGION } from '../helpers/constants'
@@ -88,6 +89,12 @@ export default class PwTestCommand extends AuthCommand {
8889
description: 'Create a Checkly check from the Playwright test.',
8990
default: false,
9091
}),
92+
'frequency': Flags.integer({
93+
char: 'f',
94+
description: 'The frequency in minutes for the created check.',
95+
default: 10,
96+
options: ['1', '2', '5', '10', '15', '30', '60', '120', '180', '360', '720', '1440'],
97+
}),
9198
'stream-logs': Flags.boolean({
9299
description: 'Stream logs from the test run to the console.',
93100
default: true,
@@ -121,6 +128,7 @@ export default class PwTestCommand extends AuthCommand {
121128
'create-check': createCheck,
122129
'stream-logs': streamLogs,
123130
'include': includeFlag,
131+
'frequency': frequency,
124132
} = flags
125133
const { configDirectory, configFilenames } = splitConfigFilePath(configFilename)
126134
const pwPathFlag = this.getConfigPath(playwrightFlags)
@@ -147,6 +155,7 @@ export default class PwTestCommand extends AuthCommand {
147155
runLocation as keyof Region,
148156
privateRunLocation,
149157
dir,
158+
frequency,
150159
)
151160
if (createCheck) {
152161
this.style.actionStart('Adding check with specified options to the Checkly config file')
@@ -245,7 +254,8 @@ export default class PwTestCommand extends AuthCommand {
245254
const checkBundles = Object.values(projectBundle.data.check)
246255

247256
if (!checkBundles.length) {
248-
this.log(`Unable to find checks to run`)
257+
this.style.shortError('Unable to find checks to run.')
258+
this.style.shortInfo('Check your Playwright configuration to ensure it targets your test files.')
249259
return
250260
}
251261

@@ -297,10 +307,15 @@ export default class PwTestCommand extends AuthCommand {
297307
}, links))
298308
})
299309

310+
const noTestsFoundChecks = new Set<string>()
311+
300312
runner.on(Events.CHECK_SUCCESSFUL,
301313
(sequenceId: SequenceId, check, result, testResultId, links?: TestResultsShortLinks) => {
302314
if (result.hasFailures) {
303315
process.exitCode = 1
316+
if (noTestsFoundChecks.has(check.logicalId)) {
317+
this.style.shortInfo('Check your Playwright configuration to ensure it targets your test files.')
318+
}
304319
}
305320

306321
reporters.forEach(r => r.onCheckEnd(sequenceId, {
@@ -318,6 +333,9 @@ export default class PwTestCommand extends AuthCommand {
318333
hasFailures: true,
319334
runError: message,
320335
}))
336+
if (message.includes('No tests found')) {
337+
this.style.shortInfo('Check your Playwright configuration to ensure it targets your test files.')
338+
}
321339
process.exitCode = 1
322340
})
323341
runner.on(Events.RUN_FINISHED, () => reporters.forEach(r => r.onEnd()))
@@ -327,6 +345,10 @@ export default class PwTestCommand extends AuthCommand {
327345
})
328346
runner.on(Events.STREAM_LOGS, (check: any, sequenceId: SequenceId, logs) => {
329347
reporters.forEach(r => r.onStreamLogs(check, sequenceId, logs))
348+
const hasNoTestsFound = logs.some((log: { message: string }) => log.message?.includes('No tests found'))
349+
if (hasNoTestsFound) {
350+
noTestsFoundChecks.add(check.logicalId)
351+
}
330352
})
331353
await runner.run()
332354
}
@@ -336,6 +358,7 @@ export default class PwTestCommand extends AuthCommand {
336358
runLocation: keyof Region,
337359
privateRunLocation: string | undefined,
338360
dir: string,
361+
frequency: number = 10,
339362
): Promise<PlaywrightSlimmedProp> {
340363
const parseArgs = args.map(arg => shellQuote(arg))
341364
const input = parseArgs.join(' ') || ''
@@ -352,7 +375,7 @@ export default class PwTestCommand extends AuthCommand {
352375
name: `Playwright Test: ${input}`,
353376
testCommand,
354377
...locationConfig,
355-
frequency: 10,
378+
frequency,
356379
}
357380
}
358381

@@ -378,7 +401,7 @@ export default class PwTestCommand extends AuthCommand {
378401
this.style.actionSuccess()
379402
return
380403
}
381-
const checklyAst = recast.parse(configFile.checklyConfig)
404+
const checklyAst = recast.parse(configFile.checklyConfig, { parser: acornParser })
382405
const checksAst = findPropertyByName(checklyAst, 'checks')
383406
if (!checksAst) {
384407
this.style.longError('Unable to automatically sync your config file.', 'This can happen if your Checkly config is '
@@ -395,7 +418,7 @@ export default class PwTestCommand extends AuthCommand {
395418
)
396419

397420
const playwrightCheckString = `const playwrightCheck = ${JSON5.stringify(playwrightCheck, { space: 2 })}`
398-
const playwrightCheckAst = recast.parse(playwrightCheckString)
421+
const playwrightCheckAst = recast.parse(playwrightCheckString, { parser: acornParser })
399422
const playwrightCheckNode = playwrightCheckAst.program.body[0].declarations[0].init
400423
addOrReplaceItem(checksAst.value, playwrightPropertyNode, 'playwrightConfigPath')
401424
addItemToArray(checksAst.value, playwrightCheckNode, 'playwrightChecks')

packages/cli/src/commands/sync-playwright.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import path from 'path'
66
import { ux } from '@oclif/core'
77
import PlaywrightConfigTemplate from '../playwright/playwright-config-template'
88
import { addOrReplaceItem, findPropertyByName, reWriteChecklyConfigFile } from '../helpers/write-config-helpers'
9+
import * as acornParser from '../helpers/recast-acorn-parser'
910

1011
export default class SyncPlaywright extends BaseCommand {
1112
static hidden = false
@@ -20,7 +21,7 @@ export default class SyncPlaywright extends BaseCommand {
2021
if (!configFile) {
2122
return this.handleError('Could not find a checkly config file')
2223
}
23-
const checklyAst = recast.parse(configFile.checklyConfig)
24+
const checklyAst = recast.parse(configFile.checklyConfig, { parser: acornParser })
2425

2526
const config = await loadPlaywrightConfig()
2627
if (!config) {
@@ -35,7 +36,7 @@ export default class SyncPlaywright extends BaseCommand {
3536
}
3637

3738
const pwtConfig = new PlaywrightConfigTemplate(config).getConfigTemplate()
38-
const pwtConfigAst = findPropertyByName(recast.parse(pwtConfig), 'playwrightConfig')
39+
const pwtConfigAst = findPropertyByName(recast.parse(pwtConfig, { parser: acornParser }), 'playwrightConfig')
3940
addOrReplaceItem(checksAst.value, pwtConfigAst, 'playwrightConfig')
4041

4142
const checklyConfigData = recast.print(checklyAst, { tabWidth: 2 }).code
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, it, expect } from 'vitest'
2+
import * as recast from 'recast'
3+
import * as acornParser from '../recast-acorn-parser'
4+
import { findPropertyByName } from '../write-config-helpers'
5+
6+
const normalizeLineEndings = (str: string) => str.replace(/\r\n/g, '\n')
7+
8+
describe('recast-acorn-parser', () => {
9+
it('should parse numeric literals with underscore separators', () => {
10+
const source = 'const x = 1_000_000'
11+
const ast = recast.parse(source, { parser: acornParser })
12+
const output = recast.print(ast).code
13+
expect(normalizeLineEndings(output)).toBe(source)
14+
})
15+
16+
it('should parse config-like code with underscore-separated numbers', () => {
17+
const source = `const config = {
18+
timeout: 30_000,
19+
frequency: 1_440,
20+
}`
21+
const ast = recast.parse(source, { parser: acornParser })
22+
const prop = findPropertyByName(ast, 'timeout')
23+
expect(prop).toBeDefined()
24+
expect(normalizeLineEndings(recast.print(ast).code)).toBe(source)
25+
})
26+
27+
it('should preserve comments', () => {
28+
const source = '// a comment\nconst x = 1'
29+
const ast = recast.parse(source, { parser: acornParser })
30+
expect(normalizeLineEndings(recast.print(ast).code)).toBe(source)
31+
})
32+
33+
it('should parse module syntax', () => {
34+
const source = 'import foo from \'bar\'\nexport default foo'
35+
const ast = recast.parse(source, { parser: acornParser })
36+
expect(normalizeLineEndings(recast.print(ast).code)).toBe(source)
37+
})
38+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as acorn from 'acorn'
2+
3+
export function parse (source: string) {
4+
const comments: acorn.Comment[] = []
5+
const tokens: acorn.Token[] = []
6+
const ast = acorn.parse(source, {
7+
ecmaVersion: 'latest',
8+
sourceType: 'module',
9+
locations: true,
10+
allowImportExportEverywhere: true,
11+
allowReturnOutsideFunction: true,
12+
onComment: comments,
13+
onToken: tokens,
14+
})
15+
if (!(ast as any).comments) {
16+
(ast as any).comments = comments
17+
}
18+
if (!(ast as any).tokens) {
19+
(ast as any).tokens = tokens
20+
}
21+
return ast
22+
}

0 commit comments

Comments
 (0)