codeceptjs 3.0.4 → 3.0.5

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 (45) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/docs/build/Appium.js +1 -1
  3. package/docs/build/Nightmare.js +4 -5
  4. package/docs/build/Playwright.js +41 -15
  5. package/docs/build/Protractor.js +1 -1
  6. package/docs/build/Puppeteer.js +1 -1
  7. package/docs/build/REST.js +20 -1
  8. package/docs/build/TestCafe.js +1 -1
  9. package/docs/build/WebDriver.js +3 -3
  10. package/docs/changelog.md +34 -0
  11. package/docs/data.md +5 -5
  12. package/docs/detox.md +2 -2
  13. package/docs/docker.md +11 -11
  14. package/docs/helpers/Appium.md +1 -1
  15. package/docs/helpers/Nightmare.md +4 -5
  16. package/docs/helpers/Playwright.md +81 -61
  17. package/docs/helpers/Protractor.md +1 -1
  18. package/docs/helpers/Puppeteer.md +1 -1
  19. package/docs/helpers/REST.md +9 -0
  20. package/docs/helpers/TestCafe.md +1 -1
  21. package/docs/helpers/WebDriver.md +1 -1
  22. package/docs/locators.md +2 -2
  23. package/docs/mobile-react-native-locators.md +2 -2
  24. package/docs/mobile.md +3 -3
  25. package/docs/pageobjects.md +3 -1
  26. package/docs/reports.md +3 -3
  27. package/docs/typescript.md +47 -5
  28. package/docs/webapi/fillField.mustache +1 -1
  29. package/lib/cli.js +16 -10
  30. package/lib/command/interactive.js +6 -7
  31. package/lib/config.js +6 -1
  32. package/lib/helper/Nightmare.js +1 -1
  33. package/lib/helper/Playwright.js +8 -16
  34. package/lib/helper/REST.js +20 -1
  35. package/lib/helper/WebDriver.js +2 -2
  36. package/lib/interfaces/gherkin.js +9 -3
  37. package/lib/output.js +2 -2
  38. package/lib/plugin/allure.js +3 -7
  39. package/lib/plugin/screenshotOnFail.js +1 -2
  40. package/lib/step.js +2 -1
  41. package/lib/within.js +1 -1
  42. package/lib/workers.js +13 -1
  43. package/package.json +5 -5
  44. package/typings/index.d.ts +7 -2
  45. package/typings/types.d.ts +55 -21
@@ -19,10 +19,10 @@ Example:
19
19
 
20
20
  ![Quick Info](/img/Quick_info.gif)
21
21
 
22
- - Checks types - thanks to TypeScript support in CodeceptJS now allow to tests your tests. TypeScript can prevent some errors:
22
+ - Checks types - thanks to TypeScript support in CodeceptJS now allow to tests your tests. TypeScript can prevent some errors:
23
23
  - invalid type of variables passed to function;
24
24
  - calls no-exist method from PageObject or `I` object;
25
- - incorrectly used CodeceptJS features;
25
+ - incorrectly used CodeceptJS features;
26
26
 
27
27
 
28
28
  ## Getting Started
