browser-recorder
v0.1.3
Published
Record a real browser session into a faithful, plain Patchright script, managed from a terminal UI
Maintainers
Readme
browser-recorder
Record a real browser session and get a faithful, plain
Patchright script back
(Patchright is a Playwright-compatible engine), managed from a terminal UI. You
click, type, scroll, drag and answer dialogs in a real Chromium-based browser
window; the app writes a script.mjs that replays it with the same timing, and
lets you replay, rename and delete recordings from a library.
Requirements
- Node.js 22.13 or newer.
- pnpm 10.16 or newer when running from a source checkout.
- An interactive terminal for the UI (it refuses to start through a pipe). The
replaycommand line below works without one. - Chromium: installed for you on first start (about 150 MB, one time) through
Patchright. If that install fails (for example offline), the screen shows
the installer's exit code and the manual command
(
npm exec -y -p [email protected] -- patchright install chromium). If another browser was detected you can still record with it; presslto keep using the library (list, rename, delete, timeline), orenterto retry.
On Linux, Chromium also needs system libraries. If the browser fails to launch for missing libraries, the app prints the command and leaves running it to you:
sudo npm exec -y -p [email protected] -- patchright install-deps chromiumInstall and run
Install the published CLI globally with npm:
npm install --global browser-recorder
browser-recorderFor development from a source checkout, use the pinned pnpm toolchain:
pnpm install
pnpm start # builds, then opens the terminal UIOr build once and run the entry point directly:
pnpm build
node dist/main.js # the same file the `browser-recorder` bin points toUsage
- New recording: type a name and, optionally, a start URL (http or https), then pick a browser and a profile (see below). A browser window opens; use it as you normally would.
- The timeline fills in live. Press
sto stop and save, orxto discard. Closing the browser window also saves and ends the recording. - Library: pick a recording to replay, view its timeline, rename or delete it.
- A replay opens a browser, runs the script and shows each step with how far
it drifted from the recorded time. Press
hin the library first to replay with human-like pauses instead (see below); the choice is for that replay only and is never remembered.
Dialogs (alert, confirm, prompt) are answered from the recorder: press a
to accept, d to dismiss, or type the text for a prompt and press Enter. Answers
given in the browser's own dialog are recorded too.
What is captured: clicks (any button, with modifiers), double clicks, checkbox and radio changes, typing, selects, key presses and shortcuts, scrolling, drag and drop, file inputs (names only), dialogs, new tabs, page closes, hovers that open menus, and navigation (goto, redirects, reload, back, forward), including iframes and cross-origin iframes. Locators are chosen in this order: test id, role and name, label, placeholder, a stable id, exact text, a CSS path.
Keys
| Where | Keys |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Everywhere | Ctrl+C saves a live recording and quits |
| Menus and lists | Up / Down or k / j, PgUp / PgDn, Enter to open, q to quit |
| New recording | Tab / Up / Down switch field, Left / Right change the browser or profile, Enter start, Esc back, Ctrl+U clear |
| Recording | s stop and save, x discard (then y / n) |
| Recording dialogs | a accept, d dismiss; prompt: type the text, Enter accept, Esc dismiss |
| Library | Enter / p replay, h human or recorded timing, t timeline, r rename, d delete (y / n), n new |
| Timeline | Up / Down, PgUp / PgDn, Esc back |
| Replay | c or Esc cancel while running, Esc back when finished |
NO_COLOR turns colors off.
Command line
Replay one saved recording without opening the UI:
browser-recorder replay <name|slug> [options]
pnpm replay <name|slug> [options] # builds first, then the same commandThe argument is matched against the recording's slug first, then against its
name ignoring case. No match, or a name that two recordings share, exits with
code 2 and (for a shared name) lists the slugs to choose from. Running
browser-recorder with no argument still opens the terminal UI.
| Option | What it does |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| -r, --random | Human-like pauses between steps (see below), using the default range 250-900. |
| -d, --delay <min-max> | The pause range in milliseconds, whole numbers, 0 <= min <= max <= 60000; implies --random. |
| --headless | Run without a visible browser window. |
| -h, --help | Print the usage and exit 0. |
| -v, --version | Print the version of package.json and exit 0. |
Without -r or -d the replay keeps the recorded timing, exactly as the UI
does. Options go after the recording: pnpm replay demo -r -d 100-300.
pnpm adds its own banner and an ELIFECYCLE line on failure; for clean output
use pnpm -s replay demo or pnpm exec browser-recorder replay demo. A negative
looking delay needs the equals form: --delay=-5-10 (it is refused anyway).
Output. Each step prints one line on stdout ([3/12] click Save button
(1.2s)) and the run ends with ✔ <name> replayed in <s>. Warnings, such as
falling back to the bundled Chromium or the network never going quiet before
the browser closed or a hover that could not be performed, errors and the usage after a mistake go
to stderr. A failure prints ✖ <name> failed at step <n> (<kind>): <message>
and the last stderr lines of the script, also on stderr. Typed values, passwords
included, are never printed: a step shows what it acts on, not what it types.
Colors appear only when stdout is a terminal and never with NO_COLOR, on
stdout and stderr alike; a pipe gets plain text. The command never installs a browser: when Chromium is missing it prints
the manual command (npm exec -y -p [email protected] -- patchright install chromium) and exits 1.
| Exit code | Meaning |
| --------- | ----------------------------------------------------------------- |
| 0 | The replay succeeded |
| 1 | The replay failed, could not start, or the recording is invalid |
| 2 | Usage error, recording not found or ambiguous |
| 130 | Interrupted with Ctrl+C (143 when the process received SIGTERM) |
Ctrl+C cancels cleanly: the script closes the browser, the temporary profile is
deleted and the command prints ■ <name> cancelled before it exits. On Windows
Ctrl+C and Ctrl+Break take the same path; the automated Ctrl+C test is skipped
there because Node cannot send SIGINT to a child process on that platform.
Human timing
By default a replay waits until each step's recorded offset. In human mode it instead waits a random delay in the range after the previous step finished: no wait before the first step, and none before a step that only observes a consequence (a URL change, a dialog, a file chooser or a tab opened by an action). Typing is done key by key with a pause of a tenth of the range between keys, and the field always ends with the exact recorded value. Drift is not reported in human mode, because the recorded offsets no longer apply.
The mode is chosen at replay time and is not saved in the recording. The script reads it from the environment, so you can also run it by hand:
| Variable | Value |
| ------------------------------ | ------------------------------------------------------------- |
| BROWSER_RECORDER_TIMING | recorded (default; any other value means recorded), human |
| BROWSER_RECORDER_HUMAN_DELAY | <min>-<max> in milliseconds, default 250-900 |
| BROWSER_RECORDER_SEED | Optional, up to ten digits: the same seed repeats the pauses |
Browsers and profiles
The browser picker lists what is installed on this machine, in this order: Brave, Chrome, Edge, Chromium, Vivaldi and Opera, then the bundled Chromium (always available, installed on first start). The first detected browser with a managed profile is preselected. Firefox and Safari are not supported: the recorder drives Chromium-based browsers only.
Each recording remembers its browser and profile mode, and a replay uses the same ones. If that browser is gone, the replay runs on the bundled Chromium and shows a warning that names the missing browser.
| Profile | What it is |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| Managed | One profile per browser that this tool owns and keeps between recordings and replays: log in once and stay logged in. |
| Copy of a profile | A snapshot of one of the browser's own profiles (Default, Profile 2, ...), made on every launch. |
| Ephemeral | An empty profile that is deleted when the session ends. |
Managed, copied and ephemeral profiles live under the tool's own app-data
folder (~/Library/Application Support/browser-recorder on macOS,
%LOCALAPPDATA%\browser-recorder on Windows, $XDG_DATA_HOME/browser-recorder
or ~/.local/share/browser-recorder on Linux), never next to your browser's
own data.
Copy of a real profile, explained honestly
- It copies the profile on every launch into a fresh private folder and deletes the copy afterwards. It never modifies the original: the copy opens files read-only, and a test checks that the source stays byte for byte the same. Close the browser first when you can; if it is running, the snapshot may miss its latest changes and the screen says so.
- Why a copy and not the profile itself: since Chrome 136 a browser refuses to expose remote debugging on its default profile directory, and two browsers cannot share one profile folder anyway.
- A copy keeps your logins only if the browser can decrypt the cookies again. On macOS the browser reads its key from the keychain, so the system may ask for permission the first time. On Windows, Chrome, Brave and Edge protect new cookies with app-bound encryption that only the original install can open: the recorder warns before launching, and the copy probably will not be logged in.
- Opera keeps its profile directly in its data folder, so copying a profile is not offered for it; use managed or ephemeral.
- A profile that another browser process holds cannot be launched twice: the recorder shows an error naming the browser instead of hanging.
Where things are stored
recordings/<slug>/recording.json the recorded events (source of truth)
recordings/<slug>/script.mjs the generated Patchright script
recordings/<slug>/files/ files for file inputs, picked by namescript.mjs is plain ESM that imports only patchright. The replay passes it
the browser through four environment variables
(BROWSER_RECORDER_EXECUTABLE_PATH, BROWSER_RECORDER_USER_DATA_DIR,
BROWSER_RECORDER_BROWSER_ARGS, BROWSER_RECORDER_REAL_KEYCHAIN); run on its
own it uses the bundled Chromium on a temporary profile. A script written by an
older version is regenerated before every replay. It prints
::step <index> <ms> before each step, ::done <ms> at the end and
::error ... on failure. Before closing the browser on success it waits until
the network is quiet (no request in flight for 500 ms, at most 5 s) and prints
::warn "..." when it gave up at the cap; the warning counts the requests
still in flight and never lists URLs. Hovers are best effort: one that cannot be
performed within 2 s (covered by a modal, missing, hidden) prints
::warn "Skipped the hover of step <n>: ..." with the first line of the reason
only, and the replay goes on; every other step stays strict. While recording, a
page change caused by a click or key press in the 400 ms before is not blamed on
the element under the pointer. A step that follows a click waiting for a
page change waits for a NEW navigation, even to the same URL. All of it is
tracked from Node through Patchright events, never with code in the page. In the default recorded mode it sleeps until each
step's recorded offset, so a slow step does not shift the ones after it; see
Human timing for the other mode. Run one yourself with
node recordings/<slug>/script.mjs; set BROWSER_RECORDER_HEADLESS=1 to run
without a window.
Recordings are plaintext
Typed values, including passwords, are stored as typed in recording.json and
script.mjs. Password fields are only flagged, never masked in the files.
recordings/ is git-ignored: never commit, paste or log its contents. See
SECURITY.md.
Development
pnpm quality # typecheck, lint, format, dead code, dependency rules, tests
pnpm test:coverage # tests with coverage thresholds
pnpm build # tsc plus the bundled in-page capture scriptEvery test and probe runs headless: nothing opens a browser window. A test that
genuinely needs a headed browser is skipped unless
BROWSER_RECORDER_HEADED_TESTS=1 is set. The tests never read or write your
real browser profiles or app-data folders: they run on an isolated home.
Tests that launch your installed browser are opt-in, because they depend on what is installed on the machine:
BROWSER_RECORDER_REAL_BROWSER_TESTS=1 pnpm testThey copy only from the made-up profiles under tests/fixtures/profiles (a
test asserts that), launch the detected browser headless and check that the
fixture stays unchanged. On macOS they may trigger a keychain prompt, because
a copy of a profile uses the real keychain. Set
BROWSER_RECORDER_REAL_BROWSER_PATH to point them at a browser that is not
in the catalogue. The layout and the working agreements are in
AGENTS.md.
Contributing
See CONTRIBUTING.md and the Code of Conduct. Report vulnerabilities privately, as described in SECURITY.md.
License
browser-recorder is source-available under the PolyForm Noncommercial License 1.0.0. It is free to use, modify and share for non-commercial purposes. Commercial use requires permission from the author: contact the owner through GitHub. This is not an OSI-approved open source license (it is not an OSI open-source license), so the software is source-available rather than open source.
