@revoloo/cypress6
Version:
Cypress.io end to end testing tool
216 lines (170 loc) • 5.62 kB
JavaScript
/* eslint-disable no-console */
const _ = require('lodash')
const minimist = require('minimist')
const Promise = require('bluebird')
const retry = require('bluebird-retry')
const got = require('got')
// always print the debug logs
const debug = require('debug')('*')
const { seconds, minutes } = require('./utils')
// we expect CircleCI to set the current polling job name
const jobName = process.env.CIRCLE_JOB || 'wait-on-circle-jobs'
const workflowId = process.env.CIRCLE_WORKFLOW_ID
const getAuth = () => `${process.env.CIRCLE_TOKEN}:`
const verifyCI = () => {
if (!process.env.CIRCLE_TOKEN) {
console.error('Cannot find CIRCLE_TOKEN')
process.exit(1)
}
if (!process.env.CIRCLE_WORKFLOW_ID) {
console.error('Cannot find CIRCLE_WORKFLOW_ID')
process.exit(1)
}
}
/* eslint-disable-next-line no-unused-vars */
const getWorkflow = async (workflowId) => {
const auth = getAuth()
const url = `https://${auth}@circleci.com/api/v2/workflow/${workflowId}`
const response = await got(url).json()
// returns something like
// {
// pipeline_id: '5b937e8b-6138-41ad-b8d0-1c1969c4dad1',
// id: '566ffe9a-62d4-45cd-9a27-9882139e0121',
// name: 'linux',
// project_slug: 'gh/cypress-io/cypress',
// status: 'failed',
// started_by: '45ae8c6a-4686-4e71-a078-fb7a3b9d9e59',
// pipeline_number: 12461,
// created_at: '2020-07-20T19:45:41Z',
// stopped_at: '2020-07-20T20:06:54Z'
// }
return response
}
/**
* Job status
* - blocked (has not run yet)
* - running (currently running)
* - failed | success
*/
const getJobStatus = async (workflowId) => {
const auth = getAuth()
// typo at https://circleci.com/docs/2.0/api-intro/
// to retrieve all jobs, the url is "/workflow/:id/job"
const url = `https://${auth}@circleci.com/api/v2/workflow/${workflowId}/job`
const response = await got(url).json()
// returns something like
// {
// next_page_token: null,
// items: [
// {
// dependencies: [],
// job_number: 400959,
// id: '7021bcc7-90c1-47d9-bf99-c0372a4f8f49',
// started_at: '2020-07-20T19:45:46Z',
// name: 'build',
// project_slug: 'gh/cypress-io/cypress',
// status: 'success',
// type: 'build',
// stopped_at: '2020-07-20T19:50:07Z'
// }
// ]
// }
return response
}
const waitForAllJobs = async (jobNames, workflowId) => {
let response
try {
response = await getJobStatus(workflowId)
} catch (e) {
console.error(e)
process.exit(1)
}
// if a job is pending, its status will be "blocked"
const blockedJobs = _.filter(response.items, { status: 'blocked' })
const failedJobs = _.filter(response.items, { status: 'failed' })
const runningJobs = _.filter(response.items, { status: 'running' })
const blockedJobNames = _.map(blockedJobs, 'name')
const runningJobNames = _.map(runningJobs, 'name')
debug('failed jobs %o', _.map(failedJobs, 'name'))
debug('blocked jobs %o', blockedJobNames)
debug('running jobs %o', runningJobNames)
if (!runningJobs.length || (runningJobs.length === 1 && runningJobs[0].name === jobName)) {
// there are no more jobs to run, or this is the last running job
console.log('all jobs are done, finishing this job')
return Promise.resolve()
}
const futureOrRunning = _.union(blockedJobs, runningJobNames)
const jobsToWaitFor = _.intersection(jobNames, futureOrRunning)
debug('jobs to wait for %o', jobsToWaitFor)
if (!jobsToWaitFor.length) {
console.log('No more jobs to wait for!')
return Promise.resolve()
}
return Promise.reject(new Error('Jobs have not finished'))
}
const waitForJobToPass = Promise.method(async (jobName, workflow = workflowId) => {
verifyCI()
let response
try {
response = await getJobStatus(workflow)
} catch (e) {
console.error(e)
process.exit(1)
}
const job = _.find(response.items, { name: jobName })
if (!job) {
return Promise.reject(new Error('Job not found'))
}
const { status } = job
if (status === 'success') {
return Promise.resolve()
}
if (status === 'failed') {
return Promise.reject(new Error('Job failed'))
}
await Promise.delay(seconds(30))
return waitForJobToPass(jobName, workflow)
})
const main = () => {
verifyCI()
const args = minimist(process.argv.slice(2), { boolean: false })
const jobNames = _
.chain(args['job-names'])
.split(',')
.without('true')
.map(_.trim)
.compact()
.value()
if (!jobNames.length) {
console.error('Missing argument: --job-names')
console.error('You must pass a comma separated list of Circle CI job names to wait for.')
process.exit(1)
}
debug('received circle jobs: %o', jobNames)
// finished, has one failed job
// const workflowId = '566ffe9a-62d4-45cd-9a27-9882139e0121'
// pending workflow
// jobs that have not run have "status: 'blocked'"
// getWorkflow(workflowId).then(console.log, console.error)
// getWorkflowJobs(workflowId).then(console.log, console.error)
// https://github.com/demmer/bluebird-retry
retry(waitForAllJobs.bind(null, jobNames, workflowId), {
timeout: minutes(30), // max time for this job
interval: seconds(30), // poll intervals
max_interval: seconds(30),
}).then(() => {
console.log('all done')
}, (err) => {
console.error(err)
process.exit(1)
})
// getJobStatus(workflowId).then(console.log, console.error)
}
// execute main function if called from command line
if (require.main === module) {
main()
}
module.exports = {
minutes,
waitForJobToPass,
}