@@ -106,7 +106,7 @@ declare namespace CodeceptJS {
106
106
 
107
107
  ## Types for custom helper or page object
108
108
 
109
- If you want to get types for your [custom helper](https://codecept.io/helpers/#configuration), you can add their automatically with CodeceptJS command `npx codeceptjs def`.
109
+ If you want to get types for your [custom helper](https://codecept.io/helpers/#configuration), you can add their automatically with CodeceptJS command `npx codeceptjs def`.
110
110
 
111
111
  For example, if you add the new step `printMessage` for your custom helper like this:
112
112
  ```js
@@ -121,9 +121,9 @@ export = CustomHelper
121
121
  ```
122
122
 
123
123
  Then you need to add this helper to your `codecept.conf.js` like in this [docs](https://codecept.io/helpers/#configuration).
124
- And then run the command `npx codeceptjs def`.
124
+ And then run the command `npx codeceptjs def`.
125
125
 
126
- As result our `steps.d.ts` file will be updated like this:
126
+ As result our `steps.d.ts` file will be updated like this:
127
127
  ```ts
128
128
  /// <reference types='codeceptjs' />
129
129
  type CustomHelper = import('./CustomHelper');
@@ -156,3 +156,45 @@ declare namespace CodeceptJS {
156
156
  }
157
157
  }
158
158
  ```
159
+
160
+ ## Types for custom strict locators
161
+
162
+ You can define [custom strict locators](https://codecept.io/locators/#custom-strict-locators) that can be used in all methods taking a locator (parameter type `LocatorOrString`).
163
+
164
+ Example: A custom strict locator with a `data` property, which can be used like this:
165
+
166
+ ```ts
167
+ I.click({ data: 'user-login' });
168
+ ```
169
+
170
+ In order to use the custom locator in TypeScript code, its type shape needs to be registered in the interface `CustomLocators` in your `steps.d.ts` file:
171
+
172
+ ```ts
173
+ /// <reference types='codeceptjs' />
174
+ ...
175
+
176
+ declare namespace CodeceptJS {
177
+ ...
178
+
179
+ interface CustomLocators {
180
+ data: { data: string };
181
+ }
182
+ }
183
+ ```
184
+
185
+ The property keys used in the `CustomLocators` interface do not matter (only the *types* of the interface properties are used). For simplicity it is recommended to use the name that is also used in your custom locator itself.
186
+
187
+ You can also define more complicated custom locators with multiple (also optional) properties:
188
+
189
+ ```ts
190
+ /// <reference types='codeceptjs' />
191
+ ...
192
+
193
+ declare namespace CodeceptJS {
194
+ ...
195
+
196
+ interface CustomLocators {
197
+ data: { data: string, value?: number, flag?: boolean };
198
+ }
199
+ }
200
+ ```
@@ -12,4 +12,4 @@ I.fillField('form#login input[name=username]', 'John');
12
12
  I.fillField({css: 'form#login input[name=username]'}, 'John');
13
13
  ```
14
14
  @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
15
- @param {string} value text value to fill.
15
+ @param {CodeceptJS.StringOrSecret} value text value to fill.
package/lib/cli.js CHANGED
@@ -3,6 +3,7 @@ const ms = require('ms');
3
3
  const event = require('./event');
4
4
  const AssertionFailedError = require('./assert/error');
5
5
  const output = require('./output');
6
+ const { MetaStep } = require('./step');
6
7
 
7
8
  const cursor = Base.cursor;
8
9
  let currentMetaStep = [];
@@ -77,17 +78,22 @@ class Cli extends Base {
77
78
  });
78
79
 
79
80
  event.dispatcher.on(event.step.started, (step) => {
80
- output.stepShift = 3;
81
- const printMetaStep = (metaStep) => {
82
- if (!metaStep) return currentMetaStep.shift();
83
- if (currentMetaStep.indexOf(metaStep.toString()) >= 0) return; // step is the same
84
- if (metaStep.metaStep) {
85
- printMetaStep(metaStep.metaStep);
81
+ let processingStep = step;
82
+ const metaSteps = [];
83
+ while (processingStep.metaStep) {
84
+ metaSteps.unshift(processingStep.metaStep);
85
+ processingStep = processingStep.metaStep;
86
+ }
87
+ const shift = metaSteps.length;
88
+
89
+ for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
90
+ if (currentMetaStep[i] !== metaSteps[i]) {
91
+ output.stepShift = 3 + 2 * i;
92
+ if (metaSteps[i]) output.step(metaSteps[i]);
86
93
  }
87
- currentMetaStep.unshift(metaStep.toString());
88
- output.step(metaStep);
89
- };
90
- printMetaStep(step.metaStep);
94
+ }
95
+ currentMetaStep = metaSteps;
96
+ output.stepShift = 3 + 2 * shift;
91
97
  output.step(step);
92
98
  });
93
99
 
@@ -4,7 +4,7 @@ const Codecept = require('../codecept');
4
4
  const event = require('../event');
5
5
  const output = require('../output');
6
6
 
7
- module.exports = function (path, options) {
7
+ module.exports = async function (path, options) {
8
8
  // Backward compatibility for --profile
9
9
  process.profile = options.profile;
10
10
  process.env.profile = options.profile;
@@ -14,11 +14,8 @@ module.exports = function (path, options) {
14
14
  const codecept = new Codecept(config, options);
15
15
  codecept.init(testsPath);
16
16
 
17
- codecept.runBootstrap((err) => {
18
- if (err) {
19
- output.error(`Error while running bootstrap file :${err}`);
20
- return;
21
- }
17
+ try {
18
+ await codecept.bootstrap();
22
19
 
23
20
  if (options.verbose) output.level(3);
24
21
 
@@ -36,5 +33,7 @@ module.exports = function (path, options) {
36
33
  recorder.add(() => event.emit(event.suite.after, {}));
37
34
  recorder.add(() => event.emit(event.all.result, {}));
38
35
  recorder.add(() => codecept.teardown());
39
- });
36
+ } catch (err) {
37
+ output.error(`Error while running bootstrap file :${err}`);
38
+ }
40
39
  };
package/lib/config.js CHANGED
@@ -77,7 +77,12 @@ class Config {
77
77
  return loadConfigFile(jsonConfig);
78
78
  }
79
79
 
80
- throw new Error(`Can not load config from ${jsConfig} or ${jsonConfig}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`);
80
+ const tsConfig = path.join(configFile, 'codecept.conf.ts');
81
+ if (isFile(tsConfig)) {
82
+ return loadConfigFile(tsConfig);
83
+ }
84
+
85
+ throw new Error(`Can not load config from ${jsConfig} or ${jsonConfig} or ${tsConfig}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`);
81
86
  }
82
87
 
83
88
  /**
@@ -803,7 +803,7 @@ class Nightmare extends Helper {
803
803
  }
804
804
 
805
805
  /**
806
- * {{> grabValueFrom }}
806
+ * {{> grabValueFromAll }}
807
807
  */
808
808
  async grabValueFromAll(locator) {
809
809
  locator = new Locator(locator, 'css');
@@ -800,11 +800,7 @@ class Playwright extends Helper {
800
800
  }
801
801
 
802
802
  /**
803
- * Checks that title is equal to provided one.
804
- *
805
- * ```js
806
- * I.seeTitleEquals('Test title.');
807
- * ```
803
+ * {{> seeTitleEquals }}
808
804
  */
809
805
  async seeTitleEquals(text) {
810
806
  const title = await this.page.title();
@@ -1069,14 +1065,7 @@ class Playwright extends Helper {
1069
1065
  }
1070
1066
 
1071
1067
  /**
1072
- *
1073
- * Force clicks an element without waiting for it to become visible and not animating.
1074
- *
1075
- * ```js
1076
- * I.forceClick('#hiddenButton');
1077
- * I.forceClick('Click me', '#hidden');
1078
- * ```
1079
- *
1068
+ * {{> forceClick }}
1080
1069
  */
1081
1070
  async forceClick(locator, context = null) {
1082
1071
  return proceedClick.call(this, locator, context, { force: true });
@@ -1500,6 +1489,10 @@ class Playwright extends Helper {
1500
1489
  * I.executeScript(([x, y]) => x + y, [x, y]);
1501
1490
  * ```
1502
1491
  * If a function returns a Promise it will wait for its resolution.
1492
+ *
1493
+ * @param {string|function} fn function to be executed in browser context.
1494
+ * @param {any} [arg] optional argument to pass to the function
1495
+ * @return {Promise<any>}
1503
1496
  */
1504
1497
  async executeScript(fn, arg) {
1505
1498
  let context = this.page;
@@ -1590,8 +1583,7 @@ class Playwright extends Helper {
1590
1583
  async grabCssPropertyFromAll(locator, cssProperty) {
1591
1584
  const els = await this._locate(locator);
1592
1585
  this.debug(`Matched ${els.length} elements`);
1593
- const res = await Promise.all(els.map(el => el.$eval('xpath=.', el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
1594
- const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
1586
+ const cssValues = await Promise.all(els.map(el => el.$eval('xpath=.', (el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty)));
1595
1587
 
1596
1588
  return cssValues;
1597
1589
  }
@@ -2083,7 +2075,7 @@ class Playwright extends Helper {
2083
2075
  /**
2084
2076
  * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2085
2077
  *
2086
- * See [Pupeteer's reference](https://github.com/microsoft/Playwright/blob/master/docs/api.md#pagewaitfornavigationoptions)
2078
+ * See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
2087
2079
  *
2088
2080
  * @param {*} opts
2089
2081
  */
@@ -1,4 +1,5 @@
1
1
  const axios = require('axios').default;
2
+ const Secret = require('../secret');
2
3
 
3
4
  const Helper = require('../helper');
4
5
 
@@ -75,12 +76,18 @@ class REST extends Helper {
75
76
  * @param {*} request
76
77
  */
77
78
  async _executeRequest(request) {
79
+ const _debugRequest = { ...request };
78
80
  axios.defaults.timeout = request.timeout || this.options.timeout;
79
81
 
80
82
  if (this.headers && this.headers.auth) {
81
83
  request.auth = this.headers.auth;
82
84
  }
83
85
 
86
+ if (request.data instanceof Secret) {
87
+ _debugRequest.data = '*****';
88
+ request.data = typeof request.data === 'object' ? { ...request.data.toString() } : request.data.toString();
89
+ }
90
+
84
91
  if ((typeof request.data) === 'string') {
85
92
  if (!request.headers || !request.headers['Content-Type']) {
86
93
  request.headers = { ...request.headers, ...{ 'Content-Type': 'application/x-www-form-urlencoded' } };
@@ -91,7 +98,7 @@ class REST extends Helper {
91
98
  await this.config.onRequest(request);
92
99
  }
93
100
 
94
- this.debugSection('Request', JSON.stringify(request));
101
+ this.debugSection('Request', JSON.stringify(_debugRequest));
95
102
 
96
103
  let response;
97
104
  try {
@@ -149,6 +156,10 @@ class REST extends Helper {
149
156
  *
150
157
  * ```js
151
158
  * I.sendPostRequest('/api/users.json', { "email": "user@user.com" });
159
+ *
160
+ * // To mask the payload in logs
161
+ * I.sendPostRequest('/api/users.json', secret({ "email": "user@user.com" }));
162
+ *
152
163
  * ```
153
164
  *
154
165
  * @param {*} url
@@ -176,6 +187,10 @@ class REST extends Helper {
176
187
  *
177
188
  * ```js
178
189
  * I.sendPatchRequest('/api/users.json', { "email": "user@user.com" });
190
+ *
191
+ * // To mask the payload in logs
192
+ * I.sendPatchRequest('/api/users.json', secret({ "email": "user@user.com" }));
193
+ *
179
194
  * ```
180
195
  *
181
196
  * @param {string} url
@@ -203,6 +218,10 @@ class REST extends Helper {
203
218
  *
204
219
  * ```js
205
220
  * I.sendPutRequest('/api/users.json', { "email": "user@user.com" });
221
+ *
222
+ * // To mask the payload in logs
223
+ * I.sendPutRequest('/api/users.json', secret({ "email": "user@user.com" }));
224
+ *
206
225
  * ```
207
226
  *
208
227
  * @param {string} url
@@ -718,7 +718,7 @@ class WebDriver extends Helper {
718
718
  * @param {object} locator
719
719
  */
720
720
  async _smartWait(locator) {
721
- this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
721
+ this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`);
722
722
  await this.defineTimeout({ implicit: this.options.smartWait });
723
723
  }
724
724
 
@@ -1370,7 +1370,7 @@ class WebDriver extends Helper {
1370
1370
  */
1371
1371
  async grabBrowserLogs() {
1372
1372
  if (this.browser.isW3C) {
1373
- this.debug('Logs not awailable in W3C specification');
1373
+ this.debug('Logs not available in W3C specification');
1374
1374
  return;
1375
1375
  }
1376
1376
  return this.browser.getLogs('browser');
@@ -31,7 +31,13 @@ module.exports = (text) => {
31
31
  const metaStep = new Step.MetaStep(null, step.text);
32
32
  metaStep.actor = step.keyword.trim();
33
33
  const setMetaStep = (step) => {
34
- if (step.metaStep) step = step.metaStep; // assign metastep to metastep for nested steps
34
+ if (step.metaStep) {
35
+ if (step.metaStep === metaStep) {
36
+ return;
37
+ }
38
+ setMetaStep(step.metaStep);
39
+ return;
40
+ }
35
41
  step.metaStep = metaStep;
36
42
  };
37
43
  const fn = matchStep(step.text);
@@ -46,14 +52,14 @@ module.exports = (text) => {
46
52
  step.startTime = Date.now();
47
53
  step.match = fn.line;
48
54
  event.emit(event.bddStep.before, step);
49
- event.dispatcher.on(event.step.before, setMetaStep);
55
+ event.dispatcher.prependListener(event.step.before, setMetaStep);
50
56
  try {
51
57
  await fn(...fn.params);
52
58
  step.status = 'passed';
53
59
  } catch (err) {
54
60
  step.status = 'failed';
55
61
  step.err = err;
56
- return err;
62
+ throw err;
57
63
  } finally {
58
64
  step.endTime = Date.now();
59
65
  event.dispatcher.removeListener(event.step.before, setMetaStep);
package/lib/output.js CHANGED
@@ -102,14 +102,14 @@ module.exports = {
102
102
  if (!step) return;
103
103
  // Avoid to print non-gherkin steps, when gherkin is running for --steps mode
104
104
  if (outputLevel === 1) {
105
- if (!step.isMetaStep() && step.hasBDDAncestor()) {
105
+ if (step.hasBDDAncestor()) {
106
106
  return;
107
107
  }
108
108
  }
109
109
 
110
110
  let stepLine = step.toString();
111
111
  if (step.metaStep && outputLevel >= 1) {
112
- this.stepShift += 2;
112
+ // this.stepShift += 2;
113
113
  stepLine = colors.green(truncate(stepLine, this.spaceShift));
114
114
  }
115
115
  if (step.comment) {
@@ -261,13 +261,9 @@ module.exports = (config) => {
261
261
  if (isHookSteps === false) {
262
262
  startMetaStep(step.metaStep);
263
263
  if (currentStep !== step) {
264
- // The reason we need to do this cause
265
- // the step actor contains this and hence
266
- // the allure reporter could not parse and
267
- // generate the report
268
- if (step.actor.includes('\u001b[36m')) {
269
- step.actor = step.actor.replace('\u001b[36m', '').replace('\u001b[39m', '');
270
- }
264
+ // In multi-session scenarios, actors' names will be highlighted with ANSI
265
+ // escape sequences which are invalid XML values
266
+ step.actor = step.actor.replace(ansiRegExp(), '');
271
267
  reporter.startStep(step.toString());
272
268
  currentStep = step;
273
269
  }
@@ -99,8 +99,7 @@ module.exports = function (config) {
99
99
  await helper.saveScreenshot(fileName, options.fullPageScreenshots);
100
100
 
101
101
  test.artifacts.screenshot = path.join(global.output_dir, fileName);
102
-
103
- if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].attachments) {
102
+ if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
104
103
  test.attachments = [path.join(global.output_dir, fileName)];
105
104
  }
106
105
 
package/lib/step.js CHANGED
@@ -208,9 +208,10 @@ class MetaStep extends Step {
208
208
  let result;
209
209
 
210
210
  const registerStep = (step) => {
211
+ this.metaStep = null;
211
212
  step.metaStep = this;
212
213
  };
213
- event.dispatcher.on(event.step.before, registerStep);
214
+ event.dispatcher.prependListener(event.step.before, registerStep);
214
215
  try {
215
216
  this.startTime = Date.now();
216
217
  result = fn.apply(this.context, this.args);
package/lib/within.js CHANGED
@@ -20,7 +20,7 @@ function within(context, fn) {
20
20
  const defineMetaStep = step => step.metaStep = metaStep;
21
21
  recorder.session.start('within');
22
22
 
23
- event.dispatcher.on(event.step.before, defineMetaStep);
23
+ event.dispatcher.prependListener(event.step.before, defineMetaStep);
24
24
 
25
25
  Object.keys(helpers).forEach((helper) => {
26
26
  if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context));
package/lib/workers.js CHANGED
@@ -318,11 +318,21 @@ class Workers extends EventEmitter {
318
318
  _listenWorkerEvents(worker) {
319
319
  worker.on('message', (message) => {
320
320
  output.process(message.workerIndex);
321
+
321
322
  switch (message.event) {
323
+ case event.suite.before:
324
+ this.emit(event.suite.before, repackTest(message.data));
325
+ break;
322
326
  case event.hook.failed:
323
327
  this.emit(event.hook.failed, repackTest(message.data));
324
328
  this.errors.push(message.data.err);
325
329
  break;
330
+ case event.test.before:
331
+ this.emit(event.test.before, repackTest(message.data));
332
+ break;
333
+ case event.test.started:
334
+ this.emit(event.test.started, repackTest(message.data));
335
+ break;
326
336
  case event.test.failed:
327
337
  this._updateFinishedTests(repackTest(message.data));
328
338
  this.emit(event.test.failed, repackTest(message.data));
@@ -335,7 +345,9 @@ class Workers extends EventEmitter {
335
345
  this._updateFinishedTests(repackTest(message.data));
336
346
  this.emit(event.test.skipped, repackTest(message.data));
337
347
  break;
338
- case event.test.finished: this.emit(event.test.finished, repackTest(message.data)); break;
348
+ case event.test.finished:
349
+ this.emit(event.test.finished, repackTest(message.data));
350
+ break;
339
351
  case event.test.after:
340
352
  this.emit(event.test.after, repackTest(message.data));
341
353
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -55,11 +55,11 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@codeceptjs/configure": "^0.6.2",
58
- "@codeceptjs/helper": "^1.0.1",
58
+ "@codeceptjs/helper": "^1.0.2",
59
59
  "acorn": "^7.4.1",
60
60
  "allure-js-commons": "^1.3.2",
61
61
  "arrify": "^2.0.1",
62
- "axios": "^0.19.1",
62
+ "axios": "^0.21.1",
63
63
  "chalk": "^4.1.0",
64
64
  "commander": "^2.20.3",
65
65
  "cross-spawn": "^7.0.3",
@@ -90,8 +90,8 @@
90
90
  "devDependencies": {
91
91
  "@codeceptjs/detox-helper": "^1.0.2",
92
92
  "@codeceptjs/mock-request": "^0.3.0",
93
- "@pollyjs/adapter-puppeteer": "^2.6.3",
94
- "@pollyjs/core": "^2.6.3",
93
+ "@pollyjs/adapter-puppeteer": "^5.1.0",
94
+ "@pollyjs/core": "^5.1.0",
95
95
  "@types/inquirer": "^0.0.35",
96
96
  "@types/node": "^8.10.66",
97
97
  "@wdio/sauce-service": "^5.22.5",
@@ -22,7 +22,9 @@ declare namespace CodeceptJS {
22
22
  interface I {}
23
23
  interface IHook {}
24
24
  interface IScenario {}
25
- interface IFeature {}
25
+ interface IFeature {
26
+ (title: string): FeatureConfig
27
+ }
26
28
  interface CallbackOrder extends Array<any> {}
27
29
  interface SupportObject {
28
30
  I: CodeceptJS.I;
@@ -52,7 +54,10 @@ declare namespace CodeceptJS {
52
54
  | { android: string, ios: string }
53
55
  | { react: string };
54
56
 
55
- type LocatorOrString = string | ILocator | Locator;
57
+ interface CustomLocators { }
58
+ type LocatorOrString = string | ILocator | Locator | CustomLocators[keyof CustomLocators];
59
+
60
+ type StringOrSecret = string | CodeceptJS.Secret;
56
61
 
57
62
  interface HookCallback { (args: SupportObject): void; }
58
63
  interface Scenario extends IScenario { only: IScenario, skip: IScenario, todo: IScenario}