import module from 'node:module' import { HOOK_DEFINITION } from '@wdio/utils' import type { Options, Services, Reporters } from '@wdio/types' const require = module.createRequire(import.meta.url) const pkgJSON = require('../package.json') export const pkg = pkgJSON export const CLI_EPILOGUE = `Documentation: https://webdriver.io\n@wdio/cli (v${pkg.version})` export const SUPPORTED_COMMANDS = ['run', 'install', 'config', 'repl'] export const ANDROID_CONFIG = { platformName: 'Android', automationName: 'UiAutomator2', deviceName: 'Test' } export const IOS_CONFIG = { platformName: 'iOS', automationName: 'XCUITest', deviceName: 'iPhone Simulator' } const SUPPORTED_SNAPSHOTSTATE_OPTIONS = ['all', 'new', 'none'] as const export const TESTRUNNER_DEFAULTS: Options.Definition = { /** * Define specs for test execution. You can either specify a glob * pattern to match multiple files at once or wrap a glob or set of * paths into an array to run them within a single worker process. */ specs: { type: 'object', validate: (param: string[]) => { if (!Array.isArray(param)) { throw new Error('the "specs" option needs to be a list of strings') } } }, /** * exclude specs from test execution */ exclude: { type: 'object', validate: (param: string[]) => { if (!Array.isArray(param)) { throw new Error('the "exclude" option needs to be a list of strings') } } }, /** * key/value definition of suites (named by key) and a list of specs as value * to specify a specific set of tests to execute */ suites: { type: 'object' }, /** * Project root directory path. */ rootDir: { type: 'string' }, /** * If you only want to run your tests until a specific amount of tests have failed use * bail (default is 0 - don't bail, run all tests). */ bail: { type: 'number', default: 0 }, /** * supported test framework by wdio testrunner */ framework: { type: 'string' }, /** * capabilities of WebDriver sessions */ capabilities: { type: 'object', validate: (param: unknown) => { /** * should be an object */ if (!Array.isArray(param)) { if (typeof param === 'object') { return true } throw new Error('the "capabilities" options needs to be an object or a list of objects') } /** * or an array of objects */ for (const option of param) { if (typeof option === 'object') { // Check does not work recursively continue } throw new Error('expected every item of a list of capabilities to be of type object') } return true }, required: true }, /** * list of reporters to use, a reporter can be either a string or an object with * reporter options, e.g.: * [ * 'dot', * { * name: 'spec', * outputDir: __dirname + '/reports' * } * ] */ reporters: { type: 'object', validate: (param: Reporters.ReporterEntry[]) => { /** * option must be an array */ if (!Array.isArray(param)) { throw new Error('the "reporters" options needs to be a list of strings') } const isValidReporter = (option: string | Function) => ( (typeof option === 'string') || (typeof option === 'function') ) /** * array elements must be: */ for (const option of param) { /** * either a string or a function (custom reporter) */ if (isValidReporter(option as string)) { continue } /** * or an array with the name of the reporter as first element and the options * as second element */ if ( Array.isArray(option) && typeof option[1] === 'object' && isValidReporter(option[0]) ) { continue } throw new Error( 'a reporter should be either a string in the format "wdio--reporter" ' + 'or a function/class. Please see the docs for more information on custom reporters ' + '(https://webdriver.io/docs/customreporter)' ) } return true } }, /** * set of WDIO services to use */ services: { type: 'object', validate: (param: Services.ServiceEntry[]) => { /** * should be an array */ if (!Array.isArray(param)) { throw new Error('the "services" options needs to be a list of strings and/or arrays') } /** * with arrays and/or strings */ for (const option of param) { if (!Array.isArray(option)) { if (typeof option === 'string') { continue } throw new Error('the "services" options needs to be a list of strings and/or arrays') } } return true }, default: [] }, /** * Node arguments to specify when launching child processes */ execArgv: { type: 'object', validate: (param: string[]) => { if (!Array.isArray(param)) { throw new Error('the "execArgv" options needs to be a list of strings') } }, default: [] }, /** * amount of instances to be allowed to run in total */ maxInstances: { type: 'number' }, /** * amount of instances to be allowed to run per capability */ maxInstancesPerCapability: { type: 'number' }, /** * whether or not testrunner should inject `browser`, `$` and `$$` as * global environment variables */ injectGlobals: { type: 'boolean' }, /** * Set to true if you want to update your snapshots. */ updateSnapshots: { type: 'string', default: SUPPORTED_SNAPSHOTSTATE_OPTIONS[1], validate: (param: Options.Testrunner['updateSnapshots']) => { if (param && !SUPPORTED_SNAPSHOTSTATE_OPTIONS.includes(param)) { throw new Error(`the "updateSnapshots" options needs to be one of "${SUPPORTED_SNAPSHOTSTATE_OPTIONS.join('", "')}"`) } } }, /** * Overrides default snapshot path. For example, to store snapshots next to test files. */ resolveSnapshotPath: { type: 'function', validate: (param: Options.Testrunner['resolveSnapshotPath']) => { if (param && typeof param !== 'function') { throw new Error('the "resolveSnapshotPath" options needs to be a function') } } }, /** * The number of times to retry the entire specfile when it fails as a whole */ specFileRetries: { type: 'number', default: 0 }, /** * Delay in seconds between the spec file retry attempts */ specFileRetriesDelay: { type: 'number', default: 0 }, /** * Whether or not retried spec files should be retried immediately or deferred to the end of the queue */ specFileRetriesDeferred: { type: 'boolean', default: true }, /** * whether or not print the log output grouped by test files */ groupLogsByTestSpec: { type: 'boolean', default: false }, /** * list of strings to watch of `wdio` command is called with `--watch` flag */ filesToWatch: { type: 'object', validate: (param: string[]) => { if (!Array.isArray(param)) { throw new Error('the "filesToWatch" option needs to be a list of strings') } } }, shard: { type: 'object', validate: (param: unknown) => { if (typeof param !== 'object') { throw new Error('the "shard" options needs to be an object') } const p = param as { current: number, total: number } if (typeof p.current !== 'number' || typeof p.total !== 'number') { throw new Error('the "shard" option needs to have "current" and "total" properties with number values') } if (p.current < 0 || p.current > p.total) { throw new Error('the "shard.current" value has to be between 0 and "shard.total"') } } }, /** * hooks */ onPrepare: HOOK_DEFINITION, onWorkerStart: HOOK_DEFINITION, onWorkerEnd: HOOK_DEFINITION, before: HOOK_DEFINITION, beforeSession: HOOK_DEFINITION, beforeSuite: HOOK_DEFINITION, beforeHook: HOOK_DEFINITION, beforeTest: HOOK_DEFINITION, afterTest: HOOK_DEFINITION, afterHook: HOOK_DEFINITION, afterSuite: HOOK_DEFINITION, afterSession: HOOK_DEFINITION, after: HOOK_DEFINITION, onComplete: HOOK_DEFINITION, onReload: HOOK_DEFINITION, beforeAssertion: HOOK_DEFINITION, afterAssertion: HOOK_DEFINITION } export const WORKER_GROUPLOGS_MESSAGES = { normalExit: (cid: string) => `\n***** List of steps of WorkerID=[${cid}] *****`, exitWithError: (cid: string) => `\n***** List of steps of WorkerID=[${cid}] that preceded the error above *****` }