codeceptjs 2.3.4 → 2.4.1
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/CHANGELOG.md +52 -0
- package/README.md +28 -6
- package/bin/codecept.js +42 -0
- package/docs/advanced.md +45 -1
- package/docs/angular.md +3 -3
- package/docs/basics.md +162 -118
- package/docs/bdd.md +30 -5
- package/docs/best.md +8 -6
- package/docs/books.md +6 -1
- package/docs/build/Appium.js +95 -85
- package/docs/build/FileSystem.js +48 -3
- package/docs/build/GraphQL.js +3 -2
- package/docs/build/GraphQLDataFactory.js +1 -0
- package/docs/build/Mochawesome.js +3 -2
- package/docs/build/MockRequest.js +23 -5
- package/docs/build/Nightmare.js +87 -128
- package/docs/build/Protractor.js +107 -155
- package/docs/build/Puppeteer.js +190 -174
- package/docs/build/REST.js +13 -9
- package/docs/build/SeleniumWebdriver.js +0 -17
- package/docs/build/TestCafe.js +164 -158
- package/docs/build/WebDriver.js +236 -211
- package/docs/build/WebDriverIO.js +218 -187
- package/docs/changelog.md +57 -1
- package/docs/commands.md +41 -2
- package/docs/community-helpers.md +12 -1
- package/docs/configuration.md +5 -2
- package/docs/continuous-integration.md +22 -0
- package/docs/{helpers.md → custom-helpers.md} +16 -10
- package/docs/data.md +7 -6
- package/docs/detox.md +6 -6
- package/docs/email.md +4 -2
- package/docs/examples.md +22 -3
- package/docs/helpers/ApiDataFactory.md +15 -13
- package/docs/helpers/Appium.md +1011 -468
- package/docs/helpers/Detox.md +33 -26
- package/docs/helpers/FileSystem.md +43 -13
- package/docs/helpers/GraphQL.md +17 -15
- package/docs/helpers/GraphQLDataFactory.md +15 -13
- package/docs/helpers/Mochawesome.md +3 -1
- package/docs/helpers/MockRequest.md +37 -19
- package/docs/helpers/Nightmare.md +129 -240
- package/docs/helpers/Polly.md +1 -1
- package/docs/helpers/Protractor.md +157 -298
- package/docs/helpers/Puppeteer.md +216 -335
- package/docs/helpers/REST.md +29 -24
- package/docs/helpers/TestCafe.md +137 -235
- package/docs/helpers/WebDriver.md +250 -347
- package/docs/hooks.md +14 -10
- package/docs/index.md +112 -0
- package/docs/installation.md +3 -1
- package/docs/locators.md +19 -8
- package/docs/mobile-react-native-locators.md +2 -2
- package/docs/mobile.md +5 -3
- package/docs/nightmare.md +2 -1
- package/docs/pageobjects.md +4 -2
- package/docs/parallel.md +4 -2
- package/docs/plugins.md +41 -15
- package/docs/puppeteer.md +8 -6
- package/docs/quickstart.md +130 -0
- package/docs/react.md +4 -2
- package/docs/reports.md +6 -4
- package/docs/testcafe.md +10 -8
- package/docs/translation.md +4 -2
- package/docs/ui.md +56 -0
- package/docs/videos.md +11 -2
- package/docs/visual.md +7 -5
- package/docs/vue.md +121 -0
- package/docs/webapi/appendField.mustache +1 -1
- package/docs/webapi/attachFile.mustache +1 -1
- package/docs/webapi/checkOption.mustache +2 -2
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +2 -2
- package/docs/webapi/clickLink.mustache +2 -2
- package/docs/webapi/dontSee.mustache +1 -2
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/dontSeeElement.mustache +1 -1
- package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/doubleClick.mustache +2 -2
- package/docs/webapi/downloadFile.mustache +1 -1
- package/docs/webapi/dragSlider.mustache +1 -1
- package/docs/webapi/executeAsyncScript.mustache +2 -1
- package/docs/webapi/executeScript.mustache +2 -1
- package/docs/webapi/fillField.mustache +1 -1
- package/docs/webapi/grabAttributeFrom.mustache +1 -1
- package/docs/webapi/grabBrowserLogs.mustache +1 -1
- package/docs/webapi/grabCookie.mustache +2 -2
- package/docs/webapi/grabCssPropertyFrom.mustache +1 -1
- package/docs/webapi/grabHTMLFrom.mustache +1 -1
- package/docs/webapi/grabNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/grabPageScrollPosition.mustache +1 -1
- package/docs/webapi/grabTextFrom.mustache +2 -2
- package/docs/webapi/grabValueFrom.mustache +1 -1
- package/docs/webapi/moveCursorTo.mustache +3 -3
- package/docs/webapi/pressKey.mustache +1 -1
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
- package/docs/webapi/rightClick.mustache +2 -2
- package/docs/webapi/saveScreenshot.mustache +1 -1
- package/docs/webapi/scrollIntoView.mustache +10 -0
- package/docs/webapi/scrollTo.mustache +3 -3
- package/docs/webapi/see.mustache +1 -1
- package/docs/webapi/seeAttributesOnElements.mustache +1 -1
- package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
- package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
- package/docs/webapi/seeElement.mustache +1 -1
- package/docs/webapi/seeElementInDOM.mustache +1 -1
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/webapi/seeNumberOfElements.mustache +1 -1
- package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
- package/docs/webapi/seeTextEquals.mustache +8 -0
- package/docs/webapi/selectOption.mustache +2 -2
- package/docs/webapi/switchTo.mustache +1 -1
- package/docs/webapi/uncheckOption.mustache +2 -2
- package/docs/webapi/waitForClickable.mustache +10 -0
- package/docs/webapi/waitForDetached.mustache +2 -2
- package/docs/webapi/waitForElement.mustache +2 -2
- package/docs/webapi/waitForEnabled.mustache +2 -2
- package/docs/webapi/waitForFunction.mustache +2 -2
- package/docs/webapi/waitForInvisible.mustache +2 -2
- package/docs/webapi/waitForText.mustache +2 -2
- package/docs/webapi/waitForValue.mustache +1 -1
- package/docs/webapi/waitForVisible.mustache +2 -2
- package/docs/webapi/waitInUrl.mustache +1 -1
- package/docs/webapi/waitNumberOfVisibleElements.mustache +2 -2
- package/docs/webapi/waitToHide.mustache +2 -2
- package/docs/webapi/waitUntil.mustache +3 -2
- package/docs/webapi/waitUrlEquals.mustache +1 -1
- package/docs/webdriver.md +20 -18
- package/docs/wiki/.git/FETCH_HEAD +1 -0
- package/docs/wiki/.git/HEAD +1 -0
- package/docs/wiki/.git/ORIG_HEAD +1 -0
- package/docs/wiki/.git/config +11 -0
- package/docs/wiki/.git/description +1 -0
- package/docs/wiki/.git/hooks/applypatch-msg.sample +15 -0
- package/docs/wiki/.git/hooks/commit-msg.sample +24 -0
- package/docs/wiki/.git/hooks/fsmonitor-watchman.sample +114 -0
- package/docs/wiki/.git/hooks/post-update.sample +8 -0
- package/docs/wiki/.git/hooks/pre-applypatch.sample +14 -0
- package/docs/wiki/.git/hooks/pre-commit.sample +49 -0
- package/docs/wiki/.git/hooks/pre-push.sample +53 -0
- package/docs/wiki/.git/hooks/pre-rebase.sample +169 -0
- package/docs/wiki/.git/hooks/pre-receive.sample +24 -0
- package/docs/wiki/.git/hooks/prepare-commit-msg.sample +42 -0
- package/docs/wiki/.git/hooks/update.sample +128 -0
- package/docs/wiki/.git/index +0 -0
- package/docs/wiki/.git/info/exclude +6 -0
- package/docs/wiki/.git/logs/HEAD +4 -0
- package/docs/wiki/.git/logs/refs/heads/master +4 -0
- package/docs/wiki/.git/logs/refs/remotes/origin/HEAD +1 -0
- package/docs/wiki/.git/logs/refs/remotes/origin/master +3 -0
- package/docs/wiki/.git/objects/00/d216b0774d15db2d0a2a0d4ce249b5251acc55 +3 -0
- package/docs/wiki/.git/objects/09/01d87c5241905fdfe3493cfe8f04df4a2685ea +0 -0
- package/docs/wiki/.git/objects/0d/bdd0c20c4deb6a8cc81dbbf32ecf8c09238983 +2 -0
- package/docs/wiki/.git/objects/1a/c29e4fa82422c52392f22f0f2b8d1a759535bf +0 -0
- package/docs/wiki/.git/objects/27/12f92898d3e8f68e229b6cda76570d6c66d781 +0 -0
- package/docs/wiki/.git/objects/2d/dbe22c257166b648928eeb9460ecfb71ba702d +0 -0
- package/docs/wiki/.git/objects/2f/c942ec3773efd2678d9ff98035c61fcded81a1 +0 -0
- package/docs/wiki/.git/objects/40/a2856342c67796b48911a256b764fb06888b94 +5 -0
- package/docs/wiki/.git/objects/47/53181844fc4dc563cf3aa5e80462243cb58d38 +0 -0
- package/docs/wiki/.git/objects/4e/24a95fb2e4f8ffef51f19b694451a205c06f10 +3 -0
- package/docs/wiki/.git/objects/73/31ebd96f3c7e08a9f63f05a25f939afa0d4de1 +0 -0
- package/docs/wiki/.git/objects/86/19cbb2289caa502e33fccf0ed14eecf6ba2ba0 +0 -0
- package/docs/wiki/.git/objects/a4/72f797d9d74b87c9f71a2b1539d75bb07d1e35 +0 -0
- package/docs/wiki/.git/objects/c9/9f3e4bd227d6b050b2e416f9876df49583dbf6 +0 -0
- package/docs/wiki/.git/objects/ca/e609b4ef3e0ef85fcbe0d68d1a58246584b915 +0 -0
- package/docs/wiki/.git/objects/d5/8386ca72f6d550548f3d71d74e3ac73d5ad488 +0 -0
- package/docs/wiki/.git/objects/d9/c6874a6de524bdafeb563a20d847f4fdd59a86 +0 -0
- package/docs/wiki/.git/objects/f1/c944675bb38b40ae553b0be36c14674c79af54 +0 -0
- package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.idx +0 -0
- package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.pack +0 -0
- package/docs/wiki/.git/packed-refs +2 -0
- package/docs/wiki/.git/refs/heads/master +1 -0
- package/docs/wiki/.git/refs/remotes/origin/HEAD +1 -0
- package/docs/wiki/.git/refs/remotes/origin/master +1 -0
- package/docs/wiki/Books-&-Posts.md +27 -0
- package/docs/wiki/Community-Helpers.md +41 -0
- package/docs/wiki/Examples.md +138 -0
- package/docs/wiki/Home.md +11 -0
- package/docs/wiki/Release-process.md +25 -0
- package/docs/wiki/Roadmap.md +23 -0
- package/docs/wiki/Videos.md +19 -0
- package/lib/actor.js +18 -1
- package/lib/assert/error.js +3 -3
- package/lib/codecept.js +9 -6
- package/lib/command/configMigrate.js +7 -6
- package/lib/command/definitions.js +98 -350
- package/lib/command/generate.js +22 -17
- package/lib/command/gherkin/init.js +2 -1
- package/lib/command/gherkin/snippets.js +6 -6
- package/lib/command/gherkin/steps.js +0 -1
- package/lib/command/info.js +40 -0
- package/lib/command/init.js +54 -41
- package/lib/command/run-multiple.js +5 -4
- package/lib/command/run-rerun.js +39 -0
- package/lib/command/run-workers.js +4 -6
- package/lib/command/run.js +8 -18
- package/lib/command/utils.js +23 -2
- package/lib/command/workers/runTests.js +1 -2
- package/lib/config.js +10 -4
- package/lib/container.js +31 -6
- package/lib/data/dataTableArgument.js +31 -0
- package/lib/data/table.js +4 -0
- package/lib/event.js +65 -1
- package/lib/helper/Appium.js +52 -38
- package/lib/helper/FileSystem.js +48 -3
- package/lib/helper/GraphQL.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +1 -0
- package/lib/helper/Mochawesome.js +3 -2
- package/lib/helper/MockRequest.js +23 -5
- package/lib/helper/Nightmare.js +5 -6
- package/lib/helper/Protractor.js +7 -8
- package/lib/helper/Puppeteer.js +76 -20
- package/lib/helper/REST.js +13 -9
- package/lib/helper/SeleniumWebdriver.js +0 -17
- package/lib/helper/TestCafe.js +84 -36
- package/lib/helper/WebDriver.js +113 -59
- package/lib/helper/WebDriverIO.js +43 -59
- package/lib/helper/clientscripts/nightmare.js +66 -4
- package/lib/helper/scripts/isElementClickable.js +24 -0
- package/lib/helper.js +34 -10
- package/lib/history.js +1 -1
- package/lib/hooks.js +2 -1
- package/lib/index.js +19 -0
- package/lib/interfaces/bdd.js +4 -0
- package/lib/interfaces/featureConfig.js +10 -3
- package/lib/interfaces/gherkin.js +6 -2
- package/lib/interfaces/scenarioConfig.js +17 -6
- package/lib/listener/config.js +1 -1
- package/lib/listener/exit.js +6 -0
- package/lib/listener/steps.js +0 -1
- package/lib/listener/trace.js +0 -1
- package/lib/locator.js +67 -2
- package/lib/output.js +53 -0
- package/lib/parser.js +2 -71
- package/lib/pause.js +3 -2
- package/lib/plugin/allure.js +41 -22
- package/lib/plugin/autoLogin.js +4 -1
- package/lib/plugin/pauseOnFail.js +38 -0
- package/lib/plugin/puppeteerCoverage.js +8 -7
- package/lib/plugin/screenshotOnFail.js +13 -8
- package/lib/plugin/stepByStepReport.js +7 -6
- package/lib/plugin/wdio.js +2 -1
- package/lib/recorder.js +85 -7
- package/lib/rerun.js +81 -0
- package/lib/secret.js +6 -0
- package/lib/session.js +9 -2
- package/lib/step.js +37 -2
- package/lib/store.js +5 -1
- package/lib/ui.js +34 -8
- package/lib/utils.js +6 -13
- package/lib/within.js +5 -0
- package/package.json +49 -29
- package/typings/Mocha.d.ts +21 -0
- package/typings/Protractor.d.ts +16 -0
- package/typings/index.d.ts +169 -0
- package/typings/jsdoc.conf.js +34 -0
- package/typings/jsdoc.namespace.js +29 -0
- package/typings/types.d.ts +9827 -0
- package/typings/utils.d.ts +7 -0
- package/docs/acceptance.md +0 -409
- package/docs/api/codecept.md +0 -75
- package/docs/api/config.md +0 -49
- package/docs/api/container.md +0 -66
- package/docs/api/helper.md +0 -116
- package/docs/api/output.md +0 -67
- package/docs/api/recorder.md +0 -63
- package/docs/helpers/SeleniumWebdriver.md +0 -92
- package/docs/helpers/WebDriverIO.md +0 -1671
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
const colors = require('chalk');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
const figures = require('figures');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const mkdirp = require('mkdirp');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
1
8
|
const Container = require('../container');
|
|
2
9
|
const recorder = require('../recorder');
|
|
3
10
|
const event = require('../event');
|
|
4
11
|
const output = require('../output');
|
|
5
|
-
const mkdirp = require('mkdirp');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const figures = require('figures');
|
|
9
|
-
const colors = require('chalk');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
12
|
const { template, deleteDir } = require('../utils');
|
|
12
13
|
|
|
13
14
|
const supportedHelpers = [
|
package/lib/plugin/wdio.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
const debug = require('debug')('codeceptjs:plugin:wdio');
|
|
2
|
+
|
|
1
3
|
const container = require('../container');
|
|
2
4
|
const mainConfig = require('../config');
|
|
3
5
|
const recorder = require('../recorder');
|
|
4
6
|
const event = require('../event');
|
|
5
7
|
const output = require('../output');
|
|
6
|
-
const debug = require('debug')('codeceptjs:plugin:wdio');
|
|
7
8
|
|
|
8
9
|
const defaultConfig = {
|
|
9
10
|
services: [],
|
package/lib/recorder.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
const debug = require('debug')('codeceptjs');
|
|
1
2
|
const promiseRetry = require('promise-retry');
|
|
3
|
+
|
|
2
4
|
const log = require('./output').log;
|
|
3
|
-
const debug = require('debug')('codeceptjs');
|
|
4
5
|
|
|
5
6
|
let promise;
|
|
6
7
|
let running = false;
|
|
@@ -20,11 +21,14 @@ const defaultRetryOptions = {
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Singleton object to record all test steps as promises and run them in chain.
|
|
24
|
+
* @alias recorder
|
|
25
|
+
* @interface
|
|
23
26
|
*/
|
|
24
27
|
module.exports = {
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
|
-
* @
|
|
30
|
+
* @type {Array<Object<string, *>>}
|
|
31
|
+
* @inner
|
|
28
32
|
*/
|
|
29
33
|
retries: [],
|
|
30
34
|
|
|
@@ -32,6 +36,7 @@ module.exports = {
|
|
|
32
36
|
* Start recording promises
|
|
33
37
|
*
|
|
34
38
|
* @api
|
|
39
|
+
* @inner
|
|
35
40
|
*/
|
|
36
41
|
start() {
|
|
37
42
|
running = true;
|
|
@@ -40,10 +45,18 @@ module.exports = {
|
|
|
40
45
|
this.reset();
|
|
41
46
|
},
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @return {boolean}
|
|
50
|
+
* @inner
|
|
51
|
+
*/
|
|
43
52
|
isRunning() {
|
|
44
53
|
return running;
|
|
45
54
|
},
|
|
46
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @return {void}
|
|
58
|
+
* @inner
|
|
59
|
+
*/
|
|
47
60
|
startUnlessRunning() {
|
|
48
61
|
if (!this.isRunning()) {
|
|
49
62
|
this.start();
|
|
@@ -54,7 +67,8 @@ module.exports = {
|
|
|
54
67
|
* Add error handler to catch rejected promises
|
|
55
68
|
*
|
|
56
69
|
* @api
|
|
57
|
-
* @param {
|
|
70
|
+
* @param {function} fn
|
|
71
|
+
* @inner
|
|
58
72
|
*/
|
|
59
73
|
errHandler(fn) {
|
|
60
74
|
errFn = fn;
|
|
@@ -65,6 +79,7 @@ module.exports = {
|
|
|
65
79
|
* Resets recorder to initial state.
|
|
66
80
|
*
|
|
67
81
|
* @api
|
|
82
|
+
* @inner
|
|
68
83
|
*/
|
|
69
84
|
reset() {
|
|
70
85
|
if (promise && running) this.catch();
|
|
@@ -79,9 +94,27 @@ module.exports = {
|
|
|
79
94
|
this.retries = [];
|
|
80
95
|
},
|
|
81
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @name CodeceptJS.recorder~session
|
|
99
|
+
* @type {CodeceptJS.RecorderSession}
|
|
100
|
+
* @inner
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @alias RecorderSession
|
|
105
|
+
* @interface
|
|
106
|
+
*/
|
|
82
107
|
session: {
|
|
108
|
+
/**
|
|
109
|
+
* @type {boolean}
|
|
110
|
+
* @inner
|
|
111
|
+
*/
|
|
83
112
|
running: false,
|
|
84
113
|
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} name
|
|
116
|
+
* @inner
|
|
117
|
+
*/
|
|
85
118
|
start(name) {
|
|
86
119
|
log(`${currentQueue()}Starting <${name}> session`);
|
|
87
120
|
tasks.push('--->');
|
|
@@ -91,6 +124,10 @@ module.exports = {
|
|
|
91
124
|
promise = Promise.resolve();
|
|
92
125
|
},
|
|
93
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {string} name
|
|
129
|
+
* @inner
|
|
130
|
+
*/
|
|
94
131
|
restore(name) {
|
|
95
132
|
tasks.push('<---');
|
|
96
133
|
log(`${currentQueue()}Finalize <${name}> session`);
|
|
@@ -100,6 +137,10 @@ module.exports = {
|
|
|
100
137
|
promise = promise.then(() => oldPromises.pop());
|
|
101
138
|
},
|
|
102
139
|
|
|
140
|
+
/**
|
|
141
|
+
* @param {function} fn
|
|
142
|
+
* @inner
|
|
143
|
+
*/
|
|
103
144
|
catch(fn) {
|
|
104
145
|
promise = promise.catch(fn);
|
|
105
146
|
},
|
|
@@ -110,12 +151,14 @@ module.exports = {
|
|
|
110
151
|
* Adds a promise to a chain.
|
|
111
152
|
* Promise description should be passed as first parameter.
|
|
112
153
|
*
|
|
113
|
-
* @param {
|
|
114
|
-
* @param {
|
|
115
|
-
* @param {
|
|
116
|
-
* @param {boolean} retry -
|
|
154
|
+
* @param {string} taskName
|
|
155
|
+
* @param {function} [fn]
|
|
156
|
+
* @param {boolean} [force=false]
|
|
157
|
+
* @param {boolean} [retry=true] -
|
|
117
158
|
* true: it will retries if `retryOpts` set.
|
|
118
159
|
* false: ignore `retryOpts` and won't retry.
|
|
160
|
+
* @return {Promise<*> | undefined}
|
|
161
|
+
* @inner
|
|
119
162
|
*/
|
|
120
163
|
add(taskName, fn = undefined, force = false, retry = true) {
|
|
121
164
|
if (typeof taskName === 'function') {
|
|
@@ -152,6 +195,11 @@ module.exports = {
|
|
|
152
195
|
});
|
|
153
196
|
},
|
|
154
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {*} opts
|
|
200
|
+
* @return {*}
|
|
201
|
+
* @inner
|
|
202
|
+
*/
|
|
155
203
|
retry(opts) {
|
|
156
204
|
if (!promise) return;
|
|
157
205
|
|
|
@@ -164,6 +212,11 @@ module.exports = {
|
|
|
164
212
|
return this.add(() => this.retries.push(opts));
|
|
165
213
|
},
|
|
166
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @param {function} [customErrFn]
|
|
217
|
+
* @return {Promise<*>}
|
|
218
|
+
* @inner
|
|
219
|
+
*/
|
|
167
220
|
catch(customErrFn) {
|
|
168
221
|
return promise = promise.catch((err) => {
|
|
169
222
|
log(`${currentQueue()}Error | ${err}`);
|
|
@@ -179,6 +232,11 @@ module.exports = {
|
|
|
179
232
|
});
|
|
180
233
|
},
|
|
181
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @param {function} customErrFn
|
|
237
|
+
* @return {Promise<*>}
|
|
238
|
+
* @inner
|
|
239
|
+
*/
|
|
182
240
|
catchWithoutStop(customErrFn) {
|
|
183
241
|
return promise = promise.catch((err) => {
|
|
184
242
|
log(`${currentQueue()}Error | ${err}`);
|
|
@@ -198,6 +256,7 @@ module.exports = {
|
|
|
198
256
|
*
|
|
199
257
|
* @api
|
|
200
258
|
* @param {*} err
|
|
259
|
+
* @inner
|
|
201
260
|
*/
|
|
202
261
|
throw(err) {
|
|
203
262
|
return this.add(`throw error ${err}`, () => {
|
|
@@ -205,16 +264,28 @@ module.exports = {
|
|
|
205
264
|
});
|
|
206
265
|
},
|
|
207
266
|
|
|
267
|
+
/**
|
|
268
|
+
* @param {*} err
|
|
269
|
+
* @inner
|
|
270
|
+
*/
|
|
208
271
|
saveFirstAsyncError(err) {
|
|
209
272
|
if (asyncErr === null) {
|
|
210
273
|
asyncErr = err;
|
|
211
274
|
}
|
|
212
275
|
},
|
|
213
276
|
|
|
277
|
+
/**
|
|
278
|
+
* @return {*}
|
|
279
|
+
* @inner
|
|
280
|
+
*/
|
|
214
281
|
getAsyncErr() {
|
|
215
282
|
return asyncErr;
|
|
216
283
|
},
|
|
217
284
|
|
|
285
|
+
/**
|
|
286
|
+
* @return {void}
|
|
287
|
+
* @inner
|
|
288
|
+
*/
|
|
218
289
|
cleanAsyncErr() {
|
|
219
290
|
asyncErr = null;
|
|
220
291
|
},
|
|
@@ -222,6 +293,7 @@ module.exports = {
|
|
|
222
293
|
/**
|
|
223
294
|
* Stops recording promises
|
|
224
295
|
* @api
|
|
296
|
+
* @inner
|
|
225
297
|
*/
|
|
226
298
|
stop() {
|
|
227
299
|
debug(this.toString());
|
|
@@ -234,6 +306,8 @@ module.exports = {
|
|
|
234
306
|
* Get latest promise in chain.
|
|
235
307
|
*
|
|
236
308
|
* @api
|
|
309
|
+
* @return {Promise<*>}
|
|
310
|
+
* @inner
|
|
237
311
|
*/
|
|
238
312
|
promise() {
|
|
239
313
|
return promise;
|
|
@@ -241,6 +315,8 @@ module.exports = {
|
|
|
241
315
|
|
|
242
316
|
/**
|
|
243
317
|
* Get a list of all chained tasks
|
|
318
|
+
* @return {string}
|
|
319
|
+
* @inner
|
|
244
320
|
*/
|
|
245
321
|
scheduled() {
|
|
246
322
|
return tasks.join('\n');
|
|
@@ -248,6 +324,8 @@ module.exports = {
|
|
|
248
324
|
|
|
249
325
|
/**
|
|
250
326
|
* Get a state of current queue and tasks
|
|
327
|
+
* @return {string}
|
|
328
|
+
* @inner
|
|
251
329
|
*/
|
|
252
330
|
toString() {
|
|
253
331
|
return `Queue: ${currentQueue()}\n\nTasks: ${this.scheduled()}`;
|
package/lib/rerun.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const fsPath = require('path');
|
|
2
|
+
const container = require('./container');
|
|
3
|
+
const event = require('./event');
|
|
4
|
+
const BaseCodecept = require('./codecept');
|
|
5
|
+
const output = require('./output');
|
|
6
|
+
|
|
7
|
+
class CodeceptRerunner extends BaseCodecept {
|
|
8
|
+
runOnce(test) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
// @ts-ignore
|
|
11
|
+
container.createMocha();
|
|
12
|
+
const mocha = container.mocha();
|
|
13
|
+
this.testFiles.forEach((file) => {
|
|
14
|
+
delete require.cache[file];
|
|
15
|
+
});
|
|
16
|
+
mocha.files = this.testFiles;
|
|
17
|
+
if (test) {
|
|
18
|
+
if (!fsPath.isAbsolute(test)) {
|
|
19
|
+
test = fsPath.join(global.codecept_dir, test);
|
|
20
|
+
}
|
|
21
|
+
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
mocha.run((failures) => {
|
|
25
|
+
if (failures === 0) {
|
|
26
|
+
resolve();
|
|
27
|
+
} else {
|
|
28
|
+
reject(new Error(`${failures} tests fail`));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
} catch (e) {
|
|
32
|
+
reject(e);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async runTests(test) {
|
|
38
|
+
const configRerun = this.config.rerun || {};
|
|
39
|
+
const minSuccess = configRerun.minSuccess || 1;
|
|
40
|
+
const maxReruns = configRerun.maxReruns || 1;
|
|
41
|
+
if (minSuccess > maxReruns) throw new Error('minSuccess must be less than maxReruns');
|
|
42
|
+
if (maxReruns === 1) {
|
|
43
|
+
await this.runOnce(test);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let successCounter = 0;
|
|
47
|
+
let rerunsCounter = 0;
|
|
48
|
+
while (rerunsCounter < maxReruns && successCounter < minSuccess) {
|
|
49
|
+
rerunsCounter++;
|
|
50
|
+
try {
|
|
51
|
+
await this.runOnce(test);
|
|
52
|
+
successCounter++;
|
|
53
|
+
output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`);
|
|
56
|
+
console.error(e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (successCounter < minSuccess) {
|
|
60
|
+
throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
run(test) {
|
|
65
|
+
const done = () => {
|
|
66
|
+
event.emit(event.all.result, this);
|
|
67
|
+
event.emit(event.all.after, this);
|
|
68
|
+
};
|
|
69
|
+
event.emit(event.all.before, this);
|
|
70
|
+
return this.runTests(test)
|
|
71
|
+
.then(() => {
|
|
72
|
+
this.teardown(done);
|
|
73
|
+
})
|
|
74
|
+
.catch((e) => {
|
|
75
|
+
this.teardown(done);
|
|
76
|
+
throw e;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = CodeceptRerunner;
|
package/lib/secret.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
/** @param {string} secret */
|
|
1
2
|
class Secret {
|
|
2
3
|
constructor(secret) {
|
|
3
4
|
this._secret = secret;
|
|
4
5
|
}
|
|
5
6
|
|
|
7
|
+
/** @returns {string} */
|
|
6
8
|
toString() {
|
|
7
9
|
return this._secret;
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @param {*} secret
|
|
14
|
+
* @returns {Secret}
|
|
15
|
+
*/
|
|
10
16
|
static secret(secret) {
|
|
11
17
|
return new Secret(secret);
|
|
12
18
|
}
|
package/lib/session.js
CHANGED
|
@@ -15,6 +15,12 @@ const sessionColors = [
|
|
|
15
15
|
|
|
16
16
|
const savedSessions = {};
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {CodeceptJS.LocatorOrString} sessionName
|
|
20
|
+
* @param {Function | Object<string, *>} config
|
|
21
|
+
* @param {Function} [fn]
|
|
22
|
+
* @return {Promise<*> | undefined}
|
|
23
|
+
*/
|
|
18
24
|
function session(sessionName, config, fn) {
|
|
19
25
|
if (typeof config === 'function') {
|
|
20
26
|
if (typeof fn === 'function') {
|
|
@@ -34,12 +40,13 @@ function session(sessionName, config, fn) {
|
|
|
34
40
|
if (!savedSessions[sessionName]) {
|
|
35
41
|
for (const helper in helpers) {
|
|
36
42
|
if (!helpers[helper]._session) continue;
|
|
37
|
-
savedSessions[sessionName] =
|
|
43
|
+
savedSessions[sessionName] = {
|
|
38
44
|
start: () => null,
|
|
39
45
|
stop: () => null,
|
|
40
46
|
loadVars: () => null,
|
|
41
47
|
restoreVars: () => null,
|
|
42
|
-
|
|
48
|
+
...(store.dryRun ? {} : helpers[helper]._session()),
|
|
49
|
+
};
|
|
43
50
|
break;
|
|
44
51
|
}
|
|
45
52
|
|
package/lib/step.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// TODO: place MetaStep in other file, disable rule
|
|
2
|
+
/* eslint-disable max-classes-per-file */
|
|
3
|
+
|
|
1
4
|
const store = require('./store');
|
|
2
5
|
const Secret = require('./secret');
|
|
3
6
|
|
|
@@ -10,30 +13,52 @@ let support;
|
|
|
10
13
|
/**
|
|
11
14
|
* Each command in test executed through `I.` object is wrapped in Step.
|
|
12
15
|
* Step allows logging executed commands and triggers hook before and after step execution.
|
|
16
|
+
* @param {CodeceptJS.Helper} helper
|
|
17
|
+
* @param {string} name
|
|
13
18
|
*/
|
|
14
19
|
class Step {
|
|
15
20
|
constructor(helper, name) {
|
|
21
|
+
/** @member {string} */
|
|
16
22
|
this.actor = 'I'; // I = actor
|
|
23
|
+
/** @member {CodeceptJS.Helper} */
|
|
17
24
|
this.helper = helper; // corresponding helper
|
|
25
|
+
/** @member {string} */
|
|
18
26
|
this.name = name; // name of a step console
|
|
27
|
+
/** @member {string} */
|
|
19
28
|
this.helperMethod = name; // helper method
|
|
29
|
+
/** @member {string} */
|
|
20
30
|
this.status = 'pending';
|
|
31
|
+
/**
|
|
32
|
+
* @member {string} suffix
|
|
33
|
+
* @memberof CodeceptJS.Step#
|
|
34
|
+
*/
|
|
35
|
+
/** @member {string} */
|
|
21
36
|
this.prefix = this.suffix = '';
|
|
37
|
+
/** @member {string} */
|
|
22
38
|
this.comment = '';
|
|
39
|
+
/** @member {Array<*>} */
|
|
23
40
|
this.args = [];
|
|
41
|
+
/** @member {string} */
|
|
24
42
|
this.stack = '';
|
|
25
43
|
this.setTrace();
|
|
26
44
|
}
|
|
27
45
|
|
|
46
|
+
/** @function */
|
|
28
47
|
setTrace() {
|
|
29
48
|
Error.captureStackTrace(this);
|
|
49
|
+
/** @member {MetaStep} */
|
|
30
50
|
this.metaStep = detectMetaStep(this.stack.split('\n'));
|
|
31
51
|
}
|
|
32
52
|
|
|
53
|
+
/** @param {Array<*>} args */
|
|
33
54
|
setArguments(args) {
|
|
34
55
|
this.args = args;
|
|
35
56
|
}
|
|
36
57
|
|
|
58
|
+
/**
|
|
59
|
+
* @param {...any} args
|
|
60
|
+
* @return {*}
|
|
61
|
+
*/
|
|
37
62
|
run() {
|
|
38
63
|
this.args = Array.prototype.slice.call(arguments);
|
|
39
64
|
if (store.dryRun) {
|
|
@@ -51,11 +76,13 @@ class Step {
|
|
|
51
76
|
return result;
|
|
52
77
|
}
|
|
53
78
|
|
|
79
|
+
/** @param {string} status */
|
|
54
80
|
setStatus(status) {
|
|
55
81
|
this.status = status;
|
|
56
82
|
if (this.metaStep) this.metaStep.setStatus(status);
|
|
57
83
|
}
|
|
58
84
|
|
|
85
|
+
/** @return {string} */
|
|
59
86
|
humanize() {
|
|
60
87
|
return this.name
|
|
61
88
|
// insert a space before all caps
|
|
@@ -66,11 +93,13 @@ class Step {
|
|
|
66
93
|
.replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase());
|
|
67
94
|
}
|
|
68
95
|
|
|
96
|
+
/** @return {string} */
|
|
69
97
|
humanizeArgs() {
|
|
70
98
|
return this.args.map((arg) => {
|
|
71
99
|
if (typeof arg === 'string') {
|
|
72
100
|
return `"${arg}"`;
|
|
73
|
-
}
|
|
101
|
+
}
|
|
102
|
+
if (Array.isArray(arg)) {
|
|
74
103
|
try {
|
|
75
104
|
const res = JSON.stringify(arg);
|
|
76
105
|
return res;
|
|
@@ -92,16 +121,19 @@ class Step {
|
|
|
92
121
|
}).join(', ');
|
|
93
122
|
}
|
|
94
123
|
|
|
124
|
+
/** @return {string} */
|
|
95
125
|
line() {
|
|
96
126
|
const lines = this.stack.split('\n');
|
|
97
127
|
if (lines[STACK_LINE]) return lines[STACK_LINE].trim();
|
|
98
128
|
return '';
|
|
99
129
|
}
|
|
100
130
|
|
|
131
|
+
/** @return {string} */
|
|
101
132
|
toString() {
|
|
102
133
|
return `${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`;
|
|
103
134
|
}
|
|
104
135
|
|
|
136
|
+
/** @return {string} */
|
|
105
137
|
toCode() {
|
|
106
138
|
return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`;
|
|
107
139
|
}
|
|
@@ -110,6 +142,7 @@ class Step {
|
|
|
110
142
|
return this.constructor.name === 'MetaStep';
|
|
111
143
|
}
|
|
112
144
|
|
|
145
|
+
/** @return {boolean} */
|
|
113
146
|
hasBDDAncestor() {
|
|
114
147
|
let hasBDD = false;
|
|
115
148
|
let processingStep;
|
|
@@ -127,7 +160,7 @@ class Step {
|
|
|
127
160
|
}
|
|
128
161
|
}
|
|
129
162
|
|
|
130
|
-
|
|
163
|
+
/** @extends Step */
|
|
131
164
|
class MetaStep extends Step {
|
|
132
165
|
constructor(obj, method) {
|
|
133
166
|
super(null, method);
|
|
@@ -141,10 +174,12 @@ class MetaStep extends Step {
|
|
|
141
174
|
setTrace() {
|
|
142
175
|
}
|
|
143
176
|
|
|
177
|
+
/** @return {void} */
|
|
144
178
|
run() {
|
|
145
179
|
}
|
|
146
180
|
}
|
|
147
181
|
|
|
182
|
+
/** @type {Class<MetaStep>} */
|
|
148
183
|
Step.MetaStep = MetaStep;
|
|
149
184
|
|
|
150
185
|
module.exports = Step;
|
package/lib/store.js
CHANGED
package/lib/ui.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
* Module dependencies.
|
|
3
|
-
*/
|
|
4
|
-
|
|
1
|
+
const escapeRe = require('escape-string-regexp');
|
|
5
2
|
const Suite = require('mocha/lib/suite');
|
|
6
3
|
const Test = require('mocha/lib/test');
|
|
4
|
+
|
|
7
5
|
const scenario = require('./scenario');
|
|
8
6
|
const ScenarioConfig = require('./interfaces/scenarioConfig');
|
|
9
7
|
const FeatureConfig = require('./interfaces/featureConfig');
|
|
10
|
-
const escapeRe = require('escape-string-regexp');
|
|
11
8
|
const addDataContext = require('./data/context');
|
|
12
9
|
|
|
13
|
-
|
|
14
10
|
/**
|
|
15
11
|
* Codecept-style interface:
|
|
16
12
|
*
|
|
@@ -21,7 +17,8 @@ const addDataContext = require('./data/context');
|
|
|
21
17
|
* I.see('Hello, '+data.login);
|
|
22
18
|
* });
|
|
23
19
|
*
|
|
24
|
-
* @param {Suite} suite Root suite.
|
|
20
|
+
* @param {Mocha.Suite} suite Root suite.
|
|
21
|
+
* @ignore
|
|
25
22
|
*/
|
|
26
23
|
module.exports = function (suite) {
|
|
27
24
|
const suites = [suite];
|
|
@@ -47,7 +44,7 @@ module.exports = function (suite) {
|
|
|
47
44
|
const test = new Test(title, fn);
|
|
48
45
|
test.fullTitle = () => `${suite.title}: ${test.title}`;
|
|
49
46
|
|
|
50
|
-
test.tags = (suite.tags || []).concat(title.match(/(\@[a-zA-Z0-9-_]+)/g)); // match tags from title
|
|
47
|
+
test.tags = (suite.tags || []).concat(title.match(/(\@[a-zA-Z0-9-_]+)/g) || []); // match tags from title
|
|
51
48
|
test.file = file;
|
|
52
49
|
if (!test.inject) {
|
|
53
50
|
test.inject = {};
|
|
@@ -71,6 +68,10 @@ module.exports = function (suite) {
|
|
|
71
68
|
* Describe a "suite" with the given `title`
|
|
72
69
|
* and callback `fn` containing nested suites
|
|
73
70
|
* and/or tests.
|
|
71
|
+
* @global
|
|
72
|
+
* @param {string} title
|
|
73
|
+
* @param {Object<string, *>} [opts]
|
|
74
|
+
* @returns {FeatureConfig}
|
|
74
75
|
*/
|
|
75
76
|
|
|
76
77
|
context.Feature = function (title, opts) {
|
|
@@ -123,10 +124,12 @@ module.exports = function (suite) {
|
|
|
123
124
|
* Describe a specification or test-case
|
|
124
125
|
* with the given `title` and callback `fn`
|
|
125
126
|
* acting as a thunk.
|
|
127
|
+
* @ignore
|
|
126
128
|
*/
|
|
127
129
|
context.Scenario = addScenario;
|
|
128
130
|
/**
|
|
129
131
|
* Exclusive test-case.
|
|
132
|
+
* @ignore
|
|
130
133
|
*/
|
|
131
134
|
context.Scenario.only = function (title, opts, fn, data) {
|
|
132
135
|
const reString = `^${escapeRe(`${suites[0].title}: ${title}`.replace(/( \| {.+})?$/g, ''))}`;
|
|
@@ -136,11 +139,34 @@ module.exports = function (suite) {
|
|
|
136
139
|
|
|
137
140
|
/**
|
|
138
141
|
* Pending test case.
|
|
142
|
+
* @global
|
|
143
|
+
* @kind constant
|
|
144
|
+
* @type {CodeceptJS.IScenario}
|
|
139
145
|
*/
|
|
140
146
|
context.xScenario = context.Scenario.skip = function (title) {
|
|
141
147
|
return context.Scenario(title, {});
|
|
142
148
|
};
|
|
143
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Pending test case with message: 'Test not implemented!'.
|
|
152
|
+
* @global
|
|
153
|
+
* @kind constant
|
|
154
|
+
* @type {CodeceptJS.IScenario}
|
|
155
|
+
*/
|
|
156
|
+
context.Scenario.todo = function (title, opts = {}, fn) {
|
|
157
|
+
if (typeof opts === 'function' && !fn) {
|
|
158
|
+
fn = opts;
|
|
159
|
+
opts = {};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const skipInfo = {
|
|
163
|
+
message: 'Test not implemented!',
|
|
164
|
+
description: fn ? fn.toString() : '',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return context.Scenario(title, { ...opts, skipInfo });
|
|
168
|
+
};
|
|
169
|
+
|
|
144
170
|
addDataContext(context);
|
|
145
171
|
});
|
|
146
172
|
|
package/lib/utils.js
CHANGED
|
@@ -9,18 +9,6 @@ function isObject(item) {
|
|
|
9
9
|
return item && typeof item === 'object' && !Array.isArray(item);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function flatTail(flatParams, params) {
|
|
13
|
-
const res = [];
|
|
14
|
-
for (const fp of flatParams) {
|
|
15
|
-
for (const p of params) {
|
|
16
|
-
res.push([p].concat(fp));
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return res;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
module.exports.flatTail = flatTail;
|
|
23
|
-
|
|
24
12
|
function deepMerge(target, source) {
|
|
25
13
|
const merge = require('lodash.merge');
|
|
26
14
|
return merge(target, source);
|
|
@@ -222,6 +210,7 @@ function toCamelCase(name) {
|
|
|
222
210
|
return letter.toUpperCase();
|
|
223
211
|
});
|
|
224
212
|
}
|
|
213
|
+
module.exports.toCamelCase = toCamelCase;
|
|
225
214
|
|
|
226
215
|
function convertFontWeightToNumber(name) {
|
|
227
216
|
const fontWeightPatterns = [
|
|
@@ -282,13 +271,17 @@ module.exports.deleteDir = function (dir_path) {
|
|
|
282
271
|
}
|
|
283
272
|
};
|
|
284
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Returns absolute filename to save screenshot.
|
|
276
|
+
* @param fileName {string} - filename.
|
|
277
|
+
*/
|
|
285
278
|
module.exports.screenshotOutputFolder = function (fileName) {
|
|
286
279
|
const fileSep = path.sep;
|
|
287
280
|
|
|
288
281
|
if (!fileName.includes(fileSep) || fileName.includes('record_')) {
|
|
289
282
|
return path.join(global.output_dir, fileName);
|
|
290
283
|
}
|
|
291
|
-
return path.
|
|
284
|
+
return path.resolve(global.codecept_dir, fileName);
|
|
292
285
|
};
|
|
293
286
|
|
|
294
287
|
module.exports.beautify = function (code) {
|
package/lib/within.js
CHANGED
|
@@ -6,6 +6,11 @@ const event = require('./event');
|
|
|
6
6
|
const Step = require('./step');
|
|
7
7
|
const { isAsyncFunction } = require('./utils');
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @param {CodeceptJS.LocatorOrString} context
|
|
11
|
+
* @param {Function} fn
|
|
12
|
+
* @return {Promise<*> | undefined}
|
|
13
|
+
*/
|
|
9
14
|
function within(context, fn) {
|
|
10
15
|
const helpers = store.dryRun ? {} : container.helpers();
|
|
11
16
|
const locator = typeof context === 'object' ? JSON.stringify(context) : context;
|