feat(tests): migrate e2e tests to dynamic admin helper

This commit is contained in:
perfectra1n 2025-12-09 10:27:25 -08:00
parent 99e426da7c
commit 43eedae028
15 changed files with 152 additions and 389 deletions

View File

@ -1,12 +1,7 @@
import { test, expect, AuthHelper, TEST_CREDENTIALS, TIMEOUTS } from './fixtures/auth';
import { TestHelpers } from './utils/test-helpers';
import { test, expect, TIMEOUTS } from './fixtures/auth';
import { E2ETestAuthHelper } from './utils/test-auth-helper';
test.describe('Authentication', () => {
test.beforeEach(async ({ page }) => {
const authHelper = new AuthHelper(page);
await authHelper.ensureLoggedOut();
});
test('should display login form on initial visit', async ({ page }) => {
await page.goto('/');
@ -17,9 +12,13 @@ test.describe('Authentication', () => {
});
test('should login with valid credentials', async ({ page }) => {
const authHelper = new AuthHelper(page);
// Create a dynamic test user via API
const authHelper = new E2ETestAuthHelper(page);
const testUser = await authHelper.createTestUser();
await authHelper.loginAs(TEST_CREDENTIALS.admin);
// Login with the dynamically created user
const loginSuccess = await authHelper.loginUser(testUser.credentials);
expect(loginSuccess).toBe(true);
// Should redirect to dashboard
await page.waitForURL(/.*\/dashboard.*/, { timeout: TIMEOUTS.navigation });
@ -44,10 +43,10 @@ test.describe('Authentication', () => {
});
test.skip('should logout successfully', async ({ page }) => {
const authHelper = new AuthHelper(page);
// First login
await authHelper.loginAs(TEST_CREDENTIALS.admin);
// Create and login with a dynamic test user
const authHelper = new E2ETestAuthHelper(page);
const testUser = await authHelper.createTestUser();
await authHelper.loginUser(testUser.credentials);
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.navigation });
@ -65,10 +64,10 @@ test.describe('Authentication', () => {
});
test.skip('should persist session on page reload', async ({ page }) => {
const authHelper = new AuthHelper(page);
// Login first
await authHelper.loginAs(TEST_CREDENTIALS.admin);
// Create and login with a dynamic test user
const authHelper = new E2ETestAuthHelper(page);
const testUser = await authHelper.createTestUser();
await authHelper.loginUser(testUser.credentials);
await page.waitForURL(/\/dashboard|\//, { timeout: TIMEOUTS.navigation });

View File

@ -1,8 +1,7 @@
import { test, expect } from './fixtures/auth';
import { TEST_CREDENTIALS } from './fixtures/auth';
test.describe('Dashboard', () => {
test('should display welcome back message after login', async ({ authenticatedPage: page }) => {
test('should display welcome back message after login', async ({ dynamicAdminPage: page, testAdmin }) => {
// Navigate to dashboard
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
@ -11,10 +10,10 @@ test.describe('Dashboard', () => {
await expect(page.locator('h4:has-text("Welcome back,")')).toBeVisible();
// Check for username in welcome message
await expect(page.locator(`h4:has-text("Welcome back, ${TEST_CREDENTIALS.admin.username}!")`)).toBeVisible();
await expect(page.locator(`h4:has-text("Welcome back, ${testAdmin.credentials.username}!")`)).toBeVisible();
});
test('should display dashboard stats', async ({ authenticatedPage: page }) => {
test('should display dashboard stats', async ({ dynamicAdminPage: page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
@ -25,7 +24,7 @@ test.describe('Dashboard', () => {
await expect(page.locator('text="Searchable"')).toBeVisible();
});
test('should display quick actions', async ({ authenticatedPage: page }) => {
test('should display quick actions', async ({ dynamicAdminPage: page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
@ -35,7 +34,7 @@ test.describe('Dashboard', () => {
await expect(page.locator('text="Browse Documents"')).toBeVisible();
});
test('should have working navigation', async ({ authenticatedPage: page }) => {
test('should have working navigation', async ({ dynamicAdminPage: page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');

View File

@ -5,13 +5,13 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Debug Upload', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
await authenticatedPage.goto('/upload');
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await dynamicAdminPage.goto('/upload');
await helpers.waitForLoadingToComplete();
});
test('should debug upload workflow', async ({ authenticatedPage: page }) => {
test('should debug upload workflow', async ({ dynamicAdminPage: page }) => {
console.log('Starting upload debug test...');
// Find file input

View File

@ -5,12 +5,12 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Document Management', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/documents');
});
test('should display document list', async ({ authenticatedPage: page }) => {
test('should display document list', async ({ dynamicAdminPage: page }) => {
// The documents page should be visible with title and description
// Use more flexible selectors for headings - based on artifact, it's h4
const documentsHeading = page.locator('h4:has-text("Documents")');
@ -86,7 +86,7 @@ test.describe('Document Management', () => {
console.log('Document list page test completed successfully');
});
test.skip('should navigate to document details', async ({ authenticatedPage: page }) => {
test.skip('should navigate to document details', async ({ dynamicAdminPage: page }) => {
// Click on first document if available
const firstDocument = page.locator('.MuiCard-root').first();
@ -103,7 +103,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should display document metadata', async ({ authenticatedPage: page }) => {
test.skip('should display document metadata', async ({ dynamicAdminPage: page }) => {
const firstDocument = page.locator('.MuiCard-root').first();
if (await firstDocument.isVisible()) {
@ -117,7 +117,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should allow document download', async ({ authenticatedPage: page }) => {
test.skip('should allow document download', async ({ dynamicAdminPage: page }) => {
const firstDocument = page.locator('[data-testid="document-item"], .document-item, .document-card').first();
if (await firstDocument.isVisible()) {
@ -139,7 +139,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should allow document deletion', async ({ authenticatedPage: page }) => {
test.skip('should allow document deletion', async ({ dynamicAdminPage: page }) => {
const firstDocument = page.locator('[data-testid="document-item"], .document-item, .document-card').first();
if (await firstDocument.isVisible()) {
@ -163,7 +163,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should filter documents by type', async ({ authenticatedPage: page }) => {
test.skip('should filter documents by type', async ({ dynamicAdminPage: page }) => {
// Look for filter controls
const filterDropdown = page.locator('[data-testid="type-filter"], select[name="type"], .type-filter');
if (await filterDropdown.isVisible()) {
@ -180,7 +180,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should sort documents', async ({ authenticatedPage: page }) => {
test.skip('should sort documents', async ({ dynamicAdminPage: page }) => {
const sortDropdown = page.locator('[data-testid="sort"], select[name="sort"], .sort-dropdown');
if (await sortDropdown.isVisible()) {
await sortDropdown.selectOption('date-desc');
@ -192,7 +192,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should display OCR status', async ({ authenticatedPage: page }) => {
test.skip('should display OCR status', async ({ dynamicAdminPage: page }) => {
const firstDocument = page.locator('.MuiCard-root').first();
if (await firstDocument.isVisible()) {
@ -207,7 +207,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should search within document content', async ({ authenticatedPage: page }) => {
test.skip('should search within document content', async ({ dynamicAdminPage: page }) => {
const firstDocument = page.locator('.MuiCard-root').first();
if (await firstDocument.isVisible()) {
@ -230,7 +230,7 @@ test.describe('Document Management', () => {
}
});
test.skip('should paginate document list', async ({ authenticatedPage: page }) => {
test.skip('should paginate document list', async ({ dynamicAdminPage: page }) => {
// Look for pagination controls
const nextPageButton = page.locator('[data-testid="next-page"], button:has-text("Next"), .pagination-next');
if (await nextPageButton.isVisible()) {
@ -246,7 +246,7 @@ test.describe('Document Management', () => {
}
});
test('should show document thumbnails'.skip, async ({ authenticatedPage: page }) => {
test('should show document thumbnails'.skip, async ({ dynamicAdminPage: page }) => {
// Check for document thumbnails in list view
const documentThumbnails = page.locator('[data-testid="document-thumbnail"], .thumbnail, .document-preview');
if (await documentThumbnails.first().isVisible()) {

View File

@ -2,18 +2,6 @@ import { test as base, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import { E2ETestAuthHelper, type E2ETestUser, type TestCredentials } from '../utils/test-auth-helper';
// Legacy credentials for backward compatibility (still available for seeded admin user)
export const TEST_CREDENTIALS = {
admin: {
username: 'admin',
password: 'readur2024'
},
user: {
username: 'user',
password: 'userpass123'
}
} as const;
export const TIMEOUTS = {
login: 15000,
navigation: 15000,
@ -21,174 +9,14 @@ export const TIMEOUTS = {
} as const;
export interface AuthFixture {
authenticatedPage: Page;
adminPage: Page;
userPage: Page;
dynamicAdminPage: Page;
dynamicUserPage: Page;
testUser: E2ETestUser;
testAdmin: E2ETestUser;
}
// Shared authentication helper functions
export class AuthHelper {
constructor(private page: Page) {}
async loginAs(credentials: typeof TEST_CREDENTIALS.admin | typeof TEST_CREDENTIALS.user) {
console.log(`Attempting to login as ${credentials.username}...`);
// Go to home page and wait for it to load
await this.page.goto('/');
await this.page.waitForLoadState('networkidle');
// Check if already logged in by looking for dashboard content
const welcomeText = await this.page.locator('h4:has-text("Welcome back,")').isVisible().catch(() => false);
if (welcomeText) {
console.log('Already logged in - found welcome message');
return;
}
// Wait for login page to be ready - look for the distinctive login page content
await this.page.waitForSelector('h3:has-text("Welcome to Readur")', { timeout: TIMEOUTS.login });
await this.page.waitForSelector('h5:has-text("Sign in to your account")', { timeout: TIMEOUTS.login });
// Material-UI creates input elements inside TextFields, but we need to wait for them to be ready
// The inputs have the name attributes from react-hook-form register
const usernameField = this.page.locator('input[name="username"]');
const passwordField = this.page.locator('input[name="password"]');
// Wait for both fields to be attached and visible
await usernameField.waitFor({ state: 'attached', timeout: TIMEOUTS.login });
await passwordField.waitFor({ state: 'attached', timeout: TIMEOUTS.login });
// WebKit can be slower - add extra wait time
const browserName = await this.page.evaluate(() => navigator.userAgent);
const isWebKit = browserName.includes('WebKit') && !browserName.includes('Chrome');
if (isWebKit) {
console.log('WebKit browser detected - adding extra wait time');
await this.page.waitForTimeout(5000);
}
// Clear any existing content and fill the fields
await usernameField.clear();
await usernameField.fill(credentials.username);
await passwordField.clear();
await passwordField.fill(credentials.password);
// WebKit needs extra time for form validation
if (isWebKit) {
await this.page.waitForTimeout(3000);
}
// Click submit button - look for the sign in button specifically
const signInButton = this.page.locator('button[type="submit"]:has-text("Sign in")');
await signInButton.waitFor({ state: 'visible', timeout: TIMEOUTS.login });
if (isWebKit) {
// WebKit-specific approach: don't wait for API response, just click and wait for navigation
await signInButton.click();
// WebKit needs more time before checking navigation
await this.page.waitForTimeout(2000);
// Wait for navigation with longer timeout for WebKit
await this.page.waitForURL(/.*\/dashboard.*/, { timeout: 25000 });
console.log(`Successfully navigated to: ${this.page.url()}`);
// Wait for dashboard content to load with extra time for WebKit
await this.page.waitForFunction(() => {
return document.querySelector('h4') !== null &&
(document.querySelector('h4')?.textContent?.includes('Welcome') ||
document.querySelector('[role="main"]') !== null);
}, { timeout: 20000 });
} else {
// Standard approach for other browsers
const loginPromise = this.page.waitForResponse(response =>
response.url().includes('/auth/login') && response.status() === 200,
{ timeout: TIMEOUTS.login }
);
await signInButton.click();
try {
const response = await loginPromise;
// Wait for navigation to dashboard with more flexible URL pattern
await this.page.waitForURL(/.*\/dashboard.*/, { timeout: TIMEOUTS.navigation });
console.log(`Successfully navigated to: ${this.page.url()}`);
// Wait for dashboard content to load - be more flexible about the welcome message
await this.page.waitForFunction(() => {
return document.querySelector('h4') !== null &&
(document.querySelector('h4')?.textContent?.includes('Welcome') ||
document.querySelector('[role="main"]') !== null);
}, { timeout: TIMEOUTS.navigation });
} catch (error) {
// Take a screenshot for debugging
await this.page.screenshot({
path: `test-results/login-failure-${credentials.username}-${Date.now()}.png`,
fullPage: true
});
throw error;
}
}
}
async logout() {
// Look for logout button/link and click it
const logoutButton = this.page.locator('[data-testid="logout"], button:has-text("Logout"), a:has-text("Logout")').first();
if (await logoutButton.isVisible()) {
await logoutButton.click();
// Wait for redirect to login page
await this.page.waitForFunction(() =>
window.location.pathname.includes('/login') || window.location.pathname === '/',
{ timeout: TIMEOUTS.navigation }
);
}
}
async ensureLoggedOut() {
await this.page.goto('/');
await this.page.waitForLoadState('networkidle');
// If we see a login form, we're already logged out
const usernameInput = await this.page.locator('input[name="username"]').isVisible().catch(() => false);
if (usernameInput) {
return;
}
// Otherwise, try to logout
await this.logout();
}
}
export const test = base.extend<AuthFixture>({
// Legacy fixtures using seeded users (for backward compatibility)
authenticatedPage: async ({ page }, use) => {
const auth = new AuthHelper(page);
await auth.loginAs(TEST_CREDENTIALS.admin);
await use(page);
},
adminPage: async ({ page }, use) => {
const auth = new AuthHelper(page);
await auth.loginAs(TEST_CREDENTIALS.admin);
await use(page);
},
userPage: async ({ page }, use) => {
const auth = new AuthHelper(page);
await auth.loginAs(TEST_CREDENTIALS.admin); // Use admin since 'user' doesn't exist
await use(page);
},
// New dynamic fixtures using API-created users
// Dynamic fixtures using API-created users
testUser: async ({ page }, use) => {
const authHelper = new E2ETestAuthHelper(page);
const testUser = await authHelper.createTestUser();

View File

@ -4,11 +4,11 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Navigation', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
});
test('should check available routes after login', async ({ authenticatedPage: page }) => {
test('should check available routes after login', async ({ dynamicAdminPage: page }) => {
// Check current URL after login
console.log('Current URL after login:', page.url());
@ -50,7 +50,7 @@ test.describe('Navigation', () => {
}
});
test('should check what elements are on dashboard', async ({ authenticatedPage: page }) => {
test('should check what elements are on dashboard', async ({ dynamicAdminPage: page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle', { timeout: 5000 });

View File

@ -5,12 +5,12 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('OCR Retry Workflow', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ adminPage }) => {
helpers = new TestHelpers(adminPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/documents');
});
test('should display failed OCR documents', async ({ adminPage: page }) => {
test('should display failed OCR documents', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
@ -38,7 +38,7 @@ test.describe('OCR Retry Workflow', () => {
}
});
test('should retry individual failed OCR document', async ({ adminPage: page }) => {
test('should retry individual failed OCR document', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
@ -76,7 +76,7 @@ test.describe('OCR Retry Workflow', () => {
}
});
test('should bulk retry multiple failed OCR documents', async ({ adminPage: page }) => {
test('should bulk retry multiple failed OCR documents', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
@ -130,7 +130,7 @@ test.describe('OCR Retry Workflow', () => {
}
});
test('should show OCR retry history', async ({ adminPage: page }) => {
test('should show OCR retry history', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
@ -153,7 +153,7 @@ test.describe('OCR Retry Workflow', () => {
}
});
test('should display OCR failure reasons', async ({ adminPage: page }) => {
test('should display OCR failure reasons', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();
@ -185,7 +185,7 @@ test.describe('OCR Retry Workflow', () => {
}
});
test('should filter failed documents by failure type', async ({ adminPage: page }) => {
test('should filter failed documents by failure type', async ({ dynamicAdminPage: page }) => {
await page.goto('/documents');
await helpers.waitForLoadingToComplete();

View File

@ -5,20 +5,20 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Search Functionality', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/search');
// Ensure we have test documents for search functionality
await helpers.ensureTestDocumentsExist();
});
test.skip('should display search interface', async ({ authenticatedPage: page }) => {
test.skip('should display search interface', async ({ dynamicAdminPage: page }) => {
// Check for search components
await expect(page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]')).toBeVisible();
await expect(page.locator('button:has-text("Search"), [data-testid="search-button"]')).toBeVisible();
});
test.skip('should perform basic search', async ({ authenticatedPage: page }) => {
test.skip('should perform basic search', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Search for known OCR content from test images
@ -39,7 +39,7 @@ test.describe('Search Functionality', () => {
});
});
test.skip('should show search suggestions', async ({ authenticatedPage: page }) => {
test.skip('should show search suggestions', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Start typing "Test" to trigger suggestions based on OCR content
@ -51,7 +51,7 @@ test.describe('Search Functionality', () => {
});
});
test.skip('should filter search results', async ({ authenticatedPage: page }) => {
test.skip('should filter search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Search for content that should match multiple test images
@ -76,7 +76,7 @@ test.describe('Search Functionality', () => {
}
});
test.skip('should perform advanced search', async ({ authenticatedPage: page }) => {
test.skip('should perform advanced search', async ({ dynamicAdminPage: page }) => {
// Look for advanced search toggle
const advancedToggle = page.locator('[data-testid="advanced-search"], button:has-text("Advanced"), .advanced-toggle');
@ -103,7 +103,7 @@ test.describe('Search Functionality', () => {
}
});
test.skip('should handle empty search results', async ({ authenticatedPage: page }) => {
test.skip('should handle empty search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Search for something that doesn't exist
@ -118,7 +118,7 @@ test.describe('Search Functionality', () => {
});
});
test.skip('should navigate to document from search results', async ({ authenticatedPage: page }) => {
test.skip('should navigate to document from search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search
@ -137,7 +137,7 @@ test.describe('Search Functionality', () => {
}
});
test.skip('should preserve search state on page reload', async ({ authenticatedPage: page }) => {
test.skip('should preserve search state on page reload', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search
@ -156,7 +156,7 @@ test.describe('Search Functionality', () => {
});
});
test.skip('should sort search results', async ({ authenticatedPage: page }) => {
test.skip('should sort search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search
@ -175,7 +175,7 @@ test.describe('Search Functionality', () => {
}
});
test.skip('should paginate search results', async ({ authenticatedPage: page }) => {
test.skip('should paginate search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search
@ -197,7 +197,7 @@ test.describe('Search Functionality', () => {
}
});
test.skip('should highlight search terms in results', async ({ authenticatedPage: page }) => {
test.skip('should highlight search terms in results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search with specific term
@ -212,7 +212,7 @@ test.describe('Search Functionality', () => {
});
});
test.skip('should clear search results', async ({ authenticatedPage: page }) => {
test.skip('should clear search results', async ({ dynamicAdminPage: page }) => {
const searchInput = page.locator('input[type="search"], input[placeholder*="search" i], [data-testid="search-input"]').first();
// Perform search

View File

@ -5,17 +5,17 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Settings Management', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/settings');
});
test.skip('should display settings interface', async ({ authenticatedPage: page }) => {
test.skip('should display settings interface', async ({ dynamicAdminPage: page }) => {
// Check for settings page components
await expect(page.locator('[data-testid="settings-container"], .settings-page, .settings-form')).toBeVisible();
});
test('should update OCR settings', async ({ authenticatedPage: page }) => {
test('should update OCR settings', async ({ dynamicAdminPage: page }) => {
// Look for OCR settings section
const ocrSection = page.locator('[data-testid="ocr-settings"], .ocr-section, .settings-section:has-text("OCR")');
if (await ocrSection.isVisible()) {
@ -35,7 +35,7 @@ test.describe('Settings Management', () => {
}
});
test('should update watch folder settings', async ({ authenticatedPage: page }) => {
test('should update watch folder settings', async ({ dynamicAdminPage: page }) => {
// Navigate to watch folder section if it's a separate page
const watchFolderNav = page.locator('a[href="/watch-folder"], [data-testid="watch-folder-nav"]');
if (await watchFolderNav.isVisible()) {
@ -67,7 +67,7 @@ test.describe('Settings Management', () => {
}
});
test('should update notification settings', async ({ authenticatedPage: page }) => {
test('should update notification settings', async ({ dynamicAdminPage: page }) => {
const notificationSection = page.locator('[data-testid="notification-settings"], .notification-section, .settings-section:has-text("Notification")');
if (await notificationSection.isVisible()) {
// Enable notifications
@ -96,7 +96,7 @@ test.describe('Settings Management', () => {
}
});
test('should update search settings', async ({ authenticatedPage: page }) => {
test('should update search settings', async ({ dynamicAdminPage: page }) => {
const searchSection = page.locator('[data-testid="search-settings"], .search-section, .settings-section:has-text("Search")');
if (await searchSection.isVisible()) {
// Configure search results per page
@ -120,7 +120,7 @@ test.describe('Settings Management', () => {
}
});
test('should reset settings to defaults', async ({ authenticatedPage: page }) => {
test('should reset settings to defaults', async ({ dynamicAdminPage: page }) => {
// Look for reset button
const resetButton = page.locator('button:has-text("Reset"), button:has-text("Default"), [data-testid="reset-settings"]');
if (await resetButton.isVisible()) {
@ -142,7 +142,7 @@ test.describe('Settings Management', () => {
}
});
test('should validate settings before saving', async ({ authenticatedPage: page }) => {
test('should validate settings before saving', async ({ dynamicAdminPage: page }) => {
// Try to set invalid values
const pathInput = page.locator('input[name="watchPath"], [data-testid="watch-path"]');
if (await pathInput.isVisible()) {
@ -159,7 +159,7 @@ test.describe('Settings Management', () => {
}
});
test('should export settings', async ({ authenticatedPage: page }) => {
test('should export settings', async ({ dynamicAdminPage: page }) => {
const exportButton = page.locator('button:has-text("Export"), [data-testid="export-settings"]');
if (await exportButton.isVisible()) {
// Set up download listener
@ -173,7 +173,7 @@ test.describe('Settings Management', () => {
}
});
test('should import settings', async ({ authenticatedPage: page }) => {
test('should import settings', async ({ dynamicAdminPage: page }) => {
const importButton = page.locator('button:has-text("Import"), [data-testid="import-settings"]');
if (await importButton.isVisible()) {
// Look for file input
@ -202,7 +202,7 @@ test.describe('Settings Management', () => {
}
});
test('should display current system status', async ({ authenticatedPage: page }) => {
test('should display current system status', async ({ dynamicAdminPage: page }) => {
// Look for system status section
const statusSection = page.locator('[data-testid="system-status"], .status-section, .settings-section:has-text("Status")');
if (await statusSection.isVisible()) {
@ -211,7 +211,7 @@ test.describe('Settings Management', () => {
}
});
test('should test OCR functionality', async ({ authenticatedPage: page }) => {
test('should test OCR functionality', async ({ dynamicAdminPage: page }) => {
const ocrSection = page.locator('[data-testid="ocr-settings"], .ocr-section');
if (await ocrSection.isVisible()) {
const testButton = page.locator('button:has-text("Test OCR"), [data-testid="test-ocr"]');
@ -228,7 +228,7 @@ test.describe('Settings Management', () => {
}
});
test('should clear cache', async ({ authenticatedPage: page }) => {
test('should clear cache', async ({ dynamicAdminPage: page }) => {
const clearCacheButton = page.locator('button:has-text("Clear Cache"), [data-testid="clear-cache"]');
if (await clearCacheButton.isVisible()) {
const clearResponse = helpers.waitForApiCall('/api/cache/clear');
@ -240,7 +240,7 @@ test.describe('Settings Management', () => {
}
});
test('should update user profile', async ({ authenticatedPage: page }) => {
test('should update user profile', async ({ dynamicAdminPage: page }) => {
// Look for user profile section
const profileSection = page.locator('[data-testid="profile-settings"], .profile-section, .settings-section:has-text("Profile")');
if (await profileSection.isVisible()) {
@ -265,7 +265,7 @@ test.describe('Settings Management', () => {
}
});
test('should change password', async ({ authenticatedPage: page }) => {
test('should change password', async ({ dynamicAdminPage: page }) => {
const passwordSection = page.locator('[data-testid="password-settings"], .password-section, .settings-section:has-text("Password")');
if (await passwordSection.isVisible()) {
await page.fill('input[name="currentPassword"], [data-testid="current-password"]', 'currentpass');

View File

@ -5,18 +5,18 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('Source Management', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ adminPage }) => {
helpers = new TestHelpers(adminPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/sources');
});
test.skip('should display sources interface', async ({ adminPage: page }) => {
test.skip('should display sources interface', async ({ dynamicAdminPage: page }) => {
// Check for sources page components
await expect(page.locator('[data-testid="sources-list"], .sources-list, .sources-container')).toBeVisible();
await expect(page.locator('button:has-text("Add Source"), [data-testid="add-source"]')).toBeVisible();
});
test.skip('should create a new local folder source', async ({ adminPage: page }) => {
test.skip('should create a new local folder source', async ({ dynamicAdminPage: page }) => {
// Click add source button
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
@ -51,7 +51,7 @@ test.describe('Source Management', () => {
await expect(page.locator(':has-text("Test Local Folder")')).toBeVisible({ timeout: TIMEOUTS.medium });
});
test.skip('should create a new WebDAV source', async ({ adminPage: page }) => {
test.skip('should create a new WebDAV source', async ({ dynamicAdminPage: page }) => {
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal, .source-form')).toBeVisible();
@ -79,7 +79,7 @@ test.describe('Source Management', () => {
await expect(page.locator(':has-text("Test WebDAV")')).toBeVisible({ timeout: TIMEOUTS.medium });
});
test.skip('should create a new S3 source', async ({ adminPage: page }) => {
test.skip('should create a new S3 source', async ({ dynamicAdminPage: page }) => {
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal, .source-form')).toBeVisible();
@ -108,7 +108,7 @@ test.describe('Source Management', () => {
await expect(page.locator(':has-text("Test S3 Bucket")')).toBeVisible({ timeout: TIMEOUTS.medium });
});
test('should edit existing source', async ({ adminPage: page }) => {
test('should edit existing source', async ({ dynamicAdminPage: page }) => {
// Look for existing source to edit
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
@ -138,7 +138,7 @@ test.describe('Source Management', () => {
}
});
test('should delete source', async ({ adminPage: page }) => {
test('should delete source', async ({ dynamicAdminPage: page }) => {
// First wait for sources list to load
await helpers.waitForLoadingToComplete();
@ -298,7 +298,7 @@ test.describe('Source Management', () => {
}
});
test.skip('should start source sync', async ({ adminPage: page }) => {
test.skip('should start source sync', async ({ dynamicAdminPage: page }) => {
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
if (await firstSource.isVisible()) {
@ -319,7 +319,7 @@ test.describe('Source Management', () => {
}
});
test('should stop source sync', async ({ adminPage: page }) => {
test('should stop source sync', async ({ dynamicAdminPage: page }) => {
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
if (await firstSource.isVisible()) {
@ -347,7 +347,7 @@ test.describe('Source Management', () => {
}
});
test('should display source status and statistics', async ({ adminPage: page }) => {
test('should display source status and statistics', async ({ dynamicAdminPage: page }) => {
// First wait for sources list to load
await helpers.waitForLoadingToComplete();
@ -420,7 +420,7 @@ test.describe('Source Management', () => {
}
});
test.skip('should test source connection', async ({ adminPage: page }) => {
test.skip('should test source connection', async ({ dynamicAdminPage: page }) => {
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal')).toBeVisible();
@ -451,7 +451,7 @@ test.describe('Source Management', () => {
}
});
test('should filter sources by type', async ({ adminPage: page }) => {
test('should filter sources by type', async ({ dynamicAdminPage: page }) => {
// Look for filter dropdown
const filterDropdown = page.locator('[data-testid="source-filter"], select[name="filter"], .source-filter');
if (await filterDropdown.isVisible()) {
@ -467,7 +467,7 @@ test.describe('Source Management', () => {
}
});
test('should display sync history', async ({ adminPage: page }) => {
test('should display sync history', async ({ dynamicAdminPage: page }) => {
const firstSource = page.locator('[data-testid="source-item"], .source-item, .source-card').first();
if (await firstSource.isVisible()) {
@ -482,7 +482,7 @@ test.describe('Source Management', () => {
}
});
test.skip('should validate required fields in source creation', async ({ adminPage: page }) => {
test.skip('should validate required fields in source creation', async ({ dynamicAdminPage: page }) => {
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
await expect(page.locator('[data-testid="add-source-form"], .add-source-modal')).toBeVisible();
@ -501,7 +501,7 @@ test.describe('Source Management', () => {
}
});
test('should schedule automatic sync', async ({ adminPage: page }) => {
test('should schedule automatic sync', async ({ dynamicAdminPage: page }) => {
// First wait for sources list to load
await helpers.waitForLoadingToComplete();

View File

@ -57,23 +57,7 @@ export class E2ETestAuthHelper {
if (!response.ok()) {
const errorText = await response.text();
console.warn(`Warning: Failed to create dynamic test user. Status: ${response.status()}, Body: ${errorText}`);
// Fallback to seeded admin user (since no regular user is seeded)
console.log('Falling back to seeded admin user...');
return {
credentials: {
username: 'admin',
email: 'admin@test.com',
password: 'readur2024'
},
userResponse: {
id: 'seeded-admin',
username: 'admin',
email: 'admin@test.com',
role: 'Admin'
}
};
throw new Error(`Failed to create dynamic test user. Status: ${response.status()}, Body: ${errorText}`);
}
const userResponse: TestUserResponse = await response.json();
@ -84,22 +68,7 @@ export class E2ETestAuthHelper {
};
} catch (error) {
console.error('❌ Failed to create E2E test user:', error);
// Fallback to seeded admin user (since no regular user is seeded)
console.log('Falling back to seeded admin user due to error...');
return {
credentials: {
username: 'admin',
email: 'admin@test.com',
password: 'readur2024'
},
userResponse: {
id: 'seeded-admin',
username: 'admin',
email: 'admin@test.com',
role: 'Admin'
}
};
throw error;
}
}
@ -129,23 +98,7 @@ export class E2ETestAuthHelper {
if (!response.ok()) {
const errorText = await response.text();
console.warn(`Warning: Failed to create dynamic admin user. Status: ${response.status()}, Body: ${errorText}`);
// Fallback to seeded admin user
console.log('Falling back to seeded admin user...');
return {
credentials: {
username: 'admin',
email: 'admin@test.com',
password: 'readur2024'
},
userResponse: {
id: 'seeded-admin',
username: 'admin',
email: 'admin@test.com',
role: 'Admin'
}
};
throw new Error(`Failed to create dynamic admin user. Status: ${response.status()}, Body: ${errorText}`);
}
const userResponse: TestUserResponse = await response.json();
@ -156,22 +109,7 @@ export class E2ETestAuthHelper {
};
} catch (error) {
console.error('❌ Failed to create E2E admin user:', error);
// Fallback to seeded admin user
console.log('Falling back to seeded admin user due to error...');
return {
credentials: {
username: 'admin',
email: 'admin@test.com',
password: 'readur2024'
},
userResponse: {
id: 'seeded-admin',
username: 'admin',
email: 'admin@test.com',
role: 'Admin'
}
};
throw error;
}
}

View File

@ -1,7 +1,6 @@
import { TEST_CREDENTIALS } from '../fixtures/auth';
// Test users are now created dynamically via E2ETestAuthHelper
// Invalid credentials for testing error cases
export const TEST_USERS = {
valid: TEST_CREDENTIALS.admin,
invalid: {
username: 'invaliduser',
password: 'wrongpassword'

View File

@ -5,12 +5,12 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('WebDAV Workflow (Dynamic Auth)', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/sources');
});
test.skip('should create and configure WebDAV source with dynamic admin', async ({ authenticatedPage: page }) => {
test.skip('should create and configure WebDAV source with dynamic admin', async ({ dynamicAdminPage: page }) => {
// Increase timeout for this test as WebDAV operations can be slow
test.setTimeout(60000);
@ -352,7 +352,7 @@ test.describe('WebDAV Workflow (Dynamic Auth)', () => {
console.log('✅ WebDAV source creation test completed by authenticated admin');
});
test('should test WebDAV connection with dynamic admin', async ({ authenticatedPage: page }) => {
test('should test WebDAV connection with dynamic admin', async ({ dynamicAdminPage: page }) => {
console.log('Testing WebDAV connection with authenticated admin');
// This test assumes a WebDAV source exists from the previous test or setup

View File

@ -5,12 +5,12 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('WebDAV Workflow', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ authenticatedPage }) => {
helpers = new TestHelpers(authenticatedPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/sources');
});
test.skip('should create and configure WebDAV source', async ({ authenticatedPage: page }) => {
test.skip('should create and configure WebDAV source', async ({ dynamicAdminPage: page }) => {
// Increase timeout for this test as WebDAV operations can be slow
// This addresses the timeout issues with Material-UI Select components
test.setTimeout(60000);
@ -223,7 +223,7 @@ test.describe('WebDAV Workflow', () => {
}
});
test('should test WebDAV connection', async ({ authenticatedPage: page }) => {
test('should test WebDAV connection', async ({ dynamicAdminPage: page }) => {
// This test assumes a WebDAV source exists from the previous test or setup
await page.goto('/sources');
await helpers.waitForLoadingToComplete();
@ -256,7 +256,7 @@ test.describe('WebDAV Workflow', () => {
}
});
test('should initiate WebDAV sync', async ({ authenticatedPage: page }) => {
test('should initiate WebDAV sync', async ({ dynamicAdminPage: page }) => {
await page.goto('/sources');
await helpers.waitForLoadingToComplete();
@ -287,7 +287,7 @@ test.describe('WebDAV Workflow', () => {
}
});
test('should show WebDAV sync history', async ({ authenticatedPage: page }) => {
test('should show WebDAV sync history', async ({ dynamicAdminPage: page }) => {
await page.goto('/sources');
await helpers.waitForLoadingToComplete();
@ -310,7 +310,7 @@ test.describe('WebDAV Workflow', () => {
}
});
test('should handle WebDAV source deletion', async ({ authenticatedPage: page }) => {
test('should handle WebDAV source deletion', async ({ dynamicAdminPage: page }) => {
await page.goto('/sources');
await helpers.waitForLoadingToComplete();

View File

@ -5,8 +5,8 @@ import { TestHelpers } from './utils/test-helpers';
test.describe('WebSocket Sync Progress', () => {
let helpers: TestHelpers;
test.beforeEach(async ({ adminPage }) => {
helpers = new TestHelpers(adminPage);
test.beforeEach(async ({ dynamicAdminPage }) => {
helpers = new TestHelpers(dynamicAdminPage);
await helpers.navigateToPage('/sources');
});
@ -99,7 +99,7 @@ test.describe('WebSocket Sync Progress', () => {
return fallbackElement;
}
test('should establish WebSocket connection for sync progress', async ({ adminPage: page }) => {
test('should establish WebSocket connection for sync progress', async ({ dynamicAdminPage: page }) => {
// Add browser console logging to debug WebSocket connections
const consoleLogs: string[] = [];
page.on('console', msg => {
@ -177,7 +177,7 @@ test.describe('WebSocket Sync Progress', () => {
await expect(progressIndicators).toBeVisible({ timeout: TIMEOUTS.short });
});
test('should handle WebSocket connection errors gracefully', async ({ adminPage: page }) => {
test('should handle WebSocket connection errors gracefully', async ({ dynamicAdminPage: page }) => {
// Mock WebSocket connection failure
await page.route('**/sync/progress/ws**', route => {
route.abort('connectionrefused');
@ -215,7 +215,7 @@ test.describe('WebSocket Sync Progress', () => {
}
});
test('should automatically reconnect on WebSocket disconnection', async ({ adminPage: page }) => {
test('should automatically reconnect on WebSocket disconnection', async ({ dynamicAdminPage: page }) => {
// Create and sync a source
const sourceName = await helpers.createTestSource('Reconnect Test Source', 'webdav');
@ -266,7 +266,7 @@ test.describe('WebSocket Sync Progress', () => {
await expect(finalStatus).toBeVisible({ timeout: TIMEOUTS.medium });
});
test('should display real-time progress updates via WebSocket', async ({ adminPage: page }) => {
test('should display real-time progress updates via WebSocket', async ({ dynamicAdminPage: page }) => {
// Create a source and start sync
const sourceName = await helpers.createTestSource('Progress Updates Test', 'webdav');
@ -303,7 +303,7 @@ test.describe('WebSocket Sync Progress', () => {
await expect(statsLocator).toBeVisible({ timeout: TIMEOUTS.short });
});
test('should handle multiple concurrent WebSocket connections', async ({ adminPage: page }) => {
test('should handle multiple concurrent WebSocket connections', async ({ dynamicAdminPage: page }) => {
// Create multiple sources
const sourceNames = [];
const baseNames = ['Multi Source 1', 'Multi Source 2'];
@ -370,7 +370,7 @@ test.describe('WebSocket Sync Progress', () => {
console.log('Multiple concurrent WebSocket test completed - infrastructure verified');
});
test('should authenticate WebSocket connection with JWT token', async ({ adminPage: page }) => {
test('should authenticate WebSocket connection with JWT token', async ({ dynamicAdminPage: page }) => {
// Check that user has a valid JWT token stored
const tokenInfo = await page.evaluate(() => {
const token = localStorage.getItem('token');
@ -403,7 +403,7 @@ test.describe('WebSocket Sync Progress', () => {
console.log('WebSocket authentication test passed - connection established successfully');
});
test('should handle WebSocket authentication failures', async ({ adminPage: page }) => {
test('should handle WebSocket authentication failures', async ({ dynamicAdminPage: page }) => {
// Mock authentication failure for WebSocket connections
await page.route('**/sync/progress/ws**', route => {
if (route.request().url().includes('token=')) {
@ -476,7 +476,7 @@ test.describe('WebSocket Sync Progress', () => {
}
});
test('should properly clean up WebSocket connections on component unmount', async ({ adminPage: page }) => {
test('should properly clean up WebSocket connections on component unmount', async ({ dynamicAdminPage: page }) => {
// Instead of creating a new source, just use existing sources to test component lifecycle
// This avoids the hanging issue with source creation
@ -514,7 +514,7 @@ test.describe('WebSocket Sync Progress', () => {
console.log('WebSocket cleanup test completed - component lifecycle verified via reload');
});
test('should handle WebSocket message parsing errors', async ({ adminPage: page }) => {
test('should handle WebSocket message parsing errors', async ({ dynamicAdminPage: page }) => {
// Mock WebSocket with malformed messages
await page.addInitScript(() => {
const originalWebSocket = window.WebSocket;
@ -561,7 +561,7 @@ test.describe('WebSocket Sync Progress', () => {
await expect(page.locator('body')).toBeVisible();
});
test('should display WebSocket connection status indicators', async ({ adminPage: page }) => {
test('should display WebSocket connection status indicators', async ({ dynamicAdminPage: page }) => {
// Create and sync a source
const sourceName = await helpers.createTestSource('Status Test Source', 'webdav');
@ -586,7 +586,7 @@ test.describe('WebSocket Sync Progress', () => {
await expect(statusChip).toHaveClass(/MuiChip-root/);
});
test('should support WebSocket connection health monitoring', async ({ adminPage: page }) => {
test('should support WebSocket connection health monitoring', async ({ dynamicAdminPage: page }) => {
// This test verifies that the WebSocket connection monitors connection health
let heartbeatReceived = false;
@ -651,7 +651,7 @@ test.describe('WebSocket Sync Progress - Cross-browser Compatibility', () => {
await syncCard.click();
}
test('should work in different browser engines', async ({ adminPage: page }) => {
test('should work in different browser engines', async ({ dynamicAdminPage: page }) => {
// This test would run across different browsers (Chrome, Firefox, Safari)
// The test framework should handle this automatically