cypress-interceptor v3.8.1
Cypress Interceptor
About
Cypress Interceptor is a substitute for cy.intercept
. Its main purpose is to log all fetch or XHR requests, which can be analyzed in case of failure. It provides extended ways to log these statistics, including the ability to mock or throttle requests easily. Cypress Interceptor is better than cy.intercept
because it can avoid issues, especially when using global request catch.
There is also an option to monitor the web browser console output and log it to a file or work with websockets. For more details, refer to the Watch The Console or websocket section.
Motivation
This diagnostic tool is born out of extensive firsthand experience tracking down elusive, seemingly random Cypress test failures. These issues often weren’t tied to Cypress itself, but rather to the behavior of the underlying web application—especially in headless runs on build servers where no manual interaction is possible. By offering robust logging for both API requests and the Web console, the tool provides greater transparency and insight into the root causes of failures, ultimately helping developers streamline their debugging process and ensure more reliable test outcomes.
What's new
- Added
test.unit
as a helper for testing. - Improved the use of Interceptor in
before
hooks and added the ability to pass a function tocy.waitUntilRequestIsDone
- Watch The Console has been reworked and its logic completely changed
- The improved Watch The Console now safely logs objects and functions, with an added filtering option
- Added
cy.writeInterceptorStatsToLog
andcy.wsInterceptorStatsToLog
- Complete rework, exclude
cy.intercept
as the main tool of logging, stabilizing runs, support all fetch and XHR body types - Work with canceled and aborted requests
- Add support for Websockets
Table of contents
- Cypress Interceptor
- Watch The Console
- Websocket Interceptor
test.unit
Getting started
It is very simple, just install the package using yarn
or npm
and import the package in your cypress/support/e2e.js
, cypress/support/e2e.ts
or in any of your test files:
import "cypress-interceptor";
Would you just log all requests to a file on fail?
Would you like to wait until a request or requests are done?
The Cypress Interceptor commands
/**
* 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 matches.
*/
interceptorLastRequest: (
routeMatcher?: IRouteMatcher
) => Chainable<CallStack | undefined>;
/**
* Set the Interceptor options. This must be called before a request occurs.
*
* @param options Options
* @returns The current Interceptor options
*/
interceptorOptions: (options?: InterceptorOptions) => Chainable<InterceptorOptions>;
/**
* Get the number of requests matching the provided route matcher.
*
* @param routeMatcher A route matcher
* @returns The number of requests matching the provided route matcher since the current test started.
*/
interceptorRequestCalls: (routeMatcher?: IRouteMatcher) => Chainable<number>;
/**
* Get the statistics for all requests matching the provided route matcher since the beginning
* of the current test.
*
* @param routeMatcher A route matcher
* @returns It returns all requests matching the provided route matcher with detailed information.
* If none match, it 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, and then the mock is removed. Set `times` in the options to change
* how many times the matching requests should be mocked.
*
* @param routeMatcher A route matcher
* @param mock The response mock
* @param options The mock options
* @returns The ID of the created mock. This is needed if you want to remove the mock manually.
*/
mockInterceptorResponse(
routeMatcher: IRouteMatcher,
mock: IMockResponse,
options?: IMockResponseOptions
): Chainable<number>;
/**
* Reset the Interceptor's watch. It sets the pointer to the last call. Resetting the pointer
* is necessary 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 that occurs after clicking a button. Since we cannot know which api/getUser call
* to wait for, calling this method sets the exact point from which we want to check the next requests.
*/
resetInterceptorWatch: VoidFunction;
/**
* Start the time measurement (a helper function)
*
* @returns performance.now() when the code is executed
*/
startTiming: () => Chainable<number>;
/**
* Stop the time measurement (a helper function)
*
* @returns If `cy.startTiming` was called, it 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, and then the throttle is removed. Set times in the options to change
* how many times the matching requests should be throttled.
*
* @param routeMatcher A route matcher
* @param delay The delay in ms
* @param options The throttle options (which can include mocking the response).
* @returns The ID of the created throttle. This 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 are finished or until
* the maximum waiting time (`timeout` in options) is reached.
*
* By default, there must be at least one match. Otherwise, it waits until a request matches the
* provided route matcher or until the maximum waiting time is reached. This behavior can be changed
* by setting `enforceCheck` to `false` in the options.
*
* @param action An action which should trigger a request
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns The result from the action
*/
waitUntilRequestIsDone<T>(
action: () => Cypress.Chainable<T>,
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Chainable<T>;
/**
* @param action An action which should trigger a request
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns The result from the action
*/
waitUntilRequestIsDone<T>(
action: () => T,
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Chainable<T>;
/**
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns An instance of the Interceptor
*/
waitUntilRequestIsDone(
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Chainable<Interceptor>;
/**
* Write the logged requests' information (or those filtered by the provided route matcher) to a file
*
* @example cy.writeInterceptorStatsToLog("./out") => the output file will be "./out/Description - It.stats.json"
* @example cy.writeInterceptorStatsToLog("./out", { fileName: "file_name" }) => the output file will be "./out/file_name.stats.json"
* @example cy.writeInterceptorStatsToLog("./out", { routeMatcher: { method: "GET" } }) => write only "GET" requests to the output file
* @example cy.writeInterceptorStatsToLog("./out", { mapper: (callStack) => ({ type: callStack.type, url: callStack.url }) }) => map the output that will be written to the output file
*
* @param outputDir The path for the output folder
* @param options Options
*/
writeInterceptorStatsToLog: (outputDir: string, options?: WriteStatsOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>) => Chainable<null>;
Cypress environment variables
You can provide Cypress environment variables to set certain Interceptor options globally:
e2e: {
env: {
INTERCEPTOR_REQUEST_TIMEOUT: number; // default 10000
}
}
INTERCEPTOR_REQUEST_TIMEOUT
- the value (in ms) that defines how long the Interceptor will wait for pending requests when call cy.waitUntilRequestIsDone()
Documentation and examples
In almost all methods, there is a route matcher (IRouteMatcher
) that can be a string, a RegExp (StringMatcher
), or an object with multiple matching options. For more information about matching options, explore IRouteMatcherObject
.
cy.interceptor
interceptor: () => Chainable<Interceptor>;
Get an instance of the Interceptor
Example
cy.interceptor().then(interceptor => {
interceptor.resetWatch();
interceptor.removeMock(1);
interceptor.removeThrottle(1);
});
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 the Interceptor options. It's best to call this at the beginning of the test, in before
or beforeEach
. The default options are:
ignoreCrossDomain: false
Example
// ignore the cross-domain requests
cy.interceptorOptions({
ignoreCrossDomain: true
});
cy.interceptorRequestCalls
interceptorRequestCalls: (routeMatcher?: IRouteMatcher) => Chainable<number>;
References:
Get the number of requests matching the provided route matcher.
// There should be only one call logged to a URL ending with `/api/getOrder`
cy.interceptorRequestCalls("**/api/getOrder").should("eq", 1);
// there should be only 4 fetch requests
cy.interceptorRequestCalls({ resourceType: ["fetch"] }).should("eq", 4);
cy.interceptorStats
interceptorStats: (routeMatcher?: IRouteMatcher) => Chainable<CallStack[]>;
References:
Get the 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 for the 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].duration).to.be.lt(1500);
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.pathname.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, and then the mock is removed. Set times
in the options to change how many times the matching requests should be mocked.
Important
By default, the mocked request does not reach the network layer. Set allowHitTheNetwork
to true
if you want the request to reach the network layer
Examples
// return status 400 to all fetch requests, indefinitely
cy.mockInterceptorResponse(
{ resourceType: "fetch" },
{ statusCode: 400 },
{ times: Number.POSITIVE_INFINITY }
);
// 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, indefinitely
cy.mockInterceptorResponse(
{ method: "POST" },
{
header: {
"custom-header": "value"
}
},
{ times: Number.POSITIVE_INFINITY }
);
// return a custom body to any fetch request, twice
cy.mockInterceptorResponse(
{ resourceType: "fetch" },
{
generateBody: (request, getJsonRequestBody) => {
return {
userName: "LordVoldemort"
};
}
},
{ times: 2 }
);
// generate the response dynamically
cy.mockInterceptorResponse(
{ resourceType: "fetch" },
{
generateBody: (_, getJsonRequestBody) =>
"page" in getJsonRequestBody<{ page: number }>()
? ({ custom: "resposne 1" })
: ({ custom: "resposne 2" })
}
);
// mock the 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 the request having `page` in the request body, default once
cy.mockInterceptorResponse(
{
bodyMatcher: () => {
try {
const body = JSON.parse(bodyString);
return isObject(body) && "page" in body;
} catch {
return false;
}
}
},
{
body: {
userName: "LordVoldemort"
}
}
);
cy.resetInterceptorWatch
resetInterceptorWatch: () => void;
Reset the Interceptor's watch. It sets the pointer to the last call. Resetting the pointer is necessary 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 that occurs after clicking a button. Since we cannot know which api/getUser call to wait for, calling this method sets the exact point from which we want to check the next requests.
// this page contains multiple requests to `api/getUser` when visit
cy.visit("https://www.my-page.org");
// reset the watch, so all the previous (or pending) 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();
// the test will not continue until the request to `/api/getUser` is finished
cy.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, and then the throttle is removed. Set times in the options to change how many times the matching requests should be throttled.
In the options, the mockResponse
property can accept the same mocking object as shown in cy.mockInterceptorResponse.
Example
// make the request to `/api/getUser` last for 5 seconds
cy.throttleInterceptorRequest("**/api/getUser", 5000);
// throttle a request which has the URL query string containing key `page` equal to 5
cy.throttleInterceptorRequest({ queryMatcher: (query) => query?.page === 5}, 5000);
// throtlle all requests for 5 seconds
cy.throttleInterceptorRequest({ resourceType: "all" }, 5000, { times: Number.POSITIVE_INFINITY });
cy.throttleInterceptorRequest("*", 5000, { times: Number.POSITIVE_INFINITY });
// throttle the request having `userName` in the request body
cy.throttleInterceptorRequest(
{
bodyMatcher: (bodyString) => {
try {
const body = JSON.parse(bodyString);
return isObject(body) && "userName" in body;
} catch {
return false;
}
}
},
5000
);
cy.waitUntilRequestIsDone
/**
* @param action An action which should trigger a request
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns The result from the action
*/
public waitUntilRequestIsDone<T>(
action: () => Cypress.Chainable<T>,
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Cypress.Chainable<T>;
/**
* @param action An action which should trigger a request
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns The result from the action
*/
public waitUntilRequestIsDone<T>(
action: () => T,
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Cypress.Chainable<T>;
/**
* @param stringMatcherOrOptions A string matcher OR options with a route matcher
* @param errorMessage An error message when the maximum waiting time is reached
* @returns An instance of the Interceptor
*/
public waitUntilRequestIsDone(
stringMatcherOrOptions?: StringMatcher | WaitUntilRequestOptions,
errorMessage?: string
): Cypress.Chainable<Interceptor>;
References:
The method will wait until all requests matching the provided route matcher are finished or until the maximum waiting time (timeout
in options) is reached.
The timeout
option is set to 10 seconds by default. This option can be set globally by Cypress environment variable INTERCEPTOR_REQUEST_TIMEOUT
.
The timeout
priority resolution.
const DEFAULT_TIMEOUT = 10000;
const timeout = option?.timeout ?? Cypress.env("INTERCEPTOR_REQUEST_TIMEOUT") ?? DEFAULT_TIMEOUT;
By default, there must be at least one match. Otherwise, it waits until a request matches the provided route matcher or until the maximum waiting time is reached. This behavior can be changed by setting enforceCheck
to false
in the options.
Important
It is crutial to call cy.resetInterceptorWatch()
before an action that should trigger a request you want to wait for, or pass an action that should trigger the request as the first argument. The reason is that there may be a chain of requests preventing the one you want to wait for from being processed. More details below.
Examples
// will wait until all requests in progress are finished
cy.waitUntilRequestIsDone();
// will wait until the login request is finished
// cy.resetInterceptorWatch is called automatically when you provide a function as the first argument
cy.waitUntilRequestIsDone(
// the request which triggers the request
() => cy.contains("button", "Log In").click(),
"**/api/log-in"
);
// when you do not provide a function as the first argument it is needed to call cy.resetInterceptorWatch
// because the request you want to wait for could be called before and Interceptor will ses it as done
cy.resetInterceptorWatch();
// any action that should trigger the request
cy.contains("button", "Log In").click();
// wait for requests ending with `/api/getUser`
cy.waitUntilRequestIsDone("**/api/getUser");
cy.waitUntilRequestIsDone(new RegExp("api\/getUser$", "i"));
cy.waitUntilRequestIsDone();
// any action that should trigger the request
action();
// wait for requests containing `/api/`
cy.waitUntilRequestIsDone("**/api/**");
cy.waitUntilRequestIsDone(new RegExp("(.*)\/api\/(.*)", "i"));
// 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 maximum 200s for this fetch to finish
cy.waitUntilRequestIsDone({ url: "http://my-page.org/api/getUser", timeout: 200000 });
// wait 2s after the request to `api/getUser` finishes to check if there is an another request
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 none
cy.waitUntilRequestIsDone({ crossDomain: true, enforceCheck: false });
// increase the timeout of `cy.writeFile` if you expect the stats to be large
cy.waitUntilRequestIsDone(outputDir, {
timeout: 60000
});
cy.writeInterceptorStatsToLog
writeInterceptorStatsToLog(outputDir: string, options?: WriteStatsOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>): Chainable<null>
References:
Write the logged requests' information (or those filtered by the provided route matcher) to a file. The file will contain the JSON.stringify of callStack
.
Example
afterAll(() => {
// the output file will be "./out/test.cy.ts (Description - It).stats.json" (the name of the file `test.cy.ts (Description - It)` will be generated from the running test)
cy.writeInterceptorStatsToLog("./out");
// increase the timeout for `cy.writeFile` when you expect a big output
cy.writeInterceptorStatsToLog("./out", { timeout: 120000 });
// the output file will be "./out/file_name.stats.json"
cy.writeInterceptorStatsToLog("./out", { fileName: "file_name" });
// write only "fetch" requests to the output file
cy.writeInterceptorStatsToLog("./out", { routeMatcher: { resourceType: "fetch" }});
// write only "POST" requests to the output file
cy.writeInterceptorStatsToLog("./out", { filter: (entry) => entry.method === "POST" });
// map the output that will be written to the output file
cy.writeInterceptorStatsToLog("./out", { mapper: (entry) => ({ url: entry.url }) });
});
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
).
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 the mock entry by ID.
removeThrottle
removeThrottle(id: number): boolean;
Remove the throttle entry by ID.
resetWatch
Same as cy.resetInterceptorWatch
.
setOptions
Same as cy.interceptorOptions
.
throttleRequest
Same as cy.throttleInterceptorRequest
.
waitUntilRequestIsDone
Same as cy.waitUntilRequestIsDone
.
writeStatsToLog
Same as cy.writeInterceptorStatsToLog
.
Interfaces
IHeadersNormalized
type IHeadersNormalized = { [key: string]: string };
InterceptorOptions
interface InterceptorOptions {
/**
* Ignore requests outside the domain (default: `false`)
*/
ignoreCrossDomain?: boolean;
}
IMockResponse
interface IMockResponse {
/**
* When this property is set to `true`, it allows the request to reach the network.
* By default, the mocked request does not reach the network layer.
*/
allowHitTheNetwork?: boolean;
/**
* The response body, it can be anything
*/
body?: unknown;
/**
* Generate a body with the original response body. This option has higher priority
* than the `body` option.
*
* @param request An object with the request data (body, query, method, ...)
* @param getJsonRequestBody It will try to return a parsed request body
* @returns The response body, it can be anything
*/
generateBody?: (request: IRequest, getJsonRequestBody: <T = unknown>() => T) => unknown;
/**
* If provided, this will be added to the original response headers.
*/
headers?: IHeadersNormalized;
/**
* The response status code
*/
statusCode?: number;
/**
* The response status text
*/
statusText?: string;
}
IMockResponseOptions
interface IMockResponseOptions {
/**
* The number of times the response should be mocked. By default, it is set to 1.
* Set it to Number.POSITIVE_INFINITY to mock the response indefinitely.
*/
times?: number;
}
IRouteMatcher
/**
* String comparison is case-insensitive. Provide a RegExp without the case-sensitive flag if needed.
*/
type IRouteMatcher = StringMatcher | IRouteMatcherObject;
IRouteMatcherObject
type IRouteMatcherObject = {
/**
* A matcher for the request body
*
* @param requestBody The request body in string format
* @returns `true` if matches
*/
bodyMatcher?: (requestBody: string) => boolean;
/**
* If set to `true`, only cross-domain requests will match
*/
crossDomain?: boolean;
/**
* A matcher for the request headers
*
* @param requestHeaders The request headers
* @returns `true` if matches
*/
headersMatcher?: (requestHeaders: IHeaders) => boolean;
/**
* If set to `true`, only HTTPS requests will match
*/
https?: RouteMatcherOptions["https"];
/**
* The request method (GET, POST, ...)
*/
method?: RequestMethod;
/**
* A matcher for the query string (URL search params)
*
* @param query The URL qearch params as an object
* @returns `true` if matches
*/
queryMatcher?: (query: Record<string, string | number>) => boolean;
/**
* The resource type
*/
resourceType?: IResourceType | IResourceType[] | "all";
/**
* A URL matcher, use * or ** to match any word in string
*
* @example "**\/api/call" will match "http://any.com/api/call", "http://any.com/test/api/call", "http://any.com/test/api/call?page=99", ...
* @example "*\api\*" will match "http://any.com/api/call", "http://any.com/api/list", "http://any.com/api/call-2?page=99&filter=1",
* @example "**" will match any URL
*/
url?: StringMatcher;
};
IThrottleRequestOptions
interface IThrottleRequestOptions {
/**
* Mock a response for the provided route matcher. If used together with `mockResponse`
* or `cy.mockInterceptorResponse`, it has lower priority.
*/
mockResponse?: IMockResponse;
/**
* The number of times the request should be throttled. By default, it is set to 1.
* Set it to Number.POSITIVE_INFINITY to throttle the request indefinitely.
*/
times?: number;
}
StringMatcher
type StringMatcher = string | RegExp;
WaitUntilRequestOptions
interface WaitUntilRequestOptions extends IRouteMatcherObject {
/**
* The value is `true` by default. If set to `true`, a request matching the provided
* route matcher must be logged by the Interceptor; otherwise, it waits until the
* URL is logged and finished or fails if the waiting time runs out. If set to `false`,
* it checks for a request matching the provided route matcher. If one exists, it
* waits until the request is complete. If not, it does not fail and ends successfully.
*/
enforceCheck?: boolean;
/**
* The duration Interceptor will wait for pending requests. The default is set to 10,000
* or the value of the `INTERCEPTOR_REQUEST_TIMEOUT` environment variable if specified.
*/
timeout?: number;
/**
* Time to wait in milliseconds. The default is set to 750.
*
* It is necessary to wait if there might be a following request after the last one
* (due to JavaScript code and subsequent requests). Set it to 0 to skip repeated
* checking for requests.
*/
waitForNextRequest?: number;
}
WriteStatsOptions
interface WriteStatsOptions {
/**
* The name of the file. If `undefined`, it will be generated from the running test.
*/
fileName?: string;
/**
* An option to filter the logged items
*
* @param callStack Call information stored in the stack
* @returns `false` if the item should be skipped
*/
filter?: (callStack: CallStack) => boolean;
/**
* An option to map the logged items
*
* @param callStack Call information stored in the stack
* @returns Any object you want to log
*/
mapper?: (callStack: CallStack) => unknown;
/**
* When set to `true`, the output JSON will be formatted with tabs
*/
prettyOutput?: boolean;
/**
* A route matcher
*/
routeMatcher?: IRouteMatcher;
}
Useful tips
Log on fail
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.writeInterceptorStatsToLog("./mochawesome-report/_interceptor");
}
});
The code above will write all requests to the output file. However, you can use a route matcher to filter only the requests you want. For example:
// the output will contain only ajax requests
cy.writeInterceptorStatsToLog("./mochawesome-report/_interceptor", { url: "**/my-api" });
See the method you can use: cy.writeInterceptorStatsToLog
.
Clean the videos for successful tests
If you have a reporter and send the report somewhere, you may want videos only for failed tests. If so, you can do it like this in your cypress/support/e2e.ts
or cypress/support/e2e.js
:
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);
}
}
});
}
}
});
Watch The Console
Watch The Console is a helper function for logging the browser's console to a file. It provides a class which observes the web browser console output. This output is possible to log to a file.
Getting started
In your cypress/support/e2e.js
, cypress/support/e2e.ts
or in any of your test files:
import "cypress-interceptor/console";
The Cypress WatchTheConsole commands
/**
* Get an instance of the WatchTheConsole
*
* @returns An instance of the WatchTheConsole
*/
watchTheConsole: () => Chainable<WatchTheConsole>;
/**
* Set the WatchTheConsole options. This must be called before a web page is visited.
*
* @param options Options
* @returns The current WatchTheConsole options
*/
watchTheConsoleOptions: (
options?: WatchTheConsoleOptions
) => Chainable<WatchTheConsoleOptions>;
/**
* Write the logged console output to a file
*
* @example cy.writeConsoleLogToFile("./out") => the output file will be "./out/Description - It.stats.json"
* @example cy.writeConsoleLogToFile("./out", { fileName: "file_name" }) => the output file will be "./out/file_name.stats.json"
* @example cy.writeConsoleLogToFile("./out", { types: [ConsoleLogType.ConsoleError, ConsoleLogType.Error] }) => write only the
* console errors and unhandled JavaScript errors to the output file
* @example cy.writeConsoleLogToFile("./out", { filter: (type, ...args) => typeof args[0] === "string" && args[0].startsWith("Custom log:") }) =>
* filter all console output to include only entries starting with "Custom log:"
*
* @param outputDir The path for the output folder
* @param options Options
*/
writeConsoleLogToFile: (
outputDir: string,
options?: WriteLogOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>
) => Chainable<null>;
Documentation and examples
cy.watchTheConsole
watchTheConsole: () => Chainable<WatchTheConsole>;
Get an instance of the WatchTheConsole
Example
cy.watchTheConsole().then(watchTheConsole => {
expect(
watchTheConsole.log.filter((entry) => entry.type === ConsoleLogType.ConsoleError).length
).to.eq(0);
});
cy.watchTheConsoleOptions
watchTheConsoleOptions: (
options?: WatchTheConsoleOptions
) => Chainable<WatchTheConsoleOptions>;
References:
Set the WatchTheConsole options. This must be called before a web page is visited.
Example
beforeEach(() => {
/**
* My application is using `redux-logger` and provides an extended log of the Redux store. Therefore,
* it is necessary to remove circular dependencies and, most importantly, capture the object at the
* moment it is logged to prevent changes in the store over time.
*/
cy.watchTheConsoleOptions({ cloneConsoleArguments: true });
});
cy.writeConsoleLogToFile
writeConsoleLogToFile: (
outputDir: string,
options?: WriteLogOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>
) => Chainable<null>;
Write the logged console output to a file.
References:
Example
// when a test fails, log all console output to a file with formatted output
afterEach(function () {
if (this.currentTest?.state === "failed") {
cy.writeConsoleLogToFile("_console", { prettyOutput: true });
}
});
// increase the timeout for `cy.writeFile` when you expect a big output
cy.writeConsoleLogToFile("_console", {
timeout: 120000
});
// write only the console errors and unhandled JavaScript errors to the output file
cy.writeConsoleLogToFile("_console", {
types: [ConsoleLogType.ConsoleError, ConsoleLogType.Error]
});
// filter all console output to include only entries starting with "Custom log:"
cy.writeConsoleLogToFile(outputDir, {
filter: (type, ...args) => typeof args[0] === "string" && args[0].startsWith("Custom log:")
});
WatchTheConsole public methods
log
get log(): ConsoleLog[];
Return a copy of all logged console outputs.
setOptions
Same as cy.watchTheConsoleOptions
.
writeLogToFile
Same as cy.writeConsoleLogToFile
.
Interfaces
ConsoleLog
interface ConsoleLog {
/**
* The console output or the unhandled JavaScript error message and stack trace
*/
args: unknown[];
/**
* The customized date and time in the format dd/MM/yyyy, hh:mm:ss.milliseconds. (for better visual checking)
*/
currentTime: CurrentTime;
/**
* The getTime() of the Date when the console was logged (for future investigation)
*/
dateTime: DateTime;
/**
* Console Type
*/
type: ConsoleLogType;
}
ConsoleLogType
enum ConsoleLogType {
ConsoleInfo = "console.info",
ConsoleError = "console.error",
ConsoleLog = "console.log",
ConsoleWarn = "console.warn",
// this is equal to a unhandled JavaScript error
Error = "error"
}
WatchTheConsoleOptions
interface WatchTheConsoleOptions {
/**
* When the console output includes an object, it is highly recommended to set this option to `true`
* because an object can change at runtime and may not match the object that was logged at that moment.
* When set to `true`, it will deeply copy the object and remove any circular dependencies.
*/
cloneConsoleArguments?: boolean;
}
WriteLogOptions
interface WriteLogOptions {
/**
* The name of the file. If `undefined`, it will be generated from the running test.
*/
fileName?: string;
/**
* An option to filter the logged items
*
* @param type The type of the console log
* @param args The console log arguments
* @returns `false` if the item should be skipped
*/
filter?: (type: ConsoleLogType, ...args: unknown[]) => boolean;
/**
* When set to `true`, the output JSON will be formatted with tabs
*/
prettyOutput?: boolean;
/**
* "If the type is not provided, it logs all console entries
*/
types?: ConsoleLogType[];
}
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
, cypress/support/e2e.ts
or in any of your test files:
import "cypress-interceptor/websocket";
The Cypress Websocket Interceptor commands
/**
* Get an instance of the Websocket Interceptor
*
* @returns An instance of the Websocket Interceptor
*/
wsInterceptor: () => Chainable<WebsocketInterceptor>;
/**
* Get the last call matching the provided route matcher.
*
* @param matcher A matcher
* @returns The last call information or `undefined` if none matches.
*/
wsInterceptorLastRequest: (
matcher?: IWSMatcher
) => Chainable<CallStackWebsocket | undefined>;
/**
* Get the statistics for all requests matching the provided matcher since the beginning
* of the current test.
*
* @param matcher A matcher
* @returns It returns all requests matching the provided matcher with detailed information.
* If none match, it returns an empty array.
*/
wsInterceptorStats: (matcher?: IWSMatcher) => Chainable<CallStackWebsocket[]>;
/**
* Write the logged requests' information (or those filtered by the provided matcher) to a file
*
* @example cy.wsInterceptorStatsToLog("./out") => the output file will be "./out/Description - It.stats.json"
* @example cy.wsInterceptorStatsToLog("./out", { fileName: "file_name" }) => the output file will be "./out/file_name.stats.json"
* @example cy.wsInterceptorStatsToLog("./out", { matcher: { protocols: "soap" } }) => write only "soap" requests to the output file
* @example cy.wsInterceptorStatsToLog("./out", { matcher: { url: "my-url" } }) => write only requests to my-url to the output file
* @example cy.wsInterceptorStatsToLog("./out", { mapper: (entry) => ({ url: entry.url }) }) => map the output that will be written to the output file
*
* @param outputDir A path for the output directory
* @param options Options
*/
wsInterceptorStatsToLog: (
outputDir: string,
options?: WriteStatsOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>
) => Chainable<null>;
/**
* Reset the the Websocket Interceptor's watch
*/
wsResetInterceptorWatch: VoidFunction;
/**
* Wait until a websocket action occurs
*
* @param options Action options
* @param errorMessage An error message when the maximum waiting time is reached
*/
waitUntilWebsocketAction(
options?: WaitUntilActionOptions,
errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
* Wait until a websocket action occurs
*
* @param matcher A matcher
* @param errorMessage An error message when the maximum waiting time is reached
*/
waitUntilWebsocketAction(
matcher?: IWSMatcher | IWSMatcher[],
errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
/**
* Wait until a websocket action occurs
*
* @param matcher A matcher
* @param options Action options
* @param errorMessage An error message when the maximum waiting time is reached
*/
waitUntilWebsocketAction(
matcher?: IWSMatcher | IWSMatcher[],
options?: WaitUntilActionOptions,
errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
waitUntilWebsocketAction(
matcherOrOptions?: IWSMatcher | IWSMatcher[] | WaitUntilActionOptions,
errorMessageOrOptions?: string | 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 the Websocket Interceptor
Example
cy.wsInterceptor().then(interceptor => {
interceptor.resetWatch();
intereptor.writeStatsToLog("_logs", { protocols: "soap" }, "stats");
});
cy.wsInterceptorLastRequest
Get the last call matching the provided route 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.wsInterceptorStats
Get the 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.wsInterceptorStatsToLog
wsInterceptorStatsToLog: (outputDir: string,options?: WriteStatsOptions & Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>) => Chainable<null>;
References:
Write the logged requests' information (or those filtered by the provided matcher) to a file. The file will contain the JSON.stringify of callStack
.
Example
afterAll(() => {
// the output file will be "./out/test.cy.ts (Description - It).stats.json" (the name of the file `test.cy.ts (Description - It)` will be generated from the running test)
cy.wsInterceptorStatsToLog("./out");
// increase the timeout for `cy.writeFile` when you expect a big output
cy.wsInterceptorStatsToLog("./out", { timeout: 120000 });
// the output file will be "./out/file_name.stats.json"
cy.wsInterceptorStatsToLog("./out", { fileName: "file_name" });
// write only stats for a specific URL to the output file
cy.wsInterceptorStatsToLog("./out", { matcher: { url: "**/some-url" } });
// write only "onmessage" actions to the output file
cy.wsInterceptorStatsToLog("./out", { filter: (entry) => entry.type === "onmessage" });
// map the output that will be written to the output file
cy.wsInterceptorStatsToLog("./out", { mapper: (entry) => ({ type: entry.type, url: entry.url }) });
});
cy.wsResetInterceptorWatch
Reset the the Websocket Interceptor's watch
wsResetInterceptorWatch: () => void;
cy.waitUntilWebsocketAction
Wait until a websocket action occur
waitUntilWebsocketAction(
options?: WaitUntilActionOptions,
errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
waitUntilWebsocketAction(
matcher?: IWSMatcher | IWSMatcher[],
errorMessage?: string
): Cypress.Chainable<WebsocketInterceptor>;
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 with the code and reason equal to a specified value
cy.waitUntilWebsocketAction([
{
code: 1000,
reason: "some reason",
type: "close"
}
]);
Websocket Interceptor public methods
callStack
Returns a copy of all logged requests since the WebSocket Interceptor was created (the Websocket Interceptor is created in beforeEach
).
get callStack(): CallStackWebsocket[];
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
Same as cy.wsInterceptorStatsToLog
.
Interfaces
IWSMatcher
type IWSMatcher = {
/**
* A matcher for the query string (URL search params)
*
* @param query The URL qearch params as an object
* @returns `true` if matches
*/
queryMatcher?: (query: Record<string, string | number>) => boolean;
/**
* Websocket protocols
*/
protocols?: string | string[];
/**
* A URL matcher, use * or ** to match any word in string
*
* @example "**\/api/call" will match "http://any.com/api/call", "http://any.com/test/api/call", "http://any.com/test/api/call?page=99", ...
* @example "*\api\*" will match "http://any.com/api/call", "http://any.com/api/list", "http://any.com/api/call-2?page=99&filter=1",
* @example "**" will match any URL
*/
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 {
/**
* The name of the file. If `undefined`, it will be generated from the running test.
*/
fileName?: string;
/**
* An option to filter the logged items
*
* @param callStack Call information stored in the stack
* @returns `false` if the item should be skipped
*/
filter?: (callStack: CallStackWebsocket) => boolean;
/**
* An option to map the logged items
*
* @param callStack Call information stored in the stack
* @returns Any object you want to log
*/
mapper?: (callStack: CallStackWebsocket) => unknown;
/**
* A matcher
*/
matcher?: IWSMatcher;
/**
* When set to `true`, the output JSON will be formatted with tabs
*/
prettyOutput?: boolean;
}
test.unit
This is a simple helper designed to store arguments passed to lineCalled
or lineCalledWithClone
and then call them in your application for testing in Cypress. By default, the call line is not enabled — you must enable it first in your Cypress test. There's no need to worry; in the application, it does nothing and does not slow down performance.
How to use
In your application, you can call lineCalled
or lineCalledWithClone
. anywhere you need. Then, enable it in your Cypress test setup before running the test:
import { enableCallLine } from "cypress-interceptor/test.unit";
beforeEach(() => {
cy.window().then((win) => {
enableCallLine(win);
});
});
Globally in your cypress/support/e2e.js
or cypress/support/e2e.ts
:
Cypress.on("window:before:load", (win) => {
enableCallLine(win);
});
And in your tests you will be able to use it as follows:
import "cypress-interceptor/test.unit.commands";
// check that call line is really enabled
cy.callLine().its('isEnabled').should('be.true');
cy.callLineLength().should("eq", 0);
// OR cy.callLine().invoke("length").should("eq", 0);
// do some action which should call `lineCalled` or `lineCalledWithClone`
cy.contains("button", "Add").click();
cy.callLineNext().should("eq", "Some value");
// OR cy.callLineNext().should("deep.eq", ["some Value", { } ]);
// OR cy.callLine().then(callLine => expect(callLine.next).to.eq("something"));
The test.unit
Cypress commands
/**
* Get a created instance of the CallLine class
*
* @returns An instance of the CallLine class
*/
callLine(): Chainable<CallLine>;
/**
* Clean the CallLine array and start storing the values from the beginning
*/
callLineClean(): void;
/**
* The last existing entry. It can be `undefined` if there is no entry at
* the moment or `next` has not been called. Otherwise it always returns
* the last entry invoked by `next`.
*/
callLineCurrent(): Chainable<unknown | unknown[] | undefined>;
/**
* Get the number of all entries.
*/
callLineLength(): Chainable<number>;
/**
* Get the next entry. If there is no next entry, it returns undefined.
*
* If the entry was added as a single argument like `lineCalled("something")`,
* it will return the single value "something". But if it was added as multiple
* arguments like `lineCalled("something", 1, true)`, it will return an array
* `["something", 1, true]`.
*/
callLineNext(): Chainable<unknown | unknown[] | undefined>;
/**
* Resets the counter and starts from the first entry on the next call to `cy.callLineNext`
*/
callLineReset(): void;
cy.callLine
callLine(): Chainable<CallLine>;
Get a created instance of the CallLine class
Example
// check that call line is really enabled
cy.callLine().its('isEnabled').should('be.true');
cy.callLineClean
callLineClean(): void;
Clean the CallLine array and start storing the values from the beginning
cy.callLineCurrent
callLineCurrent(): Chainable<unknown | unknown[] | undefined>;
The last existing entry. It can be undefined
if there is no entry at the moment or next
has not been called. Otherwise it always returns the last entry invoked by next
.
Example
// wait for the next entry
cy.callLineNext().should("not.be.undefined");
// do some checking
cy.callLineCurrent().should("eq", "my custom string");
// do more checkings with the last entry
cy.callLineCurrent().should("eq", ...);
cy.callLineLength
callLineLength(): Chainable<number>;
Get the number of all entries.
Example
// uses a Cypress query to check the total number of entries
cy.callLineLength().should("eq", 1);
cy.callLineNext
callLineNext(): Chainable<unknown | unknown[] | undefined>;
Get the next entry. If there is no next entry, it returns undefined. It is a Cypress query.
If the entry was added as a single argument like lineCalled("something")
, it will return the single value "something". But if it was added as multiple arguments like lineCalled("something", 1, true)
, it will return an array ["something", 1, true]
.
Example
// uses a Cypress query to check if the next entry (different from undefined) is `123`
// in combination with `should` it tries to call `callLine.next` until it is not undefined
cy.callLineNext().should("eq", 123);
cy.callLineReset
callLineReset(): void;
Resets the counter and starts from the first entry on the next call to cy.callLineNext
Documentation and examples of cypress-interceptor/test.unit
enableCallLine
/**
* @param childWindow A window instance. In Cypress, it must be the window of `cy.window()`
*/
enableCallLine(childWindow?: CallLineWindowType): void;
Enable the call line in the window object.
Example
// to enable it in the current run only outside the web browser
enableCallLine();
// to enable it in the current run and the web browser
cy.window().then((win) => {
enableCallLine(win);
});
Cypress.on("window:before:load", (win) => {
enableCallLine(win);
});
isCallLineEnabled
/**
* @returns True if the call line is enabled
*/
isCallLineEnabled(): boolean;
Check if the call line is enabled.
getCallLine
/**
* @returns An instance of the CallLine class
*/
getCallLine(): CallLine;
References:
Get a created instance of the CallLine class.
lineCalled
/**
* @param args Anything that you want to store
*/
lineCalled(...args: unknown[]): void;
This is the main function that should be in your application to store any information you need.
For storing information about the line that was called. If there are more arguments, it will be saved as an array, otherwise it will be stored as a single value.
lineCalledWithClone
lineCalledWithClone(...args: unknown[]): void;
Similar to lineCalled
, but with cloned arguments. This prevents any changes to object references in the arguments over time.
Interfaces
CallLine
interface CallLine {
/**
* Get an instance of the window or an empty array if is not enabled
*/
array: unknown[];
/**
* The last existing entry. It can be `undefined` if there is no entry at
* the moment or if `next` has not been called. Otherwise, it always returns
* the last entry invoked by `next`.
*/
current: unknown | unknown[] | undefined;
/**
* True if CallLine feature is globally enabled
*/
isEnabled: boolean;
/**
* Get the number of all entries.
*/
length: number;
/**
* Get the next entry. If there is no next entry, it returns `undefined`.
*
* If the entry was added as a single argument like `lineCalled("something")`,
* it will return the single value "something". But if it was added as multiple
* arguments like `lineCalled("something", 1, true)`, it will return an array
* `["something", 1, true]`.
*/
next: unknown | unknown[] | undefined;
/**
* Clean the CallLine array and start storing the values from the beginning
*/
clean: () => void;
/**
* Resets the counter and starts from the first entry on the next call to `next`
*/
reset: () => void;
}
4 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago