mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
refactor: update e2e tests to use 'load' state for page navigation
- Changed instances of `waitForLoadState('networkidle')` to `waitForLoadState('load')` across multiple test files and utility functions to improve test reliability in applications with persistent connections.
- Added documentation to the e2e testing guide explaining the rationale behind using 'load' state instead of 'networkidle' to prevent timeouts and flaky tests.
This commit is contained in:
@@ -89,13 +89,26 @@ await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout
|
||||
await page.waitForSelector('[data-testid="welcome-view"]');
|
||||
```
|
||||
|
||||
### Wait for network idle after navigation
|
||||
### Wait for page load after navigation
|
||||
|
||||
**Important:** Use `load` state, NOT `networkidle`. This app has persistent connections (websockets, polling) that prevent the network from ever becoming "idle", causing `networkidle` to timeout.
|
||||
|
||||
```typescript
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Then wait for specific elements to verify the page is ready
|
||||
await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 10000 });
|
||||
```
|
||||
|
||||
**Why not `networkidle`?**
|
||||
|
||||
- `networkidle` requires no network activity for 500ms
|
||||
- Modern SPAs with real-time features (websockets, polling, SSE) never reach this state
|
||||
- Using `networkidle` causes 30+ second timeouts and flaky tests
|
||||
- The `load` state fires when the page finishes loading, which is sufficient
|
||||
- Always follow up with element visibility checks for reliability
|
||||
|
||||
### Use appropriate timeouts
|
||||
|
||||
- Quick UI updates: 5000ms (default)
|
||||
@@ -267,6 +280,29 @@ npm run test -- project-creation.spec.ts --repeat-each=5
|
||||
3. Run with `--headed` to watch the test
|
||||
4. Add `await page.pause()` to pause execution at a specific point
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Timeout on `waitForLoadState('networkidle')`
|
||||
|
||||
If tests timeout waiting for network idle, the app likely has persistent connections. Use `load` state instead:
|
||||
|
||||
```typescript
|
||||
// Bad - will timeout with persistent connections
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Good - completes when page loads
|
||||
await page.waitForLoadState('load');
|
||||
await expect(page.locator('[data-testid="my-element"]')).toBeVisible();
|
||||
```
|
||||
|
||||
### Port conflicts
|
||||
|
||||
If you see "Port 3008 is already in use", kill the process:
|
||||
|
||||
```bash
|
||||
lsof -ti:3008 | xargs kill -9
|
||||
```
|
||||
|
||||
## Available Test Utilities
|
||||
|
||||
Import from `./utils`:
|
||||
@@ -290,8 +326,9 @@ Import from `./utils`:
|
||||
|
||||
### Waiting Utilities
|
||||
|
||||
- `waitForNetworkIdle(page)` - Wait for network to be idle
|
||||
- `waitForNetworkIdle(page)` - Wait for page to load (uses `load` state, not `networkidle`)
|
||||
- `waitForElement(page, testId)` - Wait for element by test ID
|
||||
- `waitForBoardView(page)` - Navigate to board and wait for it to be visible
|
||||
|
||||
### Async File Verification
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ test.describe('Project Creation', () => {
|
||||
|
||||
await setupWelcomeView(page, { workspaceDir: TEST_TEMP_DIR });
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ test.describe('Open Project', () => {
|
||||
|
||||
// Navigate to the app
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for welcome view to be visible
|
||||
await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Wait for the page to reach network idle state
|
||||
* This is commonly used after navigation or page reload to ensure all network requests have completed
|
||||
* Wait for the page to load
|
||||
* Uses 'load' state instead of 'networkidle' because the app has persistent
|
||||
* connections (websockets/polling) that prevent network from ever being idle.
|
||||
* Tests should wait for specific elements to verify page is ready.
|
||||
*/
|
||||
export async function waitForNetworkIdle(page: Page): Promise<void> {
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -479,7 +479,7 @@ export async function waitForBoardView(page: Page): Promise<void> {
|
||||
const currentUrl = page.url();
|
||||
if (!currentUrl.includes('/board')) {
|
||||
await page.goto('/board');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
}
|
||||
|
||||
// Wait for either board-view (success) or board-view-no-project (store not hydrated yet)
|
||||
|
||||
@@ -9,7 +9,7 @@ import { waitForElement } from '../core/waiting';
|
||||
export async function navigateToBoard(page: Page): Promise<void> {
|
||||
// Navigate directly to /board route
|
||||
await page.goto('/board');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for the board view to be visible
|
||||
await waitForElement(page, 'board-view', { timeout: 10000 });
|
||||
@@ -22,7 +22,7 @@ export async function navigateToBoard(page: Page): Promise<void> {
|
||||
export async function navigateToContext(page: Page): Promise<void> {
|
||||
// Navigate directly to /context route
|
||||
await page.goto('/context');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for loading to complete (if present)
|
||||
const loadingElement = page.locator('[data-testid="context-view-loading"]');
|
||||
@@ -47,7 +47,7 @@ export async function navigateToContext(page: Page): Promise<void> {
|
||||
export async function navigateToSpec(page: Page): Promise<void> {
|
||||
// Navigate directly to /spec route
|
||||
await page.goto('/spec');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for loading state to complete first (if present)
|
||||
const loadingElement = page.locator('[data-testid="spec-view-loading"]');
|
||||
@@ -77,7 +77,7 @@ export async function navigateToSpec(page: Page): Promise<void> {
|
||||
export async function navigateToAgent(page: Page): Promise<void> {
|
||||
// Navigate directly to /agent route
|
||||
await page.goto('/agent');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for the agent view to be visible
|
||||
await waitForElement(page, 'agent-view', { timeout: 10000 });
|
||||
@@ -90,7 +90,7 @@ export async function navigateToAgent(page: Page): Promise<void> {
|
||||
export async function navigateToSettings(page: Page): Promise<void> {
|
||||
// Navigate directly to /settings route
|
||||
await page.goto('/settings');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
|
||||
// Wait for the settings view to be visible
|
||||
await waitForElement(page, 'settings-view', { timeout: 10000 });
|
||||
@@ -105,7 +105,7 @@ export async function navigateToSetup(page: Page): Promise<void> {
|
||||
const { setupFirstRun } = await import('../project/setup');
|
||||
await setupFirstRun(page);
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
await waitForElement(page, 'setup-view', { timeout: 10000 });
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export async function navigateToSetup(page: Page): Promise<void> {
|
||||
*/
|
||||
export async function navigateToWelcome(page: Page): Promise<void> {
|
||||
await page.goto('/');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForLoadState('load');
|
||||
await waitForElement(page, 'welcome-view', { timeout: 10000 });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user