Claude Skill

scaffold-e2e

Set up Playwright for a pracht app: install it, generate a `playwright.config.ts` that boots `pracht dev` (or the build via `pracht preview`), and emit a smoke test per manifest route asserting 200, head/title, no console errors, and navigation. Use for "scaffold E2E", "set up Pl

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Download jovidecroock-pracht-skills_scaffold-e2e-8bcdd52.zip · 2 KB

Install

skills CLI npx skills add https://github.com/JoviDeCroock/pracht/tree/main/skills/scaffold-e2e
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jovidecroock-pracht@llmmart
Git git clone https://github.com/JoviDeCroock/pracht.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jovidecroock/pracht collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Pracht Scaffold E2E

Generate a Playwright suite that exercises the live app. Aligns with recipes-testing.md.

Step 1: Decide which build the tests run against

Ask the user (default: dev):

  1. pracht dev — fast, HMR, but can mask production-only bugs (different bundling, different SSG/ISG flow).
  2. Production runtime — pracht preview: purpose-built, it builds and serves the production output locally for both Node and Cloudflare targets (Vercel prints guidance instead). Use pracht preview --skip-build to serve an existing build after an explicit pracht build. Prefer this over hand-rolling node dist/server/server.js, which is Node-adapter-only and skips the build.
  3. External URL — user provides BASE_URL; tests do not boot the server.

Step 2: Install

pnpm add -D @playwright/test
pnpm exec playwright install chromium

Add firefox and webkit only if the user requests them.

Step 3: Write playwright.config.ts

import { defineConfig, devices } from "@playwright/test";

const PORT = Number(process.env.PORT ?? 3000);

export default defineConfig({
  testDir: "./e2e",
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  reporter: process.env.CI ? "github" : "list",
  use: {
    baseURL: process.env.BASE_URL ?? `http://localhost:${PORT}`,
    trace: "on-first-retry",
    screenshot: "only-on-failure",
  },
  webServer: process.env.BASE_URL
    ? undefined
    : {
        command: "pnpm dev", // or "pracht preview" for the production runtime — choose at scaffold time
        url: `http://localhost:${PORT}`,
        reuseExistingServer: !process.env.CI,
        timeout: 120_000,
      },
  projects: [
    { name: "chromium", use: devices["Desktop Chrome"] },
  ],
});

If playwright.config.ts already exists, merge — do not clobber.

Step 4: Generate per-route smoke tests

MCP: when the pracht MCP server is registered (pracht dev-mcp), prefer its inspect_routes/inspect_api/inspect_build/doctor/verify/generate_* tools over shelling out. pracht inspect needs the pracht plugin in the vite config.

pracht inspect routes --json

For every route entry, generate one spec under e2e/smoke/. Skip routes with dynamic segments unless the user provides example params (ask via AskUserQuestion).

Per-route template:

import { test, expect } from "@playwright/test";

test.describe("smoke: <path>", () => {
  test("loads with 200 and a title", async ({ page }) => {
    const errors: string[] = [];
    page.on("console", (msg) => {
      if (msg.type() === "error") errors.push(msg.text());
    });
    page.on("pageerror", (err) => errors.push(err.message));

    const response = await page.goto("<path>");
    expect(response?.status(), "HTTP status").toBeLessThan(400);
    await expect(page).toHaveTitle(/.+/);
    expect(errors, "no console errors").toEqual([]);
  });
});

Step 5: Generate a navigation crawl

A single e2e/navigation.spec.ts that:

  1. Visits the home route.
  2. Waits for hydration with the documented readiness idiom before touching anything — otherwise the crawl races hydration and clicks fall through to full page loads:
    await page.waitForFunction(() => (window as any).__PRACHT_ROUTER_READY__);
    
  3. Collects every same-origin <a href>.
  4. Clicks each in turn, asserts no console errors, no 4xx/5xx.

This catches client-router intercept regressions and broken links.

Step 6: Generate a hydration check (optional)

