Skip to content
Draft
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: 2 additions & 0 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -34,6 +35,7 @@ const shouldExposeTestHelpers =

if (shouldExposeTestHelpers) {
useTestAuthHelpers();
useTestAuth(); // Also expose the test auth functions
}

// Handle session expired login
Expand Down
121 changes: 121 additions & 0 deletions tests/cypress/AUTHENTICATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# 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);

// 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
});
});
```

## 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
- 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
- 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 |
89 changes: 89 additions & 0 deletions tests/cypress/e2e/auth/userSwitching.spec.cy.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
20 changes: 9 additions & 11 deletions tests/cypress/e2e/comments/voteOnComments.spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
Expand Down Expand Up @@ -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');
Expand All @@ -110,21 +110,19 @@ describe("Comment voting operations", () => {
}
});

// User 2 logs in with different credentials
// 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");
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'
});

cy.wait('@graphqlRequest').its('response.statusCode').should('eq', 200);

// Navigate to the same discussion
cy.get("span").contains("Example topic 1").click();
Expand Down
Loading