npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

cypress-interceptor

v2.4.0

Published

A helper for better Cypress testing; mocking, throttling and waiting for requests with advanced statistics

Downloads

14

Readme

NPM Build Status Coverage Status

Cypress Interceptor

About

Cypress Interceptor is a global substitute for cy.intercept. It is a crucial helper for your tests based on HTTP/HTTPS requests. The main reason why this package was created is the function waitUntilRequestIsDone. This function waits until one or more requests are finished using the provided arguments. More information below.

For working with websockets, go to the websocket section.

Limits

There are some limits while using this package:

  1. Do not use any of Interceptor commands in before event. You can use it in beforeEach and in any other events.
  2. When you expecting a large response (aprox. more than 30MB), you must bypass the response because there is an issue in Cypress intercept and the request is never done in the web browser but finished in Cypress.

Whats new

  • added watchTheConsole
  • added a possibility to filter and map stats when a test fail
  • added a possibility to bypass the response
  • work with canceled and aborted request
  • work with XHR requests
  • add support for Websockets

Table of contents

Why to use?

Generally when you can not safely check that the content has been changed by the finished request.

For exapmle: Let's say we have a table, data grid, list of products, ... - just any content controlled by AJAX requests. If you use pure Cypress get/find, you can not be sure that the refresh was performed well.

it("Table refresh", () => {
    // on the page should be a table
    cy.visit("my-page.org");

    // check that the table exists, or check some content within the table
    cy.get("table#my-table").should("exist");
    cy.get("table#my-table").should("contain", "1,500.00$");

    // refresh the table
    cy.get("button#refresh").click();

    cy.waitUntilRequestIsDone();

    // check the content again
    cy.get("table#my-table").should("exist");
    cy.get("table#my-table").should("contain", "1,500.00$");

    // also you can check that the request was called
    cy.interceptorLastRequest("**/my-page.org/api/refresh-table").should("not.be.undefined");
})

Getting started

It is very simple, just install the package using yarn or npm and import the package in your cypress/support/e2e.js or cypress/support/e2e.ts:

import "cypress-interceptor";

Would you just log all requests to a file on fail?

Take a look to this example.

Interceptor Cypress commands

/**
 * Bypass a request response (it will not hit Cypress intercept response callback and not to
 * store response data in the Interceptor stack, useful for big data responses)
 *
 * @param routeMatcher A route matcher
 * @param times How many times the response should be mocked, by default it is set to 1.
 *              Set to 0 to mock the response infinitely
 */
bypassInterceptorResponse: (routeMatcher: IRouteMatcher, times?: number) => void;
/**
 * Get an instance of Interceptor
 *
 * @returns An instance of Interceptor
 */
interceptor: () => Chainable<Interceptor>;
/**
 * Get the last call matching the provided route matcher
 *
 * @param routeMatcher A route matcher
 * @returns The last call information or undefined if none match
 */
interceptorLastRequest: (
    routeMatcher?: IRouteMatcher
) => Chainable<CallStack | undefined>;
/**
 * Set Interceptor options,
 * must be called before a request/s occur
 *
 * @param options Options
 * @returns Current Interceptor options
 */
interceptorOptions: (options?: InterceptorOptions) => Chainable<InterceptorOptions>;
/**
 * Get a number of requests matching the provided route matcher
 *
 * @param routeMatcher A route matcher
 * @returns A number of requests matching the provided route matcher since the current test started
 */
interceptorRequestCalls: (routeMatcher?: IRouteMatcher) => Chainable<number>;
/**
 * Get statistics for all requests matching the provided route matcher since the beginning
 * of the current test
 *
 * @param routeMatcher A route matcher
 * @returns All requests matching the provided route matcher with detailed information,
 *          if none match, returns an empty array
 */
interceptorStats: (routeMatcher?: IRouteMatcher) => Chainable<CallStack[]>;
/**
 * Mock the response of requests matching the provided route matcher. By default it mocks
 * the first matching request, then the mock is removed. Set `times` in options
 * to change how many times should be the matching requests mocked.
 *
 * @param routeMatcher A route matcher
 * @param mock Response mocks
 * @param options Mock options
 * @returns An id of the created mock. It is needed if you want to remove
 *          the mock manually
 */
mockInterceptorResponse(
    routeMatcher: IRouteMatcher,
    mock: IMockResponse,
    options?: IMockResponseOptions
): Chainable<number>;
/**
 * Reset the watch of Interceptor. It sets the pointer to the last call. It is
 * needed to reset the pointer when you want to wait for certain requests.
 *
 * Example: on a site there are multiple requests to `api/getUser`, but we want
 * to wait for the specific one which occur after clicking on a button. We can not
 * know which one of the `api/getUser` calls we want to wait for. By calling this
 * method we set the exact point we want to check the next requests from.
 */
resetInterceptorWatch: () => void;
/**
 * Start time measuring (a helper function)
 *
 * @returns performance.now() when the code is executed
 */
