feat: playwright recorder for example-app walkthroughs#108
Conversation
Adds scripts/walkthroughs/record.mjs to record narrated walkthrough videos and preview images for dashnote, dashnote-starter, and dashmint-lab, with caption copy in scripts/walkthroughs/captions/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The recorder previously printed nothing between browser launch and final output, making long runs (especially dashmint-lab) look hung. Add a timestamped logStep helper and emit it at each major phase: browser launch, navigation, key waits, login, and tab switches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… wait Press Ctrl+C (SIGINT) or SIGTERM during recording to finalize the partial video. The shutdown path now tolerates already-closed browsers and prefers video.saveAs over manual copy. Caption restyled as a dark glass card pinned upper-right so it reads on dark UIs without covering the heading and tabs on the left. Wait for any card article rather than a specific card name so the recorder doesn't depend on contract contents. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorder the dashmint-lab walkthrough so tab navigation (All / Marketplace / Sort) and the Buy hover demo run before login. After signing in, walk through the Yours tab and animate the owned-card overflow menu (Transfer, Copy ID, View on Explorer, Burn). Center the caption within the content area to the right of the 208px sidebar so the heading and account balance stay visible. Theme the caption with an amber accent that matches the app. Extract dashmint-lab caption copy into scripts/walkthroughs/captions/dashmint-lab.json so it can be edited without touching the recorder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caption files are now the sole source of caption strings. The recorder errors out when the captions JSON is missing or a referenced key is absent, and the dashnote-starter captions file ships alongside the existing ones. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a runDashrate driver covering public aggregate browsing, identity-owned review create/edit, and review history, with caption copy in captions/dashrate.json and a walkthrough:dashrate npm script. Add scrollToLocator, scrollToFirstLocator, and scrollToTop driver helpers, a configurable per-app viewport (dashrate records at 1440x1000), and pointer-events: none on the caption overlay so it does not intercept clicks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dashmint-lab walkthrough now performs one real card mint so the token-paid create flow is visible, then visits the Tokens tab to show the signed-in identity's DashMint balance and transfer form. Captions are updated to narrate the mint and Tokens steps; the browse-only (--no-login) recording is unchanged. The recorder still avoids token transfers, purchases, burns, and contract registration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every app now outputs to a per-app directory under the repo-root walkthrough/ tree instead of beside each app, and TokenOps drops its flat-output special case.
📝 WalkthroughWalkthroughAdds a Playwright CLI that records captioned walkthrough videos for five applications, supporting authentication options, browser configuration, conditional UI flows, previews, and persisted video artifacts. ChangesWalkthrough recording
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant NpmScripts
participant RecordCLI
participant Playwright
participant TargetApp
participant WalkthroughOutput
NpmScripts->>RecordCLI: invoke app walkthrough command
RecordCLI->>TargetApp: open configured URL
RecordCLI->>Playwright: launch Chromium and enable video recording
Playwright->>TargetApp: execute application walkthrough
RecordCLI->>Playwright: capture preview and finalize video
Playwright->>WalkthroughOutput: save WebM and PNG artifacts
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/walkthroughs/README.md`:
- Around line 8-13: Add the missing TokenOps walkthrough documentation to the
command list by including the walkthrough:token-ops command and its
token-ops.json caption manifest alongside the existing walkthrough entries in
scripts/walkthroughs/README.md.
In `@scripts/walkthroughs/record.mjs`:
- Around line 1342-1350: Update the cleanup logic in the walkthrough recording
flow to remove tmpVideoDir after the temporary video has been processed,
including when processing fails if cleanup is handled in a finally path. Reuse
the existing tmpVideoDir symbol and ensure its contents are deleted without
affecting targetWebm or targetPreview.
- Around line 1393-1404: Update the video persistence handling around
video.saveAs and its copy fallback so a failure of both operations is retained
rather than only logged. Preserve the original persistence error or combined
failure, allow the surrounding recording cleanup to complete, then rethrow it so
the command does not report success without the video artifact.
- Around line 608-677: Default authenticated walkthroughs must preserve the
documented no-write contract: in scripts/walkthroughs/record.mjs lines 608-677,
guard the Dashnote create/update/delete flow behind explicit write consent or
stop before it; apply the same consent guard or early stop to the DashRate
review save flow at lines 1115-1148; update scripts/walkthroughs/README.md lines
35-39 to accurately describe the no-write behavior or document the opt-in write
mode.
- Around line 95-97: Update the --env-file handling in the argument parser to
validate that argv[++i] exists before passing it to path.resolve. Preserve the
intended “--env-file requires a value” error for missing values, then resolve
and assign the validated value to args.envFile.
- Around line 924-931: Update the Login handling around loginButtons to use the
checked locator helper, and guard both the absent-button and null-boundingBox
cases before accessing coordinates. Preserve the no-login recording flow by
skipping the cursor click when no visible Login button is available.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 85d207d3-8289-4ade-a956-24c4e1e84ab8
⛔ Files ignored due to path filters (2)
walkthrough/dashnote/dashnote-preview.pngis excluded by!**/*.pngwalkthrough/dashnote/dashnote-walkthrough.webmis excluded by!**/*.webm
📒 Files selected for processing (8)
package.jsonscripts/walkthroughs/README.mdscripts/walkthroughs/captions/dashmint-lab.jsonscripts/walkthroughs/captions/dashnote-starter.jsonscripts/walkthroughs/captions/dashnote.jsonscripts/walkthroughs/captions/dashrate.jsonscripts/walkthroughs/captions/token-ops.jsonscripts/walkthroughs/record.mjs
| ```sh | ||
| npm run walkthrough:dashmint-lab | ||
| npm run walkthrough:dashnote | ||
| npm run walkthrough:dashnote-starter | ||
| npm run walkthrough:dashrate | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the TokenOps walkthrough.
The available walkthrough:token-ops command and token-ops.json caption manifest are both omitted.
Proposed fix
npm run walkthrough:dashnote-starter
npm run walkthrough:dashrate
+npm run walkthrough:token-ops scripts/walkthroughs/captions/dashrate.json
+scripts/walkthroughs/captions/token-ops.jsonAlso applies to: 57-62
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/README.md` around lines 8 - 13, Add the missing TokenOps
walkthrough documentation to the command list by including the
walkthrough:token-ops command and its token-ops.json caption manifest alongside
the existing walkthrough entries in scripts/walkthroughs/README.md.
| } else if (arg === '--env-file') { | ||
| args.envFile = path.resolve(argv[++i]); | ||
| if (!args.envFile) throw new Error('--env-file requires a value'); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate the --env-file value before resolving it.
Without a following value, path.resolve(undefined) throws its own TypeError, bypassing the intended CLI error.
Proposed fix
} else if (arg === '--env-file') {
- args.envFile = path.resolve(argv[++i]);
- if (!args.envFile) throw new Error('--env-file requires a value');
+ const envFile = argv[++i];
+ if (!envFile) throw new Error('--env-file requires a value');
+ args.envFile = path.resolve(envFile);📝 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.
| } else if (arg === '--env-file') { | |
| args.envFile = path.resolve(argv[++i]); | |
| if (!args.envFile) throw new Error('--env-file requires a value'); | |
| } else if (arg === '--env-file') { | |
| const envFile = argv[++i]; | |
| if (!envFile) throw new Error('--env-file requires a value'); | |
| args.envFile = path.resolve(envFile); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/record.mjs` around lines 95 - 97, Update the --env-file
handling in the argument parser to validate that argv[++i] exists before passing
it to path.resolve. Preserve the intended “--env-file requires a value” error
for missing values, then resolve and assign the validated value to args.envFile.
| if (page.walkthroughCredentials) { | ||
| const newButton = page.getByRole('button', { name: 'New note' }); | ||
| if ((await newButton.count()) === 1) { | ||
| const uniqueTitle = `Walkthrough note ${Date.now().toString().slice(-6)}`; | ||
| await driver.captionKey('createNote'); | ||
| await driver.clickLocator(newButton); | ||
| await page.getByLabel('Title').waitFor({ | ||
| state: 'visible', | ||
| timeout: 10000, | ||
| }); | ||
| await driver.clickLocator(page.getByLabel('Title')); | ||
| await page.keyboard.type(uniqueTitle, { delay: 35 }); | ||
| await driver.clickLocator(page.getByLabel('Body')); | ||
| await page.keyboard.type( | ||
| 'Created by the walkthrough recorder so we can show create, update, revision metadata, and delete.', | ||
| { delay: 20 }, | ||
| ); | ||
| await driver.moveCursor(1040, 740); | ||
| await driver.delay(1200); | ||
| await driver.captionKey('saveCreate'); | ||
| await driver.clickLocator( | ||
| page.getByRole('button', { name: 'Create note' }), | ||
| ); | ||
| await page.getByText('Note created.', { exact: true }).first().waitFor({ | ||
| state: 'visible', | ||
| timeout: 90000, | ||
| }); | ||
| await page.getByText(uniqueTitle, { exact: true }).first().waitFor({ | ||
| state: 'visible', | ||
| timeout: 30000, | ||
| }); | ||
| await driver.captionKey('createdMetadata'); | ||
| await driver.moveCursor(508, 188); | ||
| await driver.delay(900); | ||
| await driver.moveCursor(688, 739); | ||
| await driver.delay(900); | ||
|
|
||
| await driver.captionKey('updateNote'); | ||
| await driver.clickLocator(page.getByLabel('Body')); | ||
| await page.keyboard.press('End'); | ||
| await page.keyboard.type( | ||
| '\n\nUpdated during the walkthrough to show revision changes.', | ||
| { delay: 20 }, | ||
| ); | ||
| await driver.clickLocator(page.getByRole('button', { name: 'Save' })); | ||
| await page.getByText('Note saved.', { exact: true }).first().waitFor({ | ||
| state: 'visible', | ||
| timeout: 90000, | ||
| }); | ||
| await driver.captionKey('updatedMetadata'); | ||
| await driver.moveCursor(506, 188); | ||
| await driver.delay(1200); | ||
|
|
||
| await driver.captionKey('deleteNote'); | ||
| await driver.clickLocator(page.getByRole('button', { name: 'Delete' })); | ||
| await page.getByRole('dialog', { name: 'Delete note' }).waitFor({ | ||
| state: 'visible', | ||
| timeout: 10000, | ||
| }); | ||
| await driver.clickLocator( | ||
| page | ||
| .getByRole('dialog', { name: 'Delete note' }) | ||
| .getByRole('button', { name: 'Delete' }), | ||
| ); | ||
| await page.getByText('Note deleted.', { exact: true }).first().waitFor({ | ||
| state: 'visible', | ||
| timeout: 90000, | ||
| }); | ||
| await driver.captionKey('deletedNote'); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Default authenticated walkthroughs violate the documented no-write contract.
scripts/walkthroughs/record.mjs#L608-L677: stop before creating, updating, or deleting the Dashnote document, or require explicit write consent.scripts/walkthroughs/record.mjs#L1115-L1148: stop before saving DashRate reviews, or require the same explicit consent.scripts/walkthroughs/README.md#L35-L39: keep the no-write guarantee accurate or document the opt-in write mode.
📍 Affects 2 files
scripts/walkthroughs/record.mjs#L608-L677(this comment)scripts/walkthroughs/record.mjs#L1115-L1148scripts/walkthroughs/README.md#L35-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/record.mjs` around lines 608 - 677, Default
authenticated walkthroughs must preserve the documented no-write contract: in
scripts/walkthroughs/record.mjs lines 608-677, guard the Dashnote
create/update/delete flow behind explicit write consent or stop before it; apply
the same consent guard or early stop to the DashRate review save flow at lines
1115-1148; update scripts/walkthroughs/README.md lines 35-39 to accurately
describe the no-write behavior or document the opt-in write mode.
| const loginButtons = await page | ||
| .getByRole('button', { name: 'Login' }) | ||
| .all(); | ||
| const loginBox = await loginButtons[loginButtons.length - 1].boundingBox(); | ||
| await driver.clickCursor( | ||
| loginBox.x + loginBox.width / 2, | ||
| loginBox.y + loginBox.height / 2, | ||
| ); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Use the checked locator helper for the read-only Login path.
An absent Login button makes the last array entry undefined; a hidden button makes loginBox null. Either condition crashes the no-login recording.
Proposed fix
- const loginButtons = await page
- .getByRole('button', { name: 'Login' })
- .all();
- const loginBox = await loginButtons[loginButtons.length - 1].boundingBox();
- await driver.clickCursor(
- loginBox.x + loginBox.width / 2,
- loginBox.y + loginBox.height / 2,
+ await driver.clickLastLocator(
+ page.getByRole('button', { name: 'Login' }),
);📝 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.
| const loginButtons = await page | |
| .getByRole('button', { name: 'Login' }) | |
| .all(); | |
| const loginBox = await loginButtons[loginButtons.length - 1].boundingBox(); | |
| await driver.clickCursor( | |
| loginBox.x + loginBox.width / 2, | |
| loginBox.y + loginBox.height / 2, | |
| ); | |
| await driver.clickLastLocator( | |
| page.getByRole('button', { name: 'Login' }), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/record.mjs` around lines 924 - 931, Update the Login
handling around loginButtons to use the checked locator helper, and guard both
the absent-button and null-boundingBox cases before accessing coordinates.
Preserve the no-login recording flow by skipping the cursor click when no
visible Login button is available.
| const tmpVideoDir = path.join( | ||
| os.tmpdir(), | ||
| `${config.fileStem}-video-${Date.now()}`, | ||
| ); | ||
| const targetWebm = path.join(outDir, `${config.fileStem}-walkthrough.webm`); | ||
| const targetPreview = path.join(outDir, `${config.fileStem}-preview.png`); | ||
|
|
||
| await fs.mkdir(outDir, { recursive: true }); | ||
| await fs.mkdir(tmpVideoDir, { recursive: true }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Remove the temporary video directory during cleanup.
Every run leaves the original recording under os.tmpdir(), so repeated walkthroughs accumulate large WebM files.
Proposed cleanup
try {
await browser.close();
} catch (err) {
logStep(`browser.close failed: ${err.message}`);
}
+ try {
+ await fs.rm(tmpVideoDir, { recursive: true, force: true });
+ } catch (err) {
+ logStep(`temporary video cleanup failed: ${err.message}`);
+ }Also applies to: 1406-1415
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/record.mjs` around lines 1342 - 1350, Update the cleanup
logic in the walkthrough recording flow to remove tmpVideoDir after the
temporary video has been processed, including when processing fails if cleanup
is handled in a finally path. Reuse the existing tmpVideoDir symbol and ensure
its contents are deleted without affecting targetWebm or targetPreview.
| if (video) { | ||
| try { | ||
| await video.saveAs(targetWebm); | ||
| } catch (err) { | ||
| logStep(`video.saveAs failed: ${err.message}`); | ||
| try { | ||
| const source = await video.path(); | ||
| await fs.copyFile(source, targetWebm); | ||
| } catch (copyErr) { | ||
| logStep(`video copy fallback failed: ${copyErr.message}`); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Fail the recording when the video cannot be persisted.
If both saveAs and the copy fallback fail, the error is only logged. The command then reports Recorded and exits successfully even though the promised artifact is absent. Retain the failure, finish cleanup, and then throw it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/walkthroughs/record.mjs` around lines 1393 - 1404, Update the video
persistence handling around video.saveAs and its copy fallback so a failure of
both operations is retained rather than only logged. Preserve the original
persistence error or combined failure, allow the surrounding recording cleanup
to complete, then rethrow it so the command does not report success without the
video artifact.
Summary
Adds a Playwright-based walkthrough recorder that produces narrated
.webmscreencasts and a preview.pngfor example apps. The recorder drives the live app with an on-screen cursor and caption overlay, optionally signing in with a testnet mnemonic to show full read/write flows.What's included
scripts/walkthroughs/record.mjs— the recorder. Onerun<App>flow per app, a shared cursor/caption driver, graceful SIGINT/SIGTERM handling that still finalizes a partial video, and progress logging.scripts/walkthroughs/captions/*.json— overlay copy per app, kept as plain JSON so wording can change without touching recorder code (caption files are required — no inline fallback).package.json—walkthrough:<app>npm scripts for each app.scripts/walkthroughs/README.md— usage, env vars, and output layout.dashnoterecording + preview underwalkthrough/dashnote/.Credentials & safety
The recorder loads
.env.walkthroughwhen present and accepts a mnemonic viaWALKTHROUGH_MNEMONIC/PLATFORM_MNEMONIC(and fallbacks). Sign-in uses.fill()into masked fields (nativepasswordinputs orcolor: transparent), so the mnemonic never renders on screen. Without credentials, apps record in read-only mode via--no-login. All identifiers shown are public testnet data.Summary by CodeRabbit
New Features
Documentation