@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
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAWjC,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIxE,eAAO,MAAM,OAAO,WAAW,CAAA;AAC/B,eAAO,MAAM,IAAI,4EAA4E,CAAA;AAE7F,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;CAkBV,CAAA;AAEV,eAAO,MAAM,OAAO,UAAW,IAAI;;;;;;;;;;;;;;;;;;GAKlC,CAAA;AAED,eAAO,MAAM,YAAY,QAAwB,OAAO,KAAG,QAAQ,aAAa,CAoF/E,CAAA;AAED,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAoBpG;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,sBAAsB,EAAE,YAAY,0BAAmB;;;;GAQ1F;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM;;;GAMzD;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,+BAQ3D;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,UAAQ,EAAE,YAAY,0BAAmB,8BA4BrI"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAWjC,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIxE,eAAO,MAAM,OAAO,WAAW,CAAA;AAC/B,eAAO,MAAM,IAAI,4EAA4E,CAAA;AAE7F,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;CAkBV,CAAA;AAEV,eAAO,MAAM,OAAO,UAAW,IAAI;;;;;;;;;;;;;;;;;;GAKlC,CAAA;AAED,eAAO,MAAM,YAAY,QAAwB,OAAO,KAAG,QAAQ,aAAa,CAyF/E,CAAA;AAED,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,iBAsBpG;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,sBAAsB,EAAE,YAAY,0BAAmB;;;;GAQ1F;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM;;;GAMzD;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,+BAQ3D;AAED;;;;;;GAMG;AACH,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,UAAQ,EAAE,YAAY,0BAAmB,8BA4BrI"}
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import util from 'node:util';
4
3
  import inquirer from 'inquirer';
5
- import { CONFIG_HELPER_INTRO, CLI_EPILOGUE, CompilerOptions, SUPPORTED_PACKAGES, CONFIG_HELPER_SUCCESS_MESSAGE, isNuxtProject, SUPPORTED_CONFIG_FILE_EXTENSION } from '../constants.js';
6
- import { convertPackageHashToObject, getAnswers, getPathForFileGeneration, getProjectProps, getProjectRoot, createPackageJSON, setupTypeScript, setupBabel, npmInstall, createWDIOConfig, createWDIOScript, runAppiumInstaller } from '../utils.js';
4
+ import { CONFIG_HELPER_INTRO, CLI_EPILOGUE, CompilerOptions, SUPPORTED_PACKAGES, configHelperSuccessMessage, isNuxtProject, SUPPORTED_CONFIG_FILE_EXTENSION, CONFIG_HELPER_SERENITY_BANNER, } from '../constants.js';
5
+ import { convertPackageHashToObject, getAnswers, getPathForFileGeneration, getProjectProps, getProjectRoot, createPackageJSON, setupTypeScript, setupBabel, npmInstall, createWDIOConfig, createWDIOScript, runAppiumInstaller, getSerenityPackages } from '../utils.js';
7
6
  const hasYarnLock = await fs.access('yarn.lock').then(() => true, () => false);
8
7
  export const command = 'config';
9
8
  export const desc = 'Initialize WebdriverIO and setup configuration in your current project.';
@@ -39,6 +38,7 @@ export const parseAnswers = async function (yes) {
39
38
  const runnerPackage = convertPackageHashToObject(answers.runner || SUPPORTED_PACKAGES.runner[0].value);
40
39
  const servicePackages = answers.services.map((service) => convertPackageHashToObject(service));
41
40
  const pluginPackages = answers.plugins.map((plugin) => convertPackageHashToObject(plugin));
41
+ const serenityPackages = getSerenityPackages(answers);
42
42
  const reporterPackages = answers.reporters.map((reporter) => convertPackageHashToObject(reporter));
43
43
  const presetPackage = convertPackageHashToObject(answers.preset || '');
44
44
  const projectProps = await getProjectProps(process.cwd());
@@ -49,7 +49,8 @@ export const parseAnswers = async function (yes) {
49
49
  presetPackage.package,
50
50
  ...reporterPackages.map(reporter => reporter.package),
51
51
  ...pluginPackages.map(plugin => plugin.package),
52
- ...servicePackages.map(service => service.package)
52
+ ...servicePackages.map(service => service.package),
53
+ ...serenityPackages,
53
54
  ].filter(Boolean);
54
55
  /**
55
56
  * find relative paths between tests and pages
@@ -77,6 +78,7 @@ export const parseAnswers = async function (yes) {
77
78
  const wdioConfigFilename = `wdio.conf.${isUsingTypeScript ? 'ts' : 'js'}`;
78
79
  const wdioConfigPath = path.resolve(projectRootDir, wdioConfigFilename);
79
80
  return {
81
+ projectName: projectProps?.packageJson.name || 'Test Suite',
80
82
  // default values required in templates
81
83
  ...({
82
84
  usePageObjects: false,
@@ -89,6 +91,7 @@ export const parseAnswers = async function (yes) {
89
91
  preset: presetPackage.short,
90
92
  framework: frameworkPackage.short,
91
93
  purpose: runnerPackage.purpose,
94
+ serenityAdapter: frameworkPackage.package === '@serenity-js/webdriverio' && frameworkPackage.purpose,
92
95
  reporters: reporterPackages.map(({ short }) => short),
93
96
  plugins: pluginPackages.map(({ short }) => short),
94
97
  services: servicePackages.map(({ short }) => short),
@@ -104,6 +107,7 @@ export const parseAnswers = async function (yes) {
104
107
  projectRootDir,
105
108
  destSpecRootPath: parsedPaths.destSpecRootPath,
106
109
  destPageObjectRootPath: parsedPaths.destPageObjectRootPath,
110
+ destSerenityLibRootPath: parsedPaths.destSerenityLibRootPath,
107
111
  relativePath: parsedPaths.relativePath,
108
112
  hasRootTSConfig,
109
113
  tsConfigFilePath,
@@ -122,7 +126,11 @@ export async function runConfigCommand(parsedAnswers, useYarn, npmTag) {
122
126
  /**
123
127
  * print success message
124
128
  */
125
- console.log(util.format(CONFIG_HELPER_SUCCESS_MESSAGE, parsedAnswers.projectRootDir, parsedAnswers.projectRootDir));
129
+ console.log(configHelperSuccessMessage({
130
+ projectRootDir: parsedAnswers.projectRootDir,
131
+ runScript: parsedAnswers.serenityAdapter ? 'serenity' : 'wdio',
132
+ extraInfo: parsedAnswers.serenityAdapter ? CONFIG_HELPER_SERENITY_BANNER : ''
133
+ }));
126
134
  await runAppiumInstaller(parsedAnswers);
127
135
  }
