playwright-interactive
Maintains a persistent Playwright session to debug local web or Electron apps without restarting the toolchain.
Install
mkdir -p .claude/skills/playwright-interactive && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/11263" && unzip -o skill.zip -d .claude/skills/playwright-interactive && rm skill.zipInstalls to .claude/skills/playwright-interactive
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use a persistent `js_repl` Playwright session to debug local web or Electron apps, keep the same handles alive across iterations, and run functional plus visual QA without restarting the whole toolchain unless the process ownership changed.Key capabilities
- →Debug local web or Electron applications
- →Maintain persistent Playwright handles across iterations
- →Run functional and visual QA tests
- →Capture screenshots for visual regression
- →Reset browser handles using recovery tools
How it works
It maintains a persistent Playwright session via js_repl, allowing the reuse of browser handles to avoid the overhead of cold-starting the testing environment.
Inputs & outputs
When to use playwright-interactive
- →Debug local web application UI
- →Maintain browser handles for rapid iteration
- →Run visual regression tests
- →Test Electron app workflows
About this skill
Playwright Interactive Skill
Use a persistent js_repl Playwright session to debug local web or Electron apps, keep the same handles alive across iterations, and run functional plus visual QA without restarting the whole toolchain unless the process ownership changed.
Preconditions
js_replmust be enabled for this skill.- If
js_replis missing, enable it in~/.codex/config.toml:
[features]
js_repl = true
- You can also start a new session with
--enable js_repl(equivalent to-c features.js_repl=true). - After enabling
js_repl, start a new Codex session so the tool list refreshes. - For now, run this workflow with sandboxing disabled: start Codex with
--sandbox danger-full-access(or the equivalent config forsandbox_mode=danger-full-access). This is a temporary requirement whilejs_repl+ Playwright support inside the sandbox is still being completed. - Run setup from the same project directory you need to debug.
- Treat
js_repl_resetas a recovery tool, not routine cleanup. Resetting the kernel destroys your Playwright handles.
One-time setup
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 later, repeat setup there.
Core Workflow
- Write a brief QA inventory before testing:
- Build the inventory from three sources: the user's requested requirements, the user-visible features or behaviors you actually implemented, and the claims you expect to make in the final response.
- Anything that appears in any of those three sources must map to at least one QA check before signoff.
- List the user-visible claims you intend to sign off on.
- List every meaningful user-facing control, mode switch, or implemented interactive behavior.
- List the state changes or view changes each control or implemented behavior can cause.
- Use this as the shared coverage list for both functional QA and visual QA.
- For each claim or control-state pair, note the intended functional check, the specific state where the visual check must happen, and the evidence you expect to capture.
- If a requirement is visually central but subjective, convert it into an observable QA check instead of leaving it implicit.
- Add at least 2 exploratory or off-happy-path scenarios that could expose fragile behavior.
- Run the bootstrap cell once.
- Start or confirm any required dev server in a persistent TTY session.
- Launch the correct runtime and keep reusing the same Playwright handles.
- After each code change, reload for renderer-only changes or relaunch for main-process/startup changes.
- Run functional QA with normal user input.
- Run a separate visual QA pass.
- Verify viewport fit and capture the screenshots needed to support your claims.
- Clean up the Playwright session only when the task is actually finished.
Bootstrap (Run 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
varfor the shared top-level Playwright handles because laterjs_replcells reuse them. - The setup cells below are intentionally short happy paths. If a handle looks stale, set that binding to
undefinedand rerun the cell instead of adding recovery logic everywhere. - Prefer one named handle per surface you care about (
page,mobilePage,appWindow) over repeatedly rediscovering pages from the context.
Shared web helpers:
var resetWebHandles = function () {
context = undefined;
page = undefined;
mobileContext = undefined;
mobilePage = undefined;
};
var ensureWebBrowser = async function () {
if (browser && !browser.isConnected()) {
browser = undefined;
resetWebHandles();
}
browser ??= await chromium.launch({ headless: false });
return browser;
};
var reloadWebContexts = async function () {
for (const currentContext of [context, mobileContext]) {
if (!currentContext) continue;
for (const p of currentContext.pages()) {
await p.reload({ waitUntil: "domcontentloaded" });
}
}
console.log("Reloaded existing web tabs");
};
Choose Session Mode
For web apps, use an explicit viewport by default and treat native-window mode as a separate validation pass.
- Use an explicit viewport for routine iteration, breakpoint checks, reproducible screenshots, snapshot diffs, and model-assisted localization. This is the default because it is stable across machines and avoids host window-manager variability.
- When you need deterministic high-DPI behavior, keep the explicit viewport and add
deviceScaleFactorrather than switching straight to native-window mode. - Use native-window mode (
viewport: null) for a separate headed pass when you need to validate launched window size, OS-level DPI behavior, browser chrome interactions, or bugs that may depend on the host display configuration. - For Electron, assume native-window behavior all the time. Electron launches through Playwright with
noDefaultViewport, so treat it like a real desktop window and check the as-launched size and layout before resizing anything. - When signoff depends on both layout breakpoints and real desktop behavior, do both passes: explicit viewport first for deterministic QA, then native-window validation for final environment-specific checks.
- Treat switching modes as a context reset. Do not reuse a viewport-emulated
contextfor a native-window pass or vice versa; close the oldpageandcontext, then create a new one for the new mode.
Start or Reuse Web Session
Desktop and mobile web sessions share the same browser, helpers, and QA flow. The main difference is which context and page pair you create.
Desktop Web Context
Set TARGET_URL to the app you are debugging. For local servers, prefer 127.0.0.1 over localhost.
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());
If context or page is stale, set context = page = undefined and rerun the cell.
Mobile Web Context
Reuse TARGET_URL when it already exists; otherwise set a mobile target directly.
var MOBILE_TARGET_URL = typeof TARGET_URL === "string"
? TARGET_URL
: "http://127.0.0.1:3000";
if (mobilePage?.isClosed()) mobilePage = undefined;
await ensureWebBrowser();
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());
If mobileContext or mobilePage is stale, set mobileContext = mobilePage = undefined and rerun the cell.
Native-Window Web Pass
var TARGET_URL = "http://127.0.0.1:3000";
await ensureWebBrowser();
await page?.close().catch(() => {});
await context?.close().catch(() => {});
page = undefined;
context = undefined;
browser ??= await chromium.launch({ headless: false });
context = await browser.newContext({ viewport: null });
page = await context.newPage();
await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });
console.log("Loaded native window:", await page.title());
Start or Reuse Electron Session
Set ELECTRON_ENTRY to . when the current workspace is the Electron app and package.json points main to the right entry file. If you need to target a specific main-process file directly, use a path such as ./main.js instead.
var ELECTRON_ENTRY = ".";
if (appWindow?.isClosed()) appWindow = undefined;
if (!appWindow && electronApp) {
await electronApp.close().catch(() => {});
electronApp = undefined;
}
electronApp ??= await electronLauncher.launch({
args: [ELECTRON_ENTRY],
});
appWindow ??= await electronApp.firstWindow();
console.log("Loaded Electron window:", await appWindow.title());
If js_repl is not already running from the Electron app workspace, pass cwd explicitly when launching.
If the app process looks stale, set electronApp = appWindow = undefined and rerun the cell.
If you already have an Electron session but need a fresh process after a main-process, preload, or startup change, use the restart cell in the next section instead of rerunning this one.
Reuse Sessions During Iteration
Keep the same session alive whenever you can.
Web renderer reload:
await reloadWebContexts();
Electron renderer-only reload:
await appWindow.reload({ waitUntil: "domcontentloaded" });
console.log("Reloaded Electron window");
Electron restart after main-process, preload, or startup 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());
If your launch requires an explicit cwd, include the same
Content truncated.
When not to use it
- →When the process ownership has changed
- →When the task is finished and cleanup is required
Prerequisites
Limitations
- →Requires js_repl to be enabled
- →Resetting the kernel destroys Playwright handles
How it compares
It keeps the browser instance alive across multiple tool calls, unlike standard testing workflows that restart the toolchain for every iteration.
Compared to similar skills
playwright-interactive side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| playwright-interactive (this skill) | 0 | 4mo | Review | Advanced |
| dependency-upgrade | 26 | 5mo | Review | Intermediate |
| upgrading-expo | 3 | 4mo | Review | Intermediate |
| pnpm | 4 | 6mo | No flags | Beginner |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by ComeOnOliver
View all by ComeOnOliver →You might also like
dependency-upgrade
wshobson
Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing. Use when upgrading framework versions, updating major dependencies, or managing breaking changes in libraries.
upgrading-expo
sickn33
Upgrade Expo SDK versions
pnpm
antfu
Node.js package manager with strict dependency resolution. Use when running pnpm specific commands, configuring workspaces, or managing dependencies with catalogs, patches, or overrides.
pnpm-upgrade
openai
Keep pnpm current: run pnpm self-update/corepack prepare, align packageManager in package.json, and bump pnpm/action-setup + pinned pnpm versions in .github/workflows to the latest release. Use this when refreshing the pnpm toolchain manually or in automation.
windsurf-linting-config
jeremylongshore
Configure and enforce code quality with AI-assisted linting. Activate when users mention "configure linting", "eslint setup", "code quality rules", "linting configuration", or "code standards". Handles linting tool configuration. Use when configuring systems or services. Trigger with phrases like "windsurf linting config", "windsurf config", "windsurf".
pnpm
valibali
Use when managing Node.js dependencies with pnpm - provides workspace setup, catalogs, CLI commands, overrides, and CI configuration