@wdio/cli 9.0.0-alpha.9 → 9.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 (86) hide show
  1. package/build/commands/config.d.ts.map +1 -1
  2. package/build/commands/repl.d.ts.map +1 -1
  3. package/build/commands/run.d.ts +14 -13
  4. package/build/commands/run.d.ts.map +1 -1
  5. package/build/constants.d.ts +83 -10
  6. package/build/constants.d.ts.map +1 -1
  7. package/build/index.cjs +46 -0
  8. package/build/index.d.cts +2 -0
  9. package/build/index.d.cts.map +1 -0
  10. package/build/index.js +3233 -4
  11. package/build/interface.d.ts +0 -1
  12. package/build/interface.d.ts.map +1 -1
  13. package/build/launcher.d.ts.map +1 -1
  14. package/build/run.d.ts.map +1 -1
  15. package/build/types.d.ts +3 -4
  16. package/build/types.d.ts.map +1 -1
  17. package/build/utils.d.ts +7 -11
  18. package/build/utils.d.ts.map +1 -1
  19. package/build/watcher.d.ts.map +1 -1
  20. package/package.json +20 -25
  21. package/build/cjs/index.d.ts +0 -2
  22. package/build/cjs/index.d.ts.map +0 -1
  23. package/build/cjs/index.js +0 -26
  24. package/build/cjs/package.json +0 -5
  25. package/build/commands/config.js +0 -197
  26. package/build/commands/index.js +0 -5
  27. package/build/commands/install.js +0 -109
  28. package/build/commands/repl.js +0 -50
  29. package/build/commands/run.js +0 -262
  30. package/build/constants.js +0 -909
  31. package/build/install.js +0 -38
  32. package/build/interface.js +0 -285
  33. package/build/launcher.js +0 -513
  34. package/build/run.js +0 -75
  35. package/build/templates/EjsHelpers.js +0 -59
  36. package/build/templates/EjsHelpers.ts +0 -84
  37. package/build/templates/exampleFiles/browser/Component.css.ejs +0 -121
  38. package/build/templates/exampleFiles/browser/Component.lit.ejs +0 -154
  39. package/build/templates/exampleFiles/browser/Component.lit.test.ejs +0 -24
  40. package/build/templates/exampleFiles/browser/Component.preact.ejs +0 -28
  41. package/build/templates/exampleFiles/browser/Component.preact.test.ejs +0 -59
  42. package/build/templates/exampleFiles/browser/Component.react.ejs +0 -29
  43. package/build/templates/exampleFiles/browser/Component.react.test.ejs +0 -58
  44. package/build/templates/exampleFiles/browser/Component.solid.ejs +0 -28
  45. package/build/templates/exampleFiles/browser/Component.solid.test.ejs +0 -58
  46. package/build/templates/exampleFiles/browser/Component.stencil.ejs +0 -43
  47. package/build/templates/exampleFiles/browser/Component.stencil.test.ejs +0 -45
  48. package/build/templates/exampleFiles/browser/Component.svelte.ejs +0 -47
  49. package/build/templates/exampleFiles/browser/Component.svelte.test.ejs +0 -58
  50. package/build/templates/exampleFiles/browser/Component.vue.ejs +0 -34
  51. package/build/templates/exampleFiles/browser/Component.vue.test.ejs +0 -62
  52. package/build/templates/exampleFiles/browser/standalone.test.ejs +0 -13
  53. package/build/templates/exampleFiles/cucumber/features/login.feature +0 -12
  54. package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +0 -55
  55. package/build/templates/exampleFiles/mochaJasmine/test.e2e.js.ejs +0 -11
  56. package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +0 -45
  57. package/build/templates/exampleFiles/pageobjects/page.js.ejs +0 -17
  58. package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +0 -20
  59. package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +0 -1
  60. package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +0 -41
  61. package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +0 -100
  62. package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +0 -36
  63. package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +0 -37
  64. package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +0 -18
  65. package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +0 -23
  66. package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +0 -17
  67. package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +0 -86
  68. package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +0 -88
  69. package/build/templates/snippets/afterTest.ejs +0 -20
  70. package/build/templates/snippets/capabilities.ejs +0 -57
  71. package/build/templates/snippets/cucumber.ejs +0 -50
  72. package/build/templates/snippets/electronTest.js.ejs +0 -7
  73. package/build/templates/snippets/jasmine.ejs +0 -20
  74. package/build/templates/snippets/macosTest.js.ejs +0 -11
  75. package/build/templates/snippets/mocha.ejs +0 -14
  76. package/build/templates/snippets/reporters.ejs +0 -14
  77. package/build/templates/snippets/serenity.ejs +0 -18
  78. package/build/templates/snippets/services.ejs +0 -18
  79. package/build/templates/snippets/testWithPO.js.ejs +0 -22
  80. package/build/templates/snippets/testWithoutPO.js.ejs +0 -19
  81. package/build/templates/snippets/vscodeTest.js.ejs +0 -9
  82. package/build/templates/wdio.conf.tpl.ejs +0 -422
  83. package/build/types.js +0 -1
  84. package/build/utils.js +0 -930
  85. package/build/watcher.js +0 -156
  86. /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/index.js CHANGED
