tftsr-devops_investigation/node_modules/create-wdio/build/index.js
Shaun Arman 8839075805 feat: initial implementation of TFTSR IT Triage & RCA application
Implements Phases 1-8 of the TFTSR implementation plan.

Rust backend (Tauri 2.x, src-tauri/):
- Multi-provider AI: OpenAI-compatible, Anthropic, Gemini, Mistral, Ollama
- PII detection engine: 11 regex patterns with overlap resolution
- SQLCipher AES-256 encrypted database with 10 versioned migrations
- 28 Tauri IPC commands for triage, analysis, document, and system ops
- Ollama: hardware probe, model recommendations, pull/delete with events
- RCA and blameless post-mortem Markdown document generators
- PDF export via printpdf
- Audit log: SHA-256 hash of every external data send
- Integration stubs for Confluence, ServiceNow, Azure DevOps (v0.2)

Frontend (React 18 + TypeScript + Vite, src/):
- 9 pages: full triage workflow NewIssue→LogUpload→Triage→Resolution→RCA→Postmortem→History+Settings
- 7 components: ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI primitives
- 3 Zustand stores: session, settings (persisted), history
- Type-safe tauriCommands.ts matching Rust backend types exactly
- 8 IT domain system prompts (Linux, Windows, Network, K8s, DB, Virt, HW, Obs)

DevOps:
- .woodpecker/test.yml: rustfmt, clippy, cargo test, tsc, vitest on every push
- .woodpecker/release.yml: linux/amd64 + linux/arm64 builds, Gogs release upload

Verified:
- cargo check: zero errors
- tsc --noEmit: zero errors
- vitest run: 13/13 unit tests passing

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 22:36:25 -05:00

1025 lines
39 KiB
JavaScript

// src/index.ts
import fs3 from "node:fs/promises";
import path3 from "node:path";
import { pathToFileURL } from "node:url";
import { execSync as execSync2 } from "node:child_process";
import chalk3 from "chalk";
import semver from "semver";
import { Command } from "commander";
import { resolve } from "import-meta-resolve";
// src/utils.ts
import url from "node:url";
import path2 from "node:path";
import util, { promisify } from "node:util";
import fs2 from "node:fs/promises";
import { execSync } from "node:child_process";
import readDir from "recursive-readdir";
import { $ } from "execa";
import { readPackageUp } from "read-pkg-up";
import inquirer from "inquirer";
import ejs from "ejs";
import spawn from "cross-spawn";
// package.json
var package_default = {
name: "create-wdio",
version: "9.21.0",
description: "Install and setup a WebdriverIO project with all its dependencies in a single run",
author: "WebdriverIO Team <mail@webdriver.io>",
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/create-wdio",
license: "MIT",
type: "module",
exports: {
".": {
types: "./build/index.d.ts",
import: "./build/index.js",
importSource: "./src/index.ts"
},
"./config/cli": {
types: "./build/config/cli.d.ts",
import: "./build/config/cli.js",
importSource: "./src/config/cli.ts"
},
"./install/cli": {
types: "./build/install/cli.d.ts",
import: "./build/install/cli.js",
importSource: "./src/install/cli.ts"
},
"./utils": {
types: "./build/utils/index.d.ts",
import: "./build/utils/index.js",
importSource: "./src/utils/index.ts"
}
},
types: "./build/index.d.ts",
typeScriptVersion: "3.8.3",
engines: {
node: ">=12.0.0"
},
repository: {
type: "git",
url: "git+https://github.com/webdriverio/webdriverio.git",
directory: "packages/create-wdio"
},
keywords: [
"webdriverio",
"create-wdio",
"wdio",
"installer",
"e2e"
],
bugs: {
url: "https://github.com/webdriverio/webdriverio/issues"
},
bin: {
"create-wdio": "./bin/wdio.js"
},
dependencies: {
chalk: "^5.3.0",
commander: "^14.0.0",
"cross-spawn": "^7.0.3",
ejs: "^3.1.10",
execa: "^9.6.0",
"import-meta-resolve": "^4.1.0",
inquirer: "^12.7.0",
"normalize-package-data": "^7.0.0",
"read-pkg-up": "^10.1.0",
"recursive-readdir": "^2.2.3",
semver: "^7.6.3",
"type-fest": "^4.41.0",
yargs: "^17.7.2"
},
devDependencies: {
"@types/cross-spawn": "^6.0.6",
"@types/ejs": "^3.1.5",
"@types/fs-extra": "^11.0.4",
"@types/normalize-package-data": "^2.4.4",
"@types/semver": "^7.5.8",
"@types/yargs": "^17.0.33"
},
publishConfig: {
access: "public"
}
};
// src/constants.ts
import path from "node:path";
import fs from "node:fs";
import chalk from "chalk";
var colorItBold = chalk.bold.rgb(234, 89, 6);
var colorIt = chalk.rgb(234, 89, 6);
var DEFAULT_NPM_TAG = "latest";
var ASCII_ROBOT = `
-:...........................-:.
+ +
\`\` + \`...\` \`...\` + \`
./+/ + .:://:::\` \`::///::\` \` + ++/.
.+oo+ + /:+ooo+-/ /-+ooo+-/ ./ + +oo+.
-ooo+ + /-+ooo+-/ /-+ooo+-/ .: + +ooo.
-+o+ + \`::///:-\` \`::///::\` + +o+-
\`\`. /. \`\`\`\`\` \`\`\`\`\` .: .\`\`
.----------------------------.
\`-::::::::::::::::::::::::::::::::::::::::-\`
.+oooo/:------------------------------:/oooo+.
\`.--/oooo- :oooo/--.\`
.::-\`\`:oooo\` .oooo-\`\`-::.
./-\` -oooo\`--.: :.-- .oooo- \`-/.
-/\` \`-/oooo////////////////////////////////////oooo/.\` \`/-
\`+\` \`/+oooooooooooooooooooooooooooooooooooooooooooooooo+:\` .+\`
-/ +o/.:oooooooooooooooooooooooooooooooooooooooooooo:-/o/ +.
-/ .o+ -oooosoooososssssooooo------------------:oooo- \`oo\` +.
-/ .o+ -oooodooohyyssosshoooo\` .oooo- oo. +.
-/ .o+ -oooodooysdooooooyyooo\` \`.--.\`\` .:::-oooo- oo. +.
-/ .o+ -oooodoyyodsoooooyyooo.//-..-:/:.\`.//.\`./oooo- oo. +.
-/ .o+ -oooohsyoooyysssysoooo+-\` \`-:::. .oooo- oo. +.
-/ .o+ -ooooosooooooosooooooo+//////////////////oooo- oo. +.
-/ .o+ -oooooooooooooooooooooooooooooooooooooooooooo- oo. +.
-/ .o+ -oooooooooooooooooooooooooooooooooooooooooooo- oo. +.
-+////o+\` -oooo---:///:----://::------------------:oooo- \`oo////+-
+ooooooo/\`-oooo\`\`:-\`\`\`.:\`.:.\`.+/- .::::::::::\` .oooo-\`+ooooooo+
oooooooo+\`-oooo\`-- \`/\` .:+ -/-\`/\` .:::::::::: .oooo-.+oooooooo
+-/+://-/ -oooo-\`:\`.o-\`:.:-\`\`\`\`.: .///:\`\`\`\`\`\` -oooo-\`/-//:+:-+
: :..--:-:.+ooo+/://o+/-.-:////:-....-::::-....--/+ooo+.:.:--.-- /
- /./\`-:-\` .:///+/ooooo/+///////////////+++ooooo/+///:. .-:.\`+./ :
:-:/. :\`ooooo\`/\` .:.ooooo : ./---
:\`ooooo\`/\` .:.ooooo :
:\`ooooo./\` .:-ooooo :
:\`ooooo./\` .:-ooooo :
\`...:-+++++:/. ./:+++++-:...\`
:-.\`\`\`\`\`\`\`\`/../ /.-:\`\`\`\`\`\`\`\`.:-
-/::::::::://:/+ \`+/:+::::::::::+.
:oooooooooooo++/ +++oooooooooooo-
`;
var PROGRAM_TITLE = `
${colorItBold("Webdriver.IO")}
${colorIt("Next-gen browser and mobile automation")}
${colorIt("test framework for Node.js")}
`;
var UNSUPPORTED_NODE_VERSION = `\u26A0\uFE0F Unsupported Node.js Version Error \u26A0\uFE0F
You are using Node.js ${process.version} which is too old to be used with WebdriverIO.
Please update to Node.js v20 to continue.
`;
var INSTALL_COMMAND = {
npm: "install",
pnpm: "add",
yarn: "add",
bun: "install"
};
var EXECUTER = {
npm: "npx",
pnpm: "pnpm",
yarn: "yarn",
bun: "bunx"
};
var EXECUTE_COMMAND = {
npm: "",
pnpm: "exec",
yarn: "exec",
bun: ""
};
var DEV_FLAG = {
npm: "--save-dev",
pnpm: "--save-dev",
yarn: "--dev",
bun: "--dev"
};
var SUPPORTED_PACKAGE_MANAGERS = Object.keys(INSTALL_COMMAND);
var CLI_EPILOGUE = `Documentation: https://webdriver.io
@wdio/cli (v${package_default.version})`;
var SUPPORTED_PACKAGES = {
runner: [
{ name: "E2E Testing - of Web or Mobile Applications", value: "@wdio/local-runner$--$local$--$e2e" },
{ name: "Component or Unit Testing - in the browser\n > https://webdriver.io/docs/component-testing", value: "@wdio/browser-runner$--$browser$--$component" },
{ name: "Desktop Testing - of Electron Applications\n > https://webdriver.io/docs/desktop-testing/electron", value: "@wdio/local-runner$--$local$--$electron" },
{ name: "Desktop Testing - of MacOS Applications\n > https://webdriver.io/docs/desktop-testing/macos", value: "@wdio/local-runner$--$local$--$macos" },
{ name: "VS Code Extension Testing\n > https://webdriver.io/docs/vscode-extension-testing", value: "@wdio/local-runner$--$local$--$vscode" },
{ name: "Roku Testing - of OTT apps running on RokuOS\n > https://webdriver.io/docs/wdio-roku-service", value: "@wdio/local-runner$--$local$--$roku" }
],
framework: [
{ name: "Mocha (https://mochajs.org/)", value: "@wdio/mocha-framework$--$mocha" },
{ name: "Mocha with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$mocha" },
{ name: "Jasmine (https://jasmine.github.io/)", value: "@wdio/jasmine-framework$--$jasmine" },
{ name: "Jasmine with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$jasmine" },
{ name: "Cucumber (https://cucumber.io/)", value: "@wdio/cucumber-framework$--$cucumber" },
{ name: "Cucumber with Serenity/JS (https://serenity-js.org/)", value: "@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$cucumber" }
],
reporter: [
{ name: "spec", value: "@wdio/spec-reporter$--$spec", checked: true },
{ name: "dot", value: "@wdio/dot-reporter$--$dot" },
{ name: "junit", value: "@wdio/junit-reporter$--$junit" },
{ name: "allure", value: "@wdio/allure-reporter$--$allure" },
{ name: "sumologic", value: "@wdio/sumologic-reporter$--$sumologic" },
{ name: "concise", value: "@wdio/concise-reporter$--$concise" },
{ name: "json", value: "@wdio/json-reporter$--$json" },
// external
{ name: "reportportal", value: "wdio-reportportal-reporter$--$reportportal" },
{ name: "video", value: "wdio-video-reporter$--$video" },
{ name: "cucumber-json", value: "wdio-cucumberjs-json-reporter$--$cucumberjs-json" },
{ name: "mochawesome", value: "wdio-mochawesome-reporter$--$mochawesome" },
{ name: "timeline", value: "wdio-timeline-reporter$--$timeline" },
{ name: "html-nice", value: "wdio-html-nice-reporter$--$html-nice" },
{ name: "slack", value: "@moroo/wdio-slack-reporter$--$slack" },
{ name: "teamcity", value: "wdio-teamcity-reporter$--$teamcity" },
{ name: "delta", value: "@delta-reporter/wdio-delta-reporter-service$--$delta" },
{ name: "testrail", value: "@wdio/testrail-reporter$--$testrail" },
{ name: "light", value: "wdio-light-reporter$--$light" },
{ name: "wdio-json-html-reporter", value: "wdio-json-html-reporter$--$jsonhtml" }
],
plugin: [
{ 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" },
{ name: "angular-component-harnesses: support for Angular component test harnesses\n > https://www.npmjs.com/package/@badisi/wdio-harness", value: "@badisi/wdio-harness$--$harness" },
{ 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" }
],
service: [
// internal or community driver services
{ name: "visual", value: "@wdio/visual-service$--$visual" },
{ name: "vite", value: "wdio-vite-service$--$vite" },
{ name: "nuxt", value: "wdio-nuxt-service$--$nuxt" },
{ name: "firefox-profile", value: "@wdio/firefox-profile-service$--$firefox-profile" },
{ name: "gmail", value: "wdio-gmail-service$--$gmail" },
{ name: "sauce", value: "@wdio/sauce-service$--$sauce" },
{ name: "testingbot", value: "@wdio/testingbot-service$--$testingbot" },
{ name: "browserstack", value: "@wdio/browserstack-service$--$browserstack" },
{ name: "lighthouse", value: "@wdio/lighthouse-service$--$lighthouse" },
{ name: "vscode", value: "wdio-vscode-service$--$vscode" },
{ name: "electron", value: "wdio-electron-service$--$electron" },
{ name: "appium", value: "@wdio/appium-service$--$appium" },
// external
{ name: "camera", value: "wdio-camera-service$--$camera" },
{ name: "eslinter-service", value: "wdio-eslinter-service$--$eslinter" },
{ name: "lambdatest", value: "wdio-lambdatest-service$--$lambdatest" },
{ name: "tvlabs", value: "@tvlabs/wdio-service$--$tvlabs" },
{ name: "zafira-listener", value: "wdio-zafira-listener-service$--$zafira-listener" },
{ name: "reportportal", value: "wdio-reportportal-service$--$reportportal" },
{ name: "docker", value: "wdio-docker-service$--$docker" },
{ name: "ui5", value: "wdio-ui5-service$--$ui5" },
{ name: "wiremock", value: "wdio-wiremock-service$--$wiremock" },
{ name: "ng-apimock", value: "wdio-ng-apimock-service$--$ng-apimock" },
{ name: "slack", value: "wdio-slack-service$--$slack" },
{ name: "cucumber-viewport-logger", value: "wdio-cucumber-viewport-logger-service$--$cucumber-viewport-logger" },
{ name: "intercept", value: "wdio-intercept-service$--$intercept" },
{ name: "docker", value: "wdio-docker-service$--$docker" },
{ name: "novus-visual-regression", value: "wdio-novus-visual-regression-service$--$novus-visual-regression" },
{ name: "rerun", value: "wdio-rerun-service$--$rerun" },
{ name: "winappdriver", value: "wdio-winappdriver-service$--$winappdriver" },
{ name: "ywinappdriver", value: "wdio-ywinappdriver-service$--$ywinappdriver" },
{ name: "performancetotal", value: "wdio-performancetotal-service$--$performancetotal" },
{ name: "cleanuptotal", value: "wdio-cleanuptotal-service$--$cleanuptotal" },
{ name: "aws-device-farm", value: "wdio-aws-device-farm-service$--$aws-device-farm" },
{ name: "ms-teams", value: "wdio-ms-teams-service$--$ms-teams" },
{ name: "tesults", value: "wdio-tesults-service$--$tesults" },
{ name: "azure-devops", value: "@gmangiapelo/wdio-azure-devops-service$--$azure-devops" },
{ name: "google-Chat", value: "wdio-google-chat-service$--$google-chat" },
{ name: "qmate-service", value: "@sap_oss/wdio-qmate-service$--$qmate-service" },
{ name: "robonut", value: "wdio-robonut-service$--$robonut" },
{ name: "qunit", value: "wdio-qunit-service$--$qunit" },
{ name: "roku", value: "wdio-roku-service$--$roku" },
{ name: "obsidian", value: "wdio-obsidian-service$--$obsidian" }
]
};
var isNuxtProject = [
path.join(process.cwd(), "nuxt.config.js"),
path.join(process.cwd(), "nuxt.config.ts"),
path.join(process.cwd(), "nuxt.config.mjs"),
path.join(process.cwd(), "nuxt.config.mts")
].map((p) => {
try {
fs.accessSync(p);
return true;
} catch {
return false;
}
}).some(Boolean);
function usesSerenity(answers) {
return answers.framework.includes("serenity-js");
}
var ProtocolOptions = /* @__PURE__ */ ((ProtocolOptions2) => {
ProtocolOptions2["HTTPS"] = "https";
ProtocolOptions2["HTTP"] = "http";
return ProtocolOptions2;
})(ProtocolOptions || {});
var BackendChoice = /* @__PURE__ */ ((BackendChoice2) => {
BackendChoice2["Local"] = "On my local machine";
BackendChoice2["Experitest"] = "In the cloud using Experitest";
BackendChoice2["Saucelabs"] = "In the cloud using Sauce Labs";
BackendChoice2["Browserstack"] = "In the cloud using BrowserStack";
BackendChoice2["OtherVendors"] = "In the cloud using Testingbot or LambdaTest or a different service";
BackendChoice2["Grid"] = "I have my own Selenium cloud";
return BackendChoice2;
})(BackendChoice || {});
var RegionOptions = /* @__PURE__ */ ((RegionOptions2) => {
RegionOptions2["US"] = "us";
RegionOptions2["EU"] = "eu";
return RegionOptions2;
})(RegionOptions || {});
var ElectronBuildToolChoice = /* @__PURE__ */ ((ElectronBuildToolChoice2) => {
ElectronBuildToolChoice2["ElectronForge"] = "Electron Forge (https://www.electronforge.io/)";
ElectronBuildToolChoice2["ElectronBuilder"] = "electron-builder (https://www.electron.build/)";
ElectronBuildToolChoice2["SomethingElse"] = "Something else";
return ElectronBuildToolChoice2;
})(ElectronBuildToolChoice || {});
var SUPPORTED_BROWSER_RUNNER_PRESETS = [
{ name: "Lit (https://lit.dev/)", value: "$--$" },
{ name: "Vue.js (https://vuejs.org/)", value: "@vitejs/plugin-vue$--$vue" },
{ name: "Svelte (https://svelte.dev/)", value: "@sveltejs/vite-plugin-svelte$--$svelte" },
{ name: "SolidJS (https://www.solidjs.com/)", value: "vite-plugin-solid$--$solid" },
{ name: "StencilJS (https://stenciljs.com/)", value: "$--$stencil" },
{ name: "React (https://reactjs.org/)", value: "@vitejs/plugin-react$--$react" },
{ name: "Preact (https://preactjs.com/)", value: "@preact/preset-vite$--$preact" },
{ name: "Other", value: null }
];
function isBrowserRunner(answers) {
return answers.runner === SUPPORTED_PACKAGES.runner[1].value;
}
function getTestingPurpose(answers) {
return convertPackageHashToObject(answers.runner).purpose;
}
var TESTING_LIBRARY_PACKAGES = {
react: "@testing-library/react",
preact: "@testing-library/preact",
vue: "@testing-library/vue",
svelte: "@testing-library/svelte",
solid: "solid-testing-library"
};
var E2E_ENVIRONMENTS = [
{ name: "Web - web applications in the browser", value: "web" },
{ name: "Mobile - native, hybrid and mobile web apps, on Android or iOS", value: "mobile" }
];
var MOBILE_ENVIRONMENTS = [
{ 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" },
{ name: "iOS - applications on iOS, iPadOS, and tvOS\n > using XCTest (https://appium.github.io/appium-xcuitest-driver)", value: "ios" }
];
var BROWSER_ENVIRONMENTS = [
{ name: "Chrome", value: "chrome", checked: true },
{ name: "Firefox", value: "firefox" },
{ name: "Safari", value: "safari" },
{ name: "Microsoft Edge", value: "MicrosoftEdge" }
];
function selectDefaultService(serviceNames) {
serviceNames = Array.isArray(serviceNames) ? serviceNames : [serviceNames];
return SUPPORTED_PACKAGES.service.filter(({ name }) => serviceNames.includes(name)).map(({ value }) => value);
}
function prioServiceOrderFor(serviceNamesParam) {
const serviceNames = Array.isArray(serviceNamesParam) ? serviceNamesParam : [serviceNamesParam];
let services = SUPPORTED_PACKAGES.service;
for (const serviceName of serviceNames) {
const index = services.findIndex(({ name }) => name === serviceName);
services = [services[index], ...services.slice(0, index), ...services.slice(index + 1)];
}
return services;
}
var QUESTIONNAIRE = [{
type: "list",
name: "runner",
message: "What type of testing would you like to do?",
choices: SUPPORTED_PACKAGES.runner
}, {
type: "list",
name: "preset",
message: "Which framework do you use for building components?",
choices: SUPPORTED_BROWSER_RUNNER_PRESETS,
// only ask if there are more than 1 runner to pick from
when: (
/* istanbul ignore next */
isBrowserRunner
)
}, {
type: "confirm",
name: "installTestingLibrary",
message: "Do you like to use Testing Library (https://testing-library.com/) as test utility?",
default: true,
// only ask if there are more than 1 runner to pick from
when: (
/* istanbul ignore next */
(answers) => isBrowserRunner(answers) && /**
* Only show if Testing Library has an add-on for framework
*/
answers.preset && TESTING_LIBRARY_PACKAGES[convertPackageHashToObject(answers.preset).short]
)
}, {
type: "list",
name: "electronBuildTool",
message: "Which tool are you using to build your Electron app?",
choices: Object.values(ElectronBuildToolChoice),
when: (
/* instanbul ignore next */
(answers) => getTestingPurpose(answers) === "electron"
)
}, {
type: "input",
name: "electronAppBinaryPath",
message: "What is the path to the binary of your built Electron app?",
when: (
/* istanbul ignore next */
(answers) => getTestingPurpose(answers) === "electron" && answers.electronBuildTool === "Something else" /* SomethingElse */
)
}, {
type: "list",
name: "backend",
message: "Where is your automation backend located?",
choices: Object.values(BackendChoice),
when: (
/* instanbul ignore next */
(answers) => getTestingPurpose(answers) === "e2e"
)
}, {
type: "list",
name: "e2eEnvironment",
message: "Which environment you would like to automate?",
choices: E2E_ENVIRONMENTS,
default: "web",
when: (
/* istanbul ignore next */
(answers) => getTestingPurpose(answers) === "e2e"
)
}, {
type: "list",
name: "mobileEnvironment",
message: "Which mobile environment you'd like to automate?",
choices: MOBILE_ENVIRONMENTS,
when: (
/* instanbul ignore next */
(answers) => getTestingPurpose(answers) === "e2e" && answers.e2eEnvironment === "mobile"
)
}, {
type: "checkbox",
name: "browserEnvironment",
message: "With which browser should we start?",
choices: BROWSER_ENVIRONMENTS,
when: (
/* instanbul ignore next */
(answers) => getTestingPurpose(answers) === "e2e" && answers.e2eEnvironment === "web"
)
}, {
type: "input",
name: "hostname",
message: "What is the host address of that cloud service?",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.indexOf("different service") > -1
)
}, {
type: "input",
name: "port",
message: "What is the port on which that service is running?",
default: "80",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.indexOf("different service") > -1
)
}, {
type: "input",
name: "expEnvAccessKey",
message: "Access key from Experitest Cloud",
default: "EXPERITEST_ACCESS_KEY",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
)
}, {
type: "input",
name: "expEnvHostname",
message: "Environment variable for cloud url",
default: "example.experitest.com",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
)
}, {
type: "input",
name: "expEnvPort",
message: "Environment variable for port",
default: "443",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Experitest" /* Experitest */
)
}, {
type: "list",
name: "expEnvProtocol",
message: "Choose a protocol for environment variable",
default: "https" /* HTTPS */,
choices: Object.values(ProtocolOptions),
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Experitest" /* Experitest */ && answers.expEnvPort !== "80" && answers.expEnvPort !== "443"
)
}, {
type: "input",
name: "env_user",
message: "Environment variable for username",
default: "LT_USERNAME",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.indexOf("LambdaTest") > -1 && (answers.hostname === "lambdatest.com" || answers.hostname?.endsWith(".lambdatest.com"))
)
}, {
type: "input",
name: "env_key",
message: "Environment variable for access key",
default: "LT_ACCESS_KEY",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.indexOf("LambdaTest") > -1 && (answers.hostname === "lambdatest.com" || answers.hostname?.endsWith(".lambdatest.com"))
)
}, {
type: "input",
name: "env_user",
message: "Environment variable for username",
default: "BROWSERSTACK_USERNAME",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using BrowserStack" /* Browserstack */
)
}, {
type: "input",
name: "env_key",
message: "Environment variable for access key",
default: "BROWSERSTACK_ACCESS_KEY",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using BrowserStack" /* Browserstack */
)
}, {
type: "input",
name: "env_user",
message: "Environment variable for username",
default: "SAUCE_USERNAME",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
)
}, {
type: "input",
name: "env_key",
message: "Environment variable for access key",
default: "SAUCE_ACCESS_KEY",
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
)
}, {
type: "list",
name: "region",
message: "In which region do you want to run your Sauce Labs tests in?",
choices: Object.values(RegionOptions),
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */
)
}, {
type: "confirm",
name: "useSauceConnect",
message: "Are you testing a local application and need Sauce Connect to be set-up?\nRead more on Sauce Connect at: https://docs.saucelabs.com/secure-connections/#sauce-connect-proxy",
default: isNuxtProject,
when: (
/* istanbul ignore next */
(answers) => answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */ && !isNuxtProject
)
}, {
type: "input",
name: "hostname",
message: "What is the IP or URI to your Selenium standalone or grid server?",
default: "localhost",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
)
}, {
type: "input",
name: "port",
message: "What is the port which your Selenium standalone or grid server is running on?",
default: "4444",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
)
}, {
type: "input",
name: "path",
message: "What is the path to your browser driver or grid server?",
default: "/",
when: (
/* istanbul ignore next */
(answers) => answers.backend && answers.backend.toString().indexOf("own Selenium cloud") > -1
)
}, {
type: "list",
name: "framework",
message: "Which framework do you want to use?",
choices: (
/* instanbul ignore next */
(answers) => {
if (isBrowserRunner(answers)) {
return SUPPORTED_PACKAGES.framework.slice(0, 1);
}
if (getTestingPurpose(answers) === "electron") {
return SUPPORTED_PACKAGES.framework.filter(
({ value }) => !value.startsWith("@serenity-js")
);
}
return SUPPORTED_PACKAGES.framework;
}
)
}, {
type: "confirm",
name: "isUsingTypeScript",
message: "Do you want to use Typescript to write tests?",
when: (
/* istanbul ignore next */
(answers) => {
if (answers.preset?.includes("stencil")) {
return false;
}
return true;
}
),
default: (
/* istanbul ignore next */
(answers) => answers.preset?.includes("stencil") || detectCompiler(answers)
)
}, {
type: "confirm",
name: "generateTestFiles",
message: "Do you want WebdriverIO to autogenerate some test files?",
default: true,
when: (
/* istanbul ignore next */
(answers) => {
if (["vscode", "electron", "macos"].includes(getTestingPurpose(answers)) && answers.framework.includes("cucumber")) {
return false;
}
return true;
}
)
}, {
type: "input",
name: "specs",
message: "What should be the location of your spec files?",
default: (
/* istanbul ignore next */
(answers) => {
const pattern = isBrowserRunner(answers) ? "src/**/*.test" : "test/specs/**/*";
return getDefaultFiles(answers, pattern);
}
),
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && /(mocha|jasmine)/.test(answers.framework)
)
}, {
type: "input",
name: "specs",
message: "What should be the location of your feature files?",
default: (answers) => getDefaultFiles(answers, "features/**/*.feature"),
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && answers.framework.includes("cucumber")
)
}, {
type: "input",
name: "stepDefinitions",
message: "What should be the location of your step definitions?",
default: (answers) => getDefaultFiles(answers, "features/step-definitions/steps"),
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && answers.framework.includes("cucumber")
)
}, {
type: "confirm",
name: "usePageObjects",
message: "Do you want to use page objects (https://martinfowler.com/bliki/PageObject.html)?",
default: true,
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && /**
* page objects aren't common for component testing
*/
!isBrowserRunner(answers) && /**
* and also not needed when running VS Code tests since the service comes with
* its own page object implementation, nor when running Electron or MacOS tests
*/
!["vscode", "electron", "macos"].includes(getTestingPurpose(answers)) && /**
* Serenity/JS generates Lean Page Objects by default, so there's no need to ask about it
* See https://serenity-js.org/handbook/web-testing/page-objects-pattern/
*/
!usesSerenity(answers)
)
}, {
type: "input",
name: "pages",
message: "Where are your page objects located?",
default: (
/* istanbul ignore next */
(answers) => answers.framework.match(/(mocha|jasmine)/) ? getDefaultFiles(answers, "test/pageobjects/**/*") : getDefaultFiles(answers, "features/pageobjects/**/*")
),
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && answers.usePageObjects
)
}, {
type: "input",
name: "serenityLibPath",
message: "What should be the location of your Serenity/JS Screenplay Pattern library?",
default: (
/* istanbul ignore next */
async (answers) => {
const projectRootDir = await getProjectRoot(answers);
const specsDir = path.resolve(projectRootDir, path.dirname(answers.specs || "").replace(/\*\*$/, ""));
return path.resolve(specsDir, "..", "serenity");
}
),
when: (
/* istanbul ignore next */
(answers) => answers.generateTestFiles && usesSerenity(answers)
)
}, {
type: "checkbox",
name: "reporters",
message: "Which reporter do you want to use?",
choices: SUPPORTED_PACKAGES.reporter
}, {
type: "checkbox",
name: "plugins",
message: "Do you want to add a plugin to your test setup?",
choices: SUPPORTED_PACKAGES.plugin,
default: []
}, {
type: "confirm",
name: "includeVisualTesting",
message: "Would you like to include Visual Testing to your setup? For more information see https://webdriver.io/docs/visual-testing!",
default: false,
when: (
/* istanbul ignore next */
(answers) => {
return ["e2e", "component"].includes(getTestingPurpose(answers));
}
)
}, {
type: "checkbox",
name: "services",
message: "Do you want to add a service to your test setup?",
choices: (answers) => {
const services = [];
if (answers.backend === "In the cloud using BrowserStack" /* Browserstack */) {
services.push("browserstack");
} else if (answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */) {
services.push("sauce");
}
if (answers.e2eEnvironment === "mobile") {
services.push("appium");
}
if (getTestingPurpose(answers) === "e2e" && isNuxtProject) {
services.push("nuxt");
}
if (getTestingPurpose(answers) === "vscode") {
return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "vscode")];
} else if (getTestingPurpose(answers) === "electron") {
return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "electron")];
} else if (getTestingPurpose(answers) === "macos") {
return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "appium")];
} else if (getTestingPurpose(answers) === "roku") {
return [SUPPORTED_PACKAGES.service.find(({ name }) => name === "roku")];
}
return prioServiceOrderFor(services);
},
default: (answers) => {
const defaultServices = [];
if (answers.backend === "In the cloud using BrowserStack" /* Browserstack */) {
defaultServices.push("browserstack");
} else if (answers.backend === "In the cloud using Sauce Labs" /* Saucelabs */) {
defaultServices.push("sauce");
}
if (answers.e2eEnvironment === "mobile" || getTestingPurpose(answers) === "macos") {
defaultServices.push("appium");
}
if (getTestingPurpose(answers) === "vscode") {
defaultServices.push("vscode");
} else if (getTestingPurpose(answers) === "electron") {
defaultServices.push("electron");
} else if (getTestingPurpose(answers) === "roku") {
defaultServices.push("roku");
}
if (isNuxtProject) {
defaultServices.push("nuxt");
}
if (answers.includeVisualTesting) {
defaultServices.push("visual");
}
return selectDefaultService(defaultServices);
}
}, {
type: "input",
name: "outputDir",
message: "In which directory should the xunit reports get stored?",
default: "./",
when: (
/* istanbul ignore next */
(answers) => answers.reporters.includes("junit")
)
}, {
type: "input",
name: "outputDir",
message: "In which directory should the json reports get stored?",
default: "./",
when: (
/* istanbul ignore next */
(answers) => answers.reporters.includes("json")
)
}, {
type: "input",
name: "outputDir",
message: "In which directory should the mochawesome json reports get stored?",
default: "./",
when: (
/* istanbul ignore next */
(answers) => answers.reporters.includes("mochawesome")
)
}, {
type: "confirm",
name: "npmInstall",
message: () => `Do you want me to run \`${detectPackageManager()} install\``,
default: true
}];
// src/utils.ts
import chalk2 from "chalk";
// src/install.ts
import { execa } from "execa";
// src/utils.ts
var NPM_COMMAND = /^win/.test(process.platform) ? "npm.cmd" : "npm";
var __dirname = path2.dirname(url.fileURLToPath(import.meta.url));
process.on("SIGINT", () => printAndExit(void 0, "SIGINT"));
var TEMPLATE_ROOT_DIR = process.env.WDIO_UNIT_TESTS ? path2.join(__dirname, "templates", "exampleFiles") : path2.join(__dirname, "..", "templates", "exampleFiles");
function runProgram(command, args, options) {
const child = spawn(command, args, { stdio: "inherit", ...options });
return new Promise((resolve2, rejects) => {
let error;
child.on("error", (e) => error = e);
child.on("close", (code, signal) => {
if (code !== 0) {
const errorMessage = error && error.message || `Error calling: ${command} ${args.join(" ")}`;
printAndExit(errorMessage, signal);
return rejects(errorMessage);
}
resolve2();
});
});
}
async function getPackageVersion() {
try {
const pkgJsonPath = path2.join(__dirname, "..", "package.json");
const pkg = JSON.parse((await fs2.readFile(pkgJsonPath)).toString());
return `v${pkg.version}`;
} catch {
}
return "unknown";
}
function printAndExit(error, signal) {
if (signal === "SIGINT") {
console.log("\n\nGoodbye \u{1F44B}");
} else {
console.log(`
\u26A0\uFE0F Ups, something went wrong${error ? `: ${error}` : ""}!`);
}
process.exit(1);
}
function convertPackageHashToObject(pkg, hash = "$--$") {
const [p, short, purpose] = pkg.split(hash);
return { package: p, short, purpose };
}
async function getProjectProps(cwd = process.cwd()) {
try {
const { packageJson, path: packageJsonPath } = await readPackageUp({ cwd }) || {};
if (!packageJson || !packageJsonPath) {
return void 0;
}
return {
esmSupported: packageJson.type === "module" || typeof packageJson.module === "string",
packageJson,
path: path2.dirname(packageJsonPath)
};
} catch {
return void 0;
}
}
async function getProjectRoot(parsedAnswers) {
if (parsedAnswers?.createPackageJSON && parsedAnswers.projectRoot) {
return parsedAnswers.projectRoot;
}
const root = (await getProjectProps())?.path;
if (!root) {
throw new Error("Could not find project root directory with a package.json");
}
return !parsedAnswers || parsedAnswers.projectRootCorrect ? root : parsedAnswers.projectRoot || process.cwd();
}
var renderFile = promisify(ejs.renderFile);
function detectPackageManager() {
if (!process.env.npm_config_user_agent) {
return "npm";
}
const detectedPM = process.env.npm_config_user_agent.split("/")[0].toLowerCase();
const matchedPM = SUPPORTED_PACKAGE_MANAGERS.find((pm) => pm.toLowerCase() === detectedPM);
return matchedPM || "npm";
}
async function getDefaultFiles(answers, pattern) {
const rootDir = await getProjectRoot(answers);
const presetPackage = convertPackageHashToObject(answers.preset || "");
const isJSX = TSX_BASED_FRAMEWORKS.includes(presetPackage.short || "");
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" : ""}`;
return val;
}
var TSX_BASED_FRAMEWORKS = ["react", "preact", "solid", "stencil"];
async function detectCompiler(answers) {
if (answers.createPackageJSON) {
return false;
}
const root = await getProjectRoot(answers);
const hasRootTSConfig = await fs2.access(path2.resolve(root, "tsconfig.json")).then(() => true, () => false);
return hasRootTSConfig;
}
// src/index.ts
var WDIO_COMMAND = "wdio";
var projectDir;
async function run(operation = createWebdriverIO) {
const version = await getPackageVersion();
if (!(process.argv.includes("--version") || process.argv.includes("-v"))) {
console.log(ASCII_ROBOT, PROGRAM_TITLE);
}
const unsupportedNodeVersion = !semver.satisfies(process.version, ">=18.18.0");
if (unsupportedNodeVersion) {
console.log(chalk3.yellow(UNSUPPORTED_NODE_VERSION));
return;
}
const program = new Command(WDIO_COMMAND).version(version, "-v, --version").arguments("[project-path]").usage(`${chalk3.green("[project-path]")} [options]`).action((name) => projectDir = name).option("-t, --npm-tag <tag>", "Which NPM version you like to install, e.g. @next", DEFAULT_NPM_TAG).option("-y, --yes", "will fill in all config defaults without prompting", false).option("-d, --dev", "Install all packages as into devDependencies", true).allowUnknownOption().on("--help", () => console.log()).parse(process.argv);
return operation(program.opts());
}
function detectPackageManager2() {
if (!process.env.npm_config_user_agent) {
return "npm";
}
const detectedPM = process.env.npm_config_user_agent.split("/")[0].toLowerCase();
const matchedPM = SUPPORTED_PACKAGE_MANAGERS.find((pm) => pm.toLowerCase() === detectedPM);
return matchedPM || "npm";
}
async function createWebdriverIO(opts) {
const npmTag = opts.npmTag.startsWith("@") ? opts.npmTag : `@${opts.npmTag}`;
const root = path3.resolve(process.cwd(), projectDir || "");
const pm = detectPackageManager2();
const hasPackageJson = await fs3.access(path3.resolve(root, "package.json")).then(() => true).catch(() => false);
if (!hasPackageJson) {
await fs3.mkdir(root, { recursive: true });
await fs3.writeFile(path3.resolve(root, "package.json"), JSON.stringify({
name: root.substring(root.lastIndexOf(path3.sep) + 1),
type: "module"
}, null, 2));
}
const cliInstalled = await isCLIInstalled(root);
if (!cliInstalled) {
console.log(`
Installing ${chalk3.bold("@wdio/cli")} to initialize project...`);
const args = [INSTALL_COMMAND[pm]];
if (opts.dev) {
args.push(DEV_FLAG[pm]);
}
args.push(`@wdio/cli${npmTag}`);
await runProgram(pm, args, { cwd: root, stdio: "ignore" });
console.log(chalk3.green.bold("\u2714 Success!"));
}
return runProgram(EXECUTER[pm], [
EXECUTE_COMMAND[pm],
WDIO_COMMAND,
"config",
...opts.yes ? ["--yes"] : [],
...opts.npmTag ? ["--npm-tag", opts.npmTag] : []
].filter((i) => !!i), { cwd: root });
}
async function isCLIInstalled(path4) {
try {
resolve("@wdio/cli", pathToFileURL(path4).href);
return true;
} catch {
try {
const output = execSync2("npm ls -g", {
encoding: "utf-8",
stdio: ["ignore", "pipe", "ignore"]
});
if (output.includes("@wdio/cli")) {
return true;
}
} catch {
return false;
}
return false;
}
}
export {
createWebdriverIO,
run
};