startTiming: () => Chainable<number>;
/**
 * Stop time measuring (a helper function)
 *
 * @returns If cy.startTiming was called, returns the time difference
 *          since startTiming was called (in ms), otherwise it returns undefined
 */
stopTiming: () => Chainable<number | undefined>;
/**
 * Throttle requests matching the provided route matcher by setting a delay. By default it
 * throttles the first matching request, then the throttle is removed. Set `times`
 * in options to change how many times should be the matching requests throttled.
 *
 * @param urlMatcher A route matcher
 * @param delay A delay in ms
 * @param options Throttle options (it can include mocking the response)
 * @returns An id of the created throttle. It is needed if you want to remove
 *          the throttle manually
 */
throttleInterceptorRequest(
    routeMatcher: IRouteMatcher,
    delay: number,
    options?: IThrottleRequestOptions
): Chainable<number>;
/**
 * The method will wait until all requests matching the provided route
 * matcher finish or the maximum time of waiting is reached (`waitTimeout` in options).
 *
 * By default there must be at least one match. Otherwise it waits until
 * there is a request matching the provided route matcher OR the maximum time of waiting
 * is reached. This behaviour can be changed by setting `enforceCheck` to false in options.
 *
 * @param stringMatcherOrOptions A string matcher OR options with a route matcher
 * @param errorMessage An error message when the maximum time of waiting is reached
 * @returns An instance of Interceptor
 */
waitUntilRequestIsDone: (
    stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
    errorMessage?: string
) => Chainable<Interceptor>;

Cypress environment variables

You can provide Cypress environment variables to set some Interceptor options globally:

e2e: {
    env: {
        INTERCEPTOR_DEBUG: boolean; // default false
        INTERCEPTOR_DISABLE_CACHE: boolean; // default false
        INTERCEPTOR_REQUEST_TIMEOUT: number; // default 10000
    }
}

INTERCEPTOR_DEBUG - enables logging of all requests. More in debugInfo

INTERCEPTOR_DISABLE_CACHE - when set to true, all requests will contain an additional header "cache-control": "no-store"

INTERCEPTOR_REQUEST_TIMEOUT - value (in ms) of how long Cypress will be waiting for pending requests

Documentation and examples

You should not be using cy.intercept together with Interceptor. Interceptor is using cy.intercept to catch all requests and if you rewrite the global rule, you can destroy the logic for catching requests and Interceptor may not work.

By default only requests with resource type in ["document", "fetch", "script", "xhr"] are logged. If you want to log another types like images, CSS, etc., set resourceTypes of cy.interceptorOptions.

In almost all methods is a route matcher (IRouteMatcher) which can be string or RegExp (StringMatcher) or an object with multiple matching options. For more information about matching options explore IRouteMatcherObject.

!IMPORTANT ABOUT CACHING!

By default all web requests have set some caching options (depends on the server). That means when you run Cypress tests, it starts the browser fresh without any caches. And when you will be running your tests, some of the requests (like CSS, JavaScript, images, etc.) will be cached during Cypress run (even though Cypress claims to clear everything before each test, these remains in the web browser until the next run). The very important thing is that when a request is cached, it will never hit cy.intercept and will be never catched by Interceptor. Therefore if you are waiting in your first test for JavaScript to load - it is ok. But in your second test the request will never happen.

Example:

it("First test", () => {
    cy.visit("https://my-page.org");

    cy.waitUntilRequestIsDone("https://my-page.org/source/my-script.js");

    // will pass
})

it("Second test", () => {
    cy.visit("https://my-page.org");

    cy.waitUntilRequestIsDone("https://my-page.org/source/my-script.js");

    // will fail because https://my-page.org/source/my-script.js has been cached in the first test
    // and now it never hits cy.intercept which is Interceptor using to catch and process all requests
})

Recommendation for caching

You can disable cache by setting Cypress environment variable INTERCEPTOR_DISABLE_CACHE to true, or by cy.interceptorOptions({ disableCache: true }}.

MY PERSONAL RECOMMENDATION IS TO LEAVE THE CACHE ENABLED and work with fetch/XHR requests only. If you want to wait for JavaScript to be loaded, you can use enforceCheck option like this:

it("Second test", () => {
    cy.visit("https://my-page.org");

    cy.waitUntilRequestIsDone({
        enforceCheck: false,
        url: "https://my-page.org/source/my-script.js"
    });
})

If there is a request to https://my-page.org/source/my-script.js it waits until it finishes otherwise it continues without fail.

cy.bypassInterceptorResponse

Bypass a request response. It will not hit Cypress intercept response callback and not to store response data in the Interceptor stack, useful for big data responses.

bypassInterceptorResponse: (routeMatcher: IRouteMatcher, times?: number) => void;

References:

Example

// it will bypass one request for URL ends with `/get-data`
cy.bypassInterceptorResponse("**/get-data");

// click on some button triggering the request
cy.click("button.getData");

// wait for all requests to be done including the one ends with `/get-data`
cy.waitUntilRequestIsDone();

cy.interceptorLastRequest("**/get-data").then(stats => {
    // the response will be undefined because the response is bypassed
    expect(stats.response).to.be.undefined;
});

