From 7b4ea33bebca06da8349f5fe2f330b56050f7a18 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Mon, 18 Mar 2024 11:45:24 +0100 Subject: [PATCH] feat: mock route for ppt --- docs/helpers/Playwright.md | 4 +- docs/helpers/Puppeteer.md | 91 +++++++++++++++++++++++------------ docs/puppeteer.md | 54 ++++++--------------- lib/helper/Playwright.js | 4 +- lib/helper/Puppeteer.js | 48 ++++++++++++++++++ test/helper/Puppeteer_test.js | 20 ++++++++ 6 files changed, 148 insertions(+), 73 deletions(-) diff --git a/docs/helpers/Playwright.md b/docs/helpers/Playwright.md index 591c6cec7..99bf03983 100644 --- a/docs/helpers/Playwright.md +++ b/docs/helpers/Playwright.md @@ -1505,7 +1505,7 @@ This method allows intercepting and mocking requests & responses. [Learn more ab #### Parameters - `url` **([string][9] | [RegExp][11])?** URL, regex or pattern for to match URL -- `handler` **[function][21]?** a function to process reques +- `handler` **[function][21]?** a function to process request ### mockTraffic @@ -2230,7 +2230,7 @@ If no handler is passed, all mock requests for the rote are disabled. #### Parameters - `url` **([string][9] | [RegExp][11])?** URL, regex or pattern for to match URL -- `handler` **[function][21]?** a function to process reques +- `handler` **[function][21]?** a function to process request ### stopRecordingTraffic diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index 4e2ff0383..856f4b1e5 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -39,26 +39,26 @@ Type: [object][4] - `url` **[string][6]** base url of website to be tested - `basicAuth` **[object][4]?** (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'} -- `show` **[boolean][20]?** show Google Chrome window for debug. -- `restart` **[boolean][20]?** restart browser between tests. -- `disableScreenshots` **[boolean][20]?** don't save screenshot on failure. -- `fullPageScreenshots` **[boolean][20]?** make full page screenshots on failure. -- `uniqueScreenshotNames` **[boolean][20]?** option to prevent screenshot override if you have scenarios with the same name in different suites. -- `trace` **[boolean][20]?** record [tracing information][24] with screenshots. -- `keepTraceForPassedTests` **[boolean][20]?** save trace for passed tests. -- `keepBrowserState` **[boolean][20]?** keep browser state between tests when `restart` is set to false. -- `keepCookies` **[boolean][20]?** keep cookies between tests when `restart` is set to false. +- `show` **[boolean][22]?** show Google Chrome window for debug. +- `restart` **[boolean][22]?** restart browser between tests. +- `disableScreenshots` **[boolean][22]?** don't save screenshot on failure. +- `fullPageScreenshots` **[boolean][22]?** make full page screenshots on failure. +- `uniqueScreenshotNames` **[boolean][22]?** option to prevent screenshot override if you have scenarios with the same name in different suites. +- `trace` **[boolean][22]?** record [tracing information][26] with screenshots. +- `keepTraceForPassedTests` **[boolean][22]?** save trace for passed tests. +- `keepBrowserState` **[boolean][22]?** keep browser state between tests when `restart` is set to false. +- `keepCookies` **[boolean][22]?** keep cookies between tests when `restart` is set to false. - `waitForAction` **[number][10]?** how long to wait after click, doubleClick or PressKey actions in ms. Default: 100. -- `waitForNavigation` **[string][6]?** when to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API][23]. Array values are accepted as well. +- `waitForNavigation` **[string][6]?** when to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API][25]. Array values are accepted as well. - `pressKeyDelay` **[number][10]?** delay between key presses in ms. Used when calling Puppeteers page.type(...) in fillField/appendField - `getPageTimeout` **[number][10]?** config option to set maximum navigation time in milliseconds. If the timeout is set to 0, then timeout will be disabled. - `waitForTimeout` **[number][10]?** default wait* timeout in ms. - `windowSize` **[string][6]?** default window size. Set a dimension in format WIDTHxHEIGHT like `640x480`. - `userAgent` **[string][6]?** user-agent string. -- `manualStart` **[boolean][20]?** do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`. +- `manualStart` **[boolean][22]?** do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`. - `browser` **[string][6]?** can be changed to `firefox` when using [puppeteer-firefox][2]. -- `chrome` **[object][4]?** pass additional [Puppeteer run options][25]. -- `highlightElement` **[boolean][20]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). +- `chrome` **[object][4]?** pass additional [Puppeteer run options][27]. +- `highlightElement` **[boolean][22]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). @@ -1255,6 +1255,21 @@ I.seeFile('avatar.jpg'); - `downloadPath` **[string][6]** change this parameter to set another directory for saving +### mockRoute + +Mocks network request using [`Request Interception`][17] + +```js +I.mockRoute(/(.png$)|(.jpg$)/, route => route.abort()); +``` + +This method allows intercepting and mocking requests & responses. [Learn more about it][17] + +#### Parameters + +- `url` **([string][6] | [RegExp][18])?** URL, regex or pattern for to match URL +- `handler` **[function][12]?** a function to process request + ### moveCursorTo Moves cursor to element matched by locator. @@ -1287,11 +1302,11 @@ I.openNewTab(); ### pressKey -_Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313][17]). +_Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313][19]). Presses a key in the browser (on a focused element). -_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][18]. +_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][20]. ```js I.pressKey('Backspace'); @@ -1358,7 +1373,7 @@ Returns **void** automatically synchronized promise through #recorder Presses a key in the browser and leaves it in a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][19]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][21]). ```js I.pressKeyDown('Control'); @@ -1376,7 +1391,7 @@ Returns **void** automatically synchronized promise through #recorder Releases a key in the browser which was previously set to a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][19]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][21]). ```js I.pressKeyDown('Control'); @@ -1470,7 +1485,7 @@ I.saveScreenshot('debug.png', true) //resizes to available scrollHeight and scro #### Parameters - `fileName` **[string][6]** file name to save. -- `fullPage` **[boolean][20]** (optional, `false` by default) flag to enable fullscreen screenshot mode. +- `fullPage` **[boolean][22]** (optional, `false` by default) flag to enable fullscreen screenshot mode. Returns **void** automatically synchronized promise through #recorder @@ -1862,6 +1877,18 @@ I.setPuppeteerRequestHeaders({ - `customHeaders` **[object][4]** headers to set +### stopMockingRoute + +Stops network mocking created by `mockRoute`. + +```js +I.stopMockingRoute(/(.png$)|(.jpg$)/); +``` + +#### Parameters + +- `url` **([string][6] | [RegExp][18])?** URL, regex or pattern for to match URL + ### switchTo Switches frame or in case of null locator reverts to parent. @@ -1907,7 +1934,7 @@ I.switchToPreviousTab(2); Types out the given text into an active field. To slow down typing use a second parameter, to set interval between key presses. -_Note:_ Should be used when [`fillField`][18] is not an option. +_Note:_ Should be used when [`fillField`][20] is not an option. ```js // passing in a string @@ -1958,7 +1985,7 @@ Use Puppeteer API inside a test. First argument is a description of an action. Second argument is async function that gets this helper as parameter. -{ [`page`][21], [`browser`][22] } from Puppeteer API are available. +{ [`page`][23], [`browser`][24] } from Puppeteer API are available. ```js I.usePuppeteerTo('emulate offline mode', async ({ page }) { @@ -2110,7 +2137,7 @@ Returns **void** automatically synchronized promise through #recorder Waits for navigation to finish. By default, takes configured `waitForNavigation` option. -See [Puppeteer's reference][23] +See [Puppeteer's reference][25] #### Parameters @@ -2313,20 +2340,24 @@ Returns **void** automatically synchronized promise through #recorder [16]: https://codecept.io/helpers/FileSystem -[17]: https://github.com/GoogleChrome/puppeteer/issues/1313 +[17]: https://pptr.dev/next/guides/request-interception + +[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp + +[19]: https://github.com/GoogleChrome/puppeteer/issues/1313 -[18]: #fillfield +[20]: #fillfield -[19]: #click +[21]: #click -[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[21]: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page +[23]: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page -[22]: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser +[24]: https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser -[23]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions +[25]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions -[24]: https://pptr.dev/api/puppeteer.tracing +[26]: https://pptr.dev/api/puppeteer.tracing -[25]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions +[27]: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions diff --git a/docs/puppeteer.md b/docs/puppeteer.md index 9b10087c6..4467776c5 100644 --- a/docs/puppeteer.md +++ b/docs/puppeteer.md @@ -216,52 +216,28 @@ await eachElement( > ℹ Learn more about [eachElement plugin](/plugins/#eachelement) -## Mocking Requests +## Mocking Network Requests -Web application sends various requests to local services (Rest API, GraphQL) or to 3rd party services (CDNS, Google Analytics, etc). -When you run tests with Puppeteer you can control those requests by mocking them. For instance, you can speed up your tests by blocking trackers, Google Analytics, and other services you don't control. - -Also you can replace real request with a one explicitly defined. This is useful when you want to isolate application testing from a backend. For instance, if you don't want to save data to database, and you know the request which performs save, you can mock the request, so application will treat this as valid response, but no data will be actually saved. - -To mock requests enable additional helper [MockRequest](/helpers/MockRequest) (which is based on Polly.js). +Network requests & responses can be mocked and modified. Use `mockRoute` which strictly follows [Puppeteer's `setRequestInterception` API](https://pptr.dev/next/api/puppeteer.page.setrequestinterception). ```js -helpers: { - Puppeteer: { - // regular Puppeteer config here - }, - MockRequest: {} -} -``` - -And install additional packages: - -``` -npm i @pollyjs/core @pollyjs/adapter-puppeteer --save-dev -``` - -After an installation function `mockRequest` will be added to `I` object. You can use it to explicitly define which requests to block and which response they should return instead: - -```js -// block all Google Analytics calls -I.mockRequest('/google-analytics/*path', 200); -// return an empty successful response -I.mockRequest('GET', '/api/users', 200); -// block post requests to /api/users and return predefined object -I.mockRequest('POST', '/api/users', { user: 'davert' }); -// return error request with body -I.mockRequest('GET', '/api/users/1', 404, { error: 'User not found' }); -``` - -> See [`mockRequest` API](/helpers/MockRequest#mockrequest) +I.mockRoute('https://reqres.in/api/comments/1', request => { + request.respond({ + status: 200, + headers: { 'Access-Control-Allow-Origin': '*' }, + contentType: 'application/json', + body: '{"name": "this was mocked" }', + }); +}) -To see `mockRequest` method in intellisense auto completion don't forget to run `codeceptjs def` command: +I.mockRoute('**/*.{png,jpg,jpeg}', route => route.abort()); -``` -npx codeceptjs def +// To disable mocking for a route call `stopMockingRoute` +// for previously mocked URL +I.stopMockingRoute('**/*.{png,jpg,jpeg}' ``` -Mocking rules will be kept while a test is running. To stop mocking use `I.stopMocking()` command +To master request intercepting [use `HTTPRequest` object](https://pptr.dev/next/api/puppeteer.httprequest) object passed into mock request handler. ## Accessing Puppeteer API diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 4c39b9682..2e1603c1d 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -2957,7 +2957,7 @@ class Playwright extends Helper { * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests) * * @param {string|RegExp} [url] URL, regex or pattern for to match URL - * @param {function} [handler] a function to process reques + * @param {function} [handler] a function to process request */ async mockRoute(url, handler) { return this.browserContext.route(...arguments); @@ -2973,7 +2973,7 @@ class Playwright extends Helper { * If no handler is passed, all mock requests for the rote are disabled. * * @param {string|RegExp} [url] URL, regex or pattern for to match URL - * @param {function} [handler] a function to process reques + * @param {function} [handler] a function to process request */ async stopMockingRoute(url, handler) { return this.browserContext.unroute(...arguments); diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 74ffe87c7..1399f44e9 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -2459,6 +2459,54 @@ class Puppeteer extends Helper { if (prop) return rect[prop]; return rect; } + + /** + * Mocks network request using [`Request Interception`](https://pptr.dev/next/guides/request-interception) + * + * ```js + * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort()); + * ``` + * This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/next/guides/request-interception) + * + * @param {string|RegExp} [url] URL, regex or pattern for to match URL + * @param {function} [handler] a function to process request + */ + async mockRoute(url, handler) { + await this.page.setRequestInterception(true); + + this.page.on('request', interceptedRequest => { + if (interceptedRequest.url().match(url)) { + // @ts-ignore + handler(interceptedRequest); + } else { + interceptedRequest.continue(); + } + }); + } + + /** + * Stops network mocking created by `mockRoute`. + * + * ```js + * I.stopMockingRoute(/(\.png$)|(\.jpg$)/); + * ``` + * + * @param {string|RegExp} [url] URL, regex or pattern for to match URL + */ + async stopMockingRoute(url) { + await this.page.setRequestInterception(true); + + this.page.off('request'); + + // Resume normal request handling for the given URL + this.page.on('request', interceptedRequest => { + if (interceptedRequest.url().includes(url)) { + interceptedRequest.continue(); + } else { + interceptedRequest.continue(); + } + }); + } } module.exports = Puppeteer; diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js index 5247271f0..c23e906dc 100644 --- a/test/helper/Puppeteer_test.js +++ b/test/helper/Puppeteer_test.js @@ -951,6 +951,26 @@ describe('Puppeteer', function () { assert.equal('TestEd Beta 2.0', title); }); }); + + describe('#mockRoute, #stopMockingRoute', () => { + it('should mock a route', async () => { + await I.amOnPage('/form/fetch_call'); + await I.mockRoute('https://reqres.in/api/comments/1', request => { + request.respond({ + status: 200, + headers: { 'Access-Control-Allow-Origin': '*' }, + contentType: 'application/json', + body: '{"name": "this was mocked" }', + }); + }); + await I.click('GET COMMENTS'); + await I.see('this was mocked'); + await I.stopMockingRoute('https://reqres.in/api/comments/1'); + await I.click('GET COMMENTS'); + await I.see('data'); + await I.dontSee('this was mocked'); + }); + }); }); let remoteBrowser;