Skip to content

Commit

Permalink
feat: support multistep glob pattern [sc-18837] (#940)
Browse files Browse the repository at this point in the history
* feat: support multistep glob pattern [sc-18837]

* feat: add unit tests [sc-18837]

* fix: spec [sc-18837]

* refactor: after review [sc-18837]
  • Loading branch information
maxigimenez committed Mar 12, 2024
1 parent bfc68da commit c6e8165
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 5 deletions.
1 change: 1 addition & 0 deletions packages/cli/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export default class Deploy extends AuthCommand {
repoUrl: checklyConfig.repoUrl,
checkMatch: checklyConfig.checks?.checkMatch,
browserCheckMatch: checklyConfig.checks?.browserChecks?.testMatch,
multiStepCheckMatch: checklyConfig.checks?.multiStepChecks?.testMatch,
ignoreDirectoriesMatch: checklyConfig.checks?.ignoreDirectoriesMatch,
checkDefaults: checklyConfig.checks,
browserCheckDefaults: checklyConfig.checks?.browserChecks,
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export default class Test extends AuthCommand {
repoUrl: checklyConfig.repoUrl,
checkMatch: checklyConfig.checks?.checkMatch,
browserCheckMatch: checklyConfig.checks?.browserChecks?.testMatch,
multiStepCheckMatch: checklyConfig.checks?.multiStepChecks?.testMatch,
ignoreDirectoriesMatch: checklyConfig.checks?.ignoreDirectoriesMatch,
checkDefaults: checklyConfig.checks,
browserCheckDefaults: checklyConfig.checks?.browserChecks,
Expand Down
20 changes: 16 additions & 4 deletions packages/cli/src/constructs/check-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type { Region } from '..'
import type { Frequency } from './frequency'
import type { RetryStrategy } from './retry-strategy'
import { AlertEscalation } from './alert-escalation-policy'
import { MultiStepCheck } from './multi-step-check'
import CheckTypes from '../constants'

const defaultApiCheckDefaults: ApiCheckDefaultConfig = {
headers: [],
Expand Down Expand Up @@ -189,14 +191,22 @@ export class CheckGroup extends Construct {
this.browserChecks = props.browserChecks
const fileAbsolutePath = Session.checkFileAbsolutePath!
if (props.browserChecks?.testMatch) {
this.__addChecks(fileAbsolutePath, props.browserChecks.testMatch)
this.__addChecks(fileAbsolutePath, props.browserChecks.testMatch, CheckTypes.BROWSER)
}
this.multiStepChecks = props.multiStepChecks
if (props.multiStepChecks?.testMatch) {
this.__addChecks(fileAbsolutePath, props.multiStepChecks.testMatch, CheckTypes.MULTI_STEP)
}
Session.registerConstruct(this)
this.__addSubscriptions()
this.__addPrivateLocationGroupAssignments()
}

private __addChecks (fileAbsolutePath: string, testMatch: string|string[]) {
private __addChecks (
fileAbsolutePath: string,
testMatch: string|string[],
checkType: typeof CheckTypes.BROWSER | typeof CheckTypes.MULTI_STEP,
) {
const parent = path.dirname(fileAbsolutePath)
const matched = glob.sync(testMatch, { nodir: true, cwd: parent })
for (const match of matched) {
Expand All @@ -210,7 +220,9 @@ export class CheckGroup extends Construct {
// the browserChecks props inherited from the group are applied in BrowserCheck.constructor()
}
const checkLogicalId = pathToPosix(path.relative(Session.basePath!, filepath))
const check = new BrowserCheck(checkLogicalId, props)
checkType === CheckTypes.BROWSER
? new BrowserCheck(checkLogicalId, props)
: new MultiStepCheck(checkLogicalId, props)
}
}

Expand Down Expand Up @@ -261,7 +273,7 @@ export class CheckGroup extends Construct {

public getMultiStepCheckDefaults (): CheckConfigDefaults {
return {
frequency: this.browserChecks?.frequency,
frequency: this.multiStepChecks?.frequency,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { test } from '@playwright/test'
test('browser -> check2', async () => {
// Go to https://example.com/
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { test } from '@playwright/test'
test('multistep -> check1', async () => {
// GET https://api.example.com/
})
27 changes: 26 additions & 1 deletion packages/cli/src/services/__tests__/project-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { parseProject } from '../project-parser'

const runtimes = {
2023.02: { name: '2023.02', default: false, stage: 'CURRENT', description: 'Main updates are Playwright 1.28.0, Node.js 16.x and Typescript support. We are also dropping support for Puppeteer', dependencies: { '@playwright/test': '1.28.0', '@opentelemetry/api': '1.0.4', '@opentelemetry/sdk-trace-base': '1.0.1', '@faker-js/faker': '5.5.3', aws4: '1.11.0', axios: '0.27.2', btoa: '1.2.1', chai: '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', expect: '29.3.1', 'form-data': '4.0.0', jsonwebtoken: '8.5.1', lodash: '4.17.21', mocha: '10.1.0', moment: '2.29.2', node: '16.x', otpauth: '9.0.2', playwright: '1.28.0', typescript: '4.8.4', uuid: '9.0.0' } },
2023.09: { name: '2023.09', default: true, stage: 'CURRENT', description: 'Main updates are Playwright 1.38.1 and the addition of ethers 6.7.1, prisma 5.1.1, zod 3.22.2, @t3-oss/env-nextjs 0.6.1 and @xmldom/xmldom 0.8.10. Node version is 18.', dependencies: { '@faker-js/faker': '8.0.2', '@google-cloud/local-auth': '3.0.0', '@opentelemetry/api': '1.4.1', '@opentelemetry/sdk-trace-base': '1.15.2', '@playwright/test': '1.38.1', '@t3-oss/env-nextjs': '0.6.1', '@xmldom/xmldom': '0.8.10', aws4: '1.12.0', axios: '0.27.2', btoa: '1.2.1', chai: '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', 'date-fns': '2.30.0', 'date-fns-tz': '2.0.0', dotenv: '16.3.1', ethers: '6.7.1', expect: '29.6.2', 'form-data': '4.0.0', 'gmail-api-parse-message-ts': '2.2.32', 'google-auth-library': '9.0.0', googleapis: '126.0.0', jose: '4.14.4', jsdom: '22.1.0', jsonwebtoken: '9.0.1', lodash: '4.17.21', moment: '2.29.4', otpauth: '9.1.4', playwright: '1.38.1', prisma: '5.1.1', twilio: '4.15.0', uuid: '9.0.0', ws: '8.13.0', 'xml-crypto': '4.1.0', 'xml-encryption': '3.0.2', zod: '3.22.2' } },
2023.09: { name: '2023.09', default: true, stage: 'CURRENT', description: 'Main updates are Playwright 1.38.1 and the addition of ethers 6.7.1, prisma 5.1.1, zod 3.22.2, @t3-oss/env-nextjs 0.6.1 and @xmldom/xmldom 0.8.10. Node version is 18.', dependencies: { '@faker-js/faker': '8.0.2', '@google-cloud/local-auth': '3.0.0', '@opentelemetry/api': '1.4.1', '@opentelemetry/sdk-trace-base': '1.15.2', '@playwright/test': '1.38.1', '@t3-oss/env-nextjs': '0.6.1', '@xmldom/xmldom': '0.8.10', aws4: '1.12.0', axios: '0.27.2', btoa: '1.2.1', chai: '4.3.7', 'chai-string': '1.5.0', 'crypto-js': '4.1.1', 'date-fns': '2.30.0', 'date-fns-tz': '2.0.0', dotenv: '16.3.1', ethers: '6.7.1', expect: '29.6.2', 'form-data': '4.0.0', 'gmail-api-parse-message-ts': '2.2.32', 'google-auth-library': '9.0.0', googleapis: '126.0.0', jose: '4.14.4', jsdom: '22.1.0', jsonwebtoken: '9.0.1', lodash: '4.17.21', moment: '2.29.4', otpauth: '9.1.4', playwright: '1.38.1', prisma: '5.1.1', twilio: '4.15.0', uuid: '9.0.0', ws: '8.13.0', 'xml-crypto': '4.1.0', 'xml-encryption': '3.0.2', zod: '3.22.2' }, multiStepSupport: true },
}

const privateLocationId = uuidv4()
Expand Down Expand Up @@ -194,4 +194,29 @@ describe('parseProject()', () => {
expect(e.message).toContain('Environment variable "EMPTY_FOO" from check group "check-group-1" is not allowed to be empty')
}
})

it('should parse a project with multistep & browser glob patterns', async () => {
const globProjectPath = path.join(__dirname, 'project-parser-fixtures', 'multistep-browser-glob-patterns')
const project = await parseProject({
directory: globProjectPath,
projectLogicalId: 'glob-project-id',
projectName: 'glob project',
availableRuntimes: runtimes,
checkMatch: [],
browserCheckMatch: ['**/__checks__/browser/*.spec.js'],
multiStepCheckMatch: ['**/__checks__/multistep/*.spec.js'],
checkDefaults: {
runtimeId: '2023.09',
},
})
expect(project.synthesize()).toMatchObject({
project: {
logicalId: 'glob-project-id',
},
resources: [
{ type: 'check', logicalId: '__checks__/browser/check2.spec.js' },
{ type: 'check', logicalId: '__checks__/multistep/check1.spec.js' },
],
})
})
})
9 changes: 9 additions & 0 deletions packages/cli/src/services/checkly-config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export type ChecklyConfig = {
*/
testMatch?: string | string[],
},
/**
* Multistep checks default configuration properties.
*/
multiStepChecks?: CheckConfigDefaults & {
/**
* Glob pattern where the CLI looks for Playwright test files, i.e. all `.spec.ts` files
*/
testMatch?: string | string[],
},
},
/**
* CLI default configuration properties.
Expand Down
35 changes: 35 additions & 0 deletions packages/cli/src/services/project-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ProjectParseOpts = {
repoUrl?: string,
checkMatch?: string | string[],
browserCheckMatch?: string | string[],
multiStepCheckMatch?: string | string[],
ignoreDirectoriesMatch?: string[],
checkDefaults?: CheckConfigDefaults,
browserCheckDefaults?: CheckConfigDefaults,
Expand All @@ -34,6 +35,7 @@ export async function parseProject (opts: ProjectParseOpts): Promise<Project> {
directory,
checkMatch = '**/*.check.{js,ts}',
browserCheckMatch,
multiStepCheckMatch,
projectLogicalId,
projectName,
repoUrl,
Expand All @@ -60,6 +62,7 @@ export async function parseProject (opts: ProjectParseOpts): Promise<Project> {
const ignoreDirectories = ['**/node_modules/**', '**/.git/**', ...ignoreDirectoriesMatch]
await loadAllCheckFiles(directory, checkMatch, ignoreDirectories)
await loadAllBrowserChecks(directory, browserCheckMatch, ignoreDirectories, project)
await loadAllMultiStepChecks(directory, multiStepCheckMatch, ignoreDirectories, project)

// private-location must be processed after all checks and groups are loaded.
await loadAllPrivateLocationsSlugNames(project)
Expand Down Expand Up @@ -124,6 +127,38 @@ async function loadAllBrowserChecks (
}
}

async function loadAllMultiStepChecks (
directory: string,
multiStepCheckFilePattern: string | string[] | undefined,
ignorePattern: string[],
project: Project,
): Promise<void> {
if (!multiStepCheckFilePattern) {
return
}
const checkFiles = await findFilesWithPattern(directory, multiStepCheckFilePattern, ignorePattern)
const preexistingCheckFiles = new Set<string>()
Object.values(project.data.check).forEach((check) => {
if ((check instanceof MultiStepCheck || check instanceof BrowserCheck) && check.scriptPath) {
preexistingCheckFiles.add(check.scriptPath)
}
})

for (const checkFile of checkFiles) {
const relPath = pathToPosix(path.relative(directory, checkFile))
// Don't create an additional check if the checkFile was already added to a check in loadAllCheckFiles.
if (preexistingCheckFiles.has(relPath)) {
continue
}
const multistepCheck = new MultiStepCheck(pathToPosix(relPath), {
name: path.basename(checkFile),
code: {
entrypoint: checkFile,
},
})
}
}

// TODO: create a function to process slug names for check or check-group to reduce duplicated code.
async function loadAllPrivateLocationsSlugNames (
project: Project,
Expand Down

0 comments on commit c6e8165

Please sign in to comment.