@ohos-ports/web-test-runner 1.0.0-beta.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.
- package/README.md +65 -0
- package/dist/TestRunnerStartError.d.ts +3 -0
- package/dist/TestRunnerStartError.d.ts.map +1 -0
- package/dist/TestRunnerStartError.js +7 -0
- package/dist/TestRunnerStartError.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +6 -0
- package/dist/bin.js.map +1 -0
- package/dist/config/TestRunnerConfig.d.ts +10 -0
- package/dist/config/TestRunnerConfig.d.ts.map +1 -0
- package/dist/config/TestRunnerConfig.js +3 -0
- package/dist/config/TestRunnerConfig.js.map +1 -0
- package/dist/config/collectGroupConfigs.d.ts +3 -0
- package/dist/config/collectGroupConfigs.d.ts.map +1 -0
- package/dist/config/collectGroupConfigs.js +43 -0
- package/dist/config/collectGroupConfigs.js.map +1 -0
- package/dist/config/loadLauncher.d.ts +3 -0
- package/dist/config/loadLauncher.d.ts.map +1 -0
- package/dist/config/loadLauncher.js +43 -0
- package/dist/config/loadLauncher.js.map +1 -0
- package/dist/config/mergeConfigs.d.ts +3 -0
- package/dist/config/mergeConfigs.d.ts.map +1 -0
- package/dist/config/mergeConfigs.js +26 -0
- package/dist/config/mergeConfigs.js.map +1 -0
- package/dist/config/parseConfig.d.ts +9 -0
- package/dist/config/parseConfig.d.ts.map +1 -0
- package/dist/config/parseConfig.js +216 -0
- package/dist/config/parseConfig.js.map +1 -0
- package/dist/config/readCliArgs.d.ts +15 -0
- package/dist/config/readCliArgs.d.ts.map +1 -0
- package/dist/config/readCliArgs.js +148 -0
- package/dist/config/readCliArgs.js.map +1 -0
- package/dist/config/readFileConfig.d.ts +18 -0
- package/dist/config/readFileConfig.d.ts.map +1 -0
- package/dist/config/readFileConfig.js +22 -0
- package/dist/config/readFileConfig.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/TestRunnerLogger.d.ts +15 -0
- package/dist/logger/TestRunnerLogger.d.ts.map +1 -0
- package/dist/logger/TestRunnerLogger.js +48 -0
- package/dist/logger/TestRunnerLogger.js.map +1 -0
- package/dist/reporter/defaultReporter.d.ts +7 -0
- package/dist/reporter/defaultReporter.d.ts.map +1 -0
- package/dist/reporter/defaultReporter.js +51 -0
- package/dist/reporter/defaultReporter.js.map +1 -0
- package/dist/reporter/dotReporter.d.ts +4 -0
- package/dist/reporter/dotReporter.d.ts.map +1 -0
- package/dist/reporter/dotReporter.js +53 -0
- package/dist/reporter/dotReporter.js.map +1 -0
- package/dist/reporter/getCodeCoverage.d.ts +3 -0
- package/dist/reporter/getCodeCoverage.d.ts.map +1 -0
- package/dist/reporter/getCodeCoverage.js +36 -0
- package/dist/reporter/getCodeCoverage.js.map +1 -0
- package/dist/reporter/getTestProgress.d.ts +16 -0
- package/dist/reporter/getTestProgress.d.ts.map +1 -0
- package/dist/reporter/getTestProgress.js +105 -0
- package/dist/reporter/getTestProgress.js.map +1 -0
- package/dist/reporter/renderProgressBar.d.ts +2 -0
- package/dist/reporter/renderProgressBar.d.ts.map +1 -0
- package/dist/reporter/renderProgressBar.js +23 -0
- package/dist/reporter/renderProgressBar.js.map +1 -0
- package/dist/reporter/reportBrowserLogs.d.ts +3 -0
- package/dist/reporter/reportBrowserLogs.d.ts.map +1 -0
- package/dist/reporter/reportBrowserLogs.js +61 -0
- package/dist/reporter/reportBrowserLogs.js.map +1 -0
- package/dist/reporter/reportRequest404s.d.ts +3 -0
- package/dist/reporter/reportRequest404s.d.ts.map +1 -0
- package/dist/reporter/reportRequest404s.js +54 -0
- package/dist/reporter/reportRequest404s.js.map +1 -0
- package/dist/reporter/reportTestFileErrors.d.ts +3 -0
- package/dist/reporter/reportTestFileErrors.d.ts.map +1 -0
- package/dist/reporter/reportTestFileErrors.js +52 -0
- package/dist/reporter/reportTestFileErrors.js.map +1 -0
- package/dist/reporter/reportTestFileResults.d.ts +3 -0
- package/dist/reporter/reportTestFileResults.d.ts.map +1 -0
- package/dist/reporter/reportTestFileResults.js +25 -0
- package/dist/reporter/reportTestFileResults.js.map +1 -0
- package/dist/reporter/reportTestsErrors.d.ts +4 -0
- package/dist/reporter/reportTestsErrors.d.ts.map +1 -0
- package/dist/reporter/reportTestsErrors.js +129 -0
- package/dist/reporter/reportTestsErrors.js.map +1 -0
- package/dist/reporter/summaryReporter.d.ts +10 -0
- package/dist/reporter/summaryReporter.d.ts.map +1 -0
- package/dist/reporter/summaryReporter.js +73 -0
- package/dist/reporter/summaryReporter.js.map +1 -0
- package/dist/reporter/utils/getFailedOnBrowsers.d.ts +2 -0
- package/dist/reporter/utils/getFailedOnBrowsers.d.ts.map +1 -0
- package/dist/reporter/utils/getFailedOnBrowsers.js +13 -0
- package/dist/reporter/utils/getFailedOnBrowsers.js.map +1 -0
- package/dist/reporter/utils/getFlattenedTestResults.d.ts +3 -0
- package/dist/reporter/utils/getFlattenedTestResults.d.ts.map +1 -0
- package/dist/reporter/utils/getFlattenedTestResults.js +21 -0
- package/dist/reporter/utils/getFlattenedTestResults.js.map +1 -0
- package/dist/reporter/utils/getPassedFailedSkippedCount.d.ts +7 -0
- package/dist/reporter/utils/getPassedFailedSkippedCount.d.ts.map +1 -0
- package/dist/reporter/utils/getPassedFailedSkippedCount.js +30 -0
- package/dist/reporter/utils/getPassedFailedSkippedCount.js.map +1 -0
- package/dist/reporter/utils/toBrowserPath.d.ts +2 -0
- package/dist/reporter/utils/toBrowserPath.d.ts.map +1 -0
- package/dist/reporter/utils/toBrowserPath.js +11 -0
- package/dist/reporter/utils/toBrowserPath.js.map +1 -0
- package/dist/reporter/utils/toFilePath.d.ts +6 -0
- package/dist/reporter/utils/toFilePath.d.ts.map +1 -0
- package/dist/reporter/utils/toFilePath.js +15 -0
- package/dist/reporter/utils/toFilePath.js.map +1 -0
- package/dist/startTestRunner.d.ts +33 -0
- package/dist/startTestRunner.d.ts.map +1 -0
- package/dist/startTestRunner.js +61 -0
- package/dist/startTestRunner.js.map +1 -0
- package/index.d.ts +2 -0
- package/index.mjs +38 -0
- package/package.json +107 -0
- package/scripts/ohos-postinstall-sign.js +117 -0
- package/src/TestRunnerStartError.ts +1 -0
- package/src/bin.ts +4 -0
- package/src/config/TestRunnerConfig.ts +14 -0
- package/src/config/collectGroupConfigs.ts +50 -0
- package/src/config/loadLauncher.ts +48 -0
- package/src/config/mergeConfigs.ts +26 -0
- package/src/config/parseConfig.ts +286 -0
- package/src/config/readCliArgs.ts +187 -0
- package/src/config/readFileConfig.ts +35 -0
- package/src/index.ts +11 -0
- package/src/logger/TestRunnerLogger.ts +54 -0
- package/src/reporter/defaultReporter.ts +78 -0
- package/src/reporter/dotReporter.ts +65 -0
- package/src/reporter/getCodeCoverage.ts +54 -0
- package/src/reporter/getTestProgress.ts +181 -0
- package/src/reporter/renderProgressBar.ts +27 -0
- package/src/reporter/reportBrowserLogs.ts +64 -0
- package/src/reporter/reportRequest404s.ts +55 -0
- package/src/reporter/reportTestFileErrors.ts +70 -0
- package/src/reporter/reportTestFileResults.ts +33 -0
- package/src/reporter/reportTestsErrors.ts +110 -0
- package/src/reporter/summaryReporter.ts +123 -0
- package/src/reporter/utils/getFailedOnBrowsers.ts +10 -0
- package/src/reporter/utils/getFlattenedTestResults.ts +24 -0
- package/src/reporter/utils/getPassedFailedSkippedCount.ts +31 -0
- package/src/reporter/utils/toBrowserPath.ts +5 -0
- package/src/reporter/utils/toFilePath.ts +9 -0
- package/src/startTestRunner.ts +101 -0
- package/web-test-runner.config.mjs +8 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BufferedLogger,
|
|
3
|
+
type Logger,
|
|
4
|
+
type Reporter,
|
|
5
|
+
type ReporterArgs,
|
|
6
|
+
} from '@web/test-runner-core';
|
|
7
|
+
|
|
8
|
+
import { getTestProgressReport } from './getTestProgress.js';
|
|
9
|
+
import { reportTestFileResults } from './reportTestFileResults.js';
|
|
10
|
+
|
|
11
|
+
export interface DefaultReporterArgs {
|
|
12
|
+
reportTestResults?: boolean;
|
|
13
|
+
reportTestProgress?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isBufferedLogger(logger: Logger): logger is BufferedLogger {
|
|
17
|
+
return (
|
|
18
|
+
typeof (logger as any).logBufferedMessages === 'function' &&
|
|
19
|
+
Array.isArray((logger as any).buffer)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaultReporter({
|
|
24
|
+
reportTestResults = true,
|
|
25
|
+
reportTestProgress = true,
|
|
26
|
+
}: DefaultReporterArgs = {}): Reporter {
|
|
27
|
+
let args: ReporterArgs;
|
|
28
|
+
let favoriteBrowser: string;
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
start(_args) {
|
|
32
|
+
args = _args;
|
|
33
|
+
favoriteBrowser =
|
|
34
|
+
args.browserNames.find(name => {
|
|
35
|
+
const n = name.toLowerCase();
|
|
36
|
+
return n.includes('chrome') || n.includes('chromium') || n.includes('firefox');
|
|
37
|
+
}) ?? args.browserNames[0];
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
reportTestFileResults({ logger, sessionsForTestFile, testFile }) {
|
|
41
|
+
if (!reportTestResults) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isBufferedLogger(logger)) {
|
|
46
|
+
throw new Error('Expected a BufferedLogger instance.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return reportTestFileResults(
|
|
50
|
+
logger,
|
|
51
|
+
testFile,
|
|
52
|
+
args.browserNames,
|
|
53
|
+
favoriteBrowser,
|
|
54
|
+
sessionsForTestFile,
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
getTestProgress({ testRun, focusedTestFile, testCoverage }) {
|
|
59
|
+
if (!reportTestProgress) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return getTestProgressReport(args.config, {
|
|
64
|
+
browsers: args.browsers,
|
|
65
|
+
browserNames: args.browserNames,
|
|
66
|
+
testRun,
|
|
67
|
+
testFiles: args.testFiles,
|
|
68
|
+
sessions: args.sessions,
|
|
69
|
+
startTime: args.startTime,
|
|
70
|
+
focusedTestFile,
|
|
71
|
+
watch: args.config.watch,
|
|
72
|
+
coverage: !!args.config.coverage,
|
|
73
|
+
coverageConfig: args.config.coverageConfig,
|
|
74
|
+
testCoverage,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Reporter, ReporterArgs, TestSuiteResult } from '@web/test-runner-core';
|
|
2
|
+
|
|
3
|
+
import { reportTestFileErrors } from './reportTestFileErrors.js';
|
|
4
|
+
import { reportTestsErrors } from './reportTestsErrors.js';
|
|
5
|
+
|
|
6
|
+
import { TestRunnerLogger } from '../logger/TestRunnerLogger.js';
|
|
7
|
+
|
|
8
|
+
const color =
|
|
9
|
+
([x, y]: [number, number]) =>
|
|
10
|
+
(z: string) =>
|
|
11
|
+
`\x1b[${x}m${z}\x1b[${y}m${reset}`;
|
|
12
|
+
const reset = `\x1b[0m\x1b[0m`;
|
|
13
|
+
const red = color([31, 89]);
|
|
14
|
+
|
|
15
|
+
/** Test reporter that summarizes all test for a given run using dot notation */
|
|
16
|
+
export function dotReporter(): Reporter {
|
|
17
|
+
let args: ReporterArgs;
|
|
18
|
+
let favoriteBrowser: string;
|
|
19
|
+
|
|
20
|
+
const logger = new TestRunnerLogger();
|
|
21
|
+
|
|
22
|
+
function log(passed: boolean) {
|
|
23
|
+
logger.log(passed ? '.' : red('x'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function logResults(results?: TestSuiteResult) {
|
|
27
|
+
for (const result of results?.tests ?? []) {
|
|
28
|
+
log(result.passed);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const suite of results?.suites ?? []) {
|
|
32
|
+
logSuite(suite);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function logSuite(suite: TestSuiteResult) {
|
|
37
|
+
logResults(suite);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
start(_args) {
|
|
42
|
+
args = _args;
|
|
43
|
+
favoriteBrowser =
|
|
44
|
+
args.browserNames.find(name => {
|
|
45
|
+
const n = name.toLowerCase();
|
|
46
|
+
return n.includes('chrome') || n.includes('chromium') || n.includes('firefox');
|
|
47
|
+
}) ?? args.browserNames[0];
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
reportTestFileResults({ sessionsForTestFile: sessions }) {
|
|
51
|
+
for (const session of sessions) {
|
|
52
|
+
logResults(session.testResults);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
onTestRunFinished({ sessions }) {
|
|
57
|
+
const failedSessions = sessions.filter(s => !s.passed);
|
|
58
|
+
if (failedSessions.length > 0) {
|
|
59
|
+
logger.log('\n\nErrors Reported in Tests:\n\n');
|
|
60
|
+
reportTestsErrors(logger, args.browserNames, favoriteBrowser, failedSessions);
|
|
61
|
+
reportTestFileErrors(logger, args.browserNames, favoriteBrowser, failedSessions, true);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CoverageConfig,
|
|
3
|
+
type CoverageThresholdConfig,
|
|
4
|
+
type TestCoverage,
|
|
5
|
+
} from '@web/test-runner-core';
|
|
6
|
+
import { bold, green, red, underline } from 'nanocolors';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const coverageTypes: (keyof CoverageThresholdConfig)[] = [
|
|
10
|
+
'lines',
|
|
11
|
+
'statements',
|
|
12
|
+
'branches',
|
|
13
|
+
'functions',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export function getCodeCoverage(
|
|
17
|
+
testCoverage: TestCoverage,
|
|
18
|
+
watch: boolean,
|
|
19
|
+
coverageConfig: CoverageConfig,
|
|
20
|
+
) {
|
|
21
|
+
const entries: string[] = [];
|
|
22
|
+
|
|
23
|
+
const coverageSum = coverageTypes.reduce((all, type) => all + testCoverage.summary[type].pct, 0);
|
|
24
|
+
const avgCoverage = Math.round((coverageSum * 100) / 4) / 100;
|
|
25
|
+
|
|
26
|
+
if (!Number.isNaN(avgCoverage)) {
|
|
27
|
+
const percent = `${avgCoverage} %`;
|
|
28
|
+
entries.push(`Code coverage: ${bold(testCoverage.passed ? green(percent) : red(percent))}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!testCoverage.passed && coverageConfig.threshold) {
|
|
32
|
+
coverageTypes.forEach(type => {
|
|
33
|
+
if (testCoverage.summary[type].pct < coverageConfig.threshold![type]) {
|
|
34
|
+
entries.push(
|
|
35
|
+
`Coverage for ${bold(type)} failed with ${bold(
|
|
36
|
+
red(`${testCoverage.summary[type].pct} %`),
|
|
37
|
+
)} compared to configured ${bold(`${coverageConfig.threshold![type]} %`)}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!watch && coverageConfig.report && coverageConfig.reporters?.includes('lcov')) {
|
|
44
|
+
entries.push(
|
|
45
|
+
`View full coverage report at ${underline(
|
|
46
|
+
path.join(coverageConfig.reportDir ?? '', 'lcov-report', 'index.html'),
|
|
47
|
+
)}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
entries.push('');
|
|
52
|
+
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_STATUS,
|
|
3
|
+
type BrowserLauncher,
|
|
4
|
+
type CoverageConfig,
|
|
5
|
+
type TestCoverage,
|
|
6
|
+
type TestRunnerCoreConfig,
|
|
7
|
+
type TestSessionManager,
|
|
8
|
+
} from '@web/test-runner-core';
|
|
9
|
+
import { bold, gray, green, red } from 'nanocolors';
|
|
10
|
+
|
|
11
|
+
import { getCodeCoverage } from './getCodeCoverage.js';
|
|
12
|
+
import { renderProgressBar } from './renderProgressBar.js';
|
|
13
|
+
import { getPassedFailedSkippedCount } from './utils/getPassedFailedSkippedCount.js';
|
|
14
|
+
|
|
15
|
+
export interface TestProgressArgs {
|
|
16
|
+
browsers: BrowserLauncher[];
|
|
17
|
+
browserNames: string[];
|
|
18
|
+
testFiles: string[];
|
|
19
|
+
testRun: number;
|
|
20
|
+
sessions: TestSessionManager;
|
|
21
|
+
startTime: number;
|
|
22
|
+
watch: boolean;
|
|
23
|
+
focusedTestFile?: string;
|
|
24
|
+
coverage: boolean;
|
|
25
|
+
coverageConfig?: CoverageConfig;
|
|
26
|
+
testCoverage?: TestCoverage;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getProgressReport(
|
|
30
|
+
name: string,
|
|
31
|
+
minWidth: number,
|
|
32
|
+
finishedFiles: number,
|
|
33
|
+
activeFiles: number,
|
|
34
|
+
testFiles: number,
|
|
35
|
+
passedTests: number,
|
|
36
|
+
skippedTests: number,
|
|
37
|
+
failedTests: number,
|
|
38
|
+
) {
|
|
39
|
+
const failedText = `${failedTests} failed`;
|
|
40
|
+
const testResults =
|
|
41
|
+
`${green(`${passedTests} passed`)}` +
|
|
42
|
+
`, ${failedTests !== 0 ? red(failedText) : failedText}` +
|
|
43
|
+
(skippedTests !== 0 ? `, ${gray(`${skippedTests} skipped`)}` : '');
|
|
44
|
+
const progressBar = `${renderProgressBar(
|
|
45
|
+
finishedFiles,
|
|
46
|
+
activeFiles,
|
|
47
|
+
testFiles,
|
|
48
|
+
)} ${finishedFiles}/${testFiles} test files`;
|
|
49
|
+
|
|
50
|
+
return `${`${name}:`.padEnd(minWidth)} ${progressBar} | ${testResults}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getTestProgressReport(config: TestRunnerCoreConfig, args: TestProgressArgs) {
|
|
54
|
+
const {
|
|
55
|
+
browsers,
|
|
56
|
+
browserNames,
|
|
57
|
+
testRun,
|
|
58
|
+
sessions,
|
|
59
|
+
watch,
|
|
60
|
+
startTime,
|
|
61
|
+
focusedTestFile,
|
|
62
|
+
coverage,
|
|
63
|
+
coverageConfig,
|
|
64
|
+
testCoverage,
|
|
65
|
+
} = args;
|
|
66
|
+
const entries: string[] = [];
|
|
67
|
+
const unfinishedSessions = Array.from(
|
|
68
|
+
sessions.forStatusAndTestFile(
|
|
69
|
+
focusedTestFile,
|
|
70
|
+
SESSION_STATUS.SCHEDULED,
|
|
71
|
+
SESSION_STATUS.INITIALIZING,
|
|
72
|
+
SESSION_STATUS.TEST_STARTED,
|
|
73
|
+
SESSION_STATUS.TEST_FINISHED,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const finishedFiles = new Set<string>();
|
|
78
|
+
let failedTestCount = 0;
|
|
79
|
+
let failed = false;
|
|
80
|
+
|
|
81
|
+
const longestBrowser = [...browserNames].sort((a, b) => b.length - a.length)[0];
|
|
82
|
+
const minWidth = longestBrowser ? longestBrowser.length + 1 : 0;
|
|
83
|
+
for (const browser of browsers) {
|
|
84
|
+
// when started or not initiliazing we render a progress bar
|
|
85
|
+
const allSessionsForBrowser = Array.from(sessions.forBrowser(browser));
|
|
86
|
+
const sessionsForBrowser = focusedTestFile
|
|
87
|
+
? allSessionsForBrowser.filter(s => s.testFile === focusedTestFile)
|
|
88
|
+
: allSessionsForBrowser;
|
|
89
|
+
const totalTestFiles = sessionsForBrowser.length;
|
|
90
|
+
let finishedFilesForBrowser = 0;
|
|
91
|
+
let activeFilesForBrowser = 0;
|
|
92
|
+
let passedTestsForBrowser = 0;
|
|
93
|
+
let skippedTestsForBrowser = 0;
|
|
94
|
+
let failedTestsForBrowser = 0;
|
|
95
|
+
|
|
96
|
+
for (const session of sessionsForBrowser) {
|
|
97
|
+
if (!session.passed) {
|
|
98
|
+
failed = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (![SESSION_STATUS.SCHEDULED, SESSION_STATUS.FINISHED].includes(session.status)) {
|
|
102
|
+
activeFilesForBrowser += 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (session.status === SESSION_STATUS.FINISHED) {
|
|
106
|
+
const { testFile, testResults } = session;
|
|
107
|
+
finishedFiles.add(testFile);
|
|
108
|
+
finishedFilesForBrowser += 1;
|
|
109
|
+
if (testResults) {
|
|
110
|
+
const parsed = getPassedFailedSkippedCount(testResults);
|
|
111
|
+
passedTestsForBrowser += parsed.passed;
|
|
112
|
+
skippedTestsForBrowser += parsed.skipped;
|
|
113
|
+
failedTestsForBrowser += parsed.failed;
|
|
114
|
+
failedTestCount += parsed.failed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
entries.push(
|
|
120
|
+
getProgressReport(
|
|
121
|
+
browser.name,
|
|
122
|
+
minWidth,
|
|
123
|
+
finishedFilesForBrowser,
|
|
124
|
+
activeFilesForBrowser,
|
|
125
|
+
totalTestFiles,
|
|
126
|
+
passedTestsForBrowser,
|
|
127
|
+
skippedTestsForBrowser,
|
|
128
|
+
failedTestsForBrowser,
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
entries.push('');
|
|
134
|
+
|
|
135
|
+
if (coverage && coverageConfig) {
|
|
136
|
+
if (testCoverage) {
|
|
137
|
+
if (!testCoverage.passed) {
|
|
138
|
+
failed = true;
|
|
139
|
+
}
|
|
140
|
+
const coverageReport = getCodeCoverage(testCoverage, watch, coverageConfig);
|
|
141
|
+
entries.push(...coverageReport);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (testRun !== -1 && unfinishedSessions.length === 0) {
|
|
146
|
+
if (coverage && !testCoverage) {
|
|
147
|
+
entries.push(bold('Calculating code coverage...'));
|
|
148
|
+
} else if (config.watch) {
|
|
149
|
+
entries.push(bold(`Finished running tests, watching for file changes...`));
|
|
150
|
+
} else {
|
|
151
|
+
const durationInSec = (Date.now() - startTime) / 1000;
|
|
152
|
+
const duration = Math.trunc(durationInSec * 10) / 10;
|
|
153
|
+
|
|
154
|
+
if (failed) {
|
|
155
|
+
if (coverage && !testCoverage?.passed) {
|
|
156
|
+
entries.push(
|
|
157
|
+
bold(red(`Finished running tests in ${duration}s, failed to meet coverage threshold.`)),
|
|
158
|
+
);
|
|
159
|
+
} else if (failedTestCount > 0) {
|
|
160
|
+
entries.push(
|
|
161
|
+
bold(
|
|
162
|
+
red(`Finished running tests in ${duration}s with ${failedTestCount} failed tests.`),
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
} else if (finishedFiles.size > 0) {
|
|
166
|
+
entries.push(bold(red(`Error while running tests.`)));
|
|
167
|
+
} else {
|
|
168
|
+
entries.push(bold(red(`Failed to run any tests.`)));
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
entries.push(bold(`Finished running tests in ${duration}s, all tests passed! 🎉`));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
entries.push(bold('Running tests...'));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
entries.push('');
|
|
179
|
+
|
|
180
|
+
return entries;
|
|
181
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { gray, white } from 'nanocolors';
|
|
2
|
+
|
|
3
|
+
const PROGRESS_BLOCKS = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉', '█'];
|
|
4
|
+
const PROGRESS_WIDTH = 30;
|
|
5
|
+
|
|
6
|
+
function createProgressBlocks(value: number, total: number) {
|
|
7
|
+
if (value >= total) {
|
|
8
|
+
return PROGRESS_BLOCKS[8].repeat(PROGRESS_WIDTH);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const count = (PROGRESS_WIDTH * value) / total;
|
|
12
|
+
const floored = Math.floor(count);
|
|
13
|
+
const partialBlock =
|
|
14
|
+
PROGRESS_BLOCKS[Math.floor((count - floored) * (PROGRESS_BLOCKS.length - 1))];
|
|
15
|
+
return `${PROGRESS_BLOCKS[8].repeat(floored)}${partialBlock}${' '.repeat(
|
|
16
|
+
PROGRESS_WIDTH - floored - 1,
|
|
17
|
+
)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function renderProgressBar(finished: number, active: number, total: number) {
|
|
21
|
+
const progressBlocks = createProgressBlocks(finished + active, total);
|
|
22
|
+
const finishedBlockCount = Math.floor((PROGRESS_WIDTH * finished) / total);
|
|
23
|
+
|
|
24
|
+
const finishedBlocks = white(progressBlocks.slice(0, finishedBlockCount));
|
|
25
|
+
const scheduledBlocks = gray(progressBlocks.slice(finishedBlockCount));
|
|
26
|
+
return `|${finishedBlocks}${scheduledBlocks}|`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Logger, type TestSession } from '@web/test-runner-core';
|
|
2
|
+
|
|
3
|
+
export function reportBrowserLogs(logger: Logger, sessions: TestSession[]) {
|
|
4
|
+
const commonLogs: any[] = [];
|
|
5
|
+
const commonStringified: string[] = [];
|
|
6
|
+
const logsByBrowser = new Map<string, any[]>();
|
|
7
|
+
|
|
8
|
+
const allStringified = sessions.map(s => JSON.stringify(s.logs));
|
|
9
|
+
for (const session of sessions) {
|
|
10
|
+
for (const args of session.logs) {
|
|
11
|
+
// for the first session, we always include all logs
|
|
12
|
+
// for others we deduplicate logs, this way we can allow the same log
|
|
13
|
+
// msg appearing multiple times while also deduplicating common logs
|
|
14
|
+
// between browsers
|
|
15
|
+
const stringifiedArgs = JSON.stringify(args);
|
|
16
|
+
if (
|
|
17
|
+
args.length > 0 &&
|
|
18
|
+
(session === sessions[0] || !commonStringified.includes(stringifiedArgs))
|
|
19
|
+
) {
|
|
20
|
+
if (allStringified.every(logs => logs.includes(stringifiedArgs))) {
|
|
21
|
+
commonLogs.push(args);
|
|
22
|
+
commonStringified.push(stringifiedArgs);
|
|
23
|
+
} else {
|
|
24
|
+
let logsForBrowser = logsByBrowser.get(session.browser.name);
|
|
25
|
+
if (!logsForBrowser) {
|
|
26
|
+
logsForBrowser = [];
|
|
27
|
+
logsByBrowser.set(session.browser.name, logsForBrowser);
|
|
28
|
+
}
|
|
29
|
+
logsForBrowser.push(args);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (commonLogs.length > 0) {
|
|
36
|
+
logger.log(` 🚧 Browser logs:`);
|
|
37
|
+
logger.group();
|
|
38
|
+
logger.group();
|
|
39
|
+
logger.group();
|
|
40
|
+
for (const args of commonLogs) {
|
|
41
|
+
logger.log(...args);
|
|
42
|
+
}
|
|
43
|
+
logger.groupEnd();
|
|
44
|
+
logger.groupEnd();
|
|
45
|
+
logger.groupEnd();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [browser, logs] of logsByBrowser) {
|
|
49
|
+
logger.log(` 🚧 Browser logs on ${browser}:`);
|
|
50
|
+
logger.group();
|
|
51
|
+
logger.group();
|
|
52
|
+
logger.group();
|
|
53
|
+
for (const args of logs) {
|
|
54
|
+
logger.log(...args);
|
|
55
|
+
}
|
|
56
|
+
logger.groupEnd();
|
|
57
|
+
logger.groupEnd();
|
|
58
|
+
logger.groupEnd();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (commonLogs.length > 0 || logsByBrowser.size > 0) {
|
|
62
|
+
logger.log('');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type Logger, type TestSession } from '@web/test-runner-core';
|
|
2
|
+
import { bold, gray } from 'nanocolors';
|
|
3
|
+
|
|
4
|
+
export function reportRequest404s(logger: Logger, sessions: TestSession[]) {
|
|
5
|
+
const common404s: string[] = [];
|
|
6
|
+
const request404sPerBrowser = new Map<string, string[]>();
|
|
7
|
+
|
|
8
|
+
const all404s = sessions.map(s => s.request404s);
|
|
9
|
+
for (const session of sessions) {
|
|
10
|
+
for (const request404 of session.request404s) {
|
|
11
|
+
// for the first session, we always include all request 404s
|
|
12
|
+
// for others we deduplicate 404s, this way we can allow the same 404
|
|
13
|
+
// msg appearing multiple times while also deduplicating common 404s
|
|
14
|
+
// between browsers
|
|
15
|
+
if (session === sessions[0] || !common404s.includes(request404)) {
|
|
16
|
+
if (all404s.every(r404s => r404s.includes(request404))) {
|
|
17
|
+
common404s.push(request404);
|
|
18
|
+
} else {
|
|
19
|
+
let request404sForBrowser = request404sPerBrowser.get(session.browser.name);
|
|
20
|
+
if (!request404sForBrowser) {
|
|
21
|
+
request404sForBrowser = [];
|
|
22
|
+
request404sPerBrowser.set(session.browser.name, request404sForBrowser);
|
|
23
|
+
}
|
|
24
|
+
request404sForBrowser.push(request404);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (common404s.length > 0) {
|
|
31
|
+
logger.log(' 🚧 404 network requests:');
|
|
32
|
+
logger.group();
|
|
33
|
+
logger.group();
|
|
34
|
+
for (const request404 of common404s) {
|
|
35
|
+
logger.log(`${bold(gray('-'))} ${request404}`);
|
|
36
|
+
}
|
|
37
|
+
logger.groupEnd();
|
|
38
|
+
logger.groupEnd();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const [browser, request404s] of request404sPerBrowser) {
|
|
42
|
+
logger.log(` 🚧 404 network requests on ${browser}:`);
|
|
43
|
+
logger.group();
|
|
44
|
+
logger.group();
|
|
45
|
+
for (const request404 of request404s) {
|
|
46
|
+
logger.log(`${bold(gray('-'))} ${request404}`);
|
|
47
|
+
}
|
|
48
|
+
logger.groupEnd();
|
|
49
|
+
logger.groupEnd();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (common404s.length > 0 || request404sPerBrowser.size > 0) {
|
|
53
|
+
logger.log('');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type Logger, type TestResultError, type TestSession } from '@web/test-runner-core';
|
|
2
|
+
import { gray, red } from 'nanocolors';
|
|
3
|
+
|
|
4
|
+
import { getFailedOnBrowsers } from './utils/getFailedOnBrowsers.js';
|
|
5
|
+
|
|
6
|
+
function isSameError(a: TestResultError, b: TestResultError) {
|
|
7
|
+
return a.message === b.message && a.stack === b.stack;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ErrorReport {
|
|
11
|
+
testFile: string;
|
|
12
|
+
failedBrowsers: string[];
|
|
13
|
+
error: TestResultError;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function reportTestFileErrors(
|
|
17
|
+
logger: Logger,
|
|
18
|
+
browserNames: string[],
|
|
19
|
+
favoriteBrowser: string,
|
|
20
|
+
sessionsForTestFile: TestSession[],
|
|
21
|
+
logFileNames?: boolean,
|
|
22
|
+
) {
|
|
23
|
+
const reports: ErrorReport[] = [];
|
|
24
|
+
|
|
25
|
+
for (const session of sessionsForTestFile) {
|
|
26
|
+
for (const error of session.errors) {
|
|
27
|
+
let report = reports.find(r => isSameError(r.error, error));
|
|
28
|
+
if (!report) {
|
|
29
|
+
report = {
|
|
30
|
+
testFile: session.testFile,
|
|
31
|
+
failedBrowsers: [],
|
|
32
|
+
error,
|
|
33
|
+
};
|
|
34
|
+
reports.push(report);
|
|
35
|
+
}
|
|
36
|
+
report.failedBrowsers.push(session.browser.name);
|
|
37
|
+
|
|
38
|
+
if (session.browser.name === favoriteBrowser) {
|
|
39
|
+
report.error = error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const report of reports) {
|
|
45
|
+
const { name, message = 'Unknown error' } = report.error;
|
|
46
|
+
const errorMsg = name ? `${name}: ${message}` : message;
|
|
47
|
+
const failedOn = getFailedOnBrowsers(browserNames, report.failedBrowsers);
|
|
48
|
+
|
|
49
|
+
if (report.error.stack) {
|
|
50
|
+
// there was a stack trace, take the first line and decorate it with an icon and which browsers it failed on
|
|
51
|
+
logger.log(` ❌ ${red(errorMsg)} ${failedOn}`);
|
|
52
|
+
|
|
53
|
+
// if there was more to the stack trace, print it
|
|
54
|
+
logger.group();
|
|
55
|
+
logger.group();
|
|
56
|
+
logger.log(gray(report.error.stack));
|
|
57
|
+
logger.groupEnd();
|
|
58
|
+
logger.groupEnd();
|
|
59
|
+
} else {
|
|
60
|
+
// there was no stack trace, so just print the error message
|
|
61
|
+
logger.log(` ❌ ${errorMsg} ${failedOn}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (logFileNames) {
|
|
65
|
+
logger.log(` ${report.testFile}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logger.log('');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type BufferedLogger, type TestSession } from '@web/test-runner-core';
|
|
2
|
+
import { bold, cyan } from 'nanocolors';
|
|
3
|
+
import { relative } from 'path';
|
|
4
|
+
|
|
5
|
+
import { reportBrowserLogs } from './reportBrowserLogs.js';
|
|
6
|
+
import { reportRequest404s } from './reportRequest404s.js';
|
|
7
|
+
import { reportTestFileErrors } from './reportTestFileErrors.js';
|
|
8
|
+
import { reportTestsErrors } from './reportTestsErrors.js';
|
|
9
|
+
|
|
10
|
+
export function reportTestFileResults(
|
|
11
|
+
logger: BufferedLogger,
|
|
12
|
+
testFile: string,
|
|
13
|
+
allBrowserNames: string[],
|
|
14
|
+
favoriteBrowser: string,
|
|
15
|
+
sessionsForTestFile: TestSession[],
|
|
16
|
+
) {
|
|
17
|
+
const failedSessions = sessionsForTestFile.filter(s => !s.passed);
|
|
18
|
+
|
|
19
|
+
reportBrowserLogs(logger, sessionsForTestFile);
|
|
20
|
+
reportRequest404s(logger, sessionsForTestFile);
|
|
21
|
+
reportTestFileErrors(logger, allBrowserNames, favoriteBrowser, sessionsForTestFile);
|
|
22
|
+
|
|
23
|
+
if (failedSessions.length > 0) {
|
|
24
|
+
reportTestsErrors(logger, allBrowserNames, favoriteBrowser, failedSessions);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (logger.buffer.length > 0) {
|
|
28
|
+
logger.buffer.unshift({
|
|
29
|
+
method: 'log',
|
|
30
|
+
args: [`${bold(cyan(relative(process.cwd(), testFile)))}:\n`],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|