cy.interceptor

interceptor: () => Chainable<Interceptor>;

Get an instance of Interceptor

Example

cy.interceptor().then(interceptor => {
    interceptor.resetWatch();
    interceptor.removeMock(1);
    interceptor.removeThrottle(1);

    if (interceptor.debugIsEnabled) {
        //
    }
});

cy.interceptorLastRequest

interceptorLastRequest: (routeMatcher?: IRouteMatcher) => Chainable<CallStack | undefined>;

References:

Get the last call matching the provided route matcher. Similar to cy.interceptorStats.

Example

// get the last fetch request
cy.interceptorLastRequest({ resourceType: "fetch" }).then((stats) => {
    // stats can be undefined
});

cy.interceptorOptions

interceptorOptions: (options?: InterceptorOptions) => Chainable<InterceptorOptions>;

References:

Set Interceptor options. Best to call at the beggining of the test, in before or beforeEach. The default options are:

disableCache: undefined,
debug: undefined,
doNotLogResponseBody: false,
ingoreCrossDomain: true,
resourceTypes: ["document", "fetch", "script", "xhr"]

Example

// catch and process all resource types and cross domain requests
cy.interceptorOptions({
    ingoreCrossDomain: false,
    resourceTypes: "all"
});

cy.interceptorRequestCalls

interceptorRequestCalls: (routeMatcher?: IRouteMatcher) => Chainable<number>;

References:

Get a number of requests matching the provided route matcher.

// there should be logged only one call to a URL ending with /api/getOrder
cy.interceptorRequestCalls("**/api/getOrder").should("eq", 1);
// there should be only 4 fetch + script requests
cy.interceptorRequestCalls({ resourceType: ["fetch", "script"] }).should("eq", 4);

cy.interceptorStats

interceptorStats: (routeMatcher?: IRouteMatcher) => Chainable<CallStack[]>;

References:

Get statistics for all requests matching the provided route matcher since the beginning of the current test.

Example

Note: It just serves as an example, but I do not recommend testing any of it except request/response query and body - in some cases. It should basically serve for logging/debugging.

cy.interceptorStats("**/getUser").then((stats) => {
    expect(stats.length).to.eq(1);
    expect(stats[0].crossDomain).to.be.false;
    expect(stats[0].delay).to.be.undefined;
    expect(stats[0].duration).to.be.lt(1500);
    expect(stats[0].isPending).to.be.false;
    expect(stats[0].request.body).to.deep.eq({ id: 5 });
    expect(stats[0].request.headers["host"]).to.eq("my-page.org");
    expect(stats[0].request.query).to.deep.eq({
        ref: 987
    });
    expect(stats[0].request.method).to.eq("POST");
    expect(stats[0].resourceType).to.eq("fetch");
    expect(stats[0].response?.body).to.deep.eq({ userName: "HarryPotter" });
    expect(stats[0].response?.statusCode).to.eq(200);
    expect(stats[0].response?.statusMessage).to.eq("OK");
    expect(stats[0].url.endsWith("/getUser")).to.be.true;
});

cy.mockInterceptorResponse

mockInterceptorResponse(
    routeMatcher: IRouteMatcher,
    mock: IMockResponse,
    options?: IMockResponseOptions
): Chainable<number>;

References:

Mock the response of requests matching the provided route matcher. By default it mocks the first matching request, then the mock is removed. Set times in options to change how many times should be the matching requests mocked.

Examples

// return status 400 to all fetch requests, infinitely
cy.mockInterceptorResponse(
    { resourceType: "fetch" },
    { statusCode: 400 },
    { times: 0 }
);
// return a custom body to a request ending with /api/getUser, default once
cy.mockInterceptorResponse(
    { url: "**/api/getUser" },
    { 
        body: {
            userName: "LordVoldemort"
        }
     }
);
// return a custom header to all POST requests, infinitely
cy.mockInterceptorResponse(
    { method: "POST" },
    { 
        header: {
            "custom-header": "value"
        }
     },
     { times: 0 }
);
// return a custom body to any fetch request, twice
cy.mockInterceptorResponse(
    { resourceType: "fetch" },
    { 
        generateBody: (_request, body) => {
            if (body && "userName" in body) {
                body.userName = "LordVoldemort";
            }

            return body;
        }
     },
     { times: 2 }
);
// mock a request having query string `page` = 5, once
cy.mockInterceptorResponse(
    {
        queryMatcher: (query) => query?.page === 5
    },
    { 
        body: {
            userName: "LordVoldemort"
        }
    },
    {
        times: 1 // this is the default value, no need to set
    }
);
// mock a request having body and body.page, default once
cy.mockInterceptorResponse(
    {
        bodyMatcher: (body) => body && "page" in body
    },
    { 
        body: {
            userName: "LordVoldemort"
        }
    }
);

cy.resetInterceptorWatch

resetInterceptorWatch: () => void;

Reset the watch of Interceptor. It sets the pointer to the last call. It is needed to reset the pointer when you want to wait for certain requests.

