Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ability to copy playwright config into checkly config #919

Merged
merged 7 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
238 changes: 192 additions & 46 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@
"prompts": "2.4.2",
"proxy-from-env": "1.1.0",
"tunnel": "0.0.6",
"uuid": "9.0.0"
"uuid": "9.0.0",
"handlebars": "4.7.8",
"recast": "0.23.4"
},
"devDependencies": {
"@types/config": "3.3.3",
Expand Down
84 changes: 84 additions & 0 deletions packages/cli/src/commands/sync-playwright.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { BaseCommand } from './baseCommand'
import * as recast from 'recast'
import playwrightConfigTemplate from '../playwright/playwright-template'
import { getChecklyConfigFile } from '../services/checkly-config-loader'
import { loadPlaywrightConfig } from '../playwright/playwright-config-loader'
import { parse } from '../services/handlebars-helpers'
import * as Handlebars from 'handlebars'
import fs from 'fs'
import path from 'path'
import { ux } from '@oclif/core'

export default class SyncPlaywright extends BaseCommand {
static hidden = true
static description = 'Sync playwright config'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Playwright and Checkly should be capitalized in the user facing messages.

Maybe we can make the help description more specific too in case users aren't familiar with the command. Maybe something like this?

Suggested change
static description = 'Sync playwright config'
static description = 'Copy Playwright config into the Checkly config file'


async run (): Promise<void> {
ux.action.start('Sync playwright config with checkly config', undefined, { stdout: true })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ux.action.start('Sync playwright config with checkly config', undefined, { stdout: true })
ux.action.start('Syncing Playwright config to the Checkly config file', undefined, { stdout: true })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clample Syncing or Synching? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think Syncing is good. Apparently some British folks use Synching though. https://english.stackexchange.com/questions/681/synced-or-synched


const config = await loadPlaywrightConfig()
if (!config) {
return this.handleError('Could not find any playwright.config file.')
}

Handlebars.registerHelper('parse', parse)
const pwtConfig = Handlebars.compile(playwrightConfigTemplate)(config)
const configFile = getChecklyConfigFile()
if (!configFile) {
return this.handleError('Could not find a checkly config file')
}

const checklyAst = recast.parse(configFile.checklyConfig)
const checksAst = this.findPropertyByName(checklyAst, 'checks')
if (!checksAst) {
return this.handleError('Could not parse your checkly config file')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that users will assume that either their config is invalid or the sync-playwright command is broken, and get confused / try to contact support.

I think that we could make it more clear that this can happen in some cases and there's no need to worry. For example, maybe they're importing the config from another file or using a helper function. Maybe something like this?

Suggested change
return this.handleError('Could not parse your checkly config file')
return this.handleError('Unable to automatically sync your config file. This can happen if your Checkly config is built using helper functions or other JS/TS features. You can still manually set Playwright config values in your Checkly config: https://www.checklyhq.com/docs/cli/constructs-reference/#project')

}

const browserCheckAst = this.findPropertyByName(checksAst.value, 'browserChecks')
if (!browserCheckAst) {
return this.handleError('Could not parse your checkly config file')
}
const pwtConfigAst = this.findPropertyByName(recast.parse(pwtConfig), 'playwrightConfig')
this.addOrReplacePlaywrightConfig(browserCheckAst.value, pwtConfigAst)

const checklyConfigData = recast.print(checklyAst, { tabWidth: 2 }).code
const dir = path.resolve(path.dirname(configFile.fileName))
this.reWriteChecklyConfigFile(checklyConfigData, configFile.fileName, dir)

ux.action.stop('✅ ')
this.log('Successfully sync')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.log('Successfully sync')
this.log('Successfully updated Checkly config file')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clample "...updated the Checkly config file"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, thanks 😅

this.exit(0)
}

private handleError (message: string) {
ux.action.stop('❌')
this.log(message)
this.exit(1)
}

private findPropertyByName (ast: any, name: string): recast.types.namedTypes.Property | undefined {
let node
recast.visit(ast, {
visitProperty (path: any) {
if (path.node.key.name === name) {
node = path.node
}
return false
},
})
return node
}

private addOrReplacePlaywrightConfig (ast: any, node: any) {
const playWrightConfig = this.findPropertyByName(ast, 'playwrightConfig')
if (playWrightConfig) {
playWrightConfig.value = node.value
} else {
ast.properties.push(node)
}
}

private reWriteChecklyConfigFile (data: string, fileName: string, dir: string) {
fs.writeFileSync(path.join(dir, fileName), data)
}
}
15 changes: 15 additions & 0 deletions packages/cli/src/playwright/playwright-config-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import path from 'path'
import { loadFile } from '../services/checkly-config-loader'

export async function loadPlaywrightConfig () {
let config
const filenames = ['playwright.config.ts', 'playwright.config.js']
for (const configFile of filenames) {
const dir = path.resolve(path.dirname(configFile))
config = await loadFile(path.join(dir, configFile))
if (config) {
break
}
}
return config
}
134 changes: 134 additions & 0 deletions packages/cli/src/playwright/playwright-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const playwrightConfigTemplate = `
const playwrightConfig = {
playwrightConfig: {
{{#timeout}}
timeout: {{.}},
{{/timeout}}
{{#use}}
use: {
{{#baseURL}}
baseURL: '{{.}}',
{{/baseURL}}
{{#colorScheme}}
colorScheme: '{{.}}',
{{/colorScheme}}
{{#geolocation}}
geolocation: {
{{#longitude}}
longitude: {{.}},
{{/longitude}}
{{#latitude}}
latitude: {{.}},
{{/latitude}}
{{#accuracy}}
accuracy: {{.}},
{{/accuracy}}
},
{{/geolocation}}
{{#locale}}
locale: '{{.}}',
{{/locale}}
{{#if permissions}}
permissions: [{{#permissions}}'{{.}}',{{/permissions}}],
{{/if}}
{{#timezoneId}}
timezoneId: '{{.}}',
{{/timezoneId}}
{{#viewport}}
viewport: {
{{#width}}
width: {{.}},
{{/width}}
{{#height}}
height: {{.}},
{{/height}}
},
{{/viewport}}
{{#deviceScaleFactor}}
deviceScaleFactor: {{.}},
{{/deviceScaleFactor}}
{{#hasTouch}}
hasTouch: {{.}},
{{/hasTouch}}
{{#isMobile}}
isMobile: {{.}},
{{/isMobile}}
{{#javaScriptEnabled}}
javaScriptEnabled: {{.}},
{{/javaScriptEnabled}}
{{#acceptDownloads}}
acceptDownloads: {{.}},
{{/acceptDownloads}}
{{#extraHTTPHeaders}}
extraHTTPHeaders: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/extraHTTPHeaders}}
{{#httpCredentials}}
httpCredentials: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/httpCredentials}}
{{#ignoreHTTPSErrors}}
ignoreHTTPSErrors: {{.}},
{{/ignoreHTTPSErrors}}
{{#offline}}
offline: {{.}},
{{/offline}}
{{#actionTimeout}}
actionTimeout: {{.}},
{{/actionTimeout}}
{{#navigationTimeout}}
navigationTimeout: {{.}},
{{/navigationTimeout}}
{{#testIdAttribute}}
testIdAttribute: '{{.}}',
{{/testIdAttribute}}
{{#launchOptions}}
launchOptions: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/launchOptions}}
{{#contextOptions}}
contextOptions: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/contextOptions}}
{{#bypassCSP}}
bypassCSP: '{{.}}',
{{/bypassCSP}}
},
{{/use}}
{{#expect}}
expect: {
{{#timeout}}
timeout: {{.}},
{{/timeout}}
{{#toHaveScreenshot}}
toHaveScreenshot: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/toHaveScreenshot}}
{{#toMatchSnapshot}}
toMatchSnapshot: {
{{#each .}}
{{@key}}: {{{parse this}}},
{{/each}}
},
{{/toMatchSnapshot}}
},
{{/expect}}
}
}
`
export default playwrightConfigTemplate
23 changes: 22 additions & 1 deletion packages/cli/src/services/checkly-config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Construct } from '../constructs/construct'
import type { Region } from '..'
import { ReporterType } from '../reporters/reporter'
import { BrowserPlaywrightDefaults } from '../constructs/browser-defaults'
import * as fs from 'fs'

export type CheckConfigDefaults = Pick<CheckProps, 'activated' | 'muted' | 'doubleCheck'
| 'shouldFail' | 'runtimeId' | 'locations' | 'tags' | 'frequency' | 'environmentVariables'
Expand Down Expand Up @@ -69,7 +70,7 @@ enum Extension {
TS = '.ts',
}

function loadFile (file: string) {
export function loadFile (file: string) {
if (!existsSync(file)) {
return Promise.resolve(null)
}
Expand All @@ -89,6 +90,26 @@ function isString (obj: any) {
return (Object.prototype.toString.call(obj) === '[object String]')
}

export function getChecklyConfigFile (): {checklyConfig: string, fileName: string} | undefined {
const filenames: string[] = ['checkly.config.ts', 'checkly.config.js', 'checkly.config.mjs']
let config
for (const configFile of filenames) {
const dir = path.resolve(path.dirname(configFile))
if (!existsSync(path.resolve(dir, configFile))) {
continue
}
const file = fs.readFileSync(path.resolve(dir, configFile))
if (file) {
config = {
checklyConfig: file.toString(),
fileName: configFile,
}
break
}
}
return config
}

export async function loadChecklyConfig (dir: string, filenames = ['checkly.config.ts', 'checkly.config.js', 'checkly.config.mjs']): Promise<{ config: ChecklyConfig, constructs: Construct[] }> {
let config
Session.loadingChecklyConfigFile = true
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/services/handlebars-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Handlebars do not handle properly nested objects/arrays
export function parse (input: any): any {
if (typeof input === 'string') {
return `'${input}'`
}
if (typeof input === 'number' || typeof input === 'boolean') {
return input
}
if (Array.isArray(input)) {
let arr
for (const o of input) {
if (!arr) {
arr = parse(o)
continue
}
arr = `${arr}, ${parse(o)}`
}
return `[${arr}]`
}
if (typeof input === 'object') {
const keys = Object.keys(input)
let returnObj = ''
for (const key of keys) {
const curr = `${key}: ${parse(input[key])}`
returnObj = `${returnObj} ${curr},`
}
return `{${returnObj}}`
}
return input
}
2 changes: 2 additions & 0 deletions packages/create-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"homepage": "https://github.com/checkly/checkly-cli#readme",
"dependencies": {
"handlebars": "4.7.8",
"@oclif/core": "2.8.11",
"@oclif/plugin-help": "5.1.20",
"@oclif/plugin-plugins": "2.3.0",
Expand All @@ -50,6 +51,7 @@
"ora": "5.4.1",
"passwd-user": "3.0.0",
"prompts": "2.4.2",
"recast": "0.23.4",
"unique-names-generator": "4.7.1"
},
"devDependencies": {
Expand Down
Loading
Loading