diff --git a/.browserslistrc b/.browserslistrc index 16cce2fc9..1870737e0 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -6,17 +6,5 @@ last 2 versions > 0.5% Firefox ESR not dead -not and_qq 14.9 -not and_uc 15.5 -not android 142 -not kaios 2.5 -not kaios 3.0-3.1 -not op_mini all -not op_mob 80 -not opera 122 -not opera 121 -not samsung 28 -not samsung 29 -not samsung 27 # For IE 9-11 support, please uncomment the next line and adjust as needed # IE 9-11 diff --git a/.gitignore b/.gitignore index 722cb2f67..11ea0aabb 100644 --- a/.gitignore +++ b/.gitignore @@ -55,7 +55,11 @@ testem.log .DS_Store Thumbs.db .vscode/ -cypress/ + +# Cypress - only ignore downloads, screenshots, and videos +cypress/downloads/ +cypress/screenshots/ +cypress/videos/ src/assets/env.js src/environments/.env.ts diff --git a/cypress.config.ts b/cypress.config.ts index 2a8789f75..bcdd870b9 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -11,7 +11,8 @@ export default defineConfig({ setupNodeEvents(on, config) { return require('./cypress/plugins/index.ts')(on, config); }, - baseUrl: 'http://localhost:4200' + baseUrl: 'http://localhost:4200', + experimentalStudio: true // enable Cypress Studio }, component: { diff --git a/cypress/e2e/audit-trails.cy.ts b/cypress/e2e/audit-trails.cy.ts new file mode 100644 index 000000000..4d40cebcb --- /dev/null +++ b/cypress/e2e/audit-trails.cy.ts @@ -0,0 +1,169 @@ +describe('Audit Trails', () => { + // Test data constants for easy maintenance + const TEST_USER = { + username: 'mifos', + password: 'password' + }; + + const BASE_URL = 'http://localhost:4200'; + + beforeEach(() => { + // Start each test from a clean state + cy.visit(BASE_URL); + }); + + it('should login, search for system, navigate to audit trails, and download CSV without errors', () => { + // Step 1: Login to the application + loginToApplication(TEST_USER.username, TEST_USER.password); + + // Step 2: Handle the warning dialog if it appears + handleWarningDialog(); + + // Step 3: Use global search to find "system" + searchInGlobalSearchBar('system'); + + // Step 4: Click directly on Audit Trails button (no need to click System menu first!) + clickAuditTrailsButton(); + + // Step 5: Download CSV from audit trails page + downloadCSV(); + + // Step 6: Verify no errors occurred (Regression Check) + verifyNoErrorToast(); + }); + + // Helper Functions for better readability and reusability + + /** + * Logs into the application with provided credentials + */ + function loginToApplication(username: string, password: string) { + // Find username input - using multiple selectors for stability + cy.get('input[placeholder="Username"], input[formcontrolname="username"], input[type="text"]') + .first() + .should('be.visible') + .clear() + .type(username); + + // Find password input + cy.get('input[placeholder="Password"], input[type="password"]').should('be.visible').clear().type(password); + + // Click the login button using text content (most stable) + cy.contains('button', 'Login').click({ force: true }); + + // Wait for login to complete + cy.url().should('not.include', '/login'); + } + + /** + * Handles the warning/info dialog that appears after login + */ + function handleWarningDialog() { + // Wait for dialog to appear and close it + cy.get('mat-dialog-container', { timeout: 10000 }) + .should('be.visible') + .within(() => { + cy.contains('button', /close|ok|continue/i).click({ force: true }); + }); + + // Wait for dialog to close + cy.wait(1000); + } + + /** + * Uses the global search bar to search for content + * Based on the original recording: mat-mdc-text-field-wrapper + */ + function searchInGlobalSearchBar(searchTerm: string) { + // Find the search field using the class you provided + cy.get('.mat-mdc-text-field-wrapper input, .mdc-text-field input') + .first() + .should('be.visible') + .clear() + .type(searchTerm); + + // Wait for search results/autocomplete to appear + cy.wait(1000); + + // Select the matching option from the dropdown + cy.get('mat-option').should('be.visible'); + cy.contains('mat-option', searchTerm, { matchCase: false }).click(); + } + + /** + * Clicks on the Audit Trails button that appears after searching for "system" + * After the search, a menu opens with various options including Audit Trails + */ + function clickAuditTrailsButton() { + // After searching for "system", a menu opens + // We need to click the Audit Trails option directly from that menu + // Class: mat-mdc-list-item mdc-list-item mat-mdc-list-item-interactive + cy.get('.mat-mdc-list-item.mdc-list-item') + .contains(/audit.*trail/i) + .should('be.visible') + .click({ force: true }); + + // Wait for audit trails page to load + cy.url().should('include', 'audit'); + cy.wait(2000); + } + + /** + * Downloads the CSV file from the audit trails page + * Based on original: looks for download button + */ + function downloadCSV() { + // Wait for the audit trails page to load completely + cy.wait(2000); // Ideally use cy.intercept, but wait is okay for now + + // strategy : Try the Icon first (Most reliable based on previous errors), then fallback to text + cy.get('body').then(($body) => { + // Check for the specific Excel Icon we found earlier + if ($body.find('.fa-file-excel-o').length > 0) { + cy.get('.fa-file-excel-o').parent().click({ force: true }); + cy.log('Clicked download button using Icon class'); + } + // fallback: Check for ARIA labels or Text + else if ($body.find('button[aria-label*="ownload"]').length > 0) { + cy.get('button[aria-label*="ownload"]').click({ force: true }); + cy.log('Clicked download button using Aria Label'); + } else { + // Last resort: Text + cy.contains('button', /csv|download|export/i).click({ force: true }); + } + }); + + // Wait for download to process + cy.wait(2000); + } + + /** + * regression check : Verifies that no error toast appears + * This ensures the download operation completed successfully + */ + function verifyNoErrorToast() { + // Primary check: No error toast should appear + // Using specific toast selectors to avoid false positives from unrelated error classes + // like mat-error, error-text, etc. + cy.get('.toast-container .toast-error, .toast-error, [data-cy="toast-error"]', { timeout: 5000 }).should( + 'not.exist' + ); + + // Additional check: If any toast exists, ensure it doesn't have error styling + cy.get('body').then(($body) => { + const toastExists = $body.find('.toast-container .toast').length > 0; + + if (toastExists) { + cy.get('.toast-container .toast').filter(':visible').should('not.have.class', 'toast-error'); + cy.log('✓ Regression check passed: Toast found but no error styling detected'); + } else { + cy.log('✓ Regression check passed: No toast notifications detected'); + } + }); + + // Note: CSV download verification + // Cypress automatically downloads files to cypress/downloads folder + // You can manually verify the CSV file after the test completes + // Or add cy.readFile('cypress/downloads/filename.csv') for automated verification + } +}); diff --git a/cypress/e2e/spec.cy.ts b/cypress/e2e/spec.cy.ts index 74e2061fa..43693da69 100644 --- a/cypress/e2e/spec.cy.ts +++ b/cypress/e2e/spec.cy.ts @@ -2,8 +2,8 @@ describe('Login Page', () => { it('Visits the initial web page', () => { cy.visit('/'); cy.url().should('includes', 'login'); - cy.get('#mat-input-0').should ('be.visible'); - cy.get('#mat-input-1').should ('be.visible'); + cy.get('#mat-input-0').should('be.visible'); + cy.get('#mat-input-1').should('be.visible'); cy.contains('Login'); cy.contains('Remember me'); }); @@ -26,5 +26,4 @@ describe('Login Page', () => { cy.get('#logout').click(); cy.contains('Username'); }); - }); diff --git a/e2e/cypress/tsconfig.json b/e2e/cypress/tsconfig.json index 0c793608f..79d78d7ec 100644 --- a/e2e/cypress/tsconfig.json +++ b/e2e/cypress/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../tsconfig.json", "include": ["**/*.ts"], "compilerOptions": { - "sourceMap": false, - "types": ["cypress"] + "sourceMap": false, + "types": ["cypress"] } }