@wdio/cli 8.17.0 → 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.
- package/build/commands/config.d.ts.map +1 -1
- package/build/commands/config.js +13 -5
- package/build/constants.d.ts +9 -3
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +40 -23
- package/build/templates/EjsHelpers.d.ts +18 -0
- package/build/templates/EjsHelpers.d.ts.map +1 -0
- package/build/templates/EjsHelpers.js +59 -0
- package/build/templates/EjsHelpers.ts +84 -0
- package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +1 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +41 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +100 -0
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +36 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +37 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +18 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +23 -0
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +17 -0
- package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +86 -0
- package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +88 -0
- package/build/templates/snippets/capabilities.ejs +9 -3
- package/build/templates/snippets/cucumber.ejs +48 -0
- package/build/templates/snippets/jasmine.ejs +20 -0
- package/build/templates/snippets/mocha.ejs +14 -0
- package/build/templates/snippets/serenity.ejs +18 -0
- package/build/templates/wdio.conf.tpl.ejs +11 -86
- package/build/types.d.ts +5 -1
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +4 -2
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +102 -21
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"
|
|
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"}
|
package/build/commands/config.js
CHANGED
|
@@ -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,
|
|
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(
|
|
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) {
|
package/build/constants.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/build/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"
|
|
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"}
|
package/build/constants.js
CHANGED
|
@@ -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
|
|
15
|
-
🤖 Successfully setup project at
|
|
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
|
|
25
|
-
$ npm run
|
|
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: '
|
|
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
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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',
|
|
@@ -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 %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs
ADDED
|
@@ -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
|
+
<% } %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs
ADDED
|
@@ -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
|
+
<% } %>
|
package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs
ADDED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%- _.import('Given, When, Then, type DataTable', '@cucumber/cucumber') %>
|
|
2
|
+
<%- _.import('Ensure, equals', '@serenity-js/assertions') %>
|
|
3
|
+
<%- _.import('type Actor', '@serenity-js/core') %>
|
|
4
|
+
<%- _.import('TodoList', '../../serenity/todo-list-app/TodoList') %>
|
|
5
|
+
|
|
6
|
+
Given('{actor} starts/started with a list containing:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
7
|
+
await actor.attemptsTo(
|
|
8
|
+
TodoList.createListContaining(itemsFrom(table)),
|
|
9
|
+
)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
When('{pronoun} marks/marked the following item(s) as completed:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
13
|
+
await actor.attemptsTo(
|
|
14
|
+
TodoList.markAsCompleted(itemsFrom(table)),
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
When('{pronoun} marks/marked the following item(s) as outstanding:', async (<%- _.param('actor', 'Actor') %>, <%- _.param('table', 'DataTable') %>) => {
|
|
19
|
+
await actor.attemptsTo(
|
|
20
|
+
TodoList.markAsOutstanding(itemsFrom(table)),
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Then('{pronoun} should see that she has {int} item(s) outstanding', async (<%- _.param('actor', 'Actor') %>, <%- _.param('expectedCount', 'number') %>) => {
|
|
25
|
+
await actor.attemptsTo(
|
|
26
|
+
Ensure.that(TodoList.outstandingItemsCount(), equals(expectedCount)),
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extracts the data from a single-column Cucumber DataTable and returns it as an `Array<string>`
|
|
32
|
+
*
|
|
33
|
+
* @param table
|
|
34
|
+
*/
|
|
35
|
+
function itemsFrom(<%- _.param('table', 'DataTable') %>)<%- _.returns('string[]') %> {
|
|
36
|
+
return table.raw().map(row => row[0])
|
|
37
|
+
}
|