Skip to content

Add browser command as Playwright CLI proxy#644

Open
BLamy wants to merge 1 commit into
mainfrom
add-browser-command
Open

Add browser command as Playwright CLI proxy#644
BLamy wants to merge 1 commit into
mainfrom
add-browser-command

Conversation

@BLamy

@BLamy BLamy commented Feb 10, 2026

Copy link
Copy Markdown

Adds a new replayio browser command that proxies to the bundled Playwright CLI, making it easy to control the browser for recording sessions. Features include session management, auto-upload on close, and generated Playwright test output.

@changeset-bot

changeset-bot Bot commented Feb 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 835ff34

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@socket-security

socket-security Bot commented Feb 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​playwright/​cli@​0.1.0661009796100

View full report

@socket-security

socket-security Bot commented Feb 10, 2026

Copy link
Copy Markdown

Caution

Review the following alerts detected in dependencies.

According to your organization's Security Policy, you must resolve all "Block" alerts before proceeding. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Block Low
Potential code anomaly (AI signal): npm playwright-core is 90.0% likely to have a medium risk anomaly

Notes: The script is a thin installer helper that downloads and runs a user-supplied macOS .pkg with sudo. The script itself contains no embedded malware or obfuscation, but it provides a high-risk primitive: executing arbitrary installer payloads as root without integrity checks. This makes it suitable for supply-chain abuse or full system compromise if the fetched package or the network stream is controlled by an attacker. Do not run this script with untrusted URLs; require cryptographic verification, whitelisting, or manual inspection before installation.

Confidence: 0.90

Severity: 0.90

From: ?npm/@playwright/cli@0.1.0npm/playwright-core@1.59.0-alpha-1770426101000

ℹ Read more on: This package | This alert | What is an AI-detected potential code anomaly?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: An AI system found a low-risk anomaly in this package. It may still be fine to use, but you should check that it is safe before proceeding.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/playwright-core@1.59.0-alpha-1770426101000. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Adds a new `replayio browser` command that proxies to the bundled
Playwright CLI, making it easy to control the browser for recording
sessions. Features include session management, auto-upload on close,
and generated Playwright test output.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@BLamy BLamy force-pushed the add-browser-command branch from 69eaaf8 to 835ff34 Compare February 10, 2026 18:00
],
"homepage": "https://github.com/replayio/replay-cli/blob/main/packages/replayio/README.md",
"dependencies": {
"@playwright/cli": "latest",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not use latest like this, it's a pretty new package (2 weeks!) so I wouldn't be surprised if the interface changes somehow soon

return sessions;
}

function escapeRegex(value: string): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function escapeRegex(value: string): string {
// replace with RegExp.escape once we can get away with node 24 as minimum supported version
function escapeRegex(value: string): string {


function resolveFromPackage(): ResolvedCommand | null {
try {
const pkgPath = require.resolve("@playwright/cli/package.json");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use resolve-from or require.resolve(pkgName, { paths: [__dirname] })

}
}

async function uploadRecordingsInSubprocess(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this happen in a subprocess? u can just import things here and call an uploading util directly

exitCode,
generatedTest,
});
process.stdout.write(`${JSON.stringify(payload)}\n`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage of process.stdout.write in this file feels quite excessive - most of those calls could easily be a normal-looking console.log

Comment on lines +35 to +38
const ENV_PATH_KEYS = [
"REPLAYIO_PLAYWRIGHT_CLI_PATH",
"REPLAY_PLAYWRIGHT_CLI_PATH",
];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I don't think you need to handle both. The new env variables are just using REPLAY_ prefix (the old format used RECORD_REPLAY_)

"REPLAYIO_PLAYWRIGHT_CLI_PATH",
"REPLAY_PLAYWRIGHT_CLI_PATH",
];
const PLAYWRIGHT_EXECUTABLE_PATH_ENV = "PLAYWRIGHT_MCP_EXECUTABLE_PATH";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: just inline this... having this constant doesn't benefit the code anyhow


program
.command("browser")
.description("Proxy to the bundled Replay Playwright CLI")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Technically, it's not bundled - it just gets installed alongside replayio given it's a regular dependency. But perhaps this wording makes sense here as a simplification

Comment on lines +555 to +558
const binRelative =
typeof binValue === "string"
? binValue
: binValue?.["playwright-cli"] ?? binValue?.playwright ?? Object.values(binValue ?? {})[0];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this really feels like redundant future-proofing. Literally all published versions of this package have this:

  "bin": {
    "playwright-cli": "./playwright-cli.js"
  }

You can't predict the future, just handle what's there right now

return blocks;
}

function appendSessionSnippets(session: string, command: string, codeBlocks: string[]) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels like making this session snippets file an append log would be better here, you could utilize appendFileSync instead of having to read the file, parse JSON and then write it back again. When reading the append log u can read it, split by new lines, parse individual lines and reduce the entries to what u want.

Comment on lines +376 to +378
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already available in node itself in timers/promises

RECORD_ALL_CONTENT: process.env.RECORD_ALL_CONTENT || "1",
RECORD_REPLAY_METADATA: JSON.stringify({
...metadata,
browserSession: session,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one feels redundant (I don't see it actually being used anywhere), especially given u already put session in the processGroupId

for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === PLAYWRIGHT_SESSION_FLAG) {
session = args[i + 1] ?? session;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would interpret -s --cdp to set "--cdp" as the session name, right? that sounds like a bug

const session = resolveBrowserSession(args);
const processGroupId = getSessionProcessGroupId(session);
const scopedRecordings = getRecordings(processGroupId);
const fallbackRecordings = getRecordings();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are those fallback recordings even computed here? it feel like overly defensive measure that muddies the true intent of the code. The processGroupId should "just work"

context: SessionCloseContext,
options: { silent: boolean }
) {
const recordings = await waitForSessionRecordings(context);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cant u trust the browser process to be closed at this point? if u can then polling implemented in this function would be redundant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants