@cypress/github-action 2.11.8 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/action.yml +1 -1
  2. package/dist/index.js +866 -866
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -5791,792 +5791,792 @@ exports.issueCommand = issueCommand;
5791
5791
  /* 104 */
5792
5792
  /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
5793
5793
 
5794
- // @ts-check
5795
- const { restoreCache, saveCache } = __webpack_require__(692)
5796
- const core = __webpack_require__(470)
5797
- const exec = __webpack_require__(986)
5798
- const io = __webpack_require__(1)
5799
- const { Octokit } = __webpack_require__(448)
5800
- const hasha = __webpack_require__(309)
5801
- const fs = __webpack_require__(747)
5802
- const os = __webpack_require__(87)
5803
- const path = __webpack_require__(622)
5804
- const quote = __webpack_require__(531)
5805
- const cliParser = __webpack_require__(324)()
5806
- const findYarnWorkspaceRoot = __webpack_require__(638)
5807
- const debug = __webpack_require__(784)('@cypress/github-action')
5808
- const { ping } = __webpack_require__(946)
5809
-
5810
- /**
5811
- * Parses input command, finds the tool and
5812
- * the runs the command.
5813
- */
5814
- const execCommand = (
5815
- fullCommand,
5816
- waitToFinish = true,
5817
- label = 'executing'
5818
- ) => {
5819
- const cwd = cypressCommandOptions.cwd
5820
-
5821
- console.log('%s command "%s"', label, fullCommand)
5822
- console.log('current working directory "%s"', cwd)
5823
-
5824
- const args = cliParser.parse(fullCommand)
5825
- debug(`parsed command: ${args.join(' ')}`)
5826
-
5827
- return io.which(args[0], true).then((toolPath) => {
5828
- debug(`found command "${toolPath}"`)
5829
- debug(`with arguments ${args.slice(1).join(' ')}`)
5830
-
5831
- const toolArguments = args.slice(1)
5832
- const argsString = toolArguments.join(' ')
5833
- debug(`running ${quote(toolPath)} ${argsString} in ${cwd}`)
5834
- debug(`waiting for the command to finish? ${waitToFinish}`)
5835
-
5836
- const promise = exec.exec(
5837
- quote(toolPath),
5838
- toolArguments,
5839
- cypressCommandOptions
5840
- )
5841
- if (waitToFinish) {
5842
- return promise
5843
- }
5844
- })
5845
- }
5846
-
5847
- const isWindows = () => os.platform() === 'win32'
5848
- const isUrl = (s) => /^https?:\/\//.test(s)
5849
-
5850
- /**
5851
- * Quote strings that contain Windows delimiters.
5852
- * https://github.com/cypress-io/github-action/issues/459
5853
- */
5854
- const quoteWindowsArgument = (value) => {
5855
- if (!/[,;=\s]/.test(value)) {
5856
- return value
5857
- }
5858
- return quote(value)
5859
- }
5860
-
5861
- /**
5862
- * Returns true if the Cypress binary installation was skipped
5863
- * via an environment variable https://on.cypress.io/installing
5864
- */
5865
- const isCypressBinarySkipped = () =>
5866
- process.env.CYPRESS_INSTALL_BINARY === '0'
5867
-
5868
- const homeDirectory = os.homedir()
5869
- const platformAndArch = `${process.platform}-${process.arch}`
5870
-
5871
- const startWorkingDirectory = process.cwd()
5872
- // seems the working directory should be absolute to work correctly
5873
- // https://github.com/cypress-io/github-action/issues/211
5874
- const workingDirectory = core.getInput('working-directory')
5875
- ? path.resolve(core.getInput('working-directory'))
5876
- : startWorkingDirectory
5877
- debug(`working directory ${workingDirectory}`)
5878
-
5879
- /**
5880
- * When running "npm install" or any other Cypress-related commands,
5881
- * use the install directory as current working directory
5882
- */
5883
- const cypressCommandOptions = {
5884
- cwd: workingDirectory
5885
- }
5886
-
5887
- const yarnFilename = path.join(
5888
- findYarnWorkspaceRoot(workingDirectory) || workingDirectory,
5889
- 'yarn.lock'
5890
- )
5891
- const packageLockFilename = path.join(
5892
- workingDirectory,
5893
- 'package-lock.json'
5894
- )
5895
-
5896
- const useYarn = () => fs.existsSync(yarnFilename)
5897
-
5898
- const lockHash = () => {
5899
- const lockFilename = useYarn() ? yarnFilename : packageLockFilename
5900
- const fileHash = hasha.fromFileSync(lockFilename)
5901
- debug(`Hash from file ${lockFilename} is ${fileHash}`)
5902
- return fileHash
5903
- }
5904
-
5905
- // enforce the same NPM cache folder across different operating systems
5906
- const NPM_CACHE_FOLDER = path.join(homeDirectory, '.npm')
5907
- const getNpmCache = () => {
5908
- const o = {}
5909
- let key = core.getInput('cache-key')
5910
- const hash = lockHash()
5911
- if (!key) {
5912
- if (useYarn()) {
5913
- key = `yarn-${platformAndArch}-${hash}`
5914
- } else {
5915
- key = `npm-${platformAndArch}-${hash}`
5916
- }
5917
- } else {
5918
- console.log('using custom cache key "%s"', key)
5919
- }
5920
-
5921
- if (useYarn()) {
5922
- o.inputPath = path.join(homeDirectory, '.cache', 'yarn')
5923
- } else {
5924
- o.inputPath = NPM_CACHE_FOLDER
5925
- }
5926
-
5927
- // use exact restore key to prevent NPM cache from growing
5928
- // https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/
5929
- o.restoreKeys = o.primaryKey = key
5930
- return o
5931
- }
5932
-
5933
- // custom Cypress binary cache folder
5934
- // see https://on.cypress.io/caching
5935
- const CYPRESS_CACHE_FOLDER =
5936
- process.env.CYPRESS_CACHE_FOLDER ||
5937
- path.join(homeDirectory, '.cache', 'Cypress')
5938
- debug(`using custom Cypress cache folder "${CYPRESS_CACHE_FOLDER}"`)
5939
-
5940
- const getCypressBinaryCache = () => {
5941
- const o = {
5942
- inputPath: CYPRESS_CACHE_FOLDER
5943
- }
5944
- const hash = lockHash()
5945
- const key = `cypress-${platformAndArch}-${hash}`
5946
-
5947
- // use only exact restore key to prevent cached folder growing in size
5948
- // https://glebbahmutov.com/blog/do-not-let-cypress-cache-snowball/
5949
- o.restoreKeys = o.primaryKey = key
5950
- return o
5951
- }
5952
-
5953
- const restoreCachedNpm = () => {
5954
- debug('trying to restore cached NPM modules')
5955
- const NPM_CACHE = getNpmCache()
5956
- return restoreCache([NPM_CACHE.inputPath], NPM_CACHE.primaryKey, [
5957
- NPM_CACHE.restoreKeys
5958
- ]).catch((e) => {
5959
- console.warn('Restoring NPM cache error: %s', e.message)
5960
- })
5961
- }
5962
-
5963
- const saveCachedNpm = () => {
5964
- debug('saving NPM modules')
5965
- const NPM_CACHE = getNpmCache()
5966
- return saveCache([NPM_CACHE.inputPath], NPM_CACHE.primaryKey).catch(
5967
- (e) => {
5968
- console.warn('Saving NPM cache error: %s', e.message)
5969
- }
5970
- )
5971
- }
5972
-
5973
- const restoreCachedCypressBinary = () => {
5974
- debug('trying to restore cached Cypress binary')
5975
- const CYPRESS_BINARY_CACHE = getCypressBinaryCache()
5976
- return restoreCache(
5977
- [CYPRESS_BINARY_CACHE.inputPath],
5978
- CYPRESS_BINARY_CACHE.primaryKey,
5979
- [CYPRESS_BINARY_CACHE.restoreKeys]
5980
- ).catch((e) => {
5981
- console.warn('Restoring Cypress cache error: %s', e.message)
5982
- })
5983
- }
5984
-
5985
- const saveCachedCypressBinary = () => {
5986
- debug('saving Cypress binary')
5987
-
5988
- if (isCypressBinarySkipped()) {
5989
- debug('Skipping Cypress cache save, binary is not installed')
5990
- return Promise.resolve()
5991
- }
5992
-
5993
- const CYPRESS_BINARY_CACHE = getCypressBinaryCache()
5994
- return saveCache(
5995
- [CYPRESS_BINARY_CACHE.inputPath],
5996
- CYPRESS_BINARY_CACHE.primaryKey
5997
- ).catch((e) => {
5998
- console.warn('Saving Cypress cache error: %s', e.message)
5999
- })
6000
- }
6001
-
6002
- const install = () => {
6003
- // prevent lots of progress messages during install
6004
- core.exportVariable('CI', '1')
6005
- core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6006
- // set NPM cache path in case the user has custom install command
6007
- core.exportVariable('npm_config_cache', NPM_CACHE_FOLDER)
6008
-
6009
- // Note: need to quote found tool to avoid Windows choking on
6010
- // npm paths with spaces like "C:\Program Files\nodejs\npm.cmd ci"
6011
- const installCommand = core.getInput('install-command')
6012
- if (installCommand) {
6013
- debug(`using custom install command "${installCommand}"`)
6014
- return execCommand(installCommand, true, 'install command')
6015
- }
6016
-
6017
- if (useYarn()) {
6018
- debug('installing NPM dependencies using Yarn')
6019
- return io.which('yarn', true).then((yarnPath) => {
6020
- debug(`yarn at "${yarnPath}"`)
6021
- return exec.exec(
6022
- quote(yarnPath),
6023
- ['--frozen-lockfile'],
6024
- cypressCommandOptions
6025
- )
6026
- })
6027
- } else {
6028
- debug('installing NPM dependencies')
6029
-
6030
- return io.which('npm', true).then((npmPath) => {
6031
- debug(`npm at "${npmPath}"`)
6032
- return exec.exec(quote(npmPath), ['ci'], cypressCommandOptions)
6033
- })
6034
- }
6035
- }
6036
-
6037
- const listCypressBinaries = () => {
6038
- debug(
6039
- `Cypress versions in the cache folder ${CYPRESS_CACHE_FOLDER}`
6040
- )
6041
-
6042
- if (isCypressBinarySkipped()) {
6043
- debug('Skipping Cypress cache list, binary is not installed')
6044
- return Promise.resolve()
6045
- }
6046
-
6047
- core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6048
- return io.which('npx', true).then((npxPath) => {
6049
- return exec.exec(
6050
- quote(npxPath),
6051
- ['cypress', 'cache', 'list'],
6052
- cypressCommandOptions
6053
- )
6054
- })
6055
- }
6056
-
6057
- const verifyCypressBinary = () => {
6058
- debug(
6059
- `Verifying Cypress using cache folder ${CYPRESS_CACHE_FOLDER}`
6060
- )
6061
- if (isCypressBinarySkipped()) {
6062
- debug('Skipping Cypress verify, binary is not installed')
6063
- return Promise.resolve()
6064
- }
6065
-
6066
- core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6067
- return io.which('npx', true).then((npxPath) => {
6068
- return exec.exec(
6069
- quote(npxPath),
6070
- ['cypress', 'verify'],
6071
- cypressCommandOptions
6072
- )
6073
- })
6074
- }
6075
-
6076
- /**
6077
- * Grabs a boolean GitHub Action parameter input and casts it.
6078
- * @param {string} name - parameter name
6079
- * @param {boolean} defaultValue - default value to use if the parameter was not specified
6080
- * @returns {boolean} converted input argument or default value
6081
- */
6082
- const getInputBool = (name, defaultValue = false) => {
6083
- const param = core.getInput(name)
6084
- if (param === 'true' || param === '1') {
6085
- return true
6086
- }
6087
- if (param === 'false' || param === '0') {
6088
- return false
6089
- }
6090
-
6091
- return defaultValue
6092
- }
6093
-
6094
- /**
6095
- * Grabs the spec input from the workflow and normalizes
6096
- * it, since sometimes it can be multiline
6097
- * @returns {string|undefined}
6098
- */
6099
- const getSpecsList = () => {
6100
- const spec = core.getInput('spec')
6101
- if (!spec) {
6102
- return
6103
- }
6104
- const specLines = spec.split('\n').join(',')
6105
- debug(`extracted spec lines into: "${specLines}"`)
6106
- return specLines
6107
- }
6108
-
6109
- const buildAppMaybe = () => {
6110
- const buildApp = core.getInput('build')
6111
- if (!buildApp) {
6112
- return
6113
- }
6114
-
6115
- debug(`building application using "${buildApp}"`)
6116
-
6117
- return execCommand(buildApp, true, 'build app')
6118
- }
6119
-
6120
- const startServersMaybe = () => {
6121
- let startCommand
6122
-
6123
- if (isWindows()) {
6124
- // allow custom Windows start command
6125
- startCommand =
6126
- core.getInput('start-windows') || core.getInput('start')
6127
- } else {
6128
- startCommand = core.getInput('start')
6129
- }
6130
- if (!startCommand) {
6131
- debug('No start command found')
6132
- return Promise.resolve()
6133
- }
6134
-
6135
- // allow commands to be separated using commas or newlines
6136
- const separateStartCommands = startCommand
6137
- .split(/,|\n/)
6138
- .map((s) => s.trim())
6139
- .filter(Boolean)
6140
- debug(
6141
- `Separated ${
6142
- separateStartCommands.length
6143
- } start commands ${separateStartCommands.join(', ')}`
6144
- )
6145
-
6146
- return separateStartCommands.map((startCommand) => {
6147
- return execCommand(
6148
- startCommand,
6149
- false,
6150
- `start server "${startCommand}`
6151
- )
6152
- })
6153
- }
6154
-
6155
- /**
6156
- * Pings give URL(s) until the timeout expires.
6157
- * @param {string} waitOn A single URL or comma-separated URLs
6158
- * @param {Number?} waitOnTimeout in seconds
6159
- */
6160
- const waitOnUrl = (waitOn, waitOnTimeout = 60) => {
6161
- console.log(
6162
- 'waiting on "%s" with timeout of %s seconds',
6163
- waitOn,
6164
- waitOnTimeout
6165
- )
6166
-
6167
- const waitTimeoutMs = waitOnTimeout * 1000
6168
-
6169
- const waitUrls = waitOn
6170
- .split(',')
6171
- .map((s) => s.trim())
6172
- .filter(Boolean)
6173
- debug(`Waiting for urls ${waitUrls.join(', ')}`)
6174
-
6175
- // run every wait promise after the previous has finished
6176
- // to avoid "noise" of debug messages
6177
- return waitUrls.reduce((prevPromise, url) => {
6178
- return prevPromise.then(() => {
6179
- debug(`Waiting for url ${url}`)
6180
- return ping(url, waitTimeoutMs)
6181
- })
6182
- }, Promise.resolve())
6183
- }
6184
-
6185
- const waitOnMaybe = () => {
6186
- const waitOn = core.getInput('wait-on')
6187
- if (!waitOn) {
6188
- return
6189
- }
6190
-
6191
- const waitOnTimeout = core.getInput('wait-on-timeout') || '60'
6192
- const timeoutSeconds = parseFloat(waitOnTimeout)
6193
-
6194
- if (isUrl(waitOn)) {
6195
- return waitOnUrl(waitOn, timeoutSeconds)
6196
- }
6197
-
6198
- console.log('Waiting using command "%s"', waitOn)
6199
- return execCommand(waitOn, true)
6200
- }
6201
-
6202
- const I = (x) => x
6203
-
6204
- /**
6205
- * Asks Cypress API if there were already builds for this commit.
6206
- * In that case increments the count to get unique parallel id.
6207
- */
6208
- const getCiBuildId = async () => {
6209
- const {
6210
- GITHUB_WORKFLOW,
6211
- GITHUB_SHA,
6212
- GITHUB_TOKEN,
6213
- GITHUB_RUN_ID,
6214
- GITHUB_REPOSITORY
6215
- } = process.env
6216
-
6217
- const [owner, repo] = GITHUB_REPOSITORY.split('/')
6218
- let branch
6219
- let buildId = `${GITHUB_WORKFLOW} - ${GITHUB_SHA}`
6220
-
6221
- if (GITHUB_TOKEN) {
6222
- debug(
6223
- `Determining build id by asking GitHub about run ${GITHUB_RUN_ID}`
6224
- )
6225
-
6226
- const client = new Octokit({
6227
- auth: GITHUB_TOKEN
6228
- })
6229
-
6230
- const resp = await client.request(
6231
- 'GET /repos/:owner/:repo/actions/runs/:run_id',
6232
- {
6233
- owner,
6234
- repo,
6235
- run_id: parseInt(GITHUB_RUN_ID)
6236
- }
6237
- )
6238
-
6239
- if (resp && resp.data && resp.data.head_branch) {
6240
- branch = resp.data.head_branch
6241
- debug(`found the branch name ${branch}`)
6242
- }
6243
-
6244
- // This will return the complete list of jobs for a run with their steps,
6245
- // this should always return data when there are jobs on the workflow.
6246
- // Every time the workflow is re-run the jobs length should stay the same
6247
- // (because the same amount of jobs were ran) but the id of them should change
6248
- // letting us, select the first id as unique id
6249
- // https://docs.github.com/en/rest/reference/actions#list-jobs-for-a-workflow-run
6250
- const runsList = await client.request(
6251
- 'GET /repos/:owner/:repo/actions/runs/:run_id/jobs',
6252
- {
6253
- owner,
6254
- repo,
6255
- run_id: parseInt(GITHUB_RUN_ID)
6256
- }
6257
- )
6258
-
6259
- if (
6260
- runsList &&
6261
- runsList.data &&
6262
- runsList.data.jobs &&
6263
- runsList.data.jobs.length
6264
- ) {
6265
- const jobId = runsList.data.jobs[0].id
6266
- debug(`fetched run list with jobId ${jobId}`)
6267
- buildId = `${GITHUB_RUN_ID}-${jobId}`
6268
- } else {
6269
- debug('could not get run list data')
6270
- }
6271
- }
6272
-
6273
- debug(`determined branch ${branch} and build id ${buildId}`)
6274
- return { branch, buildId }
6275
- }
6276
-
6277
- /**
6278
- * Forms entire command line like "npx cypress run ..."
6279
- */
6280
- const runTestsUsingCommandLine = async () => {
6281
- debug('Running Cypress tests using CLI command')
6282
- const quoteArgument = isWindows() ? quoteWindowsArgument : I
6283
-
6284
- const commandPrefix = core.getInput('command-prefix')
6285
- if (!commandPrefix) {
6286
- throw new Error('Expected command prefix')
6287
- }
6288
-
6289
- const record = getInputBool('record')
6290
- const parallel = getInputBool('parallel')
6291
- const headless = getInputBool('headless')
6292
-
6293
- // TODO using yarn to run cypress when yarn is used for install
6294
- // split potentially long command?
6295
-
6296
- let cmd = []
6297
- // we need to split the command prefix into individual arguments
6298
- // otherwise they are passed all as a single string
6299
- const parts = commandPrefix.split(' ')
6300
- cmd = cmd.concat(parts)
6301
- debug(`with concatenated command prefix: ${cmd.join(' ')}`)
6302
-
6303
- // push each CLI argument separately
6304
- cmd.push('cypress')
6305
- cmd.push('run')
6306
- if (headless) {
6307
- cmd.push('--headless')
6308
- }
6309
- if (record) {
6310
- cmd.push('--record')
6311
- }
6312
- if (parallel) {
6313
- cmd.push('--parallel')
6314
- }
6315
- const group = core.getInput('group')
6316
- if (group) {
6317
- cmd.push('--group')
6318
- cmd.push(quoteArgument(group))
6319
- }
6320
- const tag = core.getInput('tag')
6321
- if (tag) {
6322
- cmd.push('--tag')
6323
- cmd.push(quoteArgument(tag))
6324
- }
6325
- const configInput = core.getInput('config')
6326
- if (configInput) {
6327
- cmd.push('--config')
6328
- cmd.push(quoteArgument(configInput))
6329
- }
6330
- const spec = getSpecsList()
6331
- if (spec) {
6332
- cmd.push('--spec')
6333
- cmd.push(quoteArgument(spec))
6334
- }
6335
- const project = core.getInput('project')
6336
- if (project) {
6337
- cmd.push('--project')
6338
- cmd.push(quoteArgument(project))
6339
- }
6340
- const configFileInput = core.getInput('config-file')
6341
- if (configFileInput) {
6342
- cmd.push('--config-file')
6343
- cmd.push(quoteArgument(configFileInput))
6344
- }
6345
-
6346
- if (parallel || group) {
6347
- const { branch, buildId } = await getCiBuildId()
6348
- if (branch) {
6349
- core.exportVariable('GH_BRANCH', branch)
6350
- }
6351
-
6352
- cmd.push('--ci-build-id')
6353
- const ciBuildId = core.getInput('ci-build-id') || buildId
6354
- cmd.push(quoteArgument(ciBuildId))
6355
- }
6356
-
6357
- const browser = core.getInput('browser')
6358
- if (browser) {
6359
- cmd.push('--browser')
6360
- // TODO should browser be quoted?
6361
- // If it is a path, it might have spaces
6362
- cmd.push(browser)
6363
- }
6364
-
6365
- const envInput = core.getInput('env')
6366
- if (envInput) {
6367
- // TODO should env be quoted?
6368
- // If it is a JSON, it might have spaces
6369
- cmd.push('--env')
6370
- cmd.push(envInput)
6371
- }
6372
-
6373
- const quiet = getInputBool('quiet')
6374
- if (quiet) {
6375
- cmd.push('--quiet')
6376
- }
6377
-
6378
- console.log('Cypress test command: npx %s', cmd.join(' '))
6379
-
6380
- // since we have quoted arguments ourselves, do not double quote them
6381
- const opts = {
6382
- ...cypressCommandOptions,
6383
- windowsVerbatimArguments: false
6384
- }
6385
-
6386
- debug(`in working directory "${cypressCommandOptions.cwd}"`)
6387
-
6388
- const npxPath = await io.which('npx', true)
6389
- debug(`npx path: ${npxPath}`)
6390
-
6391
- return exec.exec(quote(npxPath), cmd, opts)
6392
- }
6393
-
6394
- /**
6395
- * Run Cypress tests by collecting input parameters
6396
- * and using Cypress module API to run tests.
6397
- * @see https://on.cypress.io/module-api
6398
- */
6399
- const runTests = async () => {
6400
- const runTests = getInputBool('runTests', true)
6401
- if (!runTests) {
6402
- console.log('Skipping running tests: runTests parameter is false')
6403
- return
6404
- }
6405
-
6406
- // export common environment variables that help run Cypress
6407
- core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6408
- core.exportVariable('TERM', 'xterm')
6409
-
6410
- const customCommand = core.getInput('command')
6411
- if (customCommand) {
6412
- console.log('Using custom test command: %s', customCommand)
6413
- return execCommand(customCommand, true, 'run tests')
6414
- }
6415
-
6416
- const commandPrefix = core.getInput('command-prefix')
6417
- if (commandPrefix) {
6418
- return runTestsUsingCommandLine()
6419
- }
6420
-
6421
- debug('Running Cypress tests using NPM module API')
6422
- debug(`requiring cypress dependency, cwd is ${process.cwd()}`)
6423
- debug(`working directory ${workingDirectory}`)
6424
- const cypressModulePath =
6425
- require.resolve('cypress', {
6426
- paths: [workingDirectory]
6427
- }) || 'cypress'
6428
- debug(`resolved cypress ${cypressModulePath}`)
6429
-
6430
- const cypress = require(cypressModulePath)
6431
- const cypressOptions = {
6432
- headless: getInputBool('headless'),
6433
- record: getInputBool('record'),
6434
- parallel: getInputBool('parallel'),
6435
- quiet: getInputBool('quiet')
6436
- }
6437
-
6438
- if (core.getInput('group')) {
6439
- cypressOptions.group = core.getInput('group')
6440
- }
6441
- if (core.getInput('tag')) {
6442
- cypressOptions.tag = core.getInput('tag')
6443
- }
6444
- if (core.getInput('config')) {
6445
- cypressOptions.config = core.getInput('config')
6446
- debug(`Cypress config "${cypressOptions.config}"`)
6447
- }
6448
- const spec = getSpecsList()
6449
- if (spec) {
6450
- cypressOptions.spec = spec
6451
- }
6452
- if (core.getInput('config-file')) {
6453
- cypressOptions.configFile = core.getInput('config-file')
6454
- }
6455
-
6456
- // if the user set the explicit folder, use that
6457
- if (core.getInput('project')) {
6458
- cypressOptions.project = core.getInput('project')
6459
- }
6460
- if (core.getInput('browser')) {
6461
- cypressOptions.browser = core.getInput('browser')
6462
- }
6463
- if (core.getInput('env')) {
6464
- cypressOptions.env = core.getInput('env')
6465
- }
6466
-
6467
- if (cypressOptions.parallel || cypressOptions.group) {
6468
- const { branch, buildId } = await getCiBuildId()
6469
- if (branch) {
6470
- core.exportVariable('GH_BRANCH', branch)
6471
- }
6472
-
6473
- cypressOptions.ciBuildId = core.getInput('ci-build-id') || buildId
6474
- }
6475
-
6476
- debug(`Cypress options ${JSON.stringify(cypressOptions)}`)
6477
-
6478
- const onTestsFinished = (testResults) => {
6479
- process.chdir(startWorkingDirectory)
6480
-
6481
- if (testResults.failures) {
6482
- console.error('Test run failed, code %d', testResults.failures)
6483
- console.error('More information might be available above')
6484
-
6485
- if (testResults.message) {
6486
- console.error(
6487
- 'Cypress module has returned the following error message:'
6488
- )
6489
- console.error(testResults.message)
6490
- }
6491
-
6492
- return Promise.reject(
6493
- new Error(testResults.message || 'Error running Cypress')
6494
- )
6495
- }
6496
-
6497
- debug(`Cypress tests: ${testResults.totalFailed} failed`)
6498
-
6499
- const dashboardUrl = testResults.runUrl
6500
- if (dashboardUrl) {
6501
- debug(`Dashboard url ${dashboardUrl}`)
6502
- } else {
6503
- debug('There is no Dashboard url')
6504
- }
6505
- // we still set the output explicitly
6506
- core.setOutput('dashboardUrl', dashboardUrl)
6507
-
6508
- if (testResults.totalFailed) {
6509
- return Promise.reject(
6510
- new Error(`Cypress tests: ${testResults.totalFailed} failed`)
6511
- )
6512
- }
6513
- }
6514
-
6515
- const onTestsError = (e) => {
6516
- process.chdir(startWorkingDirectory)
6517
-
6518
- console.error(e)
6519
- return Promise.reject(e)
6520
- }
6521
-
6522
- process.chdir(workingDirectory)
6523
- return cypress
6524
- .run(cypressOptions)
6525
- .then(onTestsFinished, onTestsError)
6526
- }
6527
-
6528
- const installMaybe = () => {
6529
- const installParameter = getInputBool('install', true)
6530
- if (!installParameter) {
6531
- console.log('Skipping install because install parameter is false')
6532
- return Promise.resolve()
6533
- }
6534
-
6535
- return Promise.all([
6536
- restoreCachedNpm(),
6537
- restoreCachedCypressBinary()
6538
- ]).then(([npmCacheHit, cypressCacheHit]) => {
6539
- debug(`npm cache hit ${npmCacheHit}`)
6540
- debug(`cypress cache hit ${cypressCacheHit}`)
6541
-
6542
- return install().then(() => {
6543
- debug('install has finished')
6544
- return listCypressBinaries().then(() => {
6545
- if (npmCacheHit && cypressCacheHit) {
6546
- debug('no need to verify Cypress binary or save caches')
6547
- return Promise.resolve(undefined)
6548
- }
6549
-
6550
- debug('verifying Cypress binary')
6551
- return verifyCypressBinary()
6552
- .then(saveCachedNpm)
6553
- .then(saveCachedCypressBinary)
6554
- })
6555
- })
6556
- })
6557
- }
6558
-
6559
- installMaybe()
6560
- .then(buildAppMaybe)
6561
- .then(startServersMaybe)
6562
- .then(waitOnMaybe)
6563
- .then(runTests)
6564
- .then(() => {
6565
- debug('all done, exiting')
6566
- // force exit to avoid waiting for child processes,
6567
- // like the server we have started
6568
- // see https://github.com/actions/toolkit/issues/216
6569
- process.exit(0)
6570
- })
6571
- .catch((error) => {
6572
- // final catch - when anything goes wrong, throw an error
6573
- // and exit the action with non-zero code
6574
- debug(error.message)
6575
- debug(error.stack)
6576
-
6577
- core.setFailed(error.message)
6578
- process.exit(1)
6579
- })
5794
+ // @ts-check
5795
+ const { restoreCache, saveCache } = __webpack_require__(692)
5796
+ const core = __webpack_require__(470)
5797
+ const exec = __webpack_require__(986)
5798
+ const io = __webpack_require__(1)
5799
+ const { Octokit } = __webpack_require__(448)
5800
+ const hasha = __webpack_require__(309)
5801
+ const fs = __webpack_require__(747)
5802
+ const os = __webpack_require__(87)
5803
+ const path = __webpack_require__(622)
5804
+ const quote = __webpack_require__(531)
5805
+ const cliParser = __webpack_require__(324)()
5806
+ const findYarnWorkspaceRoot = __webpack_require__(638)
5807
+ const debug = __webpack_require__(784)('@cypress/github-action')
5808
+ const { ping } = __webpack_require__(946)
5809
+
5810
+ /**
5811
+ * Parses input command, finds the tool and
5812
+ * the runs the command.
5813
+ */
5814
+ const execCommand = (
5815
+ fullCommand,
5816
+ waitToFinish = true,
5817
+ label = 'executing'
5818
+ ) => {
5819
+ const cwd = cypressCommandOptions.cwd
5820
+
5821
+ console.log('%s command "%s"', label, fullCommand)
5822
+ console.log('current working directory "%s"', cwd)
5823
+
5824
+ const args = cliParser.parse(fullCommand)
5825
+ debug(`parsed command: ${args.join(' ')}`)
5826
+
5827
+ return io.which(args[0], true).then((toolPath) => {
5828
+ debug(`found command "${toolPath}"`)
5829
+ debug(`with arguments ${args.slice(1).join(' ')}`)
5830
+
5831
+ const toolArguments = args.slice(1)
5832
+ const argsString = toolArguments.join(' ')
5833
+ debug(`running ${quote(toolPath)} ${argsString} in ${cwd}`)
5834
+ debug(`waiting for the command to finish? ${waitToFinish}`)
5835
+
5836
+ const promise = exec.exec(
5837
+ quote(toolPath),
5838
+ toolArguments,
5839
+ cypressCommandOptions
5840
+ )
5841
+ if (waitToFinish) {
5842
+ return promise
5843
+ }
5844
+ })
5845
+ }
5846
+
5847
+ const isWindows = () => os.platform() === 'win32'
5848
+ const isUrl = (s) => /^https?:\/\//.test(s)
5849
+
5850
+ /**
5851
+ * Quote strings that contain Windows delimiters.
5852
+ * https://github.com/cypress-io/github-action/issues/459
5853
+ */
5854
+ const quoteWindowsArgument = (value) => {
5855
+ if (!/[,;=\s]/.test(value)) {
5856
+ return value
5857
+ }
5858
+ return quote(value)
5859
+ }
5860
+
5861
+ /**
5862
+ * Returns true if the Cypress binary installation was skipped
5863
+ * via an environment variable https://on.cypress.io/installing
5864
+ */
5865
+ const isCypressBinarySkipped = () =>
5866
+ process.env.CYPRESS_INSTALL_BINARY === '0'
5867
+
5868
+ const homeDirectory = os.homedir()
5869
+ const platformAndArch = `${process.platform}-${process.arch}`
5870
+
5871
+ const startWorkingDirectory = process.cwd()
5872
+ // seems the working directory should be absolute to work correctly
5873
+ // https://github.com/cypress-io/github-action/issues/211
5874
+ const workingDirectory = core.getInput('working-directory')
5875
+ ? path.resolve(core.getInput('working-directory'))
5876
+ : startWorkingDirectory
5877
+ debug(`working directory ${workingDirectory}`)
5878
+
5879
+ /**
5880
+ * When running "npm install" or any other Cypress-related commands,
5881
+ * use the install directory as current working directory
5882
+ */
5883
+ const cypressCommandOptions = {
5884
+ cwd: workingDirectory
5885
+ }
5886
+
5887
+ const yarnFilename = path.join(
5888
+ findYarnWorkspaceRoot(workingDirectory) || workingDirectory,
5889
+ 'yarn.lock'
5890
+ )
5891
+ const packageLockFilename = path.join(
5892
+ workingDirectory,
5893
+ 'package-lock.json'
5894
+ )
5895
+
5896
+ const useYarn = () => fs.existsSync(yarnFilename)
5897
+
5898
+ const lockHash = () => {
5899
+ const lockFilename = useYarn() ? yarnFilename : packageLockFilename
5900
+ const fileHash = hasha.fromFileSync(lockFilename)
5901
+ debug(`Hash from file ${lockFilename} is ${fileHash}`)
5902
+ return fileHash
5903
+ }
5904
+
5905
+ // enforce the same NPM cache folder across different operating systems
5906
+ const NPM_CACHE_FOLDER = path.join(homeDirectory, '.npm')
5907
+ const getNpmCache = () => {
5908
+ const o = {}
5909
+ let key = core.getInput('cache-key')
5910
+ const hash = lockHash()
5911
+ if (!key) {
5912
+ if (useYarn()) {
5913
+ key = `yarn-${platformAndArch}-${hash}`
5914
+ } else {
5915
+ key = `npm-${platformAndArch}-${hash}`
5916
+ }
5917
+ } else {
5918
+ console.log('using custom cache key "%s"', key)
5919
+ }
5920
+
5921
+ if (useYarn()) {
5922
+ o.inputPath = path.join(homeDirectory, '.cache', 'yarn')
5923
+ } else {
5924
+ o.inputPath = NPM_CACHE_FOLDER
5925
+ }
5926
+
5927
+ // use exact restore key to prevent NPM cache from growing
5928
+ // https://glebbahmutov.com/blog/do-not-let-npm-cache-snowball/
5929
+ o.restoreKeys = o.primaryKey = key
5930
+ return o
5931
+ }
5932
+
5933
+ // custom Cypress binary cache folder
5934
+ // see https://on.cypress.io/caching
5935
+ const CYPRESS_CACHE_FOLDER =
5936
+ process.env.CYPRESS_CACHE_FOLDER ||
5937
+ path.join(homeDirectory, '.cache', 'Cypress')
5938
+ debug(`using custom Cypress cache folder "${CYPRESS_CACHE_FOLDER}"`)
5939
+
5940
+ const getCypressBinaryCache = () => {
5941
+ const o = {
5942
+ inputPath: CYPRESS_CACHE_FOLDER
5943
+ }
5944
+ const hash = lockHash()
5945
+ const key = `cypress-${platformAndArch}-${hash}`
5946
+
5947
+ // use only exact restore key to prevent cached folder growing in size
5948
+ // https://glebbahmutov.com/blog/do-not-let-cypress-cache-snowball/
5949
+ o.restoreKeys = o.primaryKey = key
5950
+ return o
5951
+ }
5952
+
5953
+ const restoreCachedNpm = () => {
5954
+ debug('trying to restore cached NPM modules')
5955
+ const NPM_CACHE = getNpmCache()
5956
+ return restoreCache([NPM_CACHE.inputPath], NPM_CACHE.primaryKey, [
5957
+ NPM_CACHE.restoreKeys
5958
+ ]).catch((e) => {
5959
+ console.warn('Restoring NPM cache error: %s', e.message)
5960
+ })
5961
+ }
5962
+
5963
+ const saveCachedNpm = () => {
5964
+ debug('saving NPM modules')
5965
+ const NPM_CACHE = getNpmCache()
5966
+ return saveCache([NPM_CACHE.inputPath], NPM_CACHE.primaryKey).catch(
5967
+ (e) => {
5968
+ console.warn('Saving NPM cache error: %s', e.message)
5969
+ }
5970
+ )
5971
+ }
5972
+
5973
+ const restoreCachedCypressBinary = () => {
5974
+ debug('trying to restore cached Cypress binary')
5975
+ const CYPRESS_BINARY_CACHE = getCypressBinaryCache()
5976
+ return restoreCache(
5977
+ [CYPRESS_BINARY_CACHE.inputPath],
5978
+ CYPRESS_BINARY_CACHE.primaryKey,
5979
+ [CYPRESS_BINARY_CACHE.restoreKeys]
5980
+ ).catch((e) => {
5981
+ console.warn('Restoring Cypress cache error: %s', e.message)
5982
+ })
5983
+ }
5984
+
5985
+ const saveCachedCypressBinary = () => {
5986
+ debug('saving Cypress binary')
5987
+
5988
+ if (isCypressBinarySkipped()) {
5989
+ debug('Skipping Cypress cache save, binary is not installed')
5990
+ return Promise.resolve()
5991
+ }
5992
+
5993
+ const CYPRESS_BINARY_CACHE = getCypressBinaryCache()
5994
+ return saveCache(
5995
+ [CYPRESS_BINARY_CACHE.inputPath],
5996
+ CYPRESS_BINARY_CACHE.primaryKey
5997
+ ).catch((e) => {
5998
+ console.warn('Saving Cypress cache error: %s', e.message)
5999
+ })
6000
+ }
6001
+
6002
+ const install = () => {
6003
+ // prevent lots of progress messages during install
6004
+ core.exportVariable('CI', '1')
6005
+ core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6006
+ // set NPM cache path in case the user has custom install command
6007
+ core.exportVariable('npm_config_cache', NPM_CACHE_FOLDER)
6008
+
6009
+ // Note: need to quote found tool to avoid Windows choking on
6010
+ // npm paths with spaces like "C:\Program Files\nodejs\npm.cmd ci"
6011
+ const installCommand = core.getInput('install-command')
6012
+ if (installCommand) {
6013
+ debug(`using custom install command "${installCommand}"`)
6014
+ return execCommand(installCommand, true, 'install command')
6015
+ }
6016
+
6017
+ if (useYarn()) {
6018
+ debug('installing NPM dependencies using Yarn')
6019
+ return io.which('yarn', true).then((yarnPath) => {
6020
+ debug(`yarn at "${yarnPath}"`)
6021
+ return exec.exec(
6022
+ quote(yarnPath),
6023
+ ['--frozen-lockfile'],
6024
+ cypressCommandOptions
6025
+ )
6026
+ })
6027
+ } else {
6028
+ debug('installing NPM dependencies')
6029
+
6030
+ return io.which('npm', true).then((npmPath) => {
6031
+ debug(`npm at "${npmPath}"`)
6032
+ return exec.exec(quote(npmPath), ['ci'], cypressCommandOptions)
6033
+ })
6034
+ }
6035
+ }
6036
+
6037
+ const listCypressBinaries = () => {
6038
+ debug(
6039
+ `Cypress versions in the cache folder ${CYPRESS_CACHE_FOLDER}`
6040
+ )
6041
+
6042
+ if (isCypressBinarySkipped()) {
6043
+ debug('Skipping Cypress cache list, binary is not installed')
6044
+ return Promise.resolve()
6045
+ }
6046
+
6047
+ core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6048
+ return io.which('npx', true).then((npxPath) => {
6049
+ return exec.exec(
6050
+ quote(npxPath),
6051
+ ['cypress', 'cache', 'list'],
6052
+ cypressCommandOptions
6053
+ )
6054
+ })
6055
+ }
6056
+
6057
+ const verifyCypressBinary = () => {
6058
+ debug(
6059
+ `Verifying Cypress using cache folder ${CYPRESS_CACHE_FOLDER}`
6060
+ )
6061
+ if (isCypressBinarySkipped()) {
6062
+ debug('Skipping Cypress verify, binary is not installed')
6063
+ return Promise.resolve()
6064
+ }
6065
+
6066
+ core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6067
+ return io.which('npx', true).then((npxPath) => {
6068
+ return exec.exec(
6069
+ quote(npxPath),
6070
+ ['cypress', 'verify'],
6071
+ cypressCommandOptions
6072
+ )
6073
+ })
6074
+ }
6075
+
6076
+ /**
6077
+ * Grabs a boolean GitHub Action parameter input and casts it.
6078
+ * @param {string} name - parameter name
6079
+ * @param {boolean} defaultValue - default value to use if the parameter was not specified
6080
+ * @returns {boolean} converted input argument or default value
6081
+ */
6082
+ const getInputBool = (name, defaultValue = false) => {
6083
+ const param = core.getInput(name)
6084
+ if (param === 'true' || param === '1') {
6085
+ return true
6086
+ }
6087
+ if (param === 'false' || param === '0') {
6088
+ return false
6089
+ }
6090
+
6091
+ return defaultValue
6092
+ }
6093
+
6094
+ /**
6095
+ * Grabs the spec input from the workflow and normalizes
6096
+ * it, since sometimes it can be multiline
6097
+ * @returns {string|undefined}
6098
+ */
6099
+ const getSpecsList = () => {
6100
+ const spec = core.getInput('spec')
6101
+ if (!spec) {
6102
+ return
6103
+ }
6104
+ const specLines = spec.split('\n').join(',')
6105
+ debug(`extracted spec lines into: "${specLines}"`)
6106
+ return specLines
6107
+ }
6108
+
6109
+ const buildAppMaybe = () => {
6110
+ const buildApp = core.getInput('build')
6111
+ if (!buildApp) {
6112
+ return
6113
+ }
6114
+
6115
+ debug(`building application using "${buildApp}"`)
6116
+
6117
+ return execCommand(buildApp, true, 'build app')
6118
+ }
6119
+
6120
+ const startServersMaybe = () => {
6121
+ let startCommand
6122
+
6123
+ if (isWindows()) {
6124
+ // allow custom Windows start command
6125
+ startCommand =
6126
+ core.getInput('start-windows') || core.getInput('start')
6127
+ } else {
6128
+ startCommand = core.getInput('start')
6129
+ }
6130
+ if (!startCommand) {
6131
+ debug('No start command found')
6132
+ return Promise.resolve()
6133
+ }
6134
+
6135
+ // allow commands to be separated using commas or newlines
6136
+ const separateStartCommands = startCommand
6137
+ .split(/,|\n/)
6138
+ .map((s) => s.trim())
6139
+ .filter(Boolean)
6140
+ debug(
6141
+ `Separated ${
6142
+ separateStartCommands.length
6143
+ } start commands ${separateStartCommands.join(', ')}`
6144
+ )
6145
+
6146
+ return separateStartCommands.map((startCommand) => {
6147
+ return execCommand(
6148
+ startCommand,
6149
+ false,
6150
+ `start server "${startCommand}`
6151
+ )
6152
+ })
6153
+ }
6154
+
6155
+ /**
6156
+ * Pings give URL(s) until the timeout expires.
6157
+ * @param {string} waitOn A single URL or comma-separated URLs
6158
+ * @param {Number?} waitOnTimeout in seconds
6159
+ */
6160
+ const waitOnUrl = (waitOn, waitOnTimeout = 60) => {
6161
+ console.log(
6162
+ 'waiting on "%s" with timeout of %s seconds',
6163
+ waitOn,
6164
+ waitOnTimeout
6165
+ )
6166
+
6167
+ const waitTimeoutMs = waitOnTimeout * 1000
6168
+
6169
+ const waitUrls = waitOn
6170
+ .split(',')
6171
+ .map((s) => s.trim())
6172
+ .filter(Boolean)
6173
+ debug(`Waiting for urls ${waitUrls.join(', ')}`)
6174
+
6175
+ // run every wait promise after the previous has finished
6176
+ // to avoid "noise" of debug messages
6177
+ return waitUrls.reduce((prevPromise, url) => {
6178
+ return prevPromise.then(() => {
6179
+ debug(`Waiting for url ${url}`)
6180
+ return ping(url, waitTimeoutMs)
6181
+ })
6182
+ }, Promise.resolve())
6183
+ }
6184
+
6185
+ const waitOnMaybe = () => {
6186
+ const waitOn = core.getInput('wait-on')
6187
+ if (!waitOn) {
6188
+ return
6189
+ }
6190
+
6191
+ const waitOnTimeout = core.getInput('wait-on-timeout') || '60'
6192
+ const timeoutSeconds = parseFloat(waitOnTimeout)
6193
+
6194
+ if (isUrl(waitOn)) {
6195
+ return waitOnUrl(waitOn, timeoutSeconds)
6196
+ }
6197
+
6198
+ console.log('Waiting using command "%s"', waitOn)
6199
+ return execCommand(waitOn, true)
6200
+ }
6201
+
6202
+ const I = (x) => x
6203
+
6204
+ /**
6205
+ * Asks Cypress API if there were already builds for this commit.
6206
+ * In that case increments the count to get unique parallel id.
6207
+ */
6208
+ const getCiBuildId = async () => {
6209
+ const {
6210
+ GITHUB_WORKFLOW,
6211
+ GITHUB_SHA,
6212
+ GITHUB_TOKEN,
6213
+ GITHUB_RUN_ID,
6214
+ GITHUB_REPOSITORY
6215
+ } = process.env
6216
+
6217
+ const [owner, repo] = GITHUB_REPOSITORY.split('/')
6218
+ let branch
6219
+ let buildId = `${GITHUB_WORKFLOW} - ${GITHUB_SHA}`
6220
+
6221
+ if (GITHUB_TOKEN) {
6222
+ debug(
6223
+ `Determining build id by asking GitHub about run ${GITHUB_RUN_ID}`
6224
+ )
6225
+
6226
+ const client = new Octokit({
6227
+ auth: GITHUB_TOKEN
6228
+ })
6229
+
6230
+ const resp = await client.request(
6231
+ 'GET /repos/:owner/:repo/actions/runs/:run_id',
6232
+ {
6233
+ owner,
6234
+ repo,
6235
+ run_id: parseInt(GITHUB_RUN_ID)
6236
+ }
6237
+ )
6238
+
6239
+ if (resp && resp.data && resp.data.head_branch) {
6240
+ branch = resp.data.head_branch
6241
+ debug(`found the branch name ${branch}`)
6242
+ }
6243
+
6244
+ // This will return the complete list of jobs for a run with their steps,
6245
+ // this should always return data when there are jobs on the workflow.
6246
+ // Every time the workflow is re-run the jobs length should stay the same
6247
+ // (because the same amount of jobs were ran) but the id of them should change
6248
+ // letting us, select the first id as unique id
6249
+ // https://docs.github.com/en/rest/reference/actions#list-jobs-for-a-workflow-run
6250
+ const runsList = await client.request(
6251
+ 'GET /repos/:owner/:repo/actions/runs/:run_id/jobs',
6252
+ {
6253
+ owner,
6254
+ repo,
6255
+ run_id: parseInt(GITHUB_RUN_ID)
6256
+ }
6257
+ )
6258
+
6259
+ if (
6260
+ runsList &&
6261
+ runsList.data &&
6262
+ runsList.data.jobs &&
6263
+ runsList.data.jobs.length
6264
+ ) {
6265
+ const jobId = runsList.data.jobs[0].id
6266
+ debug(`fetched run list with jobId ${jobId}`)
6267
+ buildId = `${GITHUB_RUN_ID}-${jobId}`
6268
+ } else {
6269
+ debug('could not get run list data')
6270
+ }
6271
+ }
6272
+
6273
+ debug(`determined branch ${branch} and build id ${buildId}`)
6274
+ return { branch, buildId }
6275
+ }
6276
+
6277
+ /**
6278
+ * Forms entire command line like "npx cypress run ..."
6279
+ */
6280
+ const runTestsUsingCommandLine = async () => {
6281
+ debug('Running Cypress tests using CLI command')
6282
+ const quoteArgument = isWindows() ? quoteWindowsArgument : I
6283
+
6284
+ const commandPrefix = core.getInput('command-prefix')
6285
+ if (!commandPrefix) {
6286
+ throw new Error('Expected command prefix')
6287
+ }
6288
+
6289
+ const record = getInputBool('record')
6290
+ const parallel = getInputBool('parallel')
6291
+ const headless = getInputBool('headless')
6292
+
6293
+ // TODO using yarn to run cypress when yarn is used for install
6294
+ // split potentially long command?
6295
+
6296
+ let cmd = []
6297
+ // we need to split the command prefix into individual arguments
6298
+ // otherwise they are passed all as a single string
6299
+ const parts = commandPrefix.split(' ')
6300
+ cmd = cmd.concat(parts)
6301
+ debug(`with concatenated command prefix: ${cmd.join(' ')}`)
6302
+
6303
+ // push each CLI argument separately
6304
+ cmd.push('cypress')
6305
+ cmd.push('run')
6306
+ if (headless) {
6307
+ cmd.push('--headless')
6308
+ }
6309
+ if (record) {
6310
+ cmd.push('--record')
6311
+ }
6312
+ if (parallel) {
6313
+ cmd.push('--parallel')
6314
+ }
6315
+ const group = core.getInput('group')
6316
+ if (group) {
6317
+ cmd.push('--group')
6318
+ cmd.push(quoteArgument(group))
6319
+ }
6320
+ const tag = core.getInput('tag')
6321
+ if (tag) {
6322
+ cmd.push('--tag')
6323
+ cmd.push(quoteArgument(tag))
6324
+ }
6325
+ const configInput = core.getInput('config')
6326
+ if (configInput) {
6327
+ cmd.push('--config')
6328
+ cmd.push(quoteArgument(configInput))
6329
+ }
6330
+ const spec = getSpecsList()
6331
+ if (spec) {
6332
+ cmd.push('--spec')
6333
+ cmd.push(quoteArgument(spec))
6334
+ }
6335
+ const project = core.getInput('project')
6336
+ if (project) {
6337
+ cmd.push('--project')
6338
+ cmd.push(quoteArgument(project))
6339
+ }
6340
+ const configFileInput = core.getInput('config-file')
6341
+ if (configFileInput) {
6342
+ cmd.push('--config-file')
6343
+ cmd.push(quoteArgument(configFileInput))
6344
+ }
6345
+
6346
+ if (parallel || group) {
6347
+ const { branch, buildId } = await getCiBuildId()
6348
+ if (branch) {
6349
+ core.exportVariable('GH_BRANCH', branch)
6350
+ }
6351
+
6352
+ cmd.push('--ci-build-id')
6353
+ const ciBuildId = core.getInput('ci-build-id') || buildId
6354
+ cmd.push(quoteArgument(ciBuildId))
6355
+ }
6356
+
6357
+ const browser = core.getInput('browser')
6358
+ if (browser) {
6359
+ cmd.push('--browser')
6360
+ // TODO should browser be quoted?
6361
+ // If it is a path, it might have spaces
6362
+ cmd.push(browser)
6363
+ }
6364
+
6365
+ const envInput = core.getInput('env')
6366
+ if (envInput) {
6367
+ // TODO should env be quoted?
6368
+ // If it is a JSON, it might have spaces
6369
+ cmd.push('--env')
6370
+ cmd.push(envInput)
6371
+ }
6372
+
6373
+ const quiet = getInputBool('quiet')
6374
+ if (quiet) {
6375
+ cmd.push('--quiet')
6376
+ }
6377
+
6378
+ console.log('Cypress test command: npx %s', cmd.join(' '))
6379
+
6380
+ // since we have quoted arguments ourselves, do not double quote them
6381
+ const opts = {
6382
+ ...cypressCommandOptions,
6383
+ windowsVerbatimArguments: false
6384
+ }
6385
+
6386
+ debug(`in working directory "${cypressCommandOptions.cwd}"`)
6387
+
6388
+ const npxPath = await io.which('npx', true)
6389
+ debug(`npx path: ${npxPath}`)
6390
+
6391
+ return exec.exec(quote(npxPath), cmd, opts)
6392
+ }
6393
+
6394
+ /**
6395
+ * Run Cypress tests by collecting input parameters
6396
+ * and using Cypress module API to run tests.
6397
+ * @see https://on.cypress.io/module-api
6398
+ */
6399
+ const runTests = async () => {
6400
+ const runTests = getInputBool('runTests', true)
6401
+ if (!runTests) {
6402
+ console.log('Skipping running tests: runTests parameter is false')
6403
+ return
6404
+ }
6405
+
6406
+ // export common environment variables that help run Cypress
6407
+ core.exportVariable('CYPRESS_CACHE_FOLDER', CYPRESS_CACHE_FOLDER)
6408
+ core.exportVariable('TERM', 'xterm')
6409
+
6410
+ const customCommand = core.getInput('command')
6411
+ if (customCommand) {
6412
+ console.log('Using custom test command: %s', customCommand)
6413
+ return execCommand(customCommand, true, 'run tests')
6414
+ }
6415
+
6416
+ const commandPrefix = core.getInput('command-prefix')
6417
+ if (commandPrefix) {
6418
+ return runTestsUsingCommandLine()
6419
+ }
6420
+
6421
+ debug('Running Cypress tests using NPM module API')
6422
+ debug(`requiring cypress dependency, cwd is ${process.cwd()}`)
6423
+ debug(`working directory ${workingDirectory}`)
6424
+ const cypressModulePath =
6425
+ require.resolve('cypress', {
6426
+ paths: [workingDirectory]
6427
+ }) || 'cypress'
6428
+ debug(`resolved cypress ${cypressModulePath}`)
6429
+
6430
+ const cypress = require(cypressModulePath)
6431
+ const cypressOptions = {
6432
+ headless: getInputBool('headless'),
6433
+ record: getInputBool('record'),
6434
+ parallel: getInputBool('parallel'),
6435
+ quiet: getInputBool('quiet')
6436
+ }
6437
+
6438
+ if (core.getInput('group')) {
6439
+ cypressOptions.group = core.getInput('group')
6440
+ }
6441
+ if (core.getInput('tag')) {
6442
+ cypressOptions.tag = core.getInput('tag')
6443
+ }
6444
+ if (core.getInput('config')) {
6445
+ cypressOptions.config = core.getInput('config')
6446
+ debug(`Cypress config "${cypressOptions.config}"`)
6447
+ }
6448
+ const spec = getSpecsList()
6449
+ if (spec) {
6450
+ cypressOptions.spec = spec
6451
+ }
6452
+ if (core.getInput('config-file')) {
6453
+ cypressOptions.configFile = core.getInput('config-file')
6454
+ }
6455
+
6456
+ // if the user set the explicit folder, use that
6457
+ if (core.getInput('project')) {
6458
+ cypressOptions.project = core.getInput('project')
6459
+ }
6460
+ if (core.getInput('browser')) {
6461
+ cypressOptions.browser = core.getInput('browser')
6462
+ }
6463
+ if (core.getInput('env')) {
6464
+ cypressOptions.env = core.getInput('env')
6465
+ }
6466
+
6467
+ if (cypressOptions.parallel || cypressOptions.group) {
6468
+ const { branch, buildId } = await getCiBuildId()
6469
+ if (branch) {
6470
+ core.exportVariable('GH_BRANCH', branch)
6471
+ }
6472
+
6473
+ cypressOptions.ciBuildId = core.getInput('ci-build-id') || buildId
6474
+ }
6475
+
6476
+ debug(`Cypress options ${JSON.stringify(cypressOptions)}`)
6477
+
6478
+ const onTestsFinished = (testResults) => {
6479
+ process.chdir(startWorkingDirectory)
6480
+
6481
+ if (testResults.failures) {
6482
+ console.error('Test run failed, code %d', testResults.failures)
6483
+ console.error('More information might be available above')
6484
+
6485
+ if (testResults.message) {
6486
+ console.error(
6487
+ 'Cypress module has returned the following error message:'
6488
+ )
6489
+ console.error(testResults.message)
6490
+ }
6491
+
6492
+ return Promise.reject(
6493
+ new Error(testResults.message || 'Error running Cypress')
6494
+ )
6495
+ }
6496
+
6497
+ debug(`Cypress tests: ${testResults.totalFailed} failed`)
6498
+
6499
+ const dashboardUrl = testResults.runUrl
6500
+ if (dashboardUrl) {
6501
+ debug(`Dashboard url ${dashboardUrl}`)
6502
+ } else {
6503
+ debug('There is no Dashboard url')
6504
+ }
6505
+ // we still set the output explicitly
6506
+ core.setOutput('dashboardUrl', dashboardUrl)
6507
+
6508
+ if (testResults.totalFailed) {
6509
+ return Promise.reject(
6510
+ new Error(`Cypress tests: ${testResults.totalFailed} failed`)
6511
+ )
6512
+ }
6513
+ }
6514
+
6515
+ const onTestsError = (e) => {
6516
+ process.chdir(startWorkingDirectory)
6517
+
6518
+ console.error(e)
6519
+ return Promise.reject(e)
6520
+ }
6521
+
6522
+ process.chdir(workingDirectory)
6523
+ return cypress
6524
+ .run(cypressOptions)
6525
+ .then(onTestsFinished, onTestsError)
6526
+ }
6527
+
6528
+ const installMaybe = () => {
6529
+ const installParameter = getInputBool('install', true)
6530
+ if (!installParameter) {
6531
+ console.log('Skipping install because install parameter is false')
6532
+ return Promise.resolve()
6533
+ }
6534
+
6535
+ return Promise.all([
6536
+ restoreCachedNpm(),
6537
+ restoreCachedCypressBinary()
6538
+ ]).then(([npmCacheHit, cypressCacheHit]) => {
6539
+ debug(`npm cache hit ${npmCacheHit}`)
6540
+ debug(`cypress cache hit ${cypressCacheHit}`)
6541
+
6542
+ return install().then(() => {
6543
+ debug('install has finished')
6544
+ return listCypressBinaries().then(() => {
6545
+ if (npmCacheHit && cypressCacheHit) {
6546
+ debug('no need to verify Cypress binary or save caches')
6547
+ return Promise.resolve(undefined)
6548
+ }
6549
+
6550
+ debug('verifying Cypress binary')
6551
+ return verifyCypressBinary()
6552
+ .then(saveCachedNpm)
6553
+ .then(saveCachedCypressBinary)
6554
+ })
6555
+ })
6556
+ })
6557
+ }
6558
+
6559
+ installMaybe()
6560
+ .then(buildAppMaybe)
6561
+ .then(startServersMaybe)
6562
+ .then(waitOnMaybe)
6563
+ .then(runTests)
6564
+ .then(() => {
6565
+ debug('all done, exiting')
6566
+ // force exit to avoid waiting for child processes,
6567
+ // like the server we have started
6568
+ // see https://github.com/actions/toolkit/issues/216
6569
+ process.exit(0)
6570
+ })
6571
+ .catch((error) => {
6572
+ // final catch - when anything goes wrong, throw an error
6573
+ // and exit the action with non-zero code
6574
+ debug(error.message)
6575
+ debug(error.stack)
6576
+
6577
+ core.setFailed(error.message)
6578
+ process.exit(1)
6579
+ })
6580
6580
 
6581
6581
 
6582
6582
  /***/ }),
@@ -66123,86 +66123,86 @@ function terminator(callback)
66123
66123
  /* 946 */
66124
66124
  /***/ (function(module, __unusedexports, __webpack_require__) {
66125
66125
 
66126
- const core = __webpack_require__(470)
66127
- const got = __webpack_require__(77)
66128
- const debug = __webpack_require__(784)('@cypress/github-action')
66129
-
66130
- /**
66131
- * A small utility for checking when an URL responds, kind of
66132
- * a poor man's https://www.npmjs.com/package/wait-on. This version
66133
- * is implemented using https://github.com/sindresorhus/got
66134
- */
66135
- const ping = (url, timeout) => {
66136
- if (!timeout) {
66137
- throw new Error('Expected timeout in ms')
66138
- }
66139
-
66140
- // make copy of the error codes that "got" retries on
66141
- const errorCodes = [...got.defaults.options.retry.errorCodes]
66142
- errorCodes.push('ESOCKETTIMEDOUT')
66143
-
66144
- // we expect the server to respond within a time limit
66145
- // and if it does not - retry up to total "timeout" duration
66146
- const individualPingTimeout = Math.min(timeout, 30000)
66147
-
66148
- // add to the timeout max individual ping timeout
66149
- // to avoid long-waiting ping from "rolling" over the end
66150
- // and preventing pinging the last time
66151
- timeout += individualPingTimeout
66152
- const limit = Math.ceil(timeout / individualPingTimeout)
66153
-
66154
- debug(`total ping timeout ${timeout}`)
66155
- debug(`individual ping timeout ${individualPingTimeout}ms`)
66156
- debug(`retries limit ${limit}`)
66157
-
66158
- const start = +new Date()
66159
- return got(url, {
66160
- headers: {
66161
- Accept: 'text/html, application/json, text/plain, */*'
66162
- },
66163
- timeout: individualPingTimeout,
66164
- errorCodes,
66165
- retry: {
66166
- limit,
66167
- calculateDelay({ error, attemptCount }) {
66168
- if (error) {
66169
- debug(`got error ${JSON.stringify(error)}`)
66170
- }
66171
- const now = +new Date()
66172
- const elapsed = now - start
66173
- debug(
66174
- `${elapsed}ms ${error.method} ${error.host} ${error.code} attempt ${attemptCount}`
66175
- )
66176
- if (elapsed > timeout) {
66177
- console.error(
66178
- '%s timed out on retry %d of %d, elapsed %dms, limit %dms',
66179
- url,
66180
- attemptCount,
66181
- limit,
66182
- elapsed,
66183
- timeout
66184
- )
66185
- return 0
66186
- }
66187
-
66188
- // if the error code is ECONNREFUSED use shorter timeout
66189
- // because the server is probably starting
66190
- if (error.code === 'ECONNREFUSED') {
66191
- return 1000
66192
- }
66193
-
66194
- // default "long" timeout
66195
- return individualPingTimeout
66196
- }
66197
- }
66198
- }).then(() => {
66199
- const now = +new Date()
66200
- const elapsed = now - start
66201
- debug(`pinging ${url} has finished ok after ${elapsed}ms`)
66202
- })
66203
- }
66204
-
66205
- module.exports = { ping }
66126
+ const core = __webpack_require__(470)
66127
+ const got = __webpack_require__(77)
66128
+ const debug = __webpack_require__(784)('@cypress/github-action')
66129
+
66130
+ /**
66131
+ * A small utility for checking when an URL responds, kind of
66132
+ * a poor man's https://www.npmjs.com/package/wait-on. This version
66133
+ * is implemented using https://github.com/sindresorhus/got
66134
+ */
66135
+ const ping = (url, timeout) => {
66136
+ if (!timeout) {
66137
+ throw new Error('Expected timeout in ms')
66138
+ }
66139
+
66140
+ // make copy of the error codes that "got" retries on
66141
+ const errorCodes = [...got.defaults.options.retry.errorCodes]
66142
+ errorCodes.push('ESOCKETTIMEDOUT')
66143
+
66144
+ // we expect the server to respond within a time limit
66145
+ // and if it does not - retry up to total "timeout" duration
66146
+ const individualPingTimeout = Math.min(timeout, 30000)
66147
+
66148
+ // add to the timeout max individual ping timeout
66149
+ // to avoid long-waiting ping from "rolling" over the end
66150
+ // and preventing pinging the last time
66151
+ timeout += individualPingTimeout
66152
+ const limit = Math.ceil(timeout / individualPingTimeout)
66153
+
66154
+ debug(`total ping timeout ${timeout}`)
66155
+ debug(`individual ping timeout ${individualPingTimeout}ms`)
66156
+ debug(`retries limit ${limit}`)
66157
+
66158
+ const start = +new Date()
66159
+ return got(url, {
66160
+ headers: {
66161
+ Accept: 'text/html, application/json, text/plain, */*'
66162
+ },
66163
+ timeout: individualPingTimeout,
66164
+ errorCodes,
66165
+ retry: {
66166
+ limit,
66167
+ calculateDelay({ error, attemptCount }) {
66168
+ if (error) {
66169
+ debug(`got error ${JSON.stringify(error)}`)
66170
+ }
66171
+ const now = +new Date()
66172
+ const elapsed = now - start
66173
+ debug(
66174
+ `${elapsed}ms ${error.method} ${error.host} ${error.code} attempt ${attemptCount}`
66175
+ )
66176
+ if (elapsed > timeout) {
66177
+ console.error(
66178
+ '%s timed out on retry %d of %d, elapsed %dms, limit %dms',
66179
+ url,
66180
+ attemptCount,
66181
+ limit,
66182
+ elapsed,
66183
+ timeout
66184
+ )
66185
+ return 0
66186
+ }
66187
+
66188
+ // if the error code is ECONNREFUSED use shorter timeout
66189
+ // because the server is probably starting
66190
+ if (error.code === 'ECONNREFUSED') {
66191
+ return 1000
66192
+ }
66193
+
66194
+ // default "long" timeout
66195
+ return individualPingTimeout
66196
+ }
66197
+ }
66198
+ }).then(() => {
66199
+ const now = +new Date()
66200
+ const elapsed = now - start
66201
+ debug(`pinging ${url} has finished ok after ${elapsed}ms`)
66202
+ })
66203
+ }
66204
+
66205
+ module.exports = { ping }
66206
66206
 
66207
66207
 
66208
66208
  /***/ }),