feat: add Playwright test agents system — interactive recorder CLI, AI agents, and codegen transform#10694
feat: add Playwright test agents system — interactive recorder CLI, AI agents, and codegen transform#10694christian-byrne wants to merge 27 commits intomainfrom
Conversation
…I agents, and codegen transform
Three integrated systems for AI-assisted browser test creation:
1. comfy-test CLI (tools/test-recorder/)
- Interactive 7-step recording flow for QA testers and non-developers
- Environment checks with platform-specific install guidance
- Codegen-to-convention transform engine
- PR creation via gh CLI or manual GitHub web UI instructions
- Commands: record, transform, check, list
2. Playwright AI agents (.claude/agents/)
- Planner, generator, and healer agents patched with ComfyUI context
- Regeneration scripts for Playwright updates (scripts/update-playwright-agents.sh)
- MCP server config (.mcp.json) for agent browser interaction
- Seed test and specs directory for agent-generated tests
3. Codegen transform skill (.claude/skills/codegen-transform/)
- Transform rules: @playwright/test → comfyPageFixture, page → comfyPage,
remove goto, canvas locators, waitForTimeout → nextFrame
- Structural transforms: wrap in describe with tags, add afterEach cleanup
- Fixture API reference and before/after examples
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR introduces a comprehensive test recording and transformation infrastructure for ComfyUI. It adds three Claude agents for test planning, generation, and healing; a CLI tool ( Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as Record CLI
participant Checks as Environment<br/>Checks
participant Install as Dependency<br/>Install
participant Recorder as Playwright<br/>Recorder
participant Browser as Browser<br/>(Inspector)
participant Backend as ComfyUI<br/>Backend
participant Transform as Transform<br/>Engine
participant Output as Output File
User->>CLI: pnpm comfy-test record
CLI->>Checks: Run prerequisite checks
Checks->>Checks: Platform, Node, pnpm, Python,<br/>Playwright, Git, Xcode, GitHub CLI
Checks-->>CLI: Results
CLI->>Install: pnpm install (if needed)
Install-->>CLI: Dependencies ready
CLI->>User: Prompt for test name & description
User-->>CLI: Input provided
CLI->>Recorder: Create recording session
Recorder->>Browser: Launch Chromium + Inspector
Browser->>Backend: Connect to ComfyUI
Browser-->>User: Ready for interaction
User->>Browser: Record test steps
Browser->>Backend: Execute actions
Browser-->>Recorder: Capture codegen
User->>Recorder: Stop recording
Recorder-->>CLI: Raw test code
CLI->>User: Paste recorded code
User-->>CLI: Code pasted
CLI->>Transform: transform(rawCode, options)
Transform->>Transform: Apply regex rules<br/>+ structural transforms
Transform-->>CLI: Transformed code
CLI->>Output: Write to .spec.ts
Output-->>CLI: File written
CLI->>User: Show PR creation options
User->>CLI: Create PR (if authenticated)
CLI-->>User: PR created or manual steps
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (9)
tools/test-recorder/src/ui/logger.ts (1)
18-20: Unusedwaitfunction.The
waitfunction is defined but never exported or used. Consider removing it or exporting it if intended for future use.♻️ Option: Remove or export
Either remove the unused function:
-function wait(label: string) { - console.log(` ${pc.blue('⏳')} ${label}`) -}Or export it for use elsewhere:
-function wait(label: string) { +export function wait(label: string) { console.log(` ${pc.blue('⏳')} ${label}`) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/ui/logger.ts` around lines 18 - 20, The function wait is declared but unused; either remove the unused function wait from the module to clean up dead code, or if it was intended for external use, export it (e.g., add an export for wait) and ensure callers/imports are updated; update any related tests or imports to reflect the removal or exported symbol so the logger module has no unused declarations.tools/test-recorder/src/checks/python.ts (1)
10-11: Use a single stable check name for pass/fail paths.Returning
'Python'on success and'Python 3'on failure makes result labeling inconsistent. Use one canonical name in both branches.Also applies to: 17-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/checks/python.ts` around lines 10 - 11, The check uses inconsistent names for the same check across success/failure branches (e.g., pass('Python', version) vs returning name: 'Python 3'); update all places in this file (including the pass() calls and the returned result objects in both success and failure paths, e.g., where name is set) to use one canonical check name (suggest "Python") so the pass('Python', ...) call and every return { name: ..., ok: ..., version } use the identical string.tools/test-recorder/package.json (1)
7-14:binentry appears unused for this private workspace package.
comfy-testis a private package not meant for publication. The actual usage path is the root package'scomfy-testscript, which runstsx tools/test-recorder/src/index.tsdirectly—bypassing thebinentry entirely. The./dist/index.jstarget is never built or invoked.Either remove the unused
binentry, or (if this tool may be published later) add apreparehook to ensure the dist folder exists:Suggested fix (if bin is needed)
"scripts": { "build": "tsc", + "prepare": "pnpm build", "dev": "tsc --watch", "typecheck": "tsc --noEmit" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/package.json` around lines 7 - 14, The package.json currently defines a "bin" entry for "comfy-test" pointing to "./dist/index.js" but that dist artifact is never built or used; either remove the unused "bin" field entirely, or add a "prepare" npm script that runs the build so the CLI artifact exists when publishing (e.g., wire a "prepare" script to the existing "build" task), and ensure "scripts.build" ("tsc") and the "comfy-test" bin name remain consistent if you keep the bin entry.tools/test-recorder/src/checks/platform.ts (1)
8-15: Avoid classifying unknown platforms as Linux.Line 13-14 currently maps every non-macOS/non-Windows platform to Linux, which can yield incorrect install guidance. Consider returning an explicit
unknownplatform and handling it with generic instructions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/checks/platform.ts` around lines 8 - 15, The switch over os.platform() currently maps any non-'darwin'/'win32' value to 'linux'; change the default case to return 'unknown' (or 'other') instead of 'linux' so unknown platforms aren't misclassified, and update any call sites that consume this platform value to handle 'unknown' by showing generic install instructions or prompting the user for platform details; target the switch statement that inspects os.platform() and modify the default branch and related platform-handling logic accordingly.browser_tests/AGENTS.md (1)
33-38: Add the preferred local Playwright run command to the “After Making Changes” checklist.Consider including
pnpm test:browser:localhere so contributors run the same default local E2E command used across the repo.Based on learnings: For agents making changes locally in the Comfy-Org/ComfyUI_frontend repository,
pnpm test:browser:localis the preferred default command for running Playwright E2E tests.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@browser_tests/AGENTS.md` around lines 33 - 38, Update the "After Making Changes" checklist under the "After Making Changes" heading to include the preferred local Playwright command by adding a new bullet recommending `pnpm test:browser:local` so contributors run the same default local E2E command used across the repo; ensure the entry matches the style of the surrounding bullets and sits alongside the existing `pnpm typecheck:browser`, `pnpm exec eslint`, and `pnpm exec oxlint` items.tools/test-recorder/src/pr/manual.ts (1)
20-20: Normalize path to forward slashes before display.The function is exported and receives
relativePathas a string with no guarantee of normalization. While the current call site inrecord.tspasses a hardcoded forward-slash path, defensive normalization would align with patterns used elsewhere in the codebase (e.g.,build/plugins/comfyAPIPlugin.ts,lint-staged.config.ts) and prevent potential GitHub path misplacement if this function is called with platform-specific separators.` 3. Set path to: ${options.relativePath.replace(/\\/g, '/')}`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/pr/manual.ts` at line 20, The template currently inserts options.relativePath directly; normalize it to forward slashes before display by replacing backslashes with slashes (e.g., transform options.relativePath via a replace of "\\" with "/") where the template contains `${options.relativePath}` in the exported function in manual.ts so the displayed path is always forward-slash normalized.tools/test-recorder/src/pr/gh.ts (2)
17-36:checkGhAvailableis markedasyncbut performs no asynchronous operations.The function uses only synchronous
execSynccalls. Either remove theasynckeyword or consider using async spawn methods for consistency with the async API.♻️ Proposed fix
-export async function checkGhAvailable(): Promise<{ +export function checkGhAvailable(): { available: boolean authenticated: boolean -}> { +} {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/pr/gh.ts` around lines 17 - 36, The function checkGhAvailable is declared async but only uses synchronous execSync calls; remove the async keyword to avoid misleading the API or refactor to use asynchronous child_process methods (e.g., promisified exec or spawn) instead. Locate checkGhAvailable and replace the declaration to be a plain synchronous function (remove async) or change the execSync calls to async equivalents (promisified exec/child_process.spawn wrapped in promises), ensuring returned object shape ({ available, authenticated }) and error handling remain the same.
17-36: Consider consolidating withcheckGh()inchecks/gh.tsto avoid duplication.Both this function and
checkGh()intools/test-recorder/src/checks/gh.tsexecute the samegh --versionandgh auth statuscommands. Consider extracting shared logic or reusingcheckGh()here to maintain DRY principles.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/pr/gh.ts` around lines 17 - 36, The checkGhAvailable() implementation duplicates logic from checkGh(); replace its body by reusing the existing checkGh() helper or extract a shared helper that runs "gh --version" and "gh auth status" so both checkGh() and checkGhAvailable() call it; update imports to bring in checkGh() (or the new shared helper), adapt return shape if needed to match {available:boolean, authenticated:boolean}, and remove the duplicated execSync calls inside checkGhAvailable().tools/test-recorder/src/commands/record.ts (1)
35-42:readMultilinedoesn't handle readline errors.If stdin encounters an error, the promise will never resolve (no rejection handler). Consider adding error handling.
♻️ Proposed fix
function readMultiline(): Promise<string> { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { const lines: string[] = [] const rl = createInterface({ input: process.stdin }) rl.on('line', (line) => lines.push(line)) rl.on('close', () => resolve(lines.join('\n'))) + rl.on('error', (err) => reject(err)) }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/commands/record.ts` around lines 35 - 42, The readMultiline Promise does not handle stream errors so it can hang; update the readMultiline function (which uses createInterface and rl) to attach an 'error' listener that rejects the Promise with the error, and ensure the listener cleans up (e.g., calls rl.close() or removes listeners) to avoid leaks; also consider listening to process.stdin 'error' or forwarding that to the same rejection so any stdin error rejects the Promise instead of leaving it unresolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.claude/agents/playwright-test-generator.md:
- Around line 46-59: The example has a syntax error and doesn't follow ComfyUI
test conventions: change the test signature from async { page } to async ({
comfyPage }) and replace all uses of page with comfyPage.page; import the Comfy
fixtures (comfyPageFixture as test and comfyExpect as expect) from the ComfyPage
fixture module; wrap the suite in test.describe with appropriate tags (e.g., {
tag: ['@canvas'] }) and add the required test.afterEach hook that calls
comfyPage.canvasOps.resetView so the suite matches the mandated structure and
fixtures (refer to comfyPageFixture, comfyExpect, test.describe, test.afterEach,
comfyPage.canvasOps.resetView, and comfyPage.page.click).
In @.claude/agents/playwright-test-healer.md:
- Around line 41-44: Update the healer guidance so it no longer tells agents to
auto-mark failing tests with test.fixme(); instead, change the behavior in the
policy text that mentions test.fixme() to require explicit escalation criteria
(e.g., a linked known-issue ID and a short rationale) before recommending or
applying test.fixme(), and add a brief note to the same section explaining that
skips can mask regressions and therefore must be approved with those criteria
rather than applied automatically.
In @.mcp.json:
- Around line 1-12: The mcpServers entry "playwright-test" is using the
nonexistent Playwright subcommand "run-test-mcp-server"; update the "command"
and "args" so the MCP server is invoked via the separate package (e.g., use
"npx" as the command and "@playwright/mcp" in the args) instead of "playwright"
and "run-test-mcp-server", leaving the "mcpServers" and "playwright-test" keys
intact.
In `@AGENTS.md`:
- Around line 52-55: Update AGENTS.md to include a short section describing the
new agent roles added under .claude/agents/: add entries for the planner,
generator, and healer that each state the agent's primary responsibility (e.g.,
Planner: decomposes tasks and creates plans; Generator: produces
outputs/artifacts from plans; Healer: validates outputs and applies
corrections), list high-level guardrails for each (scope limits, data/privacy
rules, allowed actions, failure modes), and reference the directory
.claude/agents/ to show where implementations live so the top-level docs match
the codebase.
In `@scripts/patch-playwright-agents.js`:
- Around line 3-4: The script currently calls readFileSync on generated agent
files and can throw an ambiguous ENOENT if names change; before invoking
readFileSync (where agent file paths are constructed using join and read), add
an existence check (e.g., fs.existsSync or fs.promises.stat) for each expected
file, collect any missing file paths into an array, and if any are missing throw
or log a clear, deterministic error that lists the missing files and hints to
re-run init-agents; update the code paths that build the file list (the same
block around the readFileSync calls) to perform this guard and only attempt to
read files that pass the check.
In `@tools/test-recorder/README.md`:
- Around line 9-14: Update the README transform usage to show the required
positional file argument and optional extra args: change the example line to
`pnpm comfy-test transform <file>` and add a short note that additional
positional arguments (e.g., `testName` and tag(s)) may be provided, matching the
CLI behavior implemented in tools/test-recorder/src/index.ts which enforces the
file positional argument for the transform command.
In `@tools/test-recorder/src/checks/pnpm.ts`:
- Around line 13-16: Replace the hardcoded guidance string "corepack prepare
pnpm@latest --activate" in tools/test-recorder/src/checks/pnpm.ts with a pinned
pnpm version used in the repo (e.g. "corepack prepare pnpm@10.17.1 --activate");
locate the exact string literal "corepack prepare pnpm@latest --activate" and
update it to "corepack prepare pnpm@10.17.1 --activate" (or modify the code to
read the pinned version from package.json if you prefer a programmatic
solution).
In `@tools/test-recorder/src/commands/record.ts`:
- Around line 65-69: The call to spawnSync('pnpm', ['install'], ...) in the
record command doesn't check the returned result, so failures are masked:
capture the result (the value returned by spawnSync), inspect result.status
and/or result.error, and if non-zero or an error is present stop the spinner
with a failure message (use s.stop or s.fail), log or include
result.stderr/stdout for diagnostics, and abort (throw or exit) before calling
pass('Project ready', projectRoot); ensure references include spawnSync, s
(spinner), pass, and projectRoot when making the change.
- Around line 229-238: The call to copyToClipboard(fileContents) ignores its
boolean result so clipboard failures are silent; update the else branch in
record.ts to capture the result (e.g., const copied = await
copyToClipboard(fileContents)), and if copied is false, log or surface a clear
message and fall back to printing the fileContents or otherwise call
printManualInstructions with a note that clipboard failed; reference the symbols
copyToClipboard, fileContents, outputPath, slug, and printManualInstructions to
find and modify the code path.
In `@tools/test-recorder/src/commands/transform.ts`:
- Around line 29-46: The current string-based path handling can break on Windows
and may overwrite the input file; import and use Node's path utilities (e.g.,
path.basename, path.dirname, path.join, path.parse) to compute testName and
outputPath robustly: derive testName from path.basename(filePath) with
path.parse to strip the ".raw.spec.ts" suffix only when present, and compute
outputPath by joining path.dirname(filePath) with a safe filename (if the
basename ended with ".raw.spec.ts" replace that suffix with ".spec.ts",
otherwise append ".spec.ts" or require explicit options.output) before calling
writeFileSync; update the code around options.testName, filePath, outputPath,
and the writeFileSync call to use these path-based values to avoid platform
issues and accidental in-place overwrites.
In `@tools/test-recorder/src/index.ts`:
- Around line 58-60: Help text lists a --version flag that isn't implemented;
update tools/test-recorder/src/index.ts to either implement the flag handler or
remove it from the help output. Specifically, add argument parsing for
"--version" in the CLI entrypoint (index.ts) — e.g., detect process.argv
includes "--version" in the main startup logic (or the existing parseArgs/CLI
setup function) and print the package version (from package.json or a VERSION
constant) then exit with code 0; alternatively, if you don't want version
support, remove the --version line from the help generation so the advertised
option matches implemented behavior.
- Around line 33-40: The current check when required checks fail only logs
messages but doesn't set a non-zero exit code; update the failing branch that
checks the allPassed variable so it terminates the process with a non-zero exit
(e.g., call process.exit(1) or set process.exitCode = 1) after logging the error
messages in index.ts where the if (!allPassed) branch is defined to ensure
CI/automation detects failure.
In `@tools/test-recorder/src/pr/clipboard.ts`:
- Around line 4-36: The copyToClipboard function currently returns a bare
boolean which makes failures silent; update copyToClipboard to return structured
result or throw on failure: change its signature to return Promise<{ success:
boolean; platform: string; cmd?: string; code?: number; stderr?: string;
stdout?: string; error?: string }> (or throw an Error) and, where you call
spawnSync (for macos/windows and Linux xclip/xsel branches), capture and
propagate the child result details (status/code, stdout/stderr, command
attempted and platform) into the returned object or included in the thrown Error
message so callers can surface a clear fallback message; keep the existing
behavior of returning success=true when status===0.
In `@tools/test-recorder/src/pr/gh.ts`:
- Around line 45-89: The PR creation fails because the branch isn't pushed
before calling gh pr create; add a spawnSync('git', ['push', '--set-upstream',
'origin', branchName], {encoding:'utf-8', stdio:'pipe'}) call after the commit
step (after the commit variable/check and pass('Committed test file')) and
before invoking spawnSync for 'gh pr create'; check its .status like the other
git commands, call fail('Git push failed', push.stderr.trim()) and return {
success: false, error: push.stderr.trim() } on error, and call pass('Pushed
branch', branchName) on success so branchName, commit, and pr creation flow
match the existing error/success handling.
In `@tools/test-recorder/src/recorder/runner.ts`:
- Around line 98-137: Capture the return value of spawnSync(...) into a variable
(e.g., result) when invoking Playwright in the runner, then check result.error
and result.status !== 0 before printing the "Recording session complete" message
and returning success; if there is an error or a non-zero status,
log/console.error the failure with the result details and return { success:
false } (or otherwise abort) instead of proceeding to create rawOutputPath and
returning { success: true } so the caller (record.ts) won't continue to the
transform step on a failed Playwright run.
- Around line 23-30: findProjectRoot currently loops until dir === '/' which
breaks on Windows and can loop infinitely; change it to detect filesystem root
in a cross-platform way by comparing dir to path.dirname(dir) (use existsSync
and join as already used) and break/return null or throw if no
playwright.config.ts is found. For runRecording, after calling spawnSync (the
existing spawnSync invocation), inspect the returned result.status (and
result.error) and treat any non-zero status or error as failure: log/propagate
the stderr/output and return/throw a failing result instead of always returning
{ success: true }; reference the spawnSync call site and ensure runRecording
returns success only when result.status === 0 and no error is present.
In `@tools/test-recorder/src/recorder/template.ts`:
- Around line 24-26: The template currently interpolates options.workflow
directly into workflowLine which risks template injection or syntax errors if
the workflow name contains quotes/backticks; update the template generation in
template.ts so that the value passed to comfyPage.workflow.loadWorkflow is
safely escaped/serialized (e.g., use a safe string literal like
JSON.stringify(options.workflow) or an explicit escape function) rather than raw
interpolation, and ensure comfyPage.workflow.loadWorkflow(...) receives the
escaped/validated string.
In `@tools/test-recorder/src/transform/rules.ts`:
- Around line 104-120: The generated code interpolates raw user input into
single-quoted JS string literals (see variables tagStr, descName derived from
testName and tags in the wrap-in-describe transform), which breaks when inputs
contain apostrophes or backslashes; fix by escaping user strings before
interpolation — e.g., map tags and the computed descName through a sanitizer
that escapes backslashes and single quotes (or use a safe serializer like
JSON.stringify to produce properly-quoted string literals) and then use those
escaped values when building the template returned from the testMatch block so
the produced test.describe and tag array are syntactically valid.
In `@tools/test-recorder/tsconfig.json`:
- Around line 4-12: The compiled ESM output is producing extensionless relative
imports (because "moduleResolution": "bundler") which Node's ESM loader rejects
for the CLI entrypoint (dist/index.js) when package.json has "type": "module";
update tsconfig.json by replacing "moduleResolution": "bundler" with
"moduleResolution": "NodeNext" so tsc emits proper .js-extended imports, or
alternatively add explicit .js extensions to all relative imports used by the
codebase (e.g., imports like "./rules" and "../ui/logger") to ensure Node ESM
can resolve them.
---
Nitpick comments:
In `@browser_tests/AGENTS.md`:
- Around line 33-38: Update the "After Making Changes" checklist under the
"After Making Changes" heading to include the preferred local Playwright command
by adding a new bullet recommending `pnpm test:browser:local` so contributors
run the same default local E2E command used across the repo; ensure the entry
matches the style of the surrounding bullets and sits alongside the existing
`pnpm typecheck:browser`, `pnpm exec eslint`, and `pnpm exec oxlint` items.
In `@tools/test-recorder/package.json`:
- Around line 7-14: The package.json currently defines a "bin" entry for
"comfy-test" pointing to "./dist/index.js" but that dist artifact is never built
or used; either remove the unused "bin" field entirely, or add a "prepare" npm
script that runs the build so the CLI artifact exists when publishing (e.g.,
wire a "prepare" script to the existing "build" task), and ensure
"scripts.build" ("tsc") and the "comfy-test" bin name remain consistent if you
keep the bin entry.
In `@tools/test-recorder/src/checks/platform.ts`:
- Around line 8-15: The switch over os.platform() currently maps any
non-'darwin'/'win32' value to 'linux'; change the default case to return
'unknown' (or 'other') instead of 'linux' so unknown platforms aren't
misclassified, and update any call sites that consume this platform value to
handle 'unknown' by showing generic install instructions or prompting the user
for platform details; target the switch statement that inspects os.platform()
and modify the default branch and related platform-handling logic accordingly.
In `@tools/test-recorder/src/checks/python.ts`:
- Around line 10-11: The check uses inconsistent names for the same check across
success/failure branches (e.g., pass('Python', version) vs returning name:
'Python 3'); update all places in this file (including the pass() calls and the
returned result objects in both success and failure paths, e.g., where name is
set) to use one canonical check name (suggest "Python") so the pass('Python',
...) call and every return { name: ..., ok: ..., version } use the identical
string.
In `@tools/test-recorder/src/commands/record.ts`:
- Around line 35-42: The readMultiline Promise does not handle stream errors so
it can hang; update the readMultiline function (which uses createInterface and
rl) to attach an 'error' listener that rejects the Promise with the error, and
ensure the listener cleans up (e.g., calls rl.close() or removes listeners) to
avoid leaks; also consider listening to process.stdin 'error' or forwarding that
to the same rejection so any stdin error rejects the Promise instead of leaving
it unresolved.
In `@tools/test-recorder/src/pr/gh.ts`:
- Around line 17-36: The function checkGhAvailable is declared async but only
uses synchronous execSync calls; remove the async keyword to avoid misleading
the API or refactor to use asynchronous child_process methods (e.g., promisified
exec or spawn) instead. Locate checkGhAvailable and replace the declaration to
be a plain synchronous function (remove async) or change the execSync calls to
async equivalents (promisified exec/child_process.spawn wrapped in promises),
ensuring returned object shape ({ available, authenticated }) and error handling
remain the same.
- Around line 17-36: The checkGhAvailable() implementation duplicates logic from
checkGh(); replace its body by reusing the existing checkGh() helper or extract
a shared helper that runs "gh --version" and "gh auth status" so both checkGh()
and checkGhAvailable() call it; update imports to bring in checkGh() (or the new
shared helper), adapt return shape if needed to match {available:boolean,
authenticated:boolean}, and remove the duplicated execSync calls inside
checkGhAvailable().
In `@tools/test-recorder/src/pr/manual.ts`:
- Line 20: The template currently inserts options.relativePath directly;
normalize it to forward slashes before display by replacing backslashes with
slashes (e.g., transform options.relativePath via a replace of "\\" with "/")
where the template contains `${options.relativePath}` in the exported function
in manual.ts so the displayed path is always forward-slash normalized.
In `@tools/test-recorder/src/ui/logger.ts`:
- Around line 18-20: The function wait is declared but unused; either remove the
unused function wait from the module to clean up dead code, or if it was
intended for external use, export it (e.g., add an export for wait) and ensure
callers/imports are updated; update any related tests or imports to reflect the
removal or exported symbol so the logger module has no unused declarations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 221c6942-242a-4b71-a109-7cb7e8b0752c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (44)
.claude/agents/playwright-test-generator.md.claude/agents/playwright-test-healer.md.claude/agents/playwright-test-planner.md.claude/skills/codegen-transform/SKILL.md.mcp.json.oxlintrc.jsonAGENTS.mdbrowser_tests/AGENTS.mdbrowser_tests/README.mdbrowser_tests/specs/README.mdbrowser_tests/tests/seed.spec.tsknip.config.tspackage.jsonpnpm-workspace.yamlscripts/patch-playwright-agents.jsscripts/update-playwright-agents.shtools/test-recorder/README.mdtools/test-recorder/package.jsontools/test-recorder/src/checks/backend.tstools/test-recorder/src/checks/devServer.tstools/test-recorder/src/checks/gh.tstools/test-recorder/src/checks/git.tstools/test-recorder/src/checks/node.tstools/test-recorder/src/checks/platform.tstools/test-recorder/src/checks/playwright.tstools/test-recorder/src/checks/pnpm.tstools/test-recorder/src/checks/python.tstools/test-recorder/src/checks/types.tstools/test-recorder/src/checks/xcode.tstools/test-recorder/src/commands/check.tstools/test-recorder/src/commands/list.tstools/test-recorder/src/commands/record.tstools/test-recorder/src/commands/transform.tstools/test-recorder/src/index.tstools/test-recorder/src/pr/clipboard.tstools/test-recorder/src/pr/gh.tstools/test-recorder/src/pr/manual.tstools/test-recorder/src/recorder/runner.tstools/test-recorder/src/recorder/template.tstools/test-recorder/src/transform/engine.tstools/test-recorder/src/transform/rules.tstools/test-recorder/src/ui/logger.tstools/test-recorder/src/ui/steps.tstools/test-recorder/tsconfig.json
Merge latest main into playwright-test-agents-system branch. Resolve conflicts in knip.config.ts and pnpm-lock.yaml.
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
… --version Addresses review feedback: #10694 (comment) #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment) #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment) #10694 (comment)
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ✅ 833 passed, 0 failed · 6 flaky📊 Browser Reports
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
📦 Bundle: 5.09 MB gzip 🟢 -37 BDetailsSummary
Category Glance App Entry Points — 22.3 kB (baseline 22.3 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.14 MB (baseline 1.14 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 76.6 kB (baseline 76.6 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 9 added / 9 removed / 2 unchanged Panels & Settings — 484 kB (baseline 484 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 12 unchanged User & Accounts — 17.1 kB (baseline 17.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 5 added / 5 removed / 2 unchanged Editors & Dialogs — 109 kB (baseline 109 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 2 added / 2 removed UI Components — 60.3 kB (baseline 60.3 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 2.96 MB (baseline 2.96 MB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 4 unchanged Utilities & Hooks — 334 kB (baseline 334 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed / 12 unchanged Vendor & Third-Party — 9.8 MB (baseline 9.8 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 8.45 MB (baseline 8.45 MB) • ⚪ 0 BBundles that do not match a named category
Status: 55 added / 55 removed / 79 unchanged ⚡ Performance Report
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-03-30T01:09:38.491Z",
"gitSha": "61ca954c27eb23ac252fb2f351091dff284a93af",
"branch": "playwright-test-agents-system",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2022.8989999999953,
"styleRecalcs": 12,
"styleRecalcDurationMs": 11.518,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 466.29,
"heapDeltaBytes": 20351692,
"heapUsedBytes": 62735616,
"domNodes": 23,
"jsHeapTotalBytes": 23068672,
"scriptDurationMs": 30.779999999999994,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2028.5220000000095,
"styleRecalcs": 12,
"styleRecalcDurationMs": 12.657000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 446.59299999999996,
"heapDeltaBytes": 20187704,
"heapUsedBytes": 63199688,
"domNodes": 23,
"jsHeapTotalBytes": 22544384,
"scriptDurationMs": 27.872,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2066.2249999999176,
"styleRecalcs": 10,
"styleRecalcDurationMs": 11.697,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 445.95700000000005,
"heapDeltaBytes": 20332456,
"heapUsedBytes": 63095852,
"domNodes": 20,
"jsHeapTotalBytes": 23330816,
"scriptDurationMs": 29.010999999999996,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2049.6880000000033,
"styleRecalcs": 81,
"styleRecalcDurationMs": 52.994,
"layouts": 12,
"layoutDurationMs": 3.852,
"taskDurationMs": 1085.653,
"heapDeltaBytes": 16826468,
"heapUsedBytes": 61036140,
"domNodes": 66,
"jsHeapTotalBytes": 23330816,
"scriptDurationMs": 153.81,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2054.9819999999954,
"styleRecalcs": 82,
"styleRecalcDurationMs": 47.547000000000004,
"layouts": 12,
"layoutDurationMs": 3.5589999999999997,
"taskDurationMs": 1050.5040000000001,
"heapDeltaBytes": 16293332,
"heapUsedBytes": 59100632,
"domNodes": 65,
"jsHeapTotalBytes": 23592960,
"scriptDurationMs": 147.602,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2044.8949999999968,
"styleRecalcs": 83,
"styleRecalcDurationMs": 46.781000000000006,
"layouts": 12,
"layoutDurationMs": 3.438,
"taskDurationMs": 1046.6339999999998,
"heapDeltaBytes": 7348904,
"heapUsedBytes": 58726384,
"domNodes": 65,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 145.614,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1767.4240000000054,
"styleRecalcs": 31,
"styleRecalcDurationMs": 23.489,
"layouts": 6,
"layoutDurationMs": 0.6839999999999999,
"taskDurationMs": 396.879,
"heapDeltaBytes": 15684516,
"heapUsedBytes": 67102720,
"domNodes": 78,
"jsHeapTotalBytes": 23068672,
"scriptDurationMs": 34.379,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1739.3900000000144,
"styleRecalcs": 34,
"styleRecalcDurationMs": 19.189000000000004,
"layouts": 6,
"layoutDurationMs": 0.627,
"taskDurationMs": 363.63900000000007,
"heapDeltaBytes": 15106204,
"heapUsedBytes": 66741620,
"domNodes": 79,
"jsHeapTotalBytes": 23068672,
"scriptDurationMs": 26.018,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1739.6059999999807,
"styleRecalcs": 32,
"styleRecalcDurationMs": 19.673999999999996,
"layouts": 6,
"layoutDurationMs": 0.699,
"taskDurationMs": 350.76700000000005,
"heapDeltaBytes": 24703352,
"heapUsedBytes": 67127524,
"domNodes": 79,
"jsHeapTotalBytes": 19922944,
"scriptDurationMs": 30.62600000000001,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 648.2129999999984,
"styleRecalcs": 12,
"styleRecalcDurationMs": 10.077999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 409.293,
"heapDeltaBytes": 6538572,
"heapUsedBytes": 49291220,
"domNodes": 20,
"jsHeapTotalBytes": 13369344,
"scriptDurationMs": 81.816,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 590.9679999999753,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.853999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 383.64300000000003,
"heapDeltaBytes": 6347936,
"heapUsedBytes": 49304896,
"domNodes": 19,
"jsHeapTotalBytes": 12845056,
"scriptDurationMs": 66.79,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 582.5640000000476,
"styleRecalcs": 13,
"styleRecalcDurationMs": 10.563999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 378.679,
"heapDeltaBytes": 6201924,
"heapUsedBytes": 48954068,
"domNodes": 22,
"jsHeapTotalBytes": 13107200,
"scriptDurationMs": 72.989,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "large-graph-idle",
"durationMs": 2014.4170000000088,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.413000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 699.567,
"heapDeltaBytes": 3441016,
"heapUsedBytes": 54635812,
"domNodes": -257,
"jsHeapTotalBytes": 16707584,
"scriptDurationMs": 126.45200000000001,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2076.8729999999778,
"styleRecalcs": 11,
"styleRecalcDurationMs": 11.612,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 719.548,
"heapDeltaBytes": -21378324,
"heapUsedBytes": 54989588,
"domNodes": -258,
"jsHeapTotalBytes": 17612800,
"scriptDurationMs": 132.956,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2044.9929999999767,
"styleRecalcs": 9,
"styleRecalcDurationMs": 9.961,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 609.554,
"heapDeltaBytes": 4642476,
"heapUsedBytes": 55615140,
"domNodes": -260,
"jsHeapTotalBytes": 16183296,
"scriptDurationMs": 117.82700000000001,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2117.007000000001,
"styleRecalcs": 68,
"styleRecalcDurationMs": 16.854000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1215.279,
"heapDeltaBytes": 15623948,
"heapUsedBytes": 69028136,
"domNodes": -263,
"jsHeapTotalBytes": 17698816,
"scriptDurationMs": 426.39399999999995,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2161.2499999999955,
"styleRecalcs": 69,
"styleRecalcDurationMs": 17.445,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1210.5609999999997,
"heapDeltaBytes": 19305060,
"heapUsedBytes": 71244496,
"domNodes": -261,
"jsHeapTotalBytes": 17698816,
"scriptDurationMs": 428.61899999999997,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2163.269000000014,
"styleRecalcs": 68,
"styleRecalcDurationMs": 16.064000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1284.687,
"heapDeltaBytes": 15864788,
"heapUsedBytes": 69592060,
"domNodes": -264,
"jsHeapTotalBytes": 18485248,
"scriptDurationMs": 483.554,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3199.6800000000007,
"styleRecalcs": 65,
"styleRecalcDurationMs": 17.833,
"layouts": 60,
"layoutDurationMs": 8.091000000000001,
"taskDurationMs": 1451.291,
"heapDeltaBytes": 10042720,
"heapUsedBytes": 64930144,
"domNodes": -267,
"jsHeapTotalBytes": 15134720,
"scriptDurationMs": 529.199,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3200.1410000000305,
"styleRecalcs": 65,
"styleRecalcDurationMs": 17.796999999999997,
"layouts": 60,
"layoutDurationMs": 8.31,
"taskDurationMs": 1509.867,
"heapDeltaBytes": 10023288,
"heapUsedBytes": 65073852,
"domNodes": -267,
"jsHeapTotalBytes": 15503360,
"scriptDurationMs": 567.681,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3263.639000000012,
"styleRecalcs": 65,
"styleRecalcDurationMs": 18.012,
"layouts": 60,
"layoutDurationMs": 8.389000000000001,
"taskDurationMs": 1495.107,
"heapDeltaBytes": 10090700,
"heapUsedBytes": 64716040,
"domNodes": -265,
"jsHeapTotalBytes": 16707584,
"scriptDurationMs": 559.7090000000001,
"eventListeners": -123,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2054.846999999995,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.833,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 688.6619999999999,
"heapDeltaBytes": 1875032,
"heapUsedBytes": 55757540,
"domNodes": -263,
"jsHeapTotalBytes": 16445440,
"scriptDurationMs": 120.767,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.659999999999975,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2072.5020000000427,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.713999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 677.6990000000001,
"heapDeltaBytes": 5066252,
"heapUsedBytes": 57496440,
"domNodes": -258,
"jsHeapTotalBytes": 15921152,
"scriptDurationMs": 137.49299999999997,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2015.8010000000104,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.105999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 607.3519999999999,
"heapDeltaBytes": 2156048,
"heapUsedBytes": 56227264,
"domNodes": -262,
"jsHeapTotalBytes": 16183296,
"scriptDurationMs": 113.515,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 595.3420000000165,
"styleRecalcs": 47,
"styleRecalcDurationMs": 12.321,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 398.73499999999996,
"heapDeltaBytes": 6750876,
"heapUsedBytes": 49881740,
"domNodes": 20,
"jsHeapTotalBytes": 12845056,
"scriptDurationMs": 139.58800000000002,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 606.1379999999872,
"styleRecalcs": 47,
"styleRecalcDurationMs": 12.876,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 394.685,
"heapDeltaBytes": 6786080,
"heapUsedBytes": 49932828,
"domNodes": 20,
"jsHeapTotalBytes": 12845056,
"scriptDurationMs": 133.06,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 570.5590000000029,
"styleRecalcs": 48,
"styleRecalcDurationMs": 13.038,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 375.236,
"heapDeltaBytes": 6543244,
"heapUsedBytes": 50623896,
"domNodes": 22,
"jsHeapTotalBytes": 13107200,
"scriptDurationMs": 129.27100000000002,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "subgraph-idle",
"durationMs": 2024.2709999999988,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.690000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 433.09200000000004,
"heapDeltaBytes": 20326012,
"heapUsedBytes": 63375900,
"domNodes": 20,
"jsHeapTotalBytes": 23330816,
"scriptDurationMs": 25.258000000000003,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2041.4910000000077,
"styleRecalcs": 11,
"styleRecalcDurationMs": 12.238000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 439.53600000000006,
"heapDeltaBytes": 20333752,
"heapUsedBytes": 63280220,
"domNodes": 22,
"jsHeapTotalBytes": 23592960,
"scriptDurationMs": 25.83,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2021.2179999999762,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.367,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 371.63100000000003,
"heapDeltaBytes": 19745356,
"heapUsedBytes": 62953528,
"domNodes": 20,
"jsHeapTotalBytes": 23068672,
"scriptDurationMs": 17.537000000000003,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1985.131999999993,
"styleRecalcs": 85,
"styleRecalcDurationMs": 54.864,
"layouts": 16,
"layoutDurationMs": 5.231999999999999,
"taskDurationMs": 1012.5310000000001,
"heapDeltaBytes": 11771900,
"heapUsedBytes": 54843592,
"domNodes": 74,
"jsHeapTotalBytes": 22806528,
"scriptDurationMs": 112.402,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1989.562000000035,
"styleRecalcs": 88,
"styleRecalcDurationMs": 55.340999999999994,
"layouts": 16,
"layoutDurationMs": 4.8069999999999995,
"taskDurationMs": 1005.379,
"heapDeltaBytes": 11832560,
"heapUsedBytes": 54888348,
"domNodes": 73,
"jsHeapTotalBytes": 22806528,
"scriptDurationMs": 117.64099999999999,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1690.7999999999674,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.041,
"layouts": 16,
"layoutDurationMs": 4.566,
"taskDurationMs": 717.221,
"heapDeltaBytes": 11507564,
"heapUsedBytes": 54498172,
"domNodes": 63,
"jsHeapTotalBytes": 23330816,
"scriptDurationMs": 107.795,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8202.093999999988,
"styleRecalcs": 250,
"styleRecalcDurationMs": 46.25699999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4287.37,
"heapDeltaBytes": 26602048,
"heapUsedBytes": 77562680,
"domNodes": -259,
"jsHeapTotalBytes": 20844544,
"scriptDurationMs": 1444.9699999999998,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8216.206999999997,
"styleRecalcs": 251,
"styleRecalcDurationMs": 48.587,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4205.394,
"heapDeltaBytes": 25469040,
"heapUsedBytes": 76274948,
"domNodes": -259,
"jsHeapTotalBytes": 18223104,
"scriptDurationMs": 1410.421,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8305.70399999999,
"styleRecalcs": 251,
"styleRecalcDurationMs": 47.905,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4493.65,
"heapDeltaBytes": 33897112,
"heapUsedBytes": 84974488,
"domNodes": -257,
"jsHeapTotalBytes": 27922432,
"scriptDurationMs": 1663.4250000000002,
"eventListeners": -111,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 14120.470999999952,
"styleRecalcs": 2,
"styleRecalcDurationMs": 30.851000000000017,
"layouts": 2,
"layoutDurationMs": 10.774999999999993,
"taskDurationMs": 14100.979,
"heapDeltaBytes": 8513420,
"heapUsedBytes": 166584404,
"domNodes": -4997,
"jsHeapTotalBytes": 56713216,
"scriptDurationMs": 654.1250000000001,
"eventListeners": 3526,
"totalBlockingTimeMs": 950,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 13692.50199999999,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13677.44,
"heapDeltaBytes": -30753864,
"heapUsedBytes": 165679016,
"domNodes": -8331,
"jsHeapTotalBytes": 27353088,
"scriptDurationMs": 718.658,
"eventListeners": -16460,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.216666666666665,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 13082.619000000022,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13068.616000000002,
"heapDeltaBytes": -36885540,
"heapUsedBytes": 157744580,
"domNodes": -8331,
"jsHeapTotalBytes": 17391616,
"scriptDurationMs": 658.2669999999999,
"eventListeners": -16462,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 15362.899999999969,
"styleRecalcs": 76,
"styleRecalcDurationMs": 16.68700000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15337.355000000001,
"heapDeltaBytes": -58749200,
"heapUsedBytes": 147763076,
"domNodes": -8331,
"jsHeapTotalBytes": -2793472,
"scriptDurationMs": 945.636,
"eventListeners": -16488,
"totalBlockingTimeMs": 1,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 15694.40800000001,
"styleRecalcs": 84,
"styleRecalcDurationMs": 18.68700000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15670.169000000002,
"heapDeltaBytes": -22772188,
"heapUsedBytes": 172278760,
"domNodes": -8331,
"jsHeapTotalBytes": 23072768,
"scriptDurationMs": 1061.06,
"eventListeners": -16453,
"totalBlockingTimeMs": 5,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 15557.799000000046,
"styleRecalcs": 78,
"styleRecalcDurationMs": 17.436000000000007,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15529.988,
"heapDeltaBytes": -38583152,
"heapUsedBytes": 171923832,
"domNodes": -8331,
"jsHeapTotalBytes": -6025216,
"scriptDurationMs": 989.452,
"eventListeners": -16456,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 457.8769999999963,
"styleRecalcs": 19,
"styleRecalcDurationMs": 25.336,
"layouts": 5,
"layoutDurationMs": 1.4839999999999995,
"taskDurationMs": 135.481,
"heapDeltaBytes": 4716484,
"heapUsedBytes": 55027560,
"domNodes": 169,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 31.25,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 447.4790000000439,
"styleRecalcs": 18,
"styleRecalcDurationMs": 26.206,
"layouts": 5,
"layoutDurationMs": 1.4130000000000003,
"taskDurationMs": 129.806,
"heapDeltaBytes": 4496792,
"heapUsedBytes": 49131616,
"domNodes": 156,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 30.96700000000001,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 450.1239999999598,
"styleRecalcs": 16,
"styleRecalcDurationMs": 24.665,
"layouts": 5,
"layoutDurationMs": 1.3350000000000002,
"taskDurationMs": 130.139,
"heapDeltaBytes": 4487272,
"heapUsedBytes": 48381196,
"domNodes": 156,
"jsHeapTotalBytes": 0,
"scriptDurationMs": 30.924999999999997,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
}
]
} |
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
…TS.md Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
Addresses review feedback: #10694 (comment)
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
tools/test-recorder/src/transform/rules.ts (1)
38-39:⚠️ Potential issue | 🟠 MajorPreserve non-
pagefixtures when rewriting async destructuring.This replacement drops additional fixtures.
async ({ page, context })becomesasync ({ comfyPage }), which can break transformed tests that still usecontext(or others).💡 Proposed fix
- pattern: /async\s*\(\s*\{\s*page\s*(?:,\s*\w+\s*)*\}\s*\)/g, - replacement: 'async ({ comfyPage })', + pattern: /async\s*\(\s*\{\s*([^}]*\bpage\b[^}]*)\}\s*\)/g, + replacement: (_match, fixtures: string) => { + const mapped = fixtures + .split(',') + .map((fixture) => fixture.trim()) + .filter(Boolean) + .map((fixture) => (fixture === 'page' ? 'comfyPage' : fixture)) + return `async ({ ${mapped.join(', ')} })` + },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/transform/rules.ts` around lines 38 - 39, The current rule (pattern/replacement) replaces any async destructured fixtures with 'async ({ comfyPage })', dropping other fixtures like 'context'; update the regex and replacement in rules.ts so the pattern captures the full destructuring contents and the replacement injects comfyPage while preserving all other keys (e.g., transform 'async ({ page, context })' into 'async ({ comfyPage, context })' or 'async ({ comfyPage })' when no others exist). Modify the existing pattern and replacement to use capture groups for the inner object tokens (referencing pattern and replacement in the diff) and reconstruct the destructuring to include comfyPage plus the captured "rest" tokens so non-page fixtures are preserved.
🧹 Nitpick comments (1)
tools/test-recorder/src/ui/logger.ts (1)
3-42: Optional: Add explicitvoidreturn types for documentation.While the functions implicitly return
void, explicit return types can improve IDE hints and serve as self-documentation. This is purely optional and a matter of style preference.Example with explicit return type
-export function pass(label: string, detail?: string) { +export function pass(label: string, detail?: string): void { const d = detail ? pc.dim(` ${detail}`) : '' console.log(` ${pc.green('✅')} ${label}${d}`) }Apply similar pattern to
fail,warn,info,blank,header, andbox.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/ui/logger.ts` around lines 3 - 42, Add explicit void return types to the logger functions to improve IDE hints and self-documentation: update the declarations for pass, fail, warn, info, blank, header, and box to include ": void" (e.g., function pass(label: string, detail?: string): void { ... }), leaving implementation unchanged; this is purely a type annotation change so modify the function signatures only.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tools/test-recorder/src/ui/logger.ts`:
- Around line 34-42: The box function can throw when lines is empty because
Math.max(...lines.map(...)) becomes -Infinity; update box to guard against an
empty array by computing maxLen = lines.length ? Math.max(...lines.map(l =>
l.length)) : 0 (or return early when lines.length === 0), then build border and
pad using that safe maxLen; modify the box function to use this safe maxLen so
'─'.repeat(...) never receives a negative/invalid value.
---
Duplicate comments:
In `@tools/test-recorder/src/transform/rules.ts`:
- Around line 38-39: The current rule (pattern/replacement) replaces any async
destructured fixtures with 'async ({ comfyPage })', dropping other fixtures like
'context'; update the regex and replacement in rules.ts so the pattern captures
the full destructuring contents and the replacement injects comfyPage while
preserving all other keys (e.g., transform 'async ({ page, context })' into
'async ({ comfyPage, context })' or 'async ({ comfyPage })' when no others
exist). Modify the existing pattern and replacement to use capture groups for
the inner object tokens (referencing pattern and replacement in the diff) and
reconstruct the destructuring to include comfyPage plus the captured "rest"
tokens so non-page fixtures are preserved.
---
Nitpick comments:
In `@tools/test-recorder/src/ui/logger.ts`:
- Around line 3-42: Add explicit void return types to the logger functions to
improve IDE hints and self-documentation: update the declarations for pass,
fail, warn, info, blank, header, and box to include ": void" (e.g., function
pass(label: string, detail?: string): void { ... }), leaving implementation
unchanged; this is purely a type annotation change so modify the function
signatures only.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a6e78656-d021-4747-9ae2-fa0a13fc47de
📒 Files selected for processing (10)
.claude/skills/codegen-transform/SKILL.md.gitignore.mcp.json.oxlintrc.jsonbrowser_tests/AGENTS.mdknip.config.tstools/test-recorder/package.jsontools/test-recorder/src/transform/engine.tstools/test-recorder/src/transform/rules.tstools/test-recorder/src/ui/logger.ts
✅ Files skipped from review due to trivial changes (4)
- .gitignore
- browser_tests/AGENTS.md
- knip.config.ts
- tools/test-recorder/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
- .oxlintrc.json
- .mcp.json
- .claude/skills/codegen-transform/SKILL.md
| export function box(lines: string[]) { | ||
| const maxLen = Math.max(...lines.map((l) => l.length)) | ||
| const border = '─'.repeat(maxLen + 4) | ||
| console.log(` ┌${border}┐`) | ||
| for (const line of lines) { | ||
| console.log(` │ ${line.padEnd(maxLen + 2)}│`) | ||
| } | ||
| console.log(` └${border}┘`) | ||
| } |
There was a problem hiding this comment.
Guard against empty array in box().
If lines is empty, Math.max(...[]) returns -Infinity, causing '─'.repeat(-Infinity + 4) to throw a RangeError. Consider adding an early return or defaulting maxLen to 0.
🛡️ Proposed guard
export function box(lines: string[]) {
+ if (lines.length === 0) return
const maxLen = Math.max(...lines.map((l) => l.length))
const border = '─'.repeat(maxLen + 4)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function box(lines: string[]) { | |
| const maxLen = Math.max(...lines.map((l) => l.length)) | |
| const border = '─'.repeat(maxLen + 4) | |
| console.log(` ┌${border}┐`) | |
| for (const line of lines) { | |
| console.log(` │ ${line.padEnd(maxLen + 2)}│`) | |
| } | |
| console.log(` └${border}┘`) | |
| } | |
| export function box(lines: string[]) { | |
| if (lines.length === 0) return | |
| const maxLen = Math.max(...lines.map((l) => l.length)) | |
| const border = '─'.repeat(maxLen + 4) | |
| console.log(` ┌${border}┐`) | |
| for (const line of lines) { | |
| console.log(` │ ${line.padEnd(maxLen + 2)}│`) | |
| } | |
| console.log(` └${border}┘`) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tools/test-recorder/src/ui/logger.ts` around lines 34 - 42, The box function
can throw when lines is empty because Math.max(...lines.map(...)) becomes
-Infinity; update box to guard against an empty array by computing maxLen =
lines.length ? Math.max(...lines.map(l => l.length)) : 0 (or return early when
lines.length === 0), then build border and pad using that safe maxLen; modify
the box function to use this safe maxLen so '─'.repeat(...) never receives a
negative/invalid value.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
tools/test-recorder/src/transform/rules.ts (1)
38-40:⚠️ Potential issue | 🟠 MajorPreserve non-
pagefixtures when rewriting async params.This rewrite drops additional fixtures (e.g.
context,request) by convertingasync ({ page, context })intoasync ({ comfyPage }), which can break transformed tests.Proposed fix
- pattern: /async\s*\(\s*\{\s*page\s*(?:,\s*\w+\s*)*\}\s*\)/g, - replacement: 'async ({ comfyPage })', + pattern: /async\s*\(\s*\{\s*page(\s*(?:,\s*\w+\s*)*)\}\s*\)/g, + replacement: (_match, rest) => `async ({ comfyPage${rest} })`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/test-recorder/src/transform/rules.ts` around lines 38 - 40, The current rule in rules.ts that replaces pattern -> replacement drops other fixtures; update the regex and replacement so you only rename the page param to comfyPage while preserving surrounding fixtures: modify the pattern (currently named pattern) to capture the contents of the object around the word "page" (e.g. /async\s*\(\s*\{\s*([^}]*)\bpage\b([^}]*)\}\s*\)/g) and change replacement to re-insert the captured groups while replacing only "page" with "comfyPage" (e.g. 'async ({ $1comfyPage$2 })' with spacing normalized); keep category: 'fixture' unchanged and test with cases like async ({ page, context }), async ({ context, page }), and async ({ page }) to ensure other fixtures are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@tools/test-recorder/src/transform/rules.ts`:
- Around line 38-40: The current rule in rules.ts that replaces pattern ->
replacement drops other fixtures; update the regex and replacement so you only
rename the page param to comfyPage while preserving surrounding fixtures: modify
the pattern (currently named pattern) to capture the contents of the object
around the word "page" (e.g. /async\s*\(\s*\{\s*([^}]*)\bpage\b([^}]*)\}\s*\)/g)
and change replacement to re-insert the captured groups while replacing only
"page" with "comfyPage" (e.g. 'async ({ $1comfyPage$2 })' with spacing
normalized); keep category: 'fixture' unchanged and test with cases like async
({ page, context }), async ({ context, page }), and async ({ page }) to ensure
other fixtures are preserved.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fb34ab05-0005-40a5-894b-e7adc368caf8
📒 Files selected for processing (2)
.claude/skills/codegen-transform/SKILL.mdtools/test-recorder/src/transform/rules.ts
✅ Files skipped from review due to trivial changes (1)
- .claude/skills/codegen-transform/SKILL.md
Summary
Three integrated systems for AI-assisted browser test creation, targeting QA testers (non-developers) and AI agents.
What's Added
1.
comfy-testCLI (tools/test-recorder/, 26 source files)Interactive 7-step recording flow: environment checks → project setup → backend verification → test configuration → browser recording → codegen transform → PR creation.
Key features:
ghCLI or step-by-step manual GitHub web UI instructions2. Playwright AI Agents (
.claude/agents/)Three agents patched with ComfyUI-specific context:
.spec.tsfiles usingcomfyPageFixturewaitForTimeout, never weaken assertions)Regeneration scripts (
scripts/update-playwright-agents.sh) re-runinit-agentsand re-apply patches after Playwright updates.3. Codegen Transform Skill (
.claude/skills/codegen-transform/)Teaches AI agents the same transform rules the CLI uses:
@playwright/test→comfyPageFixtureimportspage→comfyPagefixturepage.goto()→ removed (fixture handles navigation)page.locator("canvas")→comfyPage.canvaswaitForTimeout()→comfyPage.nextFrame()test.describewith tags andafterEachcleanup4. Documentation Updates
AGENTS.md— addedcomfy-testcommandsbrowser_tests/AGENTS.md— "AI-Assisted Test Creation" section covering all 3 systemsbrowser_tests/README.md— "Recording Tests (For Non-Developers)" sectionFiles Changed
tools/test-recorder/src/**(26 files).claude/agents/(3 files).claude/skills/codegen-transform/SKILL.mdscripts/update-playwright-agents.sh,scripts/patch-playwright-agents.js.mcp.json,.oxlintrc.json,knip.config.tsbrowser_tests/tests/seed.spec.ts,browser_tests/specs/README.mdAGENTS.md,browser_tests/AGENTS.md,browser_tests/README.mdpackage.json,pnpm-workspace.yaml,pnpm-lock.yamlTesting
pnpm comfy-test— CLI runs, shows helppnpm comfy-test list— lists 74 workflow assetspnpm comfy-test check— runs all prerequisite checkstsc --noEmit)┆Issue is synchronized with this Notion page by Unito