codeceptjs 2.2.0 → 2.2.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 +30 -1
- package/README.md +15 -22
- package/bin/codecept.js +3 -1
- package/docs/advanced.md +1 -1
- package/docs/angular.md +6 -9
- package/docs/basics.md +388 -86
- package/docs/bdd.md +4 -3
- package/docs/build/Nightmare.js +3 -0
- package/docs/build/Polly.js +26 -12
- package/docs/build/Puppeteer.js +14 -13
- package/docs/build/TestCafe.js +101 -2
- package/docs/build/WebDriver.js +53 -52
- package/docs/changelog.md +86 -57
- package/docs/detox.md +235 -0
- package/docs/helpers/Detox.md +579 -0
- package/docs/helpers/Polly.md +13 -3
- package/docs/helpers/Puppeteer.md +155 -156
- package/docs/helpers/TestCafe.md +53 -0
- package/docs/helpers/WebDriver.md +209 -204
- package/docs/locators.md +2 -0
- package/docs/mobile.md +5 -1
- package/docs/puppeteer.md +59 -13
- package/docs/quickstart.md +47 -12
- package/docs/testcafe.md +157 -0
- package/docs/webdriver.md +453 -0
- package/lib/command/definitions.js +152 -7
- package/lib/command/gherkin/snippets.js +19 -8
- package/lib/command/init.js +30 -22
- package/lib/command/utils.js +1 -1
- package/lib/container.js +36 -10
- package/lib/data/dataScenarioConfig.js +18 -0
- package/lib/helper/Nightmare.js +3 -0
- package/lib/helper/Polly.js +26 -12
- package/lib/helper/Puppeteer.js +14 -13
- package/lib/helper/TestCafe.js +72 -2
- package/lib/helper/WebDriver.js +53 -52
- package/lib/helper/testcafe/testcafe-utils.js +3 -2
- package/lib/interfaces/scenarioConfig.js +2 -2
- package/lib/listener/config.js +2 -2
- package/lib/plugin/allure.js +3 -0
- package/lib/step.js +5 -2
- package/lib/ui.js +1 -1
- package/lib/utils.js +13 -21
- package/package.json +14 -12
package/docs/basics.md
CHANGED
|
@@ -30,20 +30,23 @@ Here is the diagram of CodeceptJS architecture
|
|
|
30
30
|
All helpers share the same API so it's easy to migrate tests from one backend to other.
|
|
31
31
|
However, because of difference in backends and their limitations, they are not guaranteed to be compatible with each other. For instance, you can't set request headers in WebDriver or Protractor, but you can do so in Puppteer or Nightmare.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
**Pick one helper, as it defines how tests are executed.** If requirements change it's easy to migrate to another, but don't use few helpers of same kind at once.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
---
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
Refer to following guides to more information on:
|
|
38
|
+
|
|
39
|
+
* [▶ WebDriver](https://codecept.io/webdriver)
|
|
40
|
+
* [▶ Protractor](https://codecept.io/angular)
|
|
41
|
+
* [▶ Puppeteer](https://codecept.io/puppeteer)
|
|
42
|
+
* [▶ Nightmare](https://codecept.io/nightmare)
|
|
43
|
+
* TestCafe
|
|
44
|
+
|
|
45
|
+
> ℹ Depending on a helper selected a list of available actions may change.
|
|
46
|
+
|
|
47
|
+
To list all available commands for current configuration run `codeceptjs list`
|
|
48
|
+
or enable [auto-completion by generating TypeScript definitions](#intellisense).
|
|
45
49
|
|
|
46
|
-
In this config config all methods of `I` will be taken from `WebDriver` helper.
|
|
47
50
|
|
|
48
51
|
## Writing Tests
|
|
49
52
|
|
|
@@ -56,11 +59,221 @@ I.see('Please Login', 'h1');
|
|
|
56
59
|
// ...
|
|
57
60
|
```
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
### Opening a Page
|
|
63
|
+
|
|
64
|
+
A test should usually start with navigating browser to the website.
|
|
65
|
+
|
|
66
|
+
Start a test by opening a page. Use `I.amOnPage()` command for this:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// When "http://site.com" is url in config
|
|
70
|
+
I.amOnPage('/'); // -> opens http://site.com/
|
|
71
|
+
I.amOnPage('/about'); // -> opens http://site.com/about
|
|
72
|
+
I.amOnPage('https://google.com'); // -> https://google.com
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
When URL doesn't start with a protocol (http:// or https://) it is considered to be a relative URL and appended to URL which was initially set in the config.
|
|
76
|
+
|
|
77
|
+
> It is recommended to use relative URLs and keep base URL in config file, so you could easily switch between development, staging, and production environments.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Locating Element
|
|
81
|
+
|
|
82
|
+
Element can be found by CSS or XPath locators.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
I.seeElement('.user'); // element with CSS class user
|
|
86
|
+
I.seeElement('//button[contains(., "press me")]'); // button
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
By default CodeceptJS tries to guess the locator type.
|
|
90
|
+
In order to specify exact locator type you can pass an object called **strict locator**.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
I.seeElement({css: 'div.user'});
|
|
94
|
+
I.seeElement({xpath: '//div[@class=user]'});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Strict locators allow to specify additional locator types:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
// locate form element by name
|
|
101
|
+
I.seeElement({name: 'password'});
|
|
102
|
+
// locate element by id
|
|
103
|
+
I.seeElement({id: 'users'});
|
|
104
|
+
// locate element by React component and props
|
|
105
|
+
I.seeElement({react: 'user-profile', props: {name: 'davert'}});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
In [mobile testing](http://codecept.io/mobile/#locating-elements) you can use `~` to specify accessibility id to locate an element. In web application you can locate element by their `aria-label` value.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
// locate element by [aria-label] attribute in web
|
|
112
|
+
// or by accessibility id in mobile
|
|
113
|
+
I.seeElement('~username');
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> [▶ Learn more about using locators in CodeceptJS](https://codecept.io/locators).
|
|
117
|
+
|
|
118
|
+
### Clicking
|
|
119
|
+
|
|
120
|
+
CodeceptJS provides a flexible syntax to specify an element to click.
|
|
121
|
+
|
|
122
|
+
By default CodeceptJS tries to find button or link with exact text on it
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// search for link or button
|
|
126
|
+
I.click('Login');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If none found, CodeceptJS tries to find link or button containing that text. In case an image is clickable its `alt` attribute will be checked for text inclusion. Form buttons will also be searched by name.
|
|
130
|
+
|
|
131
|
+
To narrow down the results you can specify a context in second parameter.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
I.click('Login', '.nav'); // search only in .nav
|
|
135
|
+
I.click('Login', {css: 'footer'}); // search only in footer
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> To skip guessing locator type pass in a strict locator. A locator starting with '#' or '.' is considered to be CSS. Locator starting with '//' or './/' is considered to be XPath.
|
|
139
|
+
|
|
140
|
+
You are not limited to buttons and links. Any element can be found by passing in valid CSS or XPath:
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
// click element by CSS
|
|
144
|
+
I.click('#signup');
|
|
145
|
+
// click element located by special test-id attribute
|
|
146
|
+
I.click('//dev[@test-id="myid"]');
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Filling Fields
|
|
150
|
+
|
|
151
|
+
Clicking the links is not what takes the most time during testing a web site. If your site consists only of links you can skip test automation. The most routine waste of time goes into the testing of forms. CodeceptJS provides several ways of doing that.
|
|
152
|
+
|
|
153
|
+
Let's submit this sample form for a test:
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<form method="post" action="/update" id="update_form">
|
|
157
|
+
<label for="user_name">Name</label>
|
|
158
|
+
<input type="text" name="user[name]" id="user_name" />
|
|
159
|
+
<label for="user_email">Email</label>
|
|
160
|
+
<input type="text" name="user[email]" id="user_email" />
|
|
161
|
+
<label for="user_gender">Gender</label>
|
|
162
|
+
<select id="user_gender" name="user[gender]">
|
|
163
|
+
<option value="m">Male</option>
|
|
164
|
+
<option value="f">Female</option>
|
|
165
|
+
</select>
|
|
166
|
+
<input type="submit" name="submitButton" value="Update" />
|
|
167
|
+
</form>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
We need to fill in all those fields and click "Update" button. CodeceptJS matches form elements by their label, name, or by CSS or XPath locators.
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
// we are using label to match user_name field
|
|
174
|
+
I.fillField('Name', 'Miles');
|
|
175
|
+
// we can use input name
|
|
176
|
+
I.fillField('user[email]','miles@davis.com');
|
|
177
|
+
// select element by label, choose option by text
|
|
178
|
+
I.selectOption('Gender','Male');
|
|
179
|
+
// click 'Update' button, found by text
|
|
180
|
+
I.click('Update');
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Alternative scenario:
|
|
184
|
+
|
|
185
|
+
```js
|
|
186
|
+
// we are using CSS
|
|
187
|
+
I.fillField('#user_name', 'Miles');
|
|
188
|
+
I.fillField('#user_email','miles@davis.com');
|
|
189
|
+
// select element by label, option by value
|
|
190
|
+
I.selectOption('#user_gender','m');
|
|
191
|
+
// click 'Update' button, found by name
|
|
192
|
+
I.click('submitButton', '#update_form');
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
To fill in sensitive data use `secret` function:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
I.fillField('password', secret('123456'));
|
|
199
|
+
```
|
|
61
200
|
|
|
62
|
-
|
|
63
|
-
|
|
201
|
+
### Assertions
|
|
202
|
+
|
|
203
|
+
In order to verify the expected behavior of a web application, a content should be checked.
|
|
204
|
+
CodeceptJS provides built-in assertions for that. They start with `see` (or `dontSee`) prefix.
|
|
205
|
+
|
|
206
|
+
The most general and common assertion is `see`, which checks visilibility of a text on a page:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
// Just a visible text on a page
|
|
210
|
+
I.see('Hello');
|
|
211
|
+
// text inside .msg element
|
|
212
|
+
I.see('Hello', '.msg');
|
|
213
|
+
// opposite
|
|
214
|
+
I.dontSee('Bye');
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
You should provide a text as first argument and optionally a locator to search for a text in a context.
|
|
218
|
+
|
|
219
|
+
You can check that specific element exists (or not) on a page, as it was described in [Locating Element](#locating-element) section.
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
I.seeElement('.notice');
|
|
223
|
+
I.dontSeeElement('.error');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Additional assertions:
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
I.seeInCurrentUrl('/user/miles');
|
|
230
|
+
I.seeInField('user[name]', 'Miles');
|
|
231
|
+
I.seeInTitle('My Website');
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
To see all possible assertions see the helper's reference.
|
|
235
|
+
|
|
236
|
+
### Grabbing
|
|
237
|
+
|
|
238
|
+
Sometimes you need to retrieve a data from a page to use it in next steps of a scenario.
|
|
239
|
+
Imagine, application generates a password and you want to ensure that user can login using this password.
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
Scenario('login with generated password', async (I) => {
|
|
243
|
+
I.fillField('email', 'miles@davis.com');
|
|
244
|
+
I.click('Generate Password');
|
|
245
|
+
const password = await I.grabTextFrom('#password');
|
|
246
|
+
I.click('Login');
|
|
247
|
+
I.fillField('email', 'miles@davis.com');
|
|
248
|
+
I.fillField('password', password);
|
|
249
|
+
I.click('Log in!');
|
|
250
|
+
I.see('Hello, Miles');
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`grabTextFrom` action is used here to retrieve text from an element. All actions starting with `grab` prefix are expected to return data. In order to synchronize this step with a scenario you should pause test execution with `await` keyword of ES6. To make it work your test should be written inside a async function (notice `async` in its definition).
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
Scenario('use page title', async (I) => {
|
|
258
|
+
// ...
|
|
259
|
+
const password = await I.grabTextFrom('#password');
|
|
260
|
+
I.fillField('password', password);
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Waiting
|
|
265
|
+
|
|
266
|
+
In modern web applications rendering is happen on client side.
|
|
267
|
+
Sometimes that may cause delays. A test may fail while trying to click an element which has not appeared on a page yet.
|
|
268
|
+
To handle this cases `wait*` methods introduced.
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
I.waitForElement('#agree_button', 30); // secs
|
|
272
|
+
// clicks a button only when it is visible
|
|
273
|
+
I.click('#agree_button');
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
> ℹ See [helpers reference](https://codecept.io/reference) for a complete list of all available commands for a helper you use.
|
|
64
277
|
|
|
65
278
|
## How It Works
|
|
66
279
|
|
|
@@ -197,6 +410,76 @@ This can be configured in [screenshotOnFail Plugin](https://codecept.io/plugins/
|
|
|
197
410
|
To see how the test was executed, use [stepByStepReport Plugin](https://codecept.io/plugins/#stepbystepreport). It saves a screenshot of each passed step and shows them in a nice slideshow.
|
|
198
411
|
|
|
199
412
|
|
|
413
|
+
## Retries
|
|
414
|
+
|
|
415
|
+
### Retry Step
|
|
416
|
+
|
|
417
|
+
If you have a step which often fails you can retry execution for this single step.
|
|
418
|
+
Use `retry()` function before an action to ask CodeceptJS to retry this step on failure:
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
I.retry().see('Welcome');
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
If you'd like to retry step more than once pass the amount as parameter:
|
|
425
|
+
|
|
426
|
+
```js
|
|
427
|
+
I.retry(3).see('Welcome');
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Additional options can be provided to retry so you can set the additional options (defined in [promise-retry](https://www.npmjs.com/package/promise-retry) library).
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
```js
|
|
434
|
+
// retry action 3 times waiting for 0.1 second before next try
|
|
435
|
+
I.retry({ retries: 3, minTimeout: 100 }).see('Hello');
|
|
436
|
+
|
|
437
|
+
// retry action 3 times waiting no more than 3 seconds for last retry
|
|
438
|
+
I.retry({ retries: 3, maxTimeout: 3000 }).see('Hello');
|
|
439
|
+
|
|
440
|
+
// retry 2 times if error with message 'Node not visible' happens
|
|
441
|
+
I.retry({
|
|
442
|
+
retries: 2,
|
|
443
|
+
when: err => err.message === 'Node not visible'
|
|
444
|
+
}).seeElement('#user');
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Pass a function to `when` option to retry only when error matches the expected one.
|
|
448
|
+
|
|
449
|
+
### Auto Retry
|
|
450
|
+
|
|
451
|
+
You can auto-retry a failed step by enabling [retryFailedStep Plugin](https://codecept.io/plugins/#retryfailedstep).
|
|
452
|
+
|
|
453
|
+
### Retry Scenario
|
|
454
|
+
|
|
455
|
+
When you need to rerun scenarios few times just add `retries` option added to `Scenario` declaration.
|
|
456
|
+
|
|
457
|
+
CodeceptJS implements retries the same way [Mocha do](https://mochajs.org#retry-tests);
|
|
458
|
+
You can set number of a retries for a feature:
|
|
459
|
+
|
|
460
|
+
```js
|
|
461
|
+
Scenario('Really complex', (I) => {
|
|
462
|
+
// test goes here
|
|
463
|
+
}).retry(2);
|
|
464
|
+
|
|
465
|
+
// alternative
|
|
466
|
+
Scenario('Really complex', { retries: 2 }, (I) => {});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
This scenario will be restarted two times on a failure.
|
|
470
|
+
|
|
471
|
+
### Retry Feature
|
|
472
|
+
|
|
473
|
+
To set this option for all scenarios in a file, add retry to a feature:
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
Feature('Complex JS Stuff').retry(3);
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Every Scenario inside this feature will be rerun 3 times.
|
|
480
|
+
You can make an exception for a specific scenario by passing `retries` option to a Scenario.
|
|
481
|
+
|
|
482
|
+
|
|
200
483
|
## Before
|
|
201
484
|
|
|
202
485
|
Common preparation steps like opening a web page, logging in a user, can be placed in `Before` or `Background` hook:
|
|
@@ -258,7 +541,23 @@ within('.js-signup-form', () => {
|
|
|
258
541
|
I.see('There were problems creating your account.');
|
|
259
542
|
```
|
|
260
543
|
|
|
261
|
-
`within` can also work with
|
|
544
|
+
`within` can also work with IFrames. Special `frame` locator is required to locate the iframe and get into its context.
|
|
545
|
+
|
|
546
|
+
See example:
|
|
547
|
+
|
|
548
|
+
```js
|
|
549
|
+
within({frame: "#editor"}, () => {
|
|
550
|
+
I.see('Page');
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Nested IFrames can be set by passing array *(WebDriver, Nightmare & Puppeteer only)*:
|
|
555
|
+
|
|
556
|
+
```js
|
|
557
|
+
within({frame: [".content", "#editor"]}, () => {
|
|
558
|
+
I.see('Page');
|
|
559
|
+
});
|
|
560
|
+
```
|
|
262
561
|
|
|
263
562
|
When running steps inside a within block will be shown with a shift:
|
|
264
563
|
|
|
@@ -293,102 +592,105 @@ I.say('This is blue', 'blue'); //blue is used
|
|
|
293
592
|
I.say('This is by default'); //cyan is used
|
|
294
593
|
```
|
|
295
594
|
|
|
296
|
-
##
|
|
595
|
+
## Multiple Sessions
|
|
297
596
|
|
|
298
|
-
|
|
299
|
-
you can generate step definitions with
|
|
300
|
-
|
|
301
|
-
```sh
|
|
302
|
-
codeceptjs def
|
|
303
|
-
```
|
|
597
|
+
CodeceptJS allows to run several browser sessions inside a test. This can be useful for testing communication between users inside a system, for instance in chats. To open another browser use `session()` function as shown in example:
|
|
304
598
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
599
|
+
```js
|
|
600
|
+
Scenario('test app', (I) => {
|
|
601
|
+
I.amOnPage('/chat');
|
|
602
|
+
I.fillField('name', 'davert');
|
|
603
|
+
I.click('Sign In');
|
|
604
|
+
I.see('Hello, davert');
|
|
605
|
+
session('john', () => {
|
|
606
|
+
// another session started
|
|
607
|
+
I.amOnPage('/chat');
|
|
608
|
+
I.fillField('name', 'john');
|
|
609
|
+
I.click('Sign In');
|
|
610
|
+
I.see('Hello, john');
|
|
611
|
+
});
|
|
612
|
+
// switching back to default session
|
|
613
|
+
I.fillField('message', 'Hi, john');
|
|
614
|
+
// there is a message from current user
|
|
615
|
+
I.see('me: Hi, john', '.messages');
|
|
616
|
+
session('john', () => {
|
|
617
|
+
// let's check if john received it
|
|
618
|
+
I.see('davert: Hi, john', '.messages');
|
|
619
|
+
});
|
|
620
|
+
});
|
|
313
621
|
```
|
|
314
|
-
but in usually case, this file has already generated when you execute `codeceptjs init`.
|
|
315
|
-
|
|
316
|
-
Alternatively, you can include `/// <reference path="./steps.d.ts" />` into your test files
|
|
317
|
-
to get method autocompletion while writing tests.
|
|
318
|
-
|
|
319
|
-
## Skipping
|
|
320
|
-
|
|
321
|
-
Like in Mocha you can use `x` and `only` to skip tests or making a single test to run.
|
|
322
|
-
|
|
323
|
-
* `xScenario` - skips current test
|
|
324
|
-
* `Scenario.only` - executes only the current test
|
|
325
|
-
|
|
326
622
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
### Retry Step
|
|
623
|
+
`session` function expects a first parameter to be a name of a session. You can switch back to session by using the same name.
|
|
330
624
|
|
|
331
|
-
|
|
332
|
-
Use `retry()` function before an action to ask CodeceptJS to retry this step on failure:
|
|
625
|
+
You can override config for session by passing second parameter:
|
|
333
626
|
|
|
334
627
|
```js
|
|
335
|
-
|
|
628
|
+
session('john', { browser: 'firefox' } , () => {
|
|
629
|
+
// run this steps in firefox
|
|
630
|
+
I.amOnPage('/');
|
|
631
|
+
});
|
|
336
632
|
```
|
|
337
633
|
|
|
338
|
-
|
|
634
|
+
or just start session without switching to it. Call `session` passing only its name:
|
|
339
635
|
|
|
340
636
|
```js
|
|
341
|
-
|
|
342
|
-
|
|
637
|
+
Scenario('test', (I) => {
|
|
638
|
+
// opens 3 additional browsers
|
|
639
|
+
session('john');
|
|
640
|
+
session('mary');
|
|
641
|
+
session('jane');
|
|
343
642
|
|
|
344
|
-
|
|
643
|
+
I.amOnPage('/');
|
|
345
644
|
|
|
645
|
+
// switch to session by its name
|
|
646
|
+
session('mary', () => {
|
|
647
|
+
I.amOnPage('/login');
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
`session` can return value which can be used in scenario:
|
|
346
652
|
|
|
347
653
|
```js
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// retry 2 times if error with message 'Node not visible' happens
|
|
355
|
-
I.retry({
|
|
356
|
-
retries: 2,
|
|
357
|
-
when: err => err.message === 'Node not visible'
|
|
358
|
-
}).seeElement('#user');
|
|
654
|
+
// inside async function
|
|
655
|
+
const val = await session('john', () => {
|
|
656
|
+
I.amOnPage('/info');
|
|
657
|
+
return I.grabTextFrom({ css: 'h1' });
|
|
658
|
+
});
|
|
659
|
+
I.fillField('Description', val);
|
|
359
660
|
```
|
|
360
661
|
|
|
361
|
-
|
|
662
|
+
Function passed into session can use `I`, page objects, and any objects declared for the scenario.
|
|
663
|
+
This function can also be declared as async (but doesn't work as generator).
|
|
362
664
|
|
|
363
|
-
|
|
665
|
+
Also, you can use `within` inside a session but you can't call session from inside `within`.
|
|
364
666
|
|
|
365
|
-
You can auto-retry a failed step by enabling [retryFailedStep Plugin](https://codecept.io/plugins/#retryfailedstep).
|
|
366
667
|
|
|
367
|
-
|
|
668
|
+
## IntelliSense
|
|
368
669
|
|
|
369
|
-
|
|
670
|
+
If you are using Visual Studio Code or other IDE that supports TypeScript Definitions,
|
|
671
|
+
you can generate step definitions with
|
|
370
672
|
|
|
371
|
-
|
|
372
|
-
|
|
673
|
+
```sh
|
|
674
|
+
codeceptjs def
|
|
675
|
+
```
|
|
373
676
|
|
|
374
|
-
|
|
375
|
-
Scenario('Really complex', (I) => {
|
|
376
|
-
// test goes here
|
|
377
|
-
}).retry(2);
|
|
677
|
+
Now you should create `jsconfig.json` in your project root directory.
|
|
378
678
|
|
|
379
|
-
|
|
380
|
-
|
|
679
|
+
```jsconfig.json
|
|
680
|
+
{
|
|
681
|
+
"compilerOptions": {
|
|
682
|
+
"allowJs": true,
|
|
683
|
+
}
|
|
684
|
+
}
|
|
381
685
|
```
|
|
686
|
+
but in usually case, this file has already generated when you execute `codeceptjs init`.
|
|
382
687
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
### Retry Feature
|
|
688
|
+
Alternatively, you can include `/// <reference path="./steps.d.ts" />` into your test files
|
|
689
|
+
to get method autocompletion while writing tests.
|
|
386
690
|
|
|
387
|
-
|
|
691
|
+
## Skipping
|
|
388
692
|
|
|
389
|
-
|
|
390
|
-
Feature('Complex JS Stuff').retry(3);
|
|
391
|
-
```
|
|
693
|
+
Like in Mocha you can use `x` and `only` to skip tests or making a single test to run.
|
|
392
694
|
|
|
393
|
-
|
|
394
|
-
|
|
695
|
+
* `xScenario` - skips current test
|
|
696
|
+
* `Scenario.only` - executes only the current test
|
package/docs/bdd.md
CHANGED
|
@@ -138,11 +138,12 @@ This scenarios are nice as live documentation but they do not test anything yet.
|
|
|
138
138
|
Steps can be defined by executing `gherkin:snippets` command:
|
|
139
139
|
|
|
140
140
|
```bash
|
|
141
|
-
codeceptjs gherkin:snippets [--path=PATH]
|
|
141
|
+
codeceptjs gherkin:snippets [--path=PATH] [--feature=PATH]
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
-
This will produce code templates for all undefined steps in
|
|
145
|
-
|
|
144
|
+
This will produce code templates for all undefined steps in the .feature files.
|
|
145
|
+
By default, it will scan all of the .feature files specified in the gherkin.features section of the config and produce code templates for all undefined steps. If the `--feature` option is specified, it will scan the specified .feature file(s).
|
|
146
|
+
The stub definitions by default will be placed into the first file specified in the gherkin.steps section of the config. However, you may also use `--path` to specify a specific file in which to place all undefined steps. This file must exist and be in the gherkin.steps array of the config.
|
|
146
147
|
Our next step will be to define those steps and transforming feature-file into a valid test.
|
|
147
148
|
|
|
148
149
|
### Step Definitions
|
package/docs/build/Nightmare.js
CHANGED
|
@@ -81,6 +81,9 @@ class Nightmare extends Helper {
|
|
|
81
81
|
static _config() {
|
|
82
82
|
return [
|
|
83
83
|
{ name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
|
|
84
|
+
{
|
|
85
|
+
name: 'show', message: 'Show browser window', default: true, type: 'confirm',
|
|
86
|
+
},
|
|
84
87
|
];
|
|
85
88
|
}
|
|
86
89
|
|
package/docs/build/Polly.js
CHANGED
|
@@ -58,7 +58,7 @@ class Polly extends Helper {
|
|
|
58
58
|
|
|
59
59
|
// Start mocking network requests/responses
|
|
60
60
|
async _startMocking(title = 'Test') {
|
|
61
|
-
if (!this.helpers &&
|
|
61
|
+
if (!(this.helpers && this.helpers.Puppeteer)) {
|
|
62
62
|
throw new Error('Puppeteer is the only supported helper right now');
|
|
63
63
|
}
|
|
64
64
|
await this._connectPuppeteer(title);
|
|
@@ -67,20 +67,21 @@ class Polly extends Helper {
|
|
|
67
67
|
// Connect Puppeteer helper to mock future requests.
|
|
68
68
|
async _connectPuppeteer(title) {
|
|
69
69
|
const adapter = require('@pollyjs/adapter-puppeteer');
|
|
70
|
-
|
|
71
70
|
PollyJS.register(adapter);
|
|
71
|
+
|
|
72
72
|
const { page } = this.helpers.Puppeteer;
|
|
73
|
+
if (!page) {
|
|
74
|
+
throw new Error('Looks like, there is no open tab');
|
|
75
|
+
}
|
|
73
76
|
await page.setRequestInterception(true);
|
|
74
77
|
|
|
75
78
|
this.polly = new PollyJS(title, {
|
|
79
|
+
mode: 'passthrough',
|
|
76
80
|
adapters: ['puppeteer'],
|
|
77
81
|
adapterOptions: {
|
|
78
82
|
puppeteer: { page },
|
|
79
83
|
},
|
|
80
84
|
});
|
|
81
|
-
|
|
82
|
-
// By default let pass through all network requests
|
|
83
|
-
if (this.polly) this.polly.server.any().passthrough();
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -90,6 +91,7 @@ class Polly extends Helper {
|
|
|
90
91
|
* I.mockRequest('GET', '/api/users', 200);
|
|
91
92
|
* I.mockRequest('ANY', '/secretsRoutes/*', 403);
|
|
92
93
|
* I.mockRequest('POST', '/secrets', { secrets: 'fakeSecrets' });
|
|
94
|
+
* I.mockRequest('GET', '/api/users/1', 404, 'User not found');
|
|
93
95
|
* ```
|
|
94
96
|
*
|
|
95
97
|
* Multiple requests
|
|
@@ -97,17 +99,27 @@ class Polly extends Helper {
|
|
|
97
99
|
* ```js
|
|
98
100
|
* I.mockRequest('GET', ['/secrets', '/v2/secrets'], 403);
|
|
99
101
|
* ```
|
|
102
|
+
* @param {string} method request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`.
|
|
103
|
+
* @param {string|array} oneOrMoreUrls url(s) to mock. Can be exact URL, a pattern, or an array of URLs.
|
|
104
|
+
* @param {number|string|object} dataOrStatusCode status code when number provided. A response body otherwise
|
|
105
|
+
* @param {string|object} additionalData response body when a status code is set by previous parameter.
|
|
106
|
+
*
|
|
100
107
|
*/
|
|
101
|
-
async mockRequest(method, oneOrMoreUrls, dataOrStatusCode) {
|
|
108
|
+
async mockRequest(method, oneOrMoreUrls, dataOrStatusCode, additionalData = null) {
|
|
102
109
|
await this._checkAndStartMocking();
|
|
110
|
+
const puppeteerConfigUrl = this.helpers.Puppeteer && this.helpers.Puppeteer.options.url;
|
|
111
|
+
|
|
103
112
|
const handler = this._getRouteHandler(
|
|
104
113
|
method,
|
|
105
114
|
oneOrMoreUrls,
|
|
106
|
-
this.options.url,
|
|
115
|
+
this.options.url || puppeteerConfigUrl,
|
|
107
116
|
);
|
|
108
117
|
|
|
109
118
|
if (typeof dataOrStatusCode === 'number') {
|
|
110
119
|
const statusCode = dataOrStatusCode;
|
|
120
|
+
if (additionalData) {
|
|
121
|
+
return handler.intercept((_, res) => res.status(statusCode).send(additionalData));
|
|
122
|
+
}
|
|
111
123
|
return handler.intercept((_, res) => res.sendStatus(statusCode));
|
|
112
124
|
}
|
|
113
125
|
const data = dataOrStatusCode;
|
|
@@ -148,16 +160,18 @@ class Polly extends Helper {
|
|
|
148
160
|
*/
|
|
149
161
|
async stopMocking() {
|
|
150
162
|
if (!this._checkIfMockingStarted()) return;
|
|
151
|
-
|
|
152
163
|
await this._disconnectPuppeteer();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
|
|
165
|
+
const { polly } = this;
|
|
166
|
+
if (!polly) return;
|
|
167
|
+
await polly.flush();
|
|
168
|
+
await polly.stop();
|
|
169
|
+
delete this.polly;
|
|
156
170
|
}
|
|
157
171
|
|
|
158
172
|
async _disconnectPuppeteer() {
|
|
159
173
|
const { page } = this.helpers.Puppeteer;
|
|
160
|
-
await page.setRequestInterception(false);
|
|
174
|
+
if (page) await page.setRequestInterception(false);
|
|
161
175
|
}
|
|
162
176
|
}
|
|
163
177
|
|