@wdio/cli 8.16.22 → 8.18.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.
Files changed (32) 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 +19 -1
  4. package/build/constants.d.ts.map +1 -1
  5. package/build/constants.js +52 -15
  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 +11 -2
  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/snippets/services.ejs +1 -12
  26. package/build/templates/wdio.conf.tpl.ejs +10 -62
  27. package/build/types.d.ts +8 -2
  28. package/build/types.d.ts.map +1 -1
  29. package/build/utils.d.ts +6 -4
  30. package/build/utils.d.ts.map +1 -1
  31. package/build/utils.js +102 -21
  32. package/package.json +8 -8
@@ -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: {
@@ -63,6 +68,11 @@ export declare enum BackendChoice {
63
68
  OtherVendors = "In the cloud using Testingbot or LambdaTest or a different service",
64
69
  Grid = "I have my own Selenium cloud"
65
70
  }
71
+ export declare enum ElectronBuildToolChoice {
72
+ ElectronForge = "Electron Forge (https://www.electronforge.io/)",
73
+ ElectronBuilder = "electron-builder (https://www.electron.build/)",
74
+ SomethingElse = "Something else"
75
+ }
66
76
  declare enum ProtocolOptions {
67
77
  HTTPS = "https",
68
78
  HTTP = "http"
@@ -85,6 +95,7 @@ export declare const BROWSER_ENVIRONMENTS: {
85
95
  value: string;
86
96
  }[];
87
97
  declare function isBrowserRunner(answers: Questionnair): boolean;
98
+ export declare function usesSerenity(answers: Questionnair): boolean;
88
99
  export declare const isNuxtProject: boolean;
89
100
  export declare const QUESTIONNAIRE: ({
90
101
  type: string;
@@ -116,6 +127,13 @@ export declare const QUESTIONNAIRE: ({
116
127
  default: boolean;
117
128
  when: (answers: Questionnair) => string | false | undefined;
118
129
  choices?: undefined;
130
+ } | {
131
+ type: string;
132
+ name: string;
133
+ message: string;
134
+ choices: ElectronBuildToolChoice[];
135
+ when: (answers: Questionnair) => boolean;
136
+ default?: undefined;
119
137
  } | {
120
138
  type: string;
121
139
  name: string;
@@ -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,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;AAMD,eAAO,MAAM,aAAa,SAYzB,CAAA;AAqBD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAkBqB,YAAY;;;;;;;oBAkBX,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"}
@@ -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' },
@@ -159,6 +166,12 @@ export var BackendChoice;
159
166
  BackendChoice["OtherVendors"] = "In the cloud using Testingbot or LambdaTest or a different service";
160
167
  BackendChoice["Grid"] = "I have my own Selenium cloud";
161
168
  })(BackendChoice || (BackendChoice = {}));
169
+ export var ElectronBuildToolChoice;
170
+ (function (ElectronBuildToolChoice) {
171
+ ElectronBuildToolChoice["ElectronForge"] = "Electron Forge (https://www.electronforge.io/)";
172
+ ElectronBuildToolChoice["ElectronBuilder"] = "electron-builder (https://www.electron.build/)";
173
+ ElectronBuildToolChoice["SomethingElse"] = "Something else";
174
+ })(ElectronBuildToolChoice || (ElectronBuildToolChoice = {}));
162
175
  var ProtocolOptions;
163
176
  (function (ProtocolOptions) {
164
177
  ProtocolOptions["HTTPS"] = "https";
@@ -187,6 +200,9 @@ export const BROWSER_ENVIRONMENTS = [
187
200
  function isBrowserRunner(answers) {
188
201
  return answers.runner === SUPPORTED_PACKAGES.runner[1].value;
189
202
  }
203
+ export function usesSerenity(answers) {
204
+ return answers.framework.includes('serenity-js');
205
+ }
190
206
  function getTestingPurpose(answers) {
191
207
  return convertPackageHashToObject(answers.runner).purpose;
192
208
  }
@@ -235,12 +251,17 @@ export const QUESTIONNAIRE = [{
235
251
  * Only show if Testing Library has an add-on for framework
236
252
  */
237
253
  answers.preset && TESTING_LIBRARY_PACKAGES[convertPackageHashToObject(answers.preset).short])
254
+ }, {
255
+ type: 'list',
256
+ name: 'electronBuildTool',
257
+ message: 'Which tool are you using to build your Electron app?',
258
+ choices: Object.values(ElectronBuildToolChoice),
259
+ when: /* instanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron'
238
260
  }, {
239
261
  type: 'input',
240
- name: 'appPath',
241
- message: 'What is the path to your compiled Electron app?',
242
- default: './dist',
243
- when: /* istanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron'
262
+ name: 'electronAppBinaryPath',
263
+ message: 'What is the path to the binary of your built Electron app?',
264
+ when: /* istanbul ignore next */ (answers) => getTestingPurpose(answers) === 'electron' && (answers.electronBuildTool === ElectronBuildToolChoice.SomethingElse)
244
265
  }, {
245
266
  type: 'list',
246
267
  name: 'backend',
@@ -421,7 +442,7 @@ export const QUESTIONNAIRE = [{
421
442
  }, {
422
443
  type: 'input',
423
444
  name: 'specs',
424
- message: 'Where should these files be located?',
445
+ message: 'What should be the location of your spec files?',
425
446
  default: /* istanbul ignore next */ (answers) => {
426
447
  const pattern = isBrowserRunner(answers) ? 'src/**/*.test' : 'test/specs/**/*';
427
448
  return getDefaultFiles(answers, pattern);
@@ -430,13 +451,13 @@ export const QUESTIONNAIRE = [{
430
451
  }, {
431
452
  type: 'input',
432
453
  name: 'specs',
433
- message: 'Where should these feature files be located?',
454
+ message: 'What should be the location of your feature files?',
434
455
  default: (answers) => getDefaultFiles(answers, 'features/**/*.feature'),
435
456
  when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && answers.framework.includes('cucumber')
436
457
  }, {
437
458
  type: 'input',
438
459
  name: 'stepDefinitions',
439
- message: 'Where should these step definitions be located?',
460
+ message: 'What should be the location of your step definitions?',
440
461
  default: (answers) => getDefaultFiles(answers, 'features/step-definitions/steps'),
441
462
  when: /* istanbul ignore next */ (answers) => answers.generateTestFiles && answers.framework.includes('cucumber')
442
463
  }, {
@@ -453,7 +474,12 @@ export const QUESTIONNAIRE = [{
453
474
  * and also not needed when running VS Code tests since the service comes with
454
475
  * its own page object implementation, nor when running Electron or MacOS tests
455
476
  */
456
- !['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))
457
483
  }, {
458
484
  type: 'input',
459
485
  name: 'pages',
@@ -462,6 +488,17 @@ export const QUESTIONNAIRE = [{
462
488
  ? getDefaultFiles(answers, 'test/pageobjects/**/*')
463
489
  : getDefaultFiles(answers, 'features/pageobjects/**/*')),
464
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)
465
502
  }, {
466
503
  type: 'checkbox',
467
504
  name: 'reporters',
@@ -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
+ }