Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/openai/skills/llms.txt

Use this file to discover all available pages before exploring further.

The playwright-interactive skill extends the base Playwright CLI skill with a persistent js_repl session that keeps browser handles alive across multiple code iterations. Instead of opening and closing a browser for each command, you launch once and keep the same page, context, and electronApp objects alive throughout the debugging session — reloading or relaunching only when the code actually changes. This makes rapid iterative QA dramatically faster for local web apps and Electron applications.

When to Use Interactive vs. Headless CLI

Use Playwright CLI (headless)

  • Scripted, repeatable browser automation
  • Form filling, navigation, screenshots, and data extraction
  • One-shot tasks that don’t need persistent state
  • CI pipelines and batch workflows
  • Any task where $PWCLI snapshot + click covers your needs

Use Playwright Interactive

  • Iterative UI debugging of a local web or Electron app
  • Keeping browser handles alive across multiple code changes
  • Visual QA with functional and screenshot-based signoff
  • Desktop and mobile viewport comparisons in a single session
  • Electron app debugging where js_repl owns the process

Prerequisites

Playwright Interactive requires js_repl to be enabled and Codex to be started with --sandbox danger-full-access. This is a temporary requirement while js_repl + Playwright sandbox support is being completed.
Enable js_repl in ~/.codex/config.toml:
[features]
js_repl = true
Or start a session with the flag directly:
codex --enable js_repl --sandbox danger-full-access
After enabling js_repl, start a new Codex session so the tool list refreshes.

One-Time Setup

Run once per workspace directory before using the skill:
test -f package.json || npm init -y
npm install playwright
# Web-only, for headed Chromium or mobile emulation:
# npx playwright install chromium
# Electron-only, and only if the target workspace is the app itself:
# npm install --save-dev electron
node -e "import('playwright').then(() => console.log('playwright import ok')).catch((error) => { console.error(error); process.exit(1); })"
If you switch to a different workspace, repeat setup there.

How It Differs from the CLI Skill

FeaturePlaywright CLIPlaywright Interactive
Session persistenceEach command is statelessHandles (page, context, electronApp) stay alive
Iteration speedRe-opens browser per sessionReload or relaunch only when code changes
Electron supportNot supportedFull _electron.launch() support via js_repl
Mobile emulationNot supportedDesktop + mobile contexts in the same session
Visual QAScreenshots via $PWCLI screenshotStructured QA inventory, multi-pass visual signoff
Sandbox requirementWorks in standard sandboxRequires --sandbox danger-full-access
Setupnpx-based, no extra installnpm install playwright per workspace

Core Session Loop

1

Bootstrap js_repl once

Run the bootstrap cell to load Playwright and declare shared handles. Use var so later cells can reuse the same bindings.
var chromium;
var electronLauncher;
var browser;
var context;
var page;

try {
  ({ chromium, _electron: electronLauncher } = await import("playwright"));
  console.log("Playwright loaded");
} catch (error) {
  throw new Error(
    `Could not load playwright from the current js_repl cwd. ` +
    `Run the setup commands from this workspace first. Original error: ${error}`
  );
}
2

Start a web session

Launch Chromium and navigate to your local dev server:
var TARGET_URL = "http://127.0.0.1:3000";

if (page?.isClosed()) page = undefined;

browser ??= await chromium.launch({ headless: false });
context ??= await browser.newContext({
  viewport: { width: 1600, height: 900 },
});
page ??= await context.newPage();

await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded:", await page.title());
3

Make code changes, then reload

For renderer-only changes, reload without relaunching:
await page.reload({ waitUntil: "domcontentloaded" });
For Electron main-process or startup changes, relaunch the app:
await electronApp.close().catch(() => {});
electronApp = undefined;
appWindow = undefined;

electronApp = await electronLauncher.launch({ args: [ELECTRON_ENTRY] });
appWindow = await electronApp.firstWindow();
console.log("Relaunched:", await appWindow.title());
4

Run functional and visual QA

Work through a shared QA inventory covering every user-visible control and state. Use real user input (keyboard, mouse, touch) — not page.evaluate() — for signoff checks.
5

Capture screenshots for signoff

Emit CSS-normalized JPEG screenshots for model interpretation:
await codex.emitImage({
  bytes: await page.screenshot({ type: "jpeg", quality: 85, scale: "css" }),
  mimeType: "image/jpeg",
  detail: "original",
});
6

Clean up when done

Only run cleanup when the task is actually finished. Closing the terminal does not implicitly close the browser or Electron app.
if (context) await context.close().catch(() => {});
if (browser) await browser.close().catch(() => {});
browser = undefined;
context = undefined;
page = undefined;
console.log("Playwright session closed");

Session Modes

The skill supports three distinct session modes. Choose based on your testing goal:

Explicit viewport (default)

Use for routine iteration, breakpoint checks, reproducible screenshots, and snapshot diffs. Stable across machines.

Native-window mode

Use viewport: null for a separate headed pass to validate launched window size, OS-level DPI, or browser chrome interactions.

Electron mode

Launch through _electron.launch(). Assumes native-window behavior. Check as-launched size and layout before resizing.
Treat switching modes as a context reset. Do not reuse a viewport-emulated context for a native-window pass — close the old page and context first, then create new ones.

Mobile Emulation

Add a mobile context alongside your desktop context in the same session:
var MOBILE_TARGET_URL = "http://127.0.0.1:3000";

if (mobilePage?.isClosed()) mobilePage = undefined;

mobileContext ??= await browser.newContext({
  viewport: { width: 390, height: 844 },
  isMobile: true,
  hasTouch: true,
});
mobilePage ??= await mobileContext.newPage();

await mobilePage.goto(MOBILE_TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded mobile:", await mobilePage.title());

Common Failure Modes

ErrorFix
Cannot find module 'playwright'Run the one-time setup in the current workspace
Browser executable missingnpx playwright install chromium
net::ERR_CONNECTION_REFUSEDConfirm dev server is running; prefer http://127.0.0.1:<port>
electron.launch hangsVerify local electron dep; ensure renderer dev server is running first
Identifier has already been declaredReuse existing top-level bindings; use js_repl_reset only if the kernel is stuck
Browser ops fail immediatelyConfirm session started with --sandbox danger-full-access

Installing

$skill-installer playwright-interactive
Restart Codex after installation to pick up the new skill.

Build docs developers (and LLMs) love