Skip to content

Playwright Playwright

Description / nameInput element
Container Registry

Build Status Last Commit

Playwright (Chromium) on FreeBSD. Use as a base image for running browser tests.

Registry ghcr.io/daemonless/playwright
Daemonless daemonless/playwright
Source microsoft/playwright
Website playwright.dev

Version Tags

Tag Description Best For
latest Upstream Binary. Built from official release. Most users — recommended.

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

Deployment

podman run --rm \
  ghcr.io/daemonless/playwright:latest

This image is intended as a base image for running browser automation scripts, not as a long-running service.

Write a script using playwright-core and run it with podman run --rm:

// test.js
const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });
  const context = await browser.newContext({
    recordVideo: { dir: '/app/videos/' },
    ignoreHTTPSErrors: true,
  });
  const page = await context.newPage();

  try {
    await page.goto('https://example.com');
    // ... your test logic ...
  } finally {
    await context.close();
    await browser.close();
  }
})();
1
2
3
4
5
6
podman run --rm \
  -v /path/to/test.js:/app/test.js:ro \
  -v /path/to/videos:/app/videos \
  --network host \
  ghcr.io/daemonless/playwright:latest \
  node /app/test.js

Videos are recorded as .webm files. Convert to mp4 with:

ffmpeg -i /path/to/videos/*.webm output.mp4

Real-World Example

Login to a web app and record a video:

// login.js
const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });
  const context = await browser.newContext({
    recordVideo: { dir: '/app/videos/', size: { width: 1280, height: 720 } },
    ignoreHTTPSErrors: true,
  });
  const page = await context.newPage();

  try {
    await page.goto('https://myapp:8181');
    await page.waitForLoadState('networkidle');

    await page.locator('input[placeholder="Enter your username"]').fill('admin');
    await page.locator('input[type="password"]').fill('admin');

    await Promise.all([
      page.waitForLoadState('networkidle'),
      page.getByRole('button', { name: 'Login' }).click(),
    ]);

    // Wait for a post-login element instead of a fixed sleep
    await page.waitForTimeout(3000);
  } finally {
    await context.close();
    await browser.close();
  }
})();
1
2
3
4
5
6
podman run --rm \
  -v /path/to/login.js:/app/test.js:ro \
  -v /path/to/videos:/app/videos \
  --network host \
  ghcr.io/daemonless/playwright:latest \
  node /app/test.js

FreeBSD Notes

Chromium and ffmpeg are installed from FreeBSD packages and symlinked into the paths playwright-core expects. The playwright-core npm package is patched at build time to register freebsd-amd64 as a native platform — no Linux masquerade needed.

Manual Install (without container)

1
2
3
4
5
git clone https://github.com/daemonless/playwright
pkg install node22 npm-node22 chromium ffmpeg
npm install -g playwright-core
sh playwright/root/build/patch-playwright.sh
doas sh playwright/root/build/setup-ms-playwright.sh

Add to your shell profile:

1
2
3
4
export PLAYWRIGHT_BROWSERS_PATH=/usr/local/libexec/ms-playwright
export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE=freebsd-amd64
export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
export NODE_PATH=$(npm root -g)

Implementation Details

  • Architectures: amd64
  • User: bsd (UID/GID set via PUID/PGID). Defaults to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.1).

Need help? Join our Discord community.