mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-07 15:31:30 -05:00
refactor: move console logging out of run.ts (#23555)
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
/* eslint-disable no-console, @cypress/dev/arrow-body-multiline-braces */
|
||||
/* eslint-disable no-console, @cypress/dev/arrow-body-multiline-braces */
|
||||
import _ from 'lodash'
|
||||
import la from 'lazy-ass'
|
||||
import pkg from '@packages/root'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import human from 'human-interval'
|
||||
import Debug from 'debug'
|
||||
import Bluebird from 'bluebird'
|
||||
import logSymbols from 'log-symbols'
|
||||
import assert from 'assert'
|
||||
|
||||
import recordMode from './record'
|
||||
@@ -22,25 +20,16 @@ import env from '../util/env'
|
||||
import trash from '../util/trash'
|
||||
import random from '../util/random'
|
||||
import system from '../util/system'
|
||||
import duration from '../util/duration'
|
||||
import newlines from '../util/newlines'
|
||||
import terminal from '../util/terminal'
|
||||
import humanTime from '../util/human_time'
|
||||
import chromePolicyCheck from '../util/chrome_policy_check'
|
||||
import * as experiments from '../experiments'
|
||||
import * as objUtils from '../util/obj_utils'
|
||||
import type { SpecWithRelativeRoot, LaunchOpts, SpecFile, TestingType } from '@packages/types'
|
||||
import type { Cfg } from '../project-base'
|
||||
import type { Browser } from '../browsers/types'
|
||||
import * as printResults from '../util/print-run'
|
||||
|
||||
type Screenshot = {
|
||||
width: number
|
||||
height: number
|
||||
path: string
|
||||
specName: string
|
||||
}
|
||||
type SetScreenshotMetadata = (data: Screenshot) => void
|
||||
type SetScreenshotMetadata = (data: TakeScreenshotProps) => void
|
||||
type ScreenshotMetadata = ReturnType<typeof screenshotMetadata>
|
||||
type TakeScreenshotProps = any
|
||||
type RunEachSpec = any
|
||||
type BeforeSpecRun = any
|
||||
type AfterSpecRun = any
|
||||
@@ -59,43 +48,6 @@ const debug = Debug('cypress:server:run')
|
||||
|
||||
const DELAY_TO_LET_VIDEO_FINISH_MS = 1000
|
||||
|
||||
const color = (val, c) => {
|
||||
return chalk[c](val)
|
||||
}
|
||||
|
||||
const gray = (val) => {
|
||||
return color(val, 'gray')
|
||||
}
|
||||
|
||||
const colorIf = function (val, c) {
|
||||
if (val === 0 || val == null) {
|
||||
val = '-'
|
||||
c = 'gray'
|
||||
}
|
||||
|
||||
return color(val, c)
|
||||
}
|
||||
|
||||
const getSymbol = function (num?: number) {
|
||||
if (num) {
|
||||
return logSymbols.error
|
||||
}
|
||||
|
||||
return logSymbols.success
|
||||
}
|
||||
|
||||
const getWidth = (table, index) => {
|
||||
// get the true width of a table's column,
|
||||
// based off of calculated table options for that column
|
||||
const columnWidth = table.options.colWidths[index]
|
||||
|
||||
if (columnWidth) {
|
||||
return columnWidth - (table.options.style['padding-left'] + table.options.style['padding-right'])
|
||||
}
|
||||
|
||||
throw new Error('Unable to get width for column')
|
||||
}
|
||||
|
||||
const relativeSpecPattern = (projectRoot, pattern) => {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern.replace(`${projectRoot}/`, '')
|
||||
@@ -104,353 +56,6 @@ const relativeSpecPattern = (projectRoot, pattern) => {
|
||||
return pattern.map((x) => x.replace(`${projectRoot}/`, ''))
|
||||
}
|
||||
|
||||
const formatBrowser = (browser) => {
|
||||
// TODO: finish browser
|
||||
return _.compact([
|
||||
browser.displayName,
|
||||
browser.majorVersion,
|
||||
browser.isHeadless && gray('(headless)'),
|
||||
]).join(' ')
|
||||
}
|
||||
|
||||
const formatFooterSummary = (results) => {
|
||||
const { totalFailed, runs } = results
|
||||
|
||||
const isCanceled = _.some(results.runs, { skippedSpec: true })
|
||||
|
||||
// pass or fail color
|
||||
const c = isCanceled ? 'magenta' : totalFailed ? 'red' : 'green'
|
||||
|
||||
const phrase = (() => {
|
||||
if (isCanceled) {
|
||||
return 'The run was canceled'
|
||||
}
|
||||
|
||||
// if we have any specs failing...
|
||||
if (!totalFailed) {
|
||||
return 'All specs passed!'
|
||||
}
|
||||
|
||||
// number of specs
|
||||
const total = runs.length
|
||||
const failingRuns = _.filter(runs, 'stats.failures').length
|
||||
const percent = Math.round((failingRuns / total) * 100)
|
||||
|
||||
return `${failingRuns} of ${total} failed (${percent}%)`
|
||||
})()
|
||||
|
||||
return [
|
||||
isCanceled ? '-' : formatSymbolSummary(totalFailed),
|
||||
color(phrase, c),
|
||||
gray(duration.format(results.totalDuration)),
|
||||
colorIf(results.totalTests, 'reset'),
|
||||
colorIf(results.totalPassed, 'green'),
|
||||
colorIf(totalFailed, 'red'),
|
||||
colorIf(results.totalPending, 'cyan'),
|
||||
colorIf(results.totalSkipped, 'blue'),
|
||||
]
|
||||
}
|
||||
|
||||
const formatSymbolSummary = (failures) => {
|
||||
return getSymbol(failures)
|
||||
}
|
||||
|
||||
const macOSRemovePrivate = (str: string): string => {
|
||||
// consistent snapshots when running system tests on macOS
|
||||
if (process.platform === 'darwin' && str.startsWith('/private')) {
|
||||
return str.slice(8)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
const formatPath = (name, n, colour = 'reset', caller?) => {
|
||||
if (!name) return ''
|
||||
|
||||
const fakeCwdPath = env.get('FAKE_CWD_PATH')
|
||||
|
||||
if (fakeCwdPath && env.get('CYPRESS_INTERNAL_ENV') === 'test') {
|
||||
// if we're testing within Cypress, we want to strip out
|
||||
// the current working directory before calculating the stdout tables
|
||||
// this will keep our snapshots consistent everytime we run
|
||||
const cwdPath = process.cwd()
|
||||
|
||||
name = name
|
||||
.split(cwdPath)
|
||||
.join(fakeCwdPath)
|
||||
|
||||
name = macOSRemovePrivate(name)
|
||||
}
|
||||
|
||||
// add newLines at each n char and colorize the path
|
||||
if (n) {
|
||||
let nameWithNewLines = newlines.addNewlineAtEveryNChar(name, n)
|
||||
|
||||
return `${color(nameWithNewLines, colour)}`
|
||||
}
|
||||
|
||||
return `${color(name, colour)}`
|
||||
}
|
||||
|
||||
const formatNodeVersion = ({ resolvedNodeVersion, resolvedNodePath }: Pick<Cfg, 'resolvedNodeVersion' | 'resolvedNodePath'>, width) => {
|
||||
debug('formatting Node version. %o', { version: resolvedNodeVersion, path: resolvedNodePath })
|
||||
|
||||
if (resolvedNodePath) return formatPath(`v${resolvedNodeVersion} ${gray(`(${resolvedNodePath})`)}`, width)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const formatRecordParams = function (runUrl, parallel, group, tag) {
|
||||
if (runUrl) {
|
||||
if (!group) {
|
||||
group = false
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
tag = false
|
||||
}
|
||||
|
||||
return `Tag: ${tag}, Group: ${group}, Parallel: ${Boolean(parallel)}`
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const displayRunStarting = function (options: { browser: Browser, config: Cfg, group: string | undefined, parallel?: boolean, runUrl?: string, specPattern: string | RegExp | string[], specs: SpecFile[], tag: string | undefined }) {
|
||||
const { browser, config, group, parallel, runUrl, specPattern, specs, tag } = options
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.divider('=')
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Run Starting', {
|
||||
color: ['reset'],
|
||||
})
|
||||
|
||||
console.log('')
|
||||
|
||||
const experimental = experiments.getExperimentsFromResolved(config.resolved)
|
||||
const enabledExperiments = _.pickBy(experimental, _.property('enabled'))
|
||||
const hasExperiments = !_.isEmpty(enabledExperiments)
|
||||
|
||||
// if we show Node Version, then increase 1st column width
|
||||
// to include wider 'Node Version:'.
|
||||
// Without Node version, need to account for possible "Experiments" label
|
||||
const colWidths = config.resolvedNodePath ? [16, 84] : (
|
||||
hasExperiments ? [14, 86] : [12, 88]
|
||||
)
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths,
|
||||
type: 'outsideBorder',
|
||||
})
|
||||
|
||||
const formatSpecPattern = (projectRoot, specPattern) => {
|
||||
// foo.spec.js, bar.spec.js, baz.spec.js
|
||||
// also inserts newlines at col width
|
||||
if (typeof specPattern === 'string') {
|
||||
specPattern = [specPattern]
|
||||
}
|
||||
|
||||
specPattern = relativeSpecPattern(projectRoot, specPattern)
|
||||
|
||||
if (specPattern) {
|
||||
return formatPath(specPattern.join(', '), getWidth(table, 1))
|
||||
}
|
||||
|
||||
throw new Error('No specPattern in formatSpecPattern')
|
||||
}
|
||||
|
||||
const formatSpecs = (specs) => {
|
||||
// 25 found: (foo.spec.js, bar.spec.js, baz.spec.js)
|
||||
const names = _.map(specs, 'baseName')
|
||||
const specsTruncated = _.truncate(names.join(', '), { length: 250 })
|
||||
|
||||
const stringifiedSpecs = [
|
||||
`${names.length} found `,
|
||||
'(',
|
||||
specsTruncated,
|
||||
')',
|
||||
]
|
||||
.join('')
|
||||
|
||||
return formatPath(stringifiedSpecs, getWidth(table, 1))
|
||||
}
|
||||
|
||||
const data = _
|
||||
.chain([
|
||||
[gray('Cypress:'), pkg.version],
|
||||
[gray('Browser:'), formatBrowser(browser)],
|
||||
[gray('Node Version:'), formatNodeVersion(config, getWidth(table, 1))],
|
||||
[gray('Specs:'), formatSpecs(specs)],
|
||||
[gray('Searched:'), formatSpecPattern(config.projectRoot, specPattern)],
|
||||
[gray('Params:'), formatRecordParams(runUrl, parallel, group, tag)],
|
||||
[gray('Run URL:'), runUrl ? formatPath(runUrl, getWidth(table, 1)) : ''],
|
||||
[gray('Experiments:'), hasExperiments ? experiments.formatExperiments(enabledExperiments) : ''],
|
||||
])
|
||||
.filter(_.property(1))
|
||||
.value()
|
||||
|
||||
table.push(...data)
|
||||
|
||||
const heading = table.toString()
|
||||
|
||||
console.log(heading)
|
||||
|
||||
console.log('')
|
||||
|
||||
return heading
|
||||
}
|
||||
|
||||
const displaySpecHeader = function (name, curr, total, estimated) {
|
||||
console.log('')
|
||||
|
||||
const PADDING = 2
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [10, 70, 20],
|
||||
colAligns: ['left', 'left', 'right'],
|
||||
type: 'pageDivider',
|
||||
style: {
|
||||
'padding-left': PADDING,
|
||||
'padding-right': 0,
|
||||
},
|
||||
})
|
||||
|
||||
table.push(['', ''])
|
||||
table.push([
|
||||
'Running:',
|
||||
`${formatPath(name, getWidth(table, 1), 'gray')}`,
|
||||
gray(`(${curr} of ${total})`),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
if (estimated) {
|
||||
const estimatedLabel = `${' '.repeat(PADDING)}Estimated:`
|
||||
|
||||
return console.log(estimatedLabel, gray(humanTime.long(estimated)))
|
||||
}
|
||||
}
|
||||
|
||||
const collectTestResults = (obj: { video?: boolean, screenshots?: Screenshot[] }, estimated) => {
|
||||
return {
|
||||
name: _.get(obj, 'spec.name'),
|
||||
baseName: _.get(obj, 'spec.baseName'),
|
||||
tests: _.get(obj, 'stats.tests'),
|
||||
passes: _.get(obj, 'stats.passes'),
|
||||
pending: _.get(obj, 'stats.pending'),
|
||||
failures: _.get(obj, 'stats.failures'),
|
||||
skipped: _.get(obj, 'stats.skipped'),
|
||||
duration: humanTime.long(_.get(obj, 'stats.wallClockDuration')),
|
||||
estimated: estimated && humanTime.long(estimated),
|
||||
screenshots: obj.screenshots && obj.screenshots.length,
|
||||
video: Boolean(obj.video),
|
||||
}
|
||||
}
|
||||
|
||||
const renderSummaryTable = (runUrl) => {
|
||||
return function (results) {
|
||||
const { runs } = results
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.divider('=')
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Run Finished', {
|
||||
color: ['reset'],
|
||||
})
|
||||
|
||||
if (runs && runs.length) {
|
||||
const colAligns = ['left', 'left', 'right', 'right', 'right', 'right', 'right', 'right']
|
||||
const colWidths = [3, 41, 11, 9, 9, 9, 9, 9]
|
||||
|
||||
const table1 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'noBorder',
|
||||
head: [
|
||||
'',
|
||||
gray('Spec'),
|
||||
'',
|
||||
gray('Tests'),
|
||||
gray('Passing'),
|
||||
gray('Failing'),
|
||||
gray('Pending'),
|
||||
gray('Skipped'),
|
||||
],
|
||||
})
|
||||
|
||||
const table2 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'border',
|
||||
})
|
||||
|
||||
const table3 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'noBorder',
|
||||
head: formatFooterSummary(results),
|
||||
})
|
||||
|
||||
_.each(runs, (run) => {
|
||||
const { spec, stats } = run
|
||||
|
||||
const ms = duration.format(stats.wallClockDuration || 0)
|
||||
|
||||
const formattedSpec = formatPath(spec.baseName, getWidth(table2, 1))
|
||||
|
||||
if (run.skippedSpec) {
|
||||
return table2.push([
|
||||
'-',
|
||||
formattedSpec, color('SKIPPED', 'gray'),
|
||||
'-', '-', '-', '-', '-',
|
||||
])
|
||||
}
|
||||
|
||||
return table2.push([
|
||||
formatSymbolSummary(stats.failures),
|
||||
formattedSpec,
|
||||
color(ms, 'gray'),
|
||||
colorIf(stats.tests, 'reset'),
|
||||
colorIf(stats.passes, 'green'),
|
||||
colorIf(stats.failures, 'red'),
|
||||
colorIf(stats.pending, 'cyan'),
|
||||
colorIf(stats.skipped, 'blue'),
|
||||
])
|
||||
})
|
||||
|
||||
console.log('')
|
||||
console.log('')
|
||||
console.log(terminal.renderTables(table1, table2, table3))
|
||||
console.log('')
|
||||
|
||||
if (runUrl) {
|
||||
console.log('')
|
||||
|
||||
const table4 = terminal.table({
|
||||
colWidths: [100],
|
||||
type: 'pageDivider',
|
||||
style: {
|
||||
'padding-left': 2,
|
||||
},
|
||||
})
|
||||
|
||||
table4.push(['', ''])
|
||||
console.log(terminal.renderTables(table4))
|
||||
|
||||
console.log(` Recorded Run: ${formatPath(runUrl, undefined, 'gray')}`)
|
||||
console.log('')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const iterateThroughSpecs = function (options: { specs: SpecFile[], runEachSpec: RunEachSpec, beforeSpecRun?: BeforeSpecRun, afterSpecRun?: AfterSpecRun, config: Cfg }) {
|
||||
const { specs, runEachSpec, beforeSpecRun, afterSpecRun, config } = options
|
||||
|
||||
@@ -766,84 +371,6 @@ function navigateToNextSpec (spec) {
|
||||
return openProject.changeUrlToSpec(spec)
|
||||
}
|
||||
|
||||
function displayResults (obj = {}, estimated) {
|
||||
const results = collectTestResults(obj, estimated)
|
||||
|
||||
const c = results.failures ? 'red' : 'green'
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Results', {
|
||||
color: [c],
|
||||
})
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [14, 86],
|
||||
type: 'outsideBorder',
|
||||
})
|
||||
|
||||
const data = _.chain([
|
||||
['Tests:', results.tests],
|
||||
['Passing:', results.passes],
|
||||
['Failing:', results.failures],
|
||||
['Pending:', results.pending],
|
||||
['Skipped:', results.skipped],
|
||||
['Screenshots:', results.screenshots],
|
||||
['Video:', results.video],
|
||||
['Duration:', results.duration],
|
||||
estimated ? ['Estimated:', results.estimated] : undefined,
|
||||
['Spec Ran:', formatPath(results.baseName, getWidth(table, 1), c)],
|
||||
])
|
||||
.compact()
|
||||
.map((arr) => {
|
||||
const [key, val] = arr
|
||||
|
||||
return [color(key, 'gray'), color(val, c)]
|
||||
})
|
||||
.value()
|
||||
|
||||
table.push(...data)
|
||||
|
||||
console.log('')
|
||||
console.log(table.toString())
|
||||
console.log('')
|
||||
}
|
||||
|
||||
function displayScreenshots (screenshots: Screenshot[] = []) {
|
||||
console.log('')
|
||||
|
||||
terminal.header('Screenshots', { color: ['yellow'] })
|
||||
|
||||
console.log('')
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 82, 15],
|
||||
colAligns: ['left', 'left', 'right'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
screenshots.forEach((screenshot) => {
|
||||
const dimensions = gray(`(${screenshot.width}x${screenshot.height})`)
|
||||
|
||||
table.push([
|
||||
'-',
|
||||
formatPath(`${screenshot.path}`, getWidth(table, 1)),
|
||||
gray(dimensions),
|
||||
])
|
||||
})
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
console.log('')
|
||||
}
|
||||
|
||||
async function postProcessRecording (name, cname, videoCompression, shouldUploadVideo, quiet, ffmpegChaptersConfig) {
|
||||
debug('ending the video recording %o', { name, videoCompression, shouldUploadVideo })
|
||||
|
||||
@@ -863,78 +390,7 @@ async function postProcessRecording (name, cname, videoCompression, shouldUpload
|
||||
return continueProcessing()
|
||||
}
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Video', {
|
||||
color: ['cyan'],
|
||||
})
|
||||
|
||||
console.log('')
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 21, 76],
|
||||
colAligns: ['left', 'left', 'left'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
table.push([
|
||||
gray('-'),
|
||||
gray('Started processing:'),
|
||||
chalk.cyan(`Compressing to ${videoCompression} CRF`),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
const started = Date.now()
|
||||
let progress = Date.now()
|
||||
const throttle = env.get('VIDEO_COMPRESSION_THROTTLE') || human('10 seconds')
|
||||
|
||||
const onProgress = function (float) {
|
||||
if (float === 1) {
|
||||
const finished = Date.now() - started
|
||||
const dur = `(${humanTime.long(finished)})`
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 21, 61, 15],
|
||||
colAligns: ['left', 'left', 'left', 'right'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
table.push([
|
||||
gray('-'),
|
||||
gray('Finished processing:'),
|
||||
`${formatPath(name, getWidth(table, 2), 'cyan')}`,
|
||||
gray(dur),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
console.log('')
|
||||
}
|
||||
|
||||
if (Date.now() - progress > throttle) {
|
||||
// bump up the progress so we dont
|
||||
// continuously get notifications
|
||||
progress += throttle
|
||||
const percentage = `${Math.ceil(float * 100)}%`
|
||||
|
||||
console.log(' Compression progress: ', chalk.cyan(percentage))
|
||||
}
|
||||
}
|
||||
const { onProgress } = printResults.displayVideoProcessingProgress({ name, videoCompression })
|
||||
|
||||
return continueProcessing(onProgress)
|
||||
}
|
||||
@@ -1152,7 +608,7 @@ function waitForSocketConnection (project, id) {
|
||||
})
|
||||
}
|
||||
|
||||
function waitForTestsToFinishRunning (options: { project: Project, screenshots: Screenshot[], startedVideoCapture?: any, endVideoCapture?: () => Promise<void>, videoName?: string, compressedVideoName?: string, videoCompression: number | false, videoUploadOnPasses: boolean, exit: boolean, spec: SpecWithRelativeRoot, estimated: number, quiet: boolean, config: Cfg, shouldKeepTabOpen: boolean, testingType: TestingType}) {
|
||||
function waitForTestsToFinishRunning (options: { project: Project, screenshots: ScreenshotMetadata[], startedVideoCapture?: any, endVideoCapture?: () => Promise<void>, videoName?: string, compressedVideoName?: string, videoCompression: number | false, videoUploadOnPasses: boolean, exit: boolean, spec: SpecWithRelativeRoot, estimated: number, quiet: boolean, config: Cfg, shouldKeepTabOpen: boolean, testingType: TestingType}) {
|
||||
if (globalThis.CY_TEST_MOCK?.waitForTestsToFinishRunning) return Promise.resolve(globalThis.CY_TEST_MOCK.waitForTestsToFinishRunning)
|
||||
|
||||
const { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated, quiet, config, shouldKeepTabOpen, testingType } = options
|
||||
@@ -1228,10 +684,7 @@ function waitForTestsToFinishRunning (options: { project: Project, screenshots:
|
||||
results.shouldUploadVideo = shouldUploadVideo
|
||||
|
||||
if (!quiet && !skippedSpec) {
|
||||
displayResults(results, estimated)
|
||||
if (screenshots && screenshots.length) {
|
||||
displayScreenshots(screenshots)
|
||||
}
|
||||
printResults.displayResults(results, estimated)
|
||||
}
|
||||
|
||||
const project = openProject.getProject()
|
||||
@@ -1284,6 +737,7 @@ function screenshotMetadata (data, resp) {
|
||||
path: resp.path,
|
||||
height: resp.dimensions.height,
|
||||
width: resp.dimensions.width,
|
||||
pathname: undefined as string | undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1298,7 +752,7 @@ async function runSpecs (options: { config: Cfg, browser: Browser, sys: any, hea
|
||||
browser.isHeaded = !isHeadless
|
||||
|
||||
if (!options.quiet) {
|
||||
displayRunStarting({
|
||||
printResults.displayRunStarting({
|
||||
config,
|
||||
specs,
|
||||
group,
|
||||
@@ -1314,7 +768,7 @@ async function runSpecs (options: { config: Cfg, browser: Browser, sys: any, hea
|
||||
|
||||
async function runEachSpec (spec: SpecWithRelativeRoot, index: number, length: number, estimated: number) {
|
||||
if (!options.quiet) {
|
||||
displaySpecHeader(spec.baseName, index + 1, length, estimated)
|
||||
printResults.displaySpecHeader(spec.baseName, index + 1, length, estimated)
|
||||
}
|
||||
|
||||
const { results } = await runSpec(config, spec, options, estimated, isFirstSpec, index === length - 1)
|
||||
@@ -1601,7 +1055,7 @@ async function ready (options: { projectRoot: string, record: boolean, key: stri
|
||||
})
|
||||
|
||||
if (!options.quiet) {
|
||||
renderSummaryTable(runUrl)(results)
|
||||
printResults.renderSummaryTable(runUrl, results)
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
/* eslint-disable no-console */
|
||||
import _ from 'lodash'
|
||||
import logSymbols from 'log-symbols'
|
||||
import chalk from 'chalk'
|
||||
import human from 'human-interval'
|
||||
import pkg from '@packages/root'
|
||||
import humanTime from './human_time'
|
||||
import duration from './duration'
|
||||
import newlines from './newlines'
|
||||
import env from './env'
|
||||
import terminal from './terminal'
|
||||
import * as experiments from '../experiments'
|
||||
import type { SpecFile } from '@packages/types'
|
||||
import type { Cfg } from '../project-base'
|
||||
import type { Browser } from '../browsers/types'
|
||||
import type { Table } from 'cli-table3'
|
||||
|
||||
type Screenshot = {
|
||||
width: number
|
||||
height: number
|
||||
path: string
|
||||
specName: string
|
||||
}
|
||||
|
||||
function color (val: any, c: string) {
|
||||
return chalk[c](val)
|
||||
}
|
||||
|
||||
function gray (val: any) {
|
||||
return color(val, 'gray')
|
||||
}
|
||||
|
||||
function colorIf (val: any, c: string) {
|
||||
if (val === 0 || val == null) {
|
||||
val = '-'
|
||||
c = 'gray'
|
||||
}
|
||||
|
||||
return color(val, c)
|
||||
}
|
||||
|
||||
function getWidth (table: Table, index: number) {
|
||||
// get the true width of a table's column,
|
||||
// based off of calculated table options for that column
|
||||
const columnWidth = table.options.colWidths[index]
|
||||
|
||||
if (columnWidth) {
|
||||
return columnWidth - (table.options.style['padding-left'] + table.options.style['padding-right'])
|
||||
}
|
||||
|
||||
throw new Error('Unable to get width for column')
|
||||
}
|
||||
|
||||
function formatBrowser (browser: Browser) {
|
||||
return _.compact([
|
||||
browser.displayName,
|
||||
browser.majorVersion,
|
||||
browser.isHeadless && gray('(headless)'),
|
||||
]).join(' ')
|
||||
}
|
||||
|
||||
function formatFooterSummary (results: any) {
|
||||
const { totalFailed, runs } = results
|
||||
|
||||
const isCanceled = _.some(results.runs, { skippedSpec: true })
|
||||
|
||||
// pass or fail color
|
||||
const c = isCanceled ? 'magenta' : totalFailed ? 'red' : 'green'
|
||||
|
||||
const phrase = (() => {
|
||||
if (isCanceled) {
|
||||
return 'The run was canceled'
|
||||
}
|
||||
|
||||
// if we have any specs failing...
|
||||
if (!totalFailed) {
|
||||
return 'All specs passed!'
|
||||
}
|
||||
|
||||
// number of specs
|
||||
const total = runs.length
|
||||
const failingRuns = _.filter(runs, 'stats.failures').length
|
||||
const percent = Math.round((failingRuns / total) * 100)
|
||||
|
||||
return `${failingRuns} of ${total} failed (${percent}%)`
|
||||
})()
|
||||
|
||||
return [
|
||||
isCanceled ? '-' : formatSymbolSummary(totalFailed),
|
||||
color(phrase, c),
|
||||
gray(duration.format(results.totalDuration)),
|
||||
colorIf(results.totalTests, 'reset'),
|
||||
colorIf(results.totalPassed, 'green'),
|
||||
colorIf(totalFailed, 'red'),
|
||||
colorIf(results.totalPending, 'cyan'),
|
||||
colorIf(results.totalSkipped, 'blue'),
|
||||
]
|
||||
}
|
||||
|
||||
function formatSymbolSummary (failures: number) {
|
||||
return failures ? logSymbols.error : logSymbols.success
|
||||
}
|
||||
|
||||
function macOSRemovePrivate (str: string) {
|
||||
// consistent snapshots when running system tests on macOS
|
||||
if (process.platform === 'darwin' && str.startsWith('/private')) {
|
||||
return str.slice(8)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
function collectTestResults (obj: { video?: boolean, screenshots?: Screenshot[], spec?: any, stats?: any }, estimated: number) {
|
||||
return {
|
||||
name: _.get(obj, 'spec.name'),
|
||||
baseName: _.get(obj, 'spec.baseName'),
|
||||
tests: _.get(obj, 'stats.tests'),
|
||||
passes: _.get(obj, 'stats.passes'),
|
||||
pending: _.get(obj, 'stats.pending'),
|
||||
failures: _.get(obj, 'stats.failures'),
|
||||
skipped: _.get(obj, 'stats.skipped'),
|
||||
duration: humanTime.long(_.get(obj, 'stats.wallClockDuration')),
|
||||
estimated: estimated && humanTime.long(estimated),
|
||||
screenshots: obj.screenshots && obj.screenshots.length,
|
||||
video: Boolean(obj.video),
|
||||
}
|
||||
}
|
||||
|
||||
function formatPath (name: string, n: number | undefined, pathColor = 'reset') {
|
||||
if (!name) return ''
|
||||
|
||||
const fakeCwdPath = env.get('FAKE_CWD_PATH')
|
||||
|
||||
if (fakeCwdPath && env.get('CYPRESS_INTERNAL_ENV') === 'test') {
|
||||
// if we're testing within Cypress, we want to strip out
|
||||
// the current working directory before calculating the stdout tables
|
||||
// this will keep our snapshots consistent everytime we run
|
||||
const cwdPath = process.cwd()
|
||||
|
||||
name = name
|
||||
.split(cwdPath)
|
||||
.join(fakeCwdPath)
|
||||
|
||||
name = macOSRemovePrivate(name)
|
||||
}
|
||||
|
||||
// add newLines at each n char and colorize the path
|
||||
if (n) {
|
||||
let nameWithNewLines = newlines.addNewlineAtEveryNChar(name, n)
|
||||
|
||||
return `${color(nameWithNewLines, pathColor)}`
|
||||
}
|
||||
|
||||
return `${color(name, pathColor)}`
|
||||
}
|
||||
|
||||
function formatNodeVersion ({ resolvedNodeVersion, resolvedNodePath }: Pick<Cfg, 'resolvedNodeVersion' | 'resolvedNodePath'>, width: number) {
|
||||
if (resolvedNodePath) return formatPath(`v${resolvedNodeVersion} ${gray(`(${resolvedNodePath})`)}`, width)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
function formatRecordParams (runUrl?: string, parallel?: boolean, group?: string, tag?: string) {
|
||||
if (runUrl) {
|
||||
return `Tag: ${tag || 'false'}, Group: ${group || 'false'}, Parallel: ${Boolean(parallel)}`
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
export function displayRunStarting (options: { browser: Browser, config: Cfg, group: string | undefined, parallel?: boolean, runUrl?: string, specPattern: string | RegExp | string[], specs: SpecFile[], tag: string | undefined }) {
|
||||
const { browser, config, group, parallel, runUrl, specPattern, specs, tag } = options
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.divider('=')
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Run Starting', {
|
||||
color: ['reset'],
|
||||
})
|
||||
|
||||
console.log('')
|
||||
|
||||
const experimental = experiments.getExperimentsFromResolved(config.resolved)
|
||||
const enabledExperiments = _.pickBy(experimental, _.property('enabled'))
|
||||
const hasExperiments = !_.isEmpty(enabledExperiments)
|
||||
|
||||
// if we show Node Version, then increase 1st column width
|
||||
// to include wider 'Node Version:'.
|
||||
// Without Node version, need to account for possible "Experiments" label
|
||||
const colWidths = config.resolvedNodePath ? [16, 84] : (
|
||||
hasExperiments ? [14, 86] : [12, 88]
|
||||
)
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths,
|
||||
type: 'outsideBorder',
|
||||
}) as Table
|
||||
|
||||
if (!specPattern) throw new Error('No specPattern in displayRunStarting')
|
||||
|
||||
const formatSpecs = (specs) => {
|
||||
// 25 found: (foo.spec.js, bar.spec.js, baz.spec.js)
|
||||
const names = _.map(specs, 'baseName')
|
||||
const specsTruncated = _.truncate(names.join(', '), { length: 250 })
|
||||
|
||||
const stringifiedSpecs = [
|
||||
`${names.length} found `,
|
||||
'(',
|
||||
specsTruncated,
|
||||
')',
|
||||
]
|
||||
.join('')
|
||||
|
||||
return formatPath(stringifiedSpecs, getWidth(table, 1))
|
||||
}
|
||||
|
||||
const data = _
|
||||
.chain([
|
||||
[gray('Cypress:'), pkg.version],
|
||||
[gray('Browser:'), formatBrowser(browser)],
|
||||
[gray('Node Version:'), formatNodeVersion(config, getWidth(table, 1))],
|
||||
[gray('Specs:'), formatSpecs(specs)],
|
||||
[gray('Searched:'), formatPath(Array.isArray(specPattern) ? specPattern.join(', ') : String(specPattern), getWidth(table, 1))],
|
||||
[gray('Params:'), formatRecordParams(runUrl, parallel, group, tag)],
|
||||
[gray('Run URL:'), runUrl ? formatPath(runUrl, getWidth(table, 1)) : ''],
|
||||
[gray('Experiments:'), hasExperiments ? experiments.formatExperiments(enabledExperiments) : ''],
|
||||
])
|
||||
.filter(_.property(1))
|
||||
.value()
|
||||
|
||||
// @ts-expect-error incorrect type in Table
|
||||
table.push(...data)
|
||||
|
||||
const heading = table.toString()
|
||||
|
||||
console.log(heading)
|
||||
|
||||
console.log('')
|
||||
|
||||
return heading
|
||||
}
|
||||
|
||||
export function displaySpecHeader (name: string, curr: number, total: number, estimated: number) {
|
||||
console.log('')
|
||||
|
||||
const PADDING = 2
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [10, 70, 20],
|
||||
colAligns: ['left', 'left', 'right'],
|
||||
type: 'pageDivider',
|
||||
style: {
|
||||
'padding-left': PADDING,
|
||||
'padding-right': 0,
|
||||
},
|
||||
})
|
||||
|
||||
table.push(['', ''])
|
||||
table.push([
|
||||
'Running:',
|
||||
`${formatPath(name, getWidth(table, 1), 'gray')}`,
|
||||
gray(`(${curr} of ${total})`),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
if (estimated) {
|
||||
const estimatedLabel = `${' '.repeat(PADDING)}Estimated:`
|
||||
|
||||
return console.log(estimatedLabel, gray(humanTime.long(estimated)))
|
||||
}
|
||||
}
|
||||
|
||||
export function renderSummaryTable (runUrl: string | undefined, results: any) {
|
||||
const { runs } = results
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.divider('=')
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Run Finished', {
|
||||
color: ['reset'],
|
||||
})
|
||||
|
||||
if (runs && runs.length) {
|
||||
const colAligns = ['left', 'left', 'right', 'right', 'right', 'right', 'right', 'right']
|
||||
const colWidths = [3, 41, 11, 9, 9, 9, 9, 9]
|
||||
|
||||
const table1 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'noBorder',
|
||||
head: [
|
||||
'',
|
||||
gray('Spec'),
|
||||
'',
|
||||
gray('Tests'),
|
||||
gray('Passing'),
|
||||
gray('Failing'),
|
||||
gray('Pending'),
|
||||
gray('Skipped'),
|
||||
],
|
||||
})
|
||||
|
||||
const table2 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'border',
|
||||
})
|
||||
|
||||
const table3 = terminal.table({
|
||||
colAligns,
|
||||
colWidths,
|
||||
type: 'noBorder',
|
||||
head: formatFooterSummary(results),
|
||||
})
|
||||
|
||||
_.each(runs, (run) => {
|
||||
const { spec, stats } = run
|
||||
|
||||
const ms = duration.format(stats.wallClockDuration || 0)
|
||||
|
||||
const formattedSpec = formatPath(spec.baseName, getWidth(table2, 1))
|
||||
|
||||
if (run.skippedSpec) {
|
||||
return table2.push([
|
||||
'-',
|
||||
formattedSpec, color('SKIPPED', 'gray'),
|
||||
'-', '-', '-', '-', '-',
|
||||
])
|
||||
}
|
||||
|
||||
return table2.push([
|
||||
formatSymbolSummary(stats.failures),
|
||||
formattedSpec,
|
||||
color(ms, 'gray'),
|
||||
colorIf(stats.tests, 'reset'),
|
||||
colorIf(stats.passes, 'green'),
|
||||
colorIf(stats.failures, 'red'),
|
||||
colorIf(stats.pending, 'cyan'),
|
||||
colorIf(stats.skipped, 'blue'),
|
||||
])
|
||||
})
|
||||
|
||||
console.log('')
|
||||
console.log('')
|
||||
console.log(terminal.renderTables(table1, table2, table3))
|
||||
console.log('')
|
||||
|
||||
if (runUrl) {
|
||||
console.log('')
|
||||
|
||||
const table4 = terminal.table({
|
||||
colWidths: [100],
|
||||
type: 'pageDivider',
|
||||
style: {
|
||||
'padding-left': 2,
|
||||
},
|
||||
})
|
||||
|
||||
table4.push(['', ''])
|
||||
console.log(terminal.renderTables(table4))
|
||||
|
||||
console.log(` Recorded Run: ${formatPath(runUrl, undefined, 'gray')}`)
|
||||
console.log('')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function displayResults (obj: { screenshots?: Screenshot[] }, estimated: number) {
|
||||
const results = collectTestResults(obj, estimated)
|
||||
|
||||
const c = results.failures ? 'red' : 'green'
|
||||
|
||||
console.log('')
|
||||
|
||||
terminal.header('Results', {
|
||||
color: [c],
|
||||
})
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [14, 86],
|
||||
type: 'outsideBorder',
|
||||
})
|
||||
|
||||
const data = _.chain([
|
||||
['Tests:', results.tests],
|
||||
['Passing:', results.passes],
|
||||
['Failing:', results.failures],
|
||||
['Pending:', results.pending],
|
||||
['Skipped:', results.skipped],
|
||||
['Screenshots:', results.screenshots],
|
||||
['Video:', results.video],
|
||||
['Duration:', results.duration],
|
||||
estimated ? ['Estimated:', results.estimated] : undefined,
|
||||
['Spec Ran:', formatPath(results.baseName, getWidth(table, 1), c)],
|
||||
])
|
||||
.compact()
|
||||
.map((arr) => {
|
||||
const [key, val] = arr
|
||||
|
||||
return [color(key, 'gray'), color(val, c)]
|
||||
})
|
||||
.value()
|
||||
|
||||
table.push(...data)
|
||||
|
||||
console.log('')
|
||||
console.log(table.toString())
|
||||
console.log('')
|
||||
|
||||
if (obj.screenshots?.length) displayScreenshots(obj.screenshots)
|
||||
}
|
||||
|
||||
function displayScreenshots (screenshots: Screenshot[] = []) {
|
||||
console.log('')
|
||||
|
||||
terminal.header('Screenshots', { color: ['yellow'] })
|
||||
|
||||
console.log('')
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 82, 15],
|
||||
colAligns: ['left', 'left', 'right'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
screenshots.forEach((screenshot) => {
|
||||
const dimensions = gray(`(${screenshot.width}x${screenshot.height})`)
|
||||
|
||||
table.push([
|
||||
'-',
|
||||
formatPath(`${screenshot.path}`, getWidth(table, 1)),
|
||||
gray(dimensions),
|
||||
])
|
||||
})
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
console.log('')
|
||||
}
|
||||
|
||||
export function displayVideoProcessingProgress (opts: { name: string, videoCompression: number | false }) {
|
||||
console.log('')
|
||||
|
||||
terminal.header('Video', {
|
||||
color: ['cyan'],
|
||||
})
|
||||
|
||||
console.log('')
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 21, 76],
|
||||
colAligns: ['left', 'left', 'left'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
table.push([
|
||||
gray('-'),
|
||||
gray('Started processing:'),
|
||||
chalk.cyan(`Compressing to ${opts.videoCompression} CRF`),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
const started = Date.now()
|
||||
let progress = Date.now()
|
||||
const throttle = env.get('VIDEO_COMPRESSION_THROTTLE') || human('10 seconds')
|
||||
|
||||
return {
|
||||
onProgress (float: number) {
|
||||
if (float === 1) {
|
||||
const finished = Date.now() - started
|
||||
const dur = `(${humanTime.long(finished)})`
|
||||
|
||||
const table = terminal.table({
|
||||
colWidths: [3, 21, 61, 15],
|
||||
colAligns: ['left', 'left', 'left', 'right'],
|
||||
type: 'noBorder',
|
||||
style: {
|
||||
'padding-right': 0,
|
||||
},
|
||||
chars: {
|
||||
'left': ' ',
|
||||
'right': '',
|
||||
},
|
||||
})
|
||||
|
||||
table.push([
|
||||
gray('-'),
|
||||
gray('Finished processing:'),
|
||||
`${formatPath(opts.name, getWidth(table, 2), 'cyan')}`,
|
||||
gray(dur),
|
||||
])
|
||||
|
||||
console.log(table.toString())
|
||||
|
||||
console.log('')
|
||||
}
|
||||
|
||||
if (Date.now() - progress > throttle) {
|
||||
// bump up the progress so we dont
|
||||
// continuously get notifications
|
||||
progress += throttle
|
||||
const percentage = `${Math.ceil(float * 100)}%`
|
||||
|
||||
console.log(' Compression progress: ', chalk.cyan(percentage))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user