npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@douglance/play

v0.2.0

Published

Run Playwright scripts with zero boilerplate

Readme

@douglance/play

@douglance/play is a zero-boilerplate Playwright runner with persistent sessions and real page debugger control built in.

You can still run one-shot scripts, but the primary workflow is now:

  1. open a named browser session
  2. execute small action batches with play do
  3. trigger or hit a real page-side debugger;
  4. inspect and control the paused browser debugger from the CLI
  5. continue or step without leaving the play session

Why play

  • No imports, fixtures, or browser lifecycle code
  • Built-in globals like goto(), click(), and text() are auto-awaited
  • Smart selector resolution for roles, labels, placeholders, text, and test IDs
  • goto() can auto-detect common local dev servers
  • Assertions produce CI-friendly exit codes
  • Persistent named sessions for agent iteration
  • Real Chromium page debugger control through CDP
  • Debugger queries over paused browser state
  • Raw Playwright escape hatches are still available through page, browser, and context

Install

npm install --save-dev @douglance/play

Then run it with the bundled play binary:

npx play 'goto("https://example.com"); log(text("h1"))'

For one-off usage without adding it to your project:

npx @douglance/play 'goto("https://example.com"); log(title())'

Quick Start

Inline script:

play 'goto("https://example.com"); log(text("h1"))'

Script file:

play check-homepage.play.ts

From stdin:

echo 'goto("https://example.com"); screenshot("homepage.png")' | play

Persistent agent loop:

play open agent1 --headed --devtools
play do agent1 'goto("https://example.com")'
play do agent1 'evaluate(() => { debugger; })'
play debug status agent1
play debug step-over agent1
play debug continue agent1
play close agent1

Modes

play now has two modes:

  1. One-shot mode: play 'goto("https://example.com"); log(title())'

    Each invocation launches a browser, runs the script, prints output, and exits.

  2. Session mode: play open agent1 + play do agent1 '...'

    The browser, context, page, and page debugger state stay alive across commands until play close.

Built-in globals are auto-awaited, so you can write:

goto("https://example.com")
click("Sign in")
log(text("h1"))

If you use raw Playwright objects like page, browser, or context, write normal Playwright code and use await yourself.

TypeScript syntax is accepted for inline code and .ts script files. Type annotations are stripped before execution.

Persistent Session Workflow

Open a session:

play open agent1

Run actions inside the same live browser:

play do agent1 'goto("https://example.com"); log(title())'
play do agent1 'click("More information")'

Inspect or list sessions:

play status
play status agent1

Open a visible Chromium debug session:

play open agent1 --headed --devtools

Run actions in the live page:

play do agent1 'goto("https://example.com"); click("More information")'

Inspect the real page debugger:

play debug status agent1
play debug query agent1 'frames()'
play debug query agent1 'frame(0)'

Pause or continue the browser debugger:

play debug pause agent1
play debug step-over agent1
play debug step-into agent1
play debug step-out agent1
play debug continue agent1

Close the session:

play close agent1

Assertions and Exit Codes

assert(condition, message?) records pass/fail results without stopping the rest of the script. If any assertion fails, play exits with code 1.

play 'goto("https://example.com"); assert(text("h1") === "Example Domain", "heading correct")'

In session mode, assertions and output are still reported batch by batch.

Debugging and Queries

play debug now means only one thing: real page debugger control.

The debugger surface is Chromium/CDP-based and is intended to drive actual paused page JavaScript, not a synthetic automation-step pause:

play open agent1 --headed --devtools
play do agent1 'evaluate(() => { debugger; })'
play debug status agent1
play debug step-over agent1
play debug continue agent1

play debug query evaluates against a restricted paused-debugger query root with helpers such as:

  • status()
  • frames()
  • frame(0)
  • frame(0).scopeChain()
  • frame(0).vars()
  • frame(0).var("name").props()
  • scripts()
  • script("pattern").lines(start, end)
  • breakpoints()

play debug eval evaluates in the paused call frame when the debugger is stopped, and falls back to runtime evaluation otherwise.

Smart Selectors

