From e7e2b2b5943449a7d58ab3ddd05ef821fb9e8063 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 18:36:59 +0000 Subject: [PATCH 1/4] Initial plan From 3e960ebcb881eb0608ec050c545847f7ff3d95f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 18:52:53 +0000 Subject: [PATCH 2/4] Implement enhanced programmatic auth for multiple users in Cypress Co-authored-by: gennitdev <114821397+gennitdev@users.noreply.github.com> --- layouts/default.vue | 2 + .../cypress/e2e/auth/userSwitching.spec.cy.ts | 89 ++++++++ .../e2e/comments/voteOnComments.spec.cy.ts | 17 +- tests/cypress/support/commands.ts | 202 +++++++++++------- tests/cypress/support/index.d.ts | 25 +++ 5 files changed, 245 insertions(+), 90 deletions(-) create mode 100644 tests/cypress/e2e/auth/userSwitching.spec.cy.ts diff --git a/layouts/default.vue b/layouts/default.vue index fa383b30..e573f1e7 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -11,6 +11,7 @@ import { sideNavIsOpenVar, setSideNavIsOpenVar } from "@/cache"; // Composables for separated concerns import { useAuthManager } from "@/composables/useAuthManager"; import { useTestAuthHelpers } from "@/composables/useTestAuthHelpers"; +import { useTestAuth } from "@/composables/useTestAuth"; const isDevelopment = computed(() => config.environment === "development"); const route = useRoute(); @@ -34,6 +35,7 @@ const shouldExposeTestHelpers = if (shouldExposeTestHelpers) { useTestAuthHelpers(); + useTestAuth(); // Also expose the test auth functions } // Handle session expired login diff --git a/tests/cypress/e2e/auth/userSwitching.spec.cy.ts b/tests/cypress/e2e/auth/userSwitching.spec.cy.ts new file mode 100644 index 00000000..a368c1f2 --- /dev/null +++ b/tests/cypress/e2e/auth/userSwitching.spec.cy.ts @@ -0,0 +1,89 @@ +import { DISCUSSION_LIST } from "../constants"; +import { setupTestData } from "../../support/testSetup"; + +describe("User Switching Authentication", () => { + // Set up test data once for all tests in this file + setupTestData(); + + beforeEach(() => { + // Clear all auth state before each test to prevent interference + cy.clearAllAuthState(); + }); + + it("should successfully switch between two different users", () => { + // Set up GraphQL request interception + cy.intercept('POST', '**/graphql').as('graphqlRequest'); + + // Test 1: Login as Admin User (User 1) + cy.visit(DISCUSSION_LIST); + cy.authenticateOnCurrentPage(); + cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); + + // Verify we're authenticated as admin user + cy.window().its('localStorage').invoke('getItem', 'token').should('exist'); + + // Test 2: Switch to User 2 + const username2 = Cypress.env("auth0_username_2"); + const password2 = Cypress.env("auth0_password_2"); + + cy.switchToUser({ + username: username2, + password: password2, + displayName: 'testuser2' + }); + + // Verify we're now authenticated as user 2 with a different token + cy.window().its('localStorage').invoke('getItem', 'token').should('exist'); + + // Test 3: Switch back to Admin User + cy.switchToUser({ + username: Cypress.env("auth0_username"), + password: Cypress.env("auth0_password"), + displayName: 'cluse' + }); + + // Verify we can switch back + cy.window().its('localStorage').invoke('getItem', 'token').should('exist'); + + cy.log('โœ… User switching test completed successfully'); + }); + + it("should maintain separate authentication states", () => { + // Set up GraphQL request interception + cy.intercept('POST', '**/graphql').as('graphqlRequest'); + + const username1 = Cypress.env("auth0_username"); + const password1 = Cypress.env("auth0_password"); + const username2 = Cypress.env("auth0_username_2"); + const password2 = Cypress.env("auth0_password_2"); + + // Login as User 1 and capture token + cy.visit(DISCUSSION_LIST); + cy.switchToUser({ + username: username1, + password: password1, + displayName: 'cluse' + }); + + let token1: string; + cy.window().its('localStorage').invoke('getItem', 'token').should('exist').then((token) => { + token1 = token as string; + console.log('User 1 token captured:', token1.substring(0, 20) + '...'); + }); + + // Switch to User 2 and capture token + cy.switchToUser({ + username: username2, + password: password2, + displayName: 'testuser2' + }); + + cy.window().its('localStorage').invoke('getItem', 'token').should('exist').then((token2) => { + console.log('User 2 token captured:', (token2 as string).substring(0, 20) + '...'); + + // Verify the tokens are different + expect(token2).to.not.equal(token1); + cy.log('โœ… Tokens are different - auth states are separate'); + }); + }); +}); \ No newline at end of file diff --git a/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts b/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts index d40305eb..7d1b2127 100644 --- a/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts +++ b/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts @@ -11,7 +11,7 @@ describe("Comment voting operations", () => { }); it("User 1 can undo upvote on their own comment", () => { - const TEST_COMMENT_TEXT = "Test comment"; + const TEST_COMMENT_TEXT = "Test comment by user 1"; // Set up GraphQL request interception cy.intercept('POST', '**/graphql').as('graphqlRequest'); @@ -25,7 +25,7 @@ describe("Comment voting operations", () => { } }); - // User 1 logs in programmatically + // User 1 logs in programmatically (admin user) cy.visit(DISCUSSION_LIST); cy.authenticateOnCurrentPage(); cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); @@ -98,7 +98,7 @@ describe("Comment voting operations", () => { }); it("User 2 can upvote another user's comment", () => { - const TEST_COMMENT_TEXT = "Test comment"; + const TEST_COMMENT_TEXT = "Test comment by user 1"; // Set up GraphQL request interception cy.intercept('POST', '**/graphql').as('graphqlRequest'); @@ -110,15 +110,14 @@ describe("Comment voting operations", () => { } }); - // User 2 logs in with different credentials + // Navigate to the page first + cy.visit(DISCUSSION_LIST); + + // ENHANCED: Switch to User 2 with improved authentication const username2 = Cypress.env("auth0_username_2"); const password2 = Cypress.env("auth0_password_2"); - // Visit the page first - cy.visit(DISCUSSION_LIST); - - // Then authenticate as User 2 - cy.authenticateAsUserOnCurrentPage({ + cy.switchToUser({ username: username2, password: password2, displayName: 'testuser2' diff --git a/tests/cypress/support/commands.ts b/tests/cypress/support/commands.ts index 293c6fd2..51749828 100644 --- a/tests/cypress/support/commands.ts +++ b/tests/cypress/support/commands.ts @@ -189,70 +189,75 @@ Cypress.Commands.add("writeClipboardText", () => { }); }); -// FIXED: Clear all auth state completely including Auth0's internal cache +// ENHANCED: Clear all auth state completely including Auth0's internal cache Cypress.Commands.add("clearAllAuthState", () => { - // Clear browser localStorage - including Auth0's cache - cy.window().then((window) => { + console.log('๐Ÿงน Starting complete auth state clear...'); + + // Clear Cypress localStorage cache first + localStorage.removeItem(AUTH_TOKEN_CACHE_KEY); + + // Clear ALL Cypress localStorage that might be Auth0 related + Object.keys(localStorage).forEach(key => { + if (key.includes('@@auth0spajs@@') || + key.includes('auth0') || + key.startsWith('auth0.') || + key.startsWith(AUTH_TOKEN_CACHE_KEY)) { + localStorage.removeItem(key); + } + }); + + return cy.window().then((window) => { + // Reset Vue reactive auth state FIRST + const testWin = window as any; + if (testWin.__SET_AUTH_STATE_DIRECT__) { + console.log('๐Ÿงน Resetting reactive auth state to unauthenticated'); + testWin.__SET_AUTH_STATE_DIRECT__({ + username: '', + authenticated: false + }); + } + // Clear our app's auth tokens window.localStorage.removeItem(AUTH_TOKEN_NAME); window.localStorage.removeItem(AUTH_TOKEN_CACHE_KEY); // Clear ALL Auth0-related localStorage entries - // Auth0 stores tokens with keys like: @@auth0spajs@@::CLIENT_ID::AUDIENCE::scope - Object.keys(window.localStorage).forEach(key => { + const localStorageKeys = Object.keys(window.localStorage); + localStorageKeys.forEach(key => { if (key.includes('@@auth0spajs@@') || key.includes('auth0') || key.startsWith('auth0.') || key.startsWith(AUTH_TOKEN_CACHE_KEY)) { - console.log('Clearing Auth0 cache key:', key); + console.log('๐Ÿงน Clearing Auth0 localStorage key:', key); window.localStorage.removeItem(key); } }); - // Clear session storage too (Auth0 might use this) - Object.keys(window.sessionStorage).forEach(key => { + // Clear session storage + const sessionStorageKeys = Object.keys(window.sessionStorage); + sessionStorageKeys.forEach(key => { if (key.includes('@@auth0spajs@@') || key.includes('auth0') || key.startsWith('auth0.')) { - console.log('Clearing Auth0 session key:', key); + console.log('๐Ÿงน Clearing Auth0 sessionStorage key:', key); window.sessionStorage.removeItem(key); } }); // Clear the rest of session storage window.sessionStorage.clear(); - }); - - // Clear Cypress localStorage cache - localStorage.removeItem(AUTH_TOKEN_CACHE_KEY); - - // Clear ALL Cypress localStorage that might be Auth0 related - Object.keys(localStorage).forEach(key => { - if (key.includes('@@auth0spajs@@') || - key.includes('auth0') || - key.startsWith('auth0.') || - key.startsWith(AUTH_TOKEN_CACHE_KEY)) { - localStorage.removeItem(key); - } - }); - - // Reset Vue reactive auth state - cy.window().then((win) => { - const testWin = win as any; - if (testWin.__SET_AUTH_STATE_DIRECT__) { - testWin.__SET_AUTH_STATE_DIRECT__({ - username: '', - authenticated: false - }); - } + + console.log('๐Ÿงน Auth state clear completed'); + }).then(() => { + // Wait for the state changes to propagate + cy.wait(100); }); }); -// FIXED: Login as different user with proper cache clearing +// ENHANCED: Login as different user with guaranteed fresh token Cypress.Commands.add("loginAsUser", (userCredentials: { username: string; password: string }) => { // Log the credentials being used for debugging - console.log(`Attempting login for user: ${userCredentials.username}`); - console.log(`Password provided: ${userCredentials.password ? '[REDACTED]' : 'UNDEFINED/EMPTY'}`); + console.log(`๐Ÿ” Attempting fresh login for user: ${userCredentials.username}`); // Check if credentials are actually provided if (!userCredentials.username || !userCredentials.password) { @@ -275,14 +280,16 @@ Cypress.Commands.add("loginAsUser", (userCredentials: { username: string; passwo }, }; - cy.request(options).then((response) => { + return cy.request(options).then((response) => { const accessToken = response.body.access_token; - console.log(`Fresh token for ${userCredentials.username}:`, accessToken.substring(0, 20) + '...'); + console.log(`๐Ÿ” Fresh token obtained for ${userCredentials.username}:`, accessToken.substring(0, 20) + '...'); // Set token directly in browser localStorage - no caching - cy.window().then((window) => { + return cy.window().then((window) => { window.localStorage.setItem(AUTH_TOKEN_NAME, accessToken); + console.log('๐Ÿ” Token stored in browser localStorage'); + return accessToken; }); }); }); @@ -342,53 +349,86 @@ Cypress.Commands.add("authenticateOnCurrentPage", () => { }); }); -// IMPROVED: Authenticate as different user on current page +// ENHANCED: Authenticate as different user on current page with reliable state sync Cypress.Commands.add("authenticateAsUserOnCurrentPage", (userCredentials: { username: string; password: string; displayName?: string }) => { - // First, completely clear all previous auth state - cy.clearAllAuthState(); - - // Wait a moment for the clear to take effect - cy.wait(500); - - // Set the auth token programmatically for the specified user - cy.loginAsUser(userCredentials); - - // Wait for page to fully load - cy.wait(2000); + console.log('๐Ÿ”„ Starting user authentication switch to:', userCredentials.displayName || userCredentials.username); - // Wait for the auth function to be available and then call it - cy.window().then((win) => { - const testWin = win as any; + // Step 1: Completely clear all previous auth state + return cy.clearAllAuthState().then(() => { + console.log('๐Ÿ”„ Auth state cleared, proceeding with fresh login'); - if (testWin.__SET_AUTH_STATE_DIRECT__) { - console.log('๐Ÿ”ง Setting auth state directly for user:', userCredentials.displayName || userCredentials.username); - testWin.__SET_AUTH_STATE_DIRECT__({ - username: userCredentials.displayName || userCredentials.username, - authenticated: true - }); - } else { - console.log('โŒ __SET_AUTH_STATE_DIRECT__ not available for user auth, retrying...'); + // Step 2: Get fresh token for the specified user + return cy.loginAsUser(userCredentials); + }).then((accessToken) => { + console.log('๐Ÿ”„ Fresh token obtained, syncing with UI state'); + + // Step 3: Wait for auth functions to be available and sync the UI state + return cy.window().then((win) => { + const testWin = win as any; - // Retry after a short delay - cy.wait(1000).then(() => { - cy.window().then((retryWin) => { - const retryTestWin = retryWin as any; - if (retryTestWin.__SET_AUTH_STATE_DIRECT__) { - console.log('๐Ÿ”ง Setting auth state directly for user (retry):', userCredentials.displayName || userCredentials.username); - retryTestWin.__SET_AUTH_STATE_DIRECT__({ - username: userCredentials.displayName || userCredentials.username, - authenticated: true - }); - } else { - console.error('โŒ __SET_AUTH_STATE_DIRECT__ still not available for user auth after retry'); - } - }); - }); - } + // Helper function to attempt auth state sync + const syncAuthState = (retryCount = 0) => { + if (testWin.__SET_AUTH_STATE_DIRECT__) { + const displayName = userCredentials.displayName || userCredentials.username; + console.log(`๐Ÿ”„ Setting auth state directly for user: ${displayName} (attempt ${retryCount + 1})`); + + testWin.__SET_AUTH_STATE_DIRECT__({ + username: displayName, + authenticated: true + }); + + return Promise.resolve(); + } else if (retryCount < 3) { + console.log(`๐Ÿ”„ Auth sync function not available, retrying in 500ms (attempt ${retryCount + 1})`); + return new Promise((resolve) => { + setTimeout(() => { + resolve(syncAuthState(retryCount + 1)); + }, 500); + }); + } else { + console.error('โŒ Auth sync function not available after multiple retries'); + return Promise.resolve(); + } + }; + + return syncAuthState(); + }); + }).then(() => { + // Step 4: Verify authentication is complete + console.log('๐Ÿ”„ Verifying authentication completion'); + + return cy.window().its('localStorage').invoke('getItem', 'token').should('exist').then((token) => { + console.log(`โœ… Authentication switch completed for ${userCredentials.username}`); + console.log(`๐Ÿ”‘ Token verification: ${(token as string).substring(0, 20)}...`); + }); + }).then(() => { + // Step 5: Final wait for UI to update + cy.wait(100); + console.log('๐Ÿ”„ User authentication switch completed successfully'); }); +}); + +// NEW: Simplified command for quick user switching within the same test +Cypress.Commands.add("switchToUser", (userCredentials: { username: string; password: string; displayName?: string }) => { + console.log('๐Ÿ”€ Quick user switch to:', userCredentials.displayName || userCredentials.username); + + // Clear current auth state + cy.clearAllAuthState(); - // Verify authentication is complete and token is different - cy.window().its('localStorage').invoke('getItem', 'token').should('exist').then((token) => { - console.log(`Token set for ${userCredentials.username}:`, (token as string).substring(0, 20) + '...'); + // Login as new user and sync state + cy.loginAsUser(userCredentials).then(() => { + return cy.window().then((win) => { + const testWin = win as any; + if (testWin.__SET_AUTH_STATE_DIRECT__) { + testWin.__SET_AUTH_STATE_DIRECT__({ + username: userCredentials.displayName || userCredentials.username, + authenticated: true + }); + } + }); }); + + // Verify the switch was successful + cy.window().its('localStorage').invoke('getItem', 'token').should('exist'); + cy.wait(100); // Brief wait for UI to update }); \ No newline at end of file diff --git a/tests/cypress/support/index.d.ts b/tests/cypress/support/index.d.ts index c9604eb5..02cb4e9c 100644 --- a/tests/cypress/support/index.d.ts +++ b/tests/cypress/support/index.d.ts @@ -51,5 +51,30 @@ declare namespace Cypress { * Setup clipboard text writing */ writeClipboardText(): Chainable; + + /** + * Clear all authentication state completely + */ + clearAllAuthState(): Chainable; + + /** + * Log in as a specific user with fresh token + */ + loginAsUser(userCredentials: { username: string; password: string }): Chainable; + + /** + * Authenticate on current page programmatically + */ + authenticateOnCurrentPage(): Chainable; + + /** + * Authenticate as a different user on current page + */ + authenticateAsUserOnCurrentPage(userCredentials: { username: string; password: string; displayName?: string }): Chainable; + + /** + * Quick user switch within the same test + */ + switchToUser(userCredentials: { username: string; password: string; displayName?: string }): Chainable; } } \ No newline at end of file From 21f65c1b78974fe8df474436e87052d6d5da64d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 18:54:30 +0000 Subject: [PATCH 3/4] Add comprehensive authentication documentation and validation Co-authored-by: gennitdev <114821397+gennitdev@users.noreply.github.com> --- tests/cypress/AUTHENTICATION.md | 116 ++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/cypress/AUTHENTICATION.md diff --git a/tests/cypress/AUTHENTICATION.md b/tests/cypress/AUTHENTICATION.md new file mode 100644 index 00000000..cfe81db8 --- /dev/null +++ b/tests/cypress/AUTHENTICATION.md @@ -0,0 +1,116 @@ +# Cypress Programmatic Authentication Guide + +This guide explains how to use the enhanced programmatic authentication in Cypress tests that support multiple users. + +## Available Commands + +### Basic Authentication + +```typescript +// Authenticate as the default admin user +cy.authenticateOnCurrentPage(); + +// Clear all authentication state (localStorage, sessionStorage, reactive state) +cy.clearAllAuthState(); +``` + +### Multi-User Authentication + +```typescript +// Switch to a different user within the same test +cy.switchToUser({ + username: 'user@example.com', + password: 'password123', + displayName: 'TestUser' // Optional display name for UI +}); + +// Full authentication setup (use when visiting a new page) +cy.authenticateAsUserOnCurrentPage({ + username: 'user@example.com', + password: 'password123', + displayName: 'TestUser' +}); +``` + +## Example: Testing with Multiple Users + +```typescript +describe("Multi-user workflow", () => { + beforeEach(() => { + // Always clear auth state before each test + cy.clearAllAuthState(); + }); + + it("User 1 creates content, User 2 interacts with it", () => { + // Set up network interception + cy.intercept('POST', '**/graphql').as('graphqlRequest'); + + // User 1: Create content + cy.visit(DISCUSSION_LIST); + cy.authenticateOnCurrentPage(); // Default admin user + cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); + + // ... User 1 creates content ... + + // User 2: Interact with content + const user2Credentials = { + username: Cypress.env("auth0_username_2"), + password: Cypress.env("auth0_password_2"), + displayName: 'testuser2' + }; + + cy.switchToUser(user2Credentials); + cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); + + // ... User 2 interacts with content ... + }); +}); +``` + +## Environment Variables + +Make sure your `cypress.env.json` includes credentials for multiple users: + +```json +{ + "auth0_username": "user1@example.com", + "auth0_password": "password1", + "auth0_username_2": "user2@example.com", + "auth0_password_2": "password2" +} +``` + +## Best Practices + +1. **Always clear auth state**: Use `cy.clearAllAuthState()` in `beforeEach()` to prevent test interference +2. **Use switchToUser for quick switches**: Within the same test, use `cy.switchToUser()` for better performance +3. **Wait for GraphQL requests**: Always wait for the initial GraphQL request after authentication +4. **Verify token existence**: Check that tokens are set correctly after authentication +5. **Use network interception**: Set up GraphQL interception to wait for requests properly + +## Troubleshooting + +### Authentication Not Working +- Check that environment variables are set correctly +- Verify that the test auth composables are loaded (they should be in the default layout) +- Look for console logs starting with ๐Ÿ”ง, ๐Ÿ”„, or ๐Ÿงน for debugging information + +### Timing Issues +- Increase wait times if needed, especially after user switches +- Ensure GraphQL interception is set up before authentication +- Use `cy.wait('@graphqlRequest')` after authentication calls + +### State Not Clearing +- The enhanced `clearAllAuthState()` should handle all Auth0 and app state +- If issues persist, check browser localStorage/sessionStorage manually +- Look for any remaining auth-related keys that might need clearing + +## Command Reference + +| Command | Purpose | When to Use | +|---------|---------|-------------| +| `cy.clearAllAuthState()` | Clear all auth state completely | Before each test, before user switches | +| `cy.authenticateOnCurrentPage()` | Login as default admin user | Single-user tests, first user in multi-user tests | +| `cy.switchToUser(credentials)` | Quick user switch | Within the same test for different users | +| `cy.authenticateAsUserOnCurrentPage(credentials)` | Full auth setup for specific user | When visiting new pages as different users | +| `cy.loginAsUser(credentials)` | Low-level token retrieval | Advanced use cases, custom auth flows | \ No newline at end of file From 422f1d0657c22319c8dd523cfc6e551b7268bc78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Jul 2025 19:38:15 +0000 Subject: [PATCH 4/4] Fix switchToUser authentication chaining and test timing Co-authored-by: gennitdev <114821397+gennitdev@users.noreply.github.com> --- tests/cypress/AUTHENTICATION.md | 11 +++- .../e2e/comments/voteOnComments.spec.cy.ts | 3 +- tests/cypress/support/commands.ts | 63 ++++++++++++++----- 3 files changed, 57 insertions(+), 20 deletions(-) diff --git a/tests/cypress/AUTHENTICATION.md b/tests/cypress/AUTHENTICATION.md index cfe81db8..4884d53e 100644 --- a/tests/cypress/AUTHENTICATION.md +++ b/tests/cypress/AUTHENTICATION.md @@ -60,9 +60,13 @@ describe("Multi-user workflow", () => { }; cy.switchToUser(user2Credentials); - cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); + + // IMPORTANT: After user switch, wait before performing actions + // The switchToUser command sets auth state but doesn't trigger GraphQL requests + cy.wait(500); // Allow UI to update with new auth state // ... User 2 interacts with content ... + // Any GraphQL requests will now be authenticated as the new user }); }); ``` @@ -97,8 +101,9 @@ Make sure your `cypress.env.json` includes credentials for multiple users: ### Timing Issues - Increase wait times if needed, especially after user switches -- Ensure GraphQL interception is set up before authentication -- Use `cy.wait('@graphqlRequest')` after authentication calls +- After `switchToUser()`, allow time for UI to update before performing actions +- The `switchToUser()` command itself doesn't trigger GraphQL requests - wait for subsequent user actions +- Use `cy.wait('@graphqlRequest')` after user actions, not after authentication calls ### State Not Clearing - The enhanced `clearAllAuthState()` should handle all Auth0 and app state diff --git a/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts b/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts index 7d1b2127..a3d0c0ff 100644 --- a/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts +++ b/tests/cypress/e2e/comments/voteOnComments.spec.cy.ts @@ -112,6 +112,7 @@ describe("Comment voting operations", () => { // Navigate to the page first cy.visit(DISCUSSION_LIST); + cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); // ENHANCED: Switch to User 2 with improved authentication const username2 = Cypress.env("auth0_username_2"); @@ -122,8 +123,6 @@ describe("Comment voting operations", () => { password: password2, displayName: 'testuser2' }); - - cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200); // Navigate to the same discussion cy.get("span").contains("Example topic 1").click(); diff --git a/tests/cypress/support/commands.ts b/tests/cypress/support/commands.ts index 51749828..826ef9ef 100644 --- a/tests/cypress/support/commands.ts +++ b/tests/cypress/support/commands.ts @@ -412,23 +412,56 @@ Cypress.Commands.add("authenticateAsUserOnCurrentPage", (userCredentials: { user Cypress.Commands.add("switchToUser", (userCredentials: { username: string; password: string; displayName?: string }) => { console.log('๐Ÿ”€ Quick user switch to:', userCredentials.displayName || userCredentials.username); - // Clear current auth state - cy.clearAllAuthState(); - - // Login as new user and sync state - cy.loginAsUser(userCredentials).then(() => { + // Properly chain the operations to ensure they execute in sequence + return cy.clearAllAuthState().then(() => { + console.log('๐Ÿ”€ Auth state cleared, logging in as new user'); + + // Login as new user and sync state + return cy.loginAsUser(userCredentials); + }).then((accessToken) => { + console.log('๐Ÿ”€ New user token obtained, syncing UI state'); + + // Sync the UI state with proper retry logic return cy.window().then((win) => { const testWin = win as any; - if (testWin.__SET_AUTH_STATE_DIRECT__) { - testWin.__SET_AUTH_STATE_DIRECT__({ - username: userCredentials.displayName || userCredentials.username, - authenticated: true - }); - } + + const syncAuthState = (retryCount = 0) => { + if (testWin.__SET_AUTH_STATE_DIRECT__) { + const displayName = userCredentials.displayName || userCredentials.username; + console.log(`๐Ÿ”€ Setting auth state for user: ${displayName}`); + + testWin.__SET_AUTH_STATE_DIRECT__({ + username: displayName, + authenticated: true + }); + + return Promise.resolve(); + } else if (retryCount < 3) { + console.log(`๐Ÿ”€ Auth sync function not available, retrying... (attempt ${retryCount + 1})`); + return new Promise((resolve) => { + setTimeout(() => { + resolve(syncAuthState(retryCount + 1)); + }, 200); + }); + } else { + console.error('โŒ Auth sync function not available after retries in switchToUser'); + return Promise.resolve(); + } + }; + + return syncAuthState(); }); + }).then(() => { + // Verify the switch was successful + console.log('๐Ÿ”€ Verifying user switch completion'); + + return cy.window().its('localStorage').invoke('getItem', 'token').should('exist').then((token) => { + console.log(`โœ… User switch completed for ${userCredentials.username}`); + console.log(`๐Ÿ”‘ Token verification: ${(token as string).substring(0, 20)}...`); + }); + }).then(() => { + // Additional wait for UI to fully update + cy.wait(200); + console.log('๐Ÿ”€ User switch completed successfully'); }); - - // Verify the switch was successful - cy.window().its('localStorage').invoke('getItem', 'token').should('exist'); - cy.wait(100); // Brief wait for UI to update }); \ No newline at end of file