Spens logoSpens

Spens with Playwright

Give a sandboxed agent a real headless browser with a custom environment and the frontend-check package

This example gives the pi agent a real headless Chromium inside the sandbox, so it can render your frontend, click through user flows, take screenshots, and read console errors — without any of that touching your machine.

There are three pieces:

  1. A custom environment template that installs the system packages Chromium needs
  2. A spens config whose pre_sandbox_commands install the frontend-check package into the project
  3. A spens session run with that environment

1. The custom environment

Create templates/playwright.json in your workspace:

{
  "template_type": "environment",
  "name": "playwright",
  "base-image": "node:24-bookworm",
  "nono-command": "curl -fsSL https://nono.sh/install.sh | sh",
  "packages": [
    "curl",
    "ca-certificates",
    "fonts-liberation",
    "fonts-noto-color-emoji",
    "libnss3",
    "libnspr4",
    "libdbus-1-3",
    "libatk1.0-0",
    "libatk-bridge2.0-0",
    "libcups2",
    "libxcomposite1",
    "libxdamage1",
    "libxfixes3",
    "libxrandr2",
    "libgbm1",
    "libxkbcommon0",
    "libatspi2.0-0",
    "libpango-1.0-0",
    "libcairo2",
    "libasound2"
  ],
  "package_manager": "apt",
  "agent_user": {
    "name": "node",
    "uid": 1000,
    "home": "/home/node",
    "create": false
  }
}

The packages list is the base set of system libraries a headless Chromium needs on Debian, plus fonts so screenshots render real text instead of boxes. These are baked into the Docker image at build time, so they are installed once and then cached with the image. Spens picks the template up automatically; check with spens list.

2. The spens config

Start with what spens init generates for a node project:

spens init --yes --stack node --provider anthropic

Then add two things to .spens.config.json: the pre_sandbox_commands that install the frontend-check package, and a domain rule for the Playwright browser download. The result looks like this:

{
  "pre_sandbox_commands": [
    "npm install",
    "pi install npm:pi-frontend-check -l --approve"
  ],
  "domain_rules": [
    {"pattern": "*api.anthropic.com*", "allow": ["GET", "POST", "PUT", "DELETE", "OPTIONS"]},
    {"pattern": "*registry.npmjs.org*", "allow": ["GET", "HEAD", "POST", "PUT"]},
    {"pattern": "*cdn.playwright.dev*", "allow": ["GET"]}
  ],
  "inject_headers": [
    {
      "placeholder": "ANTHROPIC_API_KEY",
      "env_var": "ANTHROPIC_API_KEY",
      "for_domains": ["*api.anthropic.com*"]
    }
  ]
}

What the two additions do:

  • npm install installs the project's dependencies (dev server, test runner, and so on) inside the container before the agent starts.
  • pi install npm:pi-frontend-check -l --approve installs the frontend-check package into the project: -l writes it to .pi/settings.json in the workspace, so the package is versioned with the repo and anyone running a session gets it automatically. --approve trusts the project-local settings for the install command, because pre-sandbox commands run non-interactively and would otherwise stall on a trust prompt. Pre-sandbox commands run unsandboxed as the unprivileged agent user, and their traffic still goes through the interceptor.
  • The *cdn.playwright.dev* rule lets the browser download through. Frontend-check downloads its Chromium build (~150 MB) from the Playwright CDN the first time the agent opens a browser. The network policy fails closed, so without this rule the download is blocked. The download repeats once per session — the image is cached but the container is fresh — which is the price of an ephemeral browser.

3. Run the session

spens playwright pi .

The first run builds the images, which takes a few minutes. When pi starts it asks whether to trust the project — answer yes. That trust prompt appears because the workspace now contains .pi/settings.json, and approving it is what lets pi load the frontend-check skill and tools.

Then ask for something concrete:

Start the dev server (npm run dev). Then use the frontend-testing skill to open http://localhost:5173, take screenshots of the homepage at mobile and desktop widths, walk through the login flow, and report any console errors.

The agent starts the dev server, drives a real headless Chromium, and reports back with screenshots — all inside the container. The browser and the dev server can only reach the domains your rules allow.

If you want to run this unattended (yolo mode), note that pi never shows a trust prompt when running non-interactively, so project-local packages are ignored unless the project is trusted. Set {"defaultProjectTrust": "always"} in .spens/.pi/agent/settings.json; spens copies that file into the image for the pi agent, and frontend-check then loads in yolo sessions too.

On this page