Efficient Cypress API Testing with Custom Commands

When working on projects that involve extensive API testing, managing API requests in your Cypress tests can quickly become overwhelming. Writing repetitive cy.request() calls directly in test files not only makes them cluttered but also difficult to maintain. To address this, I’ve adopted a structured approach to organize and simplify API requests using custom commands in Cypress. Here, I’ll walk you through my solution step by step, which you can implement to streamline your API tests. Step 1: A General Function for All Requests The first step is to create a generic function to handle any type of API request. This function accepts parameters such as the HTTP method, URL, query parameters, request body, bearer token, and device headers. Here’s the implementation: function callRequest(method, url, params, body, bearer, device) { var headers = { uuid: Cypress.uuid // Unique identifier for tracking requests } if (bearer) { headers.authentication = 'Bearer ' + bearer; } if (device) { headers['Origin-Type'] = device; } return cy.request({ method: method, url: url, qs: params, body: body, headers: headers, failOnStatusCode: false, }).then((response) => { if (response.status === 500) { cy.task('log', `[\${new Date().toISOString()}] Request URL: \${url}, Parameters: \${JSON.stringify(params)}, Response: \${JSON.stringify(response.body)}`); } return cy.wrap(response); }); } This function is a flexible foundation for all types of requests and ensures consistent handling of headers, query parameters, and error logging. Step 2: Specific Functions for HTTP Methods Since different HTTP methods often require varying parameters (e.g., GET typically doesn’t need a request body), I created separate functions for each method. For example: function getRequest(url, params = { countryId: Cypress.env().countryId }, bearer) { return callRequest('GET', url, params, undefined, bearer); } function postRequest(url, params = { countryId: Cypress.env().countryId }, body, bearer) { return callRequest('POST', url, params, body, bearer); } This abstraction makes the code cleaner and avoids repeatedly specifying HTTP method details in every API call. Step 3: Custom Cypress Commands To further simplify API usage in tests, I created custom Cypress commands. Each command defines the specific endpoint URL and invokes the appropriate HTTP method function. Here are a couple of examples: Cypress.Commands.add('getTranslation', (params) => { const reqUrl = '/_api/v1/translations/translate/'; return getRequest(reqUrl, params); }); Cypress.Commands.add('setUserData', (params, body, bearer) => { const reqUrl = '/api/v1/user/signup-info-data/'; return postRequest(reqUrl, undefined, body, bearer); }); These commands make it incredibly easy to add new API endpoints as needed. You’ll only need to define the endpoint URL and choose the appropriate HTTP method function. I have created a separate file only for API custom commands. To be able to use it, I had to add import './commands-api' to the e2e.js file in the support folder. Step 4: Using Custom Commands in Tests With everything set up, using API requests in your tests becomes effortless. For example: cy.getTranslation().then(response => { expect(response.status).to.eq(200); expect(response.body).to.have.property('translations'); }); Benefits of This Approach Maintainability: Changes to API request logic (e.g., headers, logging) can be made in a single location. Usability: Reuse the same custom commands across multiple tests. Readability: Test files remain concise and focused on assertions. Scalability: Easily add new endpoints or HTTP methods without duplicating code. Conclusion By organizing API requests into a general function, HTTP method-specific functions, and custom commands, you can significantly improve the maintainability and scalability of your Cypress API tests. This approach reduces code duplication and makes it easy to handle even complex testing scenarios. Feel free to implement and adapt this structure in your projects. Let me know your thoughts or share your own approaches to organizing API requests in Cypress!

Jan 19, 2025 - 22:18
Efficient Cypress API Testing with Custom Commands

When working on projects that involve extensive API testing, managing API requests in your Cypress tests can quickly become overwhelming. Writing repetitive cy.request() calls directly in test files not only makes them cluttered but also difficult to maintain. To address this, I’ve adopted a structured approach to organize and simplify API requests using custom commands in Cypress. Here, I’ll walk you through my solution step by step, which you can implement to streamline your API tests.

Step 1: A General Function for All Requests

The first step is to create a generic function to handle any type of API request. This function accepts parameters such as the HTTP method, URL, query parameters, request body, bearer token, and device headers. Here’s the implementation:

function callRequest(method, url, params, body, bearer, device) {
    var headers = {
        uuid: Cypress.uuid // Unique identifier for tracking requests
    }
    if (bearer) {
        headers.authentication = 'Bearer ' + bearer;
    }
    if (device) {
        headers['Origin-Type'] = device;
    }
    return cy.request({
        method: method,
        url: url,
        qs: params,
        body: body,
        headers: headers,
        failOnStatusCode: false,
    }).then((response) => {
        if (response.status === 500) {
            cy.task('log', `[\${new Date().toISOString()}] Request URL: \${url}, Parameters: \${JSON.stringify(params)}, Response: \${JSON.stringify(response.body)}`);
        }
        return cy.wrap(response);
    });
}

This function is a flexible foundation for all types of requests and ensures consistent handling of headers, query parameters, and error logging.

Step 2: Specific Functions for HTTP Methods

Since different HTTP methods often require varying parameters (e.g., GET typically doesn’t need a request body), I created separate functions for each method. For example:

function getRequest(url, params = { countryId: Cypress.env().countryId }, bearer) {
    return callRequest('GET', url, params, undefined, bearer);
}

function postRequest(url, params = { countryId: Cypress.env().countryId }, body, bearer) {
    return callRequest('POST', url, params, body, bearer);
}

This abstraction makes the code cleaner and avoids repeatedly specifying HTTP method details in every API call.

Step 3: Custom Cypress Commands

To further simplify API usage in tests, I created custom Cypress commands. Each command defines the specific endpoint URL and invokes the appropriate HTTP method function. Here are a couple of examples:

Cypress.Commands.add('getTranslation', (params) => {
    const reqUrl = '/_api/v1/translations/translate/';
    return getRequest(reqUrl, params);
});

Cypress.Commands.add('setUserData', (params, body, bearer) => {
    const reqUrl = '/api/v1/user/signup-info-data/';
    return postRequest(reqUrl, undefined, body, bearer);
});

These commands make it incredibly easy to add new API endpoints as needed. You’ll only need to define the endpoint URL and choose the appropriate HTTP method function.
I have created a separate file only for API custom commands. To be able to use it, I had to add import './commands-api' to the e2e.js file in the support folder.

Step 4: Using Custom Commands in Tests

With everything set up, using API requests in your tests becomes effortless. For example:

cy.getTranslation().then(response => {
    expect(response.status).to.eq(200);
    expect(response.body).to.have.property('translations');
});

Benefits of This Approach

  1. Maintainability: Changes to API request logic (e.g., headers, logging) can be made in a single location.
  2. Usability: Reuse the same custom commands across multiple tests.
  3. Readability: Test files remain concise and focused on assertions.
  4. Scalability: Easily add new endpoints or HTTP methods without duplicating code.

Conclusion

By organizing API requests into a general function, HTTP method-specific functions, and custom commands, you can significantly improve the maintainability and scalability of your Cypress API tests. This approach reduces code duplication and makes it easy to handle even complex testing scenarios.

Feel free to implement and adapt this structure in your projects. Let me know your thoughts or share your own approaches to organizing API requests in Cypress!