Example

On a site there are multiple requests to api/getUser, but we want to wait for the specific one which occur after clicking on a button. We can not know which one of the api/getUser calls we want to wait for. By calling this method we set the exact point we want to check the next requests from.

// this page contains multiple requests to api/getUser when visit
cy.visit("https://www.my-page.org");

// reset the watch, so all the previous requests will be ignored in the next `waitUntilRequestIsDone`
cy.resetInterceptorWatch();

// this click should trigger a request to /api/getUser
cy.get("button#user-info").click();

// this method will not continue until the request to /api/getUser is finished
waitUntilRequestIsDone("**/api/getUser");

cy.throttleInterceptorRequest

throttleInterceptorRequest(
    routeMatcher: IRouteMatcher,
    delay: number,
    options?: IThrottleRequestOptions
): Chainable<number>;

References:

Throttle requests matching the provided route matcher by setting a delay. By default it throttles the first matching request, then the throttle is removed. Set times in options to change how many times should be the matching requests throttled.

Example

// make the request to /api/getUser last for 5 seconds
cy.throttleInterceptorRequest("**/api/getUser", 5000);
// throttle a request which has URL query string containing key `page` = 5
cy.throttleInterceptorRequest({ queryMatcher: (query) => query?.page === 5}, 5000);
// throtlle all requests for 5 seconds
cy.throttleInterceptorRequest({ resourceType: "all" }, 5000, { times: 0 });
cy.throttleInterceptorRequest("*", 5000, { times: 0 });
// throttle a request having body and body.userName
cy.throttleInterceptorRequest({ bodyMatcher: (body) => body && "userName" in body }, 5000);

cy.waitUntilRequestIsDone

waitUntilRequestIsDone: (
    stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
    errorMessage?: string
) => Chainable<Interceptor>;

References:

The method will wait until all requests matching the provided route matcher finish or the maximum time of waiting is reached (waitTimeout in options). By default waitTimeout is set to 10 seconds. This option can be set globally by Cypress environment variable INTERCEPTOR_REQUEST_TIMEOUT.

waitTimeout condition inside waitUntilRequestIsDone:

const DEFAULT_TIMEOUT = 10000;
const waitTimeout = option?.waitTimeout ?? Cypress.env("INTERCEPTOR_REQUEST_TIMEOUT") ?? DEFAULT_TIMEOUT;

By default there must be at least one match. Otherwise it waits until there is a request matching the provided route matcher OR the maximum time of waiting is reached. This behaviour can be changed by setting enforceCheck to false in options.

Examples

// will wait until all requests are finished
cy.waitUntilRequestIsDone();
// wait for requests ending with /api/getUser
cy.waitUntilRequestIsDone("**/api/getUser");
cy.waitUntilRequestIsDone(new RegExp("api\/getUser$", "i"));
// wait for requests containing /api/
cy.waitUntilRequestIsDone("**/api/**");
cy.waitUntilRequestIsDone(new RegExp("(.*)\/api\/(.*)", "i"));
// wait until this script is loaded
cy.waitUntilRequestIsDone("http://my-page.org/source/script.js");
// wait until this request is finished
cy.waitUntilRequestIsDone("http://my-page.org/api/getUser");
// providing a custom error message when maximum time of waiting is reached
cy.waitUntilRequestIsDone("http://my-page.org/api/getUser", "Request never happened");
// wait until all fetch requests are finished
cy.waitUntilRequestIsDone({ resourceType: "fetch" });
// wait until this script is loaded and if there is no such request, continue
cy.waitUntilRequestIsDone({ enforceCheck: false, url: "http://my-page.org/source/script.js" });
// wait maximum 200s for this fetch to finish
cy.waitUntilRequestIsDone({ url: "http://my-page.org/api/getUser", waitTimeout: 200000 });
// wait 2s then check if there is an another request after this one is finished
cy.waitUntilRequestIsDone({ url: "http://my-page.org/api/getUser", waitForNextRequest: 2000 });
// wait until all cross domain requests are finished but do not fail if there is no one
cy.waitUntilRequestIsDone({ crossDomain: true, enforceCheck: false });

Interceptor public methods

callStack

get callStack(): CallStack[];

Return a copy of all logged requests since the Interceptor has been created (the Interceptor is created in beforeEach).

debugInfo

get debugInfo(): IDebug[];

Get an array with all logged/skiped calls to track down a possible issue. All requests not matching the global resource types or cross domain option are skipped.

debugIsEnabled

get debugIsEnabled(): boolean;

Returns true if debug is enabled by Interceptor options or Cypress environment variable INTERCEPTOR_DEBUG. The Interceptor debug option has the highest priority so if the option is undefined (by default), it returns Cypress.env("INTERCEPTOR_DEBUG").

Implementation

return this._options.debug ?? !!Cypress.env("INTERCEPTOR_DEBUG");

getLastRequest

Same as cy.interceptorLastRequest.

getStats

Same as cy.interceptorStats.

requestCalls

Same as cy.interceptorRequestCalls.

mockResponse

