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

  1. Request hooks cannot intercept WebSocket requests (ws:// and wss://).
  2. Native Automation limits the range of available request modifications.

Table of Contents

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.

  1. Create a new class that inherits from the RequestHook class.
  2. Override the onRequest method to process the request.
  3. 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) {
        // ...
    }
}
  1. 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.

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.