UNPKG

@luminati-io/webdriverio8

Version:

Next-gen browser and mobile automation test framework for Node.js

160 lines (153 loc) 9.55 kB
import DevtoolsNetworkInterception from '../../utils/interception/devtools.js'; import WebDriverNetworkInterception from '../../utils/interception/webdriver.js'; import { getBrowserObject } from '../../utils/index.js'; export const SESSION_MOCKS = {}; export const CDP_SESSIONS = {}; /** * Mock the response of a request. You can define a mock based on a matching * glob and corresponding header and status code. Calling the mock method * returns a stub object that you can use to modify the response of the * web resource. * * With the stub object you can then either return a custom response or * have the request fail. * * There are 3 ways to modify the response: * - return a custom JSON object (for stubbing API request) * - replace web resource with a local file (service a modified JavaScript file) or * - redirect resource to a different url * * :::info * * Note that using the `mock` command requires support for Chrome DevTools protocol. * That support is given if you run tests locally in Chromium based browser or if * you use a Selenium Grid v4 or higher. This command can __not__ be used when running * automated tests in the cloud. Find out more in the [Automation Protocols](/docs/automationProtocols) section. * * ::: * * <example> :mock.js it('should mock network resources', async () => { // via static string const userListMock = await browser.mock('**' + '/users/list') // or as regular expression const userListMock = await browser.mock(/https:\/\/(domainA|domainB)\.com\/.+/) // you can also specifying the mock even more by filtering resources // by request or response headers, status code, postData, e.g. mock only responses with specific // header set and statusCode const strictMock = await browser.mock('**', { // mock all json responses statusCode: 200, headers: { 'Content-Type': 'application/json' }, responseHeaders: { 'Cache-Control': 'no-cache' }, postData: 'foobar' }) // comparator function const apiV1Mock = await browser.mock('**' + '/api/v1', { statusCode: (statusCode) => statusCode >= 200 && statusCode <= 203, headers: (headers) => headers['Authorization'] && headers['Authorization'].startsWith('Bearer '), responseHeaders: (headers) => headers['Impersonation'], postData: (data) => typeof data === 'string' && data.includes('foo') }) }) it('should modify API responses', async () => { // filter by method const todoMock = await browser.mock('**' + '/todos', { method: 'get' }) // mock an endpoint with a fixed fixture todoMock.respond([{ title: 'Injected Todo', order: null, completed: false, url: "http://todo-backend-express-knex.herokuapp.com/916" }]) // respond with different status code or header todoMock.respond([{ title: 'Injected Todo', order: null, completed: false, url: "http://todo-backend-express-knex.herokuapp.com/916" }], { statusCode: 404, headers: { 'x-custom-header': 'foobar' } }) }) it('should modify text assets', async () => { const scriptMock = await browser.mock('**' + '/script.min.js') scriptMock.respond('./tests/fixtures/script.js') }) it('should redirect web resources', async () => { const headerMock = await browser.mock('**' + '/header.png') headerMock.respond('https://media.giphy.com/media/F9hQLAVhWnL56/giphy.gif') const pageMock = await browser.mock('https://google.com/') pageMock.respond('https://webdriver.io') await browser.url('https://google.com') console.log(await browser.getTitle()) // returns "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js" }) * </example> * * @alias browser.mock * @param {String|RegExp} url url to mock * @param {MockFilterOptions=} filterOptions filter mock resource by additional options * @param {String|Function=} filterOptions.method filter resource by HTTP method * @param {Object|Function=} filterOptions.headers filter resource by specific request headers * @param {Object|Function=} filterOptions.responseHeaders filter resource by specific response headers * @param {String|Function=} filterOptions.postData filter resource by request postData * @param {Number|Function=} filterOptions.statusCode filter resource by response statusCode * @return {Mock} a mock object to modify the response * @type utility * */ export async function mock(url, filterOptions) { const NetworkInterception = this.isSauce ? WebDriverNetworkInterception : DevtoolsNetworkInterception; if (!this.isSauce) { await this.getPuppeteer(); } if (!this.puppeteer) { throw new Error('No Puppeteer connection could be established which is required to use this command'); } const browser = getBrowserObject(this); const handle = await browser.getWindowHandle(); if (!SESSION_MOCKS[handle]) { SESSION_MOCKS[handle] = new Set(); } /** * enable network Mocking if not already */ if (SESSION_MOCKS[handle].size === 0 && !this.isSauce) { const pages = await this.puppeteer.pages(); /** * get active page */ let page; for (let i = 0; i < pages.length && !page; i++) { const isHidden = await pages[i].evaluate(() => document.hidden); if (!isHidden) { page = pages[i]; } } /** * fallback to the first page */ if (!page) { page = pages[0]; } const client = CDP_SESSIONS[handle] = await page.target().createCDPSession(); await client.send('Fetch.enable', { patterns: [{ requestStage: 'Request' }, { requestStage: 'Response' }] }); client.on('Fetch.requestPaused', NetworkInterception .handleRequestInterception(client, SESSION_MOCKS[handle])); } const networkInterception = new NetworkInterception(url, filterOptions, browser); SESSION_MOCKS[handle].add(networkInterception); if (this.isSauce) { await networkInterception.init(); } return networkInterception; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9jay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9jb21tYW5kcy9icm93c2VyL21vY2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTywyQkFBMkIsTUFBTSxzQ0FBc0MsQ0FBQTtBQUM5RSxPQUFPLDRCQUE0QixNQUFNLHVDQUF1QyxDQUFBO0FBQ2hGLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHNCQUFzQixDQUFBO0FBS3ZELE1BQU0sQ0FBQyxNQUFNLGFBQWEsR0FBc0MsRUFBRSxDQUFBO0FBQ2xFLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBK0IsRUFBRSxDQUFBO0FBRTFEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F5R0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLElBQUksQ0FFdEIsR0FBb0IsRUFDcEIsYUFBaUM7SUFFakMsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDLENBQUMsMkJBQTJCLENBQUE7SUFFckcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNoQixNQUFNLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQTtJQUM3QixDQUFDO0lBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUNsQixNQUFNLElBQUksS0FBSyxDQUFDLG9GQUFvRixDQUFDLENBQUE7SUFDekcsQ0FBQztJQUVELE1BQU0sT0FBTyxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFBO0lBQ3RDLE1BQU0sTUFBTSxHQUFHLE1BQU0sT0FBTyxDQUFDLGVBQWUsRUFBRSxDQUFBO0lBQzlDLElBQUksQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztRQUN6QixhQUFhLENBQUMsTUFBTSxDQUFDLEdBQUcsSUFBSSxHQUFHLEVBQUUsQ0FBQTtJQUNyQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3BELE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUUxQzs7V0FFRztRQUNILElBQUksSUFBSSxDQUFBO1FBQ1IsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUM3QyxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFBO1lBQy9ELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDWixJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO1lBQ25CLENBQUM7UUFDTCxDQUFDO1FBRUQ7O1dBRUc7UUFDSCxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDUixJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ25CLENBQUM7UUFFRCxNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQTtRQUM1RSxNQUFNLE1BQU0sQ0FBQyxJQUFJLENBQUMsY0FBYyxFQUFFO1lBQzlCLFFBQVEsRUFBRSxDQUFDLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxDQUFDO1NBQ3hFLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxFQUFFLENBQ0wscUJBQXFCLEVBQ3BCLG1CQUFxRTthQUNqRSx5QkFBeUIsQ0FBQyxNQUFNLEVBQUUsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQ2hFLENBQUE7SUFDTCxDQUFDO0lBRUQsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLG1CQUFtQixDQUFDLEdBQUcsRUFBRSxhQUFhLEVBQUUsT0FBTyxDQUFDLENBQUE7SUFDaEYsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUMsQ0FBQyxDQUFBO0lBRTlELElBQUksSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2YsTUFBTyxtQkFBb0QsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtJQUN0RSxDQUFDO0lBRUQsT0FBTyxtQkFBMkIsQ0FBQTtBQUN0QyxDQUFDIn0=