Skip to content

feat(cli): support authenticated measurement via --curl/--curl-file cookies - #2

Open
kokorolx wants to merge 1 commit into
nano-step:mainfrom
kokorolx:feature/curl-cookie-support
Open

kokorolx wants to merge 1 commit into
nano-step:mainfrom
kokorolx:feature/curl-cookie-support

Conversation

@kokorolx

@kokorolx kokorolx commented Jul 3, 2026

Copy link
Copy Markdown

Summary

  • Adds --curl <command> and --curl-file <path> to ohmyperf run, letting a curl command pasted from Chrome DevTools ("Copy as cURL") seed the browser context's cookies before navigation — so pages behind login can be measured.
  • Cookies are parsed out of the -H 'Cookie: ...' header or -b/--cookie flag (bash- and zsh-quoted curl exports both supported) and applied via Playwright's storageState.cookies, scoped by domain/path — not leaked to third-party origins the way a blanket extraHTTPHeaders would.
  • New CookieInput type on LaunchOpts (@ohmyperf/core) and cookies/extraHTTPHeaders support on PlaywrightAdapterOptions (@ohmyperf/driver-playwright), so the SDK consumers get this too, not just the CLI.

Why

ohmyperf currently only measures anonymous/logged-out pages — no way to pass auth. Real product pages (account, checkout, inventory, etc.) are behind login, so this closes a real gap for the tool's stated "measure and let an AI agent fix it" use case.

