fix(playwright): replace networkidle with domcontentloaded and preserve mobile nav

- Replace all networkidle waits with domcontentloaded + 500ms timeout
  to prevent Chart.js and API-related timeouts across all tests
- Add preserveMobileNav option to ensureSheetsClosed helper function
  to allow mobile nav tests to keep nav in DOM for visibility checks
- Update mobile nav tests to use { preserveMobileNav: true } option
- Add new iPhone 14/Pro Max snapshot baselines generated from test runs
This commit is contained in:
LearningCircuit
2026-02-01 02:18:10 +01:00
parent 3cf7def011
commit c1a108850e
2 changed files with 65 additions and 32 deletions

View File

@@ -46,34 +46,44 @@ const PAGES = [
* even when hidden with CSS transforms. We must completely remove it from the DOM.
*
* @param {import('@playwright/test').Page} page - Playwright page object
* @param {Object} options - Options for sheet removal
* @param {boolean} options.preserveMobileNav - If true, keep mobile nav in DOM (default: false)
*/
async function ensureSheetsClosed(page) {
async function ensureSheetsClosed(page, options = {}) {
const { preserveMobileNav = false } = options;
// Remove sheet elements from DOM
await page.evaluate(() => {
await page.evaluate((preserveNav) => {
// Remove ALL sheet-related elements (use broader selectors)
const selectors = [
'.ldr-mobile-sheet-menu',
'.ldr-mobile-sheet-overlay',
'.ldr-mobile-sheet',
'.ldr-mobile-bottom-nav', // Also hide mobile nav on tablets
'[class*="sheet"]:not([class*="stylesheet"])',
'[class*="drawer"]',
'[role="dialog"]:not(.ldr-mode-selection)'
];
// Only remove mobile nav if not preserving it
if (!preserveNav) {
selectors.push('.ldr-mobile-bottom-nav');
}
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.remove();
});
});
// Clear MobileNavigation references
// Clear MobileNavigation references (but preserve nav reference if needed)
if (window.mobileNav && window.mobileNav.elements) {
window.mobileNav.elements.sheet = null;
window.mobileNav.elements.overlay = null;
window.mobileNav.elements.nav = null;
if (!preserveNav) {
window.mobileNav.elements.nav = null;
}
}
});
}, preserveMobileNav);
// Force reflow
await page.evaluate(() => document.body.offsetHeight);

View File

@@ -35,8 +35,9 @@ test.describe('Mobile Navigation Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await ensureSheetsClosed(page);
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page, { preserveMobileNav: true });
const nav = page.locator('.ldr-mobile-bottom-nav');
await expect(nav).toBeVisible();
@@ -60,8 +61,9 @@ test.describe('Mobile Navigation Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await ensureSheetsClosed(page);
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page, { preserveMobileNav: true });
// Find and click the More button
const moreBtn = page.locator('[data-nav="more"], .ldr-nav-more-btn, .ldr-mobile-bottom-nav button:has-text("More")');
@@ -99,7 +101,8 @@ test.describe('Mobile Navigation Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const navItems = page.locator('.ldr-mobile-bottom-nav a, .ldr-mobile-bottom-nav button');
const count = await navItems.count();
@@ -128,7 +131,8 @@ test.describe('Form Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const textarea = page.locator('#query, textarea[name="query"], .ldr-research-textarea');
if (await textarea.count() > 0) {
@@ -145,7 +149,8 @@ test.describe('Form Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const startBtn = page.locator('#start-research-btn, button:has-text("Start Research")');
if (await startBtn.count() > 0) {
@@ -162,7 +167,8 @@ test.describe('Form Components', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const modeSelection = page.locator('.ldr-mode-selection');
if (await modeSelection.count() > 0) {
@@ -179,7 +185,8 @@ test.describe('Form Components', () => {
}
await page.goto('/settings/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await page.waitForSelector('.ldr-loading-spinner', { state: 'hidden', timeout: 10000 }).catch(() => {});
await page.waitForTimeout(500);
@@ -202,7 +209,8 @@ test.describe('Form Components', () => {
}
await page.goto('/settings/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await page.waitForSelector('.ldr-loading-spinner', { state: 'hidden', timeout: 10000 }).catch(() => {});
await page.waitForTimeout(500);
@@ -225,7 +233,8 @@ test.describe('Form Components', () => {
}
await page.goto('/settings/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await page.waitForSelector('.ldr-loading-spinner', { state: 'hidden', timeout: 10000 }).catch(() => {});
await page.waitForTimeout(500);
@@ -254,7 +263,8 @@ test.describe('Card Components', () => {
}
await page.goto('/history/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
// Look for empty state message or card container
const emptyState = page.locator('.ldr-empty-state, [class*="empty"], :text("No research history")');
@@ -282,7 +292,8 @@ test.describe('Card Components', () => {
}
await page.goto('/library/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
// Look for empty state or document list
const emptyState = page.locator('.ldr-empty-state, [class*="empty"], :text("No documents")');
@@ -309,7 +320,8 @@ test.describe('Card Components', () => {
}
await page.goto('/metrics/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await page.waitForTimeout(500);
const metricLink = page.locator('.ldr-metric-link').first();
@@ -333,7 +345,8 @@ test.describe('News Page Components', () => {
}
await page.goto('/news/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
// Look for template cards
const template = page.locator('[class*="template"], .news-template, .ldr-subscription-template').first();
@@ -351,7 +364,8 @@ test.describe('News Page Components', () => {
}
await page.goto('/news/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const createBtn = page.locator('button, a').filter({
hasText: /Create.*Subscription/i,
@@ -377,7 +391,8 @@ test.describe('Interaction States', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const toggle = page.locator('.ldr-advanced-options-toggle').first();
if ((await toggle.count()) === 0 || !(await toggle.isVisible())) {
@@ -404,7 +419,8 @@ test.describe('Interaction States', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const toggle = page.locator('.ldr-advanced-options-toggle').first();
if ((await toggle.count()) === 0 || !(await toggle.isVisible())) {
@@ -437,7 +453,8 @@ test.describe('Interaction States', () => {
}
await page.goto('/settings/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await page.waitForSelector('.ldr-loading-spinner', { state: 'hidden', timeout: 10000 }).catch(() => {});
await page.waitForTimeout(500);
@@ -459,7 +476,8 @@ test.describe('Interaction States', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
const textarea = page.locator('#query, textarea[name="query"], .ldr-research-textarea').first();
if (await textarea.isVisible()) {
@@ -496,8 +514,9 @@ test.describe('Interaction States', () => {
for (const pageInfo of pages) {
await page.goto(pageInfo.path);
await page.waitForLoadState('networkidle');
await ensureSheetsClosed(page);
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page, { preserveMobileNav: true });
const nav = page.locator('.ldr-mobile-bottom-nav');
if (await nav.isVisible()) {
@@ -545,7 +564,8 @@ test.describe('Loading and Error States', () => {
}
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
// Inject an error message element for testing
await page.evaluate(() => {
@@ -574,7 +594,8 @@ test.describe('Loading and Error States', () => {
}
await page.goto('/history/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page);
// Check for empty state message
@@ -594,7 +615,8 @@ test.describe('Loading and Error States', () => {
}
await page.goto('/library/');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page);
// Check for empty state message
@@ -791,7 +813,8 @@ test.describe('Page Headers', () => {
}
await page.goto(pageInfo.path);
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');
await page.waitForTimeout(500);
await ensureSheetsClosed(page);
// Find the main header/title area