UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

318 lines (252 loc) 8.63 kB
import e2e from './e2e' import dayjs from 'dayjs' import _ from 'lodash' const expect = global.expect as unknown as Chai.ExpectStatic const STATIC_DATE = '2018-02-01T20:14:19.323Z' const expectDurationWithin = function (obj, duration, low, high, reset) { const d = _.get(obj, duration) // bail if we don't have a duration if (!_.isNumber(d)) { return } // ensure the duration is within range expect(d, duration).to.be.within(low, high) // once valid, mutate and set static range return _.set(obj, duration, reset) } const expectStartToBeBeforeEnd = function (obj, start, end) { const s = _.get(obj, start) const e = _.get(obj, end) expect( dayjs(s).isBefore(e), `expected start: ${s} to be before end: ${e}`, ).to.be.true // once valid, mutate and set static dates _.set(obj, start, STATIC_DATE) return _.set(obj, end, STATIC_DATE) } const normalizeTestTimings = function (obj, timings) { const t = _.get(obj, timings) // bail if we don't have any timings if (!t) { return } _.set(obj, 'timings', _.mapValues(t, (val, key) => { switch (key) { case 'lifecycle': // ensure that lifecycle is under 500ms expect(val, 'lifecycle').to.be.within(0, 500) // reset to 100 return 100 case 'test': // ensure test fn duration is within 2000ms expectDurationWithin(val, 'fnDuration', 0, 2000, 400) // ensure test after fn duration is within 500ms expectDurationWithin(val, 'afterFnDuration', 0, 500, 200) return val default: return _.map(val, (hook) => { // ensure test fn duration is within 1500ms expectDurationWithin(hook, 'fnDuration', 0, 1500, 400) // ensure test after fn duration is within 500ms expectDurationWithin(hook, 'afterFnDuration', 0, 500, 200) return hook }) } })) } export const expectRunsToHaveCorrectTimings = (runs = []) => { runs.forEach((run) => { expect(run.config).to.not.exist expectStartToBeBeforeEnd(run, 'stats.wallClockStartedAt', 'stats.wallClockEndedAt') expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') // grab all the wallclock durations for all test (and retried attempts) // because our duration should be at least this const attempts = _.flatMap(run.tests, (test) => test.attempts) const wallClocks = _.sumBy(attempts, 'wallClockDuration') // ensure each run's duration is around the sum // of all tests wallclock duration // TODO: if this remains flaky, increase padding here // and add an additional non-e2e performance test with baseline p95 expectDurationWithin( run, 'stats.wallClockDuration', wallClocks, wallClocks + 400, // add 400ms to account for padding 1234, ) expectDurationWithin( run, 'reporterStats.duration', wallClocks, wallClocks + 400, // add 400ms to account for padding 1234, ) const addFnAndAfterFn = (obj) => { return obj.fnDuration + obj.afterFnDuration } _.each(run.tests, (test) => { try { if (test.displayError) { test.displayError = e2e.normalizeStdout(test.displayError) } const attempts = test.attempts // now make sure that each tests wallclock duration // is around the sum of all of its timings attempts.forEach((attempt) => { if (attempt.error) { attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() } // cannot sum an object, must use array of values const timings = _.sumBy(_.values(attempt.timings), (val) => { if (_.isArray(val)) { // array for hooks return _.sumBy(val, addFnAndAfterFn) } if (_.isObject(val)) { // obj for test itself return addFnAndAfterFn(val) } return val }) expectDurationWithin( attempt, 'wallClockDuration', timings, timings + 80, // add 80ms to account for padding 1234, ) // now reset all the test timings normalizeTestTimings(attempt, 'timings') if (attempt.wallClockStartedAt) { const d = new Date(attempt.wallClockStartedAt) expect(d.toJSON()).to.eq(attempt.wallClockStartedAt) attempt.wallClockStartedAt = STATIC_DATE expect(attempt.videoTimestamp).to.be.a('number') attempt.videoTimestamp = 9999 } }) } catch (e) { e.message = `Error during validation for test \n${e.message}` throw e } }) run.screenshots = _.map(run.screenshots, (screenshot) => { expect(screenshot.screenshotId).to.have.length(5) screenshot.screenshotId = 'some-random-id' const d = new Date(screenshot.takenAt) expect(d.toJSON()).to.eq(screenshot.takenAt) screenshot.takenAt = STATIC_DATE return screenshot }) }) } export const expectCorrectModuleApiResult = (json, opts: { e2ePath: string runs: number video: boolean }) => { if (opts.video == null) { opts.video = true } // should be n runs expect(json.runs).to.have.length(opts.runs) // ensure that config has been set expect(json.config).to.be.an('object') expect(json.config.projectName).to.eq('e2e') expect(json.config.projectRoot).to.eq(opts.e2ePath) // but zero out config because it's too volatile json.config = {} expect(json.browserPath).to.be.a('string') expect(json.browserName).to.be.a('string') expect(json.browserVersion).to.be.a('string') expect(json.osName).to.be.a('string') expect(json.osVersion).to.be.a('string') expect(json.cypressVersion).to.be.a('string') _.extend(json, { browserPath: 'path/to/browser', browserName: 'FooBrowser', browserVersion: '88', osName: 'FooOS', osVersion: '1234', cypressVersion: '9.9.9', }) // ensure the totals are accurate expect(json.totalTests).to.eq( _.sum([ json.totalFailed, json.totalPassed, json.totalPending, json.totalSkipped, ]), ) // ensure totalDuration matches all of the stats durations expectDurationWithin( json, 'totalDuration', _.sumBy(json.runs, 'stats.duration'), _.sumBy(json.runs, 'stats.duration'), 5555, ) expectStartToBeBeforeEnd(json, 'startedTestsAt', 'endedTestsAt') json.runs.forEach((run) => { expectStartToBeBeforeEnd(run, 'stats.startedAt', 'stats.endedAt') expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') const attempts = _.flatMap(run.tests, (test) => test.attempts) const wallClocks = _.sumBy(attempts, 'duration') // ensure each run's duration is around the sum // of all tests wallclock duration expectDurationWithin( run, 'stats.duration', wallClocks, wallClocks + 400, // add 400ms to account for padding 1234, ) expectDurationWithin( run, 'reporterStats.duration', wallClocks, wallClocks + 400, // add 400ms to account for padding 1234, ) run.spec.absolute = e2e.normalizeStdout(run.spec.absolute) _.each(run.tests, (test) => { if (test.displayError) { test.displayError = e2e.normalizeStdout(test.displayError) } }) attempts.forEach((attempt) => { // normalize stack if (attempt.error) { attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() } // normalize startedAt if (attempt.startedAt) { const d = new Date(attempt.startedAt) expect(d.toJSON()).to.eq(attempt.startedAt) attempt.startedAt = STATIC_DATE if (opts.video) { expect(attempt.videoTimestamp).to.be.a('number') attempt.videoTimestamp = 9999 } } attempt.screenshots.forEach((screenshot) => { // expect(screenshot.screenshotId).to.have.length(5) const d = new Date(screenshot.takenAt) expect(d.toJSON()).to.eq(screenshot.takenAt) screenshot.takenAt = STATIC_DATE // screenshot.screenshotId = 'some-random-id' screenshot.path = e2e.normalizeStdout(screenshot.path) }) if (attempt.duration) { expect(attempt.duration).to.be.a('number') attempt.duration = 1234 } }) if (opts.video) { // normalize video path run.video = e2e.normalizeStdout(run.video) } }) }