var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/cli/config.ts var config_exports = {}; __export(config_exports, { builder: () => builder, canAccessConfigPath: () => canAccessConfigPath, cmdArgs: () => cmdArgs, command: () => command, desc: () => desc, formatConfigFilePaths: () => formatConfigFilePaths, handler: () => handler, missingConfigurationPrompt: () => missingConfigurationPrompt }); import fss from "node:fs"; // src/utils.ts import url from "node:url"; import path from "node:path"; import util, { promisify } from "node:util"; import fs from "node:fs/promises"; import { execSync } from "node:child_process"; import readDir from "recursive-readdir"; import { $ } from "execa"; import { readPackageUp } from "read-pkg-up"; // src/templates/EjsHelpers.ts var EjsHelpers = class { useTypeScript; useEsm; constructor(config) { this.useTypeScript = config.useTypeScript ?? false; this.useEsm = config.useEsm ?? false; } if(condition, trueValue, falseValue = "") { return condition ? trueValue : falseValue; } ifTs = (trueValue, falseValue = "") => this.if(this.useTypeScript, trueValue, falseValue); ifEsm = (trueValue, falseValue = "") => this.if(this.useEsm, trueValue, falseValue); param(name, type) { return this.useTypeScript ? `${name}: ${type}` : name; } returns(type) { return this.useTypeScript ? `: ${type}` : ""; } import(exports, moduleId) { const individualExports = exports.split(",").map((id) => id.trim()); const imports = this.useTypeScript ? individualExports : individualExports.filter((id) => !id.startsWith("type ")); if (!imports.length) { return ""; } const modulePath = this.modulePathFrom(moduleId); return this.useEsm || this.useTypeScript ? `import { ${imports.join(", ")} } from '${modulePath}'` : `const { ${imports.join(", ")} } = require('${modulePath}')`; } modulePathFrom(moduleId) { if (!(moduleId.startsWith(".") && this.useEsm)) { return moduleId; } if (moduleId.endsWith("/") && this.useEsm) { return moduleId + "index.js"; } return moduleId + ".js"; } export(keyword, name) { if (this.useTypeScript) { return `export ${keyword} ${name}`; } if (this.useEsm) { return `export ${keyword} ${name}`; } if (["class", "function"].includes(keyword)) { return `module.exports.${name} = ${keyword} ${name}`; } return `module.exports.${name}`; } }; // src/utils.ts import inquirer from "inquirer"; import ejs from "ejs"; import spawn from "cross-spawn"; import chalk from "chalk"; // src/install.ts import { execa } from "execa"; var installCommand = { npm: "install", pnpm: "add", yarn: "add", bun: "install" }; var devFlag = { npm: "--save-dev", pnpm: "--save-dev", yarn: "--dev", bun: "--dev" }; async function installPackages(cwd, packages, dev) { const pm = detectPackageManager(); const devParam = dev ? devFlag[pm] : ""; console.log("\n"); const p = execa(pm, [installCommand[pm], ...packages, devParam], { cwd, stdout: process.stdout, stderr: process.stderr }); const { stdout, stderr, exitCode } = await p; if (exitCode !== 0) { const cmd = getInstallCommand(pm, packages, dev); const customError = `\u26A0\uFE0F An unknown error happened! Please retry installing dependencies via "${cmd}" Error: ${stderr || stdout || "unknown"}`; console.error(customError); return false; } return true; } function getInstallCommand(pm, packages, dev) { const devParam = dev ? devFlag[pm] : ""; return `${pm} ${installCommand[pm]} ${packages.join(" ")} ${devParam}`; } // src/utils.ts var NPM_COMMAND = /^win/.test(process.platform) ? "npm.cmd" : "npm"; var __dirname = path.dirname(url.fileURLToPath(import.meta.url)); process.on("SIGINT", () => printAndExit(void 0, "SIGINT")); var TEMPLATE_ROOT_DIR = process.env.WDIO_UNIT_TESTS ? path.join(__dirname, "templates", "exampleFiles") : path.join(__dirname, "..", "templates", "exampleFiles"); function runProgram(command2, args, options) { const child = spawn(command2, args, { stdio: "inherit", ...options }); return new Promise((resolve, rejects) => { let error; child.on("error", (e) => error = e); child.on("close", (code, signal) => { if (code !== 0) { const errorMessage = error && error.message || `Error calling: ${command2} ${args.join(" ")}`; printAndExit(errorMessage, signal); return rejects(errorMessage); } resolve(); }); }); } 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 getAnswers(yes) { if (yes) { const ignoredQuestions = ["e2eEnvironment"]; const filteredQuestionaire = QUESTIONNAIRE.filter((question) => !ignoredQuestions.includes(question.name)); const answers2 = {}; for (const question of filteredQuestionaire) { if (question.when && !question.when(answers2)) { continue; } Object.assign(answers2, { [question.name]: typeof question.default !== "undefined" ? typeof question.default === "function" ? await question.default(answers2) : question.default : question.choices && question.choices.length ? typeof question.choices === "function" ? question.choices(answers2)[0].value ? question.choices(answers2)[0].value : question.choices(answers2)[0] : question.choices[0].value ? question.type === "checkbox" ? [question.choices[0].value] : question.choices[0].value : question.choices[0] : {} }); } answers2.isUsingTypeScript = await answers2.isUsingTypeScript; answers2.specs = await answers2.specs; answers2.pages = await answers2.pages; return answers2; } const projectProps = await getProjectProps(process.cwd()); const isProjectExisting = Boolean(projectProps); const nameInPackageJsonIsNotCreateWdioDefault = projectProps?.packageJson?.name !== "my-new-project"; const projectName = projectProps?.packageJson?.name ? ` named "${projectProps.packageJson.name}"` : ""; const projectRootQuestions = [ { type: "confirm", name: "createPackageJSON", default: true, message: `Couldn't find a package.json in "${process.cwd()}" or any of the parent directories, do you want to create one?`, when: !isProjectExisting }, { type: "confirm", name: "projectRootCorrect", default: true, message: `A project${projectName} was detected at "${projectProps?.path}", correct?`, when: isProjectExisting && nameInPackageJsonIsNotCreateWdioDefault }, { type: "input", name: "projectRoot", message: "What is the project root for your test project?", default: projectProps?.path, // only ask if there are more than 1 runner to pick from when: ( /* istanbul ignore next */ (answers2) => isProjectExisting && nameInPackageJsonIsNotCreateWdioDefault && !answers2.projectRootCorrect ) } ]; const answers = await inquirer.prompt(projectRootQuestions); if (answers.createPackageJSON) { answers.projectRoot = process.cwd(); } return inquirer.prompt(QUESTIONNAIRE, answers); } 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: path.dirname(packageJsonPath) }; } catch { return void 0; } } function getPathForFileGeneration(answers, projectRootDir) { const specAnswer = answers.specs || ""; const stepDefinitionAnswer = answers.stepDefinitions || ""; const pageObjectAnswer = answers.pages || ""; const destSpecRootPath = generatePathFromAnswer(specAnswer, projectRootDir).replace(/\*\*$/, ""); const destStepRootPath = generatePathFromAnswer(stepDefinitionAnswer, projectRootDir); const destPageObjectRootPath = answers.usePageObjects ? generatePathFromAnswer(pageObjectAnswer, projectRootDir).replace(/\*\*$/, "") : ""; const destSerenityLibRootPath = usesSerenity(answers) ? path.resolve(projectRootDir, answers.serenityLibPath || "serenity") : ""; const relativePath = answers.generateTestFiles && answers.usePageObjects ? !(convertPackageHashToObject(answers.framework).short === "cucumber") ? path.relative(destSpecRootPath, destPageObjectRootPath) : path.relative(destStepRootPath, destPageObjectRootPath) : ""; return { destSpecRootPath, destStepRootPath, destPageObjectRootPath, destSerenityLibRootPath, relativePath: relativePath.replaceAll(path.sep, "/") }; } function generatePathFromAnswer(answers, projectRootDir) { return path.resolve( projectRootDir, path.dirname(answers) === "." ? path.resolve(answers) : path.dirname(answers) ); } 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(); } async function createPackageJSON(parsedAnswers) { const packageJsonExists = await fs.access(path.resolve(process.cwd(), "package.json")).then(() => true, () => false); if (packageJsonExists) { return; } if (parsedAnswers.createPackageJSON === false) { if (!packageJsonExists) { console.log(`No WebdriverIO configuration found in "${parsedAnswers.wdioConfigPath}"`); return !process.env.WDIO_UNIT_TESTS && process.exit(0); } return; } if (parsedAnswers.createPackageJSON) { console.log(`Creating a ${chalk.bold("package.json")} for the directory...`); await fs.writeFile(path.resolve(process.cwd(), "package.json"), JSON.stringify({ name: "webdriverio-tests", version: "0.0.0", private: true, license: "ISC", type: "module", dependencies: {}, devDependencies: {} }, null, 2)); console.log(chalk.green(chalk.bold("\u2714 Success!\n"))); } } async function setupTypeScript(parsedAnswers) { if (!parsedAnswers.isUsingTypeScript) { return; } if (parsedAnswers.hasRootTSConfig) { return; } console.log("Setting up TypeScript..."); const frameworkPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.framework); const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service)); const serenityTypes = parsedAnswers.serenityAdapter === "jasmine" ? ["jasmine"] : []; const types = [ "node", ...parsedAnswers.framework === "jasmine" ? ["jasmine"] : [], "@wdio/globals/types", ...parsedAnswers.framework === "jasmine" ? ["expect-webdriverio/jasmine"] : ["expect-webdriverio"], ...parsedAnswers.serenityAdapter ? serenityTypes : [frameworkPackage.package], ...parsedAnswers.runner === "browser" ? ["@wdio/browser-runner"] : [], ...servicePackages.filter((service) => ( /** * given that we know that all "official" services have * typescript support we only include them */ service.package.startsWith("@wdio") || /** * also include community maintained packages with known * support for TypeScript */ COMMUNITY_PACKAGES_WITH_TS_SUPPORT.includes(service.package) )).map((service) => service.package) ]; const preset = getPreset(parsedAnswers); const config = { compilerOptions: { // compiler moduleResolution: "node", module: !parsedAnswers.esmSupport ? "commonjs" : "ESNext", target: "es2022", lib: ["es2022", "dom"], types, skipLibCheck: true, // bundler noEmit: true, allowImportingTsExtensions: true, resolveJsonModule: true, isolatedModules: true, // linting strict: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true, ...Object.assign( preset === "lit" ? { experimentalDecorators: true, useDefineForClassFields: false } : {}, preset === "react" ? { jsx: "react-jsx" } : {}, preset === "preact" ? { jsx: "react-jsx", jsxImportSource: "preact" } : {}, preset === "solid" ? { jsx: "preserve", jsxImportSource: "solid-js" } : {}, preset === "stencil" ? { experimentalDecorators: true, jsx: "react", jsxFactory: "h", jsxFragmentFactory: "Fragment" } : {} ) }, 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"] }; if (parsedAnswers.framework === "cucumber") { config.include.push("features"); } await fs.mkdir(path.dirname(parsedAnswers.tsConfigFilePath), { recursive: true }); await fs.writeFile( parsedAnswers.tsConfigFilePath, JSON.stringify(config, null, 4) ); console.log(chalk.green(chalk.bold("\u2714 Success!\n"))); } var SEP = "\n- "; async function npmInstall(parsedAnswers, npmTag) { const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service)); const presetPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.preset || ""); if (parsedAnswers.installTestingLibrary && TESTING_LIBRARY_PACKAGES[presetPackage.short]) { parsedAnswers.packagesToInstall.push( TESTING_LIBRARY_PACKAGES[presetPackage.short], "@testing-library/jest-dom" ); } if (presetPackage.short === "solid") { parsedAnswers.packagesToInstall.push("solid-js"); } if (parsedAnswers.includeVisualTesting) { parsedAnswers.packagesToInstall.push("@wdio/visual-service"); } const preset = getPreset(parsedAnswers); if (preset === "lit") { parsedAnswers.packagesToInstall.push("lit"); } if (preset === "stencil") { parsedAnswers.packagesToInstall.push("@stencil/core"); } if (presetPackage.short === "react") { parsedAnswers.packagesToInstall.push("react"); if (!parsedAnswers.installTestingLibrary) { parsedAnswers.packagesToInstall.push("react-dom"); } } if (parsedAnswers.framework === "jasmine" && parsedAnswers.isUsingTypeScript) { parsedAnswers.packagesToInstall.push("@types/jasmine"); } if (parsedAnswers.isUsingTypeScript) { parsedAnswers.packagesToInstall.push( "@types/node", "@wdio/globals", "expect-webdriverio" ); } if (parsedAnswers.purpose === "macos") { parsedAnswers.packagesToInstall.push("appium-mac2-driver"); } if (parsedAnswers.mobileEnvironment === "android") { parsedAnswers.packagesToInstall.push("appium-uiautomator2-driver"); } if (parsedAnswers.mobileEnvironment === "ios") { parsedAnswers.packagesToInstall.push("appium-xcuitest-driver"); } addServiceDeps(servicePackages, parsedAnswers.packagesToInstall); parsedAnswers.packagesToInstall = specifyVersionIfNeeded(parsedAnswers.packagesToInstall, package_default.version, npmTag); const cwd = await getProjectRoot(parsedAnswers); const pm = detectPackageManager(); if (parsedAnswers.npmInstall) { console.log(`Installing packages using ${pm}:${SEP}${parsedAnswers.packagesToInstall.join(SEP)}`); const success = await installPackages(cwd, parsedAnswers.packagesToInstall, true); if (success) { console.log(chalk.green(chalk.bold("\u2714 Success!\n"))); } } else { const installationCommand = getInstallCommand(pm, parsedAnswers.packagesToInstall, true); console.log(util.format(DEPENDENCIES_INSTALLATION_MESSAGE, installationCommand)); } } function getPreset(parsedAnswers) { const isUsingFramework = typeof parsedAnswers.preset === "string"; return isUsingFramework ? parsedAnswers.preset || "lit" : ""; } function addServiceDeps(names, packages, update = false) { if (names.some(({ short }) => short === "appium")) { const result = execSync("appium --version || echo APPIUM_MISSING", { stdio: "pipe" }).toString().trim(); if (result === "APPIUM_MISSING") { packages.push("appium"); } else if (update) { console.log( "\n=======", "\nUsing globally installed appium", result, "\nPlease add the following to your wdio.conf.js:", "\nappium: { command: 'appium' }", "\n=======\n" ); } } } async function createWDIOConfig(parsedAnswers) { try { console.log("Creating a WebdriverIO config file..."); const tplPath = path.resolve(__dirname, "..", "templates", "wdio.conf.tpl.ejs"); const renderedTpl = await renderFile(tplPath, { answers: parsedAnswers, _: new EjsHelpers({ useEsm: parsedAnswers.esmSupport, useTypeScript: parsedAnswers.isUsingTypeScript }) }); await fs.writeFile(parsedAnswers.wdioConfigPath, renderedTpl); console.log(chalk.green(chalk.bold("\u2714 Success!\n"))); if (parsedAnswers.generateTestFiles) { console.log("Autogenerate test files..."); await generateTestFiles(parsedAnswers); console.log(chalk.green(chalk.bold("\u2714 Success!\n"))); } } catch (err) { throw new Error(`\u26A0\uFE0F Couldn't write config file: ${err.stack}`); } } var renderFile = promisify(ejs.renderFile); async function createWDIOScript(parsedAnswers) { const rootDir = await getProjectRoot(parsedAnswers); const pathToWdioConfig = `./${path.join(".", parsedAnswers.wdioConfigPath.replace(rootDir, ""))}`; const wdioScripts = { "wdio": `wdio run ${pathToWdioConfig}` }; const serenityScripts = { "serenity": "failsafe serenity:clean wdio serenity:report", "serenity:clean": "rimraf target", "wdio": `wdio run ${pathToWdioConfig}`, "serenity:report": "serenity-bdd run" }; const scripts = parsedAnswers.serenityAdapter ? serenityScripts : wdioScripts; for (const [script, command2] of Object.entries(scripts)) { const args = ["pkg", "set", `scripts.${script}=${command2}`]; try { console.log(`Adding ${chalk.bold(`"${script}"`)} script to package.json`); await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir }); } catch (err) { const [preArgs, scriptPath] = args.join(" ").split("="); console.error( `\u26A0\uFE0F Couldn't add script to package.json: "${err.message}", you can add it manually by running: ${NPM_COMMAND} ${preArgs}="${scriptPath}"` ); return false; } } console.log(chalk.green(chalk.bold("\u2714 Success!"))); return true; } async function runAppiumInstaller(parsedAnswers) { if (parsedAnswers.e2eEnvironment !== "mobile") { return; } const answer = await inquirer.prompt({ name: "continueWithAppiumSetup", message: "Continue with Appium setup using appium-installer (https://github.com/AppiumTestDistribution/appium-installer)?", type: "confirm", default: true }); if (!answer.continueWithAppiumSetup) { return console.log( "Ok! You can learn more about setting up mobile environments in the Appium docs at https://appium.io/docs/en/latest/quickstart/" ); } return $({ stdio: "inherit" })`npx appium-installer`; } function getSerenityPackages(answers) { const framework = convertPackageHashToObject(answers.framework); if (framework.package !== "@serenity-js/webdriverio") { return []; } const packages = { cucumber: [ "@cucumber/cucumber", "@serenity-js/cucumber" ], mocha: [ "@serenity-js/mocha", "mocha" ], jasmine: [ "@serenity-js/jasmine", "jasmine" ], common: [ "@serenity-js/assertions", "@serenity-js/console-reporter", "@serenity-js/core", "@serenity-js/rest", "@serenity-js/serenity-bdd", "@serenity-js/web", "npm-failsafe", "rimraf" ] }; if (answers.isUsingTypeScript) { packages.mocha.push("@types/mocha"); packages.jasmine.push("@types/jasmine"); packages.common.push("@types/node"); } return [ ...packages[framework.purpose], ...packages.common ].filter(Boolean).sort(); } 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") ? path.join(rootDir, pattern) : answers?.isUsingTypeScript ? `${path.join(rootDir, pattern)}.ts${isJSX ? "x" : ""}` : `${path.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 fs.access(path.resolve(root, "tsconfig.json")).then(() => true, () => false); return hasRootTSConfig; } var VERSION_REGEXP = /(\d+)\.(\d+)\.(\d+)-(alpha|beta|)\.(\d+)\+(.+)/g; function specifyVersionIfNeeded(packagesToInstall, version, npmTag) { const { value } = version.matchAll(VERSION_REGEXP).next(); const [major, minor, patch, tagName, build] = (value || []).slice(1, -1); return packagesToInstall.map((p) => { if (p.startsWith("@wdio") && p !== "@wdio/visual-service" || ["webdriver", "webdriverio"].includes(p)) { const tag = major && npmTag === "latest" ? `^${major}.${minor}.${patch}-${tagName}.${build}` : npmTag; return `${p}@${tag}`; } return p; }); } async function generateTestFiles(answers) { if (answers.serenityAdapter) { return generateSerenityExamples(answers); } if (answers.runner === "local") { return generateLocalRunnerTestFiles(answers); } return generateBrowserRunnerTestFiles(answers); } async function generateSerenityExamples(answers) { const templateDirectories = Object.entries({ [answers.projectRootDir]: path.join(TEMPLATE_ROOT_DIR, "serenity-js", "common", "config"), [answers.destSpecRootPath]: path.join(TEMPLATE_ROOT_DIR, "serenity-js", answers.serenityAdapter), [answers.destSerenityLibRootPath]: path.join(TEMPLATE_ROOT_DIR, "serenity-js", "common", "serenity") }); await Promise.all(templateDirectories.map(async ([destinationRootDir, templateRootDir]) => { const pathsToTemplates = await readDir(templateRootDir); await Promise.all(pathsToTemplates.map(async (pathToTemplate) => { const extension = answers.isUsingTypeScript ? ".ts" : ".js"; const destination = path.join(destinationRootDir, path.relative(templateRootDir, pathToTemplate)).replace(/\.ejs$/, "").replace(/\.ts$/, extension); const contents = await renderFile( pathToTemplate, { answers, _: new EjsHelpers({ useEsm: answers.esmSupport, useTypeScript: answers.isUsingTypeScript }) } ); await fs.mkdir(path.dirname(destination), { recursive: true }); await fs.writeFile(destination, contents); })); })); } async function generateLocalRunnerTestFiles(answers) { const testFiles = answers.framework === "cucumber" ? [path.join(TEMPLATE_ROOT_DIR, "cucumber")] : [path.join(TEMPLATE_ROOT_DIR, "mochaJasmine")]; if (answers.usePageObjects) { testFiles.push(path.join(TEMPLATE_ROOT_DIR, "pageobjects")); } const files = (await Promise.all(testFiles.map((dirPath) => readDir( dirPath, [(file, stats) => !stats.isDirectory() && !(file.endsWith(".ejs") || file.endsWith(".feature"))] )))).reduce((cur, acc) => [...acc, ...cur], []); await Promise.all(files.map(async (file) => { const renderedTpl = await renderFile(file, { answers }); const isJSX = answers.preset && TSX_BASED_FRAMEWORKS.includes(answers.preset); const fileEnding = (answers.isUsingTypeScript ? ".ts" : ".js") + (isJSX ? "x" : ""); const destPath = (file.endsWith("page.js.ejs") ? path.join(answers.destPageObjectRootPath, path.basename(file)) : file.includes("step_definition") ? path.join(answers.destStepRootPath, path.basename(file)) : path.join(answers.destSpecRootPath, path.basename(file))).replace(/\.ejs$/, "").replace(/\.js$/, fileEnding); await fs.mkdir(path.dirname(destPath), { recursive: true }); await fs.writeFile(destPath, renderedTpl); })); } async function generateBrowserRunnerTestFiles(answers) { const isUsingFramework = typeof answers.preset === "string"; const preset = getPreset(answers); const tplRootDir = path.join(TEMPLATE_ROOT_DIR, "browser"); await fs.mkdir(answers.destSpecRootPath, { recursive: true }); if (isUsingFramework) { const renderedCss = await renderFile(path.join(tplRootDir, "Component.css.ejs"), { answers }); await fs.writeFile(path.join(answers.destSpecRootPath, "Component.css"), renderedCss); } const testExt = `${answers.isUsingTypeScript ? "ts" : "js"}${TSX_BASED_FRAMEWORKS.includes(preset) ? "x" : ""}`; const fileExt = ["svelte", "vue"].includes(preset) ? preset : testExt; if (preset) { const componentOutFileName = `Component.${fileExt}`; const renderedComponent = await renderFile(path.join(tplRootDir, `Component.${preset}.ejs`), { answers }); await fs.writeFile(path.join(answers.destSpecRootPath, componentOutFileName), renderedComponent); } const componentFileName = preset ? `Component.${preset}.test.ejs` : "standalone.test.ejs"; const renderedTest = await renderFile(path.join(tplRootDir, componentFileName), { answers }); await fs.writeFile(path.join(answers.destSpecRootPath, `Component.test.${testExt}`), renderedTest); } async function formatConfigFilePaths(config) { const fullPath = path.isAbsolute(config) ? config : path.join(process.cwd(), config); const fullPathNoExtension = fullPath.substring(0, fullPath.lastIndexOf(path.extname(fullPath))); return { fullPath, fullPathNoExtension }; } // 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 ", 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 path2 from "node:path"; import fs2 from "node:fs"; import chalk2 from "chalk"; var colorItBold = chalk2.bold.rgb(234, 89, 6); var colorIt = chalk2.rgb(234, 89, 6); 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 SUPPORTED_PACKAGE_MANAGERS = Object.keys(INSTALL_COMMAND); var CONFIG_HELPER_INTRO = ` =============================== \u{1F916} WDIO Configuration Wizard \u{1F9D9} =============================== `; 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 configHelperSuccessMessage = ({ projectRootDir, runScript, extraInfo = "" }) => ` \u{1F916} Successfully setup project at ${projectRootDir} \u{1F389} Join our Discord Community Server and instantly find answers to your issues or queries. Or just join and say hi \u{1F44B}! \u{1F517} https://discord.webdriver.io Visit the project on GitHub to report bugs \u{1F41B} or raise feature requests \u{1F4A1}: \u{1F517} https://github.com/webdriverio/webdriverio ${extraInfo} To run your tests, execute: $ cd ${projectRootDir} $ npm run ${runScript} `; var isNuxtProject = [ path2.join(process.cwd(), "nuxt.config.js"), path2.join(process.cwd(), "nuxt.config.ts"), path2.join(process.cwd(), "nuxt.config.mjs"), path2.join(process.cwd(), "nuxt.config.mts") ].map((p) => { try { fs2.accessSync(p); return true; } catch { return false; } }).some(Boolean); var SUPPORTED_CONFIG_FILE_EXTENSION = ["js", "ts", "mjs", "mts", "cjs", "cts"]; var CONFIG_HELPER_SERENITY_BANNER = ` Learn more about Serenity/JS: \u{1F517} https://serenity-js.org/ \u{1F517} https://serenity-js.org/handbook/test-runners/webdriverio/ `; 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 = path2.resolve(projectRootDir, path2.dirname(answers.specs || "").replace(/\*\*$/, "")); return path2.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 }]; var COMMUNITY_PACKAGES_WITH_TS_SUPPORT = [ "wdio-electron-service", "wdio-vscode-service", "wdio-nuxt-service", "wdio-vite-service", "wdio-gmail-service", "wdio-roku-service", "wdio-obsidian-service" ]; var DEPENDENCIES_INSTALLATION_MESSAGE = ` To install dependencies, execute: %s `; // src/cli/utils.ts import path3 from "node:path"; import fs3 from "node:fs/promises"; import inquirer2 from "inquirer"; async function runConfigCommand(parsedAnswers, npmTag) { console.log("\n"); await createPackageJSON(parsedAnswers); await setupTypeScript(parsedAnswers); await npmInstall(parsedAnswers, npmTag); await createWDIOConfig(parsedAnswers); await createWDIOScript(parsedAnswers); console.log( configHelperSuccessMessage({ projectRootDir: parsedAnswers.projectRootDir, runScript: parsedAnswers.serenityAdapter ? "serenity" : "wdio", extraInfo: parsedAnswers.serenityAdapter ? CONFIG_HELPER_SERENITY_BANNER : "" }) ); await runAppiumInstaller(parsedAnswers); } async function missingConfigurationPrompt(command2, configPath, runConfigCmd = runConfigCommand) { const message = `Could not execute "${command2}" due to missing configuration, file "${path3.parse(configPath).name}[.js/.ts]" not found! Would you like to create one?`; const { config } = await inquirer2.prompt({ type: "confirm", name: "config", message, default: false }); if (!config) { console.log(`No WebdriverIO configuration found in "${process.cwd()}"`); if (!process.env.WDIO_UNIT_TESTS) { process.exit(0); } return configPath; } const parsedAnswers = await parseAnswers(false); await runConfigCmd(parsedAnswers, "latest"); return configPath; } var parseAnswers = async function(yes) { console.log(CONFIG_HELPER_INTRO); const answers = await getAnswers(yes); const frameworkPackage = convertPackageHashToObject(answers.framework); const runnerPackage = convertPackageHashToObject(answers.runner || SUPPORTED_PACKAGES.runner[0].value); const servicePackages = answers.services.map((service) => convertPackageHashToObject(service)); const pluginPackages = answers.plugins.map((plugin) => convertPackageHashToObject(plugin)); const serenityPackages = getSerenityPackages(answers); const reporterPackages = answers.reporters.map((reporter) => convertPackageHashToObject(reporter)); const presetPackage = convertPackageHashToObject(answers.preset || ""); const projectProps = await getProjectProps(process.cwd()); const projectRootDir = await getProjectRoot(answers); const packagesToInstall = [ runnerPackage.package, frameworkPackage.package, presetPackage.package, ...reporterPackages.map((reporter) => reporter.package), ...pluginPackages.map((plugin) => plugin.package), ...servicePackages.map((service) => service.package), ...serenityPackages ].filter(Boolean); const hasRootTSConfig = await fs3.access(path3.resolve(projectRootDir, "tsconfig.json")).then(() => true, () => false); const tsConfigFilePath = !hasRootTSConfig ? path3.resolve(projectRootDir, "tsconfig.json") : answers.specs ? path3.resolve( path3.dirname(answers.specs.split(path3.sep).filter((s) => !s.includes("*")).join(path3.sep)), "tsconfig.json" ) : path3.resolve(projectRootDir, `tsconfig.${runnerPackage.short === "local" ? "e2e" : "wdio"}.json`); const parsedPaths = getPathForFileGeneration(answers, projectRootDir); const isUsingTypeScript = answers.isUsingTypeScript; const wdioConfigFilename = `wdio.conf.${isUsingTypeScript ? "ts" : "js"}`; const wdioConfigPath = path3.resolve(projectRootDir, wdioConfigFilename); return { projectName: projectProps?.packageJson.name || "Test Suite", // default values required in templates ...{ usePageObjects: false, installTestingLibrary: false }, ...answers, useSauceConnect: isNuxtProject || answers.useSauceConnect, rawAnswers: answers, runner: runnerPackage.short, preset: presetPackage.short, framework: frameworkPackage.short, purpose: runnerPackage.purpose, serenityAdapter: frameworkPackage.package === "@serenity-js/webdriverio" && frameworkPackage.purpose, reporters: reporterPackages.map(({ short }) => short), plugins: pluginPackages.map(({ short }) => short), services: servicePackages.map(({ short }) => short), specs: answers.specs && `./${path3.relative(projectRootDir, answers.specs).replaceAll(path3.sep, "/")}`, stepDefinitions: answers.stepDefinitions && `./${path3.relative(projectRootDir, answers.stepDefinitions).replaceAll(path3.sep, "/")}`, packagesToInstall, isUsingTypeScript, esmSupport: projectProps && !projectProps.esmSupported ? false : true, isSync: false, _async: "async ", _await: "await ", projectRootDir, destSpecRootPath: parsedPaths.destSpecRootPath, destStepRootPath: parsedPaths.destStepRootPath, destPageObjectRootPath: parsedPaths.destPageObjectRootPath, destSerenityLibRootPath: parsedPaths.destSerenityLibRootPath, relativePath: parsedPaths.relativePath, hasRootTSConfig, tsConfigFilePath, tsProject: `./${path3.relative(projectRootDir, tsConfigFilePath).replaceAll(path3.sep, "/")}`, wdioConfigPath }; }; async function canAccessConfigPath(configPathNoExtension, configPath) { return new Promise((resolve, reject) => { if (configPath) { fs3.access(configPath).then(() => resolve(configPath), reject); } else { reject(); } }).catch(() => Promise.all(SUPPORTED_CONFIG_FILE_EXTENSION.map(async (supportedExtension) => { const configPathWithExtension = `${configPathNoExtension}.${supportedExtension}`; return fs3.access(configPathWithExtension).then(() => configPathWithExtension, () => void 0); })).then( (configFilePaths) => configFilePaths.find(Boolean), () => void 0 )); } // src/cli/config.ts var hasYarnLock = false; try { fss.accessSync("yarn.lock"); hasYarnLock = true; } catch { hasYarnLock = false; } var command = "config"; var desc = "Initialize WebdriverIO and setup configuration in your current project."; var cmdArgs = { yarn: { type: "boolean", desc: "Install packages via Yarn package manager.", default: hasYarnLock }, yes: { alias: "y", desc: "will fill in all config defaults without prompting", type: "boolean", default: false }, npmTag: { alias: "t", desc: "define NPM tag to use for WebdriverIO related packages", type: "string", default: "latest" } }; var builder = (yargs) => { return yargs.options(cmdArgs).epilogue(CLI_EPILOGUE).help(); }; async function handler(argv, runConfigCmd = runConfigCommand) { const parsedAnswers = await parseAnswers(argv.yes); await runConfigCmd(parsedAnswers, argv.npmTag); return { success: true, parsedAnswers, installedPackages: parsedAnswers.packagesToInstall.map((pkg) => pkg.split("--")[0]) }; } export { config_exports as config };