@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.
- package/build/commands/config.d.ts.map +1 -1
- package/build/commands/repl.d.ts.map +1 -1
- package/build/commands/run.d.ts +14 -13
- package/build/commands/run.d.ts.map +1 -1
- package/build/constants.d.ts +83 -10
- package/build/constants.d.ts.map +1 -1
- package/build/index.cjs +46 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.cts.map +1 -0
- package/build/index.js +3233 -4
- package/build/interface.d.ts +0 -1
- package/build/interface.d.ts.map +1 -1
- package/build/launcher.d.ts.map +1 -1
- package/build/run.d.ts.map +1 -1
- package/build/types.d.ts +3 -4
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +7 -11
- package/build/utils.d.ts.map +1 -1
- package/build/watcher.d.ts.map +1 -1
- package/package.json +20 -25
- package/build/cjs/index.d.ts +0 -2
- package/build/cjs/index.d.ts.map +0 -1
- package/build/cjs/index.js +0 -26
- package/build/cjs/package.json +0 -5
- package/build/commands/config.js +0 -197
- package/build/commands/index.js +0 -5
- package/build/commands/install.js +0 -109
- package/build/commands/repl.js +0 -50
- package/build/commands/run.js +0 -262
- package/build/constants.js +0 -909
- package/build/install.js +0 -38
- package/build/interface.js +0 -285
- package/build/launcher.js +0 -513
- package/build/run.js +0 -75
- package/build/templates/EjsHelpers.js +0 -59
- package/build/templates/EjsHelpers.ts +0 -84
- package/build/templates/exampleFiles/browser/Component.css.ejs +0 -121
- package/build/templates/exampleFiles/browser/Component.lit.ejs +0 -154
- package/build/templates/exampleFiles/browser/Component.lit.test.ejs +0 -24
- package/build/templates/exampleFiles/browser/Component.preact.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.preact.test.ejs +0 -59
- package/build/templates/exampleFiles/browser/Component.react.ejs +0 -29
- package/build/templates/exampleFiles/browser/Component.react.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.solid.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.solid.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.stencil.ejs +0 -43
- package/build/templates/exampleFiles/browser/Component.stencil.test.ejs +0 -45
- package/build/templates/exampleFiles/browser/Component.svelte.ejs +0 -47
- package/build/templates/exampleFiles/browser/Component.svelte.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.vue.ejs +0 -34
- package/build/templates/exampleFiles/browser/Component.vue.test.ejs +0 -62
- package/build/templates/exampleFiles/browser/standalone.test.ejs +0 -13
- package/build/templates/exampleFiles/cucumber/features/login.feature +0 -12
- package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +0 -55
- package/build/templates/exampleFiles/mochaJasmine/test.e2e.js.ejs +0 -11
- package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +0 -45
- package/build/templates/exampleFiles/pageobjects/page.js.ejs +0 -17
- package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +0 -20
- package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +0 -1
- package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +0 -41
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +0 -100
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +0 -36
- package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +0 -37
- package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +0 -18
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +0 -23
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +0 -17
- package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +0 -86
- package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +0 -88
- package/build/templates/snippets/afterTest.ejs +0 -20
- package/build/templates/snippets/capabilities.ejs +0 -57
- package/build/templates/snippets/cucumber.ejs +0 -50
- package/build/templates/snippets/electronTest.js.ejs +0 -7
- package/build/templates/snippets/jasmine.ejs +0 -20
- package/build/templates/snippets/macosTest.js.ejs +0 -11
- package/build/templates/snippets/mocha.ejs +0 -14
- package/build/templates/snippets/reporters.ejs +0 -14
- package/build/templates/snippets/serenity.ejs +0 -18
- package/build/templates/snippets/services.ejs +0 -18
- package/build/templates/snippets/testWithPO.js.ejs +0 -22
- package/build/templates/snippets/testWithoutPO.js.ejs +0 -19
- package/build/templates/snippets/vscodeTest.js.ejs +0 -9
- package/build/templates/wdio.conf.tpl.ejs +0 -422
- package/build/types.js +0 -1
- package/build/utils.js +0 -930
- package/build/watcher.js +0 -156
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/index.js
CHANGED
|
@@ -1,4 +1,3233 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
};
|