Preface

It sounds reasonable to ask an AI programming assistant to revise a frontend and then “click through the page to verify it”, but it often gets stuck in the workflow in practice: every verification requires restarting the browser and re-acquiring page handles; it’s unclear whether you should reload the page or restart the entire process after modifying the code; the functional path works, but visual checks are not done separately, and issues like first-screen cropping, contrast ratios, and intermediate states of animations are easily missed. Compared to “running a one-off CLI to go through a webpage workflow”, debugging local Web or Electron applications requires repeated iteration within the same session.

OpenAI provides the playwright-interactive Skill in its curated skill library. Instead of following the one-off playwright-cli command flow in the terminal, it uses a persistent js_repl session to attach to Playwright/Electron handles, allowing the Agent to act like a developer: modify code → refresh or restart → functional QA → visual QA → take screenshots for documentation. This article is organized based on cross-verification of the official SKILL.md, the playwright Skill in the same repository, and related Codex Issues.

What It Is

playwright-interactive is an Agent Skill (in the universal SKILL.md format), hosted in the skills/.curated/playwright-interactive directory of the openai/skills repository maintained by OpenAI. The official one-sentence positioning is:

Persistent browser and Electron interaction via js_repl for rapid iterative UI debugging.

It solves a very specific problem: when debugging local Web or Electron applications, maintain the same set of Playwright handles (browser / context / page, or electronApp / appWindow) across multiple rounds of modifications, and complete functional verification and visual QA without restarting the entire tool chain each time.

There is also the CLI-focused playwright Skill (playwright-cli: open pages, take snapshots, click by ref) in the same repository. The two complement each other: playwright is better suited for “running an automation workflow against a single URL”; playwright-interactive is better suited for “working on a locally developed application, iterating and verifying like a REPL”.

It should be noted in advance: the README of the openai/skills repository has been marked deprecated, and subsequent examples have shifted to OpenAI Plugins. More importantly, js_repl, which this Skill strongly depends on, has been marked removed in the current Codex (this can be cross-confirmed via community Issues and the output of codex features list). Therefore, the following will introduce its design and usage as described in the official SKILL.md, and separately clarify the runtime prerequisites in the notes—do not treat it as a capability that works out of the box in all current Codex environments.

Core Features and Highlights

After verifying official materials, the capabilities can be summarized as follows.

1. Persistent Playwright sessions instead of cold starts every time

Declare top-level handles (browser, context, page, electronApp, appWindow, etc.) with var in the js_repl for reuse in subsequent cells. The official clarification: treat js_repl_reset as a recovery measure, not daily cleanup—resetting the kernel will destroy all Playwright handles.

2. Support desktop Web, mobile Web, native windows, and Electron
- Desktop Web: Default explicit viewport (e.g. 1600×900) for reproducible screenshots and breakpoint debugging.
- Mobile Web: Separate mobileContext / mobilePage (e.g. 390×844, isMobile + hasTouch).
- Native window mode: viewport: null, used to verify real window sizes, system DPI, and browser chrome-related behaviors.
- Electron: Use Playwright’s _electron.launch to handle real desktop windows (noDefaultViewport).

The official recommendation: Use explicit viewports for routine iterations; only use native-window mode for final environment-related sign-offs; mode switching counts as a context reset and should not be mixed.

3. Create a QA inventory first, then separate functional QA and visual QA

Before testing, write a shared QA inventory sourced from user requirements, implemented user-visible behaviors, and statements intended to be signed off in the final response. Functional QA requires using real user inputs (keyboard, mouse, clicks, touches, etc. via Playwright APIs). page.evaluate / electronApp.evaluate can inspect states, but cannot count as signed-off inputs. Visual QA should be separated from functional QA, and user-visible statements must be checked under the “specific state in which the statement should be perceived”.

4. Clear reload / relaunch decisions after code changes
- Rendering layer changes only: Use reloadWebContexts() for Web, and appWindow.reload(...) for Electron.
- Main process, preload, or startup logic changes: Close and re-launch Electron.
- Unsure about process ownership or startup code: It’s better to relaunch than guess.