Selector handling is intentionally forgiving:

  • CSS selectors are used directly for inputs like .cta, #main, or [data-testid="hero"]
  • XPath selectors are used directly for inputs like //button
  • Plain strings fall through a smart cascade that tries roles, labels, placeholders, exact text, partial text, and test IDs
  • fill() prioritizes labels and placeholders before role and text matching

That means commands like click("Submit"), fill("Email", "..."), and wait(".results") work without writing verbose Playwright locators most of the time.

Local Dev Server Detection

If you call goto() without a URL, play tries to find a local app automatically.

It reads package.json, inspects scripts.dev, and infers ports for common setups such as:

  • vite
  • next dev
  • nuxt dev
  • astro dev
  • webpack serve

Explicit --port or -p flags take priority. If no port can be inferred, play falls back to scanning common local ports.

You can always override detection:

play --port 3000 'goto(); log(title())'
play --url "http://localhost:8080" 'goto(); log(url())'

Session Commands

| Command | Description | | --- | --- | | play open <name> [--headed] [--devtools] | Start a persistent named browser session | | play status [name] | List sessions or inspect one session | | play do <name> '<actions>' | Run a batch of actions in an existing session | | play close <name> | Close the session | | play debug status <name> | Show real page debugger status | | play debug pause <name> | Ask the page debugger to pause | | play debug continue <name> | Resume the paused page debugger | | play debug step-over <name> | Step over one paused debugger line | | play debug step-into <name> | Step into one paused debugger line | | play debug step-out <name> | Step out of the current paused frame | | play debug eval <name> '<expr>' | Evaluate against the paused call frame or runtime | | play debug source <name> [file start end] | Show source around the current or named script | | play debug query <name> '<expr>' | Query paused debugger frames/scripts/breakpoints |

One-Shot Options

| Flag | Short | Description | | --- | --- | --- | | --headed | -h | Show the browser window | | --slow <ms> | -s | Add slow motion delay between Playwright actions | | --port <port> | -p | Base local port for goto() | | --url <url> | -u | Base URL for goto() | | --timeout <ms> | | Set Playwright action timeout and page default timeout | | --browser <name> | | Choose chromium, firefox, or webkit | | --device <name> | | Emulate a Playwright device profile such as iPhone 14 | | --devtools | | Auto-open Chromium DevTools for one-shot runs |

Built-In Globals

The following globals are available in every script:

| Category | Globals | | --- | --- | | Navigation | goto(url?), reload(), back(), forward() | | Interaction | click(sel), dblclick(sel), fill(sel, value), type(sel, text), press(key), check(sel), uncheck(sel), select(sel, value), hover(sel), focus(sel), clear(sel), upload(sel, path), drag(from, to) | | Extraction | text(sel), texts(sel), attr(sel, name), html(sel), value(sel), count(sel), visible(sel), title(), url(), content(), table(sel?), evaluate(fn) | | Waiting | wait(target), waitFor(target), sleep(ms) | | Media | screenshot(path?), pdf(path?) | | Network | mock(url, handler), waitForResponse(url), waitForRequest(url) | | Output | log(...args) | | Assertions | assert(condition, message?) | | Escape hatches | page, browser, context |

wait() and waitFor() treat values starting with * or / as URL patterns. Everything else is treated as an element target.

Advanced Example

Use the built-ins for the common path and drop to raw Playwright when needed:

goto("https://example.com")

const response = await page.request.get("https://example.com")
log(await response.text())

JS API

import {
	closePlaySession,
	continuePlayDebug,
	getPlayDebugStatus,
	openPlaySession,
	queryPlayDebug,
	runPlayActions,
	stepOverPlayDebug,
} from "@douglance/play";

await openPlaySession("agent1", { headed: true, devtools: true });
await runPlayActions("agent1", 'goto("https://example.com")');
await runPlayActions("agent1", 'evaluate(() => { debugger; })');
await getPlayDebugStatus("agent1");
await stepOverPlayDebug("agent1");
await queryPlayDebug("agent1", "frames()");
await continuePlayDebug("agent1");
await closePlaySession("agent1");

License

MIT