Same as cy.mockInterceptorResponse.

onRequestError

Function called when a request is cancelled, aborted or fails.

onRequestError(func: OnRequestError);

removeMock

removeMock(id: number): boolean;

Remove a mock entry by id.

removeThrottle

removeThrottle(id: number): boolean;

Remove a throttle entry by id.

resetWatch

Same as cy.resetInterceptorWatch.

setOptions

Same as cy.interceptorOptions.

throttleRequest

Same as cy.throttleInterceptorRequest.

waitUntilRequestIsDone

Same as cy.waitUntilRequestIsDone.

writeDebugToLog

writeDebugToLog(outputDir: string, options?: WriteDebugOptions): void;

References:

Write the debug information to a file (debug must be enabled). The file will contain JSON.stringify of debugInfo.

Example

afterAll(() => {
    cy.interceptor().then(interceptor => {
        // example output will be "./out/test.cy.ts (Description - It).debug.json" (the name of the file `test.cy.ts (Description - It)` will be composed from the running test)
        interceptor.writeDebugToLog("./out");
        // example output will be "./out/file_name.debug.json"
        interceptor.writeDebugToLog("./out", { fileName: "file_name" });
        // filter output
        interceptor.writeDebugToLog("./out", { filter: (entry) => entry.method === "POST" });
        // map output
        interceptor.writeDebugToLog("./out", { mapper: (entry) => ({ type: entry.type, url: entry.url }) });
    });
});

writeStatsToLog

public writeStatsToLog(outputDir: string, options?: WriteStatsOptions): void;

References:

Write the logged requests' (or filtered by the provided route matcher) information to a file. The file will contain JSON.stringify of callStack.

Example

afterAll(() => {
    cy.interceptor().then(interceptor => {
        // example output will be "./out/test.cy.ts (Description - It).stats.json" (the name of the file `test.cy.ts (Description - It)` will be composed from the running test)
        interceptor.writeStatsToLog("./out");
        // example output will be "./out/file_name.stats.json"
        interceptor.writeStatsToLog("./out", { fileName: "file_name" });
        // write only "fetch" requests
        interceptor.writeStatsToLog("./out", { routeMatcher: { resourceType: "fetch" }});
        // filter output
        interceptor.writeStatsToLog("./out", { filter: (entry) => entry.method === "POST" });
        // map output
        interceptor.writeStatsToLog("./out", { mapper: (entry) => ({ url: entry.url }) });
    });
});

Interfaces

IHeadersNormalized

type IHeadersNormalized = { [key: string]: string };

InterceptorOptions

interface InterceptorOptions {
    /**
     * By default the web browser is caching the requests. Caching can be disabled by Cypress.env
     * `INTERCEPTOR_DISABLE_CACHE` or by this option. This option has the highest priority. If it is
     * set to false, the cache is always enabled no matter to value of Cypress.env("INTERCEPTOR_DISABLE_CACHE")
     */
    disableCache?: boolean;
    /**
     * When it is true, calling `debugInfo` will return an array with all catched requests
     */
    debug?: boolean;
    /**
     * When true, response body will not be logged due to performance issues
     */
    doNotLogResponseBody?: boolean;
    /**
     * Ignore request outside the domain, default: true
     */
    ingoreCrossDomain?: boolean;
    /**
     * Which resource types should be processed, default: ["document", "fetch", "script", "xhr"],
     *
     * Provide "all" for processing all requests no matter to the resource type
     */
    resourceTypes?: ResourceType | ResourceType[] | "all";
}

IMockResponse

interface IMockResponse {
    /**
     * A response body, it can be anything
     */
    body?: unknown;
    /**
     * Generate a body with the original response body, this option is preferred before option `body`
     *
     * @param request An object with the request data (body, query, method, ...)
     * @param originalBody The original response body
     * @returns A response body, it can be anything
     */
    generateBody?: (request: IRequest, originalBody: unknown) => unknown;
    /**
     * If provided, will be added to the original response headers
     */
    headers?: IHeadersNormalized;
    /**
     * Response status code
     */
    statusCode?: number;
}

IMockResponseOptions

interface IMockResponseOptions {
    /**
     * How many times the response should be mocked, by default it is set to 1.
     * Set to 0 to mock the response infinitely
     */
    times?: number;
}

IRouteMatcher

/**
 * String comparison is case insensitive. Provide RegExp without case sensitive flag if needed.
 */
type IRouteMatcher = StringMatcher | IRouteMatcherObject;

IRouteMatcherObject

type IRouteMatcherObject = {
    /**
     * A matcher for the request body
     *
     * @param body The request body
     * @returns True if matches
     */
    bodyMatcher?: (body: unknown) => boolean;
    /**
     * If true, only cross domain requests match
     */
    crossDomain?: boolean;
    /**
     * A matcher for headers
     *
     * @param headers The request headers
     * @returns True if matches
     */
    headersMatcher?: (headers: IHeadersNormalized) => boolean;
    /**
     * If true, only HTTPS requests match
     */
    https?: RouteMatcherOptions["https"];
    /**
     * Request method (GET, POST, ...)
     */
    method?: RequestMethod;
    /**
     * A matcher for query string
     *
     * @param query The URL query string
     * @returns True if matches
     */
    queryMatcher?: (query: Record<string, string | number>) => boolean;
    /**
     * Resource type (document, script, fetch, ....)
     */
    resourceType?: ResourceType | ResourceType[] | "all";
    /**
     * A URL matcher, use * or ** to match any word in string ("**\/api/call", "**\/script.js", ...)
     */
    url?: StringMatcher;
};

