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.
Files changed (130) hide show
  1. package/README.md +20 -29
  2. package/dist/Command.js +52 -0
  3. package/dist/Npm.js +154 -74
  4. package/dist/browsers/RecoverableBrowserError.js +6 -0
  5. package/dist/browsers/chromeArguments.js +5 -0
  6. package/dist/browsers/chromeOptions.js +29 -0
  7. package/dist/browsers/consoleMessage.js +23 -0
  8. package/dist/browsers/factory.js +168 -3
  9. package/dist/browsers/networkResponse.js +15 -0
  10. package/dist/browsers/playwright.js +104 -0
  11. package/dist/browsers/puppeteer.js +54 -88
  12. package/dist/browsers/seleniumWebdriver.js +124 -0
  13. package/dist/browsers/webdriverio.js +85 -0
  14. package/dist/cli.js +18 -12
  15. package/dist/configuration/CommandLine.js +46 -26
  16. package/dist/configuration/ConfigurationValidator.js +132 -18
  17. package/dist/configuration/OptionValidationError.js +50 -2
  18. package/dist/configuration/derivations.js +8 -0
  19. package/dist/configuration/options.js +380 -23
  20. package/dist/configuration/validations.js +135 -0
  21. package/dist/configuration/validators/boolean.js +1 -1
  22. package/dist/configuration/validators/enumeration.js +7 -0
  23. package/dist/configuration/validators/fsEntry.js +12 -12
  24. package/dist/configuration/validators/index.js +8 -2
  25. package/dist/configuration/validators/integer.js +7 -1
  26. package/dist/configuration/validators/json.js +23 -0
  27. package/dist/configuration/validators/libraryMapping.js +28 -0
  28. package/dist/configuration/validators/percent.js +2 -2
  29. package/dist/configuration/validators/regexp.js +2 -2
  30. package/dist/configuration/validators/secret.js +8 -0
  31. package/dist/configuration/validators/string.js +1 -1
  32. package/dist/configuration/validators/timeout.js +2 -2
  33. package/dist/configuration/validators/url.js +3 -3
  34. package/dist/end.js +58 -0
  35. package/dist/if.js +22 -0
  36. package/dist/modes/Modes.js +2 -1
  37. package/dist/modes/batch/BatchItem.js +1 -0
  38. package/dist/modes/batch/batchTask.js +159 -0
  39. package/dist/modes/batch/index.js +98 -0
  40. package/dist/modes/batch/output.js +22 -0
  41. package/dist/modes/batch/report.js +45 -0
  42. package/dist/modes/batch/resolve.js +105 -0
  43. package/dist/modes/dumpConfig.js +6 -0
  44. package/dist/modes/execute.js +6 -5
  45. package/dist/modes/help.js +87 -2
  46. package/dist/modes/log/LogReader.js +3 -2
  47. package/dist/modes/log/LogStorage.js +31 -27
  48. package/dist/modes/log/index.js +38 -6
  49. package/dist/modes/log/{REserve.js → reserve.js} +25 -2
  50. package/dist/modes/mcp/IDocument.js +2 -0
  51. package/dist/modes/mcp/frontmatter.js +142 -0
  52. package/dist/modes/mcp/index.js +49 -0
  53. package/dist/modes/mcp/knowledgeBase.js +104 -0
  54. package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
  55. package/dist/modes/mcp/reserve.js +175 -0
  56. package/dist/modes/mcp/tools/getTopic.js +46 -0
  57. package/dist/modes/mcp/tools/index.js +6 -0
  58. package/dist/modes/mcp/tools/listTopics.js +14 -0
  59. package/dist/modes/test/PageContext.js +1 -0
  60. package/dist/modes/test/Server.js +99 -0
  61. package/dist/modes/test/browser.js +31 -17
  62. package/dist/modes/test/browserConfig.js +17 -0
  63. package/dist/modes/test/coverage/collect.js +38 -0
  64. package/dist/modes/test/coverage/index.js +3 -0
  65. package/dist/modes/test/coverage/instrument.js +70 -0
  66. package/dist/modes/test/coverage/nyc.js +15 -0
  67. package/dist/modes/test/coverage/report.js +71 -0
  68. package/dist/modes/test/coverage/settings.js +53 -0
  69. package/dist/modes/test/index.js +94 -34
  70. package/dist/modes/test/pageTask.js +293 -98
  71. package/dist/modes/test/report.js +20 -3
  72. package/dist/modes/test/reserve.js +76 -0
  73. package/dist/modes/test/screenshot.js +49 -0
  74. package/dist/platform/Crypto.js +9 -0
  75. package/dist/platform/Exit.js +33 -24
  76. package/dist/platform/FileSystem.js +6 -2
  77. package/dist/platform/Host.js +9 -1
  78. package/dist/platform/Http.js +54 -29
  79. package/dist/platform/Module.js +2 -1
  80. package/dist/platform/Path.js +5 -1
  81. package/dist/platform/Process.js +67 -13
  82. package/dist/platform/Terminal.js +12 -8
  83. package/dist/platform/Thread.js +1 -1
  84. package/dist/platform/assert.js +13 -12
  85. package/dist/platform/environment.js +9 -18
  86. package/dist/platform/index.js +1 -0
  87. package/dist/platform/logger/CompressionContext.js +117 -0
  88. package/dist/platform/logger/allCompressed.js +8 -7
  89. package/dist/platform/logger/compress.js +29 -129
  90. package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
  91. package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
  92. package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
  93. package/dist/platform/logger/output/factory.js +5 -1
  94. package/dist/platform/logger/output.js +24 -6
  95. package/dist/platform/logger/toInternalLogAttributes.js +10 -11
  96. package/dist/platform/logger/traceFileName.js +1 -0
  97. package/dist/platform/logger.js +52 -21
  98. package/dist/platform/mock.js +110 -19
  99. package/dist/platform/version.js +1 -2
  100. package/dist/platform/workerBootstrap.js +1 -1
  101. package/dist/reports/initReportBuilder.js +18 -0
  102. package/dist/reports/junit.js +64 -0
  103. package/dist/reports/saveReport.js +10 -0
  104. package/dist/reports/textReport.js +92 -0
  105. package/dist/reserveLogger.js +22 -0
  106. package/dist/sendToParentProcess.js +6 -0
  107. package/dist/start.js +79 -0
  108. package/dist/types/ISecretValue.js +1 -0
  109. package/dist/types/SecretValue.js +15 -0
  110. package/dist/ui/agent.js +4 -2
  111. package/dist/ui/html-report.js +2 -2
  112. package/dist/ui/lib.js +1 -1
  113. package/dist/ui/log-viewer.js +1 -1
  114. package/dist/utils/node/Folder.js +2 -4
  115. package/dist/utils/node/FramedStreamReader.js +4 -5
  116. package/dist/utils/node/FramedStreamWriter.js +6 -5
  117. package/dist/utils/node/anonymize.js +11 -0
  118. package/dist/utils/node/timeout.js +11 -0
  119. package/dist/utils/shared/ProgressBar.js +1 -2
  120. package/dist/utils/shared/TestReportBuilder.js +11 -7
  121. package/dist/utils/shared/host.js +11 -0
  122. package/dist/utils/shared/object.js +12 -3
  123. package/dist/utils/shared/schema.js +20 -0
  124. package/dist/utils/shared/string.js +1 -0
  125. package/dist/utils/shared/toIError.js +3 -7
  126. package/dist/utils/shared/version.js +52 -0
  127. package/package.json +3 -79
  128. package/dist/configuration/validators/browser.js +0 -11
  129. package/dist/modes/test/REserve.js +0 -31
  130. 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,3 @@
1
+ export { instrument } from './instrument.js';
2
+ export { collect } from './collect.js';
3
+ export { generateReport } from './report.js';
@@ -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
+ };
@@ -1,66 +1,126 @@
1
- import { logger, logEnvironnement, Exit, FileSystem, Path, Http } from '../../platform/index.js';
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 { pageTask } from './pageTask.js';
7
- import { reportBuilder } from './report.js';
8
- import { generateHtmlReport } from '../../reports/html.js';
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 { server } from './server.js';
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
- logger.debug({ source: 'job', message: 'Configuration', data: { defaults, configuration } });
16
- await logEnvironnement();
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 browser;
33
+ let isBrowserStarted = false;
19
34
  try {
20
- const port = await server.start(configuration);
21
- const version = JSON.parse(await Http.get(`http://localhost:${port}/resources/sap-ui-version.json`));
22
- const { version: coreVersion } = version.libraries.find(({ name }) => name === 'sap.ui.core') ?? {
23
- version: 'unknown'
24
- };
25
- logger.info({ source: 'job', message: `UI5 version used by the local server: ${coreVersion}` });
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`).toString()];
51
+ configuration.url = [new URL(configuration.testsuite, `http://localhost:0`).href];
28
52
  }
29
- const urls = [...configuration.url.map((url) => url.replace(':0/', `:${port}/`))];
30
- browser = await setupBrowser(configuration);
31
- logger.info({ source: 'progress', message: 'Executing pages', pageId: undefined, data: { value: 0, max: 0 } });
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
- await parallelize(pageTask, urls, {
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 === 'failed') {
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
- Exit.code = -1;
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: 'Executing pages',
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
- reportBuilder.finalize();
52
- FileSystem.writeFileSync(Path.join(configuration.reportDir, 'report.json'), JSON.stringify(reportBuilder.report, undefined, 2), 'utf8');
53
- await generateHtmlReport(configuration, reportBuilder.report);
54
- const { duration } = reportBuilder.report.results.summary;
55
- if (duration) {
56
- logger.info({ source: 'job', message: `Tests duration: ${formatDuration(duration)}` });
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: 'An error occurred', error });
118
+ logger.error({ source: 'job', message: 'an error occurred', error });
61
119
  }
62
120
  finally {
63
- await browser?.shutdown();
64
- await server.stop();
121
+ if (isBrowserStarted) {
122
+ await getBrowser().shutdown();
123
+ }
124
+ await Server.stop();
65
125
  }
66
126
  };