@wdio/cli 8.17.0 → 8.18.2

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.
Files changed (31) hide show
  1. package/build/commands/config.d.ts.map +1 -1
  2. package/build/commands/config.js +13 -5
  3. package/build/constants.d.ts +10 -3
  4. package/build/constants.d.ts.map +1 -1
  5. package/build/constants.js +47 -23
  6. package/build/templates/EjsHelpers.d.ts +18 -0
  7. package/build/templates/EjsHelpers.d.ts.map +1 -0
  8. package/build/templates/EjsHelpers.js +59 -0
  9. package/build/templates/EjsHelpers.ts +84 -0
  10. package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +1 -0
  11. package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +41 -0
  12. package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +100 -0
  13. package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +36 -0
  14. package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +37 -0
  15. package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +18 -0
  16. package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +23 -0
  17. package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +17 -0
  18. package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +86 -0
  19. package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +88 -0
  20. package/build/templates/snippets/capabilities.ejs +9 -3
  21. package/build/templates/snippets/cucumber.ejs +48 -0
  22. package/build/templates/snippets/jasmine.ejs +20 -0
  23. package/build/templates/snippets/mocha.ejs +14 -0
  24. package/build/templates/snippets/serenity.ejs +18 -0
  25. package/build/templates/wdio.conf.tpl.ejs +11 -86
  26. package/build/types.d.ts +5 -1
  27. package/build/types.d.ts.map +1 -1
  28. package/build/utils.d.ts +4 -2
  29. package/build/utils.d.ts.map +1 -1
  30. package/build/utils.js +170 -81
  31. package/package.json +7 -7
package/build/utils.js CHANGED
@@ -1,12 +1,10 @@
1
1
  import fs from 'node:fs/promises';
