@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,50 @@
|
|
|
1
|
+
import { ConfigLoaderError, readConfig } from '@web/config-loader';
|
|
2
|
+
import { type TestRunnerGroupConfig } from '@web/test-runner-core';
|
|
3
|
+
import globby from 'globby';
|
|
4
|
+
import { TestRunnerStartError } from '../TestRunnerStartError.js';
|
|
5
|
+
|
|
6
|
+
function validateGroupConfig(configFilePath: string, config: Partial<TestRunnerGroupConfig>) {
|
|
7
|
+
if (config.browsers != null && !Array.isArray(config.browsers)) {
|
|
8
|
+
throw new TestRunnerStartError(
|
|
9
|
+
`Group config at ${configFilePath} has invalid browsers option. It should be an array.`,
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (config.files != null && !(typeof config.files === 'string' || Array.isArray(config.files))) {
|
|
14
|
+
throw new TestRunnerStartError(
|
|
15
|
+
`Group config at ${configFilePath} has an invalid files option. It should be a string or an array.`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { name: configFilePath, configFilePath, ...config } as TestRunnerGroupConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function collectGroupConfigs(patterns: string[]) {
|
|
23
|
+
const groupConfigFiles = new Set<string>();
|
|
24
|
+
const groupConfigs: TestRunnerGroupConfig[] = [];
|
|
25
|
+
|
|
26
|
+
for (const pattern of patterns) {
|
|
27
|
+
const filePaths = globby.sync(pattern, { absolute: true });
|
|
28
|
+
for (const filePath of filePaths) {
|
|
29
|
+
groupConfigFiles.add(filePath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const groupConfigFile of groupConfigFiles) {
|
|
34
|
+
try {
|
|
35
|
+
const maybeGroupConfig = (await readConfig(
|
|
36
|
+
'',
|
|
37
|
+
groupConfigFile,
|
|
38
|
+
)) as Partial<TestRunnerGroupConfig>;
|
|
39
|
+
const groupConfig = validateGroupConfig(groupConfigFile, maybeGroupConfig);
|
|
40
|
+
groupConfigs.push(groupConfig);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof ConfigLoaderError) {
|
|
43
|
+
throw new TestRunnerStartError(error.message);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return groupConfigs;
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TestRunnerStartError } from '../TestRunnerStartError.js';
|
|
2
|
+
|
|
3
|
+
const puppeteerBrowsers = ['chrome', 'firefox'];
|
|
4
|
+
const playwrightBrowsers = ['chromium', 'firefox', 'webkit'];
|
|
5
|
+
|
|
6
|
+
function loadLauncher(name: string) {
|
|
7
|
+
const pkg = `@web/test-runner-${name}`;
|
|
8
|
+
try {
|
|
9
|
+
const path = require.resolve(pkg, { paths: [__dirname, process.cwd()] });
|
|
10
|
+
return require(path);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if ((error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
|
|
13
|
+
throw new TestRunnerStartError(
|
|
14
|
+
`You need to add ${pkg} as a dependency of your project to use the --${name} flag.`,
|
|
15
|
+
);
|
|
16
|
+
} else {
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function puppeteerLauncher(browsers: string[] = ['chrome']) {
|
|
23
|
+
for (const browser of browsers) {
|
|
24
|
+
if (!puppeteerBrowsers.includes(browser)) {
|
|
25
|
+
throw new TestRunnerStartError(
|
|
26
|
+
`Unknown puppeteer browser: ${browser}. ` +
|
|
27
|
+
`Supported browsers: ${puppeteerBrowsers.join(', ')}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const launcher = loadLauncher('puppeteer').puppeteerLauncher;
|
|
33
|
+
return browsers.map(product => launcher({ launchOptions: { product } }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function playwrightLauncher(browsers: string[] = ['chromium']) {
|
|
37
|
+
for (const browser of browsers) {
|
|
38
|
+
if (!playwrightBrowsers.includes(browser)) {
|
|
39
|
+
throw new TestRunnerStartError(
|
|
40
|
+
`Unknown playwright browser: ${browser}. ` +
|
|
41
|
+
`Supported browsers: ${playwrightBrowsers.join(', ')}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const launcher = loadLauncher('playwright').playwrightLauncher;
|
|
47
|
+
return browsers.map(product => launcher({ product }));
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type TestRunnerConfig } from './TestRunnerConfig.js';
|
|
2
|
+
|
|
3
|
+
const arrayKeys = ['plugins', 'middleware'];
|
|
4
|
+
|
|
5
|
+
export function mergeConfigs(...configs: Partial<TestRunnerConfig | undefined>[]) {
|
|
6
|
+
const finalConfig: any = {
|
|
7
|
+
plugins: [],
|
|
8
|
+
middleware: [],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
for (const config of configs) {
|
|
12
|
+
if (config) {
|
|
13
|
+
for (const [key, value] of Object.entries(config)) {
|
|
14
|
+
if (arrayKeys.includes(key)) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
finalConfig[key].push(...value);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
finalConfig[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return finalConfig as Partial<TestRunnerConfig>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { chromeLauncher } from '@web/test-runner-chrome';
|
|
2
|
+
import {
|
|
3
|
+
emulateMediaPlugin,
|
|
4
|
+
filePlugin,
|
|
5
|
+
selectOptionPlugin,
|
|
6
|
+
sendKeysPlugin,
|
|
7
|
+
sendMousePlugin,
|
|
8
|
+
setUserAgentPlugin,
|
|
9
|
+
setViewportPlugin,
|
|
10
|
+
snapshotPlugin,
|
|
11
|
+
} from '@web/test-runner-commands/plugins';
|
|
12
|
+
import {
|
|
13
|
+
type CoverageConfig,
|
|
14
|
+
type TestRunnerCoreConfig,
|
|
15
|
+
type TestRunnerGroupConfig,
|
|
16
|
+
} from '@web/test-runner-core';
|
|
17
|
+
import { cpus } from 'os';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { getPortPromise } from 'portfinder';
|
|
20
|
+
|
|
21
|
+
import { esbuildPlugin, nodeResolvePlugin } from '@web/dev-server';
|
|
22
|
+
import { TestRunnerStartError } from '../TestRunnerStartError.js';
|
|
23
|
+
import { TestRunnerLogger } from '../logger/TestRunnerLogger.js';
|
|
24
|
+
import { defaultReporter } from '../reporter/defaultReporter.js';
|
|
25
|
+
import { type TestRunnerConfig } from './TestRunnerConfig.js';
|
|
26
|
+
import { collectGroupConfigs } from './collectGroupConfigs.js';
|
|
27
|
+
import { playwrightLauncher, puppeteerLauncher } from './loadLauncher.js';
|
|
28
|
+
import { mergeConfigs } from './mergeConfigs.js';
|
|
29
|
+
import { type TestRunnerCliArgs } from './readCliArgs.js';
|
|
30
|
+
|
|
31
|
+
const secondMs = 1000;
|
|
32
|
+
const minuteMs = secondMs * 60;
|
|
33
|
+
|
|
34
|
+
const defaultConfig: Partial<TestRunnerConfig> = {
|
|
35
|
+
rootDir: process.cwd(),
|
|
36
|
+
protocol: 'http:',
|
|
37
|
+
hostname: 'localhost',
|
|
38
|
+
middleware: [],
|
|
39
|
+
plugins: [],
|
|
40
|
+
watch: false,
|
|
41
|
+
concurrentBrowsers: 2,
|
|
42
|
+
concurrency: Math.max(1, cpus().length / 2),
|
|
43
|
+
browserStartTimeout: minuteMs / 2,
|
|
44
|
+
testsStartTimeout: secondMs * 20,
|
|
45
|
+
testsFinishTimeout: minuteMs * 2,
|
|
46
|
+
browserLogs: true,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const defaultCoverageConfig: CoverageConfig = {
|
|
50
|
+
exclude: ['**/node_modules/**/*', '**/web_modules/**/*'],
|
|
51
|
+
threshold: { statements: 0, functions: 0, branches: 0, lines: 0 },
|
|
52
|
+
report: true,
|
|
53
|
+
reportDir: 'coverage',
|
|
54
|
+
reporters: ['lcov'],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function validate(config: Record<string, unknown>, key: string, type: string) {
|
|
58
|
+
if (config[key] == null) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof config[key] !== type) {
|
|
63
|
+
throw new TestRunnerStartError(`Configuration error: The ${key} setting should be a ${type}.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stringSettings = ['rootDir', 'hostname'];
|
|
68
|
+
const numberSettings = [
|
|
69
|
+
'port',
|
|
70
|
+
'concurrentBrowsers',
|
|
71
|
+
'concurrency',
|
|
72
|
+
'browserStartTimeout',
|
|
73
|
+
'testsStartTimeout',
|
|
74
|
+
'testsFinishTimeout',
|
|
75
|
+
];
|
|
76
|
+
const booleanSettings = [
|
|
77
|
+
'watch',
|
|
78
|
+
'preserveSymlinks',
|
|
79
|
+
'browserLogs',
|
|
80
|
+
'coverage',
|
|
81
|
+
'staticLogging',
|
|
82
|
+
'manual',
|
|
83
|
+
'open',
|
|
84
|
+
'debug',
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
export function validateConfig(config: Partial<TestRunnerConfig>) {
|
|
88
|
+
stringSettings.forEach(key => validate(config, key, 'string'));
|
|
89
|
+
numberSettings.forEach(key => validate(config, key, 'number'));
|
|
90
|
+
booleanSettings.forEach(key => validate(config, key, 'boolean'));
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
config.esbuildTarget != null &&
|
|
94
|
+
!(typeof config.esbuildTarget === 'string' || Array.isArray(config.esbuildTarget))
|
|
95
|
+
) {
|
|
96
|
+
throw new TestRunnerStartError(
|
|
97
|
+
`Configuration error: The esbuildTarget setting should be a string or array.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (config.files != null && !(typeof config.files === 'string' || Array.isArray(config.files))) {
|
|
102
|
+
throw new TestRunnerStartError(
|
|
103
|
+
`Configuration error: The files setting should be a string or an array.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return config as TestRunnerConfig;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function parseConfigGroups(config: TestRunnerConfig, cliArgs: TestRunnerCliArgs) {
|
|
111
|
+
const groupConfigs: TestRunnerGroupConfig[] = [];
|
|
112
|
+
const configPatterns: string[] = [];
|
|
113
|
+
|
|
114
|
+
if (cliArgs.groups) {
|
|
115
|
+
// groups are provided from CLI args
|
|
116
|
+
configPatterns.push(cliArgs.groups);
|
|
117
|
+
} else if (config.groups) {
|
|
118
|
+
// groups are provided from config
|
|
119
|
+
for (const entry of config.groups) {
|
|
120
|
+
if (typeof entry === 'object') {
|
|
121
|
+
groupConfigs.push(entry);
|
|
122
|
+
} else {
|
|
123
|
+
configPatterns.push(entry);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// group entries which are strings are globs which point to group conigs
|
|
129
|
+
groupConfigs.push(...(await collectGroupConfigs(configPatterns)));
|
|
130
|
+
return groupConfigs;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function parseConfig(
|
|
134
|
+
config: Partial<TestRunnerConfig>,
|
|
135
|
+
cliArgs: TestRunnerCliArgs = {},
|
|
136
|
+
): Promise<{ config: TestRunnerCoreConfig; groupConfigs: TestRunnerGroupConfig[] }> {
|
|
137
|
+
const cliArgsConfig: Partial<TestRunnerConfig> = {
|
|
138
|
+
...(cliArgs as Omit<TestRunnerCliArgs, 'groups' | 'browsers'>),
|
|
139
|
+
};
|
|
140
|
+
// CLI has properties with the same name as the config, but with different values
|
|
141
|
+
// delete them so they don't overwrite when merging, the CLI values are read separately
|
|
142
|
+
delete cliArgsConfig.groups;
|
|
143
|
+
delete cliArgsConfig.browsers;
|
|
144
|
+
|
|
145
|
+
const mergedConfigs = mergeConfigs(defaultConfig, config, cliArgsConfig);
|
|
146
|
+
|
|
147
|
+
// backwards compatibility for configs written for es-dev-server, where middleware was
|
|
148
|
+
// spelled incorrectly as middlewares
|
|
149
|
+
if (Array.isArray((mergedConfigs as any).middlewares)) {
|
|
150
|
+
mergedConfigs.middleware!.push(...(mergedConfigs as any).middlewares);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const finalConfig = validateConfig(mergedConfigs);
|
|
154
|
+
// filter out non-objects from plugin list
|
|
155
|
+
finalConfig.plugins = (finalConfig.plugins ?? []).filter(pl => typeof pl === 'object');
|
|
156
|
+
|
|
157
|
+
// ensure rootDir is always resolved
|
|
158
|
+
if (typeof finalConfig.rootDir === 'string') {
|
|
159
|
+
finalConfig.rootDir = path.resolve(finalConfig.rootDir);
|
|
160
|
+
} else {
|
|
161
|
+
throw new TestRunnerStartError('No rootDir specified.');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// generate a default random port
|
|
165
|
+
if (typeof finalConfig.port !== 'number') {
|
|
166
|
+
finalConfig.port = await getPortPromise({ port: 8000 });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
finalConfig.coverageConfig = {
|
|
170
|
+
...defaultCoverageConfig,
|
|
171
|
+
...finalConfig.coverageConfig,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
let groupConfigs = await parseConfigGroups(finalConfig, cliArgs);
|
|
175
|
+
if (groupConfigs.find(g => g.name === 'default')) {
|
|
176
|
+
throw new TestRunnerStartError(
|
|
177
|
+
'Cannot create a group named "default". This named is reserved by the test runner.',
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (cliArgs.group != null) {
|
|
182
|
+
if (cliArgs.group === 'default') {
|
|
183
|
+
// default group is an alias for the root config
|
|
184
|
+
groupConfigs = [];
|
|
185
|
+
} else {
|
|
186
|
+
const groupConfig = groupConfigs.find(c => c.name === cliArgs.group);
|
|
187
|
+
if (!groupConfig) {
|
|
188
|
+
throw new TestRunnerStartError(`Could not find any group named ${cliArgs.group}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// when focusing a group, ensure that the "default" group isn't run
|
|
192
|
+
// we can improve this by relying only on groups inside the test runner itself
|
|
193
|
+
if (groupConfig.files == null) {
|
|
194
|
+
groupConfig.files = finalConfig.files;
|
|
195
|
+
}
|
|
196
|
+
finalConfig.files = undefined;
|
|
197
|
+
|
|
198
|
+
groupConfigs = [groupConfig];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (cliArgs.puppeteer) {
|
|
203
|
+
if (finalConfig.browsers && finalConfig.browsers.length > 0) {
|
|
204
|
+
throw new TestRunnerStartError(
|
|
205
|
+
'The --puppeteer flag cannot be used when defining browsers manually in your finalConfig.',
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
finalConfig.browsers = puppeteerLauncher(cliArgs.browsers);
|
|
209
|
+
} else if (cliArgs.playwright) {
|
|
210
|
+
if (finalConfig.browsers && finalConfig.browsers.length > 0) {
|
|
211
|
+
throw new TestRunnerStartError(
|
|
212
|
+
'The --playwright flag cannot be used when defining browsers manually in your finalConfig.',
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
finalConfig.browsers = playwrightLauncher(cliArgs.browsers);
|
|
216
|
+
} else {
|
|
217
|
+
if (cliArgs.browsers != null) {
|
|
218
|
+
throw new TestRunnerStartError(
|
|
219
|
+
`The browsers option must be used along with the puppeteer or playwright option.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// add default chrome launcher if the user did not configure their own browsers
|
|
224
|
+
if (!finalConfig.browsers) {
|
|
225
|
+
finalConfig.browsers = [chromeLauncher()];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
finalConfig.testFramework = {
|
|
230
|
+
path: require.resolve('@web/test-runner-mocha/dist/autorun.js'),
|
|
231
|
+
...(finalConfig.testFramework ?? {}),
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (!finalConfig.reporters) {
|
|
235
|
+
finalConfig.reporters = [defaultReporter()];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!finalConfig.logger) {
|
|
239
|
+
finalConfig.logger = new TestRunnerLogger(config.debug);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (finalConfig.plugins == null) {
|
|
243
|
+
finalConfig.plugins = [];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// plugin with a noop transformImport hook, this will cause the dev server to analyze modules and
|
|
247
|
+
// catch syntax errors. this way we still report syntax errors when the user has no flags enabled
|
|
248
|
+
finalConfig.plugins.unshift({
|
|
249
|
+
name: 'syntax-checker',
|
|
250
|
+
transformImport() {
|
|
251
|
+
return undefined;
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
finalConfig.plugins.unshift(
|
|
256
|
+
setViewportPlugin(),
|
|
257
|
+
emulateMediaPlugin(),
|
|
258
|
+
setUserAgentPlugin(),
|
|
259
|
+
selectOptionPlugin(),
|
|
260
|
+
filePlugin(),
|
|
261
|
+
sendKeysPlugin(),
|
|
262
|
+
sendMousePlugin(),
|
|
263
|
+
snapshotPlugin({ updateSnapshots: !!cliArgs.updateSnapshots }),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (finalConfig.nodeResolve) {
|
|
267
|
+
const userOptions =
|
|
268
|
+
typeof finalConfig.nodeResolve === 'object' ? finalConfig.nodeResolve : undefined;
|
|
269
|
+
// do node resolve after user plugins, to allow user plugins to resolve imports
|
|
270
|
+
finalConfig.plugins!.push(
|
|
271
|
+
nodeResolvePlugin(finalConfig.rootDir!, finalConfig.preserveSymlinks, userOptions),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (finalConfig?.esbuildTarget) {
|
|
276
|
+
finalConfig.plugins!.unshift(esbuildPlugin(finalConfig.esbuildTarget));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if ((!finalConfig.files || finalConfig.files.length === 0) && groupConfigs.length === 0) {
|
|
280
|
+
throw new TestRunnerStartError(
|
|
281
|
+
'Did not find any tests to run. Use the "files" or "groups" option to configure test files.',
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { config: finalConfig as TestRunnerCoreConfig, groupConfigs };
|
|
286
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import camelCase from 'camelcase';
|
|
2
|
+
import commandLineArgs from 'command-line-args';
|
|
3
|
+
import commandLineUsage, { OptionDefinition } from 'command-line-usage';
|
|
4
|
+
|
|
5
|
+
import { type TestRunnerConfig } from './TestRunnerConfig.js';
|
|
6
|
+
|
|
7
|
+
export interface TestRunnerCliArgs extends Partial<
|
|
8
|
+
Pick<
|
|
9
|
+
TestRunnerConfig,
|
|
10
|
+
| 'files'
|
|
11
|
+
| 'rootDir'
|
|
12
|
+
| 'watch'
|
|
13
|
+
| 'coverage'
|
|
14
|
+
| 'concurrentBrowsers'
|
|
15
|
+
| 'concurrency'
|
|
16
|
+
| 'staticLogging'
|
|
17
|
+
| 'manual'
|
|
18
|
+
| 'open'
|
|
19
|
+
| 'port'
|
|
20
|
+
| 'preserveSymlinks'
|
|
21
|
+
| 'nodeResolve'
|
|
22
|
+
| 'debug'
|
|
23
|
+
| 'esbuildTarget'
|
|
24
|
+
>
|
|
25
|
+
> {
|
|
26
|
+
config?: string;
|
|
27
|
+
groups?: string;
|
|
28
|
+
group?: string;
|
|
29
|
+
puppeteer?: boolean;
|
|
30
|
+
playwright?: boolean;
|
|
31
|
+
browsers?: string[];
|
|
32
|
+
updateSnapshots?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const options: OptionDefinition[] = [
|
|
36
|
+
{
|
|
37
|
+
name: 'files',
|
|
38
|
+
type: String,
|
|
39
|
+
multiple: true,
|
|
40
|
+
defaultOption: true,
|
|
41
|
+
description: 'Test files to run',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'root-dir',
|
|
45
|
+
type: String,
|
|
46
|
+
description: 'Root directory to serve files from.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'watch',
|
|
50
|
+
type: Boolean,
|
|
51
|
+
description: 'Reload tests on file changes',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'coverage',
|
|
55
|
+
type: Boolean,
|
|
56
|
+
description: 'Check for code coverage. Slows down testing.',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'concurrent-browsers',
|
|
60
|
+
type: Number,
|
|
61
|
+
description: 'Amount of browsers to run concurrently. Defaults to 2.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'concurrency',
|
|
65
|
+
type: Number,
|
|
66
|
+
description:
|
|
67
|
+
'Amount of test files to run concurrently. Defaults to total CPU cores divided by 2.',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'config',
|
|
71
|
+
type: String,
|
|
72
|
+
description: 'Location to read config file from.',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'static-logging',
|
|
76
|
+
type: Boolean,
|
|
77
|
+
description: 'Disables rendering a progress bar dynamically to the terminal.',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'manual',
|
|
81
|
+
type: Boolean,
|
|
82
|
+
description:
|
|
83
|
+
'Starts test runner in manual testing mode. Ignores browsers option and prints manual testing URL.',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'open',
|
|
87
|
+
type: Boolean,
|
|
88
|
+
description: 'Opens browser for manual testing. Requires the manual option to be set.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'port',
|
|
92
|
+
type: Number,
|
|
93
|
+
description: 'Port to bind the server on.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'groups',
|
|
97
|
+
type: String,
|
|
98
|
+
description: 'Pattern of group config files.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'group',
|
|
102
|
+
type: String,
|
|
103
|
+
description:
|
|
104
|
+
'Name of the group to run tests for. When this is set, the other groups are ignored.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'preserve-symlinks',
|
|
108
|
+
type: Boolean,
|
|
109
|
+
description: "Don't follow symlinks when resolving imports",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'puppeteer',
|
|
113
|
+
type: Boolean,
|
|
114
|
+
description: 'Run tests using puppeteer',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'playwright',
|
|
118
|
+
type: Boolean,
|
|
119
|
+
description: 'Run tests using playwright',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'browsers',
|
|
123
|
+
type: String,
|
|
124
|
+
multiple: true,
|
|
125
|
+
description: 'Browsers to run when choosing puppeteer or playwright',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'node-resolve',
|
|
129
|
+
type: Boolean,
|
|
130
|
+
description: 'Resolve bare module imports using node resolution',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'update-snapshots',
|
|
134
|
+
type: Boolean,
|
|
135
|
+
description: 'Whether to accept changes in shapshots, and save them on disk',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'esbuild-target',
|
|
139
|
+
type: String,
|
|
140
|
+
multiple: true,
|
|
141
|
+
description:
|
|
142
|
+
'JS language target to compile down to using esbuild. Recommended value is "auto", which compiles based on user agent. Check the docs for more options.',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'debug',
|
|
146
|
+
type: Boolean,
|
|
147
|
+
description: 'Log debug messages',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'help',
|
|
151
|
+
type: Boolean,
|
|
152
|
+
description: 'Print help commands',
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
export interface ReadCliArgsParams {
|
|
157
|
+
argv?: string[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function readCliArgs({ argv = process.argv }: ReadCliArgsParams = {}): TestRunnerCliArgs {
|
|
161
|
+
const cliArgs = commandLineArgs(options, { argv, partial: true });
|
|
162
|
+
|
|
163
|
+
if ('help' in cliArgs) {
|
|
164
|
+
console.log(
|
|
165
|
+
commandLineUsage([
|
|
166
|
+
{
|
|
167
|
+
header: 'Web Test Runner',
|
|
168
|
+
content: 'Test runner for web applications.',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
header: 'Usage',
|
|
172
|
+
content: 'web-test-runner [options...]' + '\nwtr [options...]',
|
|
173
|
+
},
|
|
174
|
+
{ header: 'Options', optionList: options },
|
|
175
|
+
]),
|
|
176
|
+
);
|
|
177
|
+
process.exit();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const cliArgsConfig: TestRunnerCliArgs = {};
|
|
181
|
+
|
|
182
|
+
for (const [key, value] of Object.entries(cliArgs)) {
|
|
183
|
+
cliArgsConfig[camelCase(key) as keyof TestRunnerCliArgs] = value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return cliArgsConfig;
|
|
187
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ConfigLoaderError, readConfig } from '@web/config-loader';
|
|
2
|
+
|
|
3
|
+
import { TestRunnerStartError } from '../TestRunnerStartError.js';
|
|
4
|
+
|
|
5
|
+
export interface ReadFileConfigParams {
|
|
6
|
+
/**
|
|
7
|
+
* The config name to use. Defaults to web-test-runner.config.{mjs,cjs,js}
|
|
8
|
+
*/
|
|
9
|
+
configName?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Path to the config file. If this is not set, the config is looked up in the
|
|
12
|
+
* current working directory using the config name.
|
|
13
|
+
*/
|
|
14
|
+
configPath?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reads the config from disk, defaults to process.cwd() + web-test-runner.config.{mjs,js,cjs} or
|
|
19
|
+
* a custom config path.
|
|
20
|
+
* @param customConfig the custom location to read the config from
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export async function readFileConfig({
|
|
24
|
+
configName = 'web-test-runner.config',
|
|
25
|
+
configPath,
|
|
26
|
+
}: ReadFileConfigParams = {}) {
|
|
27
|
+
try {
|
|
28
|
+
return await readConfig(configName, typeof configPath === 'string' ? configPath : undefined);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof ConfigLoaderError) {
|
|
31
|
+
throw new TestRunnerStartError(error.message);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TestRunnerConfig as FullTestRunnerConfig } from './config/TestRunnerConfig.js';
|
|
2
|
+
|
|
3
|
+
export { chromeLauncher } from '@web/test-runner-chrome';
|
|
4
|
+
export * from '@web/test-runner-core';
|
|
5
|
+
export { defaultReporter } from './reporter/defaultReporter.js';
|
|
6
|
+
export { dotReporter } from './reporter/dotReporter.js';
|
|
7
|
+
export { formatError } from './reporter/reportTestsErrors.js';
|
|
8
|
+
export { summaryReporter } from './reporter/summaryReporter.js';
|
|
9
|
+
export { startTestRunner } from './startTestRunner.js';
|
|
10
|
+
|
|
11
|
+
export type TestRunnerConfig = Partial<FullTestRunnerConfig>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type ErrorWithLocation, type Logger } from '@web/test-runner-core';
|
|
2
|
+
|
|
3
|
+
export class TestRunnerLogger implements Logger {
|
|
4
|
+
loggedSyntaxErrors = new Map<string, ErrorWithLocation[]>();
|
|
5
|
+
|
|
6
|
+
constructor(private debugLogging: boolean = false) {}
|
|
7
|
+
|
|
8
|
+
log(...messages: unknown[]) {
|
|
9
|
+
console.log(...messages);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
debug(...messages: unknown[]) {
|
|
13
|
+
if (this.debugLogging) {
|
|
14
|
+
console.debug(...messages);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
error(...messages: unknown[]) {
|
|
19
|
+
console.error(...messages);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
warn(...messages: unknown[]) {
|
|
23
|
+
console.warn(...messages);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
group() {
|
|
27
|
+
console.group();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
groupEnd() {
|
|
31
|
+
console.groupEnd();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logSyntaxError(error: ErrorWithLocation) {
|
|
35
|
+
const { message, code, filePath, column, line } = error;
|
|
36
|
+
let errors = this.loggedSyntaxErrors.get(filePath);
|
|
37
|
+
if (!errors) {
|
|
38
|
+
errors = [];
|
|
39
|
+
this.loggedSyntaxErrors.set(filePath, errors);
|
|
40
|
+
} else if (
|
|
41
|
+
errors.find(
|
|
42
|
+
e => e.code === code && e.message === message && e.column === column && e.line === line,
|
|
43
|
+
)
|
|
44
|
+
) {
|
|
45
|
+
// dedupe syntax errors we already logged
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
errors.push(error);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clearLoggedSyntaxErrors() {
|
|
52
|
+
this.loggedSyntaxErrors = new Map();
|
|
53
|
+
}
|
|
54
|
+
}
|