Intercept HTTP Requests
TestCafe allows you to intercept HTTP requests with request hooks.
You can intercept HTTP requests that originate from the browser (for example, when you interact with a web application), and requests that you issue with the t.request method.
Request hooks allow you to perform the following actions:
- Log HTTP Requests to track your application’s network activity.
- Mock HTTP Requests to feed dummy data to your application.
- Create Custom Request Hooks to perform other actions with HTTP requests, for example, emulate Kerberos or Client Certificate Authentication.
You can attach request hooks to specific tests and fixtures, or define them globally.
Important
- Request hooks cannot intercept WebSocket requests (ws:// and wss://).
- Native Automation limits the range of available request modifications.
Table of Contents
- Log HTTP Requests
- Mock HTTP Requests
- Create a Custom Request Hook
- Attach Hooks to Tests and Fixtures
- Define Global Hooks in the Configuration File
- Limitations
Log HTTP Requests
Use the request logger to record outgoing HTTP requests and inspect responses to these requests. For instance, to check whether the response includes correct data.
Create a request logger with the RequestLogger constructor:
import { RequestLogger } from 'testcafe';
const simpleLogger = RequestLogger('http://example.com');
const headerLogger = RequestLogger(/testcafe/, {
logRequestHeaders: true,
logResponseHeaders: true
});
To enable this request hook, attach the logger to a specific test / fixture or the entire test run:
fixture `Export`
.page('https://demos.devexpress.com/ASPxGridViewDemos/Exporting/Exporting.aspx')
.requestHooks(logger);
The RequestLogger
logs the following request data:
- The target URL
- The HTTP method
- The status code of the response
- The request sender’s user agent string
Use the following RequestLogger
methods to inspect this data:
Member | Description |
---|---|
requests | Returns an array of logged requests |
contains | Succeeds if the logger contains a matching request |
count | Returns the number of matching requests |
clear | Clears all logged requests |
Example
import { Selector, RequestLogger } from 'testcafe';
import fs from 'fs';
import path from 'path';
const url = 'https://demos.devexpress.com/ASPxGridViewDemos/Exporting/Exporting.aspx';
const logger = RequestLogger({ url, method: 'post' }, {
logResponseHeaders: true,
logResponseBody: true
});
fixture `Export`
.page(url)
.requestHooks(logger);
test('export to csv', async t => {
const exportToCSVButton = Selector('span').withText('Export to CSV');
await t
.click(exportToCSVButton)
// When you click 'Export', your browser downloads a compressed CSV file (*.gzip).
.expect(logger.contains(r => r.response.statusCode === 200)).ok();
const filePath = path.join(__dirname, 'exported-grid.zip');
// Locates the file on the disk
console.log(filePath);
console.log(logger.requests[0].response.headers);
fs.writeFileSync(filePath, logger.requests[0].response.body);
// Use 3rd party modules to unpack the archive,
// parse the CSV and check the data.
// Alternatively, verify the file manually.
});
Mock HTTP Requests
Use the request mocker to respond to HTTP requests with dummy data. This is useful when some parts of your website’s infrastructure are difficult to deploy or costly to use.
Imagine that your application sends data to a third-party traffic analysis service. You do not want your functional tests to impact your website’s analytics. Add a request mocker to intercept requests to that service, and emulate the response as needed.
Create a request mocker with the RequestMock constructor:
var mock = RequestMock();
Append the onRequestTo and respond methods to the RequestMock
constructor:
var mock = RequestMock()
.onRequestTo(request1)
.respond(responseMock1)
The onRequestTo method specifies which requests to intercept. The respond specifies how TestCafe should respond to the intercepted request.
Chain these methods to intercept multiple HTTP requests:
var mock = RequestMock()
.onRequestTo(request1)
.respond(responseMock1)
.onRequestTo(request2)
.respond(responseMock2)
To enable this request hook, attach it to a test/fixture or the entire test run.
Example
import { Selector, RequestMock } from 'testcafe';
// Google Analytics API endpoint
const collectDataGoogleAnalyticsRegExp = new RegExp('https://www.google-analytics.com/collect');
// Binary image data for the mocked response
const mockedResponse = Buffer.from([0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01]);
const mock = RequestMock()
.onRequestTo(collectDataGoogleAnalyticsRegExp)
// This hook fires when the application sends a request to the Google Analytics API endpoint
.respond(mockedResponse, 202, {
'content-length': mockedResponse.length,
'content-type': 'image/gif'
});
// TestCafe intercepts the request and responds with the dummy data above (status code: 202).
fixture `Fixture`
.page('https://devexpress.github.io/testcafe/')
.requestHooks(mock);
test('basic', async t => {
await t
.click('.get-started-button')
.debug();
// The debug action pauses the test.
// Open your browser's Developer Tools to inspect the request and the response.
});
Create a Custom Request Hook
Create custom request hooks to perform other actions with HTTP requests.
- Create a new class that inherits from the RequestHook class.
- Override the onRequest method to process the request.
- Override the onResponse method to process the response.
import { RequestHook } from 'testcafe';
export class MyRequestHook extends RequestHook {
constructor (requestFilterRules, responseEventConfigureOpts) {
super(requestFilterRules, responseEventConfigureOpts);
// ...
}
async onRequest (event) {
// ...
}
async onResponse (event) {
// ...
}
}
- Create an instance of your new hook. Attach it to a test/fixture or the entire test run.
import { MyRequestHook } from './my-request-hook';
const customHook = new MyRequestHook(/https?:\/\/example.com/);
fixture `My fixture`
.page('http://example.com')
.requestHooks(customHook);
test('My test', async t => {
// test actions
});
Example
import { Selector, RequestHook } from 'testcafe';
class JwtBearerAuthorization extends RequestHook {
constructor () {
// No URL filtering applied to this hook
// so it will be used for all requests.
super();
}
onRequest (e) {
e.requestOptions.headers['Authorization'] = 'generate token here';
}
onResponse (e) {
// This method must also be overridden,
// but you can leave it blank.
}
}
const jwtBearerAuthorization = new JwtBearerAuthorization();
fixture `Fixture`
.page('<website URL>')
.requestHooks(jwtBearerAuthorization);
test('basic', async t => {
/* some actions */
});
Attach Hooks to Tests and Fixtures
Attach a hook to a test or fixture to handle HTTP requests within these entities.
- Use the fixture.requestHooks method to attach a hook to a fixture.
- Use the test.requestHooks method to attach a hook to a test.
- Use t.addRequestHooks and t.removeRequestHooks to attach and detach hooks on the fly.
import { RequestLogger, RequestMock } from 'testcafe';
const logger = RequestLogger('http://example.com');
const mock = RequestMock()
.onRequestTo('http://external-service.com/api/')
.respond({ data: 'value' });
fixture `My fixture`
.page('http://example.com')
.requestHooks(logger);
test
.requestHooks(mock)
('My test', async t => {
await t
.click('#send-logged-request')
.expect(logger.count(() => true)).eql(1)
.removeRequestHooks(logger)
.click('#send-unlogged-request')
.expect(logger.count(() => true)).eql(1)
.addRequestHooks(logger)
.click('#send-logged-request')
.expect(logger.count(() => true)).eql(2);
});
Define Global Hooks in the Configuration File
You may need to attach the same hook to multiple tests or fixtures. To avoid code duplication, you can define a global hook in the JavaScript configuration file.
The following code shows an example that defines a global RequestMock
. Note that test code doesn’t require an explicit hook declaration. The configuration file attaches the hook to the test.
//.testcaferc.js or .testcaferc.cjs
const { RequestMock } = require('testcafe');
const mock = RequestMock()
.onRequestTo('https://api.mycorp.com/users/id/135865')
.respond({
name: 'John Hearts',
position: 'CTO',
}, 200, { 'access-control-allow-origin': '*' })
.onRequestTo(/internal.mycorp.com/)
.respond(null, 404);
module.exports = {
hooks: {
request: mock,
},
};
//test.js
fixture `RequestMock`
.page`https://mycorp.com`;
test('Should mock requests', async t => {
const user = await t
.eval(() => fetch('https://api.mycorp.com/users/id/135865')
.then(res => res.json()));
await t
.expect(user).eql({ name: 'John Hearts', position: 'CTO' })
.navigateTo('https://internal.mycorp.com');
});
The hook declaration may require an extra step. In the example above, the test does not need to read any data from the RequestMock
object. The situation changes if the hook is a RequestLogger
or a custom RequestMock
. Tests may need to obtain the hook object’s data in such cases.
The following example demonstrates how to declare a global RequestLogger
. To read and validate hook data, the test needs to access the logger object. To enable this access, define an extra file: “logger.js”. This file defines the hook object and supplies it to both the test and the configuration file.
//logger.js
const { RequestLogger } = require('testcafe');
module.exports = new RequestLogger();
//.testcaferc.js or .testcaferc.cjs
const logger = require('./logger');
module.exports = {
hooks: {
request: logger,
},
};
//test.js
const { logger } = './logger';
fixture`RequestLogger`
.page('https://devexpress.github.io/testcafe/example/');
test('Check request', async t => {
await t
.expect(logger.contains(record => record.response.statusCode === 200)).ok()
.expect(logger.requests[0].request.method).eql('get');
});
Limitations
Request Hook Conflicts
If a single request is subject to multiple request hooks, TestCafe executes the hooks in the following order:
- Global hook
- Fixture hook
- Test hook
Request Hooks and Native Automation
Native automation limits the range of available request modifications.
TestCafe uses the CDP protocol to intercept requests in Native Automation mode. At the moment, the protocol allows TestCafe to change the following request parameters:
url
method
postData
headers
Disable native automation to change other request parameters, such as key
or cert
.