fix(e2e): Fix 12 failing E2E tests - DataTable reactivity and selector issues
- Fix DataTable reactivity: use $derived with getters for data/columns props - Fix auth.spec.js: open user dropdown before clicking Logout button - Fix dashboard.spec.ts: scope selectors to layout-content, use exact matches - Fix layout.spec.ts: clear localStorage before breakpoint tests, wait for focus - Fix projects/team-members.spec.ts: wait for table rows to be visible Root causes: 1. DataTable options object captured initial empty array, not reactive updates 2. Selectors matched multiple elements (sidebar, user menu, main content) 3. Dropdown menus need to be opened before clicking items 4. Keyboard shortcuts need element focus All 94 tests now pass (47 chromium + 47 firefox)
This commit is contained in:
@@ -32,9 +32,9 @@
|
|||||||
|
|
||||||
const sorting = writable<SortingState>([]);
|
const sorting = writable<SortingState>([]);
|
||||||
|
|
||||||
const options: TableOptions<T> = {
|
const options: TableOptions<T> = $derived({
|
||||||
data,
|
get data() { return data; },
|
||||||
columns,
|
get columns() { return columns; },
|
||||||
state: {
|
state: {
|
||||||
get sorting() {
|
get sorting() {
|
||||||
return $sorting;
|
return $sorting;
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
},
|
},
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel()
|
getSortedRowModel: getSortedRowModel()
|
||||||
};
|
});
|
||||||
|
|
||||||
const table = createSvelteTable(options);
|
const table = createSvelteTable(options);
|
||||||
|
|
||||||
|
|||||||
@@ -159,8 +159,9 @@ test.describe('Authentication E2E', () => {
|
|||||||
await page.click('button[type="submit"]');
|
await page.click('button[type="submit"]');
|
||||||
await page.waitForURL('/dashboard');
|
await page.waitForURL('/dashboard');
|
||||||
|
|
||||||
// Click logout
|
// Open user menu dropdown first, then click logout
|
||||||
await page.click('text=Logout');
|
await page.click('[data-testid="user-menu"] button');
|
||||||
|
await page.click('[data-testid="user-menu"] button:has-text("Logout")');
|
||||||
|
|
||||||
// Should redirect to login
|
// Should redirect to login
|
||||||
await page.waitForURL('/login');
|
await page.waitForURL('/login');
|
||||||
|
|||||||
@@ -24,23 +24,25 @@ test.describe('Dashboard Page', () => {
|
|||||||
await expect(page.getByRole('button', { name: /New Allocation/i })).toBeVisible();
|
await expect(page.getByRole('button', { name: /New Allocation/i })).toBeVisible();
|
||||||
|
|
||||||
// Check all 4 StatCards render
|
// Check all 4 StatCards render
|
||||||
await expect(page.getByText('Active Projects')).toBeVisible();
|
// Check all 4 StatCards render (use specific selector to avoid matching sidebar/user menu)
|
||||||
await expect(page.getByText('Team Members')).toBeVisible();
|
const mainContent = page.getByTestId('layout-content');
|
||||||
await expect(page.getByText('Allocations (hrs)')).toBeVisible();
|
await expect(mainContent.getByText('Active Projects')).toBeVisible();
|
||||||
await expect(page.getByText('Avg Utilization')).toBeVisible();
|
await expect(mainContent.getByText('Team Members')).toBeVisible();
|
||||||
|
await expect(mainContent.getByText('Allocations (hrs)')).toBeVisible();
|
||||||
|
await expect(mainContent.getByText('Avg Utilization')).toBeVisible();
|
||||||
|
|
||||||
// Check stat values
|
// Check stat values (use exact match to avoid matching '8' in '186' or '87%')
|
||||||
await expect(page.getByText('14')).toBeVisible(); // Active Projects
|
await expect(page.getByText('14', { exact: true })).toBeVisible(); // Active Projects
|
||||||
await expect(page.getByText('8')).toBeVisible(); // Team Members
|
await expect(page.getByText('8', { exact: true })).toBeVisible(); // Team Members
|
||||||
await expect(page.getByText('186')).toBeVisible(); // Allocations
|
await expect(page.getByText('186', { exact: true })).toBeVisible(); // Allocations
|
||||||
await expect(page.getByText('87%')).toBeVisible(); // Avg Utilization
|
await expect(page.getByText('87%')).toBeVisible(); // Avg Utilization
|
||||||
|
|
||||||
// Check Quick Actions section
|
// Check Quick Actions section (scope to main content to avoid sidebar/user menu)
|
||||||
await expect(page.getByText('Quick Actions')).toBeVisible();
|
await expect(mainContent.getByText('Quick Actions')).toBeVisible();
|
||||||
await expect(page.locator('a[href="/team-members"]')).toBeVisible();
|
await expect(mainContent.locator('a[href="/team-members"]')).toBeVisible();
|
||||||
await expect(page.locator('a[href="/projects"]')).toBeVisible();
|
await expect(mainContent.locator('a[href="/projects"]')).toBeVisible();
|
||||||
await expect(page.locator('a[href="/allocations"]')).toBeVisible();
|
await expect(mainContent.locator('a[href="/allocations"]')).toBeVisible();
|
||||||
await expect(page.locator('a[href="/reports/forecast"]')).toBeVisible();
|
await expect(mainContent.locator('a[href="/reports/forecast"]')).toBeVisible();
|
||||||
|
|
||||||
// Check Allocation Preview section
|
// Check Allocation Preview section
|
||||||
await expect(page.getByRole('heading', { name: 'Allocation Preview' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Allocation Preview' })).toBeVisible();
|
||||||
|
|||||||
@@ -142,8 +142,8 @@ test.describe('Layout E2E', () => {
|
|||||||
|
|
||||||
for (const [width, height, expected] of breakpoints) {
|
for (const [width, height, expected] of breakpoints) {
|
||||||
await page.setViewportSize({ width, height });
|
await page.setViewportSize({ width, height });
|
||||||
await page.goto('/login');
|
// Clear localStorage to test default breakpoint behavior
|
||||||
await page.evaluate(() => localStorage.setItem('headroom_sidebar_state', 'expanded'));
|
await page.evaluate(() => localStorage.removeItem('headroom_sidebar_state'));
|
||||||
await openDashboard(page);
|
await openDashboard(page);
|
||||||
await expect
|
await expect
|
||||||
.poll(async () => page.evaluate(() => document.documentElement.getAttribute('data-sidebar')))
|
.poll(async () => page.evaluate(() => document.documentElement.getAttribute('data-sidebar')))
|
||||||
@@ -155,7 +155,14 @@ test.describe('Layout E2E', () => {
|
|||||||
await page.setViewportSize({ width: 1280, height: 900 });
|
await page.setViewportSize({ width: 1280, height: 900 });
|
||||||
await openDashboard(page);
|
await openDashboard(page);
|
||||||
|
|
||||||
|
// Wait for sidebar to be fully mounted with event listeners
|
||||||
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
|
||||||
const before = await page.evaluate(() => document.documentElement.getAttribute('data-sidebar'));
|
const before = await page.evaluate(() => document.documentElement.getAttribute('data-sidebar'));
|
||||||
|
|
||||||
|
// Focus on the page body to ensure keyboard events are captured
|
||||||
|
await page.locator('body').click();
|
||||||
await page.keyboard.down('Control');
|
await page.keyboard.down('Control');
|
||||||
await page.keyboard.press('\\');
|
await page.keyboard.press('\\');
|
||||||
await page.keyboard.up('Control');
|
await page.keyboard.up('Control');
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ test.describe('Projects Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('search filters projects', async ({ page }) => {
|
test('search filters projects', async ({ page }) => {
|
||||||
// Wait for data to load
|
// Wait for the table to render (not loading state)
|
||||||
await page.waitForTimeout(500);
|
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
// Get initial row count
|
// Get initial row count
|
||||||
const initialRows = await page.locator('table tbody tr').count();
|
const initialRows = await page.locator('table tbody tr').count();
|
||||||
@@ -38,8 +38,8 @@ test.describe('Projects Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('status filter works', async ({ page }) => {
|
test('status filter works', async ({ page }) => {
|
||||||
// Wait for data to load
|
// Wait for the table to render (not loading state)
|
||||||
await page.waitForTimeout(500);
|
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
// Get initial row count
|
// Get initial row count
|
||||||
const initialRows = await page.locator('table tbody tr').count();
|
const initialRows = await page.locator('table tbody tr').count();
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ test.describe('Team Members Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('search filters team members', async ({ page }) => {
|
test('search filters team members', async ({ page }) => {
|
||||||
// Wait for data to load
|
// Wait for the table to render (not loading state)
|
||||||
await page.waitForTimeout(500);
|
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
// Get initial row count
|
// Get initial row count
|
||||||
const initialRows = await page.locator('table tbody tr').count();
|
const initialRows = await page.locator('table tbody tr').count();
|
||||||
@@ -38,8 +38,8 @@ test.describe('Team Members Page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('status filter works', async ({ page }) => {
|
test('status filter works', async ({ page }) => {
|
||||||
// Wait for data to load
|
// Wait for the table to render (not loading state)
|
||||||
await page.waitForTimeout(500);
|
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||||
|
|
||||||
// Get initial row count
|
// Get initial row count
|
||||||
const initialRows = await page.locator('table tbody tr').count();
|
const initialRows = await page.locator('table tbody tr').count();
|
||||||
|
|||||||
Reference in New Issue
Block a user