@wdio/cli 9.0.0-alpha.9 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/config.d.ts.map +1 -1
- package/build/commands/repl.d.ts.map +1 -1
- package/build/commands/run.d.ts +14 -13
- package/build/commands/run.d.ts.map +1 -1
- package/build/constants.d.ts +83 -10
- package/build/constants.d.ts.map +1 -1
- package/build/index.cjs +46 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.cts.map +1 -0
- package/build/index.js +3233 -4
- package/build/interface.d.ts +0 -1
- package/build/interface.d.ts.map +1 -1
- package/build/launcher.d.ts.map +1 -1
- package/build/run.d.ts.map +1 -1
- package/build/types.d.ts +3 -4
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +7 -11
- package/build/utils.d.ts.map +1 -1
- package/build/watcher.d.ts.map +1 -1
- package/package.json +20 -25
- package/build/cjs/index.d.ts +0 -2
- package/build/cjs/index.d.ts.map +0 -1
- package/build/cjs/index.js +0 -26
- package/build/cjs/package.json +0 -5
- package/build/commands/config.js +0 -197
- package/build/commands/index.js +0 -5
- package/build/commands/install.js +0 -109
- package/build/commands/repl.js +0 -50
- package/build/commands/run.js +0 -262
- package/build/constants.js +0 -909
- package/build/install.js +0 -38
- package/build/interface.js +0 -285
- package/build/launcher.js +0 -513
- package/build/run.js +0 -75
- package/build/templates/EjsHelpers.js +0 -59
- package/build/templates/EjsHelpers.ts +0 -84
- package/build/templates/exampleFiles/browser/Component.css.ejs +0 -121
- package/build/templates/exampleFiles/browser/Component.lit.ejs +0 -154
- package/build/templates/exampleFiles/browser/Component.lit.test.ejs +0 -24
- package/build/templates/exampleFiles/browser/Component.preact.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.preact.test.ejs +0 -59
- package/build/templates/exampleFiles/browser/Component.react.ejs +0 -29
- package/build/templates/exampleFiles/browser/Component.react.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.solid.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.solid.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.stencil.ejs +0 -43
- package/build/templates/exampleFiles/browser/Component.stencil.test.ejs +0 -45
- package/build/templates/exampleFiles/browser/Component.svelte.ejs +0 -47
- package/build/templates/exampleFiles/browser/Component.svelte.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.vue.ejs +0 -34
- package/build/templates/exampleFiles/browser/Component.vue.test.ejs +0 -62
- package/build/templates/exampleFiles/browser/standalone.test.ejs +0 -13
- package/build/templates/exampleFiles/cucumber/features/login.feature +0 -12
- package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +0 -55
- package/build/templates/exampleFiles/mochaJasmine/test.e2e.js.ejs +0 -11
- package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +0 -45
- package/build/templates/exampleFiles/pageobjects/page.js.ejs +0 -17
- package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +0 -20
- package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +0 -1
- package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +0 -41
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +0 -100
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +0 -36
- package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +0 -37
- package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +0 -18
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +0 -23
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +0 -17
- package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +0 -86
- package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +0 -88
- package/build/templates/snippets/afterTest.ejs +0 -20
- package/build/templates/snippets/capabilities.ejs +0 -57
- package/build/templates/snippets/cucumber.ejs +0 -50
- package/build/templates/snippets/electronTest.js.ejs +0 -7
- package/build/templates/snippets/jasmine.ejs +0 -20
- package/build/templates/snippets/macosTest.js.ejs +0 -11
- package/build/templates/snippets/mocha.ejs +0 -14
- package/build/templates/snippets/reporters.ejs +0 -14
- package/build/templates/snippets/serenity.ejs +0 -18
- package/build/templates/snippets/services.ejs +0 -18
- package/build/templates/snippets/testWithPO.js.ejs +0 -22
- package/build/templates/snippets/testWithoutPO.js.ejs +0 -19
- package/build/templates/snippets/vscodeTest.js.ejs +0 -9
- package/build/templates/wdio.conf.tpl.ejs +0 -422
- package/build/types.js +0 -1
- package/build/utils.js +0 -930
- package/build/watcher.js +0 -156
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/utils.js
DELETED
|
@@ -1,930 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import util, { promisify } from 'node:util';
|
|
3
|
-
import path, { dirname } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { execSync, spawn } from 'node:child_process';
|
|
6
|
-
import ejs from 'ejs';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import inquirer from 'inquirer';
|
|
9
|
-
import pickBy from 'lodash.pickby';
|
|
10
|
-
import logger from '@wdio/logger';
|
|
11
|
-
import readDir from 'recursive-readdir';
|
|
12
|
-
import { $ } from 'execa';
|
|
13
|
-
import { readPackageUp } from 'read-pkg-up';
|
|
14
|
-
import { resolve } from 'import-meta-resolve';
|
|
15
|
-
import { SevereServiceError } from 'webdriverio';
|
|
16
|
-
import { ConfigParser } from '@wdio/config/node';
|
|
17
|
-
import { CAPABILITY_KEYS } from '@wdio/protocols';
|
|
18
|
-
import { installPackages, getInstallCommand } from './install.js';
|
|
19
|
-
import { ANDROID_CONFIG, CompilerOptions, DEPENDENCIES_INSTALLATION_MESSAGE, IOS_CONFIG, pkg, QUESTIONNAIRE, TESTING_LIBRARY_PACKAGES, COMMUNITY_PACKAGES_WITH_TS_SUPPORT, usesSerenity, PMs, } from './constants.js';
|
|
20
|
-
import { EjsHelpers } from './templates/EjsHelpers.js';
|
|
21
|
-
const log = logger('@wdio/cli:utils');
|
|
22
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const NPM_COMMAND = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
|
|
24
|
-
const VERSION_REGEXP = /(\d+)\.(\d+)\.(\d+)-(alpha|beta|)\.(\d+)\+(.+)/g;
|
|
25
|
-
const TEMPLATE_ROOT_DIR = path.join(__dirname, 'templates', 'exampleFiles');
|
|
26
|
-
export const renderFile = promisify(ejs.renderFile);
|
|
27
|
-
export class HookError extends SevereServiceError {
|
|
28
|
-
origin;
|
|
29
|
-
constructor(message, origin) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.origin = origin;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* run service launch sequences
|
|
36
|
-
*/
|
|
37
|
-
export async function runServiceHook(launcher, hookName, ...args) {
|
|
38
|
-
const start = Date.now();
|
|
39
|
-
return Promise.all(launcher.map(async (service) => {
|
|
40
|
-
try {
|
|
41
|
-
if (typeof service[hookName] === 'function') {
|
|
42
|
-
await service[hookName](...args);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
const message = `A service failed in the '${hookName}' hook\n${err.stack}\n\n`;
|
|
47
|
-
if (err instanceof SevereServiceError || err.name === 'SevereServiceError') {
|
|
48
|
-
return { status: 'rejected', reason: message, origin: hookName };
|
|
49
|
-
}
|
|
50
|
-
log.error(`${message}Continue...`);
|
|
51
|
-
}
|
|
52
|
-
})).then(results => {
|
|
53
|
-
if (launcher.length) {
|
|
54
|
-
log.debug(`Finished to run "${hookName}" hook in ${Date.now() - start}ms`);
|
|
55
|
-
}
|
|
56
|
-
const rejectedHooks = results.filter(p => p && p.status === 'rejected');
|
|
57
|
-
if (rejectedHooks.length) {
|
|
58
|
-
return Promise.reject(new HookError(`\n${rejectedHooks.map(p => p && p.reason).join()}\n\nStopping runner...`, hookName));
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Run hook in service launcher
|
|
64
|
-
* @param {Array|Function} hook - can be array of functions or single function
|
|
65
|
-
* @param {object} config
|
|
66
|
-
* @param {object} capabilities
|
|
67
|
-
*/
|
|
68
|
-
export async function runLauncherHook(hook, ...args) {
|
|
69
|
-
if (typeof hook === 'function') {
|
|
70
|
-
hook = [hook];
|
|
71
|
-
}
|
|
72
|
-
const catchFn = (e) => {
|
|
73
|
-
log.error(`Error in hook: ${e.stack}`);
|
|
74
|
-
if (e instanceof SevereServiceError) {
|
|
75
|
-
throw new HookError(e.message, hook[0].name);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
return Promise.all(hook.map((hook) => {
|
|
79
|
-
try {
|
|
80
|
-
return hook(...args);
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
return catchFn(err);
|
|
84
|
-
}
|
|
85
|
-
})).catch(catchFn);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Run onCompleteHook in Launcher
|
|
89
|
-
* @param {Array|Function} onCompleteHook - can be array of functions or single function
|
|
90
|
-
* @param {*} config
|
|
91
|
-
* @param {*} capabilities
|
|
92
|
-
* @param {*} exitCode
|
|
93
|
-
* @param {*} results
|
|
94
|
-
*/
|
|
95
|
-
export async function runOnCompleteHook(onCompleteHook, config, capabilities, exitCode, results) {
|
|
96
|
-
if (typeof onCompleteHook === 'function') {
|
|
97
|
-
onCompleteHook = [onCompleteHook];
|
|
98
|
-
}
|
|
99
|
-
return Promise.all(onCompleteHook.map(async (hook) => {
|
|
100
|
-
try {
|
|
101
|
-
await hook(exitCode, config, capabilities, results);
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
log.error(`Error in onCompleteHook: ${err.stack}`);
|
|
106
|
-
if (err instanceof SevereServiceError) {
|
|
107
|
-
throw new HookError(err.message, 'onComplete');
|
|
108
|
-
}
|
|
109
|
-
return 1;
|
|
110
|
-
}
|
|
111
|
-
}));
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* get runner identification by caps
|
|
115
|
-
*/
|
|
116
|
-
export function getRunnerName(caps = {}) {
|
|
117
|
-
let runner = caps.browserName ||
|
|
118
|
-
caps.platformName ||
|
|
119
|
-
caps['appium:platformName'] ||
|
|
120
|
-
caps['appium:appPackage'] ||
|
|
121
|
-
caps['appium:appWaitActivity'] ||
|
|
122
|
-
caps['appium:app'];
|
|
123
|
-
// MultiRemote
|
|
124
|
-
if (!runner) {
|
|
125
|
-
runner = Object.values(caps).length === 0 || Object.values(caps).some(cap => !cap.capabilities) ? 'undefined' : 'MultiRemote';
|
|
126
|
-
}
|
|
127
|
-
return runner;
|
|
128
|
-
}
|
|
129
|
-
function buildNewConfigArray(str, type, change) {
|
|
130
|
-
const newStr = str
|
|
131
|
-
.split(`${type}s: `)[1]
|
|
132
|
-
.replace(/'/g, '');
|
|
133
|
-
const newArray = newStr.match(/(\w*)/gmi)?.filter(e => !!e).concat([change]) || [];
|
|
134
|
-
return str
|
|
135
|
-
.replace('// ', '')
|
|
136
|
-
.replace(new RegExp(`(${type}s: )((.*\\s*)*)`), `$1[${newArray.map(e => `'${e}'`)}]`);
|
|
137
|
-
}
|
|
138
|
-
function buildNewConfigString(str, type, change) {
|
|
139
|
-
return str.replace(new RegExp(`(${type}: )('\\w*')`), `$1'${change}'`);
|
|
140
|
-
}
|
|
141
|
-
export function findInConfig(config, type) {
|
|
142
|
-
let regexStr = `[\\/\\/]*[\\s]*${type}s: [\\s]*\\[([\\s]*['|"]\\w*['|"],*)*[\\s]*\\]`;
|
|
143
|
-
if (type === 'framework') {
|
|
144
|
-
regexStr = `[\\/\\/]*[\\s]*${type}: ([\\s]*['|"]\\w*['|"])`;
|
|
145
|
-
}
|
|
146
|
-
const regex = new RegExp(regexStr, 'gmi');
|
|
147
|
-
return config.match(regex);
|
|
148
|
-
}
|
|
149
|
-
export function replaceConfig(config, type, name) {
|
|
150
|
-
if (type === 'framework') {
|
|
151
|
-
return buildNewConfigString(config, type, name);
|
|
152
|
-
}
|
|
153
|
-
const match = findInConfig(config, type);
|
|
154
|
-
if (!match || match.length === 0) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const text = match.pop() || '';
|
|
158
|
-
return config.replace(text, buildNewConfigArray(text, type, name));
|
|
159
|
-
}
|
|
160
|
-
export function addServiceDeps(names, packages, update = false) {
|
|
161
|
-
/**
|
|
162
|
-
* install Appium if it is not installed globally if `@wdio/appium-service`
|
|
163
|
-
* was selected for install
|
|
164
|
-
*/
|
|
165
|
-
if (names.some(({ short }) => short === 'appium')) {
|
|
166
|
-
const result = execSync('appium --version || echo APPIUM_MISSING', { stdio: 'pipe' }).toString().trim();
|
|
167
|
-
if (result === 'APPIUM_MISSING') {
|
|
168
|
-
packages.push('appium');
|
|
169
|
-
}
|
|
170
|
-
else if (update) {
|
|
171
|
-
// eslint-disable-next-line no-console
|
|
172
|
-
console.log('\n=======', '\nUsing globally installed appium', result, '\nPlease add the following to your wdio.conf.js:', "\nappium: { command: 'appium' }", '\n=======\n');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* @todo add JSComments
|
|
178
|
-
*/
|
|
179
|
-
export function convertPackageHashToObject(pkg, hash = '$--$') {
|
|
180
|
-
const [p, short, purpose] = pkg.split(hash);
|
|
181
|
-
return { package: p, short, purpose };
|
|
182
|
-
}
|
|
183
|
-
export function getSerenityPackages(answers) {
|
|
184
|
-
const framework = convertPackageHashToObject(answers.framework);
|
|
185
|
-
if (framework.package !== '@serenity-js/webdriverio') {
|
|
186
|
-
return [];
|
|
187
|
-
}
|
|
188
|
-
const isUsingTypeScript = answers.isUsingCompiler === CompilerOptions.TS;
|
|
189
|
-
const packages = {
|
|
190
|
-
cucumber: [
|
|
191
|
-
'@cucumber/cucumber',
|
|
192
|
-
'@serenity-js/cucumber',
|
|
193
|
-
],
|
|
194
|
-
mocha: [
|
|
195
|
-
'@serenity-js/mocha',
|
|
196
|
-
'mocha',
|
|
197
|
-
isUsingTypeScript && '@types/mocha',
|
|
198
|
-
],
|
|
199
|
-
jasmine: [
|
|
200
|
-
'@serenity-js/jasmine',
|
|
201
|
-
'jasmine',
|
|
202
|
-
isUsingTypeScript && '@types/jasmine',
|
|
203
|
-
],
|
|
204
|
-
common: [
|
|
205
|
-
'@serenity-js/assertions',
|
|
206
|
-
'@serenity-js/console-reporter',
|
|
207
|
-
'@serenity-js/core',
|
|
208
|
-
'@serenity-js/rest',
|
|
209
|
-
'@serenity-js/serenity-bdd',
|
|
210
|
-
'@serenity-js/web',
|
|
211
|
-
isUsingTypeScript && '@types/node',
|
|
212
|
-
'npm-failsafe',
|
|
213
|
-
'rimraf',
|
|
214
|
-
]
|
|
215
|
-
};
|
|
216
|
-
return [
|
|
217
|
-
...packages[framework.purpose],
|
|
218
|
-
...packages.common,
|
|
219
|
-
].filter(Boolean).sort();
|
|
220
|
-
}
|
|
221
|
-
export async function getCapabilities(arg) {
|
|
222
|
-
const optionalCapabilites = {
|
|
223
|
-
platformVersion: arg.platformVersion,
|
|
224
|
-
udid: arg.udid,
|
|
225
|
-
...(arg.deviceName && { deviceName: arg.deviceName })
|
|
226
|
-
};
|
|
227
|
-
/**
|
|
228
|
-
* Parsing of option property and constructing desiredCapabilities
|
|
229
|
-
* for Appium session. Could be application(1) or browser(2-3) session.
|
|
230
|
-
*/
|
|
231
|
-
if (/.*\.(apk|app|ipa)$/.test(arg.option)) {
|
|
232
|
-
return {
|
|
233
|
-
capabilities: {
|
|
234
|
-
app: arg.option,
|
|
235
|
-
...(arg.option.endsWith('apk') ? ANDROID_CONFIG : IOS_CONFIG),
|
|
236
|
-
...optionalCapabilites,
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
else if (/android/.test(arg.option)) {
|
|
241
|
-
return { capabilities: { browserName: 'Chrome', ...ANDROID_CONFIG, ...optionalCapabilites } };
|
|
242
|
-
}
|
|
243
|
-
else if (/ios/.test(arg.option)) {
|
|
244
|
-
return { capabilities: { browserName: 'Safari', ...IOS_CONFIG, ...optionalCapabilites } };
|
|
245
|
-
}
|
|
246
|
-
else if (/(js|ts)$/.test(arg.option)) {
|
|
247
|
-
const config = new ConfigParser(arg.option);
|
|
248
|
-
try {
|
|
249
|
-
await config.initialize();
|
|
250
|
-
}
|
|
251
|
-
catch (e) {
|
|
252
|
-
throw Error(e.code === 'MODULE_NOT_FOUND' ? `Config File not found: ${arg.option}` :
|
|
253
|
-
`Could not parse ${arg.option}, failed with error : ${e.message}`);
|
|
254
|
-
}
|
|
255
|
-
if (typeof arg.capabilities === 'undefined') {
|
|
256
|
-
throw Error('Please provide index/named property of capability to use from the capabilities array/object in wdio config file');
|
|
257
|
-
}
|
|
258
|
-
let requiredCaps = config.getCapabilities();
|
|
259
|
-
requiredCaps = (
|
|
260
|
-
// multi capabilities
|
|
261
|
-
requiredCaps[parseInt(arg.capabilities, 10)] ||
|
|
262
|
-
// multiremote
|
|
263
|
-
requiredCaps[arg.capabilities]);
|
|
264
|
-
const requiredW3CCaps = pickBy(requiredCaps, (_, key) => CAPABILITY_KEYS.includes(key) || key.includes(':'));
|
|
265
|
-
if (!Object.keys(requiredW3CCaps).length) {
|
|
266
|
-
throw Error(`No capability found in given config file with the provided capability indexed/named property: ${arg.capabilities}. Please check the capability in your wdio config file.`);
|
|
267
|
-
}
|
|
268
|
-
return { capabilities: { ...requiredW3CCaps } };
|
|
269
|
-
}
|
|
270
|
-
return { capabilities: { browserName: arg.option } };
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Checks if certain directory has babel configuration files
|
|
274
|
-
* @param rootDir directory where this function checks for Babel signs
|
|
275
|
-
* @returns true, if a babel config was found, otherwise false
|
|
276
|
-
*/
|
|
277
|
-
export function hasBabelConfig(rootDir) {
|
|
278
|
-
return Promise.all([
|
|
279
|
-
fs.access(path.join(rootDir, 'babel.js')),
|
|
280
|
-
fs.access(path.join(rootDir, 'babel.cjs')),
|
|
281
|
-
fs.access(path.join(rootDir, 'babel.mjs')),
|
|
282
|
-
fs.access(path.join(rootDir, '.babelrc'))
|
|
283
|
-
]).then((results) => results.filter(Boolean).length > 1, () => false);
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* detect if project has a compiler file
|
|
287
|
-
*/
|
|
288
|
-
export async function detectCompiler(answers) {
|
|
289
|
-
const root = await getProjectRoot(answers);
|
|
290
|
-
const rootTSConfigExist = await fs.access(path.resolve(root, 'tsconfig.json')).then(() => true, () => false);
|
|
291
|
-
return (await hasBabelConfig(root))
|
|
292
|
-
? CompilerOptions.Babel // default to Babel
|
|
293
|
-
: rootTSConfigExist
|
|
294
|
-
? CompilerOptions.TS // default to TypeScript
|
|
295
|
-
: CompilerOptions.Nil; // default to no compiler
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Check if package is installed
|
|
299
|
-
* @param {string} package to check existance for
|
|
300
|
-
*/
|
|
301
|
-
export async function hasPackage(pkg) {
|
|
302
|
-
try {
|
|
303
|
-
await resolve(pkg, import.meta.url);
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
catch (err) {
|
|
307
|
-
return false;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* generate test files based on CLI answers
|
|
312
|
-
*/
|
|
313
|
-
export async function generateTestFiles(answers) {
|
|
314
|
-
if (answers.serenityAdapter) {
|
|
315
|
-
return generateSerenityExamples(answers);
|
|
316
|
-
}
|
|
317
|
-
if (answers.runner === 'local') {
|
|
318
|
-
return generateLocalRunnerTestFiles(answers);
|
|
319
|
-
}
|
|
320
|
-
return generateBrowserRunnerTestFiles(answers);
|
|
321
|
-
}
|
|
322
|
-
const TSX_BASED_FRAMEWORKS = ['react', 'preact', 'solid', 'stencil'];
|
|
323
|
-
export async function generateBrowserRunnerTestFiles(answers) {
|
|
324
|
-
const isUsingFramework = typeof answers.preset === 'string';
|
|
325
|
-
const preset = getPreset(answers);
|
|
326
|
-
const tplRootDir = path.join(TEMPLATE_ROOT_DIR, 'browser');
|
|
327
|
-
await fs.mkdir(answers.destSpecRootPath, { recursive: true });
|
|
328
|
-
/**
|
|
329
|
-
* render css file
|
|
330
|
-
*/
|
|
331
|
-
if (isUsingFramework) {
|
|
332
|
-
const renderedCss = await renderFile(path.join(tplRootDir, 'Component.css.ejs'), { answers });
|
|
333
|
-
await fs.writeFile(path.join(answers.destSpecRootPath, 'Component.css'), renderedCss);
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* render component file
|
|
337
|
-
*/
|
|
338
|
-
const testExt = `${(answers.isUsingTypeScript ? 'ts' : 'js')}${TSX_BASED_FRAMEWORKS.includes(preset) ? 'x' : ''}`;
|
|
339
|
-
const fileExt = ['svelte', 'vue'].includes(preset)
|
|
340
|
-
? preset
|
|
341
|
-
: testExt;
|
|
342
|
-
if (preset) {
|
|
343
|
-
const componentOutFileName = `Component.${fileExt}`;
|
|
344
|
-
const renderedComponent = await renderFile(path.join(tplRootDir, `Component.${preset}.ejs`), { answers });
|
|
345
|
-
await fs.writeFile(path.join(answers.destSpecRootPath, componentOutFileName), renderedComponent);
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* render test file
|
|
349
|
-
*/
|
|
350
|
-
const componentFileName = preset ? `Component.${preset}.test.ejs` : 'standalone.test.ejs';
|
|
351
|
-
const renderedTest = await renderFile(path.join(tplRootDir, componentFileName), { answers });
|
|
352
|
-
await fs.writeFile(path.join(answers.destSpecRootPath, `Component.test.${testExt}`), renderedTest);
|
|
353
|
-
}
|
|
354
|
-
async function generateLocalRunnerTestFiles(answers) {
|
|
355
|
-
const testFiles = answers.framework === 'cucumber'
|
|
356
|
-
? [path.join(TEMPLATE_ROOT_DIR, 'cucumber')]
|
|
357
|
-
: [path.join(TEMPLATE_ROOT_DIR, 'mochaJasmine')];
|
|
358
|
-
if (answers.usePageObjects) {
|
|
359
|
-
testFiles.push(path.join(TEMPLATE_ROOT_DIR, 'pageobjects'));
|
|
360
|
-
}
|
|
361
|
-
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)], []);
|
|
362
|
-
for (const file of files) {
|
|
363
|
-
const renderedTpl = await renderFile(file, { answers });
|
|
364
|
-
const isJSX = answers.preset && TSX_BASED_FRAMEWORKS.includes(answers.preset);
|
|
365
|
-
const fileEnding = (answers.isUsingTypeScript ? '.ts' : '.js') + (isJSX ? 'x' : '');
|
|
366
|
-
const destPath = (file.endsWith('page.js.ejs')
|
|
367
|
-
? path.join(answers.destPageObjectRootPath, path.basename(file))
|
|
368
|
-
: file.includes('step_definition')
|
|
369
|
-
? path.join(answers.destStepRootPath, path.basename(file))
|
|
370
|
-
: path.join(answers.destSpecRootPath, path.basename(file))).replace(/\.ejs$/, '').replace(/\.js$/, fileEnding);
|
|
371
|
-
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
372
|
-
await fs.writeFile(destPath, renderedTpl);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
async function generateSerenityExamples(answers) {
|
|
376
|
-
const templateDirectories = {
|
|
377
|
-
[answers.projectRootDir]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', 'common', 'config'),
|
|
378
|
-
[answers.destSpecRootPath]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', answers.serenityAdapter),
|
|
379
|
-
[answers.destSerenityLibRootPath]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', 'common', 'serenity'),
|
|
380
|
-
};
|
|
381
|
-
for (const [destinationRootDir, templateRootDir] of Object.entries(templateDirectories)) {
|
|
382
|
-
const pathsToTemplates = await readDir(templateRootDir);
|
|
383
|
-
for (const pathToTemplate of pathsToTemplates) {
|
|
384
|
-
const extension = answers.isUsingTypeScript ? '.ts' : '.js';
|
|
385
|
-
const destination = path.join(destinationRootDir, path.relative(templateRootDir, pathToTemplate))
|
|
386
|
-
.replace(/\.ejs$/, '')
|
|
387
|
-
.replace(/\.ts$/, extension);
|
|
388
|
-
const contents = await renderFile(pathToTemplate, { answers, _: new EjsHelpers({ useEsm: answers.esmSupport, useTypeScript: answers.isUsingTypeScript }) });
|
|
389
|
-
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
390
|
-
await fs.writeFile(destination, contents);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
export async function getAnswers(yes) {
|
|
395
|
-
if (yes) {
|
|
396
|
-
const ignoredQuestions = ['e2eEnvironment'];
|
|
397
|
-
const filterdQuestionaire = QUESTIONNAIRE.filter((question) => !ignoredQuestions.includes(question.name));
|
|
398
|
-
const answers = {};
|
|
399
|
-
for (const question of filterdQuestionaire) {
|
|
400
|
-
/**
|
|
401
|
-
* set nothing if question doesn't apply
|
|
402
|
-
*/
|
|
403
|
-
if (question.when && !question.when(answers)) {
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
Object.assign(answers, {
|
|
407
|
-
[question.name]: typeof question.default !== 'undefined'
|
|
408
|
-
/**
|
|
409
|
-
* set default value if existing
|
|
410
|
-
*/
|
|
411
|
-
? typeof question.default === 'function'
|
|
412
|
-
? await question.default(answers)
|
|
413
|
-
: await question.default
|
|
414
|
-
: question.choices && question.choices.length
|
|
415
|
-
/**
|
|
416
|
-
* pick first choice, select value if it exists
|
|
417
|
-
*/
|
|
418
|
-
? typeof question.choices === 'function'
|
|
419
|
-
? question.choices(answers)[0].value
|
|
420
|
-
? question.choices(answers)[0].value
|
|
421
|
-
: question.choices(answers)[0]
|
|
422
|
-
: question.choices[0].value
|
|
423
|
-
? question.choices[0].value
|
|
424
|
-
: question.choices[0]
|
|
425
|
-
: {}
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* some questions have async defaults
|
|
430
|
-
*/
|
|
431
|
-
answers.isUsingCompiler = await answers.isUsingCompiler;
|
|
432
|
-
answers.specs = await answers.specs;
|
|
433
|
-
answers.pages = await answers.pages;
|
|
434
|
-
return answers;
|
|
435
|
-
}
|
|
436
|
-
const projectProps = await getProjectProps(process.cwd());
|
|
437
|
-
const isProjectExisting = Boolean(projectProps);
|
|
438
|
-
const projectName = projectProps?.packageJson?.name ? ` named "${projectProps.packageJson.name}"` : '';
|
|
439
|
-
const questions = [
|
|
440
|
-
/**
|
|
441
|
-
* in case the `wdio config` was called using a global installed @wdio/cli package
|
|
442
|
-
*/
|
|
443
|
-
...(!isProjectExisting
|
|
444
|
-
? [{
|
|
445
|
-
type: 'confirm',
|
|
446
|
-
name: 'createPackageJSON',
|
|
447
|
-
default: true,
|
|
448
|
-
message: `Couldn't find a package.json in "${process.cwd()}" or any of the parent directories, do you want to create one?`,
|
|
449
|
-
}]
|
|
450
|
-
/**
|
|
451
|
-
* in case create-wdio was used which creates a package.json with name "my-new-project"
|
|
452
|
-
* we don't need to ask this question
|
|
453
|
-
*/
|
|
454
|
-
: projectProps?.packageJson?.name !== 'my-new-project'
|
|
455
|
-
? [{
|
|
456
|
-
type: 'confirm',
|
|
457
|
-
name: 'projectRootCorrect',
|
|
458
|
-
default: true,
|
|
459
|
-
message: `A project${projectName} was detected at "${projectProps?.path}", correct?`,
|
|
460
|
-
}, {
|
|
461
|
-
type: 'input',
|
|
462
|
-
name: 'projectRoot',
|
|
463
|
-
message: 'What is the project root for your test project?',
|
|
464
|
-
default: projectProps?.path,
|
|
465
|
-
// only ask if there are more than 1 runner to pick from
|
|
466
|
-
when: /* istanbul ignore next */ (answers) => !answers.projectRootCorrect
|
|
467
|
-
}]
|
|
468
|
-
: []),
|
|
469
|
-
...QUESTIONNAIRE
|
|
470
|
-
];
|
|
471
|
-
return inquirer.prompt(questions);
|
|
472
|
-
}
|
|
473
|
-
/**
|
|
474
|
-
* Generates a valid file path from answers provided.
|
|
475
|
-
* @param answers The answer from which a file path is to be generated.
|
|
476
|
-
* @param projectRootDir The root directory of the project.
|
|
477
|
-
* @returns filePath
|
|
478
|
-
*/
|
|
479
|
-
function generatePathfromAnswer(answers, projectRootDir) {
|
|
480
|
-
return path.resolve(projectRootDir, path.dirname(answers) === '.' ? path.resolve(answers) : path.dirname(answers));
|
|
481
|
-
}
|
|
482
|
-
export function getPathForFileGeneration(answers, projectRootDir) {
|
|
483
|
-
const specAnswer = answers.specs || '';
|
|
484
|
-
const stepDefinitionAnswer = answers.stepDefinitions || '';
|
|
485
|
-
const pageObjectAnswer = answers.pages || '';
|
|
486
|
-
const destSpecRootPath = generatePathfromAnswer(specAnswer, projectRootDir).replace(/\*\*$/, '');
|
|
487
|
-
const destStepRootPath = generatePathfromAnswer(stepDefinitionAnswer, projectRootDir);
|
|
488
|
-
const destPageObjectRootPath = answers.usePageObjects
|
|
489
|
-
? generatePathfromAnswer(pageObjectAnswer, projectRootDir).replace(/\*\*$/, '')
|
|
490
|
-
: '';
|
|
491
|
-
const destSerenityLibRootPath = usesSerenity(answers)
|
|
492
|
-
? path.resolve(projectRootDir, answers.serenityLibPath || 'serenity')
|
|
493
|
-
: '';
|
|
494
|
-
const relativePath = (answers.generateTestFiles && answers.usePageObjects)
|
|
495
|
-
? !(convertPackageHashToObject(answers.framework).short === 'cucumber')
|
|
496
|
-
? path.relative(destSpecRootPath, destPageObjectRootPath)
|
|
497
|
-
: path.relative(destStepRootPath, destPageObjectRootPath)
|
|
498
|
-
: '';
|
|
499
|
-
return {
|
|
500
|
-
destSpecRootPath: destSpecRootPath,
|
|
501
|
-
destStepRootPath: destStepRootPath,
|
|
502
|
-
destPageObjectRootPath: destPageObjectRootPath,
|
|
503
|
-
destSerenityLibRootPath: destSerenityLibRootPath,
|
|
504
|
-
relativePath: relativePath.replaceAll(path.sep, '/')
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
export async function getDefaultFiles(answers, pattern) {
|
|
508
|
-
const rootdir = await getProjectRoot(answers);
|
|
509
|
-
const presetPackage = convertPackageHashToObject(answers.preset || '');
|
|
510
|
-
const isJSX = TSX_BASED_FRAMEWORKS.includes(presetPackage.short || '');
|
|
511
|
-
const val = pattern.endsWith('.feature')
|
|
512
|
-
? path.join(rootdir, pattern)
|
|
513
|
-
: answers?.isUsingCompiler?.toString().includes('TypeScript')
|
|
514
|
-
? `${path.join(rootdir, pattern)}.ts${isJSX ? 'x' : ''}`
|
|
515
|
-
: `${path.join(rootdir, pattern)}.js${isJSX ? 'x' : ''}`;
|
|
516
|
-
return val;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Ensure core WebdriverIO packages have the same version as cli so that if someone
|
|
520
|
-
* installs `@wdio/cli@next` and runs the wizard, all related packages have the same version.
|
|
521
|
-
* running `matchAll` to a version like "8.0.0-alpha.249+4bc237701", results in:
|
|
522
|
-
* ['8.0.0-alpha.249+4bc237701', '8', '0', '0', 'alpha', '249', '4bc237701']
|
|
523
|
-
*/
|
|
524
|
-
export function specifyVersionIfNeeded(packagesToInstall, version, npmTag) {
|
|
525
|
-
const { value } = version.matchAll(VERSION_REGEXP).next();
|
|
526
|
-
const [major, minor, patch, tagName, build] = (value || []).slice(1, -1); // drop commit bit
|
|
527
|
-
return packagesToInstall.map((p) => {
|
|
528
|
-
if ((p.startsWith('@wdio') && p !== '@wdio/visual-service') ||
|
|
529
|
-
['webdriver', 'webdriverio'].includes(p)) {
|
|
530
|
-
const tag = major && npmTag === 'latest'
|
|
531
|
-
? `^${major}.${minor}.${patch}-${tagName}.${build}`
|
|
532
|
-
: npmTag;
|
|
533
|
-
return `${p}@${tag}`;
|
|
534
|
-
}
|
|
535
|
-
return p;
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Receive project properties
|
|
540
|
-
* @returns {@type ProjectProps} if a package.json can be found in cwd or parent directories, otherwise undefined
|
|
541
|
-
* which means that a new project can be created
|
|
542
|
-
*/
|
|
543
|
-
export async function getProjectProps(cwd = process.cwd()) {
|
|
544
|
-
try {
|
|
545
|
-
const { packageJson, path: packageJsonPath } = await readPackageUp({ cwd }) || {};
|
|
546
|
-
if (!packageJson || !packageJsonPath) {
|
|
547
|
-
return undefined;
|
|
548
|
-
}
|
|
549
|
-
return {
|
|
550
|
-
esmSupported: (packageJson.type === 'module' ||
|
|
551
|
-
typeof packageJson.module === 'string'),
|
|
552
|
-
packageJson,
|
|
553
|
-
path: path.dirname(packageJsonPath)
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
catch (err) {
|
|
557
|
-
return undefined;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
export function runProgram(command, args, options) {
|
|
561
|
-
const child = spawn(command, args, { stdio: 'inherit', ...options });
|
|
562
|
-
return new Promise((resolve, reject) => {
|
|
563
|
-
let error;
|
|
564
|
-
child.on('error', (e) => (error = e));
|
|
565
|
-
child.on('close', code => {
|
|
566
|
-
if (code !== 0) {
|
|
567
|
-
return reject(new Error((error && error.message) ||
|
|
568
|
-
`Error calling: ${command} ${args.join(' ')}`));
|
|
569
|
-
}
|
|
570
|
-
resolve();
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* create package.json if not already existing
|
|
576
|
-
*/
|
|
577
|
-
export async function createPackageJSON(parsedAnswers) {
|
|
578
|
-
const packageJsonExists = await fs.access(path.resolve(process.cwd(), 'package.json')).then(() => true, () => false);
|
|
579
|
-
// Use the exisitng package.json if it already exists.
|
|
580
|
-
if (packageJsonExists) {
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
// If a user said no to creating a package.json, but it doesn't exist, abort.
|
|
584
|
-
if (parsedAnswers.createPackageJSON === false) {
|
|
585
|
-
/* istanbul ignore if */
|
|
586
|
-
if (!packageJsonExists) {
|
|
587
|
-
console.log(`No WebdriverIO configuration found in "${parsedAnswers.wdioConfigPath}"`);
|
|
588
|
-
return !process.env.VITEST_WORKER_ID && process.exit(0);
|
|
589
|
-
}
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
// Only create if the user gave explicit permission to
|
|
593
|
-
if (parsedAnswers.createPackageJSON) {
|
|
594
|
-
console.log(`Creating a ${chalk.bold('package.json')} for the directory...`);
|
|
595
|
-
await fs.writeFile(path.resolve(process.cwd(), 'package.json'), JSON.stringify({
|
|
596
|
-
name: 'webdriverio-tests',
|
|
597
|
-
version: '0.0.0',
|
|
598
|
-
private: true,
|
|
599
|
-
license: 'ISC',
|
|
600
|
-
type: 'module',
|
|
601
|
-
dependencies: {},
|
|
602
|
-
devDependencies: {}
|
|
603
|
-
}, null, 2));
|
|
604
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* run npm install only if required by the user
|
|
609
|
-
*/
|
|
610
|
-
const SEP = '\n- ';
|
|
611
|
-
export async function npmInstall(parsedAnswers, npmTag) {
|
|
612
|
-
const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service));
|
|
613
|
-
const presetPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.preset || '');
|
|
614
|
-
/**
|
|
615
|
-
* install Testing Library dependency if desired
|
|
616
|
-
*/
|
|
617
|
-
if (parsedAnswers.installTestingLibrary && TESTING_LIBRARY_PACKAGES[presetPackage.short]) {
|
|
618
|
-
parsedAnswers.packagesToInstall.push(TESTING_LIBRARY_PACKAGES[presetPackage.short], '@testing-library/jest-dom');
|
|
619
|
-
}
|
|
620
|
-
/**
|
|
621
|
-
* add helper package for Solidjs testing
|
|
622
|
-
*/
|
|
623
|
-
if (presetPackage.short === 'solid') {
|
|
624
|
-
parsedAnswers.packagesToInstall.push('solid-js');
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* add visual service if user selected support for it
|
|
628
|
-
*/
|
|
629
|
-
if (parsedAnswers.includeVisualTesting) {
|
|
630
|
-
parsedAnswers.packagesToInstall.push('@wdio/visual-service');
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* add dependency for Lit testing
|
|
634
|
-
*/
|
|
635
|
-
const preset = getPreset(parsedAnswers);
|
|
636
|
-
if (preset === 'lit') {
|
|
637
|
-
parsedAnswers.packagesToInstall.push('lit');
|
|
638
|
-
}
|
|
639
|
-
/**
|
|
640
|
-
* add dependency for Stencil testing
|
|
641
|
-
*/
|
|
642
|
-
if (preset === 'stencil') {
|
|
643
|
-
parsedAnswers.packagesToInstall.push('@stencil/core');
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* add helper for React rendering when not using Testing Library
|
|
647
|
-
*/
|
|
648
|
-
if (presetPackage.short === 'react') {
|
|
649
|
-
parsedAnswers.packagesToInstall.push('react');
|
|
650
|
-
if (!parsedAnswers.installTestingLibrary) {
|
|
651
|
-
parsedAnswers.packagesToInstall.push('react-dom');
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* add Jasmine types if necessary
|
|
656
|
-
*/
|
|
657
|
-
if (parsedAnswers.framework === 'jasmine' && parsedAnswers.isUsingTypeScript) {
|
|
658
|
-
parsedAnswers.packagesToInstall.push('@types/jasmine');
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* add Appium mobile drivers if desired
|
|
662
|
-
*/
|
|
663
|
-
if (parsedAnswers.purpose === 'macos') {
|
|
664
|
-
parsedAnswers.packagesToInstall.push('appium-mac2-driver');
|
|
665
|
-
}
|
|
666
|
-
if (parsedAnswers.mobileEnvironment === 'android') {
|
|
667
|
-
parsedAnswers.packagesToInstall.push('appium-uiautomator2-driver');
|
|
668
|
-
}
|
|
669
|
-
if (parsedAnswers.mobileEnvironment === 'ios') {
|
|
670
|
-
parsedAnswers.packagesToInstall.push('appium-xcuitest-driver');
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* add packages that are required by services
|
|
674
|
-
*/
|
|
675
|
-
addServiceDeps(servicePackages, parsedAnswers.packagesToInstall);
|
|
676
|
-
/**
|
|
677
|
-
* update package version if CLI is a pre release
|
|
678
|
-
*/
|
|
679
|
-
parsedAnswers.packagesToInstall = specifyVersionIfNeeded(parsedAnswers.packagesToInstall, pkg.version, npmTag);
|
|
680
|
-
const cwd = await getProjectRoot(parsedAnswers);
|
|
681
|
-
const pm = detectPackageManager();
|
|
682
|
-
if (parsedAnswers.npmInstall) {
|
|
683
|
-
console.log(`Installing packages using ${pm}:${SEP}${parsedAnswers.packagesToInstall.join(SEP)}`);
|
|
684
|
-
const success = await installPackages(cwd, parsedAnswers.packagesToInstall, true);
|
|
685
|
-
if (success) {
|
|
686
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
const installationCommand = getInstallCommand(pm, parsedAnswers.packagesToInstall, true);
|
|
691
|
-
console.log(util.format(DEPENDENCIES_INSTALLATION_MESSAGE, installationCommand));
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* detect the package manager that was used
|
|
696
|
-
*/
|
|
697
|
-
export function detectPackageManager(argv = process.argv) {
|
|
698
|
-
return PMs.find((pm) => (
|
|
699
|
-
// for pnpm check "~/Library/pnpm/store/v3/..."
|
|
700
|
-
// for NPM check "~/.npm/npx/..."
|
|
701
|
-
// for Yarn check "~/.yarn/bin/create-wdio"
|
|
702
|
-
// for Bun check "~/.bun/bin/create-wdio"
|
|
703
|
-
argv[1].includes(`${path.sep}${pm}${path.sep}`) ||
|
|
704
|
-
argv[1].includes(`${path.sep}.${pm}${path.sep}`))) || 'npm';
|
|
705
|
-
}
|
|
706
|
-
/**
|
|
707
|
-
* add ts-node if TypeScript is desired but not installed
|
|
708
|
-
*/
|
|
709
|
-
export async function setupTypeScript(parsedAnswers) {
|
|
710
|
-
/**
|
|
711
|
-
* don't create a `tsconfig.json` if user doesn't want to use TypeScript
|
|
712
|
-
*/
|
|
713
|
-
if (!parsedAnswers.isUsingTypeScript) {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* don't set up TypeScript if a `tsconfig.json` already exists but ensure we install `ts-node`
|
|
718
|
-
* as it is a requirement for running TypeScript tests
|
|
719
|
-
*/
|
|
720
|
-
if (parsedAnswers.hasRootTSConfig) {
|
|
721
|
-
parsedAnswers.packagesToInstall.push('ts-node');
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
console.log('Setting up TypeScript...');
|
|
725
|
-
const frameworkPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.framework);
|
|
726
|
-
const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service));
|
|
727
|
-
parsedAnswers.packagesToInstall.push('ts-node', 'typescript');
|
|
728
|
-
const serenityTypes = parsedAnswers.serenityAdapter === 'jasmine' ? ['jasmine'] : [];
|
|
729
|
-
const types = [
|
|
730
|
-
'node',
|
|
731
|
-
'@wdio/globals/types',
|
|
732
|
-
'expect-webdriverio',
|
|
733
|
-
...(parsedAnswers.serenityAdapter ? serenityTypes : [frameworkPackage.package]),
|
|
734
|
-
...(parsedAnswers.runner === 'browser' ? ['@wdio/browser-runner'] : []),
|
|
735
|
-
...servicePackages
|
|
736
|
-
.map(service => service.package)
|
|
737
|
-
.filter(service => (
|
|
738
|
-
/**
|
|
739
|
-
* given that we know that all "official" services have
|
|
740
|
-
* typescript support we only include them
|
|
741
|
-
*/
|
|
742
|
-
service.startsWith('@wdio') ||
|
|
743
|
-
/**
|
|
744
|
-
* also include community maintained packages with known
|
|
745
|
-
* support for TypeScript
|
|
746
|
-
*/
|
|
747
|
-
COMMUNITY_PACKAGES_WITH_TS_SUPPORT.includes(service)))
|
|
748
|
-
];
|
|
749
|
-
const preset = getPreset(parsedAnswers);
|
|
750
|
-
const config = {
|
|
751
|
-
compilerOptions: {
|
|
752
|
-
// compiler
|
|
753
|
-
moduleResolution: 'node',
|
|
754
|
-
module: !parsedAnswers.esmSupport ? 'commonjs' : 'ESNext',
|
|
755
|
-
target: 'es2022',
|
|
756
|
-
lib: ['es2022', 'dom'],
|
|
757
|
-
types,
|
|
758
|
-
skipLibCheck: true,
|
|
759
|
-
// bundler
|
|
760
|
-
noEmit: true,
|
|
761
|
-
allowImportingTsExtensions: true,
|
|
762
|
-
resolveJsonModule: true,
|
|
763
|
-
isolatedModules: true,
|
|
764
|
-
// linting
|
|
765
|
-
strict: true,
|
|
766
|
-
noUnusedLocals: true,
|
|
767
|
-
noUnusedParameters: true,
|
|
768
|
-
noFallthroughCasesInSwitch: true,
|
|
769
|
-
...Object.assign(preset === 'lit'
|
|
770
|
-
? {
|
|
771
|
-
experimentalDecorators: true,
|
|
772
|
-
useDefineForClassFields: false
|
|
773
|
-
}
|
|
774
|
-
: {}, preset === 'react'
|
|
775
|
-
? {
|
|
776
|
-
jsx: 'react-jsx'
|
|
777
|
-
}
|
|
778
|
-
: {}, preset === 'preact'
|
|
779
|
-
? {
|
|
780
|
-
jsx: 'react-jsx',
|
|
781
|
-
jsxImportSource: 'preact'
|
|
782
|
-
}
|
|
783
|
-
: {}, preset === 'solid'
|
|
784
|
-
? {
|
|
785
|
-
jsx: 'preserve',
|
|
786
|
-
jsxImportSource: 'solid-js'
|
|
787
|
-
}
|
|
788
|
-
: {}, preset === 'stencil'
|
|
789
|
-
? {
|
|
790
|
-
experimentalDecorators: true,
|
|
791
|
-
jsx: 'react',
|
|
792
|
-
jsxFactory: 'h',
|
|
793
|
-
jsxFragmentFactory: 'Fragment'
|
|
794
|
-
}
|
|
795
|
-
: {})
|
|
796
|
-
},
|
|
797
|
-
include: preset === 'svelte'
|
|
798
|
-
? ['src/**/*.d.ts', 'src/**/*.ts', 'src/**/*.js', 'src/**/*.svelte']
|
|
799
|
-
: preset === 'vue'
|
|
800
|
-
? ['src/**/*.ts', 'src/**/*.d.ts', 'src/**/*.tsx', 'src/**/*.vue']
|
|
801
|
-
: ['test', 'wdio.conf.ts']
|
|
802
|
-
};
|
|
803
|
-
await fs.mkdir(path.dirname(parsedAnswers.tsConfigFilePath), { recursive: true });
|
|
804
|
-
await fs.writeFile(parsedAnswers.tsConfigFilePath, JSON.stringify(config, null, 4));
|
|
805
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
806
|
-
}
|
|
807
|
-
function getPreset(parsedAnswers) {
|
|
808
|
-
const isUsingFramework = typeof parsedAnswers.preset === 'string';
|
|
809
|
-
return isUsingFramework ? (parsedAnswers.preset || 'lit') : '';
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* add @babel/register package if not installed
|
|
813
|
-
*/
|
|
814
|
-
export async function setupBabel(parsedAnswers) {
|
|
815
|
-
if (!parsedAnswers.isUsingBabel) {
|
|
816
|
-
return;
|
|
817
|
-
}
|
|
818
|
-
if (!await hasPackage('@babel/register')) {
|
|
819
|
-
parsedAnswers.packagesToInstall.push('@babel/register');
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* setup Babel if no config file exists
|
|
823
|
-
*/
|
|
824
|
-
const hasBabelConfig = await Promise.all([
|
|
825
|
-
fs.access(path.join(parsedAnswers.projectRootDir, 'babel.js')),
|
|
826
|
-
fs.access(path.join(parsedAnswers.projectRootDir, 'babel.cjs')),
|
|
827
|
-
fs.access(path.join(parsedAnswers.projectRootDir, 'babel.mjs')),
|
|
828
|
-
fs.access(path.join(parsedAnswers.projectRootDir, '.babelrc'))
|
|
829
|
-
]).then((results) => results.filter(Boolean).length > 1, () => false);
|
|
830
|
-
if (!hasBabelConfig) {
|
|
831
|
-
console.log('Setting up Babel project...');
|
|
832
|
-
if (!await hasPackage('@babel/core')) {
|
|
833
|
-
parsedAnswers.packagesToInstall.push('@babel/core');
|
|
834
|
-
}
|
|
835
|
-
if (!await hasPackage('@babel/preset-env')) {
|
|
836
|
-
parsedAnswers.packagesToInstall.push('@babel/preset-env');
|
|
837
|
-
}
|
|
838
|
-
await fs.writeFile(path.join(process.cwd(), 'babel.config.js'), `module.exports = ${JSON.stringify({
|
|
839
|
-
presets: [
|
|
840
|
-
['@babel/preset-env', {
|
|
841
|
-
targets: {
|
|
842
|
-
node: 18
|
|
843
|
-
}
|
|
844
|
-
}]
|
|
845
|
-
]
|
|
846
|
-
}, null, 4)}`);
|
|
847
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
export async function createWDIOConfig(parsedAnswers) {
|
|
851
|
-
try {
|
|
852
|
-
console.log('Creating a WebdriverIO config file...');
|
|
853
|
-
const tplPath = path.resolve(__dirname, 'templates', 'wdio.conf.tpl.ejs');
|
|
854
|
-
const renderedTpl = await renderFile(tplPath, {
|
|
855
|
-
answers: parsedAnswers,
|
|
856
|
-
_: new EjsHelpers({ useEsm: parsedAnswers.esmSupport, useTypeScript: parsedAnswers.isUsingTypeScript })
|
|
857
|
-
});
|
|
858
|
-
await fs.writeFile(parsedAnswers.wdioConfigPath, renderedTpl);
|
|
859
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
860
|
-
if (parsedAnswers.generateTestFiles) {
|
|
861
|
-
console.log('Autogenerating test files...');
|
|
862
|
-
await generateTestFiles(parsedAnswers);
|
|
863
|
-
console.log(chalk.green(chalk.bold('✔ Success!\n')));
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
catch (err) {
|
|
867
|
-
throw new Error(`⚠️ Couldn't write config file: ${err.stack}`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* Get project root directory based on questionair answers
|
|
872
|
-
* @param answers questionair answers
|
|
873
|
-
* @param projectProps project properties received via `getProjectProps`
|
|
874
|
-
* @returns project root path
|
|
875
|
-
*/
|
|
876
|
-
export async function getProjectRoot(parsedAnswers) {
|
|
877
|
-
const root = (await getProjectProps())?.path;
|
|
878
|
-
if (!root) {
|
|
879
|
-
throw new Error('Could not find project root directory with a package.json');
|
|
880
|
-
}
|
|
881
|
-
return !parsedAnswers || parsedAnswers.projectRootCorrect
|
|
882
|
-
? root
|
|
883
|
-
: parsedAnswers.projectRoot || process.cwd();
|
|
884
|
-
}
|
|
885
|
-
export async function createWDIOScript(parsedAnswers) {
|
|
886
|
-
const rootDir = await getProjectRoot(parsedAnswers);
|
|
887
|
-
const pathToWdioConfig = `./${path.join('.', parsedAnswers.wdioConfigPath.replace(rootDir, ''))}`;
|
|
888
|
-
const wdioScripts = {
|
|
889
|
-
'wdio': `wdio run ${pathToWdioConfig}`,
|
|
890
|
-
};
|
|
891
|
-
const serenityScripts = {
|
|
892
|
-
'serenity': 'failsafe serenity:update serenity:clean wdio serenity:report',
|
|
893
|
-
'serenity:update': 'serenity-bdd update',
|
|
894
|
-
'serenity:clean': 'rimraf target',
|
|
895
|
-
'wdio': `wdio run ${pathToWdioConfig}`,
|
|
896
|
-
'serenity:report': 'serenity-bdd run',
|
|
897
|
-
};
|
|
898
|
-
const scripts = parsedAnswers.serenityAdapter ? serenityScripts : wdioScripts;
|
|
899
|
-
for (const [script, command] of Object.entries(scripts)) {
|
|
900
|
-
const args = ['pkg', 'set', `scripts.${script}=${command}`];
|
|
901
|
-
try {
|
|
902
|
-
console.log(`Adding ${chalk.bold(`"${script}"`)} script to package.json`);
|
|
903
|
-
await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir });
|
|
904
|
-
}
|
|
905
|
-
catch (err) {
|
|
906
|
-
const [preArgs, scriptPath] = args.join(' ').split('=');
|
|
907
|
-
console.error(`⚠️ Couldn't add script to package.json: "${err.message}", you can add it manually ` +
|
|
908
|
-
`by running:\n\n\t${NPM_COMMAND} ${preArgs}="${scriptPath}"`);
|
|
909
|
-
return false;
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
console.log(chalk.green(chalk.bold('✔ Success!')));
|
|
913
|
-
return true;
|
|
914
|
-
}
|
|
915
|
-
export async function runAppiumInstaller(parsedAnswers) {
|
|
916
|
-
if (parsedAnswers.e2eEnvironment !== 'mobile') {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const answer = await inquirer.prompt({
|
|
920
|
-
name: 'continueWithAppiumSetup',
|
|
921
|
-
message: 'Continue with Appium setup using appium-installer (https://github.com/AppiumTestDistribution/appium-installer)?',
|
|
922
|
-
type: 'confirm',
|
|
923
|
-
default: true
|
|
924
|
-
});
|
|
925
|
-
if (!answer.continueWithAppiumSetup) {
|
|
926
|
-
return console.log('Ok! You can learn more about setting up mobile environments in the ' +
|
|
927
|
-
'Appium docs at https://appium.io/docs/en/2.0/quickstart/');
|
|
928
|
-
}
|
|
929
|
-
return $({ stdio: 'inherit' }) `npx appium-installer`;
|
|
930
|
-
}
|