IThrottleRequestOptions

interface IThrottleRequestOptions {
    /**
     * Mock a response for the provided route matcher. If provided together with
     * `mockResponse` or `cy.mockInterceptorResponse` it has lesser priority
     */
    mockResponse?: IMockResponse;
    /**
     * How many times the request should be throttled, by default it is set to 1.
     * Set to 0 to throttle the request infinitely
     */
    times?: number;
}

StringMatcher

type StringMatcher = string | RegExp;

WaitUntilRequestOptions

interface WaitUntilRequestOptions extends IRouteMatcherObject {
    /**
     * True by default. If true, a request matching the provided route matcher must be logged by Interceptor,
     * otherwise it waits until the url is logged and finished or it fails if the time of waiting runs out. If
     * set to false, it checks if there is a request matching the provided route matcher. If yes, it waits until
     * the request is done. If no, it does not fail and end successfully.
     */
    enforceCheck?: boolean;
    /**
     * Time to wait in ms. Default set to 750
     *
     * There is needed to wait if there is a possible following request after the last one (because of the JS code
     * and subsequent requests). Set to 0 to skip repetitive checking for requests.
     */
    waitForNextRequest?: number;
    /**
     * Time of how long Cypress will be waiting for the pending requests.
     * Default set to 10000 or environment variable `INTERCEPTOR_REQUEST_TIMEOUT` if set
     */
    waitTimeout?: number;
}

WriteDebugOptions

interface WriteDebugOptions {
    /**
     * A name of the file, if undefined, it will be composed from the running test
     */
    fileName?: string;
    /**
     * A possibility to filter the logged items
     *
     * @param debugInfo A call info stored in the stack
     * @returns false if the item should be skipped
     */
    filter?: (debugInfo: IDebug) => boolean;
    /**
     * A possibility to map the logged items
     *
     * @param callStack A call info stored in the stack
     * @returns Any object you want to log
     */
    mapper?: (debugInfo: IDebug) => unknown;
    /**
     * When true, the output JSON will be formatted with tabs
     */
    prettyOutput?: boolean;
}

WriteStatsOptions

interface WriteStatsOptions {
    /**
     * A name of the file, if undefined, it will be composed from the running test
     */
    fileName?: string;
    /**
     * A possibility to filter the logged items
     *
     * @param callStack A call info stored in the stack
     * @returns false if the item should be skipped
     */
    filter?: (callStack: CallStack) => boolean;
    /**
     * A possibility to map the logged items
     *
     * @param callStack A call info stored in the stack
     * @returns Any object you want to log
     */
    mapper?: (callStack: CallStack) => unknown;
    /**
     * When true, the output JSON will be formatted with tabs
     */
    prettyOutput?: boolean;
    /**
     * A route matcher
     */
    routeMatcher?: IRouteMatcher;
}

Useful tips

Log on fail

You can use Interceptor to write all non cached requests to a file. Just use this code in your cypress/support/e2e.ts or cypress/support/e2e.js:

import "cypress-interceptor";

afterEach(function () {
    if (this.currentTest?.state === "failed") {
        cy.interceptor().then(interceptor => {
            interceptor.writeStatsToLog("./mochawesome-report/_interceptor");
        });
    }
});

The code above will write all requests to the output file. But you can use a route matcher to filter only requests you want. For example:

// the output will contain only ajax requests
interceptor.writeStatsToLog("./mochawesome-report/_interceptor", { resourceType: ["fetch", "xhr"] });

See the methods you can use: writeStatsToLog or writeDebugToLog

Clean videos for successful tests

If you have a reporter and you send the report somewhere, you maybe want only videos for failed tests. If so, you can do it like this:

// in `cypress/support/e2e.js` or `cypress/support/e2e.ts`
import { defineConfig } from 'cypress';
import * as fs from 'fs';

export default defineConfig({
    e2e: {
        setupNodeEvents(on, config) {
            // clean videos for successful tests
            on('after:run', results => {
                if (!('runs' in results)) {
                    return;
                }

                for (const run of results.runs) {
                    if (run.stats.failures === 0 && run.video && fs.existsSync(run.video)) {
                        fs.unlinkSync(run.video);
                    }
                }
            });
        }
    }
});

Websocket Interceptor

Getting started

It is very simple, just install the package using yarn or npm and import the package in your cypress/support/e2e.js or cypress/support/e2e.ts:

import "cypress-interceptor/lib/websocket";

Websocket Interceptor Cypress commands

