Stateful scenarios and local browser matrices¶
LayoutLens 4 adds stateful scenarios and local browser selection. A scenario runs in one
browser context, records actions and focus transitions, and captures a
RenderState at each named checkpoint. It needs no model or API key.
from layoutlens import Scenario
scenario = (
Scenario("/checkout", base_url="http://localhost:3000")
.tab()
.expect_focus("email")
.type("me@example.com")
.click("Continue")
.expect_visible("payment")
.checkpoint("payment")
)
report = await scenario.run(browser="firefox", viewport=(1280, 800))
report.save("artifacts/checkout-firefox")
assert report.gate_status == "pass", report.to_json()
Start your application before running this example. Local HTML files also work;
LayoutLens serves the file and its neighboring assets over a temporary local
HTTP server. navigate() follows routes in the existing context, retaining
cookies and storage. Builder methods return a new scenario, so one definition
can be reused across browsers. The standalone checkout example
creates its own pages and runs all three engines.
Actions and expectations¶
A plain target such as email resolves by exact ID, then accessible label, then
test ID, then exact text. Use css=#checkout button, #email, or a Playwright
selector with an explicit engine prefix such as role=button[name="Continue"]
when you need explicit targeting. Prefix bare CSS tag selectors with css=. A selector that matches several
elements is an execution error for single-element actions; the runner never
picks an arbitrary match. expect_count() deliberately accepts multiple matches.
Interaction |
Methods and evidence |
|---|---|
Keyboard navigation |
|
Form entry |
|
Hover content |
|
Dialog dismissal |
|
Overlay interference |
|
Focus appearance |
|
Keyboard reachability |
|
Responsive navigation |
|
Dragging and pointers |
|
Routes and content |
|
Checkpoints preserve focus, DOM attributes, color scheme, and motion preference. They wait for fonts and images and check for changes during capture. They do not stop animations or reset the application. Use explicit expectations to wait for the application state you intend to test. Animated or changing captures can be incomplete.
A repeated focus target, fully obscured focus, an obscured click target, or multiple visible modals can produce a candidate finding with measurements and unresolved exceptions. These observations do not prove a keyboard trap or an inaccessible dialog. Intentional focus containment and nested modal workflows can be valid. A bounded reachability failure means only that the specified sequence did not reach its target within the configured bound.
Focus styling checks do not establish WCAG focus-appearance conformance. Computed outlines and borders do not account for every pseudo-element, image, contrast condition, or surrounding pixel. Use the screenshot and declared expectations to inspect the evidence.
Outcomes and saved evidence¶
pass: all steps ran, checkpoints were complete, and the selected policy passed.fail: an explicit expectation failed, orpolicy="findings"selected a candidate finding.incomplete: navigation or an action failed, a checkpoint was unstable or lacked required evidence, or event retention reached its limit.
The default qualified policy leaves automatic interaction candidates as
warnings. Explicit expectations are user-declared contracts and can fail a run
without statistical qualification of a heuristic. nothing reports failures
without blocking; execution gaps still produce incomplete.
Expectations continue after a failed assertion so later checkpoints can retain evidence. Action errors stop the sequence and attempt a failure checkpoint. Unexpected native browser dialogs are dismissed and mark the run incomplete. New windows also mark it incomplete; the runner follows the original page. DOM dialogs and navigation within that page are supported.
report.steps contains before/after focus and target receipts. report.events
contains ordered focus, keyboard, input, pointer, drag, and navigation events.
Typed characters are masked in keyboard events and input values are omitted from
the event log. Reports retain a SHA-256 fingerprint of the steps, including their
arguments; this is an identity check, not a way to protect secret input. Definitions exported by scenario.to_dict() include their typed
values. Checkpoints contain page data, including visible form values; password
control values are redacted. DOM attributes and screenshots are not scrubbed. Save artifacts with the same care as application test data.
Events before instrumentation starts are not replayed; the first step records
the current page state. The run retains at most 10,000 events.
from layoutlens import ScenarioReport
baseline = ScenarioReport.load("artifacts/baseline")
candidate = ScenarioReport.load("artifacts/candidate")
regression = baseline.diff(candidate, policy="findings")
assert regression.gate_status == "pass"
Comparison matches checkpoints by name and reports changed focus transitions.
Missing checkpoints, different definitions (including action arguments), and incompatible capture
conditions prevent a passing comparison. The per-checkpoint DiffReport contains
the rendered deltas. Across routes in one run, the saved states are also available
as report.checkpoints[name] for direct inspection or explicit comparison.
Browser configuration¶
Install the local engines you intend to use:
playwright install chromium firefox webkit
Scenario.run(), capture_state(), and LayoutLens() accept browser,
color_scheme, reduced_motion, locale, timezone_id, and
device_scale_factor. Capture and scenario APIs accept named viewports or a
(width, height) tuple in CSS pixels. Lower-level capture and auditor objects
accept a BrowserConfig through their browser_config argument.
for engine in ("chromium", "firefox", "webkit"):
report = await scenario.run(
browser=engine,
viewport=(1280, 800),
color_scheme="dark",
reduced_motion="reduce",
locale="en-US",
device_scale_factor=2,
)
report.save(f"artifacts/{engine}")
Each state identifies its actual browser/version, viewport, DPR, user agent, platform, locale, timezone, media preferences, touch capability, and requested emulation settings. Compare each browser with its own baseline. A Chromium baseline and Firefox candidate are incompatible for a regression gate.
All three engines capture DOM, computed styles, geometry, screenshots, form
state, and accessible roles/names. Firefox and WebKit use Playwright ARIA
snapshots; Chromium additionally supplies its native accessibility tree and
matched CSS/source-map attribution through CDP. state.capabilities records
these differences. Missing native CSS attribution is disclosed on elements and
does not by itself invalidate measured layout comparisons.
Firefox uses the requested narrow viewport and DPR for mobile presets but does
not enable Playwright’s unsupported is_mobile emulation. This does not simulate
a mobile Firefox device. These are local Playwright engines; no remote browser
farm is involved. Closed shadow-root inspection is available only through
Chromium’s CDP, and closed contents remain unsupported. Frame interiors, canvas,
and video retain their existing coverage limitations.
RenderState uses artifact schema 2 for the new environment and form-state fields. Schema 1 artifacts must be recaptured; loading an unsupported schema fails explicitly.
CLI, pytest, and MCP¶
Write a JSON definition from scenario.to_dict(), then run:
layoutlens scenario checkout.json --browser webkit --save artifacts/checkout
layoutlens scenario checkout.json --browser firefox --output sarif
layoutlens capture checkout.html --browser firefox --color-scheme dark --save artifacts/page
The scenario command exits with 0 for pass, 1 for fail, and 2 for incomplete.
It accepts the same media, locale, timezone, and DPR flags as capture/diff.
--width and --height must be supplied together for a custom viewport.
The pytest fixture adds layoutlens.assert_scenario(scenario) and the
--layoutlens-browser option. The MCP server exposes run_ui_scenario, taking
a definition and a new artifact directory, and browser options on
capture_render_state. Both return structured evidence. SARIF includes explicit
expectation failures, candidate interaction findings, and execution completeness.