@luminati-io/webdriverio8
Version:
Next-gen browser and mobile automation test framework for Node.js
160 lines (153 loc) • 9.55 kB
JavaScript
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=