/**
 * Get an instance of Websocket Interceptor
 *
 * @returns An instance of Websocket Interceptor
 */
wsInterceptor: () => Chainable<WebsocketInterceptor>;
/**
 * Get the last call matching the provided matcher
 *
 * @param matcher A matcher
 * @returns The last call information or undefined if none match
 */
wsInterceptorLastRequest: (
    matcher?: IWSMatcher
) => Chainable<CallStackWebsocket | undefined>;
/**
 * Set Websocket Interceptor options,
 * must be called before a request/s occur
 *
 * @param options Options
 * @returns Current Websocket Interceptor options
 */
wsInterceptorOptions: (
    options?: WebsocketInterceptorOptions
) => Chainable<WebsocketInterceptorOptions>;
/**
 * Get statistics for all requests matching the provided matcher since the beginning
 * of the current test
 *
 * @param matcher A matcher
 * @returns All requests matching the provided matcher with detailed information,
 *          if none match, returns an empty array
 */
wsInterceptorStats: (matcher?: IWSMatcher) => Chainable<CallStackWebsocket[]>;
/**
 * Reset the watch of Websocket Interceptor
 */
wsResetInterceptorWatch: () => void;
/**
 * Wait until a websocket action occur
 *
 * @param options Action options
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    options?: WaitUntilActionOptions,
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
 * Wait until a websocket action occur
 *
 * @param matcher A matcher
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    matcher?: IWSMatcher | IWSMatcher[],
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
 * Wait until a websocket action occur
 *
 * @param matcher A matcher
 * @param options Action options
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    matcher?: IWSMatcher | IWSMatcher[],
    options?: WaitUntilActionOptions,
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;

Cypress environment variables

Same as Cypress environment variables.

Documentation and examples

cy.wsInterceptor

wsInterceptor: () => Chainable<WebsocketInterceptor>;

Get an instance of Websocket Interceptor

Example

cy.wsInterceptor().then(interceptor => {
    interceptor.resetWatch();

    if (interceptor.debugIsEnabled) {
       intereptor.writeStatsToLog("_logs", { protocols: "soap" }, "stats");
    }
});

cy.wsInterceptorLastRequest

Get the last call matching the provided matcher

wsInterceptorLastRequest: (matcher?: IWSMatcher) => Chainable<CallStackWebsocket | undefined>;

Example

cy.wsInterceptorLastRequest({ url: "some-url" }).should("not.be.undefined");

 cy.wsInterceptorLastRequest({ type: "close" }).then((entry) => {
    expect(entry).not.to.be.undefined;
    expect(entry!.data).to.haveOwnProperty("code", code);
    expect(entry!.data).to.haveOwnProperty("reason", reason);
    expect(entry!.url.toString().endsWith("some-url")).to.be.true;
});

cy.wsInterceptorOptions

Set Websocket Interceptor options, must be called before a request/s occur

wsInterceptorOptions: (options?: WebsocketInterceptorOptions) => Chainable<WebsocketInterceptorOptions>;

cy.wsInterceptorStats

Get statistics for all requests matching the provided matcher since the beginning of the current test

wsInterceptorStats: (matcher?: IWSMatcher) => Chainable<CallStackWebsocket[]>;

Example

cy.wsInterceptorStats({ type: "send" }).then((stats) => {
    expect(stats.length).to.eq(2);
    expect(stats[0].data).not.to.be.empty;
    expect(stats[1].data).not.to.be.empty;
});

cy.wsInterceptorStats({ type: "onmessage" }).then((stats) => {
    expect(stats.length).to.eq(2);
    expect(stats[0].data).to.haveOwnProperty("data", "some response 1");
    expect(stats[1].data).to.haveOwnProperty("data", "some response 2");
});

cy.wsResetInterceptorWatch

Reset the watch of Websocket Interceptor

wsResetInterceptorWatch: () => void;

cy.waitUntilWebsocketAction

Wait until a websocket action occur

/**
 * Wait until a websocket action occur
 *
 * @param options Action options
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    options?: WaitUntilActionOptions,
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
 * Wait until a websocket action occur
 *
 * @param matcher A matcher
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    matcher?: IWSMatcher | IWSMatcher[],
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
 * Wait until a websocket action occur
 *
 * @param matcher A matcher
 * @param options Action options
 * @param errorMessage An error message when the maximum time of waiting is reached
 */
