refactor: move console logging out of run.ts (#23555)

This commit is contained in:
Zach Bloomquist
2022-08-29 15:05:58 -04:00
committed by GitHub
parent 2f337dbfa2
commit 4871ebc333
2 changed files with 541 additions and 557 deletions
+11 -557
View File
@@ -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
+530
View File
@@ -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))
}
},
}
}