128
136
  export async function handler(argv, runConfigCmd = runConfigCommand) {
@@ -3,7 +3,12 @@ export declare const pkg: any;
3
3
  export declare const CLI_EPILOGUE: string;
4
4
  export declare const CONFIG_HELPER_INTRO = "\n===============================\n\uD83E\uDD16 WDIO Configuration Wizard \uD83E\uDDD9\n===============================\n";
5
5
  export declare const SUPPORTED_CONFIG_FILE_EXTENSION: string[];
6
- export declare const CONFIG_HELPER_SUCCESS_MESSAGE = "\n\uD83E\uDD16 Successfully setup project at %s \uD83C\uDF89\n\nJoin our Discord Community Server and instantly find answers to your issues or queries. Or just join and say hi \uD83D\uDC4B!\n \uD83D\uDD17 https://discord.webdriver.io\n\nVisit the project on GitHub to report bugs \uD83D\uDC1B or raise feature requests \uD83D\uDCA1:\n \uD83D\uDD17 https://github.com/webdriverio/webdriverio\n\nTo run your tests, execute:\n$ cd %s\n$ npm run wdio\n";
6
+ export declare const configHelperSuccessMessage: ({ projectRootDir, runScript, extraInfo }: {
7
+ projectRootDir: string;
8
+ runScript: string;
9
+ extraInfo: string;
10
+ }) => string;
11
+ export declare const CONFIG_HELPER_SERENITY_BANNER = "\nLearn more about Serenity/JS:\n \uD83D\uDD17 https://serenity-js.org\n";
7
12
  export declare const DEPENDENCIES_INSTALLATION_MESSAGE = "\nTo install dependencies, execute:\n%s\n";
8
13
  export declare const NPM_INSTALL = "";
9
14
  export declare const ANDROID_CONFIG: {
@@ -64,8 +69,8 @@ export declare enum BackendChoice {
64
69
  Grid = "I have my own Selenium cloud"
65
70
  }
66
71
  export declare enum ElectronBuildToolChoice {
67
- ElectronForge = "Electron Forge",
68
- ElectronBuilder = "electron-builder",
72
+ ElectronForge = "Electron Forge (https://www.electronforge.io/)",
73
+ ElectronBuilder = "electron-builder (https://www.electron.build/)",
69
74
  SomethingElse = "Something else"
70
75
  }
71
76
  declare enum ProtocolOptions {
@@ -90,6 +95,7 @@ export declare const BROWSER_ENVIRONMENTS: {
90
95
  value: string;
91
96
  }[];
92
97
  declare function isBrowserRunner(answers: Questionnair): boolean;
98
+ export declare function usesSerenity(answers: Questionnair): boolean;
93
99
  export declare const isNuxtProject: boolean;
94
100
  export declare const QUESTIONNAIRE: ({
95
101
  type: string;
@@ -249,5 +255,6 @@ export declare const QUESTIONNAIRE: ({
249
255
  choices?: undefined;
250
256
  when?: undefined;
251
257
  })[];
258
+ export declare const COMMUNITY_PACKAGES_WITH_TS_SUPPORT: string[];
252
259
  export {};
253
260
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C,eAAO,MAAM,GAAG,KAA6B,CAAA;AAE7C,eAAO,MAAM,YAAY,QAAqE,CAAA;AAE9F,eAAO,MAAM,mBAAmB,8HAI/B,CAAA;AAED,eAAO,MAAM,+BAA+B,UAA2C,CAAA;AACvF,eAAO,MAAM,6BAA6B,ucAYzC,CAAA;AAED,eAAO,MAAM,iCAAiC,8CAG7C,CAAA;AAED,eAAO,MAAM,WAAW,KAAK,CAAA;AAE7B,eAAO,MAAM,cAAc;;;;CAI1B,CAAA;AAED,eAAO,MAAM,UAAU;;;;CAItB,CAAA;AAED,oBAAY,eAAe;IACvB,KAAK,gCAAgC;IACrC,EAAE,iDAAiD;IACnD,GAAG,QAAQ;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;CAmF9B,CAAA;AAED,eAAO,MAAM,gCAAgC;;;;;;IAS5C,CAAA;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM3D,CAAA;AAED,oBAAY,aAAa;IACrB,KAAK,wBAAwB;IAC7B,UAAU,kCAAkC;IAC5C,SAAS,kCAAkC;IAC3C,YAAY,oCAAoC;IAChD,YAAY,uEAAuE;IACnF,IAAI,iCAAiC;CACxC;AAED,oBAAY,uBAAuB;IAC/B,aAAa,mBAAmB;IAChC,eAAe,qBAAqB;IACpC,aAAa,mBAAmB;CACnC;AAED,aAAK,eAAe;IAChB,KAAK,UAAU;IACf,IAAI,SAAS;CAChB;AAED,oBAAY,aAAa;IACrB,EAAE,OAAO;IACT,EAAE,OAAO;IACT,IAAI,SAAS;CAChB;AAED,eAAO,MAAM,gBAAgB;;;GAG5B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;GAG/B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;GAKhC,CAAA;AAED,iBAAS,eAAe,CAAE,OAAO,EAAE,YAAY,WAE9C;AAUD,eAAO,MAAM,aAAa,SAYzB,CAAA;AAqBD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAkBqB,YAAY;;;;;;;oBAYX,YAAY;;;;;;;oBAiBZ,YAAY;;;;;;;;;;;oBAOb,YAAY;;;;;;;;;;oBAgBX,YAAY;;;;;oBAQb,YAAY;;;;;;;;oBAMZ,YAAY;;;;;;;;oBAyBZ,YAAY;;;;;;oBAoDZ,YAAY;;;;;;;oBASZ,YAAY;;;;;;uBA0BR,YAAY;;;;;;;;;;uBAaxC,YAAY;uBASe,YAAY;;;;;;uBAmBZ,YAAY;oBAIf,YAAY;;;;;;uBAkCT,YAAY;oBAKf,YAAY;;;;;;;;;;;;;;;;uBAqBpC,YAAY;;;;uBAuBZ,YAAY;;;;;;;;;IA2DjC,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAG9C,eAAO,MAAM,GAAG,KAA6B,CAAA;AAE7C,eAAO,MAAM,YAAY,QAAqE,CAAA;AAE9F,eAAO,MAAM,mBAAmB,8HAI/B,CAAA;AAED,eAAO,MAAM,+BAA+B,UAA2C,CAAA;AACvF,eAAO,MAAM,0BAA0B;oBAAqE,MAAM;eAAa,MAAM;eAAa,MAAM;YAYvJ,CAAA;AAED,eAAO,MAAM,6BAA6B,8EAGzC,CAAA;AAED,eAAO,MAAM,iCAAiC,8CAG7C,CAAA;AAED,eAAO,MAAM,WAAW,KAAK,CAAA;AAE7B,eAAO,MAAM,cAAc;;;;CAI1B,CAAA;AAED,eAAO,MAAM,UAAU;;;;CAItB,CAAA;AAED,oBAAY,eAAe;IACvB,KAAK,gCAAgC;IACrC,EAAE,iDAAiD;IACnD,GAAG,QAAQ;CACd;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;CAsF9B,CAAA;AAED,eAAO,MAAM,gCAAgC;;;;;;IAS5C,CAAA;AAED,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAM3D,CAAA;AAED,oBAAY,aAAa;IACrB,KAAK,wBAAwB;IAC7B,UAAU,kCAAkC;IAC5C,SAAS,kCAAkC;IAC3C,YAAY,oCAAoC;IAChD,YAAY,uEAAuE;IACnF,IAAI,iCAAiC;CACxC;AAED,oBAAY,uBAAuB;IAC/B,aAAa,mDAAmD;IAChE,eAAe,mDAAmD;IAClE,aAAa,mBAAmB;CACnC;AAED,aAAK,eAAe;IAChB,KAAK,UAAU;IACf,IAAI,SAAS;CAChB;AAED,oBAAY,aAAa;IACrB,EAAE,OAAO;IACT,EAAE,OAAO;IACT,IAAI,SAAS;CAChB;AAED,eAAO,MAAM,gBAAgB;;;GAG5B,CAAA;AAED,eAAO,MAAM,mBAAmB;;;GAG/B,CAAA;AAED,eAAO,MAAM,oBAAoB;;;GAKhC,CAAA;AAED,iBAAS,eAAe,CAAE,OAAO,EAAE,YAAY,WAE9C;AAED,wBAAgB,YAAY,CAAE,OAAO,EAAE,YAAY,WAElD;AAMD,eAAO,MAAM,aAAa,SAYzB,CAAA;AAqBD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAkBqB,YAAY;;;;;;;oBAYX,YAAY;;;;;;;oBAWZ,YAAY;;;;;;;;;;;oBAOb,YAAY;;;;;;;;;;oBAgBX,YAAY;;;;;oBAQb,YAAY;;;;;;;;oBAMZ,YAAY;;;;;;;;oBAyBZ,YAAY;;;;;;oBAoDZ,YAAY;;;;;;;oBASZ,YAAY;;;;;;uBA0BR,YAAY;;;;;;;;;;uBAaxC,YAAY;uBASe,YAAY;;;;;;uBAmBZ,YAAY;oBAIf,YAAY;;;;;;uBAuCT,YAAY;oBAKf,YAAY;;;;;;;;;;;;;;;;uBAiCpC,YAAY;;;;uBAuBZ,YAAY;;;;;;;;;IA2DjC,CAAA;AAEF,eAAO,MAAM,kCAAkC,UAM9C,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
- import { detectCompiler, getDefaultFiles, convertPackageHashToObject } from './utils.js';
4
+ import { detectCompiler, getDefaultFiles, convertPackageHashToObject, getProjectProps, getProjectRoot, } from './utils.js';
5
5
  const require = createRequire(import.meta.url);
6
6
  export const pkg = require('../package.json');
7
7
  export const CLI_EPILOGUE = `Documentation: https://webdriver.io\n@wdio/cli (v${pkg.version})`;
@@ -11,18 +11,22 @@ export const CONFIG_HELPER_INTRO = `
11
11
  ===============================
12
12
  `;
13
13
  export const SUPPORTED_CONFIG_FILE_EXTENSION = ['js', 'ts', 'mjs', 'mts', 'cjs', 'cts'];
14
- export const CONFIG_HELPER_SUCCESS_MESSAGE = `
15
- 🤖 Successfully setup project at %s 🎉
14
+ export const configHelperSuccessMessage = ({ projectRootDir, runScript, extraInfo = '' }) => `
15
+ 🤖 Successfully setup project at ${projectRootDir} 🎉
16
16
 
17
17
  Join our Discord Community Server and instantly find answers to your issues or queries. Or just join and say hi 👋!
18
18
  🔗 https://discord.webdriver.io
19
19
 
20
20
  Visit the project on GitHub to report bugs 🐛 or raise feature requests 💡:
21
21
  🔗 https://github.com/webdriverio/webdriverio
22
-
22
+ ${extraInfo}
23
23
  To run your tests, execute:
24
- $ cd %s
25
- $ npm run wdio
24
+ $ cd ${projectRootDir}
25
+ $ npm run ${runScript}
26
+ `;
27
+ export const CONFIG_HELPER_SERENITY_BANNER = `
28
+ Learn more about Serenity/JS:
29
+ 🔗 https://serenity-js.org
26
30
  `;
27
31
  export const DEPENDENCIES_INSTALLATION_MESSAGE = `
28
32
  To install dependencies, execute:
@@ -59,8 +63,11 @@ export const SUPPORTED_PACKAGES = {
59
63
  ],
60
64
  framework: [
61
65
  { name: 'Mocha (https://mochajs.org/)', value: '@wdio/mocha-framework$--$mocha' },
66
+ { name: 'Mocha with Serenity/JS (https://serenity-js.org/)', value: '@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$mocha' },
62
67
  { name: 'Jasmine (https://jasmine.github.io/)', value: '@wdio/jasmine-framework$--$jasmine' },
63
- { name: 'Cucumber (https://cucumber.io/)', value: '@wdio/cucumber-framework$--$cucumber' }
68
+ { name: 'Jasmine with Serenity/JS (https://serenity-js.org/)', value: '@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$jasmine' },
69
+ { name: 'Cucumber (https://cucumber.io/)', value: '@wdio/cucumber-framework$--$cucumber' },
70
+ { name: 'Cucumber with Serenity/JS (https://serenity-js.org/)', value: '@serenity-js/webdriverio$--$@serenity-js/webdriverio$--$cucumber' },
64
71
  ],
65
72
  reporter: [
66
73
  { name: 'spec', value: '@wdio/spec-reporter$--$spec' },
@@ -161,8 +168,8 @@ export var BackendChoice;
161
168
  })(BackendChoice || (BackendChoice = {}));
162
169
  export var ElectronBuildToolChoice;
163
170
  (function (ElectronBuildToolChoice) {
164
- ElectronBuildToolChoice["ElectronForge"] = "Electron Forge";
165
- ElectronBuildToolChoice["ElectronBuilder"] = "electron-builder";
171
+ ElectronBuildToolChoice["ElectronForge"] = "Electron Forge (https://www.electronforge.io/)";
172
+ ElectronBuildToolChoice["ElectronBuilder"] = "electron-builder (https://www.electron.build/)";
166
173
  ElectronBuildToolChoice["SomethingElse"] = "Something else";
167
174
  })(ElectronBuildToolChoice || (ElectronBuildToolChoice = {}));
168
175
  var ProtocolOptions;
@@ -193,12 +200,12 @@ export const BROWSER_ENVIRONMENTS = [
193
200
  function isBrowserRunner(answers) {
194
201
  return answers.runner === SUPPORTED_PACKAGES.runner[1].value;
195
202
  }
203
+ export function usesSerenity(answers) {
204
+ return answers.framework.includes('serenity-js');
205
+ }
196
206
  function getTestingPurpose(answers) {
197
207
  return convertPackageHashToObject(answers.runner).purpose;
198
208
  }
199
- function electronBuilderConfigIsJson(answers) {
200
- return answers.electronBuilderConfigPath?.endsWith('.json');
201
- }
202
209
  export const isNuxtProject = await Promise.all([
203
210
  path.join(process.cwd(), 'nuxt.config.js'),
204
211
  path.join(process.cwd(), 'nuxt.config.ts'),
@@ -250,17 +257,11 @@ export const QUESTIONNAIRE = [{
250
257
  message: 'Which tool are you using to build your Electron app?',
251
258
  choices: Object.values(ElectronBuildToolChoice),
252
259
  when: /* instanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron'
253
- }, {
254
- type: 'input',
255
- name: 'electronBuilderConfigPath',
256
- message: 'What is the path to your electron-builder configuration?',
257
- default: `${process.cwd()}/package.json`,
258
- when: /* instanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron' && answers.electronBuildTool === ElectronBuildToolChoice.ElectronBuilder
259
260
  }, {
260
261
  type: 'input',
261
262
  name: 'electronAppBinaryPath',
262
263
  message: 'What is the path to the binary of your built Electron app?',
263
- when: /* istanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron' && (answers.electronBuildTool !== ElectronBuildToolChoice.ElectronBuilder || !electronBuilderConfigIsJson(answers))
264
+ when: /* istanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron' && (answers.electronBuildTool === ElectronBuildToolChoice.SomethingElse)
264
265
  }, {
265
266
  type: 'list',
266
267
  name: 'backend',
@@ -441,7 +442,7 @@ export const QUESTIONNAIRE = [{
441
442
  }, {
442
443
  type: 'input',
443
444
  name: 'specs',
444
- message: 'Where should these files be located?',
445
+ message: 'What should be the location of your spec files?',
445
446
  default: /* istanbul ignore next */ (answers) => {
446
447
  const pattern = isBrowserRunner(answers) ? 'src/**/*.test' : 'test/specs/**/*';
447
448
  return getDefaultFiles(answers, pattern);
@@ -450,13 +451,13 @@ export const QUESTIONNAIRE = [{
450
451
  }, {
451
452
  type: 'input',
452
453
  name: 'specs',
453
- message: 'Where should these feature files be located?',
454
+ message: 'What should be the location of your feature files?',
454
455
  default: (answers) => getDefaultFiles(answers, 'features/**/*.feature'),
455
456
  when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && answers.framework.includes('cucumber')
456
457
  }, {
457
458
  type: 'input',
458
459
  name: 'stepDefinitions',
459
- message: 'Where should these step definitions be located?',
460
+ message: 'What should be the location of your step definitions?',
460
461
  default: (answers) => getDefaultFiles(answers, 'features/step-definitions/steps'),
461
462
  when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && answers.framework.includes('cucumber')
462
463
  }, {
@@ -473,7 +474,12 @@ export const QUESTIONNAIRE = [{
473
474
  * and also not needed when running VS Code tests since the service comes with
474
475
  * its own page object implementation, nor when running Electron or MacOS tests
475
476
  */
476
- !['vscode', 'electron', 'macos'].includes(getTestingPurpose(answers)))
477
+ !['vscode', 'electron', 'macos'].includes(getTestingPurpose(answers)) &&
478
+ /**
479
+ * Serenity/JS generates Lean Page Objects by default, so there's no need to ask about it
480
+ * See https://serenity-js.org/handbook/web-testing/page-objects-pattern/
481
+ */
482
+ !usesSerenity(answers))
477
483
  }, {
478
484
  type: 'input',
479
485
  name: 'pages',
@@ -482,6 +488,17 @@ export const QUESTIONNAIRE = [{
482
488
  ? getDefaultFiles(answers, 'test/pageobjects/**/*')
483
489
  : getDefaultFiles(answers, 'features/pageobjects/**/*')),
484
490
  when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && answers.usePageObjects
491
+ }, {
492
+ type: 'input',
493
+ name: 'serenityLibPath',
494
+ message: 'What should be the location of your Serenity/JS Screenplay Pattern library?',
495
+ default: /* istanbul ignore next */ async (answers) => {
496
+ const projectProps = await getProjectProps();
497
+ const projectRootDir = getProjectRoot(answers, projectProps);
498
+ const specsDir = path.resolve(projectRootDir, path.dirname(answers.specs || '').replace(/\*\*$/, ''));
499
+ return path.resolve(specsDir, '..', 'serenity');
500
+ },
501
+ when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && usesSerenity(answers)
485
502
  }, {
486
503
  type: 'checkbox',
487
504
  name: 'reporters',
@@ -588,3 +605,10 @@ export const QUESTIONNAIRE = [{
588
605
  message: 'Do you want me to run `npm install`',
589
606
  default: true
590
607
  }];
608
+ export const COMMUNITY_PACKAGES_WITH_TS_SUPPORT = [
609
+ 'wdio-electron-service',
610
+ 'wdio-vscode-service',
611
+ 'wdio-nuxt-service',
612
+ 'wdio-vite-service',
613
+ 'wdio-gmail-service'
614
+ ];
@@ -0,0 +1,18 @@
1
+ export interface EjsHelpersConfig {
2
+ useEsm?: boolean;
3
+ useTypeScript?: boolean;
4
+ }
5
+ export declare class EjsHelpers {
6
+ readonly useTypeScript: boolean;
7
+ readonly useEsm: boolean;
8
+ constructor(config: EjsHelpersConfig);
9
+ if(condition: boolean, trueValue: string, falseValue?: string): string;
10
+ ifTs: (trueValue: string, falseValue?: string) => string;
11
+ ifEsm: (trueValue: string, falseValue?: string) => string;
12
+ param(name: string, type: string): string;
13
+ returns(type: string): string;
14
+ import(exports: string, moduleId: string): string;
15
+ private modulePathFrom;
16
+ export(keyword: 'class' | 'function' | 'const' | 'let', name: string): string;
17
+ }
18
+ //# sourceMappingURL=EjsHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EjsHelpers.d.ts","sourceRoot":"","sources":["../../src/templates/EjsHelpers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,UAAU;IACnB,SAAgB,aAAa,EAAE,OAAO,CAAA;IACtC,SAAgB,MAAM,EAAE,OAAO,CAAA;gBAEnB,MAAM,EAAE,gBAAgB;IAKpC,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAE,MAAW;IAMjE,IAAI,cAAe,MAAM,eAAc,MAAM,YACS;IAEtD,KAAK,cAAe,MAAM,eAAc,MAAM,YACC;IAE/C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAMhC,OAAO,CAAC,IAAI,EAAE,MAAM;IAMpB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAkBxC,OAAO,CAAC,cAAc;IAYtB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,MAAM;CAevE"}
@@ -0,0 +1,59 @@
1
+ export class EjsHelpers {
2
+ useTypeScript;
3
+ useEsm;
4
+ constructor(config) {
5
+ this.useTypeScript = config.useTypeScript ?? false;
6
+ this.useEsm = config.useEsm ?? false;
7
+ }
8
+ if(condition, trueValue, falseValue = '') {
9
+ return condition
10
+ ? trueValue
11
+ : falseValue;
12
+ }
13
+ ifTs = (trueValue, falseValue = '') => this.if(this.useTypeScript, trueValue, falseValue);
14
+ ifEsm = (trueValue, falseValue = '') => this.if(this.useEsm, trueValue, falseValue);
15
+ param(name, type) {
16
+ return this.useTypeScript
17
+ ? `${name}: ${type}`
18
+ : name;
19
+ }
20
+ returns(type) {
21
+ return this.useTypeScript
22
+ ? `: ${type}`
23
+ : '';
24
+ }
25
+ import(exports, moduleId) {
26
+ const individualExports = exports.split(',').map(id => id.trim());
27
+ const imports = this.useTypeScript
28
+ ? individualExports
29
+ : individualExports.filter(id => !id.startsWith('type '));
30
+ if (!imports.length) {
31
+ return '';
32
+ }
33
+ const modulePath = this.modulePathFrom(moduleId);
34
+ return this.useEsm || this.useTypeScript
35
+ ? `import { ${imports.join(', ')} } from '${modulePath}'`
36
+ : `const { ${imports.join(', ')} } = require('${modulePath}')`;
37
+ }
38
+ modulePathFrom(moduleId) {
39
+ if (!(moduleId.startsWith('.') && this.useEsm)) {
40
+ return moduleId;
41
+ }
42
+ if (moduleId.endsWith('/') && this.useEsm) {
43
+ return moduleId + 'index.js';
44
+ }
45
+ return moduleId + '.js';
46
+ }
47
+ export(keyword, name) {
48
+ if (this.useTypeScript) {
49
+ return `export ${keyword} ${name}`;
50
+ }
51
+ if (this.useEsm) {
52
+ return `export ${keyword} ${name}`;
53
+ }
54
+ if (['class', 'function'].includes(keyword)) {
55
+ return `module.exports.${name} = ${keyword} ${name}`;
56
+ }
57
+ return `module.exports.${name}`;
58
+ }
59
+ }
@@ -0,0 +1,84 @@
1
+ export interface EjsHelpersConfig {
2
+ useEsm?: boolean;
3
+ useTypeScript?: boolean;
4
+ }
5
+
6
+ export class EjsHelpers {
7
+ public readonly useTypeScript: boolean
8
+ public readonly useEsm: boolean
9
+
10
+ constructor(config: EjsHelpersConfig) {
11
+ this.useTypeScript = config.useTypeScript ?? false
12
+ this.useEsm = config.useEsm ?? false
13
+ }
14
+
15
+ if(condition: boolean, trueValue: string, falseValue: string = '') {
16
+ return condition
17
+ ? trueValue
18
+ : falseValue
19
+ }
20
+
21
+ ifTs = (trueValue: string, falseValue: string = '') =>
22
+ this.if(this.useTypeScript, trueValue, falseValue)
23
+
24
+ ifEsm = (trueValue: string, falseValue: string = '') =>
25
+ this.if(this.useEsm, trueValue, falseValue)
26
+
27
+ param(name: string, type: string) {
28
+ return this.useTypeScript
29
+ ? `${ name }: ${ type }`
30
+ : name
31
+ }
32
+
33
+ returns(type: string) {
34
+ return this.useTypeScript
35
+ ? `: ${ type }`
36
+ : ''
37
+ }
38
+
39
+ import(exports: string, moduleId: string) {
40
+ const individualExports = exports.split(',').map(id => id.trim())
41
+
42
+ const imports: string[] = this.useTypeScript
43
+ ? individualExports
44
+ : individualExports.filter(id => ! id.startsWith('type '))
45
+
46
+ if (! imports.length) {
47
+ return ''
48
+ }
49
+
50
+ const modulePath = this.modulePathFrom(moduleId)
51
+
52
+ return this.useEsm || this.useTypeScript
53
+ ? `import { ${ imports.join(', ') } } from '${ modulePath }'`
54
+ : `const { ${ imports.join(', ') } } = require('${ modulePath }')`
55
+ }
56
+
57
+ private modulePathFrom(moduleId: string) {
58
+ if (! (moduleId.startsWith('.') && this.useEsm)) {
59
+ return moduleId
60
+ }
61
+
62
+ if (moduleId.endsWith('/') && this.useEsm) {
63
+ return moduleId + 'index.js'
64
+ }
65
+
66
+ return moduleId + '.js'
67
+ }
68
+
69
+ export(keyword: 'class' | 'function' | 'const' | 'let', name: string) {
70
+ if (this.useTypeScript) {
71
+ return `export ${ keyword } ${ name }`
72
+ }
73
+
74
+ if (this.useEsm) {
75
+ return `export ${ keyword } ${ name }`
76
+ }
77
+
78
+ if (['class', 'function'].includes(keyword)) {
79
+ return `module.exports.${ name } = ${ keyword } ${ name }`
80
+ }
81
+
82
+ return `module.exports.${ name }`
83
+ }
84
+ }
@@ -0,0 +1 @@
1
+ serenity.project.name=<%= answers.projectName %>
@@ -0,0 +1,41 @@
1
+ <%- _.import('Ensure, equals', '@serenity-js/assertions' ) %>
2
+ <%- _.import('Task', '@serenity-js/core' ) %>
3
+ <%- _.import('GetRequest, LastResponse, Send', '@serenity-js/rest' ) %>
4
+
5
+ /**
6
+ * Learn more about API testing with Serenity/JS
7
+ * https://serenity-js.org/handbook/api-testing/
8
+ */
9
+ <%- _.export('class', 'GitHubStatus') %> {
10
+ static #baseApiUrl = 'https://www.githubstatus.com/api/v2/'
11
+ static #statusJson = this.#baseApiUrl + 'status.json'
12
+
13
+ static ensureAllSystemsOperational = () =>
14
+ Task.where(`#actor ensures all GitHub systems are operational`,
15
+ Send.a(GetRequest.to(this.#statusJson)),
16
+ Ensure.that(LastResponse.status(), equals(200)),
17
+ Ensure.that(
18
+ LastResponse.body<%- _.ifTs('<StatusResponse>') %>().status.description.describedAs('GitHub Status'),
19
+ equals('All Systems Operational')
20
+ ),
21
+ )
22
+ }
23
+ <% if (_.useTypeScript) { %>
24
+ /**
25
+ * Interfaces describing a simplified response structure returned by the GitHub Status Summary API:
26
+ * https://www.githubstatus.com/api/v2/summary.json
27
+ */
28
+ interface StatusResponse {
29
+ page: {
30
+ id: string
31
+ name: string
32
+ url: string
33
+ time_zone: string
34
+ updated_at: string
35
+ }
36
+ status: {
37
+ indicator: string
38
+ description: string
39
+ }
40
+ }
41
+ <% } %>
@@ -0,0 +1,100 @@
1
+ <%- _.import('contain, Ensure, equals, includes, isGreaterThan', '@serenity-js/assertions') %>
2
+ <%- _.import('type Answerable, Check, d, type QuestionAdapter, Task, Wait', '@serenity-js/core') %>
3
+ <%- _.import('By, Enter, ExecuteScript, isVisible, Key, Navigate, Page, PageElement, PageElements, Press, Text', '@serenity-js/web') %>
4
+
5
+ <%- _.import('TodoListItem', './TodoListItem') %>
6
+
7
+ <%- _.export('class', 'TodoList') %> {
8
+
9
+ // Public API captures the business domain-focused tasks
10
+ // that an actor interacting with a TodoList app can perform
11
+
12
+ static createEmptyList = () =>
13
+ Task.where('#actor creates an empty todo list',
14
+ Navigate.to('https://todo-app.serenity-js.org/'),
15
+ Ensure.that(
16
+ Page.current().title().describedAs('website title'),
17
+ equals('Serenity/JS TodoApp'),
18
+ ),
19
+ Wait.until(this.#newTodoInput(), isVisible()),
20
+ this.#emptyLocalStorageIfNeeded(),
21
+ )
22
+
23
+ static #emptyLocalStorageIfNeeded = () =>
24
+ Task.where('#actor empties local storage if needed',
25
+ Check.whether(this.#persistedItems().length, isGreaterThan(0))
26
+ .andIfSo(
27
+ this.#emptyLocalStorage(),
28
+ Page.current().reload(),
29
+ )
30
+ )
31
+
32
+ static createListContaining = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
33
+ Task.where(`#actor starts with a list containing ${ itemNames.length } items`,
34
+ TodoList.createEmptyList(),
35
+ ...itemNames.map(itemName => this.recordItem(itemName))
36
+ )
37
+
38
+ static recordItem = (<%- _.param('itemName', 'Answerable<string>') %>) =>
39
+ Task.where(d `#actor records an item called ${ itemName }`,
40
+ Enter.theValue(itemName).into(this.#newTodoInput()),
41
+ Press.the(Key.Enter).in(this.#newTodoInput()),
42
+ Wait.until(Text.ofAll(this.#items()), contain(itemName)),
43
+ )
44
+
45
+ static markAsCompleted = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
46
+ Task.where(d`#actor marks the following items as completed: ${ itemNames }`,
47
+ ...itemNames.map(itemName => TodoListItem.markAsCompleted(this.#itemCalled(itemName)))
48
+ )
49
+
50
+ static markAsOutstanding = (<%- _.param('itemNames', 'Array<Answerable<string>>') %>) =>
51
+ Task.where(d`#actor marks the following items as outstanding: ${ itemNames }`,
52
+ ...itemNames.map(itemName => TodoListItem.markAsOutstanding(this.#itemCalled(itemName)))
53
+ )
54
+
55
+ static outstandingItemsCount = () =>
56
+ Text.of(PageElement.located(By.tagName('strong')).of(this.#outstandingItemsLabel()))
57
+ .as(Number)
58
+ .describedAs('number of items left')
59
+
60
+ // Private API captures ways to locate interactive elements and data transformation logic.
61
+ // Private API supports the public API and is not used in the test scenario directly.
62
+
63
+ static #itemCalled = (<%- _.param('name', 'Answerable<string>') %>) =>
64
+ this.#items()
65
+ .where(Text, includes(name))
66
+ .first()
67
+ .describedAs(d`an item called ${ name }`)
68
+
69
+ static #outstandingItemsLabel = () =>
70
+ PageElement.located(By.css('.todo-count'))
71
+ .describedAs('items left counter')
72
+
73
+ static #newTodoInput = () =>
74
+ PageElement.located(By.css('.new-todo'))
75
+ .describedAs('"What needs to be done?" input box')
76
+
77
+ static #items = () =>
78
+ PageElements.located(By.css('.todo-list li'))
79
+ .describedAs('displayed items')
80
+
81
+ static #persistedItems = () =>
82
+ Page.current()
83
+ .executeScript(`
84
+ return window.localStorage['serenity-js-todo-app']
85
+ ? JSON.parse(window.localStorage['serenity-js-todo-app'])
86
+ : []
87
+ `).describedAs('persisted items')<%- _.ifTs(' as QuestionAdapter<PersistedTodoItem[]>') %>
88
+
89
+ static #emptyLocalStorage = () =>
90
+ Task.where('#actor empties local storage',
91
+ ExecuteScript.sync(`window.localStorage.removeItem('serenity-js-todo-app')`)
92
+ )
93
+ }
94
+ <% if (_.useTypeScript) { %>
95
+ interface PersistedTodoItem {
96
+ id: number;
97
+ name: string;
98
+ completed: boolean;
99
+ }
100
+ <% } %>
@@ -0,0 +1,36 @@
1
+ <%- _.import('contain, not', '@serenity-js/assertions') %>
2
+ <%- _.import('Check, d, type QuestionAdapter, Task', '@serenity-js/core') %>
3
+ <%- _.import('By, Click, CssClasses, PageElement', '@serenity-js/web') %>
4
+
5
+ <%- _.export('class', 'TodoListItem') %> {
6
+
7
+ // Public API captures the business domain-focused tasks
8
+ // that an actor interacting with a TodoListItem app can perform
9
+
10
+ static markAsCompleted = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
11
+ Task.where(d `#actor marks ${ item } as completed`,
12
+ Check.whether(CssClasses.of(item), not(contain('completed')))
13
+ .andIfSo(this.toggle(item)),
14
+ )
15
+
16
+ static markAsOutstanding = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
17
+ Task.where(d `#actor marks ${ item } as outstanding`,
18
+ Check.whether(CssClasses.of(item), contain('completed'))
19
+ .andIfSo(this.toggle(item)),
20
+ )
21
+
22
+ static toggle = (<%- _.param('item', 'QuestionAdapter<PageElement>') %>) =>
23
+ Task.where(d `#actor toggles the completion status of ${ item }`,
24
+ Click.on(
25
+ this.#toggleButton().of(item),
26
+ ),
27
+ )
28
+
29
+ // Private API captures ways to locate interactive elements and data transformation logic.
30
+ // Private API supports the public API and is not used in the test scenario directly.
31
+
32
+ static #toggleButton = () =>
33
+ PageElement
34
+ .located(By.css('input.toggle'))
35
+ .describedAs('toggle button')
36
+ }