5. Built-in screenshot alignment with model coordinates and viewport adaptation checks

If you pass screenshots to the model for interpretation via codex.emitImage(...), the official default requires normalization to CSS pixels to avoid mismatches between device pixel coordinates under Retina/high DPI and Playwright CSS coordinates. Before signing off, you also need to perform viewport fit checks: prioritize screenshots, supplemented by numerical checks; cropping, occlusion, or content being pushed out of the viewport counts as a failure, even if scroll metrics look normal.

Installation and Activation

The universal form of an Agent Skill is: a directory + SKILL.md (this Skill also includes optional resources such as agents/, assets/, etc.). Different tools have different scanning paths, and the following are verifiable installation methods.

Install the Skill itself in Codex

Curated skills can be installed by name using $skill-installer within Codex:

$skill-installer playwright-interactive

Or specify the GitHub directory:

$skill-installer install https://github.com/openai/skills/tree/main/skills/.curated/playwright-interactive

After installation, it is usually located at $CODEX_HOME/skills (default ~/.codex/skills). If it does not appear automatically, restart Codex.

Enable js_repl (prerequisite for the Skill)

The official SKILL.md requires that this Skill must have js_repl enabled. If it is missing, configure it in ~/.codex/config.toml:

[features]
js_repl = true

You can also add the flag when starting a new session:

codex --enable js_repl
# Equivalent to -c features.js_repl=true

After enabling, you need to start a new Codex session to refresh the tool list. The official also notes that before the js_repl + Playwright sandbox support is completed, the sandbox needs to be temporarily disabled, for example:

codex --sandbox danger-full-access

Or set sandbox_mode to danger-full-access in the configuration.

Cross-verification: In newer Codex CLIs (community reports such as 0.128.0), codex features list may show js_repl / js_repl_tools_only as removed. At this point, enabling it according to the documentation will not bring up the corresponding tools, and the Skill cannot work “as written”. A closer alternative runtime in community discussions is node_repl (and related MCP surfaces in Browser Use documentation), but the official curated Skill text still refers to js_repl. Whether migration PRs have been merged should be confirmed based on the current state of the repository.

In tools like Cursor / Claude Code

Under the universal Agent Skill standard, you can place this directory in the path agreed by the tool, for example:
- Cursor: Project-level .cursor/skills/playwright-interactive/, or user-level ~/.cursor/skills/; also compatible with .agents/skills/, .codex/skills/, etc.
- Claude Code: .claude/skills/playwright-interactive/ or ~/.claude/skills/

Note: Skill instructions are deeply bound to runtime capabilities such as Codex’s js_repl and codex.emitImage. After copying SKILL.md to Cursor / Claude Code, the Agent may still lack an equivalent persistent JS REPL, and you can only refer to its QA inventory, reload/relaunch decisions, and screenshot specifications, and cannot assume that “copying the directory will fully reproduce the functionality”.

One-time project dependency installation

Run this in the project directory you want to debug (redo it when switching workspaces):

test -f package.json || npm init -y
npm install playwright
# For Web only, when headed Chromium or mobile emulation is needed:
# npx playwright install chromium
# For Electron only, when the current workspace is the application itself:
# npm install --save-dev electron
node -e "import('playwright').then(() => console.log('playwright import ok')).catch((error) => { console.error(error); process.exit(1); })"

When debugging local Web, use a persistent TTY session to run the development server (such as npm start), do not rely on short-lived background commands; confirm that the port is listening before calling page.goto.

Typical Usage Examples

The following snippets are all from the official SKILL.md and can be executed in js_repl as separate cells.

1. Bootstrap (run only once)

var chromium;
var electronLauncher;
var browser;
var context;
var page;
var mobileContext;
var mobilePage;
var electronApp;
var appWindow;

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}`
  );
}

Binding rules: Use var for shared handles so that they can be reused in subsequent cells; when a handle appears expired, set the binding to undefined and re-run the corresponding cell instead of adding recovery logic everywhere.

2. Start or reuse a desktop Web session

Use 127.0.0.1 first for local addresses, avoid localhost when possible:

var TARGET_URL = "http://127.0.0.1:3000";

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

await ensureWebBrowser();
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());

The helper functions such as ensureWebBrowser are given in the Shared web helpers section after the official Bootstrap: clear and relaunch the browser if disconnected (default headless: false).

3. Refresh during iteration and restart Electron

Refresh for Web rendering layer changes:

await reloadWebContexts();

Refresh only the Electron rendering layer:

await appWindow.reload({ waitUntil: "domcontentloaded" });
console.log("Reloaded Electron window");

Restart Electron after main process / preload / startup related changes:

await electronApp.close().catch(() => {});
electronApp = undefined;
appWindow = undefined;

electronApp = await electronLauncher.launch({
  args: [ELECTRON_ENTRY],
});

appWindow = await electronApp.firstWindow();
console.log("Relaunched Electron window:", await appWindow.title());

4. Cleanup only when the task is truly complete

Exiting Codex, closing the terminal, or losing the js_repl will not automatically execute browser.close() / electronApp.close(). Electron processes may particularly remain in the background. Official cleanup example (simplified logic):

if (electronApp) {
  await electronApp.close().catch(() => {});
}
if (mobileContext) {
  await mobileContext.close().catch(() => {});
}
if (context) {
  await context.close().catch(() => {});
}
if (browser) {
  await browser.close().catch(() => {});
}

browser = context = page = undefined;
mobileContext = mobilePage = undefined;
electronApp = appWindow = undefined;

console.log("Playwright session closed");

If you are about to exit Codex, run the cleanup script first and wait for the "Playwright session closed" message before exiting.

5. How to use in a conversation

You can explicitly ask the Agent to work with this Skill, for example:

Use playwright-interactive: run a UI debug session for the local http://127.0.0.1:3000.
First write the QA inventory, bootstrap the js_repl, keep the same page handle;
After I modify the code, only reload, perform functional QA and visual QA separately, and run the viewport fit check.

Clearly specify the target URL, whether it is Web or Electron, and whether mobile/native window sign-off is required. Only then should the Agent follow the official core workflow instead of falling back to a one-off CLI script.

Applicable Scenarios and Notes

Who and what scenarios it is suitable for:
- Multi-round UI iterative debugging of local Web or Electron applications;
- Need the Agent to perform continuous operations, take screenshots, and sign off against statements in the same browser/window session;
- Require both end-to-end functional paths and separate visual QA, first-screen/minimum viewport adaptation checks;
- Pair with the playwright (CLI) Skill: Use the CLI for public network exploration or one-off workflows, and use interactive for repeated UI modifications against local projects.

Limitations and pitfalls (official + cross-verified):
1. Runtime dependency on js_repl: If this feature has been removed in the current Codex, it cannot be enabled as described in the original SKILL.md; installing the Skill directory does not equal having the available capability.
2. Temporary sandbox shutdown requirement: The official note requires --sandbox danger-full-access, which has security and permission implications, do not enable it blindly on untrusted repositories.
3. js_repl_reset will destroy handles: Only use it when the kernel is truly stuck.
4. Common failures: Cannot find the playwright module (not installed in the current cwd); missing Chromium executable (requires npx playwright install chromium); net::ERR_CONNECTION_REFUSED (dev server not running in a persistent session); do not use context().newPage() as a scratch page under Electron (the official explicitly does not support this path).
5. Repository status: openai/skills has been deprecated; in the long term, pay attention to alternative solutions in OpenAI Plugins and the Codex Skills documentation.

Summary

playwright-interactive formalizes “persistent browser/Electron handles + shared QA inventory + functional/visual separate sign-off + reload/relaunch decisions” into an executable workflow for Agents, aiming to make UI debugging as iterative as a developer sitting in front of a headed browser, rather than starting over from a cold-start script every time. It complements the CLI-focused playwright Skill, but strongly relies on runtime capabilities such as js_repl; before enabling it in the current Codex, be sure to confirm whether this tool is still exposed on the local machine.

Official directory: https://github.com/openai/skills/tree/main/skills/.curated/playwright-interactive
Reference comparison: https://github.com/openai/skills/tree/main/skills/.curated/playwright