@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.
- package/build/commands/config.d.ts.map +1 -1
- package/build/commands/config.js +13 -5
- package/build/constants.d.ts +19 -1
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +52 -15
- 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 +11 -2
- 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/snippets/services.ejs +1 -12
- package/build/templates/wdio.conf.tpl.ejs +10 -62
- package/build/types.d.ts +8 -2
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +6 -4
- package/build/utils.d.ts.map +1 -1
- package/build/utils.js +102 -21
- package/package.json +8 -8
|
@@ -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: {
|
|
@@ -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;
|
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' },
|
|
@@ -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: '
|
|
241
|
-
message: 'What is the path to your
|
|
242
|
-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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 %>
|
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
|
+
}
|