Test plan

  • tsc -b clean on packages/core, packages/driver-playwright, apps/cli
  • New unit tests for the curl parser (apps/cli/src/curl-cookies.test.ts) — bash header, -b flag, zsh $'...' header, no-cookie case
  • Existing driver-playwright and cli test suites pass (20/20, no regressions)
  • End-to-end: seeded cookies through the real createPlaywrightAdapter path and hit postman-echo.com/cookies — the server echoed back exactly the cookies parsed from the curl text
  • Ran the built CLI binary with --curl-file against a live URL to confirm the flag wiring works, not just the internal functions
  • Verified a bad --curl-file path exits cleanly with an error message instead of a raw stack trace

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the ability to measure authenticated pages by seeding cookies extracted from a raw curl command (e.g., copied from Chrome DevTools). It adds --curl and --curl-file options to the CLI, implements a parser to extract cookies from these curl commands, and updates the core types and Playwright driver to support seeding these cookies and extra HTTP headers into the browser context. Feedback focuses on improving the robustness of the curl cookie parser to handle unquoted headers, zsh/bash escaped single quotes, and adding corresponding test cases. Additionally, it is recommended to explicitly error out if both --curl and --curl-file arguments are provided simultaneously.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +6 to +27
const HEADER_COOKIE_RE = /(?:-H|--header)\s+\$?(['"])\s*cookie\s*:\s*(.*?)\1/is;
const COOKIE_FLAG_RE = /(?:-b|--cookie)\s+\$?(['"])(.*?)\1/is;

/**
* Extracts cookies from a raw curl command, e.g. one pasted from Chrome DevTools
* "Copy as cURL". Looks for a `-H 'Cookie: ...'` header or a `-b '...'` flag.
* Does not attempt full shell-quote parsing (no support for escaped quotes inside
* the cookie value) — good enough for the common browser-exported curl formats.
*/
export function parseCookiesFromCurl(curl: string): ParsedCookie[] {
const raw = curl.match(HEADER_COOKIE_RE)?.[2] ?? curl.match(COOKIE_FLAG_RE)?.[2];
if (!raw) return [];
return raw
.split(";")
.map((pair) => pair.trim())
.filter(Boolean)
.map((pair) => {
const eq = pair.indexOf("=");
if (eq === -1) return { name: pair, value: "" };
return { name: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim() };
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current regular expressions do not support unquoted cookie headers/flags, and they will truncate or corrupt cookie values containing single quotes (which are common in session/auth tokens) due to bash/zsh escaping patterns (e.g., \'' or \').

We can resolve this by:

  1. Normalizing bash-escaped single quotes ('\'') to \' before matching.
  2. Updating the regexes to match quoted strings with backslash escapes, or unquoted values.
  3. Unescaping the matched cookie string to restore the original characters.
const HEADER_COOKIE_RE = /(?:-H|--header)\s+(?:\$?(['"])\s*cookie\s*:\s*((?:(?!\1).|\\.)*)\1|cookie\s*:\s*([^\s'"]+))/is;
const COOKIE_FLAG_RE = /(?:-b|--cookie)\s+(?:\$?(['"])((?:(?!\1).|\\.)*)\1|([^\s'"]+))/is;

/**
 * Extracts cookies from a raw curl command, e.g. one pasted from Chrome DevTools
 * "Copy as cURL". Looks for a `-H 'Cookie: ...'` header or a `-b '...'` flag.
 * Supports bash-escaped single quotes, zsh ANSI-C quotes, and unquoted values.
 */
export function parseCookiesFromCurl(curl: string): ParsedCookie[] {
  const normalized = curl.replace(/'\\''/g, "\\'");
  const matchHeader = normalized.match(HEADER_COOKIE_RE);
  const matchFlag = normalized.match(COOKIE_FLAG_RE);
  const raw = matchHeader?.[2] ?? matchHeader?.[3] ?? matchFlag?.[2] ?? matchFlag?.[3];
  if (!raw) return [];
  const unescaped = raw.replace(/\\(.)/g, "$1");
  return unescaped
    .split(";")
    .map((pair) => pair.trim())
    .filter(Boolean)
    .map((pair) => {
      const eq = pair.indexOf("=");
      if (eq === -1) return { name: pair, value: "" };
      return { name: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim() };
    });
}

Comment thread apps/cli/src/commands/run.ts Outdated
Comment on lines +325 to +335
const curlFile = optString(args["curl-file"]);
let curlFileContents: string | undefined;
if (curlFile) {
try {
curlFileContents = readFileSync(curlFile, "utf8");
} catch (err) {
logger.error(`failed to read --curl-file '${curlFile}': ${err instanceof Error ? err.message : String(err)}`);
process.exit(EXIT_CODES.invalidUsage);
}
}
const curlRaw = optString(args.curl) ?? curlFileContents;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If both --curl and --curl-file are provided, the CLI silently prioritizes --curl and ignores --curl-file. It is safer and more user-friendly to explicitly error out when conflicting arguments are provided.

Suggested change
const curlFile = optString(args["curl-file"]);
let curlFileContents: string | undefined;
if (curlFile) {
try {
curlFileContents = readFileSync(curlFile, "utf8");
} catch (err) {
logger.error(`failed to read --curl-file '${curlFile}': ${err instanceof Error ? err.message : String(err)}`);
process.exit(EXIT_CODES.invalidUsage);
}
}
const curlRaw = optString(args.curl) ?? curlFileContents;
const curlFile = optString(args["curl-file"]);
const curlArg = optString(args.curl);
if (curlArg && curlFile) {
logger.error("cannot provide both --curl and --curl-file");
process.exit(EXIT_CODES.invalidUsage);
}
let curlFileContents: string | undefined;
if (curlFile) {
try {
curlFileContents = readFileSync(curlFile, "utf8");
} catch (err) {
logger.error(`failed to read --curl-file '${curlFile}': ${err instanceof Error ? err.message : String(err)}`);
process.exit(EXIT_CODES.invalidUsage);
}
}
const curlRaw = curlArg ?? curlFileContents;

Comment on lines +21 to +29
it("extracts cookies from an $'...' ANSI-C quoted header (Chrome zsh export)", () => {
const curl = `curl 'https://tradeit.gg/' -H $'cookie: session=abc123'`;
expect(parseCookiesFromCurl(curl)).toEqual([{ name: "session", value: "abc123" }]);
});

it("returns an empty array when no cookie header/flag is present", () => {
expect(parseCookiesFromCurl(`curl 'https://tradeit.gg/'`)).toEqual([]);
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Add test cases to verify the robust parsing of escaped single quotes (bash and zsh formats) and unquoted cookie headers/flags.

  it("extracts cookies from an $'...' ANSI-C quoted header (Chrome zsh export)", () => {
    const curl = `curl 'https://tradeit.gg/' -H $'cookie: session=abc123'`;
    expect(parseCookiesFromCurl(curl)).toEqual([{ name: "session", value: "abc123" }]);
  });

  it("extracts cookies with escaped quotes (bash and zsh formats)", () => {
    const curlBash = `curl 'https://tradeit.gg/' -H 'cookie: session=abc'\\''123; theme=dark'`;
    expect(parseCookiesFromCurl(curlBash)).toEqual([
      { name: "session", value: "abc'123" },
      { name: "theme", value: "dark" },
    ]);

    const curlZsh = `curl 'https://tradeit.gg/' -H $'cookie: session=abc\\\'123'`;
    expect(parseCookiesFromCurl(curlZsh)).toEqual([{ name: "session", value: "abc'123" }]);
  });

  it("extracts cookies from unquoted headers and flags", () => {
    expect(parseCookiesFromCurl(`curl https://tradeit.gg/ -b session=abc123`)).toEqual([
      { name: "session", value: "abc123" },
    ]);
    expect(parseCookiesFromCurl(`curl https://tradeit.gg/ -H cookie:session=abc123`)).toEqual([
      { name: "session", value: "abc123" },
    ]);
  });

  it("returns an empty array when no cookie header/flag is present", () => {
    expect(parseCookiesFromCurl(`curl 'https://tradeit.gg/'`)).toEqual([]);
  });

…ookies

Adds --curl / --curl-file to `ohmyperf run` so a curl command pasted from
Chrome DevTools ("Copy as cURL") can seed the browser context with the
Cookie header before navigation, enabling CWV measurement of pages behind
login. Cookies are set via Playwright's storageState (scoped by domain/path,
not leaked to third-party origins like extraHTTPHeaders would).

Also handles bash's `'\''` escape for a literal quote inside a cookie value,
and errors out if both --curl and --curl-file are passed together.
@kokorolx
kokorolx force-pushed the feature/curl-cookie-support branch from 2c50537 to 8df0c30 Compare July 3, 2026 09:26
@kokorolx

kokorolx commented Jul 3, 2026

Copy link
Copy Markdown
Author

Addressed in 8df0c30:

  • --curl and --curl-file now error out as mutually exclusive instead of silently letting --curl win.
  • Handled bash's '\'' escape (literal ' inside a single-quoted arg) so a cookie value containing an apostrophe isn't truncated — added a test for it.

Left as-is: unquoted -H Cookie:... headers. Cookie headers always contain ; between pairs once there's more than one cookie, which requires quoting in the shell — an unquoted single-cookie case is a valid but vanishingly rare curl invocation, and browser "Copy as cURL" exports (the actual target use case) always quote it. Not adding it to keep the parser's regex surface area small; happy to add if it turns out to matter in practice.

Also amended the commit author to match my account.

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.

1 participant