ui5-test-runner 6.0.0-beta.4 → 6.0.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.
Files changed (131) hide show
  1. package/README.md +20 -29
  2. package/dist/Command.js +52 -0
  3. package/dist/Npm.js +193 -43
  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 +169 -4
  9. package/dist/browsers/networkResponse.js +15 -0
  10. package/dist/browsers/playwright.js +104 -0
  11. package/dist/browsers/puppeteer.js +55 -89
  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 +392 -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 +86 -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 +39 -7
  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 +5 -0
  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/Url.js +4 -0
  85. package/dist/platform/assert.js +13 -12
  86. package/dist/platform/environment.js +9 -18
  87. package/dist/platform/index.js +3 -0
  88. package/dist/platform/logger/CompressionContext.js +117 -0
  89. package/dist/platform/logger/allCompressed.js +8 -7
  90. package/dist/platform/logger/compress.js +29 -129
  91. package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
  92. package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
  93. package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
  94. package/dist/platform/logger/output/factory.js +5 -1
  95. package/dist/platform/logger/output.js +24 -6
  96. package/dist/platform/logger/toInternalLogAttributes.js +10 -11
  97. package/dist/platform/logger/traceFileName.js +1 -0
  98. package/dist/platform/logger.js +52 -21
  99. package/dist/platform/mock.js +124 -27
  100. package/dist/platform/version.js +1 -2
  101. package/dist/platform/workerBootstrap.js +1 -1
  102. package/dist/reports/initReportBuilder.js +18 -0
  103. package/dist/reports/junit.js +64 -0
  104. package/dist/reports/saveReport.js +10 -0
  105. package/dist/reports/textReport.js +92 -0
  106. package/dist/reserveLogger.js +22 -0
  107. package/dist/sendToParentProcess.js +6 -0
  108. package/dist/start.js +79 -0
  109. package/dist/types/ISecretValue.js +1 -0
  110. package/dist/types/SecretValue.js +15 -0
  111. package/dist/ui/agent.js +4 -2
  112. package/dist/ui/html-report.js +2 -2
  113. package/dist/ui/lib.js +1 -1
  114. package/dist/ui/log-viewer.js +1 -1
  115. package/dist/utils/node/Folder.js +2 -4
  116. package/dist/utils/node/FramedStreamReader.js +4 -5
  117. package/dist/utils/node/FramedStreamWriter.js +6 -5
  118. package/dist/utils/node/anonymize.js +11 -0
  119. package/dist/utils/node/timeout.js +11 -0
  120. package/dist/utils/shared/ProgressBar.js +1 -2
  121. package/dist/utils/shared/TestReportBuilder.js +11 -7
  122. package/dist/utils/shared/host.js +11 -0
  123. package/dist/utils/shared/object.js +12 -3
  124. package/dist/utils/shared/schema.js +20 -0
  125. package/dist/utils/shared/string.js +1 -0
  126. package/dist/utils/shared/toIError.js +3 -7
  127. package/dist/utils/shared/version.js +52 -0
  128. package/package.json +2 -78
  129. package/dist/configuration/validators/browser.js +0 -11
  130. package/dist/modes/test/REserve.js +0 -31
  131. package/dist/modes/test/server.js +0 -109
