There are several critical bottlenecks in your Playwright setup. The current configuration relies heavily on explicit waits (waitForTimeout) and serial execution (workers: 1), which defeats Playwright's auto-waiting architecture and dramatically slows down feedback loops.
Here is the prioritized technical plan to optimize your test suite speed:
1. Remove Hard Waits (Anti-Pattern Removal)
Your tests contain numerous await page.waitForTimeout(1000) calls. These add guaranteed latency regardless of actual application state. Replace them with Auto-Retrying Assertions.
Refactor Example (tests/playwright/comprehensive-assessment.spec.ts):
// 🔴 BAD: Hard wait
await page.fill('input', 'value');
await page.waitForTimeout(1000); // 1s wait even if UI is ready in 50ms
await expect(page).toHaveScreenshot(...);
// 🟢 GOOD: Event-driven wait
await page.fill('input', 'value');
// Wait for specific UI state indicative of "ready"
await expect(page.locator('text=Saved')).toBeVisible();
// Or simply let screenshot assertion handle the wait (it auto-retries)
await expect(page).toHaveScreenshot(...);
2. Optimize Configuration for "Fail Fast"
Your current config forces 1 worker and likely uses default high timeouts. We need to enable parallelism and reduce timeouts to identify broken selectors quickly rather than hanging.
Update playwright.config.ts:
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
// ... existing config ...
// ⚡ OPTIMIZATION 1: Enable Parallelism
// Use 'undefined' to let Playwright use available CPU cores, or set to 50%
workers: process.env.CI ? 2 : undefined,
fullyParallel: true, // Allow tests within the same file to run in parallel
// ⚡ OPTIMIZATION 2: Fail Fast Strategy
timeout: 30 * 1000, // Global test timeout (30s is usually enough for unit-like E2E)
expect: {
timeout: 5000, // Assertions fail after 5s instead of default 5s (checks generic expects)
toHaveScreenshot: {
maxDiffPixelRatio: 0.1,
// If screenshot is missing or mismatch, fail faster than default
timeout: 5000,
},
},
use: {
// ... existing config ...
// ⚡ OPTIMIZATION 3: Reduce Action Timeout
// Fails clicks/fills after 10s if element isn't found
actionTimeout: 10000,
navigationTimeout: 15000,
},
// ...
})
3. Refactor Test Helpers to be Non-Blocking
The test-helpers.ts file introduces artificial delays.
Update tests/playwright/test-helpers.ts:
// ... imports
// ⚡ OPTIMIZATION: Remove try-catch-wait logic
export const waitForPageReady = async (page: Page) => {
// Wait for the specific signal OR a critical UI element
// Does not sleep arbitrarily if the signal is missing
try {
await page.waitForFunction(() => window.__TEST_READY__ === true, { timeout: 3000 });
} catch (e) {
// Fallback: If custom signal fails, wait for a known stable element instead of sleeping
// e.g., await page.waitForSelector('main', { state: 'visible' });
console.warn('__TEST_READY__ signal not found, proceeding with UI check');
}
}
export const replaceIframeWithStableWorkout = async (page: Page) => {
await page.evaluate(() => {
// ... existing iframe replacement logic ...
})
// ⚡ OPTIMIZATION: Wait for the DOM update, not a timer
await page.waitForSelector('iframe[src^="data:text/html"]', { state: 'attached', timeout: 2000 });
}
4. Optimize comprehensive-assessment.spec.ts loops
The looping test for zones (Test Different HR Zones) accumulates delays.
Optimization:
Remove the explicit waits inside the loop. Trust waitForSelector to detect the change.
// Inside the loop in comprehensive-assessment.spec.ts
for (const [index, zone] of zones.entries()) {
// ... fill inputs ...
await mockTab.getByRole('button', { name: zone.zone }).click()
// 🔴 REMOVE: await page.waitForTimeout(...)
// 🟢 RELY ON: Specific predicate
// This waits exactly as long as needed for the text to appear
await dashboardTab.waitForSelector(`text=${zone.bpm} BPM`, { state: 'visible' });
await replaceIframeWithStableWorkout(dashboardTab)
await expect(dashboardTab).toHaveScreenshot(...)
}
5. Fix "Waiting for Label That Will Never Exist"
If you have optional elements (like "Login with Spotify" which might or might not be there), do not use standard await expect(...).
Pattern for Optional Elements:
const spotifyButton = page.locator('text=Login with Spotify');
// ⚡ Check visibility immediately without waiting 30s
if (await spotifyButton.isVisible({ timeout: 500 })) {
await replaceIframeWithStableWorkout(page);
await expect(page).toHaveScreenshot(...);
}
6. Action Item
Apply the Configuration Changes in playwright.config.ts first. This usually yields a 2-3x speedup by enabling parallelism.
# Verify the speed improvement
npx playwright test
There are several critical bottlenecks in your Playwright setup. The current configuration relies heavily on explicit waits (
waitForTimeout) and serial execution (workers: 1), which defeats Playwright's auto-waiting architecture and dramatically slows down feedback loops.Here is the prioritized technical plan to optimize your test suite speed:
1. Remove Hard Waits (Anti-Pattern Removal)
Your tests contain numerous
await page.waitForTimeout(1000)calls. These add guaranteed latency regardless of actual application state. Replace them with Auto-Retrying Assertions.Refactor Example (
tests/playwright/comprehensive-assessment.spec.ts):2. Optimize Configuration for "Fail Fast"
Your current config forces 1 worker and likely uses default high timeouts. We need to enable parallelism and reduce timeouts to identify broken selectors quickly rather than hanging.
Update
playwright.config.ts:3. Refactor Test Helpers to be Non-Blocking
The
test-helpers.tsfile introduces artificial delays.Update
tests/playwright/test-helpers.ts:4. Optimize
comprehensive-assessment.spec.tsloopsThe looping test for zones (
Test Different HR Zones) accumulates delays.Optimization:
Remove the explicit waits inside the loop. Trust
waitForSelectorto detect the change.5. Fix "Waiting for Label That Will Never Exist"
If you have optional elements (like "Login with Spotify" which might or might not be there), do not use standard
await expect(...).Pattern for Optional Elements:
6. Action Item
Apply the Configuration Changes in
playwright.config.tsfirst. This usually yields a 2-3x speedup by enabling parallelism.