2
- import util from 'node:util';
3
- import { dirname } from 'node:path';
2
+ import util, { promisify } from 'node:util';
3
+ import path, { dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { execSync, spawn } from 'node:child_process';
6
- import { promisify } from 'node:util';
7
6
  import ejs from 'ejs';
8
7
  import chalk from 'chalk';
9
- import path from 'node:path';
10
8
  import inquirer from 'inquirer';
11
9
  import pickBy from 'lodash.pickby';
12
10
  import logger from '@wdio/logger';
@@ -18,7 +16,8 @@ import { resolve } from 'import-meta-resolve';
18
16
  import { SevereServiceError } from 'webdriverio';
19
17
  import { ConfigParser } from '@wdio/config';
20
18
  import { CAPABILITY_KEYS } from '@wdio/protocols';
21
- import { ANDROID_CONFIG, IOS_CONFIG, QUESTIONNAIRE, pkg, CompilerOptions, TESTING_LIBRARY_PACKAGES, DEPENDENCIES_INSTALLATION_MESSAGE } from './constants.js';
19
+ import { ANDROID_CONFIG, CompilerOptions, DEPENDENCIES_INSTALLATION_MESSAGE, IOS_CONFIG, pkg, QUESTIONNAIRE, TESTING_LIBRARY_PACKAGES, COMMUNITY_PACKAGES_WITH_TS_SUPPORT, usesSerenity, } from './constants.js';
20
+ import { EjsHelpers } from './templates/EjsHelpers.js';
22
21
  const log = logger('@wdio/cli:utils');
23
22
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
23
  const NPM_COMMAND = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
@@ -181,6 +180,44 @@ export function convertPackageHashToObject(pkg, hash = '$--$') {
181
180
  const [p, short, purpose] = pkg.split(hash);
182
181
  return { package: p, short, purpose };
183
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
+ }
184
221
  export async function getCapabilities(arg) {
185
222
  const optionalCapabilites = {
186
223
  platformVersion: arg.platformVersion,
@@ -287,6 +324,9 @@ export async function hasPackage(pkg) {
287
324
  * generate test files based on CLI answers
288
325
  */
289
326
  export async function generateTestFiles(answers) {
327
+ if (answers.serenityAdapter) {
328
+ return generateSerenityExamples(answers);
329
+ }
290
330
  if (answers.runner === 'local') {
291
331
  return generateLocalRunnerTestFiles(answers);
292
332
  }
@@ -345,6 +385,25 @@ async function generateLocalRunnerTestFiles(answers) {
345
385
  await fs.writeFile(destPath, renderedTpl);
346
386
  }
347
387
  }
388
+ async function generateSerenityExamples(answers) {
389
+ const templateDirectories = {
390
+ [answers.projectRootDir]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', 'common', 'config'),
391
+ [answers.destSpecRootPath]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', answers.serenityAdapter),
392
+ [answers.destSerenityLibRootPath]: path.join(TEMPLATE_ROOT_DIR, 'serenity-js', 'common', 'serenity'),
393
+ };
394
+ for (const [destinationRootDir, templateRootDir] of Object.entries(templateDirectories)) {
395
+ const pathsToTemplates = await readDir(templateRootDir);
396
+ for (const pathToTemplate of pathsToTemplates) {
397
+ const extension = answers.isUsingTypeScript ? '.ts' : '.js';
398
+ const destination = path.join(destinationRootDir, path.relative(templateRootDir, pathToTemplate))
399
+ .replace(/\.ejs$/, '')
400
+ .replace(/\.ts$/, extension);
401
+ const contents = await renderFile(pathToTemplate, { answers, _: new EjsHelpers({ useEsm: answers.esmSupport, useTypeScript: answers.isUsingTypeScript }) });
402
+ await fs.mkdir(path.dirname(destination), { recursive: true });
403
+ await fs.writeFile(destination, contents);
404
+ }
405
+ }
406
+ }
348
407
  export async function getAnswers(yes) {
349
408
  if (yes) {
350
409
  const ignoredQuestions = ['e2eEnvironment'];
@@ -430,6 +489,9 @@ export function getPathForFileGeneration(answers, projectRootDir) {
430
489
  const destPageObjectRootPath = answers.usePageObjects
431
490
  ? path.resolve(projectRootDir, path.dirname(answers.pages || '').replace(/\*\*$/, ''))
432
491
  : '';
492
+ const destSerenityLibRootPath = usesSerenity(answers)
493
+ ? path.resolve(projectRootDir, answers.serenityLibPath || 'serenity')
494
+ : '';
433
495
  const relativePath = (answers.generateTestFiles && answers.usePageObjects)
434
496
  ? !(convertPackageHashToObject(answers.framework).short === 'cucumber')
435
497
  ? path.relative(destSpecRootPath, destPageObjectRootPath)
@@ -439,6 +501,7 @@ export function getPathForFileGeneration(answers, projectRootDir) {
439
501
  destSpecRootPath: destSpecRootPath,
440
502
  destStepRootPath: destStepRootPath,
441
503
  destPageObjectRootPath: destPageObjectRootPath,
504
+ destSerenityLibRootPath: destSerenityLibRootPath,
442
505
  relativePath: relativePath.replaceAll(path.sep, '/')
443
506
  };
444
507
  }
@@ -628,84 +691,94 @@ export function npmInstall(parsedAnswers, useYarn, npmTag) {
628
691
  * add ts-node if TypeScript is desired but not installed
629
692
  */
630
693
  export async function setupTypeScript(parsedAnswers) {
631
- if (!parsedAnswers.isUsingTypeScript) {
694
+ /**
695
+ * don't create a `tsconfig.json` if user doesn't want to use TypeScript
696
+ * or if a `tsconfig.json` already exists
697
+ */
698
+ if (!parsedAnswers.isUsingTypeScript || parsedAnswers.hasRootTSConfig) {
632
699
  return;
633
700
  }
634
701
  console.log('Setting up TypeScript...');
635
702
  const frameworkPackage = convertPackageHashToObject(parsedAnswers.rawAnswers.framework);
636
703
  const servicePackages = parsedAnswers.rawAnswers.services.map((service) => convertPackageHashToObject(service));
637
704
  parsedAnswers.packagesToInstall.push('ts-node', 'typescript');
705
+ const serenityTypes = parsedAnswers.serenityAdapter === 'jasmine' ? ['jasmine'] : [];
638
706
  const types = [
639
707
  'node',
640
708
  '@wdio/globals/types',
641
709
  'expect-webdriverio',
642
- frameworkPackage.package,
710
+ ...(parsedAnswers.serenityAdapter ? serenityTypes : [frameworkPackage.package]),
643
711
  ...(parsedAnswers.runner === 'browser' ? ['@wdio/browser-runner'] : []),
644
712
  ...servicePackages
645
713
  .map(service => service.package)
714
+ .filter(service => (
715
+ /**
716
+ * given that we know that all "official" services have
717
+ * typescript support we only include them
718
+ */
719
+ service.startsWith('@wdio') ||
646
720
  /**
647
- * given that we know that all "offical" services have
648
- * typescript support we only include them
721
+ * also include community maintained packages with known
722
+ * support for TypeScript
649
723
  */
650
- .filter(service => service.startsWith('@wdio'))
724
+ COMMUNITY_PACKAGES_WITH_TS_SUPPORT.includes(service)))
651
725
  ];
652
- if (!parsedAnswers.hasRootTSConfig) {
653
- const preset = getPreset(parsedAnswers);
654
- const config = {
655
- compilerOptions: {
656
- // compiler
657
- moduleResolution: 'node',
658
- module: !parsedAnswers.esmSupport ? 'commonjs' : 'ESNext',
659
- target: 'es2022',
660
- types,
661
- skipLibCheck: true,
662
- // bundler
663
- noEmit: true,
664
- allowImportingTsExtensions: true,
665
- resolveJsonModule: true,
666
- isolatedModules: true,
667
- // linting
668
- strict: true,
669
- noUnusedLocals: true,
670
- noUnusedParameters: true,
671
- noFallthroughCasesInSwitch: true,
672
- ...Object.assign(preset === 'lit'
673
- ? {
674
- experimentalDecorators: true,
675
- useDefineForClassFields: false
676
- }
677
- : {}, preset === 'react'
678
- ? {
679
- jsx: 'react-jsx'
680
- }
681
- : {}, preset === 'preact'
682
- ? {
683
- jsx: 'react-jsx',
684
- jsxImportSource: 'preact'
685
- }
686
- : {}, preset === 'solid'
687
- ? {
688
- jsx: 'preserve',
689
- jsxImportSource: 'solid-js'
690
- }
691
- : {}, preset === 'stencil'
692
- ? {
693
- experimentalDecorators: true,
694
- jsx: 'react',
695
- jsxFactory: 'h',
696
- jsxFragmentFactory: 'Fragment'
697
- }
698
- : {})
699
- },
700
- include: preset === 'svelte'
701
- ? ['src/**/*.d.ts', 'src/**/*.ts', 'src/**/*.js', 'src/**/*.svelte']
702
- : preset === 'vue'
703
- ? ['src/**/*.ts', 'src/**/*.d.ts', 'src/**/*.tsx', 'src/**/*.vue']
704
- : ['test']
705
- };
706
- await fs.mkdir(path.dirname(parsedAnswers.tsConfigFilePath), { recursive: true });
707
- await fs.writeFile(parsedAnswers.tsConfigFilePath, JSON.stringify(config, null, 4));
708
- }
726
+ const preset = getPreset(parsedAnswers);
727
+ const config = {
728
+ compilerOptions: {
729
+ // compiler
730
+ moduleResolution: 'node',
731
+ module: !parsedAnswers.esmSupport ? 'commonjs' : 'ESNext',
732
+ target: 'es2022',
733
+ lib: ['es2022', 'dom'],
734
+ types,
735
+ skipLibCheck: true,
736
+ // bundler
737
+ noEmit: true,
738
+ allowImportingTsExtensions: true,
739
+ resolveJsonModule: true,
740
+ isolatedModules: true,
741
+ // linting
742
+ strict: true,
743
+ noUnusedLocals: true,
744
+ noUnusedParameters: true,
745
+ noFallthroughCasesInSwitch: true,
746
+ ...Object.assign(preset === 'lit'
747
+ ? {
748
+ experimentalDecorators: true,
749
+ useDefineForClassFields: false
750
+ }
751
+ : {}, preset === 'react'
752
+ ? {
753
+ jsx: 'react-jsx'
754
+ }
755
+ : {}, preset === 'preact'
756
+ ? {
757
+ jsx: 'react-jsx',
758
+ jsxImportSource: 'preact'
759
+ }
760
+ : {}, preset === 'solid'
761
+ ? {
762
+ jsx: 'preserve',
763
+ jsxImportSource: 'solid-js'
764
+ }
765
+ : {}, preset === 'stencil'
766
+ ? {
767
+ experimentalDecorators: true,
768
+ jsx: 'react',
769
+ jsxFactory: 'h',
770
+ jsxFragmentFactory: 'Fragment'
771
+ }
772
+ : {})
773
+ },
774
+ include: preset === 'svelte'
775
+ ? ['src/**/*.d.ts', 'src/**/*.ts', 'src/**/*.js', 'src/**/*.svelte']
776
+ : preset === 'vue'
777
+ ? ['src/**/*.ts', 'src/**/*.d.ts', 'src/**/*.tsx', 'src/**/*.vue']
778
+ : ['test', 'wdio.conf.ts']
779
+ };
780
+ await fs.mkdir(path.dirname(parsedAnswers.tsConfigFilePath), { recursive: true });
781
+ await fs.writeFile(parsedAnswers.tsConfigFilePath, JSON.stringify(config, null, 4));
709
782
  console.log(chalk.green.bold('✔ Success!\n'));
710
783
  }
711
784
  function getPreset(parsedAnswers) {
@@ -755,7 +828,10 @@ export async function createWDIOConfig(parsedAnswers) {
755
828
  try {
756
829
  console.log('Creating a WebdriverIO config file...');
757
830
  const tplPath = path.resolve(__dirname, 'templates', 'wdio.conf.tpl.ejs');
758
- const renderedTpl = await renderFile(tplPath, { answers: parsedAnswers });
831
+ const renderedTpl = await renderFile(tplPath, {
832
+ answers: parsedAnswers,
833
+ _: new EjsHelpers({ useEsm: parsedAnswers.esmSupport, useTypeScript: parsedAnswers.isUsingTypeScript })
834
+ });
759
835
  await fs.writeFile(parsedAnswers.wdioConfigPath, renderedTpl);
760
836
  console.log(chalk.green.bold('✔ Success!\n'));
761
837
  if (parsedAnswers.generateTestFiles) {
@@ -770,20 +846,33 @@ export async function createWDIOConfig(parsedAnswers) {
770
846
  }
771
847
  export async function createWDIOScript(parsedAnswers) {
772
848
  const projectProps = await getProjectProps(process.cwd());
773
- const script = `wdio run ./${path.join('.', parsedAnswers.wdioConfigPath.replace(projectProps?.path || process.cwd(), ''))}`;
774
- const args = ['pkg', 'set', `scripts.wdio=${script}`];
775
- try {
776
- console.log(`Adding ${chalk.bold('"wdio"')} script to package.json.`);
777
- await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir });
778
- console.log(chalk.green.bold(' Success!'));
779
- return true;
780
- }
781
- catch (err) {
782
- const [preArgs, scriptPath] = args.join(' ').split('=');
783
- console.error(`⚠️ Couldn't add script to package.json: "${err.message}", you can add it manually ` +
784
- `by running:\n\n\t${NPM_COMMAND} ${preArgs}="${scriptPath}"`);
785
- return false;
849
+ const pathToWdioConfig = `./${path.join('.', parsedAnswers.wdioConfigPath.replace(projectProps?.path || process.cwd(), ''))}`;
850
+ const wdioScripts = {
851
+ 'wdio': `wdio run ${pathToWdioConfig}`,
852
+ };
853
+ const serenityScripts = {
854
+ 'serenity': 'failsafe serenity:update serenity:clean wdio serenity:report',
855
+ 'serenity:update': 'serenity-bdd update',
856
+ 'serenity:clean': 'rimraf target',
857
+ 'wdio': `wdio run ${pathToWdioConfig}`,
858
+ 'serenity:report': 'serenity-bdd run',
859
+ };
860
+ const scripts = parsedAnswers.serenityAdapter ? serenityScripts : wdioScripts;
861
+ for (const [script, command] of Object.entries(scripts)) {
862
+ const args = ['pkg', 'set', `scripts.${script}=${command}`];
863
+ try {
864
+ console.log(`Adding ${chalk.bold(`"${script}"`)} script to package.json`);
865
+ await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir });
866
+ }
867
+ catch (err) {
868
+ const [preArgs, scriptPath] = args.join(' ').split('=');
869
+ console.error(`⚠️ Couldn't add script to package.json: "${err.message}", you can add it manually ` +
870
+ `by running:\n\n\t${NPM_COMMAND} ${preArgs}="${scriptPath}"`);
871
+ return false;
872
+ }
786
873
  }
874
+ console.log(chalk.green.bold('✔ Success!'));
875
+ return true;
787
876
  }
788
877
  export async function runAppiumInstaller(parsedAnswers) {
789
878
  if (parsedAnswers.e2eEnvironment !== 'mobile') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/cli",
3
- "version": "8.17.0",
3
+ "version": "8.18.2",
4
4
  "description": "WebdriverIO testrunner command line interface",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-cli",
@@ -46,12 +46,12 @@
46
46
  "typeScriptVersion": "3.8.3",
47
47
  "dependencies": {
48
48
  "@types/node": "^20.1.1",
49
- "@wdio/config": "8.17.0",
50
- "@wdio/globals": "8.17.0",
49
+ "@wdio/config": "8.18.2",
50
+ "@wdio/globals": "8.18.2",
51
51
  "@wdio/logger": "8.16.17",
52
- "@wdio/protocols": "8.16.5",
52
+ "@wdio/protocols": "8.18.0",
53
53
  "@wdio/types": "8.17.0",
54
- "@wdio/utils": "8.17.0",
54
+ "@wdio/utils": "8.18.2",
55
55
  "async-exit-hook": "^2.0.1",
56
56
  "chalk": "^5.2.0",
57
57
  "chokidar": "^3.5.3",
@@ -66,7 +66,7 @@
66
66
  "lodash.union": "^4.6.0",
67
67
  "read-pkg-up": "10.1.0",
68
68
  "recursive-readdir": "^2.2.3",
69
- "webdriverio": "8.17.0",
69
+ "webdriverio": "8.18.2",
70
70
  "yargs": "^17.7.2",
71
71
  "yarn-install": "^1.0.0"
72
72
  },
@@ -83,5 +83,5 @@
83
83
  "publishConfig": {
84
84
  "access": "public"
85
85
  },
86
- "gitHead": "f7fe9474c580fc2003f6abca130e44ed025b1bd5"
86
+ "gitHead": "910c79f1640f0100e763a507fdcaee9e4f52560f"
87
87
  }