codeceptjs 3.6.10 → 3.7.0-beta.2
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/README.md +81 -110
- package/bin/codecept.js +2 -2
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +46 -36
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +87 -83
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +5 -25
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +10 -8
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +1 -3
- package/lib/command/init.js +8 -12
- package/lib/command/interactive.js +1 -1
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +10 -10
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +14 -17
- package/lib/container.js +327 -237
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/els.js +177 -0
- package/lib/event.js +1 -0
- package/lib/heal.js +78 -80
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +15 -30
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +57 -37
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +189 -251
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +134 -232
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +103 -162
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/artifacts.js +2 -2
- package/lib/listener/emptyRun.js +58 -0
- package/lib/listener/exit.js +4 -4
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/{timeout.js → globalTimeout.js} +9 -8
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/steps.js +17 -12
- package/lib/listener/store.js +12 -0
- package/lib/mocha/asyncWrapper.js +204 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +257 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +11 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +83 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +24 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +10 -6
- package/lib/mocha/suite.js +55 -0
- package/lib/mocha/test.js +60 -0
- package/lib/mocha/types.d.ts +31 -0
- package/lib/mocha/ui.js +219 -0
- package/lib/output.js +28 -10
- package/lib/pause.js +159 -135
- package/lib/plugin/autoDelay.js +4 -4
- package/lib/plugin/autoLogin.js +6 -7
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/debugErrors.js +2 -2
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +6 -9
- package/lib/plugin/retryFailedStep.js +4 -4
- package/lib/plugin/retryTo.js +2 -2
- package/lib/plugin/screenshotOnFail.js +9 -36
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/stepByStepReport.js +51 -13
- package/lib/plugin/stepTimeout.js +4 -11
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +1 -1
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +142 -121
- package/lib/secret.js +1 -1
- package/lib/step.js +160 -144
- package/lib/store.js +6 -2
- package/lib/template/heal.js +2 -11
- package/lib/utils.js +224 -216
- package/lib/within.js +73 -55
- package/lib/workers.js +265 -261
- package/package.json +46 -47
- package/typings/index.d.ts +172 -184
- package/typings/promiseBasedTypes.d.ts +95 -516
- package/typings/types.d.ts +169 -587
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/mochaFactory.js +0 -113
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
package/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
[](https://stand-with-ukraine.pp.ua)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
3
|
[<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url] [<img src="https://img.shields.io/badge/dockerhub-images-blue.svg?logo=codeceptjs">](https://hub.docker.com/r/codeceptjs/codeceptjs)
|
|
6
|
-
[](https://github.com/codeceptjs/CodeceptJS/edit/3.x/docs/ai.md) [](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
|
|
4
|
+
[](https://github.com/codeceptjs/CodeceptJS/edit/3.x/docs/ai.md) [](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
|
|
7
5
|
|
|
8
6
|
Build Status:
|
|
9
7
|
|
|
@@ -27,28 +25,28 @@ It abstracts browser interaction to simple steps that are written from a user's
|
|
|
27
25
|
A simple test that verifies the "Welcome" text is present on a main page of a site will look like:
|
|
28
26
|
|
|
29
27
|
```js
|
|
30
|
-
Feature('CodeceptJS demo')
|
|
28
|
+
Feature('CodeceptJS demo')
|
|
31
29
|
|
|
32
30
|
Scenario('check Welcome page on site', ({ I }) => {
|
|
33
|
-
I.amOnPage('/')
|
|
34
|
-
I.see('Welcome')
|
|
35
|
-
})
|
|
31
|
+
I.amOnPage('/')
|
|
32
|
+
I.see('Welcome')
|
|
33
|
+
})
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
CodeceptJS tests are:
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
- **Synchronous**. You don't need to care about callbacks or promises or test scenarios which are linear. But, your tests should be linear.
|
|
39
|
+
- Written from **user's perspective**. Every action is a method of `I`. That makes test easy to read, write and maintain even for non-tech persons.
|
|
40
|
+
- Backend **API agnostic**. We don't know which WebDriver implementation is running this test.
|
|
43
41
|
|
|
44
42
|
CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently, CodeceptJS has these helpers:
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
- [**Playwright**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Playwright.md) - is a Node library to automate the Chromium, WebKit and Firefox browsers with a single API.
|
|
45
|
+
- [**Puppeteer**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Puppeteer.md) - uses Google Chrome's Puppeteer for fast headless testing.
|
|
46
|
+
- [**WebDriver**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) - uses [webdriverio](http://webdriver.io/) to run tests via WebDriver or Devtools protocol.
|
|
47
|
+
- [**TestCafe**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/TestCafe.md) - cheap and fast cross-browser test automation.
|
|
48
|
+
- [**Appium**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Appium.md) - for **mobile testing** with Appium
|
|
49
|
+
- [**Detox**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Detox.md) - This is a wrapper on top of Detox library, aimed to unify testing experience for CodeceptJS framework. Detox provides a grey box testing for mobile applications, playing especially well for React Native apps.
|
|
52
50
|
|
|
53
51
|
And more to come...
|
|
54
52
|
|
|
@@ -58,17 +56,16 @@ CodeceptJS is a successor of [Codeception](http://codeception.com), a popular fu
|
|
|
58
56
|
With CodeceptJS your scenario-driven functional and acceptance tests will be as simple and clean as they can be.
|
|
59
57
|
You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
|
|
60
58
|
|
|
61
|
-
|
|
62
59
|
## Features
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
|
|
61
|
+
- 🪄 **AI-powered** with GPT features to assist and heal failing tests.
|
|
62
|
+
- ☕ Based on [Mocha](https://mochajs.org/) testing framework.
|
|
63
|
+
- 💼 Designed for scenario driven acceptance testing in BDD-style.
|
|
64
|
+
- 💻 Uses ES6 natively without transpiler.
|
|
65
|
+
- Also plays nice with TypeScript.
|
|
66
|
+
- </> Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
|
|
67
|
+
- 🌐 Interactive debugging shell: pause test at any point and try different commands in a browser.
|
|
68
|
+
- Easily create tests, pageobjects, stepobjects with CLI generators.
|
|
72
69
|
|
|
73
70
|
## Installation
|
|
74
71
|
|
|
@@ -105,7 +102,8 @@ npx codeceptjs def .
|
|
|
105
102
|
Later you can even automagically update Type Definitions to include your own custom [helpers methods](docs/helpers.md).
|
|
106
103
|
|
|
107
104
|
Note:
|
|
108
|
-
|
|
105
|
+
|
|
106
|
+
- CodeceptJS requires Node.js version `12+` or later.
|
|
109
107
|
|
|
110
108
|
## Usage
|
|
111
109
|
|
|
@@ -116,18 +114,18 @@ Learn CodeceptJS by examples. Let's assume we have CodeceptJS installed and WebD
|
|
|
116
114
|
Let's see how we can handle basic form testing:
|
|
117
115
|
|
|
118
116
|
```js
|
|
119
|
-
Feature('CodeceptJS Demonstration')
|
|
117
|
+
Feature('CodeceptJS Demonstration')
|
|
120
118
|
|
|
121
119
|
Scenario('test some forms', ({ I }) => {
|
|
122
|
-
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
123
|
-
I.fillField('Email', 'hello@world.com')
|
|
124
|
-
I.fillField('Password', secret('123456'))
|
|
125
|
-
I.checkOption('Active')
|
|
126
|
-
I.checkOption('Male')
|
|
127
|
-
I.click('Create User')
|
|
128
|
-
I.see('User is valid')
|
|
129
|
-
I.dontSeeInCurrentUrl('/documentation')
|
|
130
|
-
})
|
|
120
|
+
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
121
|
+
I.fillField('Email', 'hello@world.com')
|
|
122
|
+
I.fillField('Password', secret('123456'))
|
|
123
|
+
I.checkOption('Active')
|
|
124
|
+
I.checkOption('Male')
|
|
125
|
+
I.click('Create User')
|
|
126
|
+
I.see('User is valid')
|
|
127
|
+
I.dontSeeInCurrentUrl('/documentation')
|
|
128
|
+
})
|
|
131
129
|
```
|
|
132
130
|
|
|
133
131
|
All actions are performed by `I` object; assertions functions start with `see` function.
|
|
@@ -172,11 +170,11 @@ The same way you can locate element by name, `CSS` or `XPath` locators in tests:
|
|
|
172
170
|
|
|
173
171
|
```js
|
|
174
172
|
// by name
|
|
175
|
-
I.fillField('user_basic[email]', 'hello@world.com')
|
|
173
|
+
I.fillField('user_basic[email]', 'hello@world.com')
|
|
176
174
|
// by CSS
|
|
177
|
-
I.fillField('#user_basic_email', 'hello@world.com')
|
|
175
|
+
I.fillField('#user_basic_email', 'hello@world.com')
|
|
178
176
|
// don't make us guess locator type, specify it
|
|
179
|
-
I.fillField({css: '#user_basic_email'}, 'hello@world.com')
|
|
177
|
+
I.fillField({ css: '#user_basic_email' }, 'hello@world.com')
|
|
180
178
|
```
|
|
181
179
|
|
|
182
180
|
Other methods like `checkOption`, and `click` work in a similar manner. They can take labels or CSS or XPath locators to find elements to interact.
|
|
@@ -187,9 +185,9 @@ Assertions start with `see` or `dontSee` prefix. In our case we are asserting th
|
|
|
187
185
|
However, we can narrow the search to particular element by providing a second parameter:
|
|
188
186
|
|
|
189
187
|
```js
|
|
190
|
-
I.see('User is valid')
|
|
188
|
+
I.see('User is valid')
|
|
191
189
|
// better to specify context:
|
|
192
|
-
I.see('User is valid', '.alert-success')
|
|
190
|
+
I.see('User is valid', '.alert-success')
|
|
193
191
|
```
|
|
194
192
|
|
|
195
193
|
In this case 'User is valid' string will be searched only inside elements located by CSS `.alert-success`.
|
|
@@ -200,13 +198,13 @@ In case you need to return a value from a webpage and use it directly in test, y
|
|
|
200
198
|
They are expected to be used inside `async/await` functions, and their results will be available in test:
|
|
201
199
|
|
|
202
200
|
```js
|
|
203
|
-
Feature('CodeceptJS Demonstration')
|
|
201
|
+
Feature('CodeceptJS Demonstration')
|
|
204
202
|
|
|
205
203
|
Scenario('test page title', async ({ I }) => {
|
|
206
|
-
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
207
|
-
const title = await I.grabTitle()
|
|
208
|
-
I.expectEqual(title, 'Example application with SimpleForm and Twitter Bootstrap')
|
|
209
|
-
})
|
|
204
|
+
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
205
|
+
const title = await I.grabTitle()
|
|
206
|
+
I.expectEqual(title, 'Example application with SimpleForm and Twitter Bootstrap') // Avaiable with Expect helper. -> https://codecept.io/helpers/Expect/
|
|
207
|
+
})
|
|
210
208
|
```
|
|
211
209
|
|
|
212
210
|
The same way you can grab text, attributes, or form values and use them in next test steps.
|
|
@@ -216,23 +214,24 @@ The same way you can grab text, attributes, or form values and use them in next
|
|
|
216
214
|
Common preparation steps like opening a web page, logging in a user, can be placed in `Before` or `Background`:
|
|
217
215
|
|
|
218
216
|
```js
|
|
219
|
-
const { I } = inject()
|
|
217
|
+
const { I } = inject()
|
|
220
218
|
|
|
221
|
-
Feature('CodeceptJS Demonstration')
|
|
219
|
+
Feature('CodeceptJS Demonstration')
|
|
222
220
|
|
|
223
|
-
Before(() => {
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
Before(() => {
|
|
222
|
+
// or Background
|
|
223
|
+
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
224
|
+
})
|
|
226
225
|
|
|
227
226
|
Scenario('test some forms', () => {
|
|
228
|
-
I.click('Create User')
|
|
229
|
-
I.see('User is valid')
|
|
230
|
-
I.dontSeeInCurrentUrl('/documentation')
|
|
231
|
-
})
|
|
227
|
+
I.click('Create User')
|
|
228
|
+
I.see('User is valid')
|
|
229
|
+
I.dontSeeInCurrentUrl('/documentation')
|
|
230
|
+
})
|
|
232
231
|
|
|
233
232
|
Scenario('test title', () => {
|
|
234
|
-
I.seeInTitle('Example application')
|
|
235
|
-
})
|
|
233
|
+
I.seeInTitle('Example application')
|
|
234
|
+
})
|
|
236
235
|
```
|
|
237
236
|
|
|
238
237
|
## PageObjects
|
|
@@ -248,83 +247,55 @@ It will create a page object file for you and add it to the config.
|
|
|
248
247
|
Let's assume we created one named `docsPage`:
|
|
249
248
|
|
|
250
249
|
```js
|
|
251
|
-
const { I } = inject()
|
|
250
|
+
const { I } = inject()
|
|
252
251
|
|
|
253
252
|
module.exports = {
|
|
254
253
|
fields: {
|
|
255
254
|
email: '#user_basic_email',
|
|
256
|
-
password: '#user_basic_password'
|
|
255
|
+
password: '#user_basic_password',
|
|
257
256
|
},
|
|
258
|
-
submitButton: {css: '#new_user_basic input[type=submit]'},
|
|
257
|
+
submitButton: { css: '#new_user_basic input[type=submit]' },
|
|
259
258
|
|
|
260
259
|
sendForm(email, password) {
|
|
261
|
-
I.fillField(this.fields.email, email)
|
|
262
|
-
I.fillField(this.fields.password, password)
|
|
263
|
-
I.click(this.submitButton)
|
|
264
|
-
}
|
|
260
|
+
I.fillField(this.fields.email, email)
|
|
261
|
+
I.fillField(this.fields.password, password)
|
|
262
|
+
I.click(this.submitButton)
|
|
263
|
+
},
|
|
265
264
|
}
|
|
266
265
|
```
|
|
267
266
|
|
|
268
267
|
You can easily inject it to test by providing its name in test arguments:
|
|
269
268
|
|
|
270
269
|
```js
|
|
271
|
-
Feature('CodeceptJS Demonstration')
|
|
270
|
+
Feature('CodeceptJS Demonstration')
|
|
272
271
|
|
|
273
|
-
Before(({ I }) => {
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
Before(({ I }) => {
|
|
273
|
+
// or Background
|
|
274
|
+
I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation')
|
|
275
|
+
})
|
|
276
276
|
|
|
277
277
|
Scenario('test some forms', ({ I, docsPage }) => {
|
|
278
|
-
docsPage.sendForm('hello@world.com','123456')
|
|
279
|
-
I.see('User is valid')
|
|
280
|
-
I.dontSeeInCurrentUrl('/documentation')
|
|
281
|
-
})
|
|
278
|
+
docsPage.sendForm('hello@world.com', '123456')
|
|
279
|
+
I.see('User is valid')
|
|
280
|
+
I.dontSeeInCurrentUrl('/documentation')
|
|
281
|
+
})
|
|
282
282
|
```
|
|
283
283
|
|
|
284
284
|
When using Typescript, replace `module.exports` with `export` for autocompletion.
|
|
285
285
|
|
|
286
|
-
|
|
287
286
|
## Contributing
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
- ### [Contributing Guide](https://github.com/codeceptjs/CodeceptJS/blob/master/.github/CONTRIBUTING.md)
|
|
289
|
+
- ### [Code of conduct](https://github.com/codeceptjs/CodeceptJS/blob/master/.github/CODE_OF_CONDUCT.md)
|
|
292
290
|
|
|
293
291
|
## Contributors
|
|
294
292
|
|
|
295
|
-
Thanks
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
<a href="https://github.com/reubenmiller"><img src="https://avatars.githubusercontent.com/u/3029781?v=4" title="reubenmiller" width="80" height="80"></a>
|
|
302
|
-
<a href="https://github.com/Arhell"><img src="https://avatars.githubusercontent.com/u/26163841?v=4" title="Arhell" width="80" height="80"></a>
|
|
303
|
-
<a href="https://github.com/APshenkin"><img src="https://avatars.githubusercontent.com/u/14344430?v=4" title="APshenkin" width="80" height="80"></a>
|
|
304
|
-
<a href="https://github.com/fabioel"><img src="https://avatars.githubusercontent.com/u/9824235?v=4" title="fabioel" width="80" height="80"></a>
|
|
305
|
-
<a href="https://github.com/pablopaul"><img src="https://avatars.githubusercontent.com/u/635526?v=4" title="pablopaul" width="80" height="80"></a>
|
|
306
|
-
<a href="https://github.com/mirao"><img src="https://avatars.githubusercontent.com/u/12584138?v=4" title="mirao" width="80" height="80"></a>
|
|
307
|
-
<a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
|
|
308
|
-
<a href="https://github.com/KMKoushik"><img src="https://avatars.githubusercontent.com/u/24666922?v=4" title="KMKoushik" width="80" height="80"></a>
|
|
309
|
-
<a href="https://github.com/nikocanvacom"><img src="https://avatars.githubusercontent.com/u/83254493?v=4" title="nikocanvacom" width="80" height="80"></a>
|
|
310
|
-
<a href="https://github.com/elukoyanov"><img src="https://avatars.githubusercontent.com/u/11647141?v=4" title="elukoyanov" width="80" height="80"></a>
|
|
311
|
-
<a href="https://github.com/gkushang"><img src="https://avatars.githubusercontent.com/u/3663389?v=4" title="gkushang" width="80" height="80"></a>
|
|
312
|
-
<a href="https://github.com/tsuemura"><img src="https://avatars.githubusercontent.com/u/17092259?v=4" title="tsuemura" width="80" height="80"></a>
|
|
313
|
-
<a href="https://github.com/EgorBodnar"><img src="https://avatars.githubusercontent.com/u/63167966?v=4" title="EgorBodnar" width="80" height="80"></a>
|
|
314
|
-
<a href="https://github.com/VikalpP"><img src="https://avatars.githubusercontent.com/u/11846339?v=4" title="VikalpP" width="80" height="80"></a>
|
|
315
|
-
<a href="https://github.com/thomashohn"><img src="https://avatars.githubusercontent.com/u/3414869?v=4" title="thomashohn" width="80" height="80"></a>
|
|
316
|
-
<a href="https://github.com/elaichenkov"><img src="https://avatars.githubusercontent.com/u/29764053?v=4" title="elaichenkov" width="80" height="80"></a>
|
|
317
|
-
<a href="https://github.com/BorisOsipov"><img src="https://avatars.githubusercontent.com/u/6514276?v=4" title="BorisOsipov" width="80" height="80"></a>
|
|
318
|
-
<a href="https://github.com/ngraf"><img src="https://avatars.githubusercontent.com/u/7094389?v=4" title="ngraf" width="80" height="80"></a>
|
|
319
|
-
<a href="https://github.com/nitschSB"><img src="https://avatars.githubusercontent.com/u/39341455?v=4" title="nitschSB" width="80" height="80"></a>
|
|
320
|
-
<a href="https://github.com/hubidu"><img src="https://avatars.githubusercontent.com/u/13134082?v=4" title="hubidu" width="80" height="80"></a>
|
|
321
|
-
<a href="https://github.com/jploskonka"><img src="https://avatars.githubusercontent.com/u/669483?v=4" title="jploskonka" width="80" height="80"></a>
|
|
322
|
-
<a href="https://github.com/maojunxyz"><img src="https://avatars.githubusercontent.com/u/28778042?v=4" title="maojunxyz" width="80" height="80"></a>
|
|
323
|
-
<a href="https://github.com/abhimanyupandian"><img src="https://avatars.githubusercontent.com/u/36107381?v=4" title="abhimanyupandian" width="80" height="80"></a>
|
|
324
|
-
<a href="https://github.com/martomo"><img src="https://avatars.githubusercontent.com/u/1850135?v=4" title="martomo" width="80" height="80"></a>
|
|
325
|
-
<a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
|
|
326
|
-
|
|
327
|
-
[//]: contributor-faces
|
|
293
|
+
Thanks to our awesome contributors! 🎉
|
|
294
|
+
<a href="https://github.com/codeceptjs/codeceptjs/graphs/contributors">
|
|
295
|
+
<img src="https://contrib.rocks/image?repo=codeceptjs/codeceptjs" />
|
|
296
|
+
</a>
|
|
297
|
+
|
|
298
|
+
Made with [contrib.rocks](https://contrib.rocks).
|
|
328
299
|
|
|
329
300
|
## License
|
|
330
301
|
|
package/bin/codecept.js
CHANGED
|
@@ -32,7 +32,7 @@ const commandFlags = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const errorHandler =
|
|
35
|
-
|
|
35
|
+
fn =>
|
|
36
36
|
async (...args) => {
|
|
37
37
|
try {
|
|
38
38
|
await fn(...args)
|
|
@@ -286,7 +286,7 @@ program
|
|
|
286
286
|
|
|
287
287
|
.action(require('../lib/command/run-rerun'))
|
|
288
288
|
|
|
289
|
-
program.on('command:*',
|
|
289
|
+
program.on('command:*', cmd => {
|
|
290
290
|
console.log(`\nUnknown command ${cmd}\n`)
|
|
291
291
|
program.outputHelp()
|
|
292
292
|
})
|
|
@@ -3,7 +3,7 @@ if none provided clears all cookies.
|
|
|
3
3
|
|
|
4
4
|
```js
|
|
5
5
|
I.clearCookie();
|
|
6
|
-
I.clearCookie('test');
|
|
6
|
+
I.clearCookie('test');
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
@param {?string} [cookie=null] (optional, `null` by default) cookie name
|
package/lib/actor.js
CHANGED
|
@@ -40,7 +40,7 @@ class Actor {
|
|
|
40
40
|
limitTime(timeout) {
|
|
41
41
|
if (!store.timeouts) return this;
|
|
42
42
|
|
|
43
|
-
event.dispatcher.prependOnceListener(event.step.before,
|
|
43
|
+
event.dispatcher.prependOnceListener(event.step.before, step => {
|
|
44
44
|
output.log(`Timeout to ${step}: ${timeout}s`);
|
|
45
45
|
step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
|
|
46
46
|
});
|
|
@@ -70,37 +70,18 @@ class Actor {
|
|
|
70
70
|
* @ignore
|
|
71
71
|
*/
|
|
72
72
|
module.exports = function (obj = {}) {
|
|
73
|
-
|
|
74
|
-
store.actor = new Actor();
|
|
75
|
-
}
|
|
76
|
-
const actor = store.actor;
|
|
77
|
-
|
|
78
|
-
const translation = container.translation();
|
|
79
|
-
|
|
80
|
-
if (Object.keys(obj).length > 0) {
|
|
81
|
-
Object.keys(obj)
|
|
82
|
-
.forEach(action => {
|
|
83
|
-
const actionAlias = translation.actionAliasFor(action);
|
|
84
|
-
|
|
85
|
-
const currentMethod = obj[action];
|
|
86
|
-
const ms = new MetaStep('I', action);
|
|
87
|
-
if (translation.loaded) {
|
|
88
|
-
ms.name = actionAlias;
|
|
89
|
-
ms.actor = translation.I;
|
|
90
|
-
}
|
|
91
|
-
ms.setContext(actor);
|
|
92
|
-
actor[action] = actor[actionAlias] = ms.run.bind(ms, currentMethod);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
73
|
+
const actor = container.actor() || new Actor();
|
|
95
74
|
|
|
96
|
-
|
|
75
|
+
// load all helpers once container initialized
|
|
76
|
+
container.started(() => {
|
|
77
|
+
const translation = container.translation();
|
|
78
|
+
const helpers = container.helpers();
|
|
97
79
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
.forEach((helper) => {
|
|
80
|
+
// add methods from enabled helpers
|
|
81
|
+
Object.values(helpers).forEach(helper => {
|
|
101
82
|
methodsOfObject(helper, 'Helper')
|
|
102
83
|
.filter(method => method !== 'constructor' && method[0] !== '_')
|
|
103
|
-
.forEach(
|
|
84
|
+
.forEach(action => {
|
|
104
85
|
const actionAlias = translation.actionAliasFor(action);
|
|
105
86
|
if (!actor[action]) {
|
|
106
87
|
actor[action] = actor[actionAlias] = function () {
|
|
@@ -116,6 +97,28 @@ module.exports = function (obj = {}) {
|
|
|
116
97
|
});
|
|
117
98
|
});
|
|
118
99
|
|
|
100
|
+
// add translated custom steps from actor
|
|
101
|
+
Object.keys(obj).forEach(key => {
|
|
102
|
+
const actionAlias = translation.actionAliasFor(key);
|
|
103
|
+
if (!actor[actionAlias]) {
|
|
104
|
+
actor[actionAlias] = actor[key];
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
container.append({
|
|
109
|
+
support: {
|
|
110
|
+
I: actor,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// store.actor = actor;
|
|
115
|
+
// add custom steps from actor
|
|
116
|
+
Object.keys(obj).forEach(key => {
|
|
117
|
+
const ms = new MetaStep('I', key);
|
|
118
|
+
ms.setContext(actor);
|
|
119
|
+
actor[key] = ms.run.bind(ms, obj[key]);
|
|
120
|
+
});
|
|
121
|
+
|
|
119
122
|
return actor;
|
|
120
123
|
};
|
|
121
124
|
|
|
@@ -130,13 +133,20 @@ function recordStep(step, args) {
|
|
|
130
133
|
let val;
|
|
131
134
|
|
|
132
135
|
// run step inside promise
|
|
133
|
-
recorder.add(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
step.startTime
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
recorder.add(
|
|
137
|
+
task,
|
|
138
|
+
() => {
|
|
139
|
+
if (!step.startTime) {
|
|
140
|
+
// step can be retries
|
|
141
|
+
event.emit(event.step.started, step);
|
|
142
|
+
step.startTime = Date.now();
|
|
143
|
+
}
|
|
144
|
+
return (val = step.run(...args));
|
|
145
|
+
},
|
|
146
|
+
false,
|
|
147
|
+
undefined,
|
|
148
|
+
step.getTimeout(),
|
|
149
|
+
);
|
|
140
150
|
|
|
141
151
|
event.emit(event.step.after, step);
|
|
142
152
|
|
|
@@ -146,7 +156,7 @@ function recordStep(step, args) {
|
|
|
146
156
|
event.emit(event.step.finished, step);
|
|
147
157
|
});
|
|
148
158
|
|
|
149
|
-
recorder.catchWithoutStop(
|
|
159
|
+
recorder.catchWithoutStop(err => {
|
|
150
160
|
step.status = 'failed';
|
|
151
161
|
step.endTime = Date.now();
|
|
152
162
|
event.emit(event.step.failed, step);
|
package/lib/assert/empty.js
CHANGED
|
@@ -5,7 +5,7 @@ const output = require('../output')
|
|
|
5
5
|
|
|
6
6
|
class EmptinessAssertion extends Assertion {
|
|
7
7
|
constructor(params) {
|
|
8
|
-
super(
|
|
8
|
+
super(value => {
|
|
9
9
|
if (Array.isArray(value)) {
|
|
10
10
|
return value.length === 0
|
|
11
11
|
}
|
|
@@ -22,9 +22,7 @@ class EmptinessAssertion extends Assertion {
|
|
|
22
22
|
const err = new AssertionFailedError(this.params, "{{customMessage}}expected {{subject}} '{{value}}' {{type}}")
|
|
23
23
|
|
|
24
24
|
err.cliMessage = () => {
|
|
25
|
-
const msg = err.template
|
|
26
|
-
.replace('{{value}}', output.colors.bold('{{value}}'))
|
|
27
|
-
.replace('{{subject}}', output.colors.bold('{{subject}}'))
|
|
25
|
+
const msg = err.template.replace('{{value}}', output.colors.bold('{{value}}')).replace('{{subject}}', output.colors.bold('{{subject}}'))
|
|
28
26
|
return template(msg, this.params)
|
|
29
27
|
}
|
|
30
28
|
return err
|
|
@@ -39,5 +37,5 @@ class EmptinessAssertion extends Assertion {
|
|
|
39
37
|
|
|
40
38
|
module.exports = {
|
|
41
39
|
Assertion: EmptinessAssertion,
|
|
42
|
-
empty:
|
|
40
|
+
empty: subject => new EmptinessAssertion({ subject }),
|
|
43
41
|
}
|
package/lib/assert/equal.js
CHANGED
|
@@ -18,10 +18,7 @@ class EqualityAssertion extends Assertion {
|
|
|
18
18
|
getException() {
|
|
19
19
|
const params = this.params
|
|
20
20
|
params.jar = template(params.jar, params)
|
|
21
|
-
const err = new AssertionFailedError(
|
|
22
|
-
params,
|
|
23
|
-
'{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"',
|
|
24
|
-
)
|
|
21
|
+
const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"')
|
|
25
22
|
err.showDiff = false
|
|
26
23
|
if (typeof err.cliMessage === 'function') {
|
|
27
24
|
err.message = err.cliMessage()
|
|
@@ -42,8 +39,8 @@ class EqualityAssertion extends Assertion {
|
|
|
42
39
|
|
|
43
40
|
module.exports = {
|
|
44
41
|
Assertion: EqualityAssertion,
|
|
45
|
-
equals:
|
|
46
|
-
urlEquals:
|
|
42
|
+
equals: jar => new EqualityAssertion({ jar }),
|
|
43
|
+
urlEquals: baseUrl => {
|
|
47
44
|
const assert = new EqualityAssertion({ jar: 'url of current page' })
|
|
48
45
|
assert.comparator = function (expected, actual) {
|
|
49
46
|
if (expected.indexOf('http') !== 0) {
|
|
@@ -53,5 +50,5 @@ module.exports = {
|
|
|
53
50
|
}
|
|
54
51
|
return assert
|
|
55
52
|
},
|
|
56
|
-
fileEquals:
|
|
53
|
+
fileEquals: file => new EqualityAssertion({ file, jar: 'contents of {{file}}' }),
|
|
57
54
|
}
|
package/lib/assert/include.js
CHANGED
|
@@ -10,7 +10,7 @@ class InclusionAssertion extends Assertion {
|
|
|
10
10
|
params.jar = params.jar || 'string'
|
|
11
11
|
const comparator = function (needle, haystack) {
|
|
12
12
|
if (Array.isArray(haystack)) {
|
|
13
|
-
return haystack.filter(
|
|
13
|
+
return haystack.filter(part => part.indexOf(needle) >= 0).length > 0
|
|
14
14
|
}
|
|
15
15
|
return haystack.indexOf(needle) >= 0
|
|
16
16
|
}
|
|
@@ -28,9 +28,7 @@ class InclusionAssertion extends Assertion {
|
|
|
28
28
|
this.params.haystack = this.params.haystack.join('\n___(next element)___\n')
|
|
29
29
|
}
|
|
30
30
|
err.cliMessage = function () {
|
|
31
|
-
const msg = this.template
|
|
32
|
-
.replace('{{jar}}', output.colors.bold('{{jar}}'))
|
|
33
|
-
.replace('{{needle}}', output.colors.bold('{{needle}}'))
|
|
31
|
+
const msg = this.template.replace('{{jar}}', output.colors.bold('{{jar}}')).replace('{{needle}}', output.colors.bold('{{needle}}'))
|
|
34
32
|
return template(msg, this.params)
|
|
35
33
|
}
|
|
36
34
|
return err
|
|
@@ -66,11 +64,11 @@ class InclusionAssertion extends Assertion {
|
|
|
66
64
|
|
|
67
65
|
module.exports = {
|
|
68
66
|
Assertion: InclusionAssertion,
|
|
69
|
-
includes:
|
|
67
|
+
includes: needleType => {
|
|
70
68
|
needleType = needleType || 'string'
|
|
71
69
|
return new InclusionAssertion({ jar: needleType })
|
|
72
70
|
},
|
|
73
|
-
fileIncludes:
|
|
71
|
+
fileIncludes: file => new InclusionAssertion({ file, jar: 'file {{file}}' }),
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
function escapeRegExp(str) {
|
package/lib/assert/throws.js
CHANGED
|
@@ -11,10 +11,8 @@ function errorThrown(actual, expected) {
|
|
|
11
11
|
throw new Error(`Expected error to be thrown with message ${expected} while '${msg}' caught`)
|
|
12
12
|
}
|
|
13
13
|
if (typeof expected === 'object') {
|
|
14
|
-
if (actual.constructor.name !== expected.constructor.name)
|
|
15
|
-
|
|
16
|
-
if (expected.message && expected.message !== msg)
|
|
17
|
-
throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
|
|
14
|
+
if (actual.constructor.name !== expected.constructor.name) throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`)
|
|
15
|
+
if (expected.message && expected.message !== msg) throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
|
|
18
16
|
}
|
|
19
17
|
return null
|
|
20
18
|
}
|
package/lib/assert/truth.js
CHANGED
|
@@ -5,9 +5,9 @@ const output = require('../output')
|
|
|
5
5
|
|
|
6
6
|
class TruthAssertion extends Assertion {
|
|
7
7
|
constructor(params) {
|
|
8
|
-
super(
|
|
8
|
+
super(value => {
|
|
9
9
|
if (Array.isArray(value)) {
|
|
10
|
-
return value.filter(
|
|
10
|
+
return value.filter(val => !!val).length > 0
|
|
11
11
|
}
|
|
12
12
|
return !!value
|
|
13
13
|
}, params)
|