test(desktop): rewrite verify-ui driver for the three-region layout#125
Conversation
337c4f9 to
537d940
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 537d9409b5
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| await step('[7] Edit Markdown → auto-saves to disk', async () => { | ||
| const ed = win | ||
| .locator('[data-editor-surface="panel"] .bn-editor[contenteditable="true"]') | ||
| .first(); | ||
| await ed.waitFor({ timeout: 5000 }); | ||
| await ed.click(); | ||
| await win.keyboard.press(process.platform === 'darwin' ? 'Meta+ArrowDown' : 'Control+End'); | ||
| await win.keyboard.type(' AUTOSAVE-PROBE-XYZ'); | ||
| await win.waitForTimeout(900); | ||
| const onDisk = readFileSync(join(WORKSPACE_DIR, 'note-a.md'), 'utf-8'); | ||
| assert(onDisk.includes('AUTOSAVE-PROBE-XYZ'), 'Typed text auto-saved to note-a.md on disk'); |
There was a problem hiding this comment.
Restore lossy Markdown e2e coverage
When verify-ui is used as the live-renderer Markdown check, this new step only edits a plain note and no longer exercises the lossy/frontmatter/raw-only/list fixtures that the old driver covered; packages/desktop/test/mdSegment.test.ts explicitly leaves the BlockNote load-time projection and splice-save behavior to this script. A regression that drops HTML comments/raw HTML, corrupts frontmatter, or loosens lists can now pass verify-ui as long as plain autosave works, so please keep at least those end-to-end fixtures/assertions in the rewritten driver.
Useful? React with 👍 / 👎.
| await win.mouse.move(sb.x + sb.width / 2 + 80, sb.y + sb.height / 2, { steps: 8 }); | ||
| await win.mouse.up(); |
There was a problem hiding this comment.
Reset the sidebar width after the resize check
On a fresh run the Electron window starts at 800px wide; after this +80px drag the sidebar remains widened for the rest of the script, and when [6] opens the right editor panel its 420px minimum leaves the canvas only about 40px wide. The later card(...).dblclick() calls still target canvas badges without re-framing, so they can become clipped/off-screen and make the rewritten driver fail or flake for reasons unrelated to the feature under test. The old driver dragged the sash back after asserting the resize; this rewrite should restore the width before continuing.
Useful? React with 👍 / 👎.
| writeFileSync(join(WORKSPACE_DIR, 'icon.png'), Buffer.from(TINY_PNG_BASE64, 'base64')); | ||
| // Seed minimal-but-recognizable PDF / audio / video fixtures so we can | ||
| // verify the corresponding viewer branches mount. The browser doesn't | ||
| // need a fully valid file — it just needs the right extension to dispatch | ||
| // to <iframe> / <audio> / <video>. | ||
| writeFileSync(join(WORKSPACE_DIR, 'sample.pdf'), '%PDF-1.4\n%verify-driver placeholder\n%%EOF\n'); | ||
| writeFileSync(join(WORKSPACE_DIR, 'sample.mp3'), 'ID3\x03\x00\x00\x00'); | ||
| writeFileSync(join(WORKSPACE_DIR, 'sample.mp4'), '\x00\x00\x00\x18ftyp'); | ||
| // A code file for the read-only text/code viewer (§7h). Code is NOT materialized | ||
| // as a canvas badge in v0 — it lives in the NavTree — so this opens via the tree. | ||
| // CRLF endings on purpose: the viewer must normalize them (no stray \r). | ||
| writeFileSync(join(WORKSPACE_DIR, 'sample.pdf'), '%PDF-1.4\n%verify placeholder\n%%EOF\n'); |
There was a problem hiding this comment.
Restore non-Markdown viewer coverage
The rewritten fixture set only creates image/PDF/code files, dropping the old sample.mp3, sample.mp4, and unsupported report.docx fixtures and their checks for <audio>, <video>, and the "Open in default app" fallback. Since the desktop onboarding still promises images, PDFs, audio, and video viewers, verify-ui can now pass even if the media viewer or unsupported-file fallback paths regress; please keep representative audio/video/other files in the seeded workspace and assert those viewer branches still mount.
Useful? React with 👍 / 👎.
| assert( | ||
| siblingAfter?.references?.some((r) => r.to === 'rename-target.md'), | ||
| `Sibling's outbound ref rewritten to new name (refs: ${JSON.stringify(siblingAfter?.references)})`, | ||
| (await win.locator('button', { hasText: 'New note' }).count()) >= 1, | ||
| 'Canvas shows the floating "New note" button', |
There was a problem hiding this comment.
Exercise the New note CTA flow
This rewrite only asserts that the floating "New note" button exists, while the old driver clicked through the dialog, created a nested path, verified the editor opened, and checked the duplicate-name error. If promptForNewNote stops wiring the CTA to file creation (or stops auto-creating folders), verify-ui now still passes because it never invokes the primary note-creation path; please keep an end-to-end create/duplicate check rather than just counting the button.
Useful? React with 👍 / 👎.
| for (let i = 0; i < 20; i++) { | ||
| const b = await bhRun('badge.get', { file: 'note-c.md' }).catch(() => null); | ||
| if (b?.orphan === true) { |
There was a problem hiding this comment.
Assert watcher updates reach the canvas
The replacement external-delete step polls badge.get directly through core, so it verifies that the watcher marked the badge orphaned but no longer proves the renderer subscription refreshed the visible canvas. A regression where Finder/agent file changes update .bh but leave the canvas stale would pass this check; the old live-add coverage waited for the new DOM badge without a reload, and this should keep a DOM assertion for add/delete/orphan updates.
Useful? React with 👍 / 👎.
537d940 to
8d8fa8f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d8fa8f0de
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| await win.waitForTimeout(300); | ||
| const opt = win.locator('[role=option]', { hasText: 'bh-verify-ws2' }).first(); | ||
| assert((await opt.count()) >= 1, 'Palette offers the 2nd workspace'); | ||
| await opt.click(); |
There was a problem hiding this comment.
Exercise dirty-editor flush before switching workspaces
In this workspace-switch check, the editor no longer has a pending Markdown edit when the palette option is clicked: the earlier autosave probe already waited and verified note-a.md on disk, and this step only switches to bh-verify-ws2. A regression where workspace switching drops the current editor's unflushed BlockNote content would now pass verify-ui; please switch immediately after typing a unique marker and assert the original workspace file contains it afterward.
Useful? React with 👍 / 👎.
| if (existsSync(GHOST_WS_MOVED)) rmSync(GHOST_WS_MOVED, { recursive: true, force: true }); | ||
| await step('[11] External delete → badge marked orphan', async () => { | ||
| await bhRun('focus.clear', {}); | ||
| rmSync(join(WORKSPACE_DIR, 'note-c.md'), { force: true }); |
There was a problem hiding this comment.
Restore watcher rename coverage
This watcher check now only unlinks note-c.md, so it exercises the orphan path but not the distinct rename path where the watcher should move the badge metadata/refs and rebind any open editor without showing a delete warning. If rename coalescing regresses, verify-ui would still pass because rmSync never covers that scenario; please keep an external rename assertion in addition to the delete case.
Useful? React with 👍 / 👎.
|
|
||
| // (Palette View-switch test removed with the View feature.) | ||
| await step('[5] Canvas badges for supported files; code excluded', async () => { | ||
| for (const f of ['note-a.md', 'note-b.md', 'note-c.md', 'icon.png', 'sample.pdf']) { |
There was a problem hiding this comment.
Restore folder badge navigation coverage
This replacement canvas check only expects file badges, and the new fixture set never creates a directory, so verify-ui no longer exercises folder badges, double-clicking into a folder sub-canvas, or returning from that scoped canvas. A regression in the folder-badge path would now pass as long as flat file badges render; please keep at least one seeded folder and assert the folder-scope flow still works.
Useful? React with 👍 / 👎.
| await win.locator('[data-testid="file-badge-prompt"]').fill('Verify-driver prompt for note-b'); | ||
| await win.waitForTimeout(900); // debounced save | ||
| const badge = await bhRun('badge.get', { file: 'note-b.md' }); |
There was a problem hiding this comment.
Restore badge reference UI coverage
The File Badge page step now only fills the prompt field and reads it back through badge.get; it no longer adds/removes references or verifies that canvas edge gestures persist badge.addRef relationships. If the reference dialog, edge drag, or edge delete wiring breaks, this script still passes even though those refs feed the agent brief, so please retain an end-to-end reference CRUD/edge assertion.
Useful? React with 👍 / 👎.
| await step('[5] Canvas badges for supported files; code excluded', async () => { | ||
| for (const f of ['note-a.md', 'note-b.md', 'note-c.md', 'icon.png', 'sample.pdf']) { | ||
| assert((await card(f).count()) === 1, `Canvas badge present: ${f}`); | ||
| } | ||
| assert((await card('sample.ts').count()) === 0, 'Code file is NOT a canvas badge (NavTree only)'); |
There was a problem hiding this comment.
Restore canvas viewport persistence checks
This canvas section now stops at badge presence and never drags a badge, pans/zooms the canvas, or reloads to prove the persisted badge positions and viewport are reapplied. Regressions in workspace.setViewport/position persistence or the zoom controls would therefore pass verify-ui; please keep a small drag/pan/zoom persistence assertion around the rewritten layout.
Useful? React with 👍 / 👎.
| await win.keyboard.type(' AUTOSAVE-PROBE-XYZ'); | ||
| await win.waitForTimeout(900); | ||
| const onDisk = readFileSync(join(WORKSPACE_DIR, 'note-a.md'), 'utf-8'); |
There was a problem hiding this comment.
Restore external-edit adoption coverage
The Markdown editor coverage now only types in the renderer and waits for that local edit to auto-save, but it never mutates the open file on disk and verifies the editor adopts the external contents. If agent/Finder edits stop flowing into an already-open editor, verify-ui would still pass because this step only covers UI-to-disk saves; please keep an external rewrite assertion for an open Markdown tab.
Useful? React with 👍 / 👎.
| await bhRun('workspace.add', { path: WORKSPACE2_DIR, setup: false }); | ||
| await bhRun('workspace.use', { name: 'bh-verify-ws' }); |
There was a problem hiding this comment.
Restore unreachable-workspace UI coverage
The workspace setup now registers only reachable folders and later switches between them, so the script no longer covers the path where a registered workspace disappears from disk and the sidebar must show the Workspace folder not found recovery UI with Unregister/Re-select actions. A regression in the PATH_NOT_FOUND handling would pass this rewritten driver; please keep a missing-folder switch and unregister assertion.
Useful? React with 👍 / 👎.
| ); | ||
| }); | ||
|
|
||
| await step('[3] Sidebar: aside, name, path-in-title, sash resize, toggle', async () => { |
There was a problem hiding this comment.
Restore workspace menu dialog coverage
This sidebar step verifies the header, resize sash, and toggle, but it no longer opens the workspace menu to exercise rename/remove validation and destructive confirmation dialogs. If those sidebar menu actions or their custom dialog focus/validation wiring regress, verify-ui now still passes; please retain at least one rename validation and one remove/cancel/confirm path.
Useful? React with 👍 / 👎.
8d8fa8f to
26b5c2f
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26b5c2ff55
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| wsListAfterRestore.includes('bh-verify-ws'), | ||
| `Restored to bh-verify-ws so downstream tests work (workspaces: ${JSON.stringify(wsListAfterRestore)})`, | ||
| ); | ||
| await step('[7] Edit Markdown → auto-saves to disk', async () => { |
There was a problem hiding this comment.
Restore in-canvas Markdown edit coverage
This replacement only edits Markdown through the right-panel surface ([data-editor-surface="panel"]), so verify-ui no longer clicks the per-card inline edit control (canvas-inline-edit-button-* in BadgeNode) or proves that edits made directly on a canvas badge save back to disk. If the canvas pencil toggle, inline BlockNote mount, or finishInlineEdit save path regresses, this script will still pass as long as the separate right-panel editor works; please keep an end-to-end inline badge edit/save assertion in addition to the panel autosave check.
Useful? React with 👍 / 👎.
| assert( | ||
| (await win.locator('[data-pane-id] pre').count()) >= 1, | ||
| 'Code file opens in a read-only <pre> viewer', |
There was a problem hiding this comment.
Assert the code viewer renders normalized content
The seeded sample.ts still uses CRLF endings, but this check now only proves that some <pre> exists. A regression that opens the generic text pane while dropping the file contents, omitting the read-only/gutter UI, or leaking raw \r characters would pass here; the old coverage read the viewer text and gutter specifically, so please assert the expected source text and normalized line numbers rather than only the container element.
Useful? React with 👍 / 👎.
| await win.locator('[data-testid="sidebar-toggle"]').click(); | ||
| await win.waitForTimeout(200); | ||
| } | ||
|
|
||
| // --- 12d. Command palette (Cmd+K): opens on shortcut, filters actions | ||
| // by query, lets the user pick a file with Enter, closes after running. | ||
| console.log('\n[12d] Cmd+K command palette'); | ||
| const cmdK = process.platform === 'darwin' ? 'Meta+k' : 'Control+k'; | ||
| await win.keyboard.press(cmdK); | ||
| await win.waitForTimeout(200); | ||
| const paletteInput = win.locator('[data-testid="command-palette-input"]'); | ||
| assert((await paletteInput.count()) === 1, 'Cmd+K opens the command palette (input mounted)'); | ||
| await paletteInput.fill('intro'); | ||
| await win.waitForTimeout(150); | ||
| const visibleRows = await win.locator('[role=dialog] [role=option]').count(); | ||
| assert( | ||
| visibleRows >= 1, | ||
| `Palette filtered to "intro" shows at least one match (rows=${visibleRows})`, | ||
| ); | ||
| await win.screenshot({ path: `${SCREENS_DIR}/13-command-palette.png` }); | ||
| // Verify keyboard shortcut hints render on the chrome-action rows. | ||
| // Filter to "new note" so the only visible row is the New note action. | ||
| await paletteInput.fill('new note'); | ||
| await win.waitForTimeout(150); | ||
| const actionRowText = await win.locator('[role=dialog] [role=option]').first().innerText(); | ||
| assert( | ||
| /⌘N|Ctrl\+N/.test(actionRowText), | ||
| `New-note action row shows the global shortcut hint (row text: ${JSON.stringify(actionRowText.slice(0, 80))})`, | ||
| ); | ||
| await win.screenshot({ path: `${SCREENS_DIR}/13b-palette-shortcut-hint.png` }); | ||
|
|
||
| // Palette filter also matches against the user-written badge prompt | ||
| // (not just the filename). Set a distinctive prompt on intro.md, then | ||
| // search for a word from THAT prompt — intro.md should still appear. | ||
| await paletteInput.fill(''); | ||
| await win.waitForTimeout(100); | ||
| // Set prompt out-of-band via core so the test isn't slowed by the badge | ||
| // UI dance. | ||
| await bhRun('badge.set', { | ||
| file: 'intro.md', | ||
| patch: { prompt: 'thaumaturgically distinctive prompt for palette search' }, | ||
| assert((await win.locator('aside').count()) === 0, 'Toggle fully hides the sidebar'); | ||
| await win.locator('[data-testid="sidebar-toggle"]').click(); | ||
| await win.waitForTimeout(200); | ||
| assert((await sidebarAside().count()) === 1, 'Toggle brings the sidebar back'); |
There was a problem hiding this comment.
Verify sidebar hidden state survives reload
This toggle check hides and immediately reopens the sidebar without reloading while it is hidden, so it no longer exercises the bh:sidebar-open localStorage persistence path that the driver itself calls out as flaky across Electron userData runs. If the hide/show button works for the current render but the preference stops being saved or restored after reload, verify-ui will still pass; please keep the hidden-state reload assertion around this interaction.
Useful? React with 👍 / 👎.
| await step('[5] Canvas badges for supported files; code excluded', async () => { | ||
| for (const f of ['note-a.md', 'note-b.md', 'note-c.md', 'icon.png', 'sample.pdf']) { | ||
| assert((await card(f).count()) === 1, `Canvas badge present: ${f}`); | ||
| } | ||
| assert((await card('sample.ts').count()) === 0, 'Code file is NOT a canvas badge (NavTree only)'); |
There was a problem hiding this comment.
Assert badges render live previews, not just shells
This canvas step only counts badge test IDs, so it will pass if BadgeNode stops loading Markdown excerpts or image thumbnails and the canvas regresses to empty labeled shells. The three-region redesign depends on badges being living file previews, and the old driver checked representative text/image preview content; please assert at least one Markdown excerpt and the image thumbnail inside the cards instead of only card presence.
Useful? React with 👍 / 👎.
| await win.waitForTimeout(300); | ||
| const opt = win.locator('[role=option]', { hasText: 'note-c.md' }).first(); | ||
| assert((await opt.count()) >= 1, 'Palette finds note-c.md'); | ||
| await opt.click(); |
There was a problem hiding this comment.
Exercise palette keyboard selection
The palette checks now pick results with a mouse click, so they no longer cover the primary keyboard path where Enter runs the selected row, Escape closes the dialog, arrow keys clamp selection, and empty-result Enter is a no-op. If CommandPalette's keydown state machine regresses, keyboard users lose the command-center flow while this script still passes; please keep at least one keyboard invocation/assertion instead of only clicking the option.
Useful? React with 👍 / 👎.
| await win.keyboard.press(process.platform === 'darwin' ? 'Meta+ArrowDown' : 'Control+End'); | ||
| await win.keyboard.type(' AUTOSAVE-PROBE-XYZ'); | ||
| await win.waitForTimeout(900); | ||
| const onDisk = readFileSync(join(WORKSPACE_DIR, 'note-a.md'), 'utf-8'); | ||
| assert(onDisk.includes('AUTOSAVE-PROBE-XYZ'), 'Typed text auto-saved to note-a.md on disk'); |
There was a problem hiding this comment.
Restore undo/redo autosave coverage
This editor step only types once and verifies the initial autosave, dropping the previous Cmd/Ctrl+Z and redo checks. A regression in BlockNote history handling or in autosaving after undo/redo would now pass as long as ordinary typing saves, so please exercise undo and redo with a unique marker and assert the final redone content reaches disk.
Useful? React with 👍 / 👎.
26b5c2f to
0344ada
Compare
…ge (codex #125) The lean verify-ui rewrite dropped coverage the old driver had; codex flagged 8 areas. Restoring the first two (disk/dblclick-driven, no fragile dialog/drag): - Folder badge → sub-canvas: a `chapter/` fixture + double-click scopes into the folder (its file shown, root badges hidden), and the "← /chapter" control exits back to root. Locks the folder-as-grouping navigation. - External-edit adoption: with note-a.md open + clean, an on-disk write is ADOPTED by the editor (file = truth) — guards against silently losing an agent/Finder edit to an open file. Full driver green (44→48 assertions). Remaining codex #125 coverage to restore: viewport persistence, badge-reference UI, watcher rename, unreachable-workspace, workspace-menu, dirty-editor-flush-before-switch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c66782d to
a15f08a
Compare
…opped coverage The old Playwright-Electron driver broke wholesale on the canvas-as-backdrop redesign (failed at [2]/[3] on the removed sidebar-workspace-select testid and the old <aside> assumption). Rewritten against the current DOM: canvas-card-<file> badges, two <aside> (sidebar/editor), editor-tab/[data-pane-id] viewers, ⌘K workspace switch. Resets localStorage at startup for determinism; per-step try/catch so one failure reports instead of crashing the run. Covers onboarding, workspace add+hint, sidebar, NavTree, canvas badges, selection-as-deixis, focus chip, editor open + autosave, image/code/PDF viewers, folder→sub-canvas nav, external-edit adoption, File Badge page, ⌘K palette + workspace switch, orphaning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a15f08a to
9b5d86e
Compare
Stacked on #124 (base =
feat/focus-selection-side-effect) because the[5d]step asserts the Phase 0 selection-mirror contract, which only exists on that
branch. After #124 merges, this retargets to
main.Why
The Playwright-Electron
verify-uidriver was broken wholesale by thecanvas-as-backdrop redesign (#119/#120): it assumed the old TopBar+Main shell
and died at
[2]/[3]on the removedsidebar-workspace-selecttestid and an<aside>that now rendersnullwhen the sidebar is closed — before reachingany later check. It was un-runnable on
main.What
A from-scratch, leaner driver mapped against the current renderer DOM:
[data-testid="canvas-card-<file>"](file == node id), not.react-flow__node. Two<aside>now exist (sidebar first, editor last).Files open into
editor-tab/ pane[data-pane-id]viewers, not a singleFilePreview. The workspace switcher moved to the command palette.
localStorageat startup. UI prefs (sidebar open/width,right-panel) persist in Electron userData independent of
BH_CONFIG_DIR, so aleaked
bh:sidebar-open='0'was the real cause of the old driver's flaky[3] asidetimeout. This makes the run deterministic.step(name, fn)with try/catch, so one failingselector reports and continues instead of crashing the whole driver (the
old monolith's fatal flaw).
Coverage
Onboarding ·
workspace.createDemo· workspace add + agent-protocol hint ·sidebar (name / path-in-tooltip / sash resize / toggle) · NavTree · canvas
badges (code excluded) · selection-as-deixis
[5d](single = UI-only;multi ≥2 mirrors into
focus.md, verified viafocus.get+ the renderedbrief) · Agent Context chip + clear · editor open + Markdown autosave ·
image / code / PDF viewers · File Badge page (prompt persist) · ⌘K palette
(file open + workspace switch) · external-delete → orphan.
Verification
Green end-to-end — 44 assertions / 16 sections — against a freshly built
Phase 0 app; biome clean. Net
-2613 / +317(the old driver had accreted ~50hyper-specific sections over many PRs; this covers the load-bearing flows).
🤖 Generated with Claude Code