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.
Files changed (44) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +15 -22
  3. package/bin/codecept.js +3 -1
  4. package/docs/advanced.md +1 -1
  5. package/docs/angular.md +6 -9
  6. package/docs/basics.md +388 -86
  7. package/docs/bdd.md +4 -3
  8. package/docs/build/Nightmare.js +3 -0
  9. package/docs/build/Polly.js +26 -12
  10. package/docs/build/Puppeteer.js +14 -13
  11. package/docs/build/TestCafe.js +101 -2
  12. package/docs/build/WebDriver.js +53 -52
  13. package/docs/changelog.md +86 -57
  14. package/docs/detox.md +235 -0
  15. package/docs/helpers/Detox.md +579 -0
  16. package/docs/helpers/Polly.md +13 -3
  17. package/docs/helpers/Puppeteer.md +155 -156
  18. package/docs/helpers/TestCafe.md +53 -0
  19. package/docs/helpers/WebDriver.md +209 -204
  20. package/docs/locators.md +2 -0
  21. package/docs/mobile.md +5 -1
  22. package/docs/puppeteer.md +59 -13
  23. package/docs/quickstart.md +47 -12
  24. package/docs/testcafe.md +157 -0
  25. package/docs/webdriver.md +453 -0
  26. package/lib/command/definitions.js +152 -7
  27. package/lib/command/gherkin/snippets.js +19 -8
  28. package/lib/command/init.js +30 -22
  29. package/lib/command/utils.js +1 -1
  30. package/lib/container.js +36 -10
  31. package/lib/data/dataScenarioConfig.js +18 -0
  32. package/lib/helper/Nightmare.js +3 -0
  33. package/lib/helper/Polly.js +26 -12
  34. package/lib/helper/Puppeteer.js +14 -13
  35. package/lib/helper/TestCafe.js +72 -2
  36. package/lib/helper/WebDriver.js +53 -52
  37. package/lib/helper/testcafe/testcafe-utils.js +3 -2
  38. package/lib/interfaces/scenarioConfig.js +2 -2
  39. package/lib/listener/config.js +2 -2
  40. package/lib/plugin/allure.js +3 -0
  41. package/lib/step.js +5 -2
  42. package/lib/ui.js +1 -1
  43. package/lib/utils.js +13 -21
  44. 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
- Please note, you can't run tests by different helpers at once. You can't use some APIs from WebDriver and some from Nightmare. You should **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 at once. It's just not possible.
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
- A helper should be enabled in main config. Configuration (like base url) should be provided as well:
35
+ ---
36
36
 
37
- ```json
38
- "helpers": {
39
- "WebDriver": {
40
- "url": "http://localhost",
41
- "browser": "chrome"
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
- To list all available commands for current configuration run `codeceptjs list`
60
- or enable [auto-completion by generating TypeScript definitions](#intellisense).
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
- > For most helpers basic actions like `amOnPage`, `fillField`, `click` are the same.
63
- Proceed to [Acceptance Testing Chapter](https://codecept.io/acceptance/) to learn how to use them.
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 [iframes](/acceptance/#iframes)
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
- ## IntelliSense
595
+ ## Multiple Sessions
297
596
 
298
- If you are using Visual Studio Code or other IDE that supports TypeScript Definitions,
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
- Now you should create `tsconfig.json` in your project root directory.
306
-
307
- ```tsconfig.json
308
- {
309
- "compilerOptions": {
310
- "allowJs": true,
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
- ## Retries
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
- If you have a step which often fails you can retry execution for this single step.
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
- I.retry().see('Welcome');
628
+ session('john', { browser: 'firefox' } , () => {
629
+ // run this steps in firefox
630
+ I.amOnPage('/');
631
+ });
336
632
  ```
337
633
 
338
- If you'd like to retry step more than once pass the amount as parameter:
634
+ or just start session without switching to it. Call `session` passing only its name:
339
635
 
340
636
  ```js
341
- I.retry(3).see('Welcome');
342
- ```
637
+ Scenario('test', (I) => {
638
+ // opens 3 additional browsers
639
+ session('john');
640
+ session('mary');
641
+ session('jane');
343
642
 
344
- 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).
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
- // retry action 3 times waiting for 0.1 second before next try
349
- I.retry({ retries: 3, minTimeout: 100 }).see('Hello');
350
-
351
- // retry action 3 times waiting no more than 3 seconds for last retry
352
- I.retry({ retries: 3, maxTimeout: 3000 }).see('Hello');
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
- Pass a function to `when` option to retry only when error matches the expected one.
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
- ### Auto Retry
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
- ### Retry Scenario
668
+ ## IntelliSense
368
669
 
369
- When you need to rerun scenarios few times just add `retries` option added to `Scenario` declaration.
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
- CodeceptJS implements retries the same way [Mocha do](https://mochajs.org#retry-tests);
372
- You can set number of a retries for a feature:
673
+ ```sh
674
+ codeceptjs def
675
+ ```
373
676
 
374
- ```js
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
- // alternative
380
- Scenario('Really complex', { retries: 2 }, (I) => {});
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
- This scenario will be restarted two times on a failure.
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
- To set this option for all scenarios in a file, add retry to a feature:
691
+ ## Skipping
388
692
 
389
- ```js
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
- Every Scenario inside this feature will be rerun 3 times.
394
- You can make an exception for a specific scenario by passing `retries` option to a Scenario.
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 all feature files of this suite.
145
- It will also place stub definitions into `step_definitions/steps.js` file. However, you may also target a specific file to place all undefined steps in. This file must exist and be placed in the gherkin steps in the current config.
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
@@ -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
 
@@ -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 && !this.helpers.Puppeteer) {
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
- await this.polly.flush();
154
- await this.polly.stop();
155
- this.polly = undefined;
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