waitUntilWebsocketAction(
    matcher?: IWSMatcher | IWSMatcher[],
    options?: WaitUntilActionOptions,
    errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;

Example

// wait for the specific response
cy.waitUntilWebsocketAction({
    data: "some response",
    type: "onmessage"
});
// wait for the specific send
cy.waitUntilWebsocketAction({
    data: "some data",
    type: "send"
});
// wait for two sends
cy.waitUntilWebsocketAction(
    {
        type: "send"
    },
    { countMatch: 2 }
);
// wait for multiple actions
cy.waitUntilWebsocketAction([
    {
        data: "onmessage data",
        type: "onmessage",
        url: "**/path-1"
    },
    {
        data: "send data",
        type: "send",
        url: "**/path-2"
    },
    {
        data: "onmessage data",
        protocols: "xmpp",
        type: "onmessage",
        url: "**/path-3"
    }
]);
// wait for an action having a url filtered by RegExp
cy.waitUntilWebsocketAction({
    data: responseData12,
    type: "onmessage",
    url: new RegExp(`some-path$`, "i")
});
// wait for a specific close action having code and reason
cy.waitUntilWebsocketAction([
    {
        code: 1000,
        reason: "some reason",
        type: "close"
    }
]);

Websocket Interceptor public methods

callStack

Return a copy of all logged requests since the Websocket Interceptor has been created (the Websocket Interceptor is created in beforeEach).

get callStack(): CallStackWebsocket[];

debugIsEnabled

get debugIsEnabled(): boolean;

Returns true if debug is enabled by Websocket Interceptor options or Cypress environment variable INTERCEPTOR_DEBUG. The Websocket Interceptor debug option has the highest priority so if the option is undefined (by default), it returns Cypress.env("INTERCEPTOR_DEBUG").

Implementation

return this._options.debug ?? !!Cypress.env("INTERCEPTOR_DEBUG");

getLastRequest

Same as cy.wsInterceptorLastRequest.

getStats

Same as `cy.wsInterceptorStats.

resetWatch

Same as `cy.wsResetInterceptorWatch.

setOptions

Same as wsInterceptorOptions.

waitUntilWebsocketAction

Same as cy.waitUntilWebsocketAction.

writeStatsToLog

writeStatsToLog(outputDir: string, options?: WriteStatsOptions): void;

References:

Write the logged requests' (or filtered by the provided matcher) information to a file. The file will contain JSON.stringify of callStack.

Example

afterAll(() => {
    cy.wsInterceptor().then(interceptor => {
        // example output will be "./out/test.cy.ts (Description - It).ws.stats.json" (the name of the file `test.cy.ts (Description - It)` will be composed from the running test)
        interceptor.writeStatsToLog("./out");
        // example output will be "./out/file_name.ws.stats.json"
        interceptor.writeStatsToLog("./out", { fileName: "file_name" });
        // write only stats for a specific URL
        interceptor.writeStatsToLog("./out", { matcher: { url: "**/some-url" } });
        // filter output
        interceptor.writeStatsToLog("./out", { filter: (entry) => entry.type === "onmessage" });
        // map output
        interceptor.writeStatsToLog("./out", { mapper: (entry) => ({ type: entry.type, url: entry.url }) });
    });
});

Interfaces

IWSMatcher

type IWSMatcher = {
    /**
     * A matcher for query string
     *
     * @param query The URL query string
     * @returns True if matches
     */
    queryMatcher?: (query: Record<string, string | number>) => boolean;
    /**
     * Match by protocols
     */
    protocols?: string | string[];
    /**
     * A URL matcher, use * or ** to match any word in string ("**\/api/call", ...)
     */
    url?: StringMatcher;
} & (
    | {
          types?: ("create" | "close" | "onclose" | "onerror" | "onopen" | "onmessage" | "send")[];
      }
    | {
          type?: "create" | "onerror" | "onopen";
      }
    | {
          code?: number;
          reason?: string;
          type?: "close";
      }
    | {
          code?: number;
          reason?: string;
          type: "onclose";
      }
    | {
          data?: string;
          type: "onmessage";
      }
    | {
          data?: string;
          type: "send";
      }
);

WriteStatsOptions

interface WriteStatsOptions {
    /**
     * A name of the file, if undefined, it will be composed from the running test
     */
    fileName?: string;
    /**
     * A possibility to filter the logged items
     *
     * @param callStack A call info stored in the stack
     * @returns false if the item should be skipped
     */
    filter?: (callStack: CallStackWebsocket) => boolean;
    /**
     * A possibility to map the logged items
     *
     * @param callStack A call info stored in the stack
     * @returns Any object you want to log
     */
    mapper?: (callStack: CallStackWebsocket) => unknown;
    /**
     * A matcher
     */
    matcher?: IWSMatcher;
    /**
     * When true, the output JSON will be formatted with tabs
     */
    prettyOutput?: boolean;
}

watchTheConsole

A helper function for logging console of the browser to a file. After a test fail it creates a file in the output folder with all console entries.

Using

In your cypress/support/e2e.js or cypress/support/e2e.ts:

import { watchTheConsole } from "cypress-interceptor/lib/console";

// catch all console entries like log, info, warn, error, ...
watchTheConsole("output_dir");

Or you can customize it (there is a difference between console.error and regular JS error):

import { ConsoleLogType, watchTheConsole } from "cypress-interceptor/lib/console";

watchTheConsole("output_dir", {
    // also log to a different folder any JS error in successful tests
    customLogs: [
        { outputDir: './_jsError', types: [ConsoleLogType.Error] }
    ],
    // log to the file only errors and warnings on fail
    logOnlyType: [ConsoleLogType.ConsoleError, ConsoleLogType.ConsoleWarn, ConsoleLogType.Error],
});