From a1c9d494e68ef4f2d29657790cd41d73c0aef406 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Tue, 19 Aug 2025 20:36:15 +0530 Subject: [PATCH 1/4] Add response handler callback to interceptApi command --- cypress/support/commands/api_commands.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cypress/support/commands/api_commands.js b/cypress/support/commands/api_commands.js index 2e78dc79984..16c8dcb0f3e 100644 --- a/cypress/support/commands/api_commands.js +++ b/cypress/support/commands/api_commands.js @@ -12,10 +12,21 @@ * @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 {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); } } */ Cypress.Commands.add( 'interceptApi', - ({ alias, method = 'POST', urlPattern, triggerFn }) => { + ({ + alias, + method = 'POST', + urlPattern, + triggerFn, + onApiResponse = () => { + /* default implementation */ + }, + }) => { /* ===== TODO: Remove this block once interceptApi command becomes stable ===== */ const envVars = Cypress.env(); cy.log('Cypress Environment Variables:'); @@ -39,6 +50,8 @@ Cypress.Commands.add( triggerFn(); // Wait for the intercepted request to complete - cy.wait(`@${alias}`); + cy.wait(`@${alias}`).then((interception) => { + onApiResponse(interception); + }); } ); From 96138395f9abdc9c7a3b64ab2e244397de395690 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Tue, 19 Aug 2025 21:36:20 +0530 Subject: [PATCH 2/4] Add setter, getter & reset utils for intercepted aliases --- .../ui/validate-intercept-api-command.cy.js | 28 ++++--- cypress/support/commands/api_commands.js | 82 +++++++++++++++---- cypress/support/e2e.js | 2 +- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/cypress/e2e/ui/validate-intercept-api-command.cy.js b/cypress/e2e/ui/validate-intercept-api-command.cy.js index efa31475031..df6656bdcae 100644 --- a/cypress/e2e/ui/validate-intercept-api-command.cy.js +++ b/cypress/e2e/ui/validate-intercept-api-command.cy.js @@ -16,9 +16,9 @@ describe('Validate intercept command', () => { triggerFn: () => cy.selectAccordionItem([/^ManageIQ Region:/, /^Zone:/]), }).then(() => { // verifies that the alias is set and the request is intercepted & awaited - expect(Cypress.env('interceptedAliases')).to.have.property( - 'treeSelectApi' - ); + cy.getInterceptedApiAliases().then((interceptedAliasesObject) => { + expect(interceptedAliasesObject).to.have.property('treeSelectApi'); + }); }); }); @@ -36,10 +36,12 @@ describe('Validate intercept command', () => { triggerFn: () => cy.selectAccordionItem([/^ManageIQ Region:/, /^Zone:/]), }).then(() => { // verifies that both the aliases are set and the request is intercepted & awaited - expect(Cypress.env('interceptedAliases')).to.include.all.keys( - 'accordionSelectApi', - 'treeSelectApi' - ); + cy.getInterceptedApiAliases().then((interceptedAliasesObject) => { + expect(interceptedAliasesObject).to.include.all.keys( + 'accordionSelectApi', + 'treeSelectApi' + ); + }); }); }); @@ -50,7 +52,9 @@ describe('Validate intercept command', () => { urlPattern: /\/ops\/accordion_select\?id=.*/, triggerFn: () => cy.accordion('Diagnostics'), }).then(() => { - expect(Object.keys(Cypress.env('interceptedAliases')).length).to.equal(1); + cy.getInterceptedApiAliases().then((interceptedAliasesObject) => { + expect(Object.keys(interceptedAliasesObject).length).to.equal(1); + }); }); // second first api with alias 'treeSelectApi' cy.interceptApi({ @@ -58,7 +62,9 @@ describe('Validate intercept command', () => { urlPattern: /\/ops\/tree_select\?id=.*&text=.*/, triggerFn: () => cy.selectAccordionItem([/^ManageIQ Region:/, /^Zone:/]), }).then(() => { - expect(Object.keys(Cypress.env('interceptedAliases')).length).to.equal(2); + cy.getInterceptedApiAliases().then((interceptedAliasesObject) => { + expect(Object.keys(interceptedAliasesObject).length).to.equal(2); + }); }); // third api with a duplicate alias as above 'accordionSelectApi' cy.interceptApi({ @@ -67,7 +73,9 @@ describe('Validate intercept command', () => { triggerFn: () => cy.accordion('Access Control'), }).then(() => { // assert that the alias is not overwritten - expect(Object.keys(Cypress.env('interceptedAliases')).length).to.equal(2); + cy.getInterceptedApiAliases().then((interceptedAliasesObject) => { + expect(Object.keys(interceptedAliasesObject).length).to.equal(2); + }); }); }); }); diff --git a/cypress/support/commands/api_commands.js b/cypress/support/commands/api_commands.js index 16c8dcb0f3e..1ed72f89160 100644 --- a/cypress/support/commands/api_commands.js +++ b/cypress/support/commands/api_commands.js @@ -1,5 +1,58 @@ /* eslint-disable no-undef */ +/** + * Custom command to get the intercepted API aliases stored in Cypress environment variables. + * This command returns the object containing all registered API interception aliases. + * + * @returns {Object} An object where keys are the alias names and values are typically the same alias names + * @example + * cy.getInterceptedApiAliases().then((aliases) => { + * Check if a specific alias exists + * expect(aliases).to.have.property('myApiAlias'); + * + * Get the number of registered aliases + * const aliasCount = Object.keys(aliases).length; + * }); + */ +Cypress.Commands.add('getInterceptedApiAliases', () => + Cypress.env('interceptedAliases') +); + +/** + * Custom command to set an intercepted API alias in the Cypress environment variables. + * This command adds an alias in the intercepted aliases tracking object. + * + * @param {string} aliasKey - The key/name of the alias to set + * @param {string} [aliasValue=aliasKey] - The value to store for the alias (defaults to the same as the key) + * @example + * Set a new alias + * cy.setInterceptedApiAlias('getUsersApi'); + * + * Set an alias with a custom value + * cy.setInterceptedApiAlias('getUsersApi', 'customValue'); + */ +Cypress.Commands.add( + 'setInterceptedApiAlias', + (aliasKey, aliasValue = aliasKey) => { + cy.getInterceptedApiAliases().then((interceptedAliasesMap) => { + interceptedAliasesMap[aliasKey] = aliasValue; + Cypress.env('interceptedAliases', interceptedAliasesMap); + }); + } +); + +/** + * Custom command to reset all intercepted API aliases stored in Cypress environment variables. + * This command clears the tracking object by setting it to an empty object. + * Useful for cleaning up between tests or test suites. + * @example + * Reset all intercepted API aliases + * cy.resetInterceptedApiAliases(); + */ +Cypress.Commands.add('resetInterceptedApiAliases', () => + Cypress.env('interceptedAliases', {}) +); + /** * Custom command to intercept API calls and wait for them to complete. * This command will: @@ -34,24 +87,23 @@ Cypress.Commands.add( /* ======================================================= */ // Check if this request is already registered - const isAlreadyRegistered = !!Cypress.env('interceptedAliases')[alias]; - - // Register the intercept if not already done - if (!isAlreadyRegistered) { - cy.intercept(method, urlPattern).as(alias); + cy.getInterceptedApiAliases().then((interceptedAliasesMap) => { + // Check if this request is already registered + const isAlreadyRegistered = !!interceptedAliasesMap[alias]; - // Store the alias in the tracking object - const interceptedAliases = Cypress.env('interceptedAliases'); - interceptedAliases[alias] = alias; - Cypress.env('interceptedAliases', interceptedAliases); - } + // Register the intercept if not already done + if (!isAlreadyRegistered) { + cy.intercept(method, urlPattern).as(alias); + cy.setInterceptedApiAlias(alias); + } - // Execute the function that triggers the API call - triggerFn(); + // Execute the function that triggers the API call + triggerFn(); - // Wait for the intercepted request to complete - cy.wait(`@${alias}`).then((interception) => { - onApiResponse(interception); + // Wait for the intercepted request to complete + cy.wait(`@${alias}`).then((interception) => { + onApiResponse(interception); + }); }); } ); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index f243f145e77..1fec6ef1477 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -79,5 +79,5 @@ beforeEach(() => { // cy.stub_notifications(); // Reset the intercepted aliases tracking object - Cypress.env('interceptedAliases', {}); + cy.resetInterceptedApiAliases(); }) From 85ee9c4293f333b89fcddf18362d3609575a27cf Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Tue, 19 Aug 2025 21:39:35 +0530 Subject: [PATCH 3/4] Update readme with api_commands details --- cypress/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cypress/README.md b/cypress/README.md index e71c506074e..2246e8bc63f 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -57,6 +57,13 @@ ManageIQ implements the following cypress extensions: * `cy.toolbarItems(toolbarButton)` - returns an array of objects {text: String, disabled: Boolean} for the toolbar dropdown buttons for when a toolbar button is clicked. `toolbarButton` is the string for the text of the toolbar button that you want to click on. * `cy.toolbar(toolbarButton, toolbarOption)` - click on the toolbar button specified by the user. Can also then click on a specified dropdown option as well. `toolbarButton` is the string for the text of the toolbar button that you want to click on. `toolbarOption` is the string for the text of the toolbar dropdown option that you want to click on. +##### 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 for the given alias and 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.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. + ##### custom_logging_commands * `cy.logAndThrowError(messageToLog, messageToThrow)` - Logs a custom error message to Cypress log and then throws an error. `messageToLog` is the message to display in the Cypress command log. `messageToThrow` is the optional error message to throw, defaults to `messageToLog`. e.g. `cy.logAndThrowError('This is the logged message', 'This is the thrown error message');`, `cy.logAndThrowError('This is the message that gets logged and thrown');` From a7b4733b8e204a07fb5f53c91c88155470541cb4 Mon Sep 17 00:00:00 2001 From: asirvadAbrahamVarghese Date: Tue, 19 Aug 2025 21:55:24 +0530 Subject: [PATCH 4/4] Updated test to validate response handler in interceptApi command --- cypress/e2e/ui/validate-intercept-api-command.cy.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/ui/validate-intercept-api-command.cy.js b/cypress/e2e/ui/validate-intercept-api-command.cy.js index df6656bdcae..5e62ede5287 100644 --- a/cypress/e2e/ui/validate-intercept-api-command.cy.js +++ b/cypress/e2e/ui/validate-intercept-api-command.cy.js @@ -8,12 +8,15 @@ describe('Validate intercept command', () => { cy.menu('Settings', 'Application Settings'); }); - it('Should register the alias, intercept & wait when API fired', () => { + it('Should register the alias, intercept, wait & validate response status code when an API is fired', () => { cy.accordion('Diagnostics'); cy.interceptApi({ alias: 'treeSelectApi', urlPattern: /\/ops\/tree_select\?id=.*&text=.*/, triggerFn: () => cy.selectAccordionItem([/^ManageIQ Region:/, /^Zone:/]), + onApiResponse: (interception) => { + expect(interception.response.statusCode).to.equal(200); + }, }).then(() => { // verifies that the alias is set and the request is intercepted & awaited cy.getInterceptedApiAliases().then((interceptedAliasesObject) => {