---
name: rendering
description: Playwright rendering and capture pipeline
metadata:
  tags: playwright, rendering, screenshot, dom
---

# Rendering with Playwright

## Goals

- Render SPAs to a fully hydrated DOM
- Capture HTML, screenshots, and network responses
- Keep viewport sizing deterministic for layout fidelity

## Recommended Settings

- Desktop viewport derived from the target layout
- `waitUntil: 'networkidle'` plus a short extra wait
- Consistent locale/timezone for deterministic text rendering

## Implementation Checklist

1. Launch Chromium and open a new context
2. Configure viewport, locale, and color scheme
3. Attach response listeners for asset discovery
4. Navigate to the URL and wait for idle
5. Capture HTML and a full-page screenshot

## Example Capture Function

```ts
import { chromium } from 'playwright';

export type NetworkRequest = {
  url: string;
  status: number;
  method: string;
  resourceType: string;
  contentType?: string;
};

export const renderPage = async ({
  url,
  viewport,
  waitUntil,
  extraWaitMs,
}: {
  url: string;
  viewport: { width: number; height: number };
  waitUntil: 'load' | 'domcontentloaded' | 'networkidle';
  extraWaitMs?: number;
}) => {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport });
  const page = await context.newPage();
  const requests: NetworkRequest[] = [];

  page.on('response', (response) => {
    const request = response.request();
    requests.push({
      url: response.url(),
      status: response.status(),
      method: request.method(),
      resourceType: request.resourceType(),
      contentType: response.headers()['content-type'],
    });
  });

  await page.goto(url, { waitUntil });
  if (extraWaitMs) {
    await page.waitForTimeout(extraWaitMs);
  }

  const html = await page.content();
  const screenshot = await page.screenshot({ fullPage: true });
  await browser.close();

  return { html, screenshot, requests };
};
```
