Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ ManageIQ implements the following cypress extensions:

##### api_commands

* `cy.interceptApi({ alias, method = 'POST', urlPattern, triggerFn, onApiResponse })` - intercepts API calls and waits for them to complete. This command will: 1) Register an intercept(in method-alias format e.g. post-myApiAlias) for the given alias & URL pattern if not already registered, 2) Execute the trigger function that makes the API call, 3) Wait for the intercepted request to complete. `alias` is the string for a unique alias for this interception. `method` is the string for the HTTP method (default: 'POST'). `urlPattern` is the string or RegExp for the URL pattern to intercept. `triggerFn` is the function that triggers the API call. `onApiResponse` is an optional callback function that receives the interception object after the API call completes. Use this to perform assertions on the response, extract data, or perform additional actions based on the API result. Default is a no-op function. e.g. `cy.interceptApi({ alias: 'getUsers', method: 'GET', urlPattern: '/api/users', triggerFn: () => cy.get('#load-users').click(), onApiResponse: (interception) => { expect(interception.response.statusCode).to.equal(200); } });`
* `cy.interceptApi({ alias, method = 'POST', urlPattern, waitOnlyIfRequestIntercepted, responseInterceptor, triggerFn, onApiResponse })` - intercepts API calls and waits for them to complete. This command will: 1) Register an intercept(in method-alias format e.g. post-myApiAlias) for the given alias & URL pattern if not already registered, 2) Execute the trigger function that makes the API call, 3) Wait for the intercepted request to complete. `alias` is the string for a unique alias for this interception. `method` is the string for the HTTP method (default: 'POST'). `urlPattern` is the string or RegExp for the URL pattern to intercept. `waitOnlyIfRequestIntercepted` is a boolean that when set to true, the command will only wait for the response if the request was actually intercepted (useful for conditional API calls - default: false). `responseInterceptor` is an optional function that can modify the response before it's returned to the application, with options to stub responses (`req.reply()`), let requests go to origin (`req.continue()`), or modify origin responses (`req.continue((res) => res.send())`). e.g. `{ responseInterceptor: (req) => req.reply({ body: { customData: 'value' } }) }`, `{ responseInterceptor: (req) => req.reply({ fixture: 'users.json' }) }`, `{ responseInterceptor: (req) => req.continue((res) => { res.send(200, { modified: true }) }) }`, `triggerFn` is the function that triggers the API call. e.g. `{ triggerFn: () => { cy.get('button').click(); } }`. `onApiResponse` is an optional callback function that receives the interception object after the API call completes. Use this to perform assertions on the response, extract data, or perform additional actions based on the API result. Default is a no-op function. e.g. `{ onApiResponse: (interception) => { expect(interception.response.statusCode).to.equal(200); } }`. Usage example: `cy.interceptApi({ alias: 'getUsers', method: 'GET', urlPattern: '/api/users', triggerFn: () => cy.get('#load-users').click(), responseInterceptor: (req) => req.reply({ body: { name: "stubbed value" } }), onApiResponse: (interception) => { expect(interception.response.statusCode).to.equal(200); } });`
* `cy.getInterceptedApiAliases()` - returns the intercepted API aliases stored in Cypress environment variables.
* `cy.setInterceptedApiAlias(aliasKey, aliasValue)` - sets an intercepted API alias in the Cypress environment variables. `aliasKey` is the string for the key/name of the alias to set. `aliasValue` is an optional string for the value to store for the alias (defaults to the same as the key). e.g. `cy.setInterceptedApiAlias('getUsersApi');`, `cy.setInterceptedApiAlias('getUsersApi', 'customValue');`
* `cy.resetInterceptedApiAliases()` - resets the intercepted API aliases stored in Cypress environment variables.
Expand Down
49 changes: 40 additions & 9 deletions cypress/support/commands/api_commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ const setRequestIntercepted = (value) =>
/**
* Gets the current value of the request interception flag from Cypress environment.
* This flag indicates whether a request matching an intercept pattern was detected.
* @returns {boolean} The current value of the request interception flag
* @returns {boolean} The current value of the request interception flag, by default returns false
*/
const getRequestIntercepted = () => Cypress.env('wasRequestIntercepted');
const getRequestIntercepted = () =>
Cypress.env('wasRequestIntercepted') || false;

