| name | use-vishot-with-web |
| description | Capture deterministic web routes with Vishot. Use when the selected Vishot target is a locally runnable browser application and Codex must capture routes, responsive viewports, or interaction-driven UI states without requiring agent-browser. |
Use Vishot with Web
Prefer product-owned Vishot capture roots when available. Otherwise capture a local URL directly.
Workflow
-
Identify the route, state, viewport, theme, locale, and output directory supplied by the caller.
-
Ensure Vishot's Playwright Chromium runtime exists. If Vishot reports a missing executable, run pnpm exec playwright install chromium once and retry.
-
Start the application with its repository-owned development or preview command.
-
Wait until the development server reports that it is listening, then let Vishot handle reload stability with an explicit --settle-ms 2500. Vishot intentionally has no default delay because it serves projects with different readiness contracts.
-
Run the documented scene command when the app exposes Vishot capture roots and a ready signal.
-
Otherwise capture the route directly:
pnpm exec vishot render \
--target browser \
http://127.0.0.1:5173/settings \
--width 1440 \
--height 900 \
--settle-ms 2500 \
--output-dir /absolute/output/settings
-
Repeat for each requested route and viewport. Use --name only when URL-derived names collide or fail to describe the state.
-
Inspect each image for loading screens, stale data, iframe refusal, missing fonts, permission prompts, and animation instability.
-
Return its stable ID, title, viewport, and absolute path to $use-vishot or the caller.
Viewport Sizing
Prefer Vishot's browser dimensions when the size is known before launch:
pnpm exec vishot render \
--target browser \
http://127.0.0.1:5173/settings \
--width 1440 \
--height 900 \
--output-dir /absolute/output/settings
Use Playwright's viewport API when a browser scenario must change responsive states during the same run:
await page.setViewportSize({ width: 390, height: 844 })
await page.waitForFunction(size => (
globalThis.innerWidth === size.width
&& globalThis.innerHeight === size.height
), { width: 390, height: 844 })
- Web pages do not have an Electron
BrowserWindow; page.setViewportSize() is therefore the correct way to change the browser viewport in scenario code.
- Do not use DOM APIs such as
window.resizeTo() to control Vishot output. Browser security and window-manager behavior can ignore them, and they do not define the Playwright capture viewport reliably.
- Treat
width and height as CSS pixels. The output image can contain more device pixels when the browser context uses a device scale factor above 1.
Scenario Readiness and Locators
-
Implement readiness for the specific route and UI state. A heading, button, or expected text from one page is not a reusable readiness condition for another page.
-
Wait in this order: let Vishot establish URL and reload stability, confirm the final route, wait for a state-specific visible locator, confirm transient overlays or loading indicators are gone, then let Vishot keep its final settle window for remaining bundling, refresh, animation, or rendering.
-
Use text only when it is visible, unique, stable for the selected locale, and intrinsic to the intended state. Do not wait on generic headings, placeholders, network-derived values, or text copied from another scenario.
-
Prefer semantic locators such as getByRole('button', { name }) and getByLabel(). For an icon-only control, locate the owning button by its icon and click the button, not the icon node:
const settingsButton = page.locator('button').filter({
has: page.locator('[i-solar\\:settings-minimalistic-outline]'),
}).first()
Escape : in attribute-based icon selectors. Inspect the rendered DOM and repository icon usage before choosing a selector; avoid coordinates, nth-child, and incidental layout classes.
-
Treat drawers, menus, accordions, onboarding, and responsive navigation as stateful UI. Check the desired content, aria-expanded, or data-state before clicking a toggle. Make ensureOpen()-style helpers idempotent: return when already open, otherwise click once and wait for the open-state postcondition.
-
If no stable accessible or product-owned selector exists, add one to the product scenario or UI instead of weakening the wait to a fixed timeout.
Constraints
- Keep the target app local. Do not use this workflow to capture an untrusted remote page.
- Keep route state deterministic with fixtures or seeded local storage when necessary.
- Use identical dimensions and device scale behavior for captures that will be compared.