@@ -1,4 +1,3233 @@
1
- import 'dotenv/config';
2
- export { default as Launcher } from './launcher.js';
3
- export { default as run } from './run.js';
4
- export * from './types.js';
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/index.ts
8
+ import "dotenv/config";
9
+
10
+ // src/launcher.ts
11
+ import exitHook from "async-exit-hook";
12
+ import logger3 from "@wdio/logger";
13
+ import { validateConfig } from "@wdio/config";
14
+ import { ConfigParser as ConfigParser2 } from "@wdio/config/node";
15
+ import { initializePlugin, initializeLauncherService, sleep, enableFileLogging } from "@wdio/utils";
16
+ import { setupDriver, setupBrowser } from "@wdio/utils/node";
17
+
18
+ // src/interface.ts
19
+ import { EventEmitter } from "node:events";
20
+ import chalk2, { supportsColor } from "chalk";
21
+ import logger2 from "@wdio/logger";
22
+ import { SnapshotManager } from "@vitest/snapshot/manager";
23
+
24
+ // src/utils.ts
25
+ import fs2 from "node:fs/promises";
26
+ import util, { promisify } from "node:util";
27
+ import path2, { dirname } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { execSync, spawn } from "node:child_process";
30
+ import ejs from "ejs";
31
+ import chalk from "chalk";
32
+ import inquirer from "inquirer";
33
+ import pickBy from "lodash.pickby";
34
+ import logger from "@wdio/logger";
35
+ import readDir from "recursive-readdir";
36
+ import { $ } from "execa";
37
+ import { readPackageUp } from "read-pkg-up";
38
+ import { resolve } from "import-meta-resolve";
39
+ import { SevereServiceError } from "webdriverio";
40
+ import { ConfigParser } from "@wdio/config/node";
41
+ import { CAPABILITY_KEYS } from "@wdio/protocols";
42
+
43
+ // src/install.ts
44
+ import { execa } from "execa";
45
+ var installCommand = {
46
+ npm: "install",
47
+ pnpm: "add",
48
+ yarn: "add",
49
+ bun: "install"
50
+ };
51
+ var devFlag = {
52
+ npm: "--save-dev",
53
+ pnpm: "--save-dev",
54
+ yarn: "--dev",
55
+ bun: "--dev"
56
+ };
57
+ async function installPackages(cwd, packages, dev) {
58
+ const pm = detectPackageManager();
59
+ const devParam = dev ? devFlag[pm] : "";
60
+ console.log("\n");
61
+ const p = execa(pm, [installCommand[pm], ...packages, devParam], {
62
+ cwd,
63
+ stdout: process.stdout,
64
+ stderr: process.stderr
65
+ });
66
+ const { stdout, stderr, exitCode } = await p;
67
+ if (exitCode !== 0) {
68
+ const cmd = getInstallCommand(pm, packages, dev);
69
+ const customError = `\u26A0\uFE0F An unknown error happened! Please retry installing dependencies via "${cmd}"
70
+
71
+ Error: ${stderr || stdout || "unknown"}`;
72
+ console.error(customError);
73
+ return false;
74
+ }
75
+ return true;
76
+ }
77
+ function getInstallCommand(pm, packages, dev) {
78
+ const devParam = dev ? devFlag[pm] : "";
79
+ return `${pm} ${installCommand[pm]} ${packages.join(" ")} ${devParam}`;
80
+ }
81
+
82
+ // src/constants.ts
83
+ import fs from "node:fs";
84
+ import path from "node:path";
85
+ import { HOOK_DEFINITION } from "@wdio/utils";
86
+
87
+ // package.json
88
+ var package_default = {
89
+ name: "@wdio/cli",
90
+ version: "9.0.0-alpha.0",
91
+ description: "WebdriverIO testrunner command line interface",
92
+ author: "Christian Bromann <mail@bromann.dev>",
93
+ homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-cli",
94
+ license: "MIT",
95
+ bin: {
96
+ wdio: "./bin/wdio.js"
97
+ },
98
+ engines: {
99
+ node: ">=18"
100
+ },
101
+ repository: {
102
+ type: "git",
103
+ url: "git://github.com/webdriverio/webdriverio.git",
104
+ directory: "packages/wdio-cli"
105
+ },
106
+ keywords: [
107
+ "webdriver",
108
+ "webdriverio",
109
+ "wdio",
110
+ "cli"
111
+ ],
112
+ bugs: {
113
+ url: "https://github.com/webdriverio/webdriverio/issues"
114
+ },
115
+ main: "./build/index.cjs",
116
+ type: "module",
117
+ module: "./build/index.js",
118
+ types: "./build/index.d.ts",
119
+ exports: {
120
+ ".": {
121
+ types: "./build/index.d.ts",
122
+ import: "./build/index.js",
123
+ requireSource: "./src/index.cts",
124
+ require: "./build/index.cjs"
125
+ }
126
+ },
127
+ typeScriptVersion: "3.8.3",
128
+ dependencies: {
129
+ "@types/node": "^20.1.1",
130
+ "@vitest/snapshot": "^1.2.1",
131
+ "@wdio/config": "workspace:*",
132
+ "@wdio/globals": "workspace:*",
133
+ "@wdio/logger": "workspace:*",
134
+ "@wdio/protocols": "workspace:*",
135
+ "@wdio/types": "workspace:*",
136
+ "@wdio/utils": "workspace:*",
137
+ "async-exit-hook": "^2.0.1",
138
+ chalk: "^5.2.0",
139
+ chokidar: "^3.5.3",
140
+ "cli-spinners": "^3.0.0",
141
+ dotenv: "^16.3.1",
142
+ ejs: "^3.1.9",
143
+ execa: "^9.2.0",
144
+ "import-meta-resolve": "^4.0.0",
145
+ inquirer: "^10.1.8",
146
+ "lodash.flattendeep": "^4.4.0",
147
+ "lodash.pickby": "^4.6.0",
148
+ "lodash.union": "^4.6.0",
149
+ "read-pkg-up": "^10.0.0",
150
+ "recursive-readdir": "^2.2.3",
151
+ tsx: "^4.7.2",
152
+ webdriverio: "workspace:*",
153
+ yargs: "^17.7.2"
154
+ },
155
+ devDependencies: {
156
+ "@types/async-exit-hook": "^2.0.0",
157
+ "@types/ejs": "^3.1.2",
158
+ "@types/inquirer": "^9.0.3",
159
+ "@types/lodash.flattendeep": "^4.4.7",
160
+ "@types/lodash.pickby": "^4.6.7",
161
+ "@types/lodash.union": "^4.6.7",
162
+ "@types/recursive-readdir": "^2.2.1",
163
+ "@types/yargs": "^17.0.24"
164
+ },
165
+ publishConfig: {
166
+ access: "public"
167
+ }
168
+ };
169
+
170
+ // src/constants.ts
171
+ var pkg = package_default;
172
+ var CLI_EPILOGUE = `Documentation: https://webdriver.io
173
+ @wdio/cli (v${pkg.version})`;
174
+ var CONFIG_HELPER_INTRO = `
175
+ ===============================
176
+ \u{1F916} WDIO Configuration Wizard \u{1F9D9}
177
+ ===============================
178
+ `;
179
+ var SUPPORTED_COMMANDS = ["run", "install", "config", "repl"];
180
+ var PMs = ["npm", "yarn", "pnpm", "bun"];
181
+ var SUPPORTED_CONFIG_FILE_EXTENSION = ["js", "ts", "mjs", "mts", "cjs", "cts"];
182
+ var configHelperSuccessMessage = ({ projectRootDir, runScript, extraInfo = "" }) => `
183
+ \u{1F916} Successfully setup project at ${projectRootDir} \u{1F389}
184
+
185
+ Join our Discord Community Server and instantly find answers to your issues or queries. Or just join and say hi \u{1F44B}!
186
+ \u{1F517} https://discord.webdriver.io
187
+
188
+ Visit the project on GitHub to report bugs \u{1F41B} or raise feature requests \u{1F4A1}:
189
+ \u{1F517} https://github.com/webdriverio/webdriverio
190
+ ${extraInfo}
191
+ To run your tests, execute:
192
+ $ cd ${projectRootDir}
193
+ $ npm run ${runScript}
194
+ `;
195
+ var CONFIG_HELPER_SERENITY_BANNER = `
196
+ Learn more about Serenity/JS:
197
+ \u{1F517} https://serenity-js.org
198
+ `;
199
+ var DEPENDENCIES_INSTALLATION_MESSAGE = `
200
+ To install dependencies, execute:
201
+ %s
202
+ `;
203
+ var ANDROID_CONFIG = {
204
+ platformName: "Android",
205
+ automationName: "UiAutomator2",
206
+ deviceName: "Test"
207
+ };
208
+ var IOS_CONFIG = {
209
+ platformName: "iOS",
210
+ automationName: "XCUITest",
211
+ deviceName: "iPhone Simulator"
212
+ };
213
+ var SUPPORTED_PACKAGES = {
214
+ runner: [
215
+ { name: "E2E Testing - of Web or Mobile Applications", value: "@wdio/local-runner$--$local$--$e2e" },
216
+ { name: "Component or Unit Testing - in the browser\n > https://webdriver.io/docs/component-testing", value: "@wdio/browser-runner$--$browser$--$component" },
217
+ { name: "Desktop Testing - of Electron Applications\n > https://webdriver.io/docs/desktop-testing/electron", value: "@wdio/local-runner$--$local$--$electron" },
218
+ { name: "Desktop Testing - of MacOS Applications\n > https://webdriver.io/docs/desktop-testing/macos", value: "@wdio/local-runner$--$local$--$macos" },
219
+ { name: "VS Code Extension Testing\n > https://webdriver.io/docs/vscode-extension-testing", value: "@wdio/local-runner$--$local$--$vscode" }
220
+ ],
221
+ framework: [
222
+ { name: "Mocha (https://mochajs.org/)", value: "@wdio/mocha-framework$--$mocha" },
223
+ { name: "Mocha with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$mocha" },
224
+ { name: "Jasmine (https://jasmine.github.io/)", value: "@wdio/jasmine-framework$--$jasmine" },
225
+ { name: "Jasmine with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$jasmine" },
226
+ { name: "Cucumber (https://cucumber.io/)", value: "@wdio/cucumber-framework$--$cucumber" },
227
+ { name: "Cucumber with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$cucumber" }
228
+ ],
229
+ reporter: [
230
+ { name: "spec", value: "@wdio/spec-reporter$--$spec" },
231
+ { name: "dot", value: "@wdio/dot-reporter$--$dot" },
232
+ { name: "junit", value: "@wdio/junit-reporter$--$junit" },
233
+ { name: "allure", value: "@wdio/allure-reporter$--$allure" },
234
+ { name: "sumologic", value: "@wdio/sumologic-reporter$--$sumologic" },
235
+ { name: "concise", value: "@wdio/concise-reporter$--$concise" },
236
+ { name: "json", value: "@wdio/json-reporter$--$json" },
237
+ // external
238
+ { name: "reportportal", value: "wdio-reportportal-reporter$--$reportportal" },
239
+ { name: "video", value: "wdio-video-reporter$--$video" },
240
+ { name: "cucumber-json", value: "wdio-cucumberjs-json-reporter$--$cucumberjs-json" },
241
+ { name: "mochawesome", value: "wdio-mochawesome-reporter$--$mochawesome" },
242
+ { name: "timeline", value: "wdio-timeline-reporter$--$timeline" },
243
+ { name: "html-nice", value: "wdio-html-nice-reporter$--$html-nice" },
244
+ { name: "slack", value: "@moroo/wdio-slack-reporter$--$slack" },
245
+ { name: "teamcity", value: "wdio-teamcity-reporter$--$teamcity" },
246
+ { name: "delta", value: "@delta-reporter/wdio-delta-reporter-service$--$delta" },
247
+ { name: "testrail", value: "@wdio/testrail-reporter$--$testrail" },
248
+ { name: "light", value: "wdio-light-reporter$--$light" }
249
+ ],
250
+ plugin: [
251
+ { name: "wait-for: utilities that provide functionalities to wait for certain conditions till a defined task is complete.\n > https://www.npmjs.com/package/wdio-wait-for", value: "wdio-wait-for$--$wait-for" },
252
+ { name: "angular-component-harnesses: support for Angular component test harnesses\n > https://www.npmjs.com/package/@badisi/wdio-harness", value: "@badisi/wdio-harness$--$harness" },
253
+ { name: "Testing Library: utilities that encourage good testing practices laid down by dom-testing-library.\n > https://testing-library.com/docs/webdriverio-testing-library/intro", value: "@testing-library/webdriverio$--$testing-library" }
254
+ ],
255
+ service: [
256
+ // internal or community driver services
257
+ { name: "visual", value: "@wdio/visual-service$--$visual" },
258
+ { name: "vite", value: "wdio-vite-service$--$vite" },
259
+ { name: "nuxt", value: "wdio-nuxt-service$--$nuxt" },
260
+ { name: "firefox-profile", value: "@wdio/firefox-profile-service$--$firefox-profile" },
261
+ { name: "gmail", value: "wdio-gmail-service$--$gmail" },
262
+ { name: "sauce", value: "@wdio/sauce-service$--$sauce" },
263
+ { name: "testingbot", value: "@wdio/testingbot-service$--$testingbot" },
264
+ { name: "browserstack", value: "@wdio/browserstack-service$--$browserstack" },
265
+ { name: "lighthouse", value: "@wdio/lighthouse-service$--$lighthouse" },
266
+ { name: "vscode", value: "wdio-vscode-service$--$vscode" },
267
+ { name: "electron", value: "wdio-electron-service$--$electron" },
268
+ { name: "appium", value: "@wdio/appium-service$--$appium" },
269
+ // external
270
+ { name: "eslinter-service", value: "wdio-eslinter-service$--$eslinter" },
271
+ { name: "lambdatest", value: "wdio-lambdatest-service$--$lambdatest" },
272
+ { name: "zafira-listener", value: "wdio-zafira-listener-service$--$zafira-listener" },
273
+ { name: "reportportal", value: "wdio-reportportal-service$--$reportportal" },
274
+ { name: "docker", value: "wdio-docker-service$--$docker" },
275
+ { name: "ui5", value: "wdio-ui5-service$--$ui5" },
276
+ { name: "wiremock", value: "wdio-wiremock-service$--$wiremock" },
277
+ { name: "ng-apimock", value: "wdio-ng-apimock-service$--$ng-apimock" },
278
+ { name: "slack", value: "wdio-slack-service$--$slack" },
279
+ { name: "cucumber-viewport-logger", value: "wdio-cucumber-viewport-logger-service$--$cucumber-viewport-logger" },
280
+ { name: "intercept", value: "wdio-intercept-service$--$intercept" },
281
+ { name: "docker", value: "wdio-docker-service$--$docker" },
282
+ { name: "novus-visual-regression", value: "wdio-novus-visual-regression-service$--$novus-visual-regression" },
283
+ { name: "rerun", value: "wdio-rerun-service$--$rerun" },
284
+ { name: "winappdriver", value: "wdio-winappdriver-service$--$winappdriver" },
285
+ { name: "ywinappdriver", value: "wdio-ywinappdriver-service$--$ywinappdriver" },
286
+ { name: "performancetotal", value: "wdio-performancetotal-service$--$performancetotal" },
287
+ { name: "cleanuptotal", value: "wdio-cleanuptotal-service$--$cleanuptotal" },
288
+ { name: "aws-device-farm", value: "wdio-aws-device-farm-service$--$aws-device-farm" },
289
+ { name: "ms-teams", value: "wdio-ms-teams-service$--$ms-teams" },
290
+ { name: "tesults", value: "wdio-tesults-service$--$tesults" },
291
+ { name: "azure-devops", value: "@gmangiapelo/wdio-azure-devops-service$--$azure-devops" },
292
+ { name: "google-Chat", value: "wdio-google-chat-service$--$google-chat" },
293
+ { name: "qmate-service", value: "@sap_oss/wdio-qmate-service$--$qmate-service" },
294
+ { name: "vitaqai", value: "wdio-vitaqai-service$--$vitaqai" },
295
+ { name: "robonut", value: "wdio-robonut-service$--$robonut" },
296
+ { name: "qunit", value: "wdio-qunit-service$--$qunit" }
297
+ ]
298
+ };
299
+ var SUPPORTED_BROWSER_RUNNER_PRESETS = [
300
+ { name: "Lit (https://lit.dev/)", value: "$--$" },
301
+ { name: "Vue.js (https://vuejs.org/)", value: "@vitejs/plugin-vue$--$vue" },
302
+ { name: "Svelte (https://svelte.dev/)", value: "@sveltejs/vite-plugin-svelte$--$svelte" },
303
+ { name: "SolidJS (https://www.solidjs.com/)", value: "vite-plugin-solid$--$solid" },
304
+ { name: "StencilJS (https://stenciljs.com/)", value: "$--$stencil" },
305
+ { name: "React (https://reactjs.org/)", value: "@vitejs/plugin-react$--$react" },
306
+ { name: "Preact (https://preactjs.com/)", value: "@preact/preset-vite$--$preact" },
307
+ { name: "Other", value: false }
308
+ ];
309
+ var TESTING_LIBRARY_PACKAGES = {
310
+ react: "@testing-library/react",
311
+ preact: "@testing-library/preact",
312
+ vue: "@testing-library/vue",
313
+ svelte: "@testing-library/svelte",
314
+ solid: "solid-testing-library"
315
+ };
316
+ var BackendChoice = /* @__PURE__ */ ((BackendChoice2) => {
317
+ BackendChoice2["Local"] = "On my local machine";
318
+ BackendChoice2["Experitest"] = "In the cloud using Experitest";
319
+ BackendChoice2["Saucelabs"] = "In the cloud using Sauce Labs";
320
+ BackendChoice2["Browserstack"] = "In the cloud using BrowserStack";
321
+ BackendChoice2["OtherVendors"] = "In the cloud using Testingbot or LambdaTest or a different service";
322
+ BackendChoice2["Grid"] = "I have my own Selenium cloud";
323
+ return BackendChoice2;
324
+ })(BackendChoice || {});
325
+ var ElectronBuildToolChoice = /* @__PURE__ */ ((ElectronBuildToolChoice2) => {
326
+ ElectronBuildToolChoice2["ElectronForge"] = "Electron Forge (https://www.electronforge.io/)";
327
+ ElectronBuildToolChoice2["ElectronBuilder"] = "electron-builder (https://www.electron.build/)";
328
+ ElectronBuildToolChoice2["SomethingElse"] = "Something else";
329
+ return ElectronBuildToolChoice2;
330
+ })(ElectronBuildToolChoice || {});
331
+ var ProtocolOptions = /* @__PURE__ */ ((ProtocolOptions2) => {
332
+ ProtocolOptions2["HTTPS"] = "https";
333
+ ProtocolOptions2["HTTP"] = "http";
334
+ return ProtocolOptions2;
335
+ })(ProtocolOptions || {});
336
+ var RegionOptions = /* @__PURE__ */ ((RegionOptions2) => {
337
+ RegionOptions2["US"] = "us";
338
+ RegionOptions2["EU"] = "eu";
339
+ RegionOptions2["APAC"] = "apac";
340
+ return RegionOptions2;
341
+ })(RegionOptions || {});
342
+ var E2E_ENVIRONMENTS = [
343
+ { name: "Web - web applications in the browser", value: "web" },
344
+ { name: "Mobile - native, hybrid and mobile web apps, on Android or iOS", value: "mobile" }
345
+ ];
346
+ var MOBILE_ENVIRONMENTS = [
347
+ { name: "Android - native, hybrid and mobile web apps, tested on emulators and real devices\n > using UiAutomator2 (https://www.npmjs.com/package/appium-uiautomator2-driver)", value: "android" },
348
+ { name: "iOS - applications on iOS, iPadOS, and tvOS\n > using XCTest (https://appium.github.io/appium-xcuitest-driver)", value: "ios" }
349
+ ];
350
+ var BROWSER_ENVIRONMENTS = [
351
+ { name: "Chrome", value: "chrome" },
352
+ { name: "Firefox", value: "firefox" },
353
+ { name: "Safari", value: "safari" },
354
+ { name: "Microsoft Edge", value: "MicrosoftEdge" }
355
+ ];
356
+ function isBrowserRunner(answers) {
357
+ return answers.runner === SUPPORTED_PACKAGES.runner[1].value;
358
+ }
359
+ function usesSerenity(answers) {
360
+ return answers.framework.includes("serenity-js");
361
+ }
362
+ function getTestingPurpose(answers) {
363
+ return convertPackageHashToObject(answers.runner).purpose;
364
+ }
365
+ var isNuxtProject = [
366
+ path.join(process.cwd(), "nuxt.config.js"),
367
+ path.join(process.cwd(), "nuxt.config.ts"),
368
+ path.join(process.cwd(), "nuxt.config.mjs"),
369
+ path.join(process.cwd(), "nuxt.config.mts")
370
+ ].map((p) => {
371
+ try {
372
+ fs.accessSync(p);
373
+ return true;
374
+ } catch {
375
+ return false;
376
+ }
377
+ }).some(Boolean);
378
+ function selectDefaultService(serviceNames) {
379
+ serviceNames = Array.isArray(serviceNames) ? serviceNames : [serviceNames];
380
+ return SUPPORTED_PACKAGES.service.filter(({ name }) => serviceNames.includes(name)).map(({ value }) => value);
381
+ }
382
+ function prioServiceOrderFor(serviceNamesParam) {
383
+ const serviceNames = Array.isArray(serviceNamesParam) ? serviceNamesParam : [serviceNamesParam];
384
+ let services = SUPPORTED_PACKAGES.service;
385
+ for (const serviceName of serviceNames) {
386
+ const index = services.findIndex(({ name }) => name === serviceName);
387
+ services = [services[index], ...services.slice(0, index), ...services.slice(index + 1)];
388
+ }
389
+ return services;
390
+ }
391
+ var QUESTIONNAIRE = [{
392
+ type: "list",
393
+ name: "runner",
394
+ message: "What type of testing would you like to do?",
395
+ choices: SUPPORTED_PACKAGES.runner
396
+ }, {
397
+ type: "list",
398
+ name: "preset",
399
+ message: "Which framework do you use for building components?",
400
+ choices: SUPPORTED_BROWSER_RUNNER_PRESETS,
401
+ // only ask if there are more than 1 runner to pick from
402
+ when: (
403
+ /* istanbul ignore next */
404
+ isBrowserRunner
405
+ )
406
+ }, {
407
+ type: "confirm",
408
+ name: "installTestingLibrary",
409
+ message: "Do you like to use Testing Library (https://testing-library.com/) as test utility?",
410
+ default: true,
411
+ // only ask if there are more than 1 runner to pick from
412
+ when: (
413
+ /* istanbul ignore next */
414
+ (answers) => isBrowserRunner(answers) && /**
415
+ * Only show if Testing Library has an add-on for framework
416
+ */
417
+ answers.preset && TESTING_LIBRARY_PACKAGES[convertPackageHashToObject(answers.preset).short]
418
+ )
419
+ }, {
420
+ type: "list",
421
+ name: "electronBuildTool",
422
+ message: "Which tool are you using to build your Electron app?",
423
+ choices: Object.values(ElectronBuildToolChoice),
424
+ when: (
425
+ /* instanbul ignore next */
426
+ (answers) => getTestingPurpose(answers) === "electron"
427
+ )
428
+ }, {
429
+ type: "input",
430
+ name: "electronAppBinaryPath",
431
+ message: "What is the path to the binary of your built Electron app?",
432
+ when: (
433
+ /* istanbul ignore next */
434
+ (answers) => getTestingPurpose(answers) === "electron" && answers.electronBuildTool === "Something else" /* SomethingElse */
435
+ )
436
+ }, {
437
+ type: "list",
438
+ name: "backend",
439
+ message: "Where is your automation backend located?",
440
+ choices: Object.values(BackendChoice),
441
+ when: (
442
+ /* instanbul ignore next */
443
+ (answers) => getTestingPurpose(answers) === "e2e"
444
+ )
445
+ }, {
446
+ type: "list",
447
+ name: "e2eEnvironment",
448
+ message: "Which environment you would like to automate?",
449
+ choices: E2E_ENVIRONMENTS,
450
+ default: "web",
451
+ when: (
452
+ /* istanbul ignore next */
453
+ (answers) => getTestingPurpose(answers) === "e2e"
454
+ )
455
+ }, {
456
+ type: "list",
457
+ name: "mobileEnvironment",
458
+ message: "Which mobile environment you'ld like to automate?",
459
+ choices: MOBILE_ENVIRONMENTS,
460
+ when: (
461
+ /* instanbul ignore next */
462
+ (answers) => getTestingPurpose(answers) === "e2e" && answers.e2eEnvironment === "mobile"
463
+ )
464
+ }, {
465
+ type: "checkbox",
466
+ name: "browserEnvironment",
467
+ message: "With which browser should we start?",
468
+ choices: BROWSER_ENVIRONMENTS,
469
+ default: ["chrome"],
470
+ when: (
471
+ /* instanbul ignore next */
472
+ (answers) => getTestingPurpose(answers) === "e2e" && answers.e2eEnvironment === "web"
473
+ )
474
+ }, {
475
+ type: "input",
476
+ name: "hostname",
477
+ message: "What is the host address of that cloud service?",
478
+ when: (
479
+ /* istanbul ignore next */
480
+ (answers) => answers.backend && answers.backend.indexOf("different service") > -1
481
+ )
482
+ }, {
483
+ type: "input",
484
+ name: "port",
485
+ message: "What is the port on which that service is running?",
486
+ default: "80",
487
+ when: (
488
+ /* istanbul ignore next */
489
+ (answers) => answers.backend && answers.backend.indexOf("different service") > -1
490
+ )
491
+ }, {
492
+ type: "input",
493
+ name: "expEnvAccessKey",
494
+ message: "Access key from Experitest Cloud",
495
+ default: "EXPERITEST_ACCESS_KEY",
496
+ when: (
497
+ /* istanbul ignore next */
498
+ (answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
499
+ )
500
+ }, {
501
+ type: "input",
502
+ name: "expEnvHostname",
503
+ message: "Environment variable for cloud url",
504
+ default: "example.experitest.com",
505
+ when: (
506
+ /* istanbul ignore next */
507
+ (answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
508
+ )
509
+ }, {
510
+ type: "input",
511
+ name: "expEnvPort",
512
+ message: "Environment variable for port",
513
+ default: "443",
514
+ when: (
515
+ /* istanbul ignore next */
516
+ (answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
517
+ )
518
+ }, {
519
+ type: "list",
520
+ name: "expEnvProtocol",
521
+ message: "Choose a protocol for environment variable",
522
+ default: "https" /* HTTPS */,
523
+ choices: Object.values(ProtocolOptions),
524
+ when: (
525
+ /* istanbul ignore next */
526
+ (answers) => answers.backend === "In the cloud using Experitest" /* Experitest */ && answers.expEnvPort !== "80" && answers.expEnvPort !== "443"
527
+ )
528
+ }, {
529
+ type: "input",
530
+ name: "env_user",
531
+ message: "Environment variable for username",
532
+ default: "LT_USERNAME",
533
+ when: (
534
+ /* istanbul ignore next */
535
+ (answers) => answers.backend && answers.backend.indexOf("LambdaTest") > -1 && answers.hostname.indexOf("lambdatest.com") > -1
536
+ )
537
+ }, {
538
+ type: "input",
539
+ name: "env_key",
540
+ message: "Environment variable for access key",
541
+ default: "LT_ACCESS_KEY",
542
+ when: (
543
+ /* istanbul ignore next */
544
+ (answers) => answers.backend && answers.backend.indexOf("LambdaTest") > -1 && answers.hostname.indexOf("lambdatest.com") > -1
545
+ )
546
+ }, {
547
+ type: "input",
548
+ name: "env_user",
549
+ message: "Environment variable for username",
550
+ default: "BROWSERSTACK_USERNAME",
551
+ when: (
552
+ /* istanbul ignore next */
553
+ (answers) => answers.backend === "In the cloud using BrowserStack" /* Browserstack */
554
+ )
555
+ }, {
556
+ type: "input",
557
+ name: "env_key",
558
+ message: "Environment variable for access key",
559
+ default: "BROWSERSTACK_ACCESS_KEY",
560
+ when: (
561
+ /* istanbul ignore next */
562
+ (answers) => answers.backend === "In the cloud using BrowserStack" /* Browserstack */
563
+ )
564
+ }, {
565
+ type: "input",
566
+ name: "env_user",
567
+ message: "Environment variable for username",
568
+ default: "SAUCE_USERNAME",
569
+ when: (
570
+ /* istanbul ignore next */
571
+ (answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
572
+ )
573
+ }, {
574
+ type: "input",
575
+ name: "env_key",
576
+ message: "Environment variable for access key",
577
+ default: "SAUCE_ACCESS_KEY",
578
+ when: (
579
+ /* istanbul ignore next */
580
+ (answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
581
+ )
582
+ }, {
583
+ type: "list",
584
+ name: "region",
585
+ message: "In which region do you want to run your Sauce Labs tests in?",
586
+ choices: Object.values(RegionOptions),
587
+ when: (
588
+ /* istanbul ignore next */
589
+ (answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
590
+ )
591
+ }, {
592
+ type: "confirm",
593
+ name: "useSauceConnect",
594
+ message: "Are you testing a local application and need Sauce Connect to be set-up?\nRead more on Sauce Connect at: https://wiki.saucelabs.com/display/DOCS/Sauce+Connect+Proxy",
595
+ default: isNuxtProject,
596
+ when: (
597
+ /* istanbul ignore next */
598
+ (answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */ && !isNuxtProject
599
+ )
600
+ }, {
601
+ type: "input",
602
+ name: "hostname",
603
+ message: "What is the IP or URI to your Selenium standalone or grid server?",
604
+ default: "localhost",
605
+ when: (
606
+ /* istanbul ignore next */
607
+ (answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
608
+ )
609
+ }, {
610
+ type: "input",
611
+ name: "port",
612
+ message: "What is the port which your Selenium standalone or grid server is running on?",
613
+ default: "4444",
614
+ when: (
615
+ /* istanbul ignore next */
616
+ (answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
617
+ )
618
+ }, {
619
+ type: "input",
620
+ name: "path",
621
+ message: "What is the path to your browser driver or grid server?",
622
+ default: "/",
623
+ when: (
624
+ /* istanbul ignore next */
625
+ (answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
626
+ )
627
+ }, {
628
+ type: "list",
629
+ name: "framework",
630
+ message: "Which framework do you want to use?",
631
+ choices: (
632
+ /* instanbul ignore next */
633
+ (answers) => {
634
+ if (isBrowserRunner(answers)) {
635
+ return SUPPORTED_PACKAGES.framework.slice(0, 1);
636
+ }
637
+ if (getTestingPurpose(answers) === "electron") {
638
+ return SUPPORTED_PACKAGES.framework.filter(
639
+ ({ value }) => !value.startsWith("@serenity-js")
640
+ );
641
+ }
642
+ return SUPPORTED_PACKAGES.framework;
643
+ }
644
+ )
645
+ }, {
646
+ type: "confirm",
647
+ name: "isUsingTypeScript",
648
+ message: "Do you want to use Typescript to write tests?",
649
+ when: (
650
+ /* istanbul ignore next */
651
+ (answers) => {
652
+ if (answers.preset?.includes("stencil")) {
653
+ return false;
654
+ }
655
+ return true;
656
+ }
657
+ ),
658
+ default: (
659
+ /* istanbul ignore next */
660
+ (answers) => answers.preset?.includes("stencil") || detectCompiler(answers)
661
+ )
662
+ }, {
663
+ type: "confirm",
664
+ name: "generateTestFiles",
665
+ message: "Do you want WebdriverIO to autogenerate some test files?",
666
+ default: true,
667
+ when: (
668
+ /* istanbul ignore next */
669
+ (answers) => {
670
+ if (["vscode", "electron", "macos"].includes(getTestingPurpose(answers)) && answers.framework.includes("cucumber")) {
671
+ return false;
672
+ }
673
+ return true;
674
+ }
675
+ )
676
+ }, {
677
+ type: "input",
678
+ name: "specs",
679
+ message: "What should be the location of your spec files?",
680
+ default: (
681
+ /* istanbul ignore next */
682
+ (answers) => {
683
+ const pattern = isBrowserRunner(answers) ? "src/**/*.test" : "test/specs/**/*";
684
+ return getDefaultFiles(answers, pattern);
685
+ }
686
+ ),
687
+ when: (
688
+ /* istanbul ignore next */
689
+ (answers) => answers.generateTestFiles && answers.framework.match(/(mocha|jasmine)/)
690
+ )
691
+ }, {
692
+ type: "input",
693
+ name: "specs",
694
+ message: "What should be the location of your feature files?",
695
+ default: (answers) => getDefaultFiles(answers, "features/**/*.feature"),
696
+ when: (
697
+ /* istanbul ignore next */
698
+ (answers) => answers.generateTestFiles && answers.framework.includes("cucumber")
699
+ )
700
+ }, {
701
+ type: "input",
702
+ name: "stepDefinitions",
703
+ message: "What should be the location of your step definitions?",
704
+ default: (answers) => getDefaultFiles(answers, "features/step-definitions/steps"),
705
+ when: (
706
+ /* istanbul ignore next */
707
+ (answers) => answers.generateTestFiles && answers.framework.includes("cucumber")
708
+ )
709
+ }, {
710
+ type: "confirm",
711
+ name: "usePageObjects",
712
+ message: "Do you want to use page objects (https://martinfowler.com/bliki/PageObject.html)?",
713
+ default: true,
714
+ when: (
715
+ /* istanbul ignore next */
716
+ (answers) => answers.generateTestFiles && /**
717
+ * page objects aren't common for component testing
718
+ */
719
+ !isBrowserRunner(answers) && /**
720
+ * and also not needed when running VS Code tests since the service comes with
721
+ * its own page object implementation, nor when running Electron or MacOS tests
722
+ */
723
+ !["vscode", "electron", "macos"].includes(getTestingPurpose(answers)) && /**
724
+ * Serenity/JS generates Lean Page Objects by default, so there's no need to ask about it
725
+ * See https://serenity-js.org/handbook/web-testing/page-objects-pattern/
726
+ */
727
+ !usesSerenity(answers)
728
+ )
729
+ }, {
730
+ type: "input",
731
+ name: "pages",
732
+ message: "Where are your page objects located?",
733
+ default: (
734
+ /* istanbul ignore next */
735
+ (answers) => answers.framework.match(/(mocha|jasmine)/) ? getDefaultFiles(answers, "test/pageobjects/**/*") : getDefaultFiles(answers, "features/pageobjects/**/*")
736
+ ),
737
+ when: (
738
+ /* istanbul ignore next */
739
+ (answers) => answers.generateTestFiles && answers.usePageObjects
740
+ )
741
+ }, {
742
+ type: "input",
743
+ name: "serenityLibPath",
744
+ message: "What should be the location of your Serenity/JS Screenplay Pattern library?",
745
+ default: (
746
+ /* istanbul ignore next */
747
+ async (answers) => {
748
+ const projectRootDir = await getProjectRoot(answers);
749
+ const specsDir = path.resolve(projectRootDir, path.dirname(answers.specs || "").replace(/\*\*$/, ""));
750
+ return path.resolve(specsDir, "..", "serenity");
751
+ }
752
+ ),
753
+ when: (
754
+ /* istanbul ignore next */
755
+ (answers) => answers.generateTestFiles && usesSerenity(answers)
756
+ )
757
+ }, {
758
+ type: "checkbox",
759
+ name: "reporters",
760
+ message: "Which reporter do you want to use?",
761
+ choices: SUPPORTED_PACKAGES.reporter,
762
+ // @ts-ignore
763
+ default: [
764
+ SUPPORTED_PACKAGES.reporter.find(
765
+ /* istanbul ignore next */
766
+ ({ name }) => name === "spec"
767
+ ).value
768
+ ]
769
+ }, {
770
+ type: "checkbox",
771
+ name: "plugins",
772
+ message: "Do you want to add a plugin to your test setup?",
773
+ choices: SUPPORTED_PACKAGES.plugin,
774
+ default: []
775
+ }, {
776
+ type: "confirm",
777
+ name: "includeVisualTesting",
778
+ message: "Would you like to include Visual Testing to your setup? For more information see https://webdriver.io/docs/visual-testing!",
779
+ default: false,
780
+ when: (
781
+ /* istanbul ignore next */
782
+ (answers) => {
783
+ return ["e2e", "component"].includes(getTestingPurpose(answers));
784
+ }
785
+ )
786
+ }, {
787
+ type: "checkbox",
788
+ name: "services",
789
+ message: "Do you want to add a service to your test setup?",
790
+ choices: (answers) => {
791
+ const services = [];
792
+ if (answers.backend === "In the cloud using BrowserStack" /* Browserstack */) {
793
+ services.push("browserstack");
794
+ } else if (answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */) {
795
+ services.push("sauce");
796
+ }
797
+ if (answers.e2eEnvironment === "mobile") {
798
+ services.push("appium");
799
+ }
800
+ if (getTestingPurpose(answers) === "e2e" && isNuxtProject) {
801
+ services.push("nuxt");
802
+ }
803
+ if (getTestingPurpose(answers) === "vscode") {
804
+ return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "vscode")];
805
+ } else if (getTestingPurpose(answers) === "electron") {
806
+ return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "electron")];
807
+ } else if (getTestingPurpose(answers) === "macos") {
808
+ return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "appium")];
809
+ }
810
+ return prioServiceOrderFor(services);
811
+ },
812
+ default: (answers) => {
813
+ const defaultServices = [];
814
+ if (answers.backend === "In the cloud using BrowserStack" /* Browserstack */) {
815
+ defaultServices.push("browserstack");
816
+ } else if (answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */) {
817
+ defaultServices.push("sauce");
818
+ }
819
+ if (answers.e2eEnvironment === "mobile" || getTestingPurpose(answers) === "macos") {
820
+ defaultServices.push("appium");
821
+ }
822
+ if (getTestingPurpose(answers) === "vscode") {
823
+ defaultServices.push("vscode");
824
+ } else if (getTestingPurpose(answers) === "electron") {
825
+ defaultServices.push("electron");
826
+ }
827
+ if (isNuxtProject) {
828
+ defaultServices.push("nuxt");
829
+ }
830
+ if (answers.includeVisualTesting) {
831
+ defaultServices.push("visual");
832
+ }
833
+ return selectDefaultService(defaultServices);
834
+ }
835
+ }, {
836
+ type: "input",
837
+ name: "outputDir",
838
+ message: "In which directory should the xunit reports get stored?",
839
+ default: "./",
840
+ when: (
841
+ /* istanbul ignore next */
842
+ (answers) => answers.reporters.includes("junit")
843
+ )
844
+ }, {
845
+ type: "input",
846
+ name: "outputDir",
847
+ message: "In which directory should the json reports get stored?",
848
+ default: "./",
849
+ when: (
850
+ /* istanbul ignore next */
851
+ (answers) => answers.reporters.includes("json")
852
+ )
853
+ }, {
854
+ type: "input",
855
+ name: "outputDir",
856
+ message: "In which directory should the mochawesome json reports get stored?",
857
+ default: "./",
858
+ when: (
859
+ /* istanbul ignore next */
860
+ (answers) => answers.reporters.includes("mochawesome")
861
+ )
862
+ }, {
863
+ type: "confirm",
864
+ name: "npmInstall",
865
+ message: () => `Do you want me to run \`${detectPackageManager()} install\``,
866
+ default: true
867
+ }];
868
+ var SUPPORTED_SNAPSHOTSTATE_OPTIONS = ["all", "new", "none"];
869
+ var COMMUNITY_PACKAGES_WITH_TS_SUPPORT = [
870
+ "wdio-electron-service",
871
+ "wdio-vscode-service",
872
+ "wdio-nuxt-service",
873
+ "wdio-vite-service",
874
+ "wdio-gmail-service"
875
+ ];
876
+ var TESTRUNNER_DEFAULTS = {
877
+ /**
878
+ * Define specs for test execution. You can either specify a glob
879
+ * pattern to match multiple files at once or wrap a glob or set of
880
+ * paths into an array to run them within a single worker process.
881
+ */
882
+ specs: {
883
+ type: "object",
884
+ validate: (param) => {
885
+ if (!Array.isArray(param)) {
886
+ throw new Error('the "specs" option needs to be a list of strings');
887
+ }
888
+ }
889
+ },
890
+ /**
891
+ * exclude specs from test execution
892
+ */
893
+ exclude: {
894
+ type: "object",
895
+ validate: (param) => {
896
+ if (!Array.isArray(param)) {
897
+ throw new Error('the "exclude" option needs to be a list of strings');
898
+ }
899
+ }
900
+ },
901
+ /**
902
+ * key/value definition of suites (named by key) and a list of specs as value
903
+ * to specify a specific set of tests to execute
904
+ */
905
+ suites: {
906
+ type: "object"
907
+ },
908
+ /**
909
+ * Project root directory path.
910
+ */
911
+ rootDir: {
912
+ type: "string"
913
+ },
914
+ /**
915
+ * If you only want to run your tests until a specific amount of tests have failed use
916
+ * bail (default is 0 - don't bail, run all tests).
917
+ */
918
+ bail: {
919
+ type: "number",
920
+ default: 0
921
+ },
922
+ /**
923
+ * supported test framework by wdio testrunner
924
+ */
925
+ framework: {
926
+ type: "string"
927
+ },
928
+ /**
929
+ * capabilities of WebDriver sessions
930
+ */
931
+ capabilities: {
932
+ type: "object",
933
+ validate: (param) => {
934
+ if (!Array.isArray(param)) {
935
+ if (typeof param === "object") {
936
+ return true;
937
+ }
938
+ throw new Error('the "capabilities" options needs to be an object or a list of objects');
939
+ }
940
+ for (const option of param) {
941
+ if (typeof option === "object") {
942
+ continue;
943
+ }
944
+ throw new Error("expected every item of a list of capabilities to be of type object");
945
+ }
946
+ return true;
947
+ },
948
+ required: true
949
+ },
950
+ /**
951
+ * list of reporters to use, a reporter can be either a string or an object with
952
+ * reporter options, e.g.:
953
+ * [
954
+ * 'dot',
955
+ * {
956
+ * name: 'spec',
957
+ * outputDir: __dirname + '/reports'
958
+ * }
959
+ * ]
960
+ */
961
+ reporters: {
962
+ type: "object",
963
+ validate: (param) => {
964
+ if (!Array.isArray(param)) {
965
+ throw new Error('the "reporters" options needs to be a list of strings');
966
+ }
967
+ const isValidReporter = (option) => typeof option === "string" || typeof option === "function";
968
+ for (const option of param) {
969
+ if (isValidReporter(option)) {
970
+ continue;
971
+ }
972
+ if (Array.isArray(option) && typeof option[1] === "object" && isValidReporter(option[0])) {
973
+ continue;
974
+ }
975
+ throw new Error(
976
+ 'a reporter should be either a string in the format "wdio-<reportername>-reporter" or a function/class. Please see the docs for more information on custom reporters (https://webdriver.io/docs/customreporter)'
977
+ );
978
+ }
979
+ return true;
980
+ }
981
+ },
982
+ /**
983
+ * set of WDIO services to use
984
+ */
985
+ services: {
986
+ type: "object",
987
+ validate: (param) => {
988
+ if (!Array.isArray(param)) {
989
+ throw new Error('the "services" options needs to be a list of strings and/or arrays');
990
+ }
991
+ for (const option of param) {
992
+ if (!Array.isArray(option)) {
993
+ if (typeof option === "string") {
994
+ continue;
995
+ }
996
+ throw new Error('the "services" options needs to be a list of strings and/or arrays');
997
+ }
998
+ }
999
+ return true;
1000
+ },
1001
+ default: []
1002
+ },
1003
+ /**
1004
+ * Node arguments to specify when launching child processes
1005
+ */
1006
+ execArgv: {
1007
+ type: "object",
1008
+ validate: (param) => {
1009
+ if (!Array.isArray(param)) {
1010
+ throw new Error('the "execArgv" options needs to be a list of strings');
1011
+ }
1012
+ },
1013
+ default: []
1014
+ },
1015
+ /**
1016
+ * amount of instances to be allowed to run in total
1017
+ */
1018
+ maxInstances: {
1019
+ type: "number"
1020
+ },
1021
+ /**
1022
+ * amount of instances to be allowed to run per capability
1023
+ */
1024
+ maxInstancesPerCapability: {
1025
+ type: "number"
1026
+ },
1027
+ /**
1028
+ * whether or not testrunner should inject `browser`, `$` and `$$` as
1029
+ * global environment variables
1030
+ */
1031
+ injectGlobals: {
1032
+ type: "boolean"
1033
+ },
1034
+ /**
1035
+ * Set to true if you want to update your snapshots.
1036
+ */
1037
+ updateSnapshots: {
1038
+ type: "string",
1039
+ default: SUPPORTED_SNAPSHOTSTATE_OPTIONS[1],
1040
+ validate: (param) => {
1041
+ if (param && !SUPPORTED_SNAPSHOTSTATE_OPTIONS.includes(param)) {
1042
+ throw new Error(`the "updateSnapshots" options needs to be one of "${SUPPORTED_SNAPSHOTSTATE_OPTIONS.join('", "')}"`);
1043
+ }
1044
+ }
1045
+ },
1046
+ /**
1047
+ * Overrides default snapshot path. For example, to store snapshots next to test files.
1048
+ */
1049
+ resolveSnapshotPath: {
1050
+ type: "function",
1051
+ validate: (param) => {
1052
+ if (param && typeof param !== "function") {
1053
+ throw new Error('the "resolveSnapshotPath" options needs to be a function');
1054
+ }
1055
+ }
1056
+ },
1057
+ /**
1058
+ * The number of times to retry the entire specfile when it fails as a whole
1059
+ */
1060
+ specFileRetries: {
1061
+ type: "number",
1062
+ default: 0
1063
+ },
1064
+ /**
1065
+ * Delay in seconds between the spec file retry attempts
1066
+ */
1067
+ specFileRetriesDelay: {
1068
+ type: "number",
1069
+ default: 0
1070
+ },
1071
+ /**
1072
+ * Whether or not retried spec files should be retried immediately or deferred to the end of the queue
1073
+ */
1074
+ specFileRetriesDeferred: {
1075
+ type: "boolean",
1076
+ default: true
1077
+ },
1078
+ /**
1079
+ * whether or not print the log output grouped by test files
1080
+ */
1081
+ groupLogsByTestSpec: {
1082
+ type: "boolean",
1083
+ default: false
1084
+ },
1085
+ /**
1086
+ * list of strings to watch of `wdio` command is called with `--watch` flag
1087
+ */
1088
+ filesToWatch: {
1089
+ type: "object",
1090
+ validate: (param) => {
1091
+ if (!Array.isArray(param)) {
1092
+ throw new Error('the "filesToWatch" option needs to be a list of strings');
1093
+ }
1094
+ }
1095
+ },
1096
+ shard: {
1097
+ type: "object",
1098
+ validate: (param) => {
1099
+ if (typeof param !== "object") {
1100
+ throw new Error('the "shard" options needs to be an object');
1101
+ }
1102
+ const p = param;
1103
+ if (typeof p.current !== "number" || typeof p.total !== "number") {
1104
+ throw new Error('the "shard" option needs to have "current" and "total" properties with number values');
1105
+ }
1106
+ if (p.current < 0 || p.current > p.total) {
1107
+ throw new Error('the "shard.current" value has to be between 0 and "shard.total"');
1108
+ }
1109
+ }
1110
+ },
1111
+ /**
1112
+ * hooks
1113
+ */
1114
+ onPrepare: HOOK_DEFINITION,
1115
+ onWorkerStart: HOOK_DEFINITION,
1116
+ onWorkerEnd: HOOK_DEFINITION,
1117
+ before: HOOK_DEFINITION,
1118
+ beforeSession: HOOK_DEFINITION,
1119
+ beforeSuite: HOOK_DEFINITION,
1120
+ beforeHook: HOOK_DEFINITION,
1121
+ beforeTest: HOOK_DEFINITION,
1122
+ afterTest: HOOK_DEFINITION,
1123
+ afterHook: HOOK_DEFINITION,
1124
+ afterSuite: HOOK_DEFINITION,
1125
+ afterSession: HOOK_DEFINITION,
1126
+ after: HOOK_DEFINITION,
1127
+ onComplete: HOOK_DEFINITION,
1128
+ onReload: HOOK_DEFINITION,
1129
+ beforeAssertion: HOOK_DEFINITION,
1130
+ afterAssertion: HOOK_DEFINITION
1131
+ };
1132
+ var WORKER_GROUPLOGS_MESSAGES = {
1133
+ normalExit: (cid) => `
1134
+ ***** List of steps of WorkerID=[${cid}] *****`,
1135
+ exitWithError: (cid) => `
1136
+ ***** List of steps of WorkerID=[${cid}] that preceded the error above *****`
1137
+ };
1138
+
1139
+ // src/templates/EjsHelpers.ts
1140
+ var EjsHelpers = class {
1141
+ useTypeScript;
1142
+ useEsm;
1143
+ constructor(config) {
1144
+ this.useTypeScript = config.useTypeScript ?? false;
1145
+ this.useEsm = config.useEsm ?? false;
1146
+ }
1147
+ if(condition, trueValue, falseValue = "") {
1148
+ return condition ? trueValue : falseValue;
1149
+ }
1150
+ ifTs = (trueValue, falseValue = "") => this.if(this.useTypeScript, trueValue, falseValue);
1151
+ ifEsm = (trueValue, falseValue = "") => this.if(this.useEsm, trueValue, falseValue);
1152
+ param(name, type) {
1153
+ return this.useTypeScript ? `${name}: ${type}` : name;
1154
+ }
1155
+ returns(type) {
1156
+ return this.useTypeScript ? `: ${type}` : "";
1157
+ }
1158
+ import(exports, moduleId) {
1159
+ const individualExports = exports.split(",").map((id) => id.trim());
1160
+ const imports = this.useTypeScript ? individualExports : individualExports.filter((id) => !id.startsWith("type "));
1161
+ if (!imports.length) {
1162
+ return "";
1163
+ }
1164
+ const modulePath = this.modulePathFrom(moduleId);
1165
+ return this.useEsm || this.useTypeScript ? `import { ${imports.join(", ")} } from '${modulePath}'` : `const { ${imports.join(", ")} } = require('${modulePath}')`;
1166
+ }
1167
+ modulePathFrom(moduleId) {
1168
+ if (!(moduleId.startsWith(".") && this.useEsm)) {
1169
+ return moduleId;
1170
+ }
1171
+ if (moduleId.endsWith("/") && this.useEsm) {
1172
+ return moduleId + "index.js";
1173
+ }
1174
+ return moduleId + ".js";
1175
+ }
1176
+ export(keyword, name) {
1177
+ if (this.useTypeScript) {
1178
+ return `export ${keyword} ${name}`;
1179
+ }
1180
+ if (this.useEsm) {
1181
+ return `export ${keyword} ${name}`;
1182
+ }
1183
+ if (["class", "function"].includes(keyword)) {
1184
+ return `module.exports.${name} = ${keyword} ${name}`;
1185
+ }
1186
+ return `module.exports.${name}`;
1187
+ }
1188
+ };
1189
+
1190
+ // src/utils.ts
1191
+ var log = logger("@wdio/cli:utils");
1192
+ var __dirname = dirname(fileURLToPath(import.meta.url));
1193
+ var NPM_COMMAND = /^win/.test(process.platform) ? "npm.cmd" : "npm";
1194
+ var VERSION_REGEXP = /(\d+)\.(\d+)\.(\d+)-(alpha|beta|)\.(\d+)\+(.+)/g;
1195
+ var TEMPLATE_ROOT_DIR = path2.join(__dirname, "templates", "exampleFiles");
1196
+ var renderFile = promisify(ejs.renderFile);
1197
+ var HookError = class extends SevereServiceError {
1198
+ origin;
1199
+ constructor(message, origin) {
1200
+ super(message);
1201
+ this.origin = origin;
1202
+ }
1203
+ };
1204
+ async function runServiceHook(launcher, hookName, ...args) {
1205
+ const start = Date.now();
1206
+ return Promise.all(launcher.map(async (service) => {
1207
+ try {
1208
+ if (typeof service[hookName] === "function") {
1209
+ await service[hookName](...args);
1210
+ }
1211
+ } catch (err) {
1212
+ const message = `A service failed in the '${hookName}' hook
1213
+ ${err.stack}
1214
+
1215
+ `;
1216
+ if (err instanceof SevereServiceError || err.name === "SevereServiceError") {
1217
+ return { status: "rejected", reason: message, origin: hookName };
1218
+ }
1219
+ log.error(`${message}Continue...`);
1220
+ }
1221
+ })).then((results) => {
1222
+ if (launcher.length) {
1223
+ log.debug(`Finished to run "${hookName}" hook in ${Date.now() - start}ms`);
1224
+ }
1225
+ const rejectedHooks = results.filter((p) => p && p.status === "rejected");
1226
+ if (rejectedHooks.length) {
1227
+ return Promise.reject(new HookError(`
1228
+ ${rejectedHooks.map((p) => p && p.reason).join()}
1229
+
1230
+ Stopping runner...`, hookName));
1231
+ }
1232
+ });
1233
+ }
1234
+ async function runLauncherHook(hook, ...args) {
1235
+ if (typeof hook === "function") {
1236
+ hook = [hook];
1237
+ }
1238
+ const catchFn = (e) => {
1239
+ log.error(`Error in hook: ${e.stack}`);
1240
+ if (e instanceof SevereServiceError) {
1241
+ throw new HookError(e.message, hook[0].name);
1242
+ }
1243
+ };
1244
+ return Promise.all(hook.map((hook2) => {
1245
+ try {
1246
+ return hook2(...args);
1247
+ } catch (err) {
1248
+ return catchFn(err);
1249
+ }
1250
+ })).catch(catchFn);
1251
+ }
1252
+ async function runOnCompleteHook(onCompleteHook, config, capabilities, exitCode, results) {
1253
+ if (typeof onCompleteHook === "function") {
1254
+ onCompleteHook = [onCompleteHook];
1255
+ }
1256
+ return Promise.all(onCompleteHook.map(async (hook) => {
1257
+ try {
1258
+ await hook(exitCode, config, capabilities, results);
1259
+ return 0;
1260
+ } catch (err) {
1261
+ log.error(`Error in onCompleteHook: ${err.stack}`);
1262
+ if (err instanceof SevereServiceError) {
1263
+ throw new HookError(err.message, "onComplete");
1264
+ }
1265
+ return 1;
1266
+ }
1267
+ }));
1268
+ }
1269
+ function getRunnerName(caps = {}) {
1270
+ let runner = caps.browserName || caps.platformName || caps["appium:platformName"] || caps["appium:appPackage"] || caps["appium:appWaitActivity"] || caps["appium:app"];
1271
+ if (!runner) {
1272
+ runner = Object.values(caps).length === 0 || Object.values(caps).some((cap) => !cap.capabilities) ? "undefined" : "MultiRemote";
1273
+ }
1274
+ return runner;
1275
+ }
1276
+ function buildNewConfigArray(str, type, change) {
1277
+ const newStr = str.split(`${type}s: `)[1].replace(/'/g, "");
1278
+ const newArray = newStr.match(/(\w*)/gmi)?.filter((e) => !!e).concat([change]) || [];
1279
+ return str.replace("// ", "").replace(
1280
+ new RegExp(`(${type}s: )((.*\\s*)*)`),
1281
+ `$1[${newArray.map((e) => `'${e}'`)}]`
1282
+ );
1283
+ }
1284
+ function buildNewConfigString(str, type, change) {
1285
+ return str.replace(new RegExp(`(${type}: )('\\w*')`), `$1'${change}'`);
1286
+ }
1287
+ function findInConfig(config, type) {
1288
+ let regexStr = `[\\/\\/]*[\\s]*${type}s: [\\s]*\\[([\\s]*['|"]\\w*['|"],*)*[\\s]*\\]`;
1289
+ if (type === "framework") {
1290
+ regexStr = `[\\/\\/]*[\\s]*${type}: ([\\s]*['|"]\\w*['|"])`;
1291
+ }
1292
+ const regex = new RegExp(regexStr, "gmi");
1293
+ return config.match(regex);
1294
+ }
1295
+ function replaceConfig(config, type, name) {
1296
+ if (type === "framework") {
1297
+ return buildNewConfigString(config, type, name);
1298
+ }
1299
+ const match = findInConfig(config, type);
1300
+ if (!match || match.length === 0) {
1301
+ return;
1302
+ }
1303
+ const text = match.pop() || "";
1304
+ return config.replace(text, buildNewConfigArray(text, type, name));
1305
+ }
1306
+ function addServiceDeps(names, packages, update = false) {
1307
+ if (names.some(({ short }) => short === "appium")) {
1308
+ const result = execSync("appium --version || echo APPIUM_MISSING", { stdio: "pipe" }).toString().trim();
1309
+ if (result === "APPIUM_MISSING") {
1310
+ packages.push("appium");
1311
+ } else if (update) {
1312
+ console.log(
1313
+ "\n=======",
1314
+ "\nUsing globally installed appium",
1315
+ result,
1316
+ "\nPlease add the following to your wdio.conf.js:",
1317
+ "\nappium: { command: 'appium' }",
1318
+ "\n=======\n"
1319
+ );
1320
+ }
1321
+ }
1322
+ }
1323
+ function convertPackageHashToObject(pkg2, hash = "$--$") {
1324
+ const [p, short, purpose] = pkg2.split(hash);
1325
+ return { package: p, short, purpose };
1326
+ }
1327
+ function getSerenityPackages(answers) {
1328
+ const framework = convertPackageHashToObject(answers.framework);
1329
+ if (framework.package !== "@serenity-js/webdriverio") {
1330
+ return [];
1331
+ }
1332
+ const packages = {
1333
+ cucumber: [
1334
+ "@cucumber/cucumber",
1335
+ "@serenity-js/cucumber"
1336
+ ],
1337
+ mocha: [
1338
+ "@serenity-js/mocha",
1339
+ "mocha"
1340
+ ],
1341
+ jasmine: [
1342
+ "@serenity-js/jasmine",
1343
+ "jasmine"
1344
+ ],
1345
+ common: [
1346
+ "@serenity-js/assertions",
1347
+ "@serenity-js/console-reporter",
1348
+ "@serenity-js/core",
1349
+ "@serenity-js/rest",
1350
+ "@serenity-js/serenity-bdd",
1351
+ "@serenity-js/web",
1352
+ "npm-failsafe",
1353
+ "rimraf"
1354
+ ]
1355
+ };
1356
+ if (answers.isUsingTypeScript) {
1357
+ packages.mocha.push("@types/mocha");
1358
+ packages.jasmine.push("@types/jasmine");
1359
+ packages.common.push("@types/node");
1360
+ }
1361
+ return [
1362
+ ...packages[framework.purpose],
1363
+ ...packages.common
1364
+ ].filter(Boolean).sort();
1365
+ }
1366
+ async function getCapabilities(arg) {
1367
+ const optionalCapabilites = {
1368
+ platformVersion: arg.platformVersion,
1369
+ udid: arg.udid,
1370
+ ...arg.deviceName && { deviceName: arg.deviceName }
1371
+ };
1372
+ if (/.*\.(apk|app|ipa)$/.test(arg.option)) {
1373
+ return {
1374
+ capabilities: {
1375
+ app: arg.option,
1376
+ ...arg.option.endsWith("apk") ? ANDROID_CONFIG : IOS_CONFIG,
1377
+ ...optionalCapabilites
1378
+ }
1379
+ };
1380
+ } else if (/android/.test(arg.option)) {
1381
+ return { capabilities: { browserName: "Chrome", ...ANDROID_CONFIG, ...optionalCapabilites } };
1382
+ } else if (/ios/.test(arg.option)) {
1383
+ return { capabilities: { browserName: "Safari", ...IOS_CONFIG, ...optionalCapabilites } };
1384
+ } else if (/(js|ts)$/.test(arg.option)) {
1385
+ const config = new ConfigParser(arg.option);
1386
+ try {
1387
+ await config.initialize();
1388
+ } catch (e) {
1389
+ throw Error(e.code === "MODULE_NOT_FOUND" ? `Config File not found: ${arg.option}` : `Could not parse ${arg.option}, failed with error: ${e.message}`);
1390
+ }
1391
+ if (typeof arg.capabilities === "undefined") {
1392
+ throw Error("Please provide index/named property of capability to use from the capabilities array/object in wdio config file");
1393
+ }
1394
+ let requiredCaps = config.getCapabilities();
1395
+ requiredCaps = // multi capabilities
1396
+ requiredCaps[parseInt(arg.capabilities, 10)] || // multiremote
1397
+ requiredCaps[arg.capabilities];
1398
+ const requiredW3CCaps = pickBy(requiredCaps, (_, key) => CAPABILITY_KEYS.includes(key) || key.includes(":"));
1399
+ if (!Object.keys(requiredW3CCaps).length) {
1400
+ throw Error(`No capability found in given config file with the provided capability indexed/named property: ${arg.capabilities}. Please check the capability in your wdio config file.`);
1401
+ }
1402
+ return { capabilities: { ...requiredW3CCaps } };
1403
+ }
1404
+ return { capabilities: { browserName: arg.option } };
1405
+ }
1406
+ function hasBabelConfig(rootDir) {
1407
+ return Promise.all([
1408
+ fs2.access(path2.join(rootDir, "babel.js")),
1409
+ fs2.access(path2.join(rootDir, "babel.cjs")),
1410
+ fs2.access(path2.join(rootDir, "babel.mjs")),
1411
+ fs2.access(path2.join(rootDir, ".babelrc"))
1412
+ ]).then(
1413
+ (results) => results.filter(Boolean).length > 1,
1414
+ () => false
1415
+ );
1416
+ }
1417
+ async function detectCompiler(answers) {
1418
+ const root = await getProjectRoot(answers);
1419
+ const rootTSConfigExist = await fs2.access(path2.resolve(root, "tsconfig.json")).then(() => true, () => false);
1420
+ return await hasBabelConfig(root) || rootTSConfigExist ? true : false;
1421
+ }
1422
+ async function generateTestFiles(answers) {
1423
+ if (answers.serenityAdapter) {
1424
+ return generateSerenityExamples(answers);
1425
+ }
1426
+ if (answers.runner === "local") {
1427
+ return generateLocalRunnerTestFiles(answers);
1428
+ }
1429
+ return generateBrowserRunnerTestFiles(answers);
1430
+ }
1431
+ var TSX_BASED_FRAMEWORKS = ["react", "preact", "solid", "stencil"];
1432
+ async function generateBrowserRunnerTestFiles(answers) {
1433
+ const isUsingFramework = typeof answers.preset === "string";
1434
+ const preset = getPreset(answers);
1435
+ const tplRootDir = path2.join(TEMPLATE_ROOT_DIR, "browser");
1436
+ await fs2.mkdir(answers.destSpecRootPath, { recursive: true });
1437
+ if (isUsingFramework) {
1438
+ const renderedCss = await renderFile(path2.join(tplRootDir, "Component.css.ejs"), { answers });
1439
+ await fs2.writeFile(path2.join(answers.destSpecRootPath, "Component.css"), renderedCss);
1440
+ }
1441
+ const testExt = `${answers.isUsingTypeScript ? "ts" : "js"}${TSX_BASED_FRAMEWORKS.includes(preset) ? "x" : ""}`;
1442
+ const fileExt = ["svelte", "vue"].includes(preset) ? preset : testExt;
1443
+ if (preset) {
1444
+ const componentOutFileName = `Component.${fileExt}`;
1445
+ const renderedComponent = await renderFile(path2.join(tplRootDir, `Component.${preset}.ejs`), { answers });
1446
+ await fs2.writeFile(path2.join(answers.destSpecRootPath, componentOutFileName), renderedComponent);
1447
+ }
1448
+ const componentFileName = preset ? `Component.${preset}.test.ejs` : "standalone.test.ejs";
1449
+ const renderedTest = await renderFile(path2.join(tplRootDir, componentFileName), { answers });
1450
+ await fs2.writeFile(path2.join(answers.destSpecRootPath, `Component.test.${testExt}`), renderedTest);
1451
+ }
1452
+ async function generateLocalRunnerTestFiles(answers) {
1453
+ const testFiles = answers.framework === "cucumber" ? [path2.join(TEMPLATE_ROOT_DIR, "cucumber")] : [path2.join(TEMPLATE_ROOT_DIR, "mochaJasmine")];
1454
+ if (answers.usePageObjects) {
1455
+ testFiles.push(path2.join(TEMPLATE_ROOT_DIR, "pageobjects"));
1456
+ }
1457
+ const files = (await Promise.all(testFiles.map((dirPath) => readDir(
1458
+ dirPath,
1459
+ [(file, stats) => !stats.isDirectory() && !(file.endsWith(".ejs") || file.endsWith(".feature"))]
1460
+ )))).reduce((cur, acc) => [...acc, ...cur], []);
1461
+ for (const file of files) {
1462
+ const renderedTpl = await renderFile(file, { answers });
1463
+ const isJSX = answers.preset && TSX_BASED_FRAMEWORKS.includes(answers.preset);
1464
+ const fileEnding = (answers.isUsingTypeScript ? ".ts" : ".js") + (isJSX ? "x" : "");
1465
+ const destPath = (file.endsWith("page.js.ejs") ? path2.join(answers.destPageObjectRootPath, path2.basename(file)) : file.includes("step_definition") ? path2.join(answers.destStepRootPath, path2.basename(file)) : path2.join(answers.destSpecRootPath, path2.basename(file))).replace(/\.ejs$/, "").replace(/\.js$/, fileEnding);
1466
+ await fs2.mkdir(path2.dirname(destPath), { recursive: true });
1467
+ await fs2.writeFile(destPath, renderedTpl);
1468
+ }
1469
+ }
1470
+ async function generateSerenityExamples(answers) {
1471
+ const templateDirectories = {
1472
+ [answers.projectRootDir]: path2.join(TEMPLATE_ROOT_DIR, "serenity-js", "common", "config"),
1473
+ [answers.destSpecRootPath]: path2.join(TEMPLATE_ROOT_DIR, "serenity-js", answers.serenityAdapter),
1474
+ [answers.destSerenityLibRootPath]: path2.join(TEMPLATE_ROOT_DIR, "serenity-js", "common", "serenity")
1475
+ };
1476
+ for (const [destinationRootDir, templateRootDir] of Object.entries(templateDirectories)) {
1477
+ const pathsToTemplates = await readDir(templateRootDir);
1478
+ for (const pathToTemplate of pathsToTemplates) {
1479
+ const extension = answers.isUsingTypeScript ? ".ts" : ".js";
1480
+ const destination = path2.join(destinationRootDir, path2.relative(templateRootDir, pathToTemplate)).replace(/\.ejs$/, "").replace(/\.ts$/, extension);
1481
+ const contents = await renderFile(
1482
+ pathToTemplate,
1483
+ { answers, _: new EjsHelpers({ useEsm: answers.esmSupport, useTypeScript: answers.isUsingTypeScript }) }
1484
+ );
1485
+ await fs2.mkdir(path2.dirname(destination), { recursive: true });
1486
+ await fs2.writeFile(destination, contents);
1487
+ }
1488
+ }
1489
+ }
1490
+ async function getAnswers(yes) {
1491
+ if (yes) {
1492
+ const ignoredQuestions = ["e2eEnvironment"];
1493
+ const filterdQuestionaire = QUESTIONNAIRE.filter((question) => !ignoredQuestions.includes(question.name));
1494
+ const answers = {};
1495
+ for (const question of filterdQuestionaire) {
1496
+ if (question.when && !question.when(answers)) {
1497
+ continue;
1498
+ }
1499
+ Object.assign(answers, {
1500
+ [question.name]: typeof question.default !== "undefined" ? typeof question.default === "function" ? await question.default(answers) : await question.default : question.choices && question.choices.length ? typeof question.choices === "function" ? question.choices(answers)[0].value ? question.choices(answers)[0].value : question.choices(answers)[0] : question.choices[0].value ? question.choices[0].value : question.choices[0] : {}
1501
+ });
1502
+ }
1503
+ answers.isUsingTypeScript = await answers.isUsingTypeScript;
1504
+ answers.specs = await answers.specs;
1505
+ answers.pages = await answers.pages;
1506
+ return answers;
1507
+ }
1508
+ const projectProps = await getProjectProps(process.cwd());
1509
+ const isProjectExisting = Boolean(projectProps);
1510
+ const projectName = projectProps?.packageJson?.name ? ` named "${projectProps.packageJson.name}"` : "";
1511
+ const questions = [
1512
+ /**
1513
+ * in case the `wdio config` was called using a global installed @wdio/cli package
1514
+ */
1515
+ ...!isProjectExisting ? [{
1516
+ type: "confirm",
1517
+ name: "createPackageJSON",
1518
+ default: true,
1519
+ message: `Couldn't find a package.json in "${process.cwd()}" or any of the parent directories, do you want to create one?`
1520
+ }] : projectProps?.packageJson?.name !== "my-new-project" ? [{
1521
+ type: "confirm",
1522
+ name: "projectRootCorrect",
1523
+ default: true,
1524
+ message: `A project${projectName} was detected at "${projectProps?.path}", correct?`
1525
+ }, {
1526
+ type: "input",
1527
+ name: "projectRoot",
1528
+ message: "What is the project root for your test project?",
1529
+ default: projectProps?.path,
1530
+ // only ask if there are more than 1 runner to pick from
1531
+ when: (
1532
+ /* istanbul ignore next */
1533
+ (answers) => !answers.projectRootCorrect
1534
+ )
1535
+ }] : [],
1536
+ ...QUESTIONNAIRE
1537
+ ];
1538
+ return inquirer.prompt(questions);
1539
+ }
1540
+ function generatePathfromAnswer(answers, projectRootDir) {
1541
+ return path2.resolve(
1542
+ projectRootDir,
1543
+ path2.dirname(answers) === "." ? path2.resolve(answers) : path2.dirname(answers)
1544
+ );
1545
+ }
1546
+ function getPathForFileGeneration(answers, projectRootDir) {
1547
+ const specAnswer = answers.specs || "";
1548
+ const stepDefinitionAnswer = answers.stepDefinitions || "";
1549
+ const pageObjectAnswer = answers.pages || "";
1550
+ const destSpecRootPath = generatePathfromAnswer(specAnswer, projectRootDir).replace(/\*\*$/, "");
1551
+ const destStepRootPath = generatePathfromAnswer(stepDefinitionAnswer, projectRootDir);
1552
+ const destPageObjectRootPath = answers.usePageObjects ? generatePathfromAnswer(pageObjectAnswer, projectRootDir).replace(/\*\*$/, "") : "";
1553
+ const destSerenityLibRootPath = usesSerenity(answers) ? path2.resolve(projectRootDir, answers.serenityLibPath || "serenity") : "";
1554
+ const relativePath = answers.generateTestFiles && answers.usePageObjects ? !(convertPackageHashToObject(answers.framework).short === "cucumber") ? path2.relative(destSpecRootPath, destPageObjectRootPath) : path2.relative(destStepRootPath, destPageObjectRootPath) : "";
1555
+ return {
1556
+ destSpecRootPath,
1557
+ destStepRootPath,
1558
+ destPageObjectRootPath,
1559
+ destSerenityLibRootPath,
1560
+ relativePath: relativePath.replaceAll(path2.sep, "/")
1561
+ };
1562
+ }
1563
+ async function getDefaultFiles(answers, pattern) {
1564
+ const rootdir = await getProjectRoot(answers);
1565
+ const presetPackage = convertPackageHashToObject(answers.preset || "");
1566
+ const isJSX = TSX_BASED_FRAMEWORKS.includes(presetPackage.short || "");
1567
+ const val = pattern.endsWith(".feature") ? path2.join(rootdir, pattern) : answers?.isUsingTypeScript ? `${path2.join(rootdir, pattern)}.ts${isJSX ? "x" : ""}` : `${path2.join(rootdir, pattern)}.js${isJSX ? "x" : ""}`;
1568
+ return val;
1569
+ }
1570
+ function specifyVersionIfNeeded(packagesToInstall, version, npmTag) {
1571
+ const { value } = version.matchAll(VERSION_REGEXP).next();
1572
+ const [major, minor, patch, tagName, build] = (value || []).slice(1, -1);
1573
+ return packagesToInstall.map((p) => {
1574
+ if (p.startsWith("@wdio") && p !== "@wdio/visual-service" || ["webdriver", "webdriverio"].includes(p)) {
1575
+ const tag = major && npmTag === "latest" ? `^${major}.${minor}.${patch}-${tagName}.${build}` : npmTag;
1576
+ return `${p}@${tag}`;
1577
+ }
1578
+ return p;
1579
+ });
1580
+ }
1581
+ async function getProjectProps(cwd = process.cwd()) {
1582
+ try {
1583
+ const { packageJson, path: packageJsonPath } = await readPackageUp({ cwd }) || {};
1584
+ if (!packageJson || !packageJsonPath) {
1585
+ return void 0;
1586
+ }
1587
+ return {
1588
+ esmSupported: packageJson.type === "module" || typeof packageJson.module === "string",
1589
+ packageJson,
1590
+ path: path2.dirname(packageJsonPath)
1591
+ };
1592
+ } catch (err) {
1593
+ return void 0;
1594
+ }
1595
+ }
1596
+ function runProgram(command5, args, options) {
1597
+ const child = spawn(command5, args, { stdio: "inherit", ...options });
1598
+ return new Promise((resolve2, reject) => {
1599
+ let error;
1600
+ child.on("error", (e) => error = e);
1601
+ child.on("close", (code) => {
1602
+ if (code !== 0) {
1603
+ return reject(new Error(
1604
+ error && error.message || `Error calling: ${command5} ${args.join(" ")}`
1605
+ ));
1606
+ }
1607
+ resolve2();
1608
+ });
1609
+ });
1610
+ }
1611
+ async function createPackageJSON(parsedAnswers) {
1612
+ const packageJsonExists = await fs2.access(path2.resolve(process.cwd(), "package.json")).then(() => true, () => false);
1613
+ if (packageJsonExists) {
1614
+ return;
1615
+ }
1616
+ if (parsedAnswers.createPackageJSON === false) {
1617
+ if (!packageJsonExists) {
1618
+ console.log(`No WebdriverIO configuration found in "${parsedAnswers.wdioConfigPath}"`);
1619
+ return !process.env.VITEST_WORKER_ID && process.exit(0);
1620
+ }
1621
+ return;
1622
+ }
1623
+ if (parsedAnswers.createPackageJSON) {
1624
+ console.log(`Creating a ${chalk.bold("package.json")} for the directory...`);
1625
+ await fs2.writeFile(path2.resolve(process.cwd(), "package.json"), JSON.stringify({
1626
+ name: "webdriverio-tests",
1627
+ version: "0.0.0",
1628
+ private: true,
1629
+ license: "ISC",
1630
+ type: "module",
1631
+ dependencies: {},
1632
+ devDependencies: {}
1633
+ }, null, 2));
1634
+ console.log(chalk.green(chalk.bold("\u2714 Success!\n")));
1635
+ }
1636
+ }
1637
+ var SEP = "\n- ";
1638
+ async function npmInstall(parsedAnswers, npmTag) {
1639
+ const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service));
1640
+ const presetPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.preset || "");
1641
+ if (parsedAnswers.installTestingLibrary && TESTING_LIBRARY_PACKAGES[presetPackage.short]) {
1642
+ parsedAnswers.packagesToInstall.push(
1643
+ TESTING_LIBRARY_PACKAGES[presetPackage.short],
1644
+ "@testing-library/jest-dom"
1645
+ );
1646
+ }
1647
+ if (presetPackage.short === "solid") {
1648
+ parsedAnswers.packagesToInstall.push("solid-js");
1649
+ }
1650
+ if (parsedAnswers.includeVisualTesting) {
1651
+ parsedAnswers.packagesToInstall.push("@wdio/visual-service");
1652
+ }
1653
+ const preset = getPreset(parsedAnswers);
1654
+ if (preset === "lit") {
1655
+ parsedAnswers.packagesToInstall.push("lit");
1656
+ }
1657
+ if (preset === "stencil") {
1658
+ parsedAnswers.packagesToInstall.push("@stencil/core");
1659
+ }
1660
+ if (presetPackage.short === "react") {
1661
+ parsedAnswers.packagesToInstall.push("react");
1662
+ if (!parsedAnswers.installTestingLibrary) {
1663
+ parsedAnswers.packagesToInstall.push("react-dom");
1664
+ }
1665
+ }
1666
+ if (parsedAnswers.framework === "jasmine" && parsedAnswers.isUsingTypeScript) {
1667
+ parsedAnswers.packagesToInstall.push("@types/jasmine");
1668
+ }
1669
+ if (parsedAnswers.purpose === "macos") {
1670
+ parsedAnswers.packagesToInstall.push("appium-mac2-driver");
1671
+ }
1672
+ if (parsedAnswers.mobileEnvironment === "android") {
1673
+ parsedAnswers.packagesToInstall.push("appium-uiautomator2-driver");
1674
+ }
1675
+ if (parsedAnswers.mobileEnvironment === "ios") {
1676
+ parsedAnswers.packagesToInstall.push("appium-xcuitest-driver");
1677
+ }
1678
+ addServiceDeps(servicePackages, parsedAnswers.packagesToInstall);
1679
+ parsedAnswers.packagesToInstall = specifyVersionIfNeeded(parsedAnswers.packagesToInstall, pkg.version, npmTag);
1680
+ const cwd = await getProjectRoot(parsedAnswers);
1681
+ const pm = detectPackageManager();
1682
+ if (parsedAnswers.npmInstall) {
1683
+ console.log(`Installing packages using ${pm}:${SEP}${parsedAnswers.packagesToInstall.join(SEP)}`);
1684
+ const success = await installPackages(cwd, parsedAnswers.packagesToInstall, true);
1685
+ if (success) {
1686
+ console.log(chalk.green(chalk.bold("\u2714 Success!\n")));
1687
+ }
1688
+ } else {
1689
+ const installationCommand = getInstallCommand(pm, parsedAnswers.packagesToInstall, true);
1690
+ console.log(util.format(DEPENDENCIES_INSTALLATION_MESSAGE, installationCommand));
1691
+ }
1692
+ }
1693
+ function detectPackageManager(argv = process.argv) {
1694
+ return PMs.find((pm) => (
1695
+ // for pnpm check "~/Library/pnpm/store/v3/..."
1696
+ // for NPM check "~/.npm/npx/..."
1697
+ // for Yarn check "~/.yarn/bin/create-wdio"
1698
+ // for Bun check "~/.bun/bin/create-wdio"
1699
+ argv[1].includes(`${path2.sep}${pm}${path2.sep}`) || argv[1].includes(`${path2.sep}.${pm}${path2.sep}`)
1700
+ )) || "npm";
1701
+ }
1702
+ async function setupTypeScript(parsedAnswers) {
1703
+ if (!parsedAnswers.isUsingTypeScript) {
1704
+ return;
1705
+ }
1706
+ if (parsedAnswers.hasRootTSConfig) {
1707
+ return;
1708
+ }
1709
+ console.log("Setting up TypeScript...");
1710
+ const frameworkPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.framework);
1711
+ const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service));
1712
+ const serenityTypes = parsedAnswers.serenityAdapter === "jasmine" ? ["jasmine"] : [];
1713
+ const types = [
1714
+ "node",
1715
+ "@wdio/globals/types",
1716
+ "expect-webdriverio",
1717
+ ...parsedAnswers.serenityAdapter ? serenityTypes : [frameworkPackage.package],
1718
+ ...parsedAnswers.runner === "browser" ? ["@wdio/browser-runner"] : [],
1719
+ ...servicePackages.map((service) => service.package).filter((service) => (
1720
+ /**
1721
+ * given that we know that all "official" services have
1722
+ * typescript support we only include them
1723
+ */
1724
+ service.startsWith("@wdio") || /**
1725
+ * also include community maintained packages with known
1726
+ * support for TypeScript
1727
+ */
1728
+ COMMUNITY_PACKAGES_WITH_TS_SUPPORT.includes(service)
1729
+ ))
1730
+ ];
1731
+ const preset = getPreset(parsedAnswers);
1732
+ const config = {
1733
+ compilerOptions: {
1734
+ // compiler
1735
+ moduleResolution: "node",
1736
+ module: !parsedAnswers.esmSupport ? "commonjs" : "ESNext",
1737
+ target: "es2022",
1738
+ lib: ["es2022", "dom"],
1739
+ types,
1740
+ skipLibCheck: true,
1741
+ // bundler
1742
+ noEmit: true,
1743
+ allowImportingTsExtensions: true,
1744
+ resolveJsonModule: true,
1745
+ isolatedModules: true,
1746
+ // linting
1747
+ strict: true,
1748
+ noUnusedLocals: true,
1749
+ noUnusedParameters: true,
1750
+ noFallthroughCasesInSwitch: true,
1751
+ ...Object.assign(
1752
+ preset === "lit" ? {
1753
+ experimentalDecorators: true,
1754
+ useDefineForClassFields: false
1755
+ } : {},
1756
+ preset === "react" ? {
1757
+ jsx: "react-jsx"
1758
+ } : {},
1759
+ preset === "preact" ? {
1760
+ jsx: "react-jsx",
1761
+ jsxImportSource: "preact"
1762
+ } : {},
1763
+ preset === "solid" ? {
1764
+ jsx: "preserve",
1765
+ jsxImportSource: "solid-js"
1766
+ } : {},
1767
+ preset === "stencil" ? {
1768
+ experimentalDecorators: true,
1769
+ jsx: "react",
1770
+ jsxFactory: "h",
1771
+ jsxFragmentFactory: "Fragment"
1772
+ } : {}
1773
+ )
1774
+ },
1775
+ include: preset === "svelte" ? ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] : preset === "vue" ? ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] : ["test", "wdio.conf.ts"]
1776
+ };
1777
+ await fs2.mkdir(path2.dirname(parsedAnswers.tsConfigFilePath), { recursive: true });
1778
+ await fs2.writeFile(
1779
+ parsedAnswers.tsConfigFilePath,
1780
+ JSON.stringify(config, null, 4)
1781
+ );
1782
+ console.log(chalk.green(chalk.bold("\u2714 Success!\n")));
1783
+ }
1784
+ function getPreset(parsedAnswers) {
1785
+ const isUsingFramework = typeof parsedAnswers.preset === "string";
1786
+ return isUsingFramework ? parsedAnswers.preset || "lit" : "";
1787
+ }
1788
+ async function createWDIOConfig(parsedAnswers) {
1789
+ try {
1790
+ console.log("Creating a WebdriverIO config file...");
1791
+ const tplPath = path2.resolve(__dirname, "templates", "wdio.conf.tpl.ejs");
1792
+ const renderedTpl = await renderFile(tplPath, {
1793
+ answers: parsedAnswers,
1794
+ _: new EjsHelpers({ useEsm: parsedAnswers.esmSupport, useTypeScript: parsedAnswers.isUsingTypeScript })
1795
+ });
1796
+ await fs2.writeFile(parsedAnswers.wdioConfigPath, renderedTpl);
1797
+ console.log(chalk.green(chalk.bold("\u2714 Success!\n")));
1798
+ if (parsedAnswers.generateTestFiles) {
1799
+ console.log("Autogenerating test files...");
1800
+ await generateTestFiles(parsedAnswers);
1801
+ console.log(chalk.green(chalk.bold("\u2714 Success!\n")));
1802
+ }
1803
+ } catch (err) {
1804
+ throw new Error(`\u26A0\uFE0F Couldn't write config file: ${err.stack}`);
1805
+ }
1806
+ }
1807
+ async function getProjectRoot(parsedAnswers) {
1808
+ const root = (await getProjectProps())?.path;
1809
+ if (!root) {
1810
+ throw new Error("Could not find project root directory with a package.json");
1811
+ }
1812
+ return !parsedAnswers || parsedAnswers.projectRootCorrect ? root : parsedAnswers.projectRoot || process.cwd();
1813
+ }
1814
+ async function createWDIOScript(parsedAnswers) {
1815
+ const rootDir = await getProjectRoot(parsedAnswers);
1816
+ const pathToWdioConfig = `./${path2.join(".", parsedAnswers.wdioConfigPath.replace(rootDir, ""))}`;
1817
+ const wdioScripts = {
1818
+ "wdio": `wdio run ${pathToWdioConfig}`
1819
+ };
1820
+ const serenityScripts = {
1821
+ "serenity": "failsafe serenity:update serenity:clean wdio serenity:report",
1822
+ "serenity:update": "serenity-bdd update",
1823
+ "serenity:clean": "rimraf target",
1824
+ "wdio": `wdio run ${pathToWdioConfig}`,
1825
+ "serenity:report": "serenity-bdd run"
1826
+ };
1827
+ const scripts = parsedAnswers.serenityAdapter ? serenityScripts : wdioScripts;
1828
+ for (const [script, command5] of Object.entries(scripts)) {
1829
+ const args = ["pkg", "set", `scripts.${script}=${command5}`];
1830
+ try {
1831
+ console.log(`Adding ${chalk.bold(`"${script}"`)} script to package.json`);
1832
+ await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir });
1833
+ } catch (err) {
1834
+ const [preArgs, scriptPath] = args.join(" ").split("=");
1835
+ console.error(
1836
+ `\u26A0\uFE0F Couldn't add script to package.json: "${err.message}", you can add it manually by running:
1837
+
1838
+ ${NPM_COMMAND} ${preArgs}="${scriptPath}"`
1839
+ );
1840
+ return false;
1841
+ }
1842
+ }
1843
+ console.log(chalk.green(chalk.bold("\u2714 Success!")));
1844
+ return true;
1845
+ }
1846
+ async function runAppiumInstaller(parsedAnswers) {
1847
+ if (parsedAnswers.e2eEnvironment !== "mobile") {
1848
+ return;
1849
+ }
1850
+ const answer = await inquirer.prompt({
1851
+ name: "continueWithAppiumSetup",
1852
+ message: "Continue with Appium setup using appium-installer (https://github.com/AppiumTestDistribution/appium-installer)?",
1853
+ type: "confirm",
1854
+ default: true
1855
+ });
1856
+ if (!answer.continueWithAppiumSetup) {
1857
+ return console.log(
1858
+ "Ok! You can learn more about setting up mobile environments in the Appium docs at https://appium.io/docs/en/2.0/quickstart/"
1859
+ );
1860
+ }
1861
+ return $({ stdio: "inherit" })`npx appium-installer`;
1862
+ }
1863
+
1864
+ // src/interface.ts
1865
+ var log2 = logger2("@wdio/cli");
1866
+ var EVENT_FILTER = ["sessionStarted", "sessionEnded", "finishedCommand", "ready", "workerResponse", "workerEvent"];
1867
+ var WDIOCLInterface = class extends EventEmitter {
1868
+ constructor(_config, totalWorkerCnt, _isWatchMode = false) {
1869
+ super();
1870
+ this._config = _config;
1871
+ this.totalWorkerCnt = totalWorkerCnt;
1872
+ this._isWatchMode = _isWatchMode;
1873
+ this.hasAnsiSupport = supportsColor && supportsColor.hasBasic;
1874
+ this.totalWorkerCnt = totalWorkerCnt;
1875
+ this._isWatchMode = _isWatchMode;
1876
+ this._specFileRetries = _config.specFileRetries || 0;
1877
+ this._specFileRetriesDelay = _config.specFileRetriesDelay || 0;
1878
+ this.on("job:start", this.addJob.bind(this));
1879
+ this.on("job:end", this.clearJob.bind(this));
1880
+ this.setup();
1881
+ this.onStart();
1882
+ }
1883
+ #snapshotManager = new SnapshotManager({
1884
+ updateSnapshot: "new"
1885
+ // ignored in this context
1886
+ });
1887
+ hasAnsiSupport;
1888
+ result = {
1889
+ finished: 0,
1890
+ passed: 0,
1891
+ retries: 0,
1892
+ failed: 0
1893
+ };
1894
+ _jobs = /* @__PURE__ */ new Map();
1895
+ _specFileRetries;
1896
+ _specFileRetriesDelay;
1897
+ _skippedSpecs = 0;
1898
+ _inDebugMode = false;
1899
+ _start = /* @__PURE__ */ new Date();
1900
+ _messages = {
1901
+ reporter: {},
1902
+ debugger: {}
1903
+ };
1904
+ #hasShard() {
1905
+ return this._config.shard && this._config.shard.total !== 1;
1906
+ }
1907
+ setup() {
1908
+ this._jobs = /* @__PURE__ */ new Map();
1909
+ this._start = /* @__PURE__ */ new Date();
1910
+ this.result = {
1911
+ finished: 0,
1912
+ passed: 0,
1913
+ retries: 0,
1914
+ failed: 0
1915
+ };
1916
+ this._messages = {
1917
+ reporter: {},
1918
+ debugger: {}
1919
+ };
1920
+ }
1921
+ onStart() {
1922
+ const shardNote = this.#hasShard() ? ` (Shard ${this._config.shard.current} of ${this._config.shard.total})` : "";
1923
+ this.log(chalk2.bold(`
1924
+ Execution of ${chalk2.blue(this.totalWorkerCnt)} workers${shardNote} started at`), this._start.toISOString());
1925
+ if (this._inDebugMode) {
1926
+ this.log(chalk2.bgYellow(chalk2.black("DEBUG mode enabled!")));
1927
+ }
1928
+ if (this._isWatchMode) {
1929
+ this.log(chalk2.bgYellow(chalk2.black("WATCH mode enabled!")));
1930
+ }
1931
+ this.log("");
1932
+ }
1933
+ onSpecRunning(rid) {
1934
+ this.onJobComplete(rid, this._jobs.get(rid), 0, chalk2.bold(chalk2.cyan("RUNNING")));
1935
+ }
1936
+ onSpecRetry(rid, job, retries = 0) {
1937
+ const delayMsg = this._specFileRetriesDelay > 0 ? ` after ${this._specFileRetriesDelay}s` : "";
1938
+ this.onJobComplete(rid, job, retries, chalk2.bold(chalk2.yellow("RETRYING") + delayMsg));
1939
+ }
1940
+ onSpecPass(rid, job, retries = 0) {
1941
+ this.onJobComplete(rid, job, retries, chalk2.bold(chalk2.green("PASSED")));
1942
+ }
1943
+ onSpecFailure(rid, job, retries = 0) {
1944
+ this.onJobComplete(rid, job, retries, chalk2.bold(chalk2.red("FAILED")));
1945
+ }
1946
+ onSpecSkip(rid, job) {
1947
+ this.onJobComplete(rid, job, 0, "SKIPPED", log2.info);
1948
+ }
1949
+ onJobComplete(cid, job, retries = 0, message = "", _logger = this.log) {
1950
+ const details = [`[${cid}]`, message];
1951
+ if (job) {
1952
+ details.push("in", getRunnerName(job.caps), this.getFilenames(job.specs));
1953
+ }
1954
+ if (retries > 0) {
1955
+ details.push(`(${retries} retries)`);
1956
+ }
1957
+ return _logger(...details);
1958
+ }
1959
+ onTestError(payload) {
1960
+ const error = {
1961
+ type: payload.error?.type || "Error",
1962
+ message: payload.error?.message || (typeof payload.error === "string" ? payload.error : "Unknown error."),
1963
+ stack: payload.error?.stack
1964
+ };
1965
+ return this.log(`[${payload.cid}]`, `${chalk2.red(error.type)} in "${payload.fullTitle}"
1966
+ ${chalk2.red(error.stack || error.message)}`);
1967
+ }
1968
+ getFilenames(specs = []) {
1969
+ if (specs.length > 0) {
1970
+ return "- " + specs.join(", ").replace(new RegExp(`${process.cwd()}`, "g"), "");
1971
+ }
1972
+ return "";
1973
+ }
1974
+ /**
1975
+ * add job to interface
1976
+ */
1977
+ addJob({ cid, caps, specs, hasTests }) {
1978
+ this._jobs.set(cid, { caps, specs, hasTests });
1979
+ if (hasTests) {
1980
+ this.onSpecRunning(cid);
1981
+ } else {
1982
+ this._skippedSpecs++;
1983
+ }
1984
+ }
1985
+ /**
1986
+ * clear job from interface
1987
+ */
1988
+ clearJob({ cid, passed, retries }) {
1989
+ const job = this._jobs.get(cid);
1990
+ this._jobs.delete(cid);
1991
+ const retryAttempts = this._specFileRetries - retries;
1992
+ const retry = !passed && retries > 0;
1993
+ if (!retry) {
1994
+ this.result.finished++;
1995
+ }
1996
+ if (job && job.hasTests === false) {
1997
+ return this.onSpecSkip(cid, job);
1998
+ }
1999
+ if (passed) {
2000
+ this.result.passed++;
2001
+ this.onSpecPass(cid, job, retryAttempts);
2002
+ } else if (retry) {
2003
+ this.totalWorkerCnt++;
2004
+ this.result.retries++;
2005
+ this.onSpecRetry(cid, job, retryAttempts);
2006
+ } else {
2007
+ this.result.failed++;
2008
+ this.onSpecFailure(cid, job, retryAttempts);
2009
+ }
2010
+ }
2011
+ /**
2012
+ * for testing purposes call console log in a static method
2013
+ */
2014
+ log(...args) {
2015
+ console.log(...args);
2016
+ return args;
2017
+ }
2018
+ logHookError(error) {
2019
+ if (error instanceof HookError) {
2020
+ return this.log(`${chalk2.red(error.name)} in "${error.origin}"
2021
+ ${chalk2.red(error.stack || error.message)}`);
2022
+ }
2023
+ return this.log(`${chalk2.red(error.name)}: ${chalk2.red(error.stack || error.message)}`);
2024
+ }
2025
+ /**
2026
+ * event handler that is triggered when runner sends up events
2027
+ */
2028
+ onMessage(event) {
2029
+ if (event.name === "reporterRealTime") {
2030
+ this.log(event.content);
2031
+ return;
2032
+ }
2033
+ if (event.origin === "debugger" && event.name === "start") {
2034
+ this.log(chalk2.yellow(event.params.introMessage));
2035
+ this._inDebugMode = true;
2036
+ return this._inDebugMode;
2037
+ }
2038
+ if (event.origin === "debugger" && event.name === "stop") {
2039
+ this._inDebugMode = false;
2040
+ return this._inDebugMode;
2041
+ }
2042
+ if (event.name === "testFrameworkInit") {
2043
+ return this.emit("job:start", event.content);
2044
+ }
2045
+ if (event.name === "snapshot") {
2046
+ const snapshotResults = event.content;
2047
+ return snapshotResults.forEach((snapshotResult) => {
2048
+ this.#snapshotManager.add(snapshotResult);
2049
+ });
2050
+ }
2051
+ if (event.name === "error") {
2052
+ return this.log(
2053
+ `[${event.cid}]`,
2054
+ chalk2.white(chalk2.bgRed(chalk2.bold(" Error: "))),
2055
+ event.content ? event.content.message || event.content.stack || event.content : ""
2056
+ );
2057
+ }
2058
+ if (event.origin !== "reporter" && event.origin !== "debugger") {
2059
+ if (EVENT_FILTER.includes(event.name)) {
2060
+ return;
2061
+ }
2062
+ return this.log(event.cid, event.origin, event.name, event.content);
2063
+ }
2064
+ if (event.name === "printFailureMessage") {
2065
+ return this.onTestError(event.content);
2066
+ }
2067
+ if (!this._messages[event.origin][event.name]) {
2068
+ this._messages[event.origin][event.name] = [];
2069
+ }
2070
+ this._messages[event.origin][event.name].push(event.content);
2071
+ }
2072
+ sigintTrigger() {
2073
+ if (this._inDebugMode) {
2074
+ return false;
2075
+ }
2076
+ const isRunning = this._jobs.size !== 0 || this._isWatchMode;
2077
+ const shutdownMessage = isRunning ? "Ending WebDriver sessions gracefully ...\n(press ctrl+c again to hard kill the runner)" : "Ended WebDriver sessions gracefully after a SIGINT signal was received!";
2078
+ return this.log("\n\n" + shutdownMessage);
2079
+ }
2080
+ printReporters() {
2081
+ const reporter = this._messages.reporter;
2082
+ this._messages.reporter = {};
2083
+ for (const [reporterName, messages] of Object.entries(reporter)) {
2084
+ this.log("\n", chalk2.bold(chalk2.magenta(`"${reporterName}" Reporter:`)));
2085
+ this.log(messages.join(""));
2086
+ }
2087
+ }
2088
+ printSummary() {
2089
+ const totalJobs = this.totalWorkerCnt - this.result.retries;
2090
+ const elapsed = new Date(Date.now() - this._start.getTime()).toUTCString().match(/(\d\d:\d\d:\d\d)/)[0];
2091
+ const retries = this.result.retries ? chalk2.yellow(this.result.retries, "retries") + ", " : "";
2092
+ const failed = this.result.failed ? chalk2.red(this.result.failed, "failed") + ", " : "";
2093
+ const skipped = this._skippedSpecs > 0 ? chalk2.gray(this._skippedSpecs, "skipped") + ", " : "";
2094
+ const percentCompleted = totalJobs ? Math.round(this.result.finished / totalJobs * 100) : 0;
2095
+ const snapshotSummary = this.#snapshotManager.summary;
2096
+ const snapshotNotes = [];
2097
+ if (snapshotSummary.added > 0) {
2098
+ snapshotNotes.push(chalk2.green(`${snapshotSummary.added} snapshot(s) added.`));
2099
+ }
2100
+ if (snapshotSummary.updated > 0) {
2101
+ snapshotNotes.push(chalk2.yellow(`${snapshotSummary.updated} snapshot(s) updated.`));
2102
+ }
2103
+ if (snapshotSummary.unmatched > 0) {
2104
+ snapshotNotes.push(chalk2.red(`${snapshotSummary.unmatched} snapshot(s) unmatched.`));
2105
+ }
2106
+ if (snapshotSummary.unchecked > 0) {
2107
+ snapshotNotes.push(chalk2.gray(`${snapshotSummary.unchecked} snapshot(s) unchecked.`));
2108
+ }
2109
+ if (snapshotNotes.length > 0) {
2110
+ this.log("\nSnapshot Summary:");
2111
+ snapshotNotes.forEach((note) => this.log(note));
2112
+ }
2113
+ return this.log(
2114
+ "\nSpec Files: ",
2115
+ chalk2.green(this.result.passed, "passed") + ", " + retries + failed + skipped + totalJobs,
2116
+ "total",
2117
+ `(${percentCompleted}% completed)`,
2118
+ "in",
2119
+ elapsed,
2120
+ this.#hasShard() ? `
2121
+ Shard: ${this._config.shard.current} / ${this._config.shard.total}` : "",
2122
+ "\n"
2123
+ );
2124
+ }
2125
+ finalise() {
2126
+ this.printReporters();
2127
+ this.printSummary();
2128
+ }
2129
+ };
2130
+
2131
+ // src/launcher.ts
2132
+ var log3 = logger3("@wdio/cli:launcher");
2133
+ var Launcher = class {
2134
+ constructor(_configFilePath, _args = {}, _isWatchMode = false) {
2135
+ this._configFilePath = _configFilePath;
2136
+ this._args = _args;
2137
+ this._isWatchMode = _isWatchMode;
2138
+ this.configParser = new ConfigParser2(this._configFilePath, this._args);
2139
+ }
2140
+ configParser;
2141
+ isMultiremote = false;
2142
+ isParallelMultiremote = false;
2143
+ runner;
2144
+ interface;
2145
+ _exitCode = 0;
2146
+ _hasTriggeredExitRoutine = false;
2147
+ _schedule = [];
2148
+ _rid = [];
2149
+ _runnerStarted = 0;
2150
+ _runnerFailed = 0;
2151
+ _launcher;
2152
+ _resolve;
2153
+ /**
2154
+ * run sequence
2155
+ * @return {Promise} that only gets resolved with either an exitCode or an error
2156
+ */
2157
+ async run() {
2158
+ await this.configParser.initialize(this._args);
2159
+ const config = this.configParser.getConfig();
2160
+ const capabilities = this.configParser.getCapabilities();
2161
+ this.isParallelMultiremote = Array.isArray(capabilities) && capabilities.every((cap) => Object.values(cap).length > 0 && Object.values(cap).every((c) => typeof c === "object" && c.capabilities));
2162
+ this.isMultiremote = this.isParallelMultiremote || !Array.isArray(capabilities);
2163
+ validateConfig(TESTRUNNER_DEFAULTS, { ...config, capabilities });
2164
+ await enableFileLogging(config.outputDir);
2165
+ logger3.setLogLevelsConfig(config.logLevels, config.logLevel);
2166
+ const totalWorkerCnt = Array.isArray(capabilities) ? capabilities.map((c) => {
2167
+ if (this.isParallelMultiremote) {
2168
+ const keys = Object.keys(c);
2169
+ const caps = c[keys[0]].capabilities;
2170
+ return this.configParser.getSpecs(caps["wdio:specs"], caps["wdio:exclude"]).length;
2171
+ }
2172
+ const standaloneCaps = c;
2173
+ const cap = "alwaysMatch" in standaloneCaps ? standaloneCaps.alwaysMatch : standaloneCaps;
2174
+ return this.configParser.getSpecs(cap["wdio:specs"], cap["wdio:exclude"]).length;
2175
+ }).reduce((a, b) => a + b, 0) : 1;
2176
+ this.interface = new WDIOCLInterface(config, totalWorkerCnt, this._isWatchMode);
2177
+ config.runnerEnv.FORCE_COLOR = Number(this.interface.hasAnsiSupport);
2178
+ const [runnerName, runnerOptions] = Array.isArray(config.runner) ? config.runner : [config.runner, {}];
2179
+ const Runner = (await initializePlugin(runnerName, "runner")).default;
2180
+ this.runner = new Runner(runnerOptions, config);
2181
+ exitHook(this._exitHandler.bind(this));
2182
+ let exitCode = 0;
2183
+ let error = void 0;
2184
+ try {
2185
+ const caps = this.configParser.getCapabilities();
2186
+ const { ignoredWorkerServices, launcherServices } = await initializeLauncherService(config, caps);
2187
+ this._launcher = launcherServices;
2188
+ this._args.ignoredWorkerServices = ignoredWorkerServices;
2189
+ await this.runner.initialize();
2190
+ log3.info("Run onPrepare hook");
2191
+ await runLauncherHook(config.onPrepare, config, caps);
2192
+ await runServiceHook(this._launcher, "onPrepare", config, caps);
2193
+ await Promise.all([
2194
+ setupDriver(config, caps),
2195
+ setupBrowser(config, caps)
2196
+ ]);
2197
+ exitCode = await this._runMode(config, caps);
2198
+ log3.info("Run onComplete hook");
2199
+ const onCompleteResults = await runOnCompleteHook(config.onComplete, config, caps, exitCode, this.interface.result);
2200
+ await runServiceHook(this._launcher, "onComplete", exitCode, config, caps);
2201
+ exitCode = onCompleteResults.includes(1) ? 1 : exitCode;
2202
+ await logger3.waitForBuffer();
2203
+ this.interface.finalise();
2204
+ } catch (err) {
2205
+ error = err;
2206
+ } finally {
2207
+ if (!this._hasTriggeredExitRoutine) {
2208
+ this._hasTriggeredExitRoutine = true;
2209
+ const passesCodeCoverage = await this.runner.shutdown();
2210
+ if (!passesCodeCoverage) {
2211
+ exitCode = exitCode || 1;
2212
+ }
2213
+ }
2214
+ }
2215
+ if (error) {
2216
+ this.interface.logHookError(error);
2217
+ throw error;
2218
+ }
2219
+ return exitCode;
2220
+ }
2221
+ /**
2222
+ * run without triggering onPrepare/onComplete hooks
2223
+ */
2224
+ _runMode(config, caps) {
2225
+ if (!caps) {
2226
+ return new Promise((resolve2) => {
2227
+ log3.error("Missing capabilities, exiting with failure");
2228
+ return resolve2(1);
2229
+ });
2230
+ }
2231
+ const specFileRetries = this._isWatchMode ? 0 : config.specFileRetries;
2232
+ let cid = 0;
2233
+ if (this.isMultiremote && !this.isParallelMultiremote) {
2234
+ this._schedule.push({
2235
+ cid: cid++,
2236
+ caps,
2237
+ specs: this._formatSpecs(caps, specFileRetries),
2238
+ availableInstances: config.maxInstances || 1,
2239
+ runningInstances: 0
2240
+ });
2241
+ } else {
2242
+ for (const capabilities of caps) {
2243
+ const availableInstances = this.isParallelMultiremote ? config.maxInstances || 1 : config.runner === "browser" ? 1 : capabilities["wdio:maxInstances"] || config.maxInstancesPerCapability;
2244
+ this._schedule.push({
2245
+ cid: cid++,
2246
+ caps: capabilities,
2247
+ specs: this._formatSpecs(capabilities, specFileRetries),
2248
+ availableInstances,
2249
+ runningInstances: 0
2250
+ });
2251
+ }
2252
+ }
2253
+ return new Promise((resolve2) => {
2254
+ this._resolve = resolve2;
2255
+ if (Object.values(this._schedule).reduce((specCnt, schedule) => specCnt + schedule.specs.length, 0) === 0) {
2256
+ const { total, current } = config.shard;
2257
+ if (total > 1) {
2258
+ log3.info(`No specs to execute in shard ${current}/${total}, exiting!`);
2259
+ return resolve2(0);
2260
+ }
2261
+ log3.error("No specs found to run, exiting with failure");
2262
+ return resolve2(1);
2263
+ }
2264
+ if (this._runSpecs()) {
2265
+ resolve2(0);
2266
+ }
2267
+ });
2268
+ }
2269
+ /**
2270
+ * Format the specs into an array of objects with files and retries
2271
+ */
2272
+ _formatSpecs(capabilities, specFileRetries) {
2273
+ let caps;
2274
+ if ("alwaysMatch" in capabilities) {
2275
+ caps = capabilities.alwaysMatch;
2276
+ } else if (typeof Object.keys(capabilities)[0] === "object" && "capabilities" in capabilities[Object.keys(capabilities)[0]]) {
2277
+ caps = {};
2278
+ } else {
2279
+ caps = capabilities;
2280
+ }
2281
+ const specs = (
2282
+ // @ts-expect-error deprecated
2283
+ caps.specs || caps["wdio:specs"]
2284
+ );
2285
+ const excludes = (
2286
+ // @ts-expect-error deprecated
2287
+ caps.exclude || caps["wdio:exclude"]
2288
+ );
2289
+ const files = this.configParser.getSpecs(specs, excludes);
2290
+ return files.map((file) => {
2291
+ if (typeof file === "string") {
2292
+ return { files: [file], retries: specFileRetries };
2293
+ } else if (Array.isArray(file)) {
2294
+ return { files: file, retries: specFileRetries };
2295
+ }
2296
+ log3.warn("Unexpected entry in specs that is neither string nor array: ", file);
2297
+ return { files: [], retries: specFileRetries };
2298
+ });
2299
+ }
2300
+ /**
2301
+ * run multiple single remote tests
2302
+ * @return {Boolean} true if all specs have been run and all instances have finished
2303
+ */
2304
+ _runSpecs() {
2305
+ if (this._hasTriggeredExitRoutine) {
2306
+ return true;
2307
+ }
2308
+ const config = this.configParser.getConfig();
2309
+ while (this._getNumberOfRunningInstances() < config.maxInstances) {
2310
+ const schedulableCaps = this._schedule.filter(() => {
2311
+ const filter = typeof config.bail !== "number" || config.bail < 1 || config.bail > this._runnerFailed;
2312
+ if (!filter) {
2313
+ this._schedule.forEach((t) => {
2314
+ t.specs = [];
2315
+ });
2316
+ }
2317
+ return filter;
2318
+ }).filter(() => this._getNumberOfRunningInstances() < config.maxInstances).filter((a) => a.availableInstances > 0).filter((a) => a.specs.length > 0).sort((a, b) => a.runningInstances - b.runningInstances);
2319
+ if (schedulableCaps.length === 0) {
2320
+ break;
2321
+ }
2322
+ const specs = schedulableCaps[0].specs.shift();
2323
+ this._startInstance(
2324
+ specs.files,
2325
+ schedulableCaps[0].caps,
2326
+ schedulableCaps[0].cid,
2327
+ specs.rid,
2328
+ specs.retries
2329
+ );
2330
+ schedulableCaps[0].availableInstances--;
2331
+ schedulableCaps[0].runningInstances++;
2332
+ }
2333
+ return this._getNumberOfRunningInstances() === 0 && this._getNumberOfSpecsLeft() === 0;
2334
+ }
2335
+ /**
2336
+ * gets number of all running instances
2337
+ * @return {number} number of running instances
2338
+ */
2339
+ _getNumberOfRunningInstances() {
2340
+ return this._schedule.map((a) => a.runningInstances).reduce((a, b) => a + b);
2341
+ }
2342
+ /**
2343
+ * get number of total specs left to complete whole suites
2344
+ * @return {number} specs left to complete suite
2345
+ */
2346
+ _getNumberOfSpecsLeft() {
2347
+ return this._schedule.map((a) => a.specs.length).reduce((a, b) => a + b);
2348
+ }
2349
+ /**
2350
+ * Start instance in a child process.
2351
+ * @param {Array} specs Specs to run
2352
+ * @param {number} cid Capabilities ID
2353
+ * @param {string} rid Runner ID override
2354
+ * @param {number} retries Number of retries remaining
2355
+ */
2356
+ async _startInstance(specs, caps, cid, rid, retries) {
2357
+ if (!this.runner || !this.interface) {
2358
+ throw new Error("Internal Error: no runner initialized, call run() first");
2359
+ }
2360
+ const config = this.configParser.getConfig();
2361
+ if (typeof config.specFileRetriesDelay === "number" && config.specFileRetries > 0 && config.specFileRetries !== retries) {
2362
+ await sleep(config.specFileRetriesDelay * 1e3);
2363
+ }
2364
+ const runnerId = rid || this._getRunnerId(cid);
2365
+ const processNumber = this._runnerStarted + 1;
2366
+ const debugArgs = [];
2367
+ let debugType;
2368
+ let debugHost = "";
2369
+ const debugPort = process.debugPort;
2370
+ for (const i in process.execArgv) {
2371
+ const debugArgs2 = process.execArgv[i].match("--(debug|inspect)(?:-brk)?(?:=(.*):)?");
2372
+ if (debugArgs2) {
2373
+ const [, type, host] = debugArgs2;
2374
+ if (type) {
2375
+ debugType = type;
2376
+ }
2377
+ if (host) {
2378
+ debugHost = `${host}:`;
2379
+ }
2380
+ }
2381
+ }
2382
+ if (debugType) {
2383
+ debugArgs.push(`--${debugType}=${debugHost}${debugPort + processNumber}`);
2384
+ }
2385
+ const capExecArgs = [...config.execArgv || []];
2386
+ const defaultArgs = capExecArgs.length ? process.execArgv : [];
2387
+ const execArgv = [...defaultArgs, ...debugArgs, ...capExecArgs];
2388
+ this._runnerStarted++;
2389
+ log3.info("Run onWorkerStart hook");
2390
+ await runLauncherHook(config.onWorkerStart, runnerId, caps, specs, this._args, execArgv).catch((error) => this._workerHookError(error));
2391
+ await runServiceHook(this._launcher, "onWorkerStart", runnerId, caps, specs, this._args, execArgv).catch((error) => this._workerHookError(error));
2392
+ const worker = await this.runner.run({
2393
+ cid: runnerId,
2394
+ command: "run",
2395
+ configFile: this._configFilePath,
2396
+ args: {
2397
+ ...this._args,
2398
+ /**
2399
+ * Pass on user and key values to ensure they are available in the worker process when using
2400
+ * environment variables that were locally exported but not part of the environment.
2401
+ */
2402
+ user: config.user,
2403
+ key: config.key
2404
+ },
2405
+ caps,
2406
+ specs,
2407
+ execArgv,
2408
+ retries
2409
+ });
2410
+ worker.on("message", this.interface.onMessage.bind(this.interface));
2411
+ worker.on("error", this.interface.onMessage.bind(this.interface));
2412
+ worker.on("exit", (code) => {
2413
+ if (!this.configParser.getConfig().groupLogsByTestSpec) {
2414
+ return;
2415
+ }
2416
+ if (code.exitCode === 0) {
2417
+ console.log(WORKER_GROUPLOGS_MESSAGES.normalExit(code.cid));
2418
+ } else {
2419
+ console.log(WORKER_GROUPLOGS_MESSAGES.exitWithError(code.cid));
2420
+ }
2421
+ worker.logsAggregator.forEach((logLine) => {
2422
+ console.log(logLine.replace(new RegExp("\\n$"), ""));
2423
+ });
2424
+ });
2425
+ worker.on("exit", this._endHandler.bind(this));
2426
+ }
2427
+ _workerHookError(error) {
2428
+ if (!this.interface) {
2429
+ throw new Error("Internal Error: no interface initialized, call run() first");
2430
+ }
2431
+ this.interface.logHookError(error);
2432
+ if (this._resolve) {
2433
+ this._resolve(1);
2434
+ }
2435
+ }
2436
+ /**
2437
+ * generates a runner id
2438
+ * @param {number} cid capability id (unique identifier for a capability)
2439
+ * @return {String} runner id (combination of cid and test id e.g. 0a, 0b, 1a, 1b ...)
2440
+ */
2441
+ _getRunnerId(cid) {
2442
+ if (!this._rid[cid]) {
2443
+ this._rid[cid] = 0;
2444
+ }
2445
+ return `${cid}-${this._rid[cid]++}`;
2446
+ }
2447
+ /**
2448
+ * Close test runner process once all child processes have exited
2449
+ * @param {number} cid Capabilities ID
2450
+ * @param {number} exitCode exit code of child process
2451
+ * @param {Array} specs Specs that were run
2452
+ * @param {number} retries Number or retries remaining
2453
+ */
2454
+ async _endHandler({ cid: rid, exitCode, specs, retries }) {
2455
+ const passed = this._isWatchModeHalted() || exitCode === 0;
2456
+ if (!passed && retries > 0) {
2457
+ const requeue = this.configParser.getConfig().specFileRetriesDeferred !== false ? "push" : "unshift";
2458
+ this._schedule[parseInt(rid, 10)].specs[requeue]({ files: specs, retries: retries - 1, rid });
2459
+ } else {
2460
+ this._exitCode = this._isWatchModeHalted() ? 0 : this._exitCode || exitCode;
2461
+ this._runnerFailed += !passed ? 1 : 0;
2462
+ }
2463
+ if (!this._isWatchModeHalted() && this.interface) {
2464
+ this.interface.emit("job:end", { cid: rid, passed, retries });
2465
+ }
2466
+ const cid = parseInt(rid, 10);
2467
+ this._schedule[cid].availableInstances++;
2468
+ this._schedule[cid].runningInstances--;
2469
+ log3.info("Run onWorkerEnd hook");
2470
+ const config = this.configParser.getConfig();
2471
+ await runLauncherHook(config.onWorkerEnd, rid, exitCode, specs, retries).catch((error) => this._workerHookError(error));
2472
+ await runServiceHook(this._launcher, "onWorkerEnd", rid, exitCode, specs, retries).catch((error) => this._workerHookError(error));
2473
+ const shouldRunSpecs = this._runSpecs();
2474
+ const inWatchMode = this._isWatchMode && !this._hasTriggeredExitRoutine;
2475
+ if (!shouldRunSpecs || inWatchMode) {
2476
+ if (inWatchMode) {
2477
+ this.interface?.finalise();
2478
+ }
2479
+ return;
2480
+ }
2481
+ if (this._resolve) {
2482
+ this._resolve(passed ? this._exitCode : 1);
2483
+ }
2484
+ }
2485
+ /**
2486
+ * We need exitHandler to catch SIGINT / SIGTERM events.
2487
+ * Make sure all started selenium sessions get closed properly and prevent
2488
+ * having dead driver processes. To do so let the runner end its Selenium
2489
+ * session first before killing
2490
+ */
2491
+ _exitHandler(callback) {
2492
+ if (!callback || !this.runner || !this.interface) {
2493
+ return;
2494
+ }
2495
+ if (this._hasTriggeredExitRoutine) {
2496
+ return callback(true);
2497
+ }
2498
+ this._hasTriggeredExitRoutine = true;
2499
+ this.interface.sigintTrigger();
2500
+ return this.runner.shutdown().then(callback);
2501
+ }
2502
+ /**
2503
+ * returns true if user stopped watch mode, ex with ctrl+c
2504
+ * @returns {boolean}
2505
+ */
2506
+ _isWatchModeHalted() {
2507
+ return this._isWatchMode && this._hasTriggeredExitRoutine;
2508
+ }
2509
+ };
2510
+ var launcher_default = Launcher;
2511
+
2512
+ // src/run.ts
2513
+ import path6 from "node:path";
2514
+ import yargs from "yargs";
2515
+ import { hideBin } from "yargs/helpers";
2516
+
2517
+ // src/commands/config.ts
2518
+ var config_exports = {};
2519
+ __export(config_exports, {
2520
+ builder: () => builder,
2521
+ canAccessConfigPath: () => canAccessConfigPath,
2522
+ cmdArgs: () => cmdArgs,
2523
+ command: () => command,
2524
+ desc: () => desc,
2525
+ formatConfigFilePaths: () => formatConfigFilePaths,
2526
+ handler: () => handler,
2527
+ missingConfigurationPrompt: () => missingConfigurationPrompt,
2528
+ parseAnswers: () => parseAnswers,
2529
+ runConfigCommand: () => runConfigCommand
2530
+ });
2531
+ import fs3 from "node:fs/promises";
2532
+ import fss from "node:fs";
2533
+ import path3 from "node:path";
2534
+ import inquirer2 from "inquirer";
2535
+ var hasYarnLock = false;
2536
+ try {
2537
+ fss.accessSync("yarn.lock");
2538
+ hasYarnLock = true;
2539
+ } catch {
2540
+ hasYarnLock = false;
2541
+ }
2542
+ var command = "config";
2543
+ var desc = "Initialize WebdriverIO and setup configuration in your current project.";
2544
+ var cmdArgs = {
2545
+ yarn: {
2546
+ type: "boolean",
2547
+ desc: "Install packages via Yarn package manager.",
2548
+ default: hasYarnLock
2549
+ },
2550
+ yes: {
2551
+ alias: "y",
2552
+ desc: "will fill in all config defaults without prompting",
2553
+ type: "boolean",
2554
+ default: false
2555
+ },
2556
+ npmTag: {
2557
+ alias: "t",
2558
+ desc: "define NPM tag to use for WebdriverIO related packages",
2559
+ type: "string",
2560
+ default: "latest"
2561
+ }
2562
+ };
2563
+ var builder = (yargs2) => {
2564
+ return yargs2.options(cmdArgs).epilogue(CLI_EPILOGUE).help();
2565
+ };
2566
+ var parseAnswers = async function(yes) {
2567
+ console.log(CONFIG_HELPER_INTRO);
2568
+ const answers = await getAnswers(yes);
2569
+ const frameworkPackage = convertPackageHashToObject(answers.framework);
2570
+ const runnerPackage = convertPackageHashToObject(answers.runner || SUPPORTED_PACKAGES.runner[0].value);
2571
+ const servicePackages = answers.services.map((service) => convertPackageHashToObject(service));
2572
+ const pluginPackages = answers.plugins.map((plugin) => convertPackageHashToObject(plugin));
2573
+ const serenityPackages = getSerenityPackages(answers);
2574
+ const reporterPackages = answers.reporters.map((reporter) => convertPackageHashToObject(reporter));
2575
+ const presetPackage = convertPackageHashToObject(answers.preset || "");
2576
+ const projectProps = await getProjectProps(process.cwd());
2577
+ const projectRootDir = await getProjectRoot(answers);
2578
+ const packagesToInstall = [
2579
+ runnerPackage.package,
2580
+ frameworkPackage.package,
2581
+ presetPackage.package,
2582
+ ...reporterPackages.map((reporter) => reporter.package),
2583
+ ...pluginPackages.map((plugin) => plugin.package),
2584
+ ...servicePackages.map((service) => service.package),
2585
+ ...serenityPackages
2586
+ ].filter(Boolean);
2587
+ const hasRootTSConfig = await fs3.access(path3.resolve(projectRootDir, "tsconfig.json")).then(() => true, () => false);
2588
+ const tsConfigFilePath = !hasRootTSConfig ? path3.resolve(projectRootDir, "tsconfig.json") : answers.specs ? path3.resolve(
2589
+ path3.dirname(answers.specs.split(path3.sep).filter((s) => !s.includes("*")).join(path3.sep)),
2590
+ "tsconfig.json"
2591
+ ) : path3.resolve(projectRootDir, `tsconfig.${runnerPackage.short === "local" ? "e2e" : "wdio"}.json`);
2592
+ const parsedPaths = getPathForFileGeneration(answers, projectRootDir);
2593
+ const isUsingTypeScript = answers.isUsingTypeScript;
2594
+ const wdioConfigFilename = `wdio.conf.${isUsingTypeScript ? "ts" : "js"}`;
2595
+ const wdioConfigPath = path3.resolve(projectRootDir, wdioConfigFilename);
2596
+ return {
2597
+ projectName: projectProps?.packageJson.name || "Test Suite",
2598
+ // default values required in templates
2599
+ ...{
2600
+ usePageObjects: false,
2601
+ installTestingLibrary: false
2602
+ },
2603
+ ...answers,
2604
+ useSauceConnect: isNuxtProject || answers.useSauceConnect,
2605
+ rawAnswers: answers,
2606
+ runner: runnerPackage.short,
2607
+ preset: presetPackage.short,
2608
+ framework: frameworkPackage.short,
2609
+ purpose: runnerPackage.purpose,
2610
+ serenityAdapter: frameworkPackage.package === "@serenity-js/webdriverio" && frameworkPackage.purpose,
2611
+ reporters: reporterPackages.map(({ short }) => short),
2612
+ plugins: pluginPackages.map(({ short }) => short),
2613
+ services: servicePackages.map(({ short }) => short),
2614
+ specs: answers.specs && `./${path3.relative(projectRootDir, answers.specs).replaceAll(path3.sep, "/")}`,
2615
+ stepDefinitions: answers.stepDefinitions && `./${path3.relative(projectRootDir, answers.stepDefinitions).replaceAll(path3.sep, "/")}`,
2616
+ packagesToInstall,
2617
+ isUsingTypeScript,
2618
+ esmSupport: projectProps && !projectProps.esmSupported ? false : true,
2619
+ isSync: false,
2620
+ _async: "async ",
2621
+ _await: "await ",
2622
+ projectRootDir,
2623
+ destSpecRootPath: parsedPaths.destSpecRootPath,
2624
+ destStepRootPath: parsedPaths.destStepRootPath,
2625
+ destPageObjectRootPath: parsedPaths.destPageObjectRootPath,
2626
+ destSerenityLibRootPath: parsedPaths.destSerenityLibRootPath,
2627
+ relativePath: parsedPaths.relativePath,
2628
+ hasRootTSConfig,
2629
+ tsConfigFilePath,
2630
+ tsProject: `./${path3.relative(projectRootDir, tsConfigFilePath).replaceAll(path3.sep, "/")}`,
2631
+ wdioConfigPath
2632
+ };
2633
+ };
2634
+ async function runConfigCommand(parsedAnswers, npmTag) {
2635
+ console.log("\n");
2636
+ await createPackageJSON(parsedAnswers);
2637
+ await setupTypeScript(parsedAnswers);
2638
+ await npmInstall(parsedAnswers, npmTag);
2639
+ await createWDIOConfig(parsedAnswers);
2640
+ await createWDIOScript(parsedAnswers);
2641
+ console.log(
2642
+ configHelperSuccessMessage({
2643
+ projectRootDir: parsedAnswers.projectRootDir,
2644
+ runScript: parsedAnswers.serenityAdapter ? "serenity" : "wdio",
2645
+ extraInfo: parsedAnswers.serenityAdapter ? CONFIG_HELPER_SERENITY_BANNER : ""
2646
+ })
2647
+ );
2648
+ await runAppiumInstaller(parsedAnswers);
2649
+ }
2650
+ async function handler(argv, runConfigCmd = runConfigCommand) {
2651
+ const parsedAnswers = await parseAnswers(argv.yes);
2652
+ await runConfigCmd(parsedAnswers, argv.npmTag);
2653
+ return {
2654
+ success: true,
2655
+ parsedAnswers,
2656
+ installedPackages: parsedAnswers.packagesToInstall.map((pkg2) => pkg2.split("--")[0])
2657
+ };
2658
+ }
2659
+ async function formatConfigFilePaths(config) {
2660
+ const fullPath = path3.isAbsolute(config) ? config : path3.join(process.cwd(), config);
2661
+ const fullPathNoExtension = fullPath.substring(0, fullPath.lastIndexOf(path3.extname(fullPath)));
2662
+ return { fullPath, fullPathNoExtension };
2663
+ }
2664
+ async function canAccessConfigPath(configPath) {
2665
+ return Promise.all(SUPPORTED_CONFIG_FILE_EXTENSION.map(async (supportedExtension) => {
2666
+ const configPathWithExtension = `${configPath}.${supportedExtension}`;
2667
+ return fs3.access(configPathWithExtension).then(() => configPathWithExtension, () => void 0);
2668
+ })).then(
2669
+ (configFilePaths) => configFilePaths.find(Boolean),
2670
+ () => void 0
2671
+ );
2672
+ }
2673
+ async function missingConfigurationPrompt(command5, configPath, runConfigCmd = runConfigCommand) {
2674
+ const message = `Could not execute "${command5}" due to missing configuration, file "${path3.parse(configPath).name}[.js/.ts]" not found! Would you like to create one?`;
2675
+ const { config } = await inquirer2.prompt([{
2676
+ type: "confirm",
2677
+ name: "config",
2678
+ message,
2679
+ default: false
2680
+ }]);
2681
+ if (!config) {
2682
+ console.log(`No WebdriverIO configuration found in "${process.cwd()}"`);
2683
+ return !process.env.VITEST_WORKER_ID && process.exit(0);
2684
+ }
2685
+ const parsedAnswers = await parseAnswers(false);
2686
+ await runConfigCmd(parsedAnswers, "latest");
2687
+ }
2688
+
2689
+ // src/commands/install.ts
2690
+ var install_exports = {};
2691
+ __export(install_exports, {
2692
+ builder: () => builder2,
2693
+ cmdArgs: () => cmdArgs2,
2694
+ command: () => command2,
2695
+ desc: () => desc2,
2696
+ handler: () => handler2
2697
+ });
2698
+ import fs4 from "node:fs/promises";
2699
+ import path4 from "node:path";
2700
+ var supportedInstallations = {
2701
+ runner: SUPPORTED_PACKAGES.runner.map(({ value }) => convertPackageHashToObject(value)),
2702
+ plugin: SUPPORTED_PACKAGES.plugin.map(({ value }) => convertPackageHashToObject(value)),
2703
+ service: SUPPORTED_PACKAGES.service.map(({ value }) => convertPackageHashToObject(value)),
2704
+ reporter: SUPPORTED_PACKAGES.reporter.map(({ value }) => convertPackageHashToObject(value)),
2705
+ framework: SUPPORTED_PACKAGES.framework.map(({ value }) => convertPackageHashToObject(value))
2706
+ };
2707
+ var command2 = "install <type> <name>";
2708
+ var desc2 = [
2709
+ "Add a `reporter`, `service`, or `framework` to your WebdriverIO project.",
2710
+ "The command installs the package from NPM, adds it to your package.json",
2711
+ "and modifies the wdio.conf.js accordingly."
2712
+ ].join(" ");
2713
+ var cmdArgs2 = {
2714
+ config: {
2715
+ desc: "Location of your WDIO configuration (default: wdio.conf.(js|ts|cjs|mjs))"
2716
+ }
2717
+ };
2718
+ var builder2 = (yargs2) => {
2719
+ yargs2.options(cmdArgs2).epilogue(CLI_EPILOGUE).help();
2720
+ for (const [type, plugins] of Object.entries(supportedInstallations)) {
2721
+ for (const plugin of plugins) {
2722
+ yargs2.example(`$0 install ${type} ${plugin.short}`, `Install ${plugin.package}`);
2723
+ }
2724
+ }
2725
+ return yargs2;
2726
+ };
2727
+ async function handler2(argv) {
2728
+ const { type, name, config } = argv;
2729
+ if (!Object.keys(supportedInstallations).includes(type)) {
2730
+ console.log(`Type ${type} is not supported.`);
2731
+ process.exit(0);
2732
+ return;
2733
+ }
2734
+ const options = supportedInstallations[type].map((pkg2) => pkg2.short);
2735
+ if (!options.find((pkg2) => pkg2 === name)) {
2736
+ console.log(
2737
+ `Error: ${name} is not a supported ${type}.
2738
+
2739
+ Available options for a ${type} are:
2740
+ - ${options.join("\n- ")}`
2741
+ );
2742
+ process.exit(0);
2743
+ return;
2744
+ }
2745
+ const defaultPath = path4.resolve(process.cwd(), "wdio.conf");
2746
+ const wdioConfPathWithNoExtension = config ? (await formatConfigFilePaths(config)).fullPathNoExtension : defaultPath;
2747
+ const wdioConfPath = await canAccessConfigPath(wdioConfPathWithNoExtension);
2748
+ if (!wdioConfPath) {
2749
+ try {
2750
+ await missingConfigurationPrompt("install", wdioConfPathWithNoExtension);
2751
+ return handler2(argv);
2752
+ } catch {
2753
+ process.exit(1);
2754
+ return;
2755
+ }
2756
+ }
2757
+ const configFile = await fs4.readFile(wdioConfPath, { encoding: "utf-8" });
2758
+ const match = findInConfig(configFile, type);
2759
+ const projectRoot = await getProjectRoot();
2760
+ if (match && match[0].includes(name)) {
2761
+ console.log(`The ${type} ${name} is already part of your configuration.`);
2762
+ process.exit(0);
2763
+ return;
2764
+ }
2765
+ const selectedPackage = supportedInstallations[type].find(({ short }) => short === name);
2766
+ const pkgsToInstall = selectedPackage ? [selectedPackage.package] : [];
2767
+ addServiceDeps(selectedPackage ? [selectedPackage] : [], pkgsToInstall, true);
2768
+ const pm = detectPackageManager();
2769
+ console.log(`Installing "${selectedPackage.package}" using ${pm}.`);
2770
+ const success = await installPackages(projectRoot, pkgsToInstall, true);
2771
+ if (!success) {
2772
+ process.exit(1);
2773
+ return;
2774
+ }
2775
+ console.log(`Package "${selectedPackage.package}" installed successfully.`);
2776
+ const newConfig = replaceConfig(configFile, type, name);
2777
+ if (!newConfig) {
2778
+ throw new Error(`Couldn't find "${type}" property in ${path4.basename(wdioConfPath)}`);
2779
+ }
2780
+ await fs4.writeFile(wdioConfPath, newConfig, { encoding: "utf-8" });
2781
+ console.log("Your wdio.conf.js file has been updated.");
2782
+ process.exit(0);
2783
+ }
2784
+
2785
+ // src/commands/repl.ts
2786
+ var repl_exports = {};
2787
+ __export(repl_exports, {
2788
+ builder: () => builder4,
2789
+ cmdArgs: () => cmdArgs4,
2790
+ command: () => command4,
2791
+ desc: () => desc4,
2792
+ handler: () => handler4
2793
+ });
2794
+ import pickBy3 from "lodash.pickby";
2795
+ import { remote } from "webdriverio";
2796
+
2797
+ // src/commands/run.ts
2798
+ var run_exports = {};
2799
+ __export(run_exports, {
2800
+ builder: () => builder3,
2801
+ cmdArgs: () => cmdArgs3,
2802
+ command: () => command3,
2803
+ desc: () => desc3,
2804
+ handler: () => handler3,
2805
+ launch: () => launch,
2806
+ launchWithStdin: () => launchWithStdin,
2807
+ nodeVersion: () => nodeVersion
2808
+ });
2809
+ import path5 from "node:path";
2810
+ import fs5 from "node:fs/promises";
2811
+ import { execa as execa2 } from "execa";
2812
+
2813
+ // src/watcher.ts
2814
+ import url from "node:url";
2815
+ import chokidar from "chokidar";
2816
+ import logger4 from "@wdio/logger";
2817
+ import pickBy2 from "lodash.pickby";
2818
+ import flattenDeep from "lodash.flattendeep";
2819
+ import union from "lodash.union";
2820
+ var log4 = logger4("@wdio/cli:watch");
2821
+ var Watcher = class {
2822
+ constructor(_configFile, _args) {
2823
+ this._configFile = _configFile;
2824
+ this._args = _args;
2825
+ log4.info("Starting launcher in watch mode");
2826
+ this._launcher = new launcher_default(this._configFile, this._args, true);
2827
+ }
2828
+ _launcher;
2829
+ _specs = [];
2830
+ async watch() {
2831
+ await this._launcher.configParser.initialize();
2832
+ const specs = this._launcher.configParser.getSpecs();
2833
+ const capSpecs = this._launcher.isMultiremote ? [] : union(flattenDeep(
2834
+ this._launcher.configParser.getCapabilities().map((cap) => "alwaysMatch" in cap ? cap.alwaysMatch["wdio:specs"] : cap["wdio:specs"] || [])
2835
+ ));
2836
+ this._specs = [...specs, ...capSpecs];
2837
+ const flattenedSpecs = flattenDeep(this._specs).map((fileUrl) => url.fileURLToPath(fileUrl));
2838
+ chokidar.watch(flattenedSpecs, { ignoreInitial: true }).on("add", this.getFileListener()).on("change", this.getFileListener());
2839
+ const { filesToWatch } = this._launcher.configParser.getConfig();
2840
+ if (filesToWatch.length) {
2841
+ chokidar.watch(filesToWatch, { ignoreInitial: true }).on("add", this.getFileListener(false)).on("change", this.getFileListener(false));
2842
+ }
2843
+ await this._launcher.run();
2844
+ const workers = this.getWorkers();
2845
+ Object.values(workers).forEach((worker) => worker.on("exit", () => {
2846
+ if (Object.values(workers).find((w) => w.isBusy)) {
2847
+ return;
2848
+ }
2849
+ this._launcher.interface?.finalise();
2850
+ }));
2851
+ }
2852
+ /**
2853
+ * return file listener callback that calls `run` method
2854
+ * @param {Boolean} [passOnFile=true] if true pass on file change as parameter
2855
+ * @return {Function} chokidar event callback
2856
+ */
2857
+ getFileListener(passOnFile = true) {
2858
+ return (spec) => {
2859
+ const runSpecs = [];
2860
+ let singleSpecFound = false;
2861
+ for (let index = 0, length = this._specs.length; index < length; index += 1) {
2862
+ const value = this._specs[index];
2863
+ if (Array.isArray(value) && value.indexOf(spec) > -1) {
2864
+ runSpecs.push(value);
2865
+ } else if (!singleSpecFound && spec === value) {
2866
+ singleSpecFound = true;
2867
+ runSpecs.push(value);
2868
+ }
2869
+ }
2870
+ if (runSpecs.length === 0) {
2871
+ runSpecs.push(url.pathToFileURL(spec).href);
2872
+ }
2873
+ const { spec: _, ...args } = this._args;
2874
+ return runSpecs.map((spec2) => {
2875
+ return this.run({
2876
+ ...args,
2877
+ ...passOnFile ? { spec: [spec2] } : {}
2878
+ });
2879
+ });
2880
+ };
2881
+ }
2882
+ /**
2883
+ * helper method to get workers from worker pool of wdio runner
2884
+ * @param predicate filter by property value (see lodash.pickBy)
2885
+ * @param includeBusyWorker don't filter out busy worker (default: false)
2886
+ * @return Object with workers, e.g. {'0-0': { ... }}
2887
+ */
2888
+ getWorkers(predicate, includeBusyWorker) {
2889
+ if (!this._launcher.runner) {
2890
+ throw new Error("Internal Error: no runner initialized, call run() first");
2891
+ }
2892
+ let workers = this._launcher.runner.workerPool;
2893
+ if (typeof predicate === "function") {
2894
+ workers = pickBy2(workers, predicate);
2895
+ }
2896
+ if (!includeBusyWorker) {
2897
+ workers = pickBy2(workers, (worker) => !worker.isBusy);
2898
+ }
2899
+ return workers;
2900
+ }
2901
+ /**
2902
+ * run workers with params
2903
+ * @param params parameters to run the worker with
2904
+ */
2905
+ run(params = {}) {
2906
+ const workers = this.getWorkers(
2907
+ params.spec ? (worker) => Boolean(worker.specs.find((s) => params.spec?.includes(s))) : void 0
2908
+ );
2909
+ if (Object.keys(workers).length === 0 || !this._launcher.interface) {
2910
+ return;
2911
+ }
2912
+ this._launcher.interface.totalWorkerCnt = Object.entries(workers).length;
2913
+ this.cleanUp();
2914
+ for (const [, worker] of Object.entries(workers)) {
2915
+ const { cid, capabilities, specs, sessionId } = worker;
2916
+ const { hostname, path: path7, port, protocol, automationProtocol } = worker.config;
2917
+ const args = Object.assign({ sessionId, baseUrl: worker.config.baseUrl, hostname, path: path7, port, protocol, automationProtocol }, params);
2918
+ worker.postMessage("run", args);
2919
+ this._launcher.interface.emit("job:start", { cid, caps: capabilities, specs });
2920
+ }
2921
+ }
2922
+ cleanUp() {
2923
+ this._launcher.interface?.setup();
2924
+ }
2925
+ };
2926
+
2927
+ // src/commands/run.ts
2928
+ var command3 = "run <configPath>";
2929
+ var desc3 = "Run your WDIO configuration file to initialize your tests. (default)";
2930
+ var coerceOpts = (opts) => {
2931
+ for (const key in opts) {
2932
+ if (opts[key] === "true") {
2933
+ opts[key] = true;
2934
+ } else if (opts[key] === "false") {
2935
+ opts[key] = false;
2936
+ }
2937
+ }
2938
+ return opts;
2939
+ };
2940
+ var cmdArgs3 = {
2941
+ watch: {
2942
+ desc: "Run WebdriverIO in watch mode",
2943
+ type: "boolean"
2944
+ },
2945
+ hostname: {
2946
+ alias: "h",
2947
+ desc: "automation driver host address",
2948
+ type: "string"
2949
+ },
2950
+ port: {
2951
+ alias: "p",
2952
+ desc: "automation driver port",
2953
+ type: "number"
2954
+ },
2955
+ path: {
2956
+ type: "string",
2957
+ desc: 'path to WebDriver endpoints (default "/")'
2958
+ },
2959
+ user: {
2960
+ alias: "u",
2961
+ desc: "username if using a cloud service as automation backend",
2962
+ type: "string"
2963
+ },
2964
+ key: {
2965
+ alias: "k",
2966
+ desc: "corresponding access key to the user",
2967
+ type: "string"
2968
+ },
2969
+ logLevel: {
2970
+ alias: "l",
2971
+ desc: "level of logging verbosity",
2972
+ choices: ["trace", "debug", "info", "warn", "error", "silent"]
2973
+ },
2974
+ bail: {
2975
+ desc: "stop test runner after specific amount of tests have failed",
2976
+ type: "number"
2977
+ },
2978
+ baseUrl: {
2979
+ desc: "shorten url command calls by setting a base url",
2980
+ type: "string"
2981
+ },
2982
+ waitforTimeout: {
2983
+ alias: "w",
2984
+ desc: "timeout for all waitForXXX commands",
2985
+ type: "number"
2986
+ },
2987
+ updateSnapshots: {
2988
+ alias: "s",
2989
+ desc: "update DOM, image or test snapshots",
2990
+ type: "string",
2991
+ coerce: (value) => {
2992
+ if (value === "") {
2993
+ return "all";
2994
+ }
2995
+ return value;
2996
+ }
2997
+ },
2998
+ framework: {
2999
+ alias: "f",
3000
+ desc: "defines the framework (Mocha, Jasmine or Cucumber) to run the specs",
3001
+ type: "string"
3002
+ },
3003
+ reporters: {
3004
+ alias: "r",
3005
+ desc: "reporters to print out the results on stdout",
3006
+ type: "array"
3007
+ },
3008
+ suite: {
3009
+ desc: "overwrites the specs attribute and runs the defined suite",
3010
+ type: "array"
3011
+ },
3012
+ spec: {
3013
+ desc: "run only a certain spec file - overrides specs piped from stdin",
3014
+ type: "array"
3015
+ },
3016
+ exclude: {
3017
+ desc: "exclude certain spec file from the test run - overrides exclude piped from stdin",
3018
+ type: "array"
3019
+ },
3020
+ "repeat": {
3021
+ desc: "Repeat specific specs and/or suites N times",
3022
+ type: "number"
3023
+ },
3024
+ mochaOpts: {
3025
+ desc: "Mocha options",
3026
+ coerce: coerceOpts
3027
+ },
3028
+ jasmineOpts: {
3029
+ desc: "Jasmine options",
3030
+ coerce: coerceOpts
3031
+ },
3032
+ cucumberOpts: {
3033
+ desc: "Cucumber options",
3034
+ coerce: coerceOpts
3035
+ },
3036
+ coverage: {
3037
+ desc: "Enable coverage for browser runner"
3038
+ },
3039
+ shard: {
3040
+ desc: "Shard tests and execute only the selected shard. Specify in the one-based form like `--shard x/y`, where x is the current and y the total shard.",
3041
+ coerce: (shard) => {
3042
+ const [current, total] = shard.split("/").map(Number);
3043
+ if (Number.isNaN(current) || Number.isNaN(total)) {
3044
+ throw new Error("Shard parameter must be in the form `x/y`, where x and y are positive integers.");
3045
+ }
3046
+ return { current, total };
3047
+ }
3048
+ }
3049
+ };
3050
+ var builder3 = (yargs2) => {
3051
+ return yargs2.options(cmdArgs3).example("$0 run wdio.conf.js --suite foobar", 'Run suite on testsuite "foobar"').example("$0 run wdio.conf.js --spec ./tests/e2e/a.js --spec ./tests/e2e/b.js", "Run suite on specific specs").example("$0 run wdio.conf.js --shard 1/4", "Run only the first shard of 4 shards").example("$0 run wdio.conf.js --mochaOpts.timeout 60000", "Run suite with custom Mocha timeout").example("$0 run wdio.conf.js --tsConfigPath=./configs/bdd-tsconfig.json", "Run suite with tsx using custom tsconfig.json").epilogue(CLI_EPILOGUE).help();
3052
+ };
3053
+ function launchWithStdin(wdioConfPath, params) {
3054
+ let stdinData = "";
3055
+ process.stdin.resume();
3056
+ const stdin = process.stdin;
3057
+ stdin.setEncoding("utf8");
3058
+ stdin.on("data", (data) => {
3059
+ stdinData += data;
3060
+ });
3061
+ stdin.on("end", () => {
3062
+ if (stdinData.length > 0) {
3063
+ params.spec = stdinData.trim().split(/\r?\n/);
3064
+ }
3065
+ launch(wdioConfPath, params);
3066
+ });
3067
+ }
3068
+ async function launch(wdioConfPath, params) {
3069
+ const launcher = new launcher_default(wdioConfPath, params);
3070
+ return launcher.run().then((...args) => {
3071
+ if (!process.env.VITEST_WORKER_ID) {
3072
+ process.exit(...args);
3073
+ }
3074
+ }).catch((err) => {
3075
+ console.error(err);
3076
+ if (!process.env.VITEST_WORKER_ID) {
3077
+ process.exit(1);
3078
+ }
3079
+ });
3080
+ }
3081
+ var NodeVersion = /* @__PURE__ */ ((NodeVersion2) => {
3082
+ NodeVersion2[NodeVersion2["major"] = 0] = "major";
3083
+ NodeVersion2[NodeVersion2["minor"] = 1] = "minor";
3084
+ NodeVersion2[NodeVersion2["patch"] = 2] = "patch";
3085
+ return NodeVersion2;
3086
+ })(NodeVersion || {});
3087
+ function nodeVersion(type) {
3088
+ return process.versions.node.split(".").map(Number)[NodeVersion[type]];
3089
+ }
3090
+ async function handler3(argv) {
3091
+ const { configPath = "wdio.conf.js", ...params } = argv;
3092
+ const wdioConf = await formatConfigFilePaths(configPath);
3093
+ const confAccess = await canAccessConfigPath(wdioConf.fullPathNoExtension);
3094
+ if (!confAccess) {
3095
+ try {
3096
+ await missingConfigurationPrompt("run", wdioConf.fullPathNoExtension);
3097
+ } catch {
3098
+ process.exit(1);
3099
+ }
3100
+ }
3101
+ const nodePath = process.argv[0];
3102
+ let NODE_OPTIONS = process.env.NODE_OPTIONS || "";
3103
+ const isTSFile = wdioConf.fullPath.endsWith(".ts") || wdioConf.fullPath.endsWith(".mts") || confAccess?.endsWith(".ts") || confAccess?.endsWith(".mts");
3104
+ const runsWithLoader = Boolean(
3105
+ process.argv.find((arg) => arg.startsWith("--import") || arg.startsWith("--loader")) && process.argv.find((arg) => arg.endsWith("tsx"))
3106
+ ) || NODE_OPTIONS?.includes("tsx");
3107
+ if (isTSFile && !runsWithLoader && nodePath) {
3108
+ const moduleLoaderFlag = nodeVersion("major") >= 20 && nodeVersion("minor") >= 6 || nodeVersion("major") === 18 && nodeVersion("minor") >= 19 ? "--import" : "--loader";
3109
+ NODE_OPTIONS += ` ${moduleLoaderFlag} tsx`;
3110
+ const tsConfigPathFromEnvVar = process.env.TSCONFIG_PATH && path5.resolve(process.cwd(), process.env.TSCONFIG_PATH) || process.env.TSX_TSCONFIG_PATH && path5.resolve(process.cwd(), process.env.TSX_TSCONFIG_PATH);
3111
+ const tsConfigPathFromParams = params.tsConfigPath && path5.resolve(process.cwd(), params.tsConfigPath);
3112
+ const tsConfigPathRelativeToWdioConfig = path5.join(path5.dirname(wdioConf.fullPath), "tsconfig.json");
3113
+ if (tsConfigPathFromParams) {
3114
+ console.log("Deprecated: use the TSCONFIG_PATH environment variable instead");
3115
+ }
3116
+ const localTSConfigPath = tsConfigPathFromEnvVar || tsConfigPathFromParams || tsConfigPathRelativeToWdioConfig;
3117
+ const hasLocalTSConfig = await fs5.access(localTSConfigPath).then(() => true, () => false);
3118
+ const p = await execa2(nodePath, process.argv.slice(1), {
3119
+ reject: false,
3120
+ cwd: process.cwd(),
3121
+ stdio: "inherit",
3122
+ env: {
3123
+ ...process.env,
3124
+ ...hasLocalTSConfig ? { TSX_TSCONFIG_PATH: localTSConfigPath } : {},
3125
+ NODE_OPTIONS
3126
+ }
3127
+ });
3128
+ return !process.env.VITEST_WORKER_ID && process.exit(p.exitCode);
3129
+ }
3130
+ if (params.watch) {
3131
+ const watcher = new Watcher(wdioConf.fullPath, params);
3132
+ return watcher.watch();
3133
+ }
3134
+ if (process.stdin.isTTY || !process.stdout.isTTY) {
3135
+ return launch(wdioConf.fullPath, params);
3136
+ }
3137
+ launchWithStdin(wdioConf.fullPath, params);
3138
+ }
3139
+
3140
+ // src/commands/repl.ts
3141
+ var IGNORED_ARGS = [
3142
+ "bail",
3143
+ "framework",
3144
+ "reporters",
3145
+ "suite",
3146
+ "spec",
3147
+ "exclude",
3148
+ "mochaOpts",
3149
+ "jasmineOpts",
3150
+ "cucumberOpts"
3151
+ ];
3152
+ var command4 = "repl <option> [capabilities]";
3153
+ var desc4 = "Run WebDriver session in command line";
3154
+ var cmdArgs4 = {
3155
+ platformVersion: {
3156
+ alias: "v",
3157
+ desc: "Version of OS for mobile devices",
3158
+ type: "string"
3159
+ },
3160
+ deviceName: {
3161
+ alias: "d",
3162
+ desc: "Device name for mobile devices",
3163
+ type: "string"
3164
+ },
3165
+ udid: {
3166
+ alias: "u",
3167
+ desc: "UDID of real mobile devices",
3168
+ type: "string"
3169
+ }
3170
+ };
3171
+ var builder4 = (yargs2) => {
3172
+ return yargs2.options(pickBy3({ ...cmdArgs4, ...cmdArgs3 }, (_, key) => !IGNORED_ARGS.includes(key))).example("$0 repl firefox --path /", "Run repl locally").example("$0 repl chrome -u <SAUCE_USERNAME> -k <SAUCE_ACCESS_KEY>", "Run repl in Sauce Labs cloud").example("$0 repl android", "Run repl browser on launched Android device").example('$0 repl "./path/to/your_app.app"', "Run repl native app on iOS simulator").example('$0 repl ios -v 11.3 -d "iPhone 7" -u 123432abc', "Run repl browser on iOS device with capabilities").example('$0 repl "./path/to/wdio.config.js" 0 -p 9515', "Run repl using the first capability from the capabilty array in wdio.config.js").example('$0 repl "./path/to/wdio.config.js" "myChromeBrowser" -p 9515', "Run repl using a named multiremote capabilities in wdio.config.js").epilogue(CLI_EPILOGUE).help();
3173
+ };
3174
+ var handler4 = async (argv) => {
3175
+ const caps = await getCapabilities(argv);
3176
+ const client = await remote({ ...argv, ...caps });
3177
+ global.$ = client.$.bind(client);
3178
+ global.$$ = client.$$.bind(client);
3179
+ global.browser = client;
3180
+ await client.debug();
3181
+ return client.deleteSession();
3182
+ };
3183
+
3184
+ // src/commands/index.ts
3185
+ var commands = [config_exports, install_exports, repl_exports, run_exports];
3186
+
3187
+ // src/run.ts
3188
+ var DEFAULT_CONFIG_FILENAME = "wdio.conf.js";
3189
+ var DESCRIPTION = [
3190
+ "The `wdio` command allows you run and manage your WebdriverIO test suite.",
3191
+ "If no command is provided it calls the `run` command by default, so:",
3192
+ "",
3193
+ "$ wdio wdio.conf.js",
3194
+ "",
3195
+ "is the same as:",
3196
+ "$ wdio run wdio.conf.js",
3197
+ "",
3198
+ "For more information, visit: https://webdriver.io/docs/clioptions"
3199
+ ];
3200
+ async function run() {
3201
+ const argv = yargs(hideBin(process.argv)).command(commands).example("wdio run wdio.conf.js --suite foobar", 'Run suite on testsuite "foobar"').example("wdio run wdio.conf.js --spec ./tests/e2e/a.js --spec ./tests/e2e/b.js", "Run suite on specific specs").example("wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5", "Run scenario by line number").example("wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5:10", "Run scenarios by line number").example("wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5:10 --spec ./test/e2e/b.feature", "Run scenarios by line number in single feature and another complete feature").example("wdio install reporter spec", "Install @wdio/spec-reporter").example("wdio repl chrome -u <SAUCE_USERNAME> -k <SAUCE_ACCESS_KEY>", "Run repl in Sauce Labs cloud").updateStrings({ "Commands:": `${DESCRIPTION.join("\n")}
3202
+
3203
+ Commands:` }).version(pkg.version).epilogue(CLI_EPILOGUE);
3204
+ if (!process.argv.find((arg) => arg === "--help")) {
3205
+ argv.options(cmdArgs3);
3206
+ }
3207
+ const params = await argv.parse();
3208
+ if (!params._ || params._.find((param) => SUPPORTED_COMMANDS.includes(param))) {
3209
+ return;
3210
+ }
3211
+ const args = {
3212
+ ...params,
3213
+ configPath: path6.resolve(process.cwd(), params._[0] && params._[0].toString() || DEFAULT_CONFIG_FILENAME)
3214
+ };
3215
+ try {
3216
+ const cp = await handler3(args);
3217
+ return cp;
3218
+ } catch (err) {
3219
+ const output = await new Promise(
3220
+ (resolve2) => yargs(hideBin(process.argv)).parse("--help", (err2, argv2, output2) => resolve2(output2))
3221
+ );
3222
+ console.error(`${output}
3223
+
3224
+ ${err.stack}`);
3225
+ if (!process.env.VITEST_WORKER_ID) {
3226
+ process.exit(1);
3227
+ }
3228
+ }
3229
+ }
3230
+ export {
3231
+ launcher_default as Launcher,
3232
+ run
3233
+ };