If any route is render: "ssr" or "ssg"/"isg":

test("server HTML matches client render", async ({ page }) => {
  const errors: string[] = [];
  page.on("console", (m) => m.type() === "error" && errors.push(m.text()));
  await page.goto("<path>");
  await page.waitForLoadState("networkidle");
  expect(errors.filter((e) => /Hydration|hydrat/i.test(e))).toEqual([]);
});

Step 7: Wire package.json

{
  "scripts": {
    "e2e": "playwright test",
    "e2e:ui": "playwright test --ui"
  }
}

If an e2e script already exists in the project, confirm before overwriting.

Step 8: Run

pnpm e2e
pracht verify --json

If specs fail on first run, report. Common first-run failures:

  • Port collision (already-running dev server).
  • Missing browser binary (playwright install chromium).
  • Strict CSP blocking inline test scripts (rare).

Rules

  1. Source of routes is pracht inspect routes --json. Do not glob src/routes/**.
  2. Skip dynamic-segment routes unless example params are provided.
  3. Never overwrite an existing playwright.config.ts without diffing first.
  4. If the app uses typed routes, run pracht typegen --check before generating specs and prefer imported href() for test URLs in app-owned route fixtures.
  5. Use webServer to boot pracht dev or pracht preview so CI works out-of-the-box.
  6. Console-error capture is mandatory — silent JS errors are the most common pracht hydration regression.
  7. Wait for window.__PRACHT_ROUTER_READY__ before interacting with the page in any spec that relies on client-side navigation.

$ARGUMENTS

Files (pracht)
  • SKILL.md 5.5 KB
    ---
    name: scaffold-e2e
    version: 1.1.1
    description: |
      Set up Playwright for a pracht app: install it, generate a
      `playwright.config.ts` that boots `pracht dev` (or the build via `pracht
      preview`), and emit a smoke test per manifest route asserting 200, head/title,
      no console errors, and navigation.
      Use for "scaffold E2E", "set up Playwright", "add browser tests", "create smoke
      tests for my routes".
    allowed-tools:
      - Bash
      - Read
      - Write
      - Edit
      - Grep
      - Glob
      - AskUserQuestion
    ---
    
    # Pracht Scaffold E2E
    
    Generate a Playwright suite that exercises the live app. Aligns with
    `recipes-testing.md`.
    
    ## Step 1: Decide which build the tests run against
    
    Ask the user (default: `dev`):
    
    1. **`pracht dev`** — fast, HMR, but can mask production-only bugs (different
       bundling, different SSG/ISG flow).
    2. **Production runtime** — `pracht preview`: purpose-built, it builds and
       serves the production output locally for both Node and Cloudflare targets
       (Vercel prints guidance instead). Use `pracht preview --skip-build` to
       serve an existing build after an explicit `pracht build`. Prefer this over
       hand-rolling `node dist/server/server.js`, which is Node-adapter-only and
       skips the build.
    3. **External URL** — user provides `BASE_URL`; tests do not boot the server.
    
    ## Step 2: Install
    
    ```bash
    pnpm add -D @playwright/test
    pnpm exec playwright install chromium
    ```
    
    Add `firefox` and `webkit` only if the user requests them.
    
    ## Step 3: Write `playwright.config.ts`
    
    ```ts
    import { defineConfig, devices } from "@playwright/test";
    
    const PORT = Number(process.env.PORT ?? 3000);
    
    export default defineConfig({
      testDir: "./e2e",
      fullyParallel: true,
      retries: process.env.CI ? 2 : 0,
      reporter: process.env.CI ? "github" : "list",
      use: {
        baseURL: process.env.BASE_URL ?? `http://localhost:${PORT}`,
        trace: "on-first-retry",
        screenshot: "only-on-failure",
      },
      webServer: process.env.BASE_URL
        ? undefined
        : {
            command: "pnpm dev", // or "pracht preview" for the production runtime — choose at scaffold time
            url: `http://localhost:${PORT}`,
            reuseExistingServer: !process.env.CI,
            timeout: 120_000,
          },
      projects: [
        { name: "chromium", use: devices["Desktop Chrome"] },
      ],
    });
    ```
    
    If `playwright.config.ts` already exists, merge — do not clobber.
    
    ## Step 4: Generate per-route smoke tests
    
    MCP: when the pracht MCP server is registered (`pracht dev-mcp`), prefer its
    `inspect_routes`/`inspect_api`/`inspect_build`/`doctor`/`verify`/`generate_*`
    tools over shelling out. `pracht inspect` needs the pracht plugin in the vite
    config.
    
    ```bash
    pracht inspect routes --json
    ```
    
    For every route entry, generate one spec under `e2e/smoke/`. Skip routes with
    dynamic segments unless the user provides example params (ask via
    `AskUserQuestion`).
    
    Per-route template:
    
    ```ts
    import { test, expect } from "@playwright/test";
    
    test.describe("smoke: <path>", () => {
      test("loads with 200 and a title", async ({ page }) => {
        const errors: string[] = [];
        page.on("console", (msg) => {
          if (msg.type() === "error") errors.push(msg.text());
        });
        page.on("pageerror", (err) => errors.push(err.message));
    
        const response = await page.goto("<path>");
        expect(response?.status(), "HTTP status").toBeLessThan(400);
        await expect(page).toHaveTitle(/.+/);
        expect(errors, "no console errors").toEqual([]);
      });
    });
    ```
    
    ## Step 5: Generate a navigation crawl
    
    A single `e2e/navigation.spec.ts` that:
    
    1. Visits the home route.
    2. Waits for hydration with the documented readiness idiom before touching
       anything — otherwise the crawl races hydration and clicks fall through to
       full page loads:
       ```ts
       await page.waitForFunction(() => (window as any).__PRACHT_ROUTER_READY__);
       ```
    3. Collects every same-origin `<a href>`.
    4. Clicks each in turn, asserts no console errors, no 4xx/5xx.
    
    This catches client-router intercept regressions and broken links.
    
    ## Step 6: Generate a hydration check (optional)
    
    If any route is `render: "ssr"` or `"ssg"`/`"isg"`:
    
    ```ts
    test("server HTML matches client render", async ({ page }) => {
      const errors: string[] = [];
      page.on("console", (m) => m.type() === "error" && errors.push(m.text()));
      await page.goto("<path>");
      await page.waitForLoadState("networkidle");
      expect(errors.filter((e) => /Hydration|hydrat/i.test(e))).toEqual([]);
    });
    ```
    
    ## Step 7: Wire `package.json`
    
    ```json
    {
      "scripts": {
        "e2e": "playwright test",
        "e2e:ui": "playwright test --ui"
      }
    }
    ```
    
    If an `e2e` script already exists in the project, confirm before
    overwriting.
    
    ## Step 8: Run
    
    ```bash
    pnpm e2e
    pracht verify --json
    ```
    
    If specs fail on first run, report. Common first-run failures:
    - Port collision (already-running dev server).
    - Missing browser binary (`playwright install chromium`).
    - Strict CSP blocking inline test scripts (rare).
    
    ## Rules
    
    1. Source of routes is `pracht inspect routes --json`. Do not glob
       `src/routes/**`.
    2. Skip dynamic-segment routes unless example params are provided.
    3. Never overwrite an existing `playwright.config.ts` without diffing first.
    4. If the app uses typed routes, run `pracht typegen --check` before generating specs and prefer imported `href()` for test URLs in app-owned route fixtures.
    5. Use `webServer` to boot `pracht dev` or `pracht preview` so CI works
       out-of-the-box.
    6. Console-error capture is mandatory — silent JS errors are the most common
       pracht hydration regression.
    7. Wait for `window.__PRACHT_ROUTER_READY__` before interacting with the
       page in any spec that relies on client-side navigation.
    
    $ARGUMENTS
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.