@@ -0,0 +1,15 @@
1
+ import { logger } from '../platform/index.js';
2
+ const LOG_TYPES = [null, null, null, null, 'warn', 'error'];
3
+ export const handleNetworkResponse = (pageId, url, method, status, requestHeaders, responseHeaders) => {
4
+ const statusType = Math.floor(status / 100);
5
+ const logType = LOG_TYPES[statusType] ?? 'info';
6
+ logger[logType]({
7
+ source: 'browser/network',
8
+ message: url,
9
+ pageId,
10
+ data: {
11
+ request: { method, headers: requestHeaders },
12
+ response: { status, headers: responseHeaders }
13
+ }
14
+ });
15
+ };
@@ -0,0 +1,104 @@
1
+ import { logger, Process, assert } from '../platform/index.js';
2
+ import { Npm } from '../Npm.js';
3
+ import { handleConsoleMessage } from './consoleMessage.js';
4
+ import { handleNetworkResponse } from './networkResponse.js';
5
+ import { buildChromeLaunchOptions } from './chromeOptions.js';
6
+ export const descriptor = {
7
+ supportedBrowsers: ['chromium', 'firefox', 'webkit'],
8
+ defaultBrowser: 'chromium',
9
+ screenshotFormat: '.png'
10
+ };
11
+ export const factory = async (configuration, signal) => {
12
+ const playwright = await Npm.import(configuration, 'playwright');
13
+ const { chromium } = playwright;
14
+ let browser;
15
+ let context;
16
+ let isFirstWindow = true;
17
+ const launchAndInstallIfNeeded = async (settings) => {
18
+ const launchOptions = {
19
+ headless: !settings.visible,
20
+ handleSIGINT: false,
21
+ ...buildChromeLaunchOptions(settings.options)
22
+ };
23
+ logger.debug({ source: 'playwright', message: 'launching browser' });
24
+ try {
25
+ browser = await chromium.launch(launchOptions);
26
+ }
27
+ catch (error) {
28
+ if (error instanceof Error && error.message.includes('Executable doesn')) {
29
+ logger.info({
30
+ source: 'progress',
31
+ message: 'installing chromium (playwright)',
32
+ pageId: undefined,
33
+ data: {
34
+ value: 1,
35
+ max: 0
36
+ }
37
+ });
38
+ await Process.spawn('npx', 'playwright install chromium'.split(' '), {
39
+ shell: true,
40
+ signal
41
+ }).closed;
42
+ browser = await chromium.launch(launchOptions);
43
+ }
44
+ else {
45
+ throw error;
46
+ }
47
+ }
48
+ context = await browser.newContext({
49
+ viewport: settings.viewport ?? null,
50
+ httpCredentials: settings.basicAuthentication
51
+ ? { username: settings.basicAuthentication.username, password: settings.basicAuthentication.password.reveal() }
52
+ : undefined
53
+ });
54
+ return {
55
+ browserName: browser.browserType().name(),
56
+ browserVersion: browser.version()
57
+ };
58
+ };
59
+ return {
60
+ async setup(settings) {
61
+ return await launchAndInstallIfNeeded(settings);
62
+ },
63
+ async newWindow(settings) {
64
+ let page;
65
+ if (isFirstWindow) {
66
+ isFirstWindow = false;
67
+ const existingPages = context?.pages();
68
+ page = existingPages?.[0] ?? (await context?.newPage());
69
+ }
70
+ else {
71
+ page = await context?.newPage();
72
+ }
73
+ assert(page !== undefined);
74
+ for (const script of settings.scripts) {
75
+ await page.addInitScript(script);
76
+ }
77
+ const { pageId } = settings;
78
+ page
79
+ .on('console', (message) => {
80
+ handleConsoleMessage(message.type(), message.text(), pageId);
81
+ })
82
+ .on('response', (response) => {
83
+ const request = response.request();
84
+ handleNetworkResponse(pageId, request.url(), request.method(), response.status(), request.headers(), response.headers());
85
+ });
86
+ await page.goto(settings.url);
87
+ return {
88
+ async eval(script) {
89
+ return await page?.evaluate(script);
90
+ },
91
+ async screenshot(path) {
92
+ await page.screenshot({ path });
93
+ },
94
+ async close() {
95
+ await page.goto('about:blank');
96
+ await page.close();
97
+ }
98
+ };
99
+ },
100
+ async shutdown() {
101
+ await browser?.close();
102
+ }
103
+ };
104
+ };
@@ -1,43 +1,51 @@
1
- import { logger, Exit, Process } from '../platform/index.js';
1
+ import { logger, Process } from '../platform/index.js';
2
2
  import { Npm } from '../Npm.js';
3
- import { agentLogPrefix } from '../types/AgentState.js';
4
- export const factory = async () => {
5
- const puppeteer = await Npm.import('puppeteer');
6
- const { launch } = puppeteer;
3
+ import { handleConsoleMessage } from './consoleMessage.js';
4
+ import { handleNetworkResponse } from './networkResponse.js';
5
+ import { buildChromeLaunchOptions } from './chromeOptions.js';
6
+ import { RecoverableBrowserError } from './RecoverableBrowserError.js';
7
+ export const descriptor = {
8
+ supportedBrowsers: ['chrome', 'firefox'],
9
+ defaultBrowser: 'chrome',
10
+ screenshotFormat: '.png'
11
+ };
12
+ export const factory = async (configuration, signal) => {
13
+ const puppeteer = await Npm.import(configuration, 'puppeteer');
14
+ const launch = puppeteer.launch;
7
15
  let browser;
8
- const abortController = new AbortController();
9
- const { signal } = abortController;
10
- const task = Exit.registerAsyncTask({
11
- name: 'puppeteer',
12
- async stop() {
13
- abortController.abort();
14
- await browser?.close();
15
- }
16
- });
17
- let openedPages = 0;
16
+ let isFirstWindow = true;
17
+ let basicAuthentication;
18
+ let deviceName;
18
19
  const launchAndInstallIfNeeded = async (settings) => {
20
+ const target = settings.browser ?? 'chrome';
21
+ const arguments_ = [
22
+ settings.viewport ? `--window-size=${settings.viewport.width},${settings.viewport.height}` : '--start-maximized'
23
+ ];
24
+ const chromeLaunchOptions = buildChromeLaunchOptions(settings.options, arguments_, target === 'chrome');
19
25
  const launchOptions = {
26
+ browser: target,
20
27
  headless: !settings.visible,
21
28
  defaultViewport: null,
22
29
  handleSIGINT: false,
23
30
  signal,
24
- args: ['--start-maximized']
31
+ ...chromeLaunchOptions
25
32
  };
33
+ logger.debug({ source: 'puppeteer', message: 'launching browser' });
26
34
  try {
27
35
  browser = await launch(launchOptions);
28
36
  }
29
37
  catch (error) {
30
- if (error instanceof Error && error.message.startsWith('Could not find Chrome')) {
38
+ if (error instanceof Error && error.message.startsWith('Could not find')) {
31
39
  logger.info({
32
40
  source: 'progress',
33
- message: 'Installing chrome (puppeteer)',
41
+ message: `Installing ${target} (puppeteer)`,
34
42
  pageId: undefined,
35
43
  data: {
36
44
  value: 1,
37
45
  max: 0
38
46
  }
39
47
  });
40
- await Process.spawn('npx', 'puppeteer browsers install chrome'.split(' '), {
48
+ await Process.spawn('npx', `puppeteer browsers install ${target}`.split(' '), {
41
49
  shell: true,
42
50
  signal
43
51
  }).closed;
@@ -47,27 +55,22 @@ export const factory = async () => {
47
55
  throw error;
48
56
  }
49
57
  }
50
- logger.debug({ source: 'puppeteer', message: 'setup completed' });
51
58
  return {
52
- screenshotFormat: '.png'
59
+ browserName: target,
60
+ browserVersion: await browser.version()
53
61
  };
54
62
  };
55
63
  return {
56
64
  async setup(settings) {
57
- logger.debug({ source: 'puppeteer', message: 'setup', data: settings });
58
- try {
59
- return await launchAndInstallIfNeeded(settings);
60
- }
61
- catch (error) {
62
- logger.error({ source: 'puppeteer', message: 'setup failed', error });
63
- task[Symbol.dispose]();
64
- throw error;
65
- }
65
+ basicAuthentication = settings.basicAuthentication;
66
+ deviceName = settings.device;
67
+ return await launchAndInstallIfNeeded(settings);
66
68
  },
67
69
  async newWindow(settings) {
68
- logger.debug({ source: 'puppeteer', message: 'newWindow', data: settings });
70
+ const { pageId } = settings;
69
71
  let page;
70
- if (++openedPages === 1) {
72
+ if (isFirstWindow) {
73
+ isFirstWindow = false;
71
74
  const pages = await browser?.pages(true);
72
75
  page = pages?.[0];
73
76
  }
@@ -76,83 +79,46 @@ export const factory = async () => {
76
79
  type: 'window'
77
80
  });
78
81
  }
82
+ if (deviceName) {
83
+ const knownDevices = puppeteer.KnownDevices ?? {};
84
+ const device = knownDevices[deviceName];
85
+ if (!device) {
86
+ throw new RecoverableBrowserError(`Unknown device "${deviceName}", available devices: ${Object.keys(knownDevices).join(', ')}`);
87
+ }
88
+ await page?.emulate(device);
89
+ }
79
90
  for (const script of settings.scripts) {
80
91
  await page?.evaluateOnNewDocument(script);
81
92
  }
82
- const { pageId } = settings;
83
93
  page
84
94
  ?.on('console', (message) => {
85
- const LOG_TYPES = {
86
- error: 'error',
87
- warn: 'warn',
88
- debug: 'debug'
89
- };
90
- const logType = LOG_TYPES[message.type()] ?? 'info';
91
- let source;
92
- let messageText = message.text();
93
- if (messageText.startsWith(agentLogPrefix)) {
94
- source = 'browser/agent';
95
- messageText = messageText.slice(agentLogPrefix.length);
96
- }
97
- else {
98
- source = 'browser/console';
99
- }
100
- logger[logType]({
101
- source,
102
- message: messageText,
103
- pageId,
104
- data: { type: message.type() }
105
- });
95
+ handleConsoleMessage(message.type(), message.text(), pageId);
106
96
  })
107
97
  ?.on('response', (response) => {
108
98
  const request = response.request();
109
- const statusType = Math.floor(response.status() / 100);
110
- const LOG_TYPES = [null, null, null, null, 'warn', 'error'];
111
- const logType = LOG_TYPES[statusType] ?? 'info';
112
- logger[logType]({
113
- source: 'browser/network',
114
- message: request.url(),
115
- pageId,
116
- data: {
117
- request: {
118
- method: request.method(),
119
- headers: request.headers()
120
- },
121
- response: {
122
- status: response.status(),
123
- headers: response.headers()
124
- }
125
- }
126
- });
99
+ handleNetworkResponse(pageId, request.url(), request.method(), response.status(), request.headers(), response.headers());
127
100
  });
101
+ if (basicAuthentication) {
102
+ await page?.authenticate({
103
+ username: basicAuthentication.username,
104
+ password: basicAuthentication.password.reveal()
105
+ });
106
+ }
128
107
  await page?.goto(settings.url);
129
- logger.debug({ source: 'puppeteer', message: 'newWindow completed', data: settings });
130
108
  return {
131
109
  async eval(script) {
132
110
  return await page?.evaluate(script);
133
111
  },
134
- screenshot() {
135
- throw new Error('Not implemented');
112
+ async screenshot(path) {
113
+ await page?.screenshot({ path });
136
114
  },
137
115
  async close() {
138
- try {
139
- await page?.close();
140
- }
141
- catch (error) {
142
- logger.error({ source: 'puppeteer', message: 'page.close failed', error });
143
- }
116
+ await page?.close();
144
117
  }
145
118
  };
146
119
  },
147
120
  async shutdown() {
148
- logger.debug({ source: 'puppeteer', message: 'shutdown' });
149
- try {
150
- await browser?.close();
151
- }
152
- catch (error) {
153
- logger.error({ source: 'puppeteer', message: 'browser.close failed', error });
154
- }
155
- task[Symbol.dispose]();
121
+ await browser?.close();
156
122
  }
157
123
  };
158
124
  };
@@ -0,0 +1,124 @@
1
+ import { logger } from '../platform/index.js';
2
+ import { Npm } from '../Npm.js';
3
+ import { handleConsoleMessage } from './consoleMessage.js';
4
+ import { handleNetworkResponse } from './networkResponse.js';
5
+ import { buildChromeLaunchOptions } from './chromeOptions.js';
6
+ import { writeFile } from 'node:fs/promises';
7
+ import { RecoverableBrowserError } from './RecoverableBrowserError.js';
8
+ export const descriptor = {
9
+ supportedBrowsers: ['chrome', 'firefox', 'edge', 'safari'],
10
+ defaultBrowser: 'chrome',
11
+ screenshotFormat: '.png'
12
+ };
13
+ const headersToObject = (headers) => Object.fromEntries(headers.map(({ name, value }) => [name.toLowerCase(), value.value ?? '']));
14
+ const withApiTimeout = async (ms, promise) => {
15
+ let timer;
16
+ const timeout = new Promise((_, reject) => {
17
+ timer = setTimeout(() => reject(new RecoverableBrowserError(`Selenium WebDriver API call did not respond within ${ms}ms — the session may have expired`)), ms);
18
+ });
19
+ try {
20
+ return await Promise.race([promise, timeout]);
21
+ }
22
+ finally {
23
+ clearTimeout(timer);
24
+ }
25
+ };
26
+ export const factory = async (configuration) => {
27
+ const selenium = await Npm.import(configuration, 'selenium-webdriver');
28
+ const { Builder: SeleniumBuilder, Browser } = selenium;
29
+ const { Options: ChromeOptions } = (await Npm.import(configuration, 'selenium-webdriver/chrome'));
30
+ const LogInspector = (await Npm.import(configuration, 'selenium-webdriver/bidi/logInspector')).default;
31
+ const BrowsingContext = (await Npm.import(configuration, 'selenium-webdriver/bidi/browsingContext')).default;
32
+ const ScriptManager = (await Npm.import(configuration, 'selenium-webdriver/bidi/scriptManager')).default;
33
+ const { CreateContextParameters: CreateContextParametersClass } = (await Npm.import(configuration, 'selenium-webdriver/bidi/createContextParameters'));
34
+ const { Network } = (await Npm.import(configuration, 'selenium-webdriver/bidi/network'));
35
+ let driver;
36
+ const contextToPageId = new Map();
37
+ let isFirstWindow = true;
38
+ return {
39
+ async setup(settings) {
40
+ logger.debug({ source: 'selenium-webdriver', message: 'launching browser' });
41
+ const options = new ChromeOptions();
42
+ const { args } = buildChromeLaunchOptions(settings.options, [
43
+ ...(settings.visible ? [] : ['--headless']),
44
+ '--start-maximized',
45
+ ...(settings.viewport ? [`--window-size=${settings.viewport.width},${settings.viewport.height}`] : [])
46
+ ]);
47
+ options.addArguments(...args);
48
+ options.enableBidi();
49
+ driver = new SeleniumBuilder().forBrowser(Browser.CHROME).setChromeOptions(options).build();
50
+ const caps = await driver.getCapabilities();
51
+ const logInspector = await LogInspector(driver);
52
+ await logInspector.onConsoleEntry((entry) => {
53
+ const pageId = (entry.source.browsingContextId === null ? undefined : contextToPageId.get(entry.source.browsingContextId)) ??
54
+ -1;
55
+ handleConsoleMessage(entry.method, entry.text, pageId);
56
+ });
57
+ const network = await Network(driver);
58
+ await network.responseStarted((event) => {
59
+ if (event === null || !('response' in event))
60
+ return;
61
+ const pageId = (event.id === null ? undefined : contextToPageId.get(event.id)) ?? -1;
62
+ handleNetworkResponse(pageId, event.request.url, event.request.method, event.response.status, headersToObject(event.request.headers), headersToObject(event.response.headers));
63
+ });
64
+ return {
65
+ browserName: caps.get('browserName') ?? 'chrome',
66
+ browserVersion: caps.get('browserVersion') ?? ''
67
+ };
68
+ },
69
+ async newWindow(settings) {
70
+ const { pageId, scripts, url } = settings;
71
+ const isFirst = isFirstWindow;
72
+ isFirstWindow = false;
73
+ let context;
74
+ if (isFirst) {
75
+ context = await driver.getWindowHandle();
76
+ contextToPageId.set(context, pageId);
77
+ for (const script of scripts) {
78
+ const sm = await ScriptManager(context, driver);
79
+ await sm.addPreloadScript(`() => { ${script} }`);
80
+ }
81
+ await driver.get(url);
82
+ }
83
+ else {
84
+ const tabParameters = new CreateContextParametersClass().background(true);
85
+ const bc = await BrowsingContext(driver, { type: 'tab', createParameters: tabParameters });
86
+ context = bc.id;
87
+ contextToPageId.set(context, pageId);
88
+ for (const script of scripts) {
89
+ const sm = await ScriptManager(context, driver);
90
+ await sm.addPreloadScript(`() => { ${script} }`);
91
+ }
92
+ await bc.navigate(url, 'interactive');
93
+ }
94
+ const { seleniumApiTimeout } = configuration;
95
+ return {
96
+ async eval(script) {
97
+ return withApiTimeout(seleniumApiTimeout, (async () => {
98
+ await driver.switchTo().window(context);
99
+ return driver.executeScript(`return (${script})`);
100
+ })());
101
+ },
102
+ async screenshot(path) {
103
+ await withApiTimeout(seleniumApiTimeout, (async () => {
104
+ await driver.switchTo().window(context);
105
+ const data = await driver.takeScreenshot();
106
+ await writeFile(path, Buffer.from(data, 'base64'));
107
+ })());
108
+ },
109
+ async close() {
110
+ contextToPageId.delete(context);
111
+ if (contextToPageId.size === 0) {
112
+ await BrowsingContext(driver, { type: 'tab' });
113
+ }
114
+ const bc = await BrowsingContext(driver, { browsingContextId: context });
115
+ await bc.close();
116
+ }
117
+ };
118
+ },
119
+ async shutdown() {
120
+ await new Promise((resolve) => setTimeout(resolve, 200));
121
+ await driver?.quit();
122
+ }
123
+ };
124
+ };
@@ -0,0 +1,85 @@
1
+ import { logger, Path } from '../platform/index.js';
2
+ import { Npm } from '../Npm.js';
3
+ import { handleConsoleMessage } from './consoleMessage.js';
4
+ import { handleNetworkResponse } from './networkResponse.js';
5
+ import { buildChromeLaunchOptions } from './chromeOptions.js';
6
+ export const descriptor = {
7
+ supportedBrowsers: ['chrome', 'firefox', 'edge', 'safari'],
8
+ defaultBrowser: 'chrome',
9
+ screenshotFormat: '.png'
10
+ };
11
+ const headerValue = (value) => typeof value === 'string' ? value : (value?.value ?? '');
12
+ const headersToObject = (headers) => Object.fromEntries((headers ?? []).map(({ name, value }) => [name.toLowerCase(), headerValue(value)]));
13
+ export const factory = async (configuration) => {
14
+ const webdriverio = await Npm.import(configuration, 'webdriverio');
15
+ const { remote } = webdriverio;
16
+ let browser;
17
+ const contextToPageId = new Map();
18
+ return {
19
+ async setup(settings) {
20
+ logger.debug({ source: 'webdriverio', message: 'launching browser' });
21
+ process.env['WDIO_LOG_PATH'] = Path.join(configuration.reportDir, 'wdio.log');
22
+ browser = await remote({
23
+ capabilities: {
24
+ browserName: 'chrome',
25
+ webSocketUrl: true,
26
+ 'goog:chromeOptions': buildChromeLaunchOptions(settings.options, [
27
+ ...(settings.visible ? [] : ['--headless']),
28
+ '--start-maximized',
29
+ ...(settings.viewport ? [`--window-size=${settings.viewport.width},${settings.viewport.height}`] : [])
30
+ ])
31
+ }
32
+ });
33
+ browser.on('log.entryAdded', (entry) => {
34
+ const event = entry;
35
+ if (event.type !== 'console') {
36
+ return;
37
+ }
38
+ const pageId = (event.source?.context === undefined ? undefined : contextToPageId.get(event.source.context)) ?? -1;
39
+ handleConsoleMessage(event.method ?? 'log', event.text ?? '', pageId);
40
+ });
41
+ await browser.sessionSubscribe({ events: ['network.responseStarted'] });
42
+ browser.on('network.responseStarted', (entry) => {
43
+ const event = entry;
44
+ const pageId = (event.context === null ? undefined : contextToPageId.get(event.context)) ?? -1;
45
+ handleNetworkResponse(pageId, event.request.url, event.request.method, event.response.status, headersToObject(event.request.headers), headersToObject(event.response.headers));
46
+ });
47
+ return {
48
+ browserName: browser.capabilities.browserName ?? 'chrome',
49
+ browserVersion: browser.capabilities.browserVersion ?? ''
50
+ };
51
+ },
52
+ async newWindow(settings) {
53
+ const { pageId, scripts, url } = settings;
54
+ const { context } = await browser.browsingContextCreate({ type: 'tab', background: true });
55
+ contextToPageId.set(context, pageId);
56
+ for (const script of scripts) {
57
+ await browser.scriptAddPreloadScript({
58
+ functionDeclaration: `() => { ${script} }`,
59
+ contexts: [context]
60
+ });
61
+ }
62
+ await browser.browsingContextNavigate({ context, url, wait: 'interactive' });
63
+ const switchTo = () => browser.switchToWindow(context);
64
+ return {
65
+ async eval(script) {
66
+ await switchTo();
67
+ return browser.execute(`return (${script})`);
68
+ },
69
+ async screenshot(path) {
70
+ await switchTo();
71
+ await browser.saveScreenshot(path);
72
+ },
73
+ async close() {
74
+ contextToPageId.delete(context);
75
+ await browser.browsingContextClose({ context });
76
+ }
77
+ };
78
+ },
79
+ async shutdown() {
80
+ await new Promise((resolve) => setTimeout(resolve, 200));
81
+ await browser?.deleteSession();
82
+ delete process.env['WDIO_LOG_PATH'];
83
+ }
84
+ };
85
+ };
package/dist/cli.js CHANGED
@@ -3,18 +3,24 @@ import { Exit, Host } from './platform/index.js';
3
3
  import { version } from './platform/version.js';
4
4
  import { CommandLine } from './configuration/CommandLine.js';
5
5
  import { execute } from './modes/execute.js';
6
- const main = async () => {
7
- const cliVersion = await version();
8
- const cliName = cliVersion.split('@')[0] ?? 'ui5-test-runner';
9
- const indexOfCli = process.argv.findIndex((value) => /[\\/]cli(\.[tj]s)?$/.exec(value) || value.endsWith(cliName));
10
- const configuration = await CommandLine.buildConfigurationFrom(Host.cwd(), process.argv.slice(indexOfCli + 1));
11
- await execute(configuration);
12
- };
13
- main()
14
- .catch((error) => {
6
+ import { isIfEvaluatedAsTrue } from './if.js';
7
+ import { sendToParentProcess } from './sendToParentProcess.js';
8
+ try {
9
+ const { name: cliName } = await version();
10
+ const indexOfCli = Host.argv.findIndex((value) => /[\\/]cli(\.[tj]s)?$/.exec(value) || value.endsWith(cliName));
11
+ const configuration = await CommandLine.buildConfigurationFrom(Host.cwd(), indexOfCli === -1 ? [] : Host.argv.slice(indexOfCli + 1));
12
+ if (await isIfEvaluatedAsTrue(configuration)) {
13
+ await execute(configuration);
14
+ }
15
+ else {
16
+ console.log(String.raw `/!\ Skipping execution (--if)`);
17
+ sendToParentProcess({ type: 'skip' });
18
+ }
19
+ }
20
+ catch (error) {
15
21
  console.error(error);
16
22
  Exit.code = -1;
17
- })
18
- .finally(async () => {
23
+ }
24
+ finally {
19
25
  await Exit.shutdown();
20
- });
26
+ }