Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
debugging/error-testing.md
1# Error & Edge Case Testing23## Table of Contents451. [Error Boundaries](#error-boundaries)62. [Network Failures](#network-failures)73. [Offline Testing](#offline-testing)84. [Loading States](#loading-states)95. [Form Validation](#form-validation)1011## Error Boundaries1213### Test Component Errors1415```typescript16test("error boundary catches component error", async ({ page }) => {17// Trigger error via mock18await page.route("**/api/user", (route) => {19route.fulfill({20json: null, // Will cause component to throw21});22});2324await page.goto("/profile");2526// Error boundary should render fallback27await expect(page.getByText("Something went wrong")).toBeVisible();28await expect(page.getByRole("button", { name: "Try Again" })).toBeVisible();29});30```3132### Test Error Recovery3334```typescript35test("recover from error state", async ({ page }) => {36let requestCount = 0;3738await page.route("**/api/data", (route) => {39requestCount++;40if (requestCount === 1) {41return route.fulfill({ status: 500 });42}43return route.fulfill({44json: { data: "success" },45});46});4748await page.goto("/dashboard");4950// Error state51await expect(page.getByText("Failed to load")).toBeVisible();5253// Retry54await page.getByRole("button", { name: "Retry" }).click();5556// Success state57await expect(page.getByText("success")).toBeVisible();58});59```6061### Test JavaScript Errors6263```typescript64test("handles runtime error gracefully", async ({ page }) => {65const errors: string[] = [];6667page.on("pageerror", (error) => {68errors.push(error.message);69});7071await page.goto("/buggy-page");7273// App should still be functional despite error74await expect(page.getByRole("navigation")).toBeVisible();7576// Error was logged77expect(errors.length).toBeGreaterThan(0);78});79```8081## Network Failures8283### Test API Errors8485```typescript86test.describe("API error handling", () => {87const errorCodes = [400, 401, 403, 404, 500, 502, 503];8889for (const status of errorCodes) {90test(`handles ${status} error`, async ({ page }) => {91await page.route("**/api/data", (route) =>92route.fulfill({93status,94json: { error: `Error ${status}` },95}),96);9798await page.goto("/dashboard");99100// Appropriate error message shown101await expect(page.getByRole("alert")).toBeVisible();102});103}104});105```106107### Test Timeout108109```typescript110test("handles request timeout", async ({ page }) => {111await page.route("**/api/slow", async (route) => {112// Never respond - simulates timeout113await new Promise(() => {});114});115116await page.goto("/slow-page");117118// Should show timeout message (app should have its own timeout)119await expect(page.getByText("Request timed out")).toBeVisible({120timeout: 15000,121});122});123```124125### Test Connection Reset126127```typescript128test("handles connection failure", async ({ page }) => {129await page.route("**/api/data", (route) => {130route.abort("connectionfailed");131});132133await page.goto("/dashboard");134135await expect(page.getByText("Connection failed")).toBeVisible();136await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();137});138```139140### Test Mid-Request Failure141142```typescript143test("handles failure during request", async ({ page }) => {144let requestStarted = false;145146await page.route("**/api/upload", async (route) => {147requestStarted = true;148// Abort after small delay (mid-request)149await new Promise((resolve) => setTimeout(resolve, 500));150route.abort("failed");151});152153await page.goto("/upload");154await page.getByLabel("File").setInputFiles("./fixtures/large-file.pdf");155await page.getByRole("button", { name: "Upload" }).click();156157// Should show failure, not hang158await expect(page.getByText("Upload failed")).toBeVisible();159expect(requestStarted).toBe(true);160});161```162163## Offline Testing164165This section covers **unexpected network failures** and error recovery. For **offline-first apps (PWAs)** with service workers, caching, and background sync, see [service-workers.md](service-workers.md#offline-testing).166167### Go Offline During Session168169```typescript170test("handles going offline", async ({ page, context }) => {171await page.goto("/dashboard");172await expect(page.getByTestId("data")).toBeVisible();173174// Go offline unexpectedly175await context.setOffline(true);176177// Try to refresh data178await page.getByRole("button", { name: "Refresh" }).click();179180// Should show offline indicator181await expect(page.getByText("You're offline")).toBeVisible();182183// Go back online184await context.setOffline(false);185186// Should recover187await page.getByRole("button", { name: "Refresh" }).click();188await expect(page.getByText("You're offline")).toBeHidden();189});190```191192### Test Network Recovery193194```typescript195test("recovers gracefully when connection returns", async ({196page,197context,198}) => {199await page.goto("/dashboard");200201// Simulate connection drop202await context.setOffline(true);203204// App should show degraded state205await expect(page.getByRole("alert")).toContainText(/offline|connection/i);206207// Connection restored208await context.setOffline(false);209210// Retry should work211await page.getByRole("button", { name: "Retry" }).click();212await expect(page.getByTestId("data")).toBeVisible();213});214```215216## Loading States217218### Test Skeleton Loaders219220```typescript221test("shows skeleton during load", async ({ page }) => {222// Add delay to API response223await page.route("**/api/posts", async (route) => {224await new Promise((resolve) => setTimeout(resolve, 1000));225route.fulfill({226json: [{ id: 1, title: "Post 1" }],227});228});229230await page.goto("/posts");231232// Skeleton should appear immediately233await expect(page.getByTestId("skeleton")).toBeVisible();234235// Then content replaces skeleton236await expect(page.getByText("Post 1")).toBeVisible();237await expect(page.getByTestId("skeleton")).toBeHidden();238});239```240241### Test Loading Indicators242243```typescript244test("shows loading state for actions", async ({ page }) => {245await page.route("**/api/save", async (route) => {246await new Promise((resolve) => setTimeout(resolve, 500));247route.fulfill({ json: { success: true } });248});249250await page.goto("/editor");251await page.getByLabel("Content").fill("New content");252253const saveButton = page.getByRole("button", { name: "Save" });254await saveButton.click();255256// Button should show loading state257await expect(saveButton).toBeDisabled();258await expect(page.getByTestId("spinner")).toBeVisible();259260// Then success state261await expect(saveButton).toBeEnabled();262await expect(page.getByText("Saved")).toBeVisible();263});264```265266### Test Empty States267268```typescript269test("shows empty state when no data", async ({ page }) => {270await page.route("**/api/items", (route) => route.fulfill({ json: [] }));271272await page.goto("/items");273274await expect(page.getByText("No items yet")).toBeVisible();275await expect(276page.getByRole("button", { name: "Create First Item" }),277).toBeVisible();278});279```280281## Form Validation282283### Test Client-Side Validation284285```typescript286test("validates required fields", async ({ page }) => {287await page.goto("/signup");288289// Submit empty form290await page.getByRole("button", { name: "Sign Up" }).click();291292// Should show validation errors293await expect(page.getByText("Email is required")).toBeVisible();294await expect(page.getByText("Password is required")).toBeVisible();295296// Form should not submit297await expect(page).toHaveURL("/signup");298});299```300301### Test Format Validation302303```typescript304test("validates email format", async ({ page }) => {305await page.goto("/signup");306307await page.getByLabel("Email").fill("invalid-email");308await page.getByLabel("Email").blur();309310await expect(page.getByText("Invalid email address")).toBeVisible();311312// Fix the error313await page.getByLabel("Email").fill("[email protected]");314await page.getByLabel("Email").blur();315316await expect(page.getByText("Invalid email address")).toBeHidden();317});318```319320### Test Server-Side Validation321322```typescript323test("handles server validation errors", async ({ page }) => {324await page.route("**/api/register", (route) =>325route.fulfill({326status: 422,327json: {328errors: {329email: "Email already exists",330username: "Username is taken",331},332},333}),334);335336await page.goto("/signup");337await page.getByLabel("Email").fill("[email protected]");338await page.getByLabel("Username").fill("takenuser");339await page.getByLabel("Password").fill("password123");340await page.getByRole("button", { name: "Sign Up" }).click();341342// Server errors should display343await expect(page.getByText("Email already exists")).toBeVisible();344await expect(page.getByText("Username is taken")).toBeVisible();345});346```347348## Anti-Patterns to Avoid349350| Anti-Pattern | Problem | Solution |351| ------------------------ | ------------------------------ | -------------------------------------- |352| Only testing happy path | Misses error handling bugs | Test all error scenarios |353| No network failure tests | App crashes on poor connection | Test offline/slow/failed requests |354| Skipping loading states | Janky UX not caught | Assert loading UI appears |355| Ignoring validation | Form bugs slip through | Test both client and server validation |356357## Related References358359- **Network Mocking**: See [network-advanced.md](../advanced/network-advanced.md) for mock patterns360- **Assertions**: See [assertions-waiting.md](../core/assertions-waiting.md) for error assertions361