ui5-test-runner 6.0.0-beta.5 → 6.0.1
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 +20 -29
- package/dist/Command.js +52 -0
- package/dist/Npm.js +154 -74
- package/dist/browsers/RecoverableBrowserError.js +6 -0
- package/dist/browsers/chromeArguments.js +5 -0
- package/dist/browsers/chromeOptions.js +29 -0
- package/dist/browsers/consoleMessage.js +23 -0
- package/dist/browsers/factory.js +168 -3
- package/dist/browsers/networkResponse.js +15 -0
- package/dist/browsers/playwright.js +104 -0
- package/dist/browsers/puppeteer.js +54 -88
- package/dist/browsers/seleniumWebdriver.js +124 -0
- package/dist/browsers/webdriverio.js +85 -0
- package/dist/cli.js +18 -12
- package/dist/configuration/CommandLine.js +46 -26
- package/dist/configuration/ConfigurationValidator.js +132 -18
- package/dist/configuration/OptionValidationError.js +50 -2
- package/dist/configuration/derivations.js +8 -0
- package/dist/configuration/options.js +380 -23
- package/dist/configuration/validations.js +135 -0
- package/dist/configuration/validators/boolean.js +1 -1
- package/dist/configuration/validators/enumeration.js +7 -0
- package/dist/configuration/validators/fsEntry.js +12 -12
- package/dist/configuration/validators/index.js +8 -2
- package/dist/configuration/validators/integer.js +7 -1
- package/dist/configuration/validators/json.js +23 -0
- package/dist/configuration/validators/libraryMapping.js +28 -0
- package/dist/configuration/validators/percent.js +2 -2
- package/dist/configuration/validators/regexp.js +2 -2
- package/dist/configuration/validators/secret.js +8 -0
- package/dist/configuration/validators/string.js +1 -1
- package/dist/configuration/validators/timeout.js +2 -2
- package/dist/configuration/validators/url.js +3 -3
- package/dist/end.js +58 -0
- package/dist/if.js +22 -0
- package/dist/modes/Modes.js +2 -1
- package/dist/modes/batch/BatchItem.js +1 -0
- package/dist/modes/batch/batchTask.js +159 -0
- package/dist/modes/batch/index.js +98 -0
- package/dist/modes/batch/output.js +22 -0
- package/dist/modes/batch/report.js +45 -0
- package/dist/modes/batch/resolve.js +105 -0
- package/dist/modes/dumpConfig.js +6 -0
- package/dist/modes/execute.js +6 -5
- package/dist/modes/help.js +87 -2
- package/dist/modes/log/LogReader.js +3 -2
- package/dist/modes/log/LogStorage.js +31 -27
- package/dist/modes/log/index.js +38 -6
- package/dist/modes/log/{REserve.js → reserve.js} +25 -2
- package/dist/modes/mcp/IDocument.js +2 -0
- package/dist/modes/mcp/frontmatter.js +142 -0
- package/dist/modes/mcp/index.js +49 -0
- package/dist/modes/mcp/knowledgeBase.js +104 -0
- package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
- package/dist/modes/mcp/reserve.js +175 -0
- package/dist/modes/mcp/tools/getTopic.js +46 -0
- package/dist/modes/mcp/tools/index.js +6 -0
- package/dist/modes/mcp/tools/listTopics.js +14 -0
- package/dist/modes/test/PageContext.js +1 -0
- package/dist/modes/test/Server.js +99 -0
- package/dist/modes/test/browser.js +31 -17
- package/dist/modes/test/browserConfig.js +17 -0
- package/dist/modes/test/coverage/collect.js +38 -0
- package/dist/modes/test/coverage/index.js +3 -0
- package/dist/modes/test/coverage/instrument.js +70 -0
- package/dist/modes/test/coverage/nyc.js +15 -0
- package/dist/modes/test/coverage/report.js +71 -0
- package/dist/modes/test/coverage/settings.js +53 -0
- package/dist/modes/test/index.js +94 -34
- package/dist/modes/test/pageTask.js +293 -98
- package/dist/modes/test/report.js +20 -3
- package/dist/modes/test/reserve.js +76 -0
- package/dist/modes/test/screenshot.js +49 -0
- package/dist/platform/Crypto.js +9 -0
- package/dist/platform/Exit.js +33 -24
- package/dist/platform/FileSystem.js +6 -2
- package/dist/platform/Host.js +9 -1
- package/dist/platform/Http.js +54 -29
- package/dist/platform/Module.js +2 -1
- package/dist/platform/Path.js +5 -1
- package/dist/platform/Process.js +67 -13
- package/dist/platform/Terminal.js +12 -8
- package/dist/platform/Thread.js +1 -1
- package/dist/platform/assert.js +13 -12
- package/dist/platform/environment.js +9 -18
- package/dist/platform/index.js +1 -0
- package/dist/platform/logger/CompressionContext.js +117 -0
- package/dist/platform/logger/allCompressed.js +8 -7
- package/dist/platform/logger/compress.js +29 -129
- package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
- package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
- package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
- package/dist/platform/logger/output/factory.js +5 -1
- package/dist/platform/logger/output.js +24 -6
- package/dist/platform/logger/toInternalLogAttributes.js +10 -11
- package/dist/platform/logger/traceFileName.js +1 -0
- package/dist/platform/logger.js +52 -21
- package/dist/platform/mock.js +110 -19
- package/dist/platform/version.js +1 -2
- package/dist/platform/workerBootstrap.js +1 -1
- package/dist/reports/initReportBuilder.js +18 -0
- package/dist/reports/junit.js +64 -0
- package/dist/reports/saveReport.js +10 -0
- package/dist/reports/textReport.js +92 -0
- package/dist/reserveLogger.js +22 -0
- package/dist/sendToParentProcess.js +6 -0
- package/dist/start.js +79 -0
- package/dist/types/ISecretValue.js +1 -0
- package/dist/types/SecretValue.js +15 -0
- package/dist/ui/agent.js +4 -2
- package/dist/ui/html-report.js +2 -2
- package/dist/ui/lib.js +1 -1
- package/dist/ui/log-viewer.js +1 -1
- package/dist/utils/node/Folder.js +2 -4
- package/dist/utils/node/FramedStreamReader.js +4 -5
- package/dist/utils/node/FramedStreamWriter.js +6 -5
- package/dist/utils/node/anonymize.js +11 -0
- package/dist/utils/node/timeout.js +11 -0
- package/dist/utils/shared/ProgressBar.js +1 -2
- package/dist/utils/shared/TestReportBuilder.js +11 -7
- package/dist/utils/shared/host.js +11 -0
- package/dist/utils/shared/object.js +12 -3
- package/dist/utils/shared/schema.js +20 -0
- package/dist/utils/shared/string.js +1 -0
- package/dist/utils/shared/toIError.js +3 -7
- package/dist/utils/shared/version.js +52 -0
- package/package.json +3 -79
- package/dist/configuration/validators/browser.js +0 -11
- package/dist/modes/test/REserve.js +0 -31
- package/dist/modes/test/server.js +0 -109
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { FileSystem, Path, assert, logger } from '../../../platform/index.js';
|
|
2
|
+
const isCoverageData = (value) => {
|
|
3
|
+
if (value === null || typeof value !== 'object') {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
const entries = Object.values(value);
|
|
7
|
+
return (entries.length > 0 &&
|
|
8
|
+
entries.every((entry) => entry !== null && typeof entry === 'object' && 'path' in entry && typeof entry['path'] === 'string'));
|
|
9
|
+
};
|
|
10
|
+
const remapPaths = (coverageData, sourceDirectory) => Object.fromEntries(Object.entries(coverageData).map(([key, entry]) => {
|
|
11
|
+
const absoluteKey = Path.join(sourceDirectory, key);
|
|
12
|
+
return [absoluteKey, { ...entry, path: absoluteKey }];
|
|
13
|
+
}));
|
|
14
|
+
export const collect = async (configuration, pageContext) => {
|
|
15
|
+
const { page, pageId, url, isSuite } = pageContext;
|
|
16
|
+
if (isSuite || !configuration.coverage) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const coverageData = await page.eval('window.__coverage__');
|
|
20
|
+
if (!coverageData) {
|
|
21
|
+
logger.warn({ source: 'coverage', pageId, message: 'no coverage data found for page', data: { url } });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
assert(isCoverageData(coverageData), `Invalid coverage data for page ${url}`);
|
|
25
|
+
let data = coverageData;
|
|
26
|
+
const firstFilePath = Object.keys(data)[0];
|
|
27
|
+
try {
|
|
28
|
+
await FileSystem.access(firstFilePath, FileSystem.constants.R_OK);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
logger.debug({ source: 'coverage', pageId, message: 'coverage data requires mapping' });
|
|
32
|
+
data = remapPaths(coverageData, configuration.coverageSourceDir ?? configuration.webapp);
|
|
33
|
+
}
|
|
34
|
+
const fileName = `${pageId}.json`;
|
|
35
|
+
const filePath = Path.join(configuration.coverageTempDir, fileName);
|
|
36
|
+
await FileSystem.writeFile(filePath, JSON.stringify(data));
|
|
37
|
+
logger.debug({ source: 'coverage', pageId, message: `Coverage collected for ${url}`, data: { filePath } });
|
|
38
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { FileSystem, Host, Path, Process, assert, logger } from '../../../platform/index.js';
|
|
2
|
+
import { Folder } from '../../../utils/node/Folder.js';
|
|
3
|
+
import { getNycBin } from './nyc.js';
|
|
4
|
+
import { initSettings } from './settings.js';
|
|
5
|
+
const MIN_BASELINE_FILE_SIZE = 5;
|
|
6
|
+
export const instrument = async (configuration) => {
|
|
7
|
+
await Folder.recreate(configuration.coverageTempDir);
|
|
8
|
+
if (configuration.coverageSourceDir) {
|
|
9
|
+
logger.info({ source: 'coverage', message: 'coverageSourceDir is set, skipping local instrumentation' });
|
|
10
|
+
await generateBaseline(configuration);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (!configuration.webapp) {
|
|
14
|
+
logger.info({ source: 'coverage', message: 'no local webapp, skipping instrumentation' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (configuration.coverageRemoteInstrument?.length) {
|
|
18
|
+
logger.warn({
|
|
19
|
+
source: 'coverage',
|
|
20
|
+
message: 'coverageRemoteInstrument has no effect with local instrumentation (legacy mode)'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
logger.info({ source: 'coverage', message: 'instrumenting source files...' });
|
|
24
|
+
const { settings, settingsPath } = await initSettings(configuration);
|
|
25
|
+
const nycBin = await getNycBin(configuration);
|
|
26
|
+
const destinationDirectory = Path.join(configuration.coverageTempDir, 'instrumented');
|
|
27
|
+
const proc = Process.spawn('node', [
|
|
28
|
+
nycBin,
|
|
29
|
+
'instrument',
|
|
30
|
+
'--nycrc-path',
|
|
31
|
+
settingsPath,
|
|
32
|
+
'--cwd',
|
|
33
|
+
configuration.webapp,
|
|
34
|
+
configuration.webapp,
|
|
35
|
+
destinationDirectory
|
|
36
|
+
], { detached: true });
|
|
37
|
+
await proc.closed;
|
|
38
|
+
assert(proc.code === 0, `nyc instrument failed with code ${proc.code}`);
|
|
39
|
+
if (settings.all !== false) {
|
|
40
|
+
await generateBaseline(configuration);
|
|
41
|
+
}
|
|
42
|
+
logger.info({ source: 'coverage', message: 'instrumentation complete' });
|
|
43
|
+
};
|
|
44
|
+
const generateBaseline = async (configuration) => {
|
|
45
|
+
const { settings, settingsPath } = await initSettings(configuration);
|
|
46
|
+
if (settings.all === false) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const nycBin = await getNycBin(configuration);
|
|
50
|
+
const baselineTemporaryDirectory = Path.join(configuration.coverageTempDir, 'baseline');
|
|
51
|
+
const baselineProc = Process.spawn('node', [nycBin, '--nycrc-path', settingsPath, '--temp-dir', baselineTemporaryDirectory, Host.nodePath, '-e', ''], { detached: true });
|
|
52
|
+
await baselineProc.closed;
|
|
53
|
+
assert(baselineProc.code === 0, `nyc baseline generation failed with code ${baselineProc.code}`);
|
|
54
|
+
const baselineFiles = await FileSystem.readdir(baselineTemporaryDirectory, { withFileTypes: true });
|
|
55
|
+
let baselineIndex = 0;
|
|
56
|
+
for (const entry of baselineFiles) {
|
|
57
|
+
if (!entry.isFile()) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const sourcePath = Path.join(baselineTemporaryDirectory, entry.name);
|
|
61
|
+
const { size } = await FileSystem.stat(sourcePath);
|
|
62
|
+
if (size <= MIN_BASELINE_FILE_SIZE) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const destinationName = baselineIndex === 0 ? 'baseline.json' : `baseline-${baselineIndex}.json`;
|
|
66
|
+
await FileSystem.rename(sourcePath, Path.join(configuration.coverageTempDir, destinationName));
|
|
67
|
+
++baselineIndex;
|
|
68
|
+
}
|
|
69
|
+
await FileSystem.rm(baselineTemporaryDirectory, { recursive: true });
|
|
70
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Npm } from '../../../Npm.js';
|
|
2
|
+
import { Path } from '../../../platform/index.js';
|
|
3
|
+
const cache = new WeakMap();
|
|
4
|
+
export const getNycBin = (configuration) => {
|
|
5
|
+
let promise = cache.get(configuration);
|
|
6
|
+
if (!promise) {
|
|
7
|
+
promise = (async () => {
|
|
8
|
+
await Npm.import(configuration, 'nyc');
|
|
9
|
+
const nycRoot = await Npm.resolvePackageDir(configuration, 'nyc');
|
|
10
|
+
return Path.join(nycRoot, 'bin', 'nyc.js');
|
|
11
|
+
})();
|
|
12
|
+
cache.set(configuration, promise);
|
|
13
|
+
}
|
|
14
|
+
return promise;
|
|
15
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Host, Path, Process, logger } from '../../../platform/index.js';
|
|
2
|
+
import { createEmptyTestResults } from '../../../types/CommonTestReportFormat.js';
|
|
3
|
+
import { Folder } from '../../../utils/node/Folder.js';
|
|
4
|
+
import { getNycBin } from './nyc.js';
|
|
5
|
+
import { getSettingsPath } from './settings.js';
|
|
6
|
+
export const generateReport = async (configuration) => {
|
|
7
|
+
logger.info({ source: 'coverage', message: 'generating coverage report...' });
|
|
8
|
+
await Folder.recreate(configuration.coverageReportDir);
|
|
9
|
+
const [nycBin, settingsPath] = await Promise.all([getNycBin(configuration), getSettingsPath(configuration)]);
|
|
10
|
+
const mergedDirectory = Path.join(configuration.coverageTempDir, 'merged');
|
|
11
|
+
await Folder.create(mergedDirectory);
|
|
12
|
+
const mergedPath = Path.join(mergedDirectory, 'coverage.json');
|
|
13
|
+
const mergeProc = Process.spawn('node', [nycBin, 'merge', configuration.coverageTempDir, mergedPath], {
|
|
14
|
+
detached: true
|
|
15
|
+
});
|
|
16
|
+
await mergeProc.closed;
|
|
17
|
+
if (mergeProc.code !== 0) {
|
|
18
|
+
throw new Error(`nyc merge failed with code ${mergeProc.code}`);
|
|
19
|
+
}
|
|
20
|
+
const checks = [
|
|
21
|
+
['--branches', configuration.coverageCheckBranches],
|
|
22
|
+
['--functions', configuration.coverageCheckFunctions],
|
|
23
|
+
['--lines', configuration.coverageCheckLines],
|
|
24
|
+
['--statements', configuration.coverageCheckStatements]
|
|
25
|
+
];
|
|
26
|
+
const hasThresholds = checks.some(([, value]) => value > 0);
|
|
27
|
+
const thresholdArguments = checks.flatMap(([flag, value]) => [flag, String(value)]);
|
|
28
|
+
const checkCoverageArguments = hasThresholds ? ['--check-coverage', ...thresholdArguments] : [];
|
|
29
|
+
const reporters = [...configuration.coverageReporters];
|
|
30
|
+
if (!reporters.includes('text')) {
|
|
31
|
+
reporters.push('text');
|
|
32
|
+
}
|
|
33
|
+
const reporterArguments = reporters.flatMap((r) => ['--reporter', r]);
|
|
34
|
+
const proc = Process.spawn('node', [
|
|
35
|
+
nycBin,
|
|
36
|
+
'report',
|
|
37
|
+
'--nycrc-path',
|
|
38
|
+
settingsPath,
|
|
39
|
+
'--temp-dir',
|
|
40
|
+
mergedDirectory,
|
|
41
|
+
'--report-dir',
|
|
42
|
+
configuration.coverageReportDir,
|
|
43
|
+
...reporterArguments,
|
|
44
|
+
...checkCoverageArguments
|
|
45
|
+
], { detached: true, forceRender: true, env: { ...Host.env, NODE_OPTIONS: '' } });
|
|
46
|
+
await proc.closed;
|
|
47
|
+
if (proc.code !== 0) {
|
|
48
|
+
if (checkCoverageArguments.length > 0) {
|
|
49
|
+
const thresholds = checks
|
|
50
|
+
.filter(([, value]) => value > 0)
|
|
51
|
+
.map(([flag, value]) => `${flag.replace('--', '')}=${value}%`)
|
|
52
|
+
.join(', ');
|
|
53
|
+
logger.error({ source: 'coverage', message: `Coverage thresholds not met (${thresholds})` });
|
|
54
|
+
logger.info({ source: 'coverage', message: 'coverage report complete' });
|
|
55
|
+
const results = createEmptyTestResults();
|
|
56
|
+
results.tool.name = 'nyc';
|
|
57
|
+
results.summary.tests = 1;
|
|
58
|
+
results.summary.failed = 1;
|
|
59
|
+
results.tests.push({
|
|
60
|
+
name: 'Global code coverage check',
|
|
61
|
+
status: 'failed',
|
|
62
|
+
duration: 0,
|
|
63
|
+
message: `Coverage thresholds not met (${thresholds})`
|
|
64
|
+
});
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`nyc report failed with code ${proc.code}`);
|
|
68
|
+
}
|
|
69
|
+
logger.info({ source: 'coverage', message: 'coverage report complete' });
|
|
70
|
+
return undefined;
|
|
71
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FileSystem, Path, logger } from '../../../platform/index.js';
|
|
2
|
+
const cache = new WeakMap();
|
|
3
|
+
export const getSettingsPath = async (configuration) => {
|
|
4
|
+
const { settingsPath } = await initSettings(configuration);
|
|
5
|
+
return settingsPath;
|
|
6
|
+
};
|
|
7
|
+
export const getSettings = async (configuration) => {
|
|
8
|
+
const { settings } = await initSettings(configuration);
|
|
9
|
+
return settings;
|
|
10
|
+
};
|
|
11
|
+
export const initSettings = (configuration) => {
|
|
12
|
+
let promise = cache.get(configuration);
|
|
13
|
+
if (!promise) {
|
|
14
|
+
promise = _initSettings(configuration);
|
|
15
|
+
cache.set(configuration, promise);
|
|
16
|
+
}
|
|
17
|
+
return promise;
|
|
18
|
+
};
|
|
19
|
+
const _initSettings = async (configuration) => {
|
|
20
|
+
let base = {};
|
|
21
|
+
try {
|
|
22
|
+
await FileSystem.access(configuration.coverageSettings, FileSystem.constants.R_OK);
|
|
23
|
+
const raw = await FileSystem.readFile(configuration.coverageSettings, 'utf8');
|
|
24
|
+
base = JSON.parse(raw);
|
|
25
|
+
logger.debug({ source: 'coverage', message: `Loaded coverage settings from ${configuration.coverageSettings}` });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
logger.debug({ source: 'coverage', message: 'no coverage settings file found, using defaults' });
|
|
29
|
+
}
|
|
30
|
+
const exclude = [...(Array.isArray(base.exclude) ? base.exclude : [])];
|
|
31
|
+
const sourceDirectory = configuration.coverageSourceDir || configuration.webapp;
|
|
32
|
+
if (sourceDirectory) {
|
|
33
|
+
const temporaryDirectoryRelative = Path.relative(sourceDirectory, configuration.coverageTempDir);
|
|
34
|
+
const reportDirectoryRelative = Path.relative(sourceDirectory, configuration.coverageReportDir);
|
|
35
|
+
const testReportDirectoryRelative = Path.relative(sourceDirectory, configuration.reportDir);
|
|
36
|
+
exclude.push(`${temporaryDirectoryRelative}/**`, `${reportDirectoryRelative}/**`, `${testReportDirectoryRelative}/**`);
|
|
37
|
+
}
|
|
38
|
+
const settings = {
|
|
39
|
+
...base,
|
|
40
|
+
all: base.all !== false,
|
|
41
|
+
sourceMap: false,
|
|
42
|
+
coverageGlobalScope: 'window.top',
|
|
43
|
+
coverageGlobalScopeFunc: false,
|
|
44
|
+
...(sourceDirectory && { cwd: sourceDirectory }),
|
|
45
|
+
exclude
|
|
46
|
+
};
|
|
47
|
+
const settingsDirectory = Path.join(configuration.coverageTempDir, 'settings');
|
|
48
|
+
const settingsPath = Path.join(settingsDirectory, '.nycrc.json');
|
|
49
|
+
await FileSystem.mkdir(settingsDirectory, { recursive: true });
|
|
50
|
+
await FileSystem.writeFile(settingsPath, JSON.stringify(settings, undefined, 2));
|
|
51
|
+
logger.debug({ source: 'coverage', message: `Coverage settings written to ${settingsPath}` });
|
|
52
|
+
return { settings, settingsPath };
|
|
53
|
+
};
|
package/dist/modes/test/index.js
CHANGED
|
@@ -1,66 +1,126 @@
|
|
|
1
|
-
import { logger, logEnvironnement, Exit
|
|
1
|
+
import { logger, logEnvironnement, Exit } from '../../platform/index.js';
|
|
2
2
|
import { defaults } from '../../configuration/options.js';
|
|
3
|
+
import { redactSecrets } from '../../utils/node/anonymize.js';
|
|
4
|
+
import { toPlainObject } from '../../utils/shared/object.js';
|
|
3
5
|
import { parallelize } from '../../utils/shared/parallelize.js';
|
|
4
6
|
import { getAgentSource } from './agent.js';
|
|
5
|
-
import { setupBrowser } from './browser.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { setupBrowser, getBrowser } from './browser.js';
|
|
8
|
+
import { makePageTask, reportError } from './pageTask.js';
|
|
9
|
+
import { initBrowserConfig } from './browserConfig.js';
|
|
10
|
+
import { initReportBuilder, getReportBuilder, setReportBrowserInfo } from './report.js';
|
|
11
|
+
import { saveReport } from '../../reports/saveReport.js';
|
|
9
12
|
import { Folder } from '../../utils/node/Folder.js';
|
|
10
|
-
import {
|
|
13
|
+
import { Server } from './Server.js';
|
|
11
14
|
import { formatDuration } from '../../utils/shared/string.js';
|
|
15
|
+
import { start } from '../../start.js';
|
|
16
|
+
import { end } from '../../end.js';
|
|
17
|
+
import { sendToParentProcess } from '../../sendToParentProcess.js';
|
|
18
|
+
import { instrument, generateReport } from './coverage/index.js';
|
|
12
19
|
export const test = async (configuration) => {
|
|
20
|
+
await initReportBuilder(configuration);
|
|
13
21
|
await Folder.create(configuration.reportDir);
|
|
14
|
-
logger.start(configuration);
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
await logger.start(configuration);
|
|
23
|
+
sendToParentProcess({ type: 'report-dir', reportDir: configuration.reportDir });
|
|
24
|
+
logger.debug({
|
|
25
|
+
source: 'job',
|
|
26
|
+
message: 'configuration',
|
|
27
|
+
data: { defaults, configuration: redactSecrets(toPlainObject(configuration)) }
|
|
28
|
+
});
|
|
29
|
+
if (!configuration.noBanner) {
|
|
30
|
+
await logEnvironnement();
|
|
31
|
+
}
|
|
17
32
|
await getAgentSource();
|
|
18
|
-
let
|
|
33
|
+
let isBrowserStarted = false;
|
|
19
34
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
await start(configuration);
|
|
36
|
+
if (configuration.coverage) {
|
|
37
|
+
await instrument(configuration);
|
|
38
|
+
}
|
|
39
|
+
const port = await Server.start(configuration);
|
|
40
|
+
if (configuration.serveOnly) {
|
|
41
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
42
|
+
Exit.registerAsyncTask({
|
|
43
|
+
name: 'serveOnly',
|
|
44
|
+
stop: () => resolve()
|
|
45
|
+
});
|
|
46
|
+
logger.warn({ source: 'job', message: 'serving only, use CTRL+C to end command' });
|
|
47
|
+
await promise;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
26
50
|
if (!configuration.url) {
|
|
27
|
-
configuration.url = [new URL(configuration.testsuite, `http://localhost:0`).
|
|
51
|
+
configuration.url = [new URL(configuration.testsuite, `http://localhost:0`).href];
|
|
28
52
|
}
|
|
29
|
-
const urls =
|
|
30
|
-
|
|
31
|
-
|
|
53
|
+
const urls = configuration.url.map((url) => url.replace(':0/', () => `:${port}/`));
|
|
54
|
+
const capabilities = await setupBrowser(configuration);
|
|
55
|
+
setReportBrowserInfo(capabilities);
|
|
56
|
+
isBrowserStarted = true;
|
|
57
|
+
initBrowserConfig(configuration);
|
|
58
|
+
logger.info({ source: 'progress', message: 'executing pages', pageId: undefined, data: { value: 0, max: 0 } });
|
|
59
|
+
sendToParentProcess({ type: 'progress', count: 0, total: 0 });
|
|
32
60
|
let completed = 0;
|
|
33
|
-
|
|
61
|
+
let lastLoggedCompleted;
|
|
62
|
+
let lastLoggedMax;
|
|
63
|
+
const startedIndices = new Set();
|
|
64
|
+
await parallelize(makePageTask(configuration), urls, {
|
|
34
65
|
parallel: configuration.parallel,
|
|
35
66
|
on: (event) => {
|
|
36
|
-
if (event.type === '
|
|
67
|
+
if (event.type === 'started') {
|
|
68
|
+
startedIndices.add(event.index);
|
|
69
|
+
}
|
|
70
|
+
else if (event.type === 'failed') {
|
|
37
71
|
logger.error({ source: 'job', message: 'page failed', error: event.error, data: { url: event.input } });
|
|
38
|
-
|
|
72
|
+
++completed;
|
|
39
73
|
}
|
|
40
|
-
if (event.type === 'completed') {
|
|
74
|
+
else if (event.type === 'completed') {
|
|
41
75
|
++completed;
|
|
42
76
|
}
|
|
77
|
+
if (lastLoggedCompleted === completed && lastLoggedMax === urls.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
lastLoggedCompleted = completed;
|
|
81
|
+
lastLoggedMax = urls.length;
|
|
43
82
|
logger.info({
|
|
44
83
|
source: 'progress',
|
|
45
|
-
message: '
|
|
84
|
+
message: 'executing pages',
|
|
46
85
|
pageId: undefined,
|
|
47
86
|
data: { value: completed, max: urls.length }
|
|
48
87
|
});
|
|
88
|
+
sendToParentProcess({ type: 'progress', count: completed, total: urls.length });
|
|
49
89
|
}
|
|
50
90
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
91
|
+
for (const [index, url] of urls.entries()) {
|
|
92
|
+
if (!startedIndices.has(index)) {
|
|
93
|
+
reportError(url, 'not executed (process was interrupted)');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
getReportBuilder().finalize();
|
|
97
|
+
if (configuration.coverage) {
|
|
98
|
+
const coverageFailure = await generateReport(configuration);
|
|
99
|
+
if (coverageFailure) {
|
|
100
|
+
getReportBuilder().merge('coverage', coverageFailure);
|
|
101
|
+
}
|
|
57
102
|
}
|
|
103
|
+
const { report } = getReportBuilder();
|
|
104
|
+
await saveReport(configuration, report);
|
|
105
|
+
const { passed, failed, tests, duration } = report.results.summary;
|
|
106
|
+
const durationString = duration ? ` (${formatDuration(duration)})` : '';
|
|
107
|
+
logger.info({
|
|
108
|
+
source: 'job',
|
|
109
|
+
message: `Tests completed: passed=${passed} failed=${failed} tests=${tests}${durationString}`
|
|
110
|
+
});
|
|
111
|
+
if (failed) {
|
|
112
|
+
Exit.code = -1;
|
|
113
|
+
}
|
|
114
|
+
sendToParentProcess({ type: 'done', passed, failed, tests });
|
|
115
|
+
await end(configuration);
|
|
58
116
|
}
|
|
59
117
|
catch (error) {
|
|
60
|
-
logger.error({ source: 'job', message: '
|
|
118
|
+
logger.error({ source: 'job', message: 'an error occurred', error });
|
|
61
119
|
}
|
|
62
120
|
finally {
|
|
63
|
-
|
|
64
|
-
|
|
121
|
+
if (isBrowserStarted) {
|
|
122
|
+
await getBrowser().shutdown();
|
|
123
|
+
}
|
|
124
|
+
await Server.stop();
|
|
65
125
|
}
|
|
66
126
|
};
|