1642 lines
63 KiB
JavaScript
1642 lines
63 KiB
JavaScript
|
|
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 <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 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
|
||
|
|
};
|