/**
* Custom command to intercept API calls and wait for them to complete.
Expand All @@ -84,9 +85,22 @@ const getRequestIntercepted = () => Cypress.env('wasRequestIntercepted');
*
* @param {Object} options - The options for the intercept
* @param {string} options.alias - Unique alias for this interception
* @param {string} options.method - HTTP method (default: 'POST')
* @param {string} [options.method] - HTTP method (default: 'POST')
* @param {string|RegExp} options.urlPattern - URL pattern to intercept
* @param {Function} options.triggerFn - Function that triggers the API call
* @param {boolean} [options.waitOnlyIfRequestIntercepted] - When set to true(default: false), the command will only wait for the response
* if the request was actually intercepted. This is useful for conditional API calls that may or may not happen like in tree navigations.
* If false (default), the command will always wait for the intercepted request, where a request is always expected (e.g., button events).
* @param {Function} options.triggerFn - Function that triggers the API call. e.g. { triggerFn: () => { cy.get('button').click(); } }
* @param {Function} [options.responseInterceptor] - Optional function that can modify the response before it's returned to the application.
* This function receives the request object and can handle the response in different ways:
* 1. req.reply({body: {...}}) - Immediately respond with a stubbed response (request never goes to origin)
* 2. req.continue() - Let the request go to the origin server without modification
* 3. req.continue((res) => { res.send({...}) }) - Let the request go to origin, then modify the response
* Examples:
* - Stub response: { responseInterceptor: (req) => req.reply({ body: { customData: 'value' } }) }
* - Using fixture to stub response: { responseInterceptor: (req) => req.reply({ fixture: 'users.json' }) }
* - Pass through to origin: { responseInterceptor: (req) => req.continue() }
* - Modify origin response: { responseInterceptor: (req) => req.continue((res) => { res.send(200, { modified: true }) }) }
* @param {Function} [options.onApiResponse] - Optional callback function that receives the interception object after the API call completes.
* Use this to perform assertions on the response, extract data, or perform additional actions based on the API result.
* Default is a no-op function. e.g. { onApiResponse: (interception) => { expect(interception.response.statusCode).to.equal(200); } }
Expand All @@ -97,10 +111,14 @@ Cypress.Commands.add(
alias,
method = 'POST',
urlPattern,
waitOnlyIfRequestIntercepted = false,
triggerFn,
onApiResponse = () => {
/* default implementation */
},
responseInterceptor = () => {
/* default implementation */
},
}) => {
/* ===== TODO: Remove this block once interceptApi command becomes stable ===== */
const envVars = Cypress.env();
Expand All @@ -114,12 +132,17 @@ Cypress.Commands.add(
// Check if this request is already registered
const isAlreadyRegistered = !!interceptedAliasesMap[aliasObjectKey];
// Setting wasRequestIntercepted flag to false initially
setRequestIntercepted(false);
if (waitOnlyIfRequestIntercepted) {
setRequestIntercepted(false);
}
// Register the intercept if not already done
if (!isAlreadyRegistered) {
cy.intercept(method, urlPattern, () => {
cy.intercept(method, urlPattern, (req) => {
// Setting wasRequestIntercepted flag to true after request is intercepted
setRequestIntercepted(true);
if (waitOnlyIfRequestIntercepted) {
setRequestIntercepted(true);
}
responseInterceptor(req);
}).as(alias);
cy.setInterceptedApiAlias(aliasObjectKey, alias);
}
Expand All @@ -129,8 +152,16 @@ Cypress.Commands.add(

// Wait for the intercepted request to complete
cy.then(() => {
const isRequestIntercepted = getRequestIntercepted();
if (isRequestIntercepted) {
// If waitOnlyIfRequestIntercepted is true, check if the request was intercepted
// and then wait for the response
if (waitOnlyIfRequestIntercepted) {
const isRequestIntercepted = getRequestIntercepted();
if (isRequestIntercepted) {
cy.wait(`@${alias}`).then(onApiResponse);
}
}
// If waitOnlyIfRequestIntercepted is not required then directly wait for the response
else {
cy.wait(`@${alias}`).then(onApiResponse);
}
});
Expand Down
2 changes: 2 additions & 0 deletions cypress/support/commands/explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Cypress.Commands.add('accordion', (title) => {
alias: 'accordionSelectApi',
urlPattern: /\/[^\/]+\/accordion_select\?id=.*/,
triggerFn: () => cy.wrap(el).click(),
waitOnlyIfRequestIntercepted: true,
});
}
});
Expand Down Expand Up @@ -83,6 +84,7 @@ Cypress.Commands.add('selectAccordionItem', (accordionPath) => {
alias: 'treeSelectApi',
urlPattern: /\/[^\/]+\/tree_select\?id=.*&text=.*/,
triggerFn: () => cy.wrap(currentLiElement).click(),
waitOnlyIfRequestIntercepted: true,
});
return;
}
Expand Down