@orkestrel/scaffold 0.0.78 → 0.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +54 -4
- package/dist/bin/main.js.map +1 -1
- package/dist/host/claude/agents/orkestrel.md +4 -4
- package/dist/host/claude/rules/workspace.md +2 -0
- package/dist/host/guides/probe.md +9 -5
- package/dist/host/guides/scaffold.md +30 -12
- package/dist/host/guides/test.md +443 -149
- package/dist/host/manifest.json +7 -7
- package/dist/host/tests/config.test.ts +18 -9
- package/dist/src/core/index.cjs +16 -12
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1 -1
- package/dist/src/core/index.d.ts +1 -1
- package/dist/src/core/index.js +16 -12
- package/dist/src/core/index.js.map +1 -1
- package/package.json +8 -8
package/dist/host/guides/test.md
CHANGED
|
@@ -254,6 +254,7 @@ a union's arms escaped as `\|`.
|
|
|
254
254
|
| `Color` | type | `readonly [red, green, blue, alpha]` | Represents one rendered color as straight sRGB channels and its alpha. |
|
|
255
255
|
| `ElementOptions` | interface | `{ classes?, text?, attributes? }` | Configures one built element: its class list, its text, and its attributes. |
|
|
256
256
|
| `FrameOptions` | interface | `{ path, width, height, element? }` | Configures one captured frame: where it is written, the viewport it is shot at, and what it shoots. |
|
|
257
|
+
| `FrameOffset` | interface | `{ top, left }` | Represents how far an element frame moves the tester frame from the runner window's origin for the shot, in CSS pixels. |
|
|
257
258
|
| `FrameReading` | interface | `{ width, height, floor }` | Represents one written frame read back from the file a capture produced: its size in device pixels, and the single color its bottom row paints. |
|
|
258
259
|
| `CaptureVariant` | interface | `JourneyVariant` plus `{ apply? }` | Adds to a journey variant the document change a capture run applies before resizing. |
|
|
259
260
|
| `PortfolioOptions` | interface | `{ states, variants, variant, directory, enabled? }` | Configures a capture portfolio: the state registry, the variant matrix, this run's variant, where it writes, and whether it writes at all. |
|
|
@@ -261,6 +262,7 @@ a union's arms escaped as `\|`.
|
|
|
261
262
|
| `JournalStep` | interface | `{ action, trigger, result }` | Represents one scripted step a journal recorded, and what the surface did about it. |
|
|
262
263
|
| `JournalInterface` | interface | `{ steps, output }` plus `start` / `stop` / `record` | Records one scenario: every step it took and everything the page said while it ran. |
|
|
263
264
|
| `StateOptions` | interface | `WaitOptions` plus `{ absent? }` | Configures a bounded wait over the states a control announces. |
|
|
265
|
+
| `MediaOptions` | interface | `{ print?, motion?, forced? }` | Configures the tester's print medium, motion preference, and forced colours. |
|
|
264
266
|
| `StorageOptions` | interface | `{ values?, reads?, writes?, quota? }` | Configures an inert `Storage`: its seed, which operations the host permits, and its quota. |
|
|
265
267
|
| `WebStorageInterface` | interface | `Storage` plus `{}` plus `permit` | Holds a store the host can withhold and later grant. |
|
|
266
268
|
| `CensusReading` | interface | `{ elements, tokens, undeclared }` | Reports an authored-class census: the population walked, the tokens found, and the undeclared. |
|
|
@@ -285,79 +287,106 @@ A `Shape` cell holds the constant's declared type.
|
|
|
285
287
|
| `FOCUSABLE_SELECTOR` | const | `string` | Names what sequential keyboard navigation can reach, before disabled and unrendered elements go. |
|
|
286
288
|
| `HEADER_ROLES` | const | `Readonly<Record<string, string>>` | Names the role a `th` carries for the header axis its `scope` names. |
|
|
287
289
|
| `IMPLICIT_ROLES` | const | `Readonly<Record<string, string>>` | Names the role each listed tag carries in the accessibility tree when it declares none of its own. |
|
|
290
|
+
| `MEDIA_STAGE` | const | `string` | Names the tester root's attribute holding the media readings observed before the first stage. |
|
|
291
|
+
| `POINTER_HOLD` | const | `string` | Names the tester root's attribute holding the pressed pointer's page coordinates. |
|
|
288
292
|
|
|
289
293
|
#### Helpers
|
|
290
294
|
|
|
291
|
-
| API
|
|
292
|
-
|
|
|
293
|
-
| `resolveAccessible`
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
304
|
-
| `
|
|
305
|
-
| `
|
|
306
|
-
| `
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
311
|
-
| `
|
|
312
|
-
| `
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `
|
|
335
|
-
| `
|
|
336
|
-
| `
|
|
337
|
-
| `
|
|
338
|
-
| `
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
295
|
+
| API | Kind | Signature | Summary |
|
|
296
|
+
| -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `resolveAccessible` | function | `(name: string) => HTMLElement` / `(role: string, name: string) => HTMLElement` | Resolves one visible, focus-reachable interactive element by its exact accessible name. A wholly-off-viewport target is scrolled into view before reachability is measured. |
|
|
298
|
+
| `resolveAccessibleWithin` | function | `(region: string, role: string, name: string) => HTMLElement` | Resolves one human-reachable control by role and accessible-name text inside a named region. |
|
|
299
|
+
| `resolveRendered` | function | `(first: string, second?: string) => HTMLElement` | Resolves one rendered, focus-reachable interactive element without requiring it to intersect the viewport yet. |
|
|
300
|
+
| `computeNamePattern` | function | `(name: string) => RegExp` | Computes the pattern that matches one accessible name a decorative glyph may sit beside. |
|
|
301
|
+
| `isOutsideViewport` | function | `(rectangle: DOMRectReadOnly) => boolean` | Determines whether a rectangle lies wholly outside the browser viewport. |
|
|
302
|
+
| `isRendered` | function | `(element: Element) => boolean` | Determines whether the accessibility tree presents one element at all. |
|
|
303
|
+
| `isReachable` | function | `(element: Element) => boolean` | Determines whether a person can click one element where it sits. |
|
|
304
|
+
| `readHit` | function | `(element: Element) => Element \| undefined` | Reads the topmost element at one element's bounding-box centre. |
|
|
305
|
+
| `clickAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Clicks one visible, focus-reachable control by its accessible name through the browser provider. |
|
|
306
|
+
| `clickAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Clicks one human-reachable control by role and accessible-name text inside a named region. |
|
|
307
|
+
| `clickDisclosure` | function | `(name: string) => Promise<void>` | Opens or closes one native details disclosure by its rendered summary. |
|
|
308
|
+
| `sendProtocol` | function | `(method: string, params: Readonly<Record<string, unknown>>) => Promise<void>` | Sends one DevTools protocol command through the browser provider. |
|
|
309
|
+
| `hoverAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Hovers one visible, focus-reachable control by its accessible name through the browser provider. |
|
|
310
|
+
| `holdAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Holds the primary pointer button on one visible, focus-reachable control by its accessible name. |
|
|
311
|
+
| `holdAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Holds the primary pointer button on one control by role and accessible-name text inside a named region. |
|
|
312
|
+
| `driveHold` | function | `(resolve: () => HTMLElement, name: string) => Promise<void>` | Holds the primary pointer button on the control a resolver returns, through the browser provider. |
|
|
313
|
+
| `releasePointer` | function | `() => Promise<void>` | Releases a held pointer and parks it outside the page, clearing hover. |
|
|
314
|
+
| `typeAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value through focus, select-all, deletion, and real keystrokes. |
|
|
315
|
+
| `fillAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value in one operation, for text too long to type key by key. |
|
|
316
|
+
| `traverseAccessible` | function | `(name: string) => Promise<HTMLElement>` | Reaches a named control only through natural forward Tab traversal from the current focus. |
|
|
317
|
+
| `traverseAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<HTMLElement>` | Reaches a control by role and accessible-name text inside a named region, only through natural forward Tab traversal from the current focus. |
|
|
318
|
+
| `driveTraversal` | function | `(resolve: () => HTMLElement, name: string) => Promise<HTMLElement>` | Reaches the control a resolver returns, only through natural forward Tab traversal from the current focus. |
|
|
319
|
+
| `pressKeys` | function | `(keys: string) => Promise<void>` | Sends a key sequence to whatever holds focus, and refuses to send it to nothing. |
|
|
320
|
+
| `readPerception` | function | `(name: string) => string` | Reads the normalized visible text of one named region, dialog, table, tab panel, or alert. |
|
|
321
|
+
| `readPage` | function | `() => string` | Reads the normalized visible text of the whole page. |
|
|
322
|
+
| `readFocus` | function | `() => string \| undefined` | Reads the rendered text of the element that holds focus. |
|
|
323
|
+
| `readValue` | function | `(role: string, name: string) => string` | Reads the value a resolved control renders. |
|
|
324
|
+
| `readRefusal` | function | `(name: string) => string \| undefined` / `(role: string, name: string) => string \| undefined` | Reads the refusal one named target answers with, or nothing when it resolves. |
|
|
325
|
+
| `readText` | function | `(element: Element) => string` | Reads one element's rendered text the way a name computation reads it. |
|
|
326
|
+
| `readRole` | function | `(element: Element) => string \| undefined` | Reads the role one element carries in the accessibility tree. |
|
|
327
|
+
| `readName` | function | `(element: Element) => string` | Reads the accessible name one element is announced under. |
|
|
328
|
+
| `readStates` | function | `(element: Element) => readonly string[]` | Reads the states one element is announced in. |
|
|
329
|
+
| `describeTree` | function | `(element: Element) => string` | Describes the accessible tree one rendered element presents. |
|
|
330
|
+
| `describeFocus` | function | `(element: Element) => string` | Describes the order sequential keyboard navigation visits one element's controls in. |
|
|
331
|
+
| `waitForFrame` | function | `() => Promise<void>` | Waits for one animation frame to settle pending browser paint work. |
|
|
332
|
+
| `waitForState` | function | `(name, state, options?) => Promise<readonly string[]>` / `(role, name, state, options?) => Promise<readonly string[]>` | Waits until one named control announces a state, or stops announcing it. |
|
|
333
|
+
| `waitForAnimations` | function | `(element: Element, options?: WaitOptions) => Promise<void>` | Waits until every finite animation on one element and its subtree has stopped moving. |
|
|
334
|
+
| `build` | function | `<K extends keyof HTMLElementTagNameMap>(tag: K, options?: ElementOptions) => HTMLElementTagNameMap[K]` | Builds one unmounted element of a known tag, wearing the classes, text, and attributes asked for. |
|
|
335
|
+
| `mount` | function | `<T extends Element>(element: T) => T` | Puts one element into the document and hands it straight back. |
|
|
336
|
+
| `render` | function | `(markup: string) => HTMLDivElement` / `(tag: K, classes: string) => HTMLElementTagNameMap[K]` | Renders one fixture into the document from trusted markup. |
|
|
337
|
+
| `typeInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and announces it the way typing into the field does. |
|
|
338
|
+
| `commitInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and commits it, the way typing and then leaving the field does. |
|
|
339
|
+
| `clearStorage` | function | `() => void` | Clears both browser storage surfaces. |
|
|
340
|
+
| `removeDatabase` | function | `(name: string) => Promise<void>` | Deletes one IndexedDB database and reports what the request actually did. |
|
|
341
|
+
| `convertSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts normalized encoded sRGB channels to the clipped paint scale. |
|
|
342
|
+
| `convertLinearSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts linear sRGB channels to encoded, clipped paint channels. |
|
|
343
|
+
| `convertXYZD65` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D65 XYZ coordinates to clipped sRGB paint channels. |
|
|
344
|
+
| `convertXYZD50` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D50 XYZ coordinates to clipped sRGB paint channels. |
|
|
345
|
+
| `convertOKLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts OKLab coordinates to clipped sRGB paint channels. |
|
|
346
|
+
| `convertLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts CIE Lab coordinates relative to D50 to clipped sRGB paint channels. |
|
|
347
|
+
| `convertDisplayP3` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Display P3 channels to clipped sRGB paint channels. |
|
|
348
|
+
| `convertA98RGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded A98 RGB channels to clipped sRGB paint channels. |
|
|
349
|
+
| `convertProPhotoRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded ProPhoto RGB channels to clipped sRGB paint channels. |
|
|
350
|
+
| `convertRec2020` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Rec. 2020 channels to clipped sRGB paint channels. |
|
|
351
|
+
| `parseColor` | function | `(value: string) => Color \| undefined` | Parses computed CSS Color 4 values into clipped straight sRGB channels. |
|
|
352
|
+
| `parseCSSColor` | function | `(value: string) => Color \| undefined` | Resolves any CSS color expression to straight sRGB channels, by asking the browser. |
|
|
353
|
+
| `matchesColor` | function | `(first: string \| Color, second: string \| Color) => boolean` | Determines whether two colors render the same, within the rounding a browser does. |
|
|
354
|
+
| `blendColor` | function | `(front: Color, back: Color) => Color` | Composites one color over another. |
|
|
355
|
+
| `measureLuminance` | function | `(color: Color) => number` | Measures one opaque color's WCAG relative luminance. |
|
|
356
|
+
| `measureContrast` | function | `(front: Color, back: Color) => number` | Measures the WCAG 2.x contrast ratio between two opaque colors. |
|
|
357
|
+
| `readLayers` | function | `(element: Element) => readonly Color[]` | Collects readable background color layers and refuses an unreadable painted layer. |
|
|
358
|
+
| `readBackdrop` | function | `(element: Element, floor: Color) => Color` | Resolves the opaque color standing behind one element. |
|
|
359
|
+
| `readContrast` | function | `(element: Element, floor?: Color) => number` | Measures the WCAG 2.x contrast ratio between an element's computed text and background colors. |
|
|
360
|
+
| `readRing` | function | `(control: Element, worn?: Element) => number \| undefined` | Measures the contrast the focus chrome painted on one control reaches against its own backdrop. |
|
|
361
|
+
| `readClipEdge` | function | `(element: Element) => number \| undefined` | Measures the row a clipping element cuts its content off at, in document coordinates. |
|
|
362
|
+
| `readClipMargin` | function | `(element: Element) => number` | Measures how far past its own box a clipping element lets its content show. |
|
|
363
|
+
| `clipsOverflow` | function | `(element: Element) => boolean` | Reports whether an element clips its descendants' overflow. |
|
|
364
|
+
| `measureContent` | function | `() => number` | Measures the row the document's own content ends on, in document coordinates. |
|
|
365
|
+
| `stagePane` | function | `(width: number, height: number) => Promise<void>` | Sets the tester's viewport and renders the runner's pane at the size that viewport claims. |
|
|
366
|
+
| `releasePane` | function | `() => Promise<void>` | Hands the tester pane back to the runner's own layout, at the viewport it had before staging. |
|
|
367
|
+
| `computeOffset` | function | `(box: DOMRectReadOnly, width: number, height: number) => FrameOffset` | Computes how far an element frame moves the tester frame so the element is shot inside the runner's window. |
|
|
368
|
+
| `stageMedia` | function | `(options: MediaOptions) => Promise<void>` | Stages the tester's print medium, motion preference, and forced colours through the browser provider. |
|
|
369
|
+
| `releaseMedia` | function | `() => Promise<void>` | Restores the media readings observed before the first stage as explicit emulation. With nothing staged, clears every override and waits for a stable reading, not a proved engine baseline. Checks the budget between polls, so a frame that never paints is not bounded by it. |
|
|
370
|
+
| `captureFrame` | function | `(options: FrameOptions) => Promise<string>` | Shoots one frame at one viewport size and proves the file on disk holds this run's bytes. |
|
|
371
|
+
| `readFrame` | function | `(path: string) => Promise<FrameReading>` | Reads one written frame back and reports its size and the color its bottom row paints. |
|
|
372
|
+
| `readCascade` | function | `() => ReadonlySet<string>` | Collects every class token the stylesheets loaded into this document actually define. |
|
|
373
|
+
| `readClasses` | function | `(root: ParentNode) => ReadonlySet<string>` | Collects every class token the markup under one root carries. |
|
|
374
|
+
| `readCensus` | function | `(root: ParentNode) => CensusReading` | Takes the authored-class census of one subtree against the cascade this document loaded. |
|
|
375
|
+
| `readRules` | function | `() => readonly CSSRule[]` | Collects every rule the stylesheets loaded into this document hold, nested grouping rules included. |
|
|
376
|
+
| `findRule` | function | `(selector: string) => CSSStyleRule \| undefined` | Finds the first style rule in the cascade whose selector carries a fragment. |
|
|
377
|
+
| `findKeyframes` | function | `(name: string) => CSSKeyframesRule \| undefined` | Finds the animation the cascade declares under one name. |
|
|
378
|
+
| `readRows` | function | `(root: ParentNode, selector: string) => readonly string[]` | Reads the normalized visible text of every element a selector matches, in document order. |
|
|
379
|
+
| `extractOrphans` | function | `(root: ParentNode, child: string, parent: string) => readonly string[]` | Collects every element carrying a component class rendered outside the container it belongs to. |
|
|
380
|
+
| `extractStyles` | function | `(root: ParentNode) => readonly string[]` | Collects the markup of every element carrying a non-empty `style` attribute and of every `<style>` element, in document order, `root` included in both populations when it is an `Element`. |
|
|
381
|
+
| `readStyle` | function | `(element: Element, property: string, pseudo?: string) => string` | Reads one resolved CSS property from a real browser element or a named pseudo-element. |
|
|
382
|
+
| `readToken` | function | `(element: Element, name: string) => string` | Reads one custom property from an element's resolved style. |
|
|
383
|
+
| `readRootToken` | function | `(name: string) => string` | Reads one custom property from the document element. |
|
|
384
|
+
| `readPixels` | function | `(element: Element, property: string, pseudo?: string) => number` | Reads one resolved CSS length as a number of pixels. |
|
|
385
|
+
| `expandCaptures` | function | `(states: readonly string[], variants: readonly CaptureVariant[]) => readonly string[]` | Expands a capture registry across every variant into the filenames a complete portfolio holds. |
|
|
386
|
+
| `buildDenial` | function | `(operation: string, key?: string) => DOMException` | Builds the refusal a host withholding a storage operation raises. |
|
|
387
|
+
| `buildContrast` | function | `(bar: number) => ContrastFixture` | Builds a detached translucent stack whose composited and flat contrast readings straddle one bar. |
|
|
388
|
+
| `buildEscapes` | function | `(permitted: string) => EscapeFixture` | Builds detached markup carrying one style escape of each kind, plus the sheet a project allows. |
|
|
389
|
+
| `buildCensus` | function | `() => CensusFixture` | Builds detached markup carrying one undeclared class token on HTML and another on SVG. |
|
|
361
390
|
|
|
362
391
|
#### Factories
|
|
363
392
|
|
|
@@ -412,13 +441,20 @@ rendered status completes its accessible name: the region supplies the context,
|
|
|
412
441
|
has to be recognisable inside it. The loose match reads a computed name that includes the hidden
|
|
413
442
|
subtrees, so a glyph joins the text rather than displacing it and the control is still recognisable
|
|
414
443
|
under the words beside it. That verb owns one refusal for a control it cannot reach, absent or
|
|
415
|
-
hidden, so it needs no second pass to tell the two apart.
|
|
444
|
+
hidden, so it needs no second pass to tell the two apart. `resolveAccessibleWithin` is that
|
|
445
|
+
resolution on its own, and `holdAccessibleWithin` and `traverseAccessibleWithin` drive the control it
|
|
446
|
+
resolves the way `holdAccessible` and `traverseAccessible` drive a document-wide one, so a twin of
|
|
447
|
+
the same name in another region is left alone.
|
|
416
448
|
|
|
417
449
|
`traverseAccessible` charges a step only when focus actually lands on an element, ends when focus
|
|
418
450
|
revisits one — that is a complete cycle of the tab order — and re-resolves the target by name on
|
|
419
451
|
every step, because a framework may replace the node between resolution and focus arrival. Its hard
|
|
420
452
|
cap is three times the page's candidate count plus ten, including disabled controls and elements
|
|
421
453
|
with `tabindex="-1"`, so a page whose focus never settles fails instead of hanging.
|
|
454
|
+
`driveTraversal` is that loop over any resolver, and `driveHold` is the one pointer drive every hold
|
|
455
|
+
verb shares, releasing the pointer before refusing when the frame wait or the pressed-state read
|
|
456
|
+
fails, as well as when the press misses; each verb supplies its resolver and the name its refusals
|
|
457
|
+
voice.
|
|
422
458
|
|
|
423
459
|
`build` and `mount` are the halves of a fixture, and `render` is the pair spelled as one call.
|
|
424
460
|
`build` creates the element and applies its class list, its text, and its attributes, and leaves it
|
|
@@ -451,6 +487,16 @@ another connection is still open, and a suite that swallowed it would leave the
|
|
|
451
487
|
the previous test's records through a database that reports itself deleted. The connection holding
|
|
452
488
|
it open is the caller's to close, and [Voices](#voices) carries the message each refusal spells.
|
|
453
489
|
|
|
490
|
+
The paint parser reads computed `rgb()`, `rgba()`, `oklab()`, `oklch()`, `lab()`,
|
|
491
|
+
`lch()`, and `color()` values. Its predefined spaces are `srgb`, `srgb-linear`,
|
|
492
|
+
`display-p3`, `a98-rgb`, `prophoto-rgb`, `rec2020`, `xyz`, `xyz-d50`, and
|
|
493
|
+
`xyz-d65`. Signed channels, percentage lightness, degree hues, scientific notation, and
|
|
494
|
+
`none` components resolve to straight sRGB. The exported conversion helpers apply the
|
|
495
|
+
[CSS Color 4 matrices and white-point adaptation](https://www.w3.org/TR/css-color-4/#color-conversion-code).
|
|
496
|
+
The Rec. 2020 decoder follows Chromium's piecewise transfer curve. Encoded sRGB channels are
|
|
497
|
+
clipped to 0–255 after conversion; this reader does not perform perceptual gamut mapping.
|
|
498
|
+
Alpha is clipped to 0–1.
|
|
499
|
+
|
|
454
500
|
`parseCSSColor` is the live half of the pair `parseColor` opens. `parseColor` reads text and speaks
|
|
455
501
|
only the computed syntaxes a cascade hands back; `parseCSSColor` stages a probe element, hands the
|
|
456
502
|
expression to the real cascade, and reads back what the engine computed — which is the only way a
|
|
@@ -483,6 +529,12 @@ layers blend to identical channels over black and over white alike, because the
|
|
|
483
529
|
share falls below the last bit a channel carries, so comparing two composited readings admits the
|
|
484
530
|
stack the refusal exists for.
|
|
485
531
|
|
|
532
|
+
An unreadable painted background layer throws an error naming its element and computed value.
|
|
533
|
+
Non-finite calculations such as `color(srgb calc(infinity) 0 0)` are deliberately unreadable.
|
|
534
|
+
A layer with an explicit zero alpha paints nothing and is skipped, including an unreadable one.
|
|
535
|
+
Background images remain outside this color reader. The refusal propagates through `readBackdrop`,
|
|
536
|
+
`readContrast`, and the focused-control reading in `readRing`, even with a supplied floor.
|
|
537
|
+
|
|
486
538
|
`readBackdrop` composites that stack and takes its floor as an argument rather than reaching for
|
|
487
539
|
`CANVAS_COLOR` itself, so a measurement over a surface the canvas never shows through names the
|
|
488
540
|
color it actually sits on. When no layer paints it hands that floor straight back.
|
|
@@ -493,7 +545,8 @@ the browser painted once it landed: the `outline` the cascade declares, and the
|
|
|
493
545
|
`outline-style: auto` ring, and a focus style that only changes the control's own fill all report
|
|
494
546
|
`undefined` — in each case no measurement taken here would be about focus. `worn` names the element
|
|
495
547
|
the chrome is painted onto when that is not the element holding focus, which is the hidden-input
|
|
496
|
-
control whose visible label wears every pixel of its chrome.
|
|
548
|
+
control whose visible label wears every pixel of its chrome. Modern colors, including an
|
|
549
|
+
`oklch()` box-shadow, return the ring’s contrast ratio against its backdrop.
|
|
497
550
|
|
|
498
551
|
`readRules` is the one walk over the shipped cascade, and `readCascade`, `findRule`, and
|
|
499
552
|
`findKeyframes` all read through it. It collects each sheet's own rules in sheet order and then
|
|
@@ -556,7 +609,7 @@ command and compares it with the shot itself, which is what separates this run's
|
|
|
556
609
|
earlier run left behind. It releases the pane in a `finally`, so a refusal at any stage hands the
|
|
557
610
|
tester back before it propagates.
|
|
558
611
|
|
|
559
|
-
|
|
612
|
+
A page frame covers the whole document at the width it was given, whatever height it was given. The
|
|
560
613
|
provider shoots the tester's body in the top-level page's own coordinates, so a document taller than
|
|
561
614
|
the pane paints for the pane's height and the rows under it are the runner's page: the frame reads
|
|
562
615
|
as the surface down to the fold and as bare canvas after it. `captureFrame` therefore lays the
|
|
@@ -570,9 +623,18 @@ reads back as the document's own height. A capture that staged a pane taller tha
|
|
|
570
623
|
not descend from a reading like that — the box, `body.scrollHeight`, `body.offsetHeight`, and
|
|
571
624
|
`documentElement.scrollHeight` each answer with the pane. `measureContent` walks the elements inside
|
|
572
625
|
the body instead, taking the largest bottom edge in document coordinates plus that element's own
|
|
573
|
-
bottom margin, and adds the body's and the root's bottom padding and margin under them.
|
|
574
|
-
|
|
575
|
-
|
|
626
|
+
bottom margin, and adds the body's and the root's bottom padding and margin under them. An
|
|
627
|
+
ancestor that clips its overflow (the `clipsOverflow` helper: an `overflow-y` value other than the
|
|
628
|
+
`visible` keyword, or a paint containment) caps a descendant's edge at that ancestor's clip edge,
|
|
629
|
+
which the `readClipEdge` helper reads. An `overflow-y` value of the `hidden` keyword, the `auto`
|
|
630
|
+
keyword, or the `scroll` keyword clips at the padding box, whatever the ancestor's
|
|
631
|
+
`overflow-clip-margin` value selects. The `clip` keyword and a paint containment over a `visible`
|
|
632
|
+
overflow clip at the box that value selects, the padding box by default, expanded by the length the
|
|
633
|
+
`readClipMargin` helper reads. So a viewport-height specimen
|
|
634
|
+
inside a bounded frame ends, for the reading, where the frame ends rather than stretching the
|
|
635
|
+
document with every pane. It rounds up, which is what covers a body ending part way through a row: a
|
|
636
|
+
box ending on a fraction under a half is a row the integer scroll height drops, and that row comes
|
|
637
|
+
out as the runner's page.
|
|
576
638
|
|
|
577
639
|
The edge is read again after every staging, because a rule bound to the viewport height — a `vh`
|
|
578
640
|
length, a fixed footer, a full-height panel — lays the document out taller against the taller pane,
|
|
@@ -595,13 +657,40 @@ written at a height that is already wrong. That bound is 4: a document holding h
|
|
|
595
657
|
fixed block settles in two restagings, one whose growth is capped part way settles in three, and
|
|
596
658
|
the fourth is headroom.
|
|
597
659
|
|
|
660
|
+
An element frame no taller than the declared height is shot in the declared pane, so its viewport
|
|
661
|
+
lengths resolve against the declared viewport whether the element sits above the fold, below it, or
|
|
662
|
+
fixed: a `50vh` element under a 900-row block reads back 422 rows in an 844-row pane. The re-reading
|
|
663
|
+
takes the element's own height, so the pane grows only for an element taller than the declared
|
|
664
|
+
height, and then to that element's height, which is what its viewport lengths resolve against.
|
|
665
|
+
Where the element's box lies outside the pane, the document is scrolled by the nearest distance that
|
|
666
|
+
would bring it inside, and the box is read again. A fixed element past the pane can take a scroll
|
|
667
|
+
that does not move it, and that scroll is handed back with the rest. The document is not scrolled for
|
|
668
|
+
an element already inside the pane.
|
|
669
|
+
|
|
670
|
+
The provider paints an element that fits the runner's window only where that window shows it. Where
|
|
671
|
+
such an element lies past the window, the `captureFrame` function offsets the calling tester frame up
|
|
672
|
+
or left only as far as brings the element inside, and composites the frame so a fixed element that
|
|
673
|
+
starts past the window's height is not culled. The `computeOffset` function computes that move. The
|
|
674
|
+
offset is written on that frame's own `style` attribute, outranks the placement the `stagePane`
|
|
675
|
+
function makes, and is removed as soon as the screenshot settles. An element inside the window, or
|
|
676
|
+
too large for it, is not offset.
|
|
677
|
+
|
|
678
|
+
The capture sends no pointer input. The staging lifts the tester to the window's origin, a scroll
|
|
679
|
+
brings an element outside the pane into it, and an offset brings an element past the window inside;
|
|
680
|
+
each moves content under a pointer resting on the page. A pointer the case placed on the element,
|
|
681
|
+
with the pane already staged at the frame's size, keeps its hover in the frame where the element lies
|
|
682
|
+
inside both the pane and the runner window, because the capture then moves nothing. No hover is
|
|
683
|
+
promised after the pane is released.
|
|
684
|
+
|
|
598
685
|
`readFrame` reads a written frame back: its size in device pixels, and the single color its bottom
|
|
599
686
|
row paints. The reading comes off the file through the browser's own image decoding rather than off
|
|
600
687
|
the document that produced it, which is what makes it evidence about the capture rather than a
|
|
601
688
|
second look at the style that fed it — a clipped frame reports the runner's white canvas as its
|
|
602
689
|
floor while every style in the document still resolves to the document's own background. Pass the
|
|
603
690
|
absolute path `captureFrame` returned: the runner's `readFile` command resolves a relative path
|
|
604
|
-
against its own root rather than against the calling test file.
|
|
691
|
+
against its own root rather than against the calling test file. A file that opens with a PNG header
|
|
692
|
+
and still does not decode is refused with the width and height that header declares, in device
|
|
693
|
+
pixels; a file with no PNG header is refused without a size.
|
|
605
694
|
|
|
606
695
|
`createPortfolio` refuses an unregistered variant name at creation, so a run cannot write a filename
|
|
607
696
|
naming a combination it did not render. A portfolio left un-`enabled` is the ordinary run: `place`
|
|
@@ -991,50 +1080,51 @@ what the link reaches, not on what it stores.
|
|
|
991
1080
|
Every message `src/browser` throws. Keep them distinct: a journey asserts the one it means, and
|
|
992
1081
|
absent, present-but-gated, and ambiguous are different findings about an interface.
|
|
993
1082
|
|
|
994
|
-
| Voice | Thrown by
|
|
995
|
-
| ---------------------------------------------------------------------------------------- |
|
|
996
|
-
| `No interactive element has the accessible name "<name>"` | `resolveRendered`
|
|
997
|
-
| `Interactive target "<name>" is not visible and focus-reachable` | `resolveRendered`
|
|
998
|
-
| `Interactive target "<name>" is ambiguous across <n> elements` | `resolveRendered`
|
|
999
|
-
| `Interactive target "<name>" could not be resolved` | `resolveRendered`
|
|
1000
|
-
| `Interactive target "<name>" is unreachable after scrolling` | `resolveAccessible`
|
|
1001
|
-
| `Interactive target "<name>" is not reachable inside "<region>"` | `
|
|
1002
|
-
| `Interactive target "<name>" is ambiguous across <n> elements inside "<region>"` | `
|
|
1003
|
-
| `Interactive target "<name>" could not be resolved inside "<region>"` | `
|
|
1004
|
-
| `Native disclosure "<name>" is not visible and focus-reachable` | `clickDisclosure`
|
|
1005
|
-
| `Native disclosure "<name>" is ambiguous across <n> elements` | `clickDisclosure`
|
|
1006
|
-
| `Native disclosure "<name>" could not be resolved` | `clickDisclosure`
|
|
1007
|
-
| `Interactive target "<name>" is not reachable through forward Tab traversal: <trail>` | `
|
|
1008
|
-
| `Named region "<name>" is not visible` | `readPerception`
|
|
1009
|
-
| `Named region "<name>" is ambiguous across <n> elements` | `readPerception`
|
|
1010
|
-
| `Named region "<name>" could not be resolved` | `readPerception`
|
|
1011
|
-
| `Interactive target "<name>" does not carry a value` | `readValue`
|
|
1012
|
-
| `Computed foreground color is unavailable` | `readContrast`
|
|
1013
|
-
| `Computed background color is unavailable` | `readContrast`
|
|
1014
|
-
| `Tester pane is unavailable for a capture` | `stagePane`
|
|
1015
|
-
| `Tester pane rendered <w>x<h> for a <w>x<h> viewport` | `stagePane`
|
|
1016
|
-
| `Capture frame at <path> never settled after <n> restagings: <h> over a <h> pane` | `captureFrame`
|
|
1017
|
-
| `Capture frame was written to <path> where <path> was asked for` | `captureFrame`
|
|
1018
|
-
| `Capture frame at <path> is not the one this run shot` | `captureFrame`
|
|
1019
|
-
| `Capture frame at <path> could not be read` | `readFrame`
|
|
1020
|
-
| `Capture frame at <path> is not an image this browser decodes` | `readFrame`
|
|
1021
|
-
| `Capture frame at <path>
|
|
1022
|
-
| `Capture
|
|
1023
|
-
| `Capture
|
|
1024
|
-
| `Capture state "<state>" is
|
|
1025
|
-
| `
|
|
1026
|
-
| `IndexedDB database "<name>"
|
|
1027
|
-
| `
|
|
1028
|
-
| `
|
|
1029
|
-
| `
|
|
1030
|
-
| `Animation
|
|
1031
|
-
| `
|
|
1032
|
-
| `
|
|
1033
|
-
| `
|
|
1034
|
-
| `
|
|
1035
|
-
| `
|
|
1036
|
-
| `
|
|
1037
|
-
| `Statechart harness
|
|
1083
|
+
| Voice | Thrown by |
|
|
1084
|
+
| ---------------------------------------------------------------------------------------- | ------------------------- |
|
|
1085
|
+
| `No interactive element has the accessible name "<name>"` | `resolveRendered` |
|
|
1086
|
+
| `Interactive target "<name>" is not visible and focus-reachable` | `resolveRendered` |
|
|
1087
|
+
| `Interactive target "<name>" is ambiguous across <n> elements` | `resolveRendered` |
|
|
1088
|
+
| `Interactive target "<name>" could not be resolved` | `resolveRendered` |
|
|
1089
|
+
| `Interactive target "<name>" is unreachable after scrolling` | `resolveAccessible` |
|
|
1090
|
+
| `Interactive target "<name>" is not reachable inside "<region>"` | `resolveAccessibleWithin` |
|
|
1091
|
+
| `Interactive target "<name>" is ambiguous across <n> elements inside "<region>"` | `resolveAccessibleWithin` |
|
|
1092
|
+
| `Interactive target "<name>" could not be resolved inside "<region>"` | `resolveAccessibleWithin` |
|
|
1093
|
+
| `Native disclosure "<name>" is not visible and focus-reachable` | `clickDisclosure` |
|
|
1094
|
+
| `Native disclosure "<name>" is ambiguous across <n> elements` | `clickDisclosure` |
|
|
1095
|
+
| `Native disclosure "<name>" could not be resolved` | `clickDisclosure` |
|
|
1096
|
+
| `Interactive target "<name>" is not reachable through forward Tab traversal: <trail>` | `driveTraversal` |
|
|
1097
|
+
| `Named region "<name>" is not visible` | `readPerception` |
|
|
1098
|
+
| `Named region "<name>" is ambiguous across <n> elements` | `readPerception` |
|
|
1099
|
+
| `Named region "<name>" could not be resolved` | `readPerception` |
|
|
1100
|
+
| `Interactive target "<name>" does not carry a value` | `readValue` |
|
|
1101
|
+
| `Computed foreground color is unavailable` | `readContrast` |
|
|
1102
|
+
| `Computed background color is unavailable` | `readContrast` |
|
|
1103
|
+
| `Tester pane is unavailable for a capture` | `stagePane` |
|
|
1104
|
+
| `Tester pane rendered <w>x<h> for a <w>x<h> viewport` | `stagePane` |
|
|
1105
|
+
| `Capture frame at <path> never settled after <n> restagings: <h> over a <h> pane` | `captureFrame` |
|
|
1106
|
+
| `Capture frame was written to <path> where <path> was asked for` | `captureFrame` |
|
|
1107
|
+
| `Capture frame at <path> is not the one this run shot` | `captureFrame` |
|
|
1108
|
+
| `Capture frame at <path> could not be read` | `readFrame` |
|
|
1109
|
+
| `Capture frame at <path> is not an image this browser decodes` | `readFrame` |
|
|
1110
|
+
| `Capture frame at <path> is not an image this browser decodes: <w>x<h> device pixels` | `readFrame` |
|
|
1111
|
+
| `Capture frame at <path> cannot be measured without a 2D canvas` | `readFrame` |
|
|
1112
|
+
| `Capture variant "<name>" is not registered` | `createPortfolio` |
|
|
1113
|
+
| `Capture state "<state>" is not registered` | `place` |
|
|
1114
|
+
| `Capture state "<state>" is already placed` | `place` |
|
|
1115
|
+
| `IndexedDB database "<name>" could not be deleted` | `removeDatabase` |
|
|
1116
|
+
| `IndexedDB database "<name>" is blocked by an open connection` | `removeDatabase` |
|
|
1117
|
+
| `Key sequence "<keys>" was sent with nothing focused` | `pressKeys` |
|
|
1118
|
+
| `Condition "<subject>" did not hold within <n>ms (waited <n>ms) (last states: <states>)` | `waitForState` |
|
|
1119
|
+
| `Animation subject is not connected` | `waitForAnimations` |
|
|
1120
|
+
| `Animation "<subject>" did not settle within <n>ms (waited <n>ms): <animations>` | `waitForAnimations` |
|
|
1121
|
+
| `Class census walked no element` | `readCensus` |
|
|
1122
|
+
| `Contrast control cannot straddle the bar <bar>` | `buildContrast` |
|
|
1123
|
+
| `Access is denied for <operation> "<key>"` | `buildDenial` |
|
|
1124
|
+
| `No room is left for <key>` | `createStorage` |
|
|
1125
|
+
| `Storage quota must be a non-negative integer` | `createStorage` |
|
|
1126
|
+
| `Statechart harness mounted no transition` | `createHarness` |
|
|
1127
|
+
| `Statechart harness carries no status` | `status` |
|
|
1038
1128
|
|
|
1039
1129
|
Some of those rows are not plain `Error` messages. `buildDenial` returns a `DOMException` named
|
|
1040
1130
|
`SecurityError`, `createStorage` raises that one from every operation the permission withholds and a
|
|
@@ -1066,9 +1156,27 @@ disk disagrees with the bytes the provider handed back — which a provider that
|
|
|
1066
1156
|
never produces, so the suite proves that comparison discriminates with a planted file rather than by
|
|
1067
1157
|
reaching the refusal. `Capture frame at <path> cannot be measured without a 2D canvas` is narrowing
|
|
1068
1158
|
of the same kind: a canvas allocated for this reading and asked for no other context type hands one
|
|
1069
|
-
back. `readFrame`
|
|
1070
|
-
no image, and
|
|
1071
|
-
|
|
1159
|
+
back. The remaining refusals of the `readFrame` function are driven, by a path holding no file, by a
|
|
1160
|
+
file holding no image, and by a PNG header over no image data, and so is
|
|
1161
|
+
`Capture frame at <path> never settled after <n> restagings`, by a fixture whose full-height panel
|
|
1162
|
+
grows with every pane the capture stages.
|
|
1163
|
+
|
|
1164
|
+
The pointer, pseudo-element, and media helpers add the following voices.
|
|
1165
|
+
|
|
1166
|
+
| Voice | Thrown by |
|
|
1167
|
+
| ------------------------------------------------------------- | -------------- |
|
|
1168
|
+
| `Browser provider exposes no DevTools session` | `sendProtocol` |
|
|
1169
|
+
| `Pointer is already held at <x>x<y>` | `driveHold` |
|
|
1170
|
+
| `Interactive target "<name>" did not enter the pressed state` | `driveHold` |
|
|
1171
|
+
| `Pseudo-element "<pseudo>" must start with "::"` | `readStyle` |
|
|
1172
|
+
| `Pseudo-element "<pseudo>" is not one this engine exposes` | `readStyle` |
|
|
1173
|
+
| `Media emulation was staged with nothing to emulate` | `stageMedia` |
|
|
1174
|
+
| `Media emulation did not reach the tester: <query>` | `stageMedia` |
|
|
1175
|
+
| `Media emulation did not clear from the tester` | `releaseMedia` |
|
|
1176
|
+
|
|
1177
|
+
The resolver's absent, gated, ambiguous, and unreachable voices stay unchanged. The DevTools-session
|
|
1178
|
+
and media-delivery refusals guard provider failures the managed Chromium suite doesn't induce.
|
|
1179
|
+
The suite exercises real delivery rather than substituting a provider that manufactures a failure.
|
|
1072
1180
|
|
|
1073
1181
|
### Refusals outside the journey layer
|
|
1074
1182
|
|
|
@@ -1356,13 +1464,22 @@ These hold across `src/core`, `src/browser`, `src/server`, and this guide.
|
|
|
1356
1464
|
element, the `hidden` attribute, a hidden input, and a `display` or `visibility` that takes it
|
|
1357
1465
|
off the page. `isReachable` reads geometry, and adds connectedness, a visibility check that
|
|
1358
1466
|
honours opacity, a non-zero box, the sequential focus order, `:disabled` and `aria-disabled`, and
|
|
1359
|
-
the `[inert]` ancestor.
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1467
|
+
the `[inert]` ancestor. It adds one reading the element's own facts cannot carry: an open modal
|
|
1468
|
+
dialog. A shown `[aria-modal="true"]` element that does not contain the subject refuses it,
|
|
1469
|
+
because a pointer, a Tab, and a reader honouring that attribute all stop at the dialog while the
|
|
1470
|
+
covered control stays connected, laid out, and focusable. The dialog is put through `isRendered`,
|
|
1471
|
+
so a drawer parked at `visibility: hidden` excludes nothing, and containment follows the flat
|
|
1472
|
+
tree, so the innermost dialog rules and a host it holds carries its shadow content with it.
|
|
1473
|
+
Applying that inside the predicate is what keeps the resolver, the ambiguity count, the Tab
|
|
1474
|
+
trail, and every acting verb agreeing with the person in front of the dialog, and it is what
|
|
1475
|
+
takes away the name splitting a consumer writes to keep a covered control out of the count. A
|
|
1476
|
+
control clipped to a zero-size rectangle is the case that separates them: the accessibility tree
|
|
1477
|
+
still announces it, so `isRendered` accepts it and `isReachable` refuses it. `isReachable` is the
|
|
1478
|
+
one reachability filter the layer applies — `resolveRendered`, `clickAccessibleWithin`, and
|
|
1479
|
+
`clickDisclosure` each narrow their own candidates and then keep the ones it accepts — so a
|
|
1480
|
+
journey meets one rule rather than near-copies of it. Neither asks about the viewport;
|
|
1481
|
+
`resolveAccessible` scrolls a wholly off-viewport target into view and measures that separately
|
|
1482
|
+
with `isOutsideViewport`.
|
|
1366
1483
|
`readHit` reads beside that pair rather than filtering with it. It hit-tests one point — the
|
|
1367
1484
|
element's own bounding-box centre — which is how it sees what neither predicate can: a cover
|
|
1368
1485
|
over a control they both accept, and a wrapped inline target whose centre falls between its line
|
|
@@ -1392,19 +1509,25 @@ These hold across `src/core`, `src/browser`, `src/server`, and this guide.
|
|
|
1392
1509
|
`Tester pane rendered <w>x<h> for a <w>x<h> viewport` rather than writing a wrong frame. The
|
|
1393
1510
|
coupling therefore fails loudly, and the version this rule names moves with the fix instead of a
|
|
1394
1511
|
suite shipping thumbnails nobody inspects. The same layout decides what a frame covers: the
|
|
1395
|
-
provider shoots the tester's body in the top-level page's coordinates, so `captureFrame`
|
|
1396
|
-
the pane again at the document's own height wherever the document outruns the
|
|
1397
|
-
a frame is neither shorter nor taller than the document it photographs.
|
|
1512
|
+
provider shoots the tester's body in the top-level page's coordinates, so the `captureFrame`
|
|
1513
|
+
function stages the pane again at the document's own height wherever the document outruns the
|
|
1514
|
+
declared one, and a page frame is neither shorter nor taller than the document it photographs.
|
|
1515
|
+
That height is
|
|
1398
1516
|
`measureContent`, the content's own edge rounded up, rather than the body's box: the box is the
|
|
1399
1517
|
larger of the content and the pane, so a taller pane stretches it and a capture cannot read its
|
|
1400
1518
|
way back down. The edge is read again after every staging, because a rule bound to the viewport
|
|
1401
1519
|
height lays the document out taller against the taller pane, and each staging carries the growth
|
|
1402
1520
|
the one before it produced so a converging document lands on its fixed point rather than
|
|
1403
1521
|
creeping toward it. The re-reading is bounded by `CAPTURE_STAGINGS`, and a document still
|
|
1404
|
-
growing at that bound is refused rather than photographed at a stale height.
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
`
|
|
1522
|
+
growing at that bound is refused rather than photographed at a stale height. An element frame
|
|
1523
|
+
keeps the declared pane unless its element is taller, and offsets the calling tester frame only
|
|
1524
|
+
as far as brings the element inside the runner's window. What
|
|
1525
|
+
the capture borrows it gives back: the `releasePane` function returns the tester to the viewport
|
|
1526
|
+
it held before the staging, the capture then restores the tester's scroll position, even where
|
|
1527
|
+
the release rejects, and it restores the offset frame's
|
|
1528
|
+
`style` attribute as soon as the screenshot settles. So the variant a frame was shot at belongs
|
|
1529
|
+
to that frame alone, and a suite that wants a size of its own calls the `page.viewport` method
|
|
1530
|
+
rather than this pair.
|
|
1408
1531
|
19. **The statechart harness is test-side, and the markup is its whole contract.** A page cannot
|
|
1409
1532
|
import this package. `@orkestrel/test` is a development dependency, its browser entry imports
|
|
1410
1533
|
`vitest/browser` at module scope, and that import throws outside Browser Mode — so an
|
|
@@ -1480,7 +1603,7 @@ packages wrote is still refused when it is one suite's policy, a redeclaration o
|
|
|
1480
1603
|
`@orkestrel` package already publishes, or a race; a shape few packages wrote still ships when the
|
|
1481
1604
|
mechanism is a contract every consumer has to implement identically.
|
|
1482
1605
|
|
|
1483
|
-
The journey layer in `src/browser` is that second case. It is what `orkestrel-journey`
|
|
1606
|
+
The journey layer in `src/browser` is that second case. It is what `orkestrel-prove-journey`
|
|
1484
1607
|
requires every browser workspace to implement, and a workspace writing its own copy of it writes a
|
|
1485
1608
|
slightly different resolver, a slightly different set of failure voices, and a journey that reads as
|
|
1486
1609
|
if it proved something it did not. Publishing it once is what keeps those implementations identical.
|
|
@@ -1536,6 +1659,18 @@ or when a consumer appears the ruling did not consider.
|
|
|
1536
1659
|
| A generated or published harness page | Refused | Which transitions a surface owes, where that page is deep-linked, and whether it ships to anyone are product decisions, and framework code stops before them. A page hosting a harness would also have to import this package, which rule 19 rules out: the browser entry imports `vitest/browser` at module scope. The mechanism ships and the page does not. |
|
|
1537
1660
|
| A framework-class disclosure settle | Refused | A helper that waits for a named element to carry one class and not two others encodes one framework's transition vocabulary, which is that framework's policy rather than a mechanism. `waitForState` waits on what the control announces and `waitForAnimations` waits on the paint itself, and between them they answer the question the class poll was asked. A surface announcing nothing is the finding. |
|
|
1538
1661
|
|
|
1662
|
+
The journey additions have these candidate rulings.
|
|
1663
|
+
|
|
1664
|
+
| Candidate | Ruling | Why |
|
|
1665
|
+
| ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1666
|
+
| A hover verb | Ships | `hoverAccessible` adds exact role/name resolution and the journey's reachability boundary to the provider's hover primitive. |
|
|
1667
|
+
| A pointer hold | Ships | `holdAccessible` composes resolution, tester-scale mapping, a trusted press, and a pressed-state read-back through `driveHold`; `holdAccessibleWithin` resolves inside a named region and drives the same hold. `releasePointer` releases the hold and clears hover. Veneer's Button consumes the held paint. |
|
|
1668
|
+
| A pseudo-element style read | Ships | The third argument of `readStyle` and `readPixels` selects another subject of the same CSSOM reading. A separate reader would duplicate that operation. |
|
|
1669
|
+
| A medium emulation | Ships | `stageMedia` bounds the axes through `MediaOptions`, reads back `print: true`, either `motion` value, and either `forced` value, and pairs with `releaseMedia` for teardown. A `print: false` stage is sent and followed by a frame wait without a read-back. |
|
|
1670
|
+
| A DevTools command door | Ships | `sendProtocol` centralizes the unchecked provider boundary shared by pointer and media operations. The scale control consumes it directly. |
|
|
1671
|
+
| A general media-feature map | Refused | The consumer needs print, motion, and forced colours. A free-form map adds an unbounded contract without a consumer. |
|
|
1672
|
+
| A scoped hold taking a callback | Refused | The layer already uses stage-and-release pairs. An `afterEach` hook owns release after a failed test without a second lifecycle form. |
|
|
1673
|
+
|
|
1539
1674
|
`ScratchInterface`'s own members were ruled the same way, and coherence rather than demand decided
|
|
1540
1675
|
them. `ensure` ships because it is the one member that produces an empty directory — `write` always
|
|
1541
1676
|
creates a file. `names` and `link` ship because a fixture that seeds a tree has to list it and to
|
|
@@ -1558,6 +1693,50 @@ peer, protocol fixture, and domain builder stays in the package that owns it.
|
|
|
1558
1693
|
A shipped helper can still decline the question it looks like it answers. Each bound here belongs to
|
|
1559
1694
|
the helper rather than to the host, and each names what to reach for instead.
|
|
1560
1695
|
|
|
1696
|
+
- **`holdAccessible` maps through the tester iframe's painted scale and reads `:active` back.** It
|
|
1697
|
+
presses the control's centre and releases before refusing a missed press. If that release also
|
|
1698
|
+
rejects, the missed-press refusal carries it as its cause. That path can't be driven from inert
|
|
1699
|
+
input against a conforming engine, because the marker is built from the coordinates the press
|
|
1700
|
+
used; it is covered by review. The measured layout is a single accessible, uniformly scaled tester
|
|
1701
|
+
iframe. A covered centre or unsupported geometry can refuse even when the resolver accepts the
|
|
1702
|
+
target. The verb inherits the resolver's focus reachability conditions.
|
|
1703
|
+
- **`releasePointer` parks the pointer outside the page.** It releases at the recorded point first,
|
|
1704
|
+
which can produce a click. It then clears hover by moving to (-1, -1) in the runner page's
|
|
1705
|
+
coordinates, one pixel above and to the left of that page's viewport. The browser hit-tests
|
|
1706
|
+
nothing outside the viewport, so no element takes a `mouseover` event or hover paint from the
|
|
1707
|
+
parked pointer until the next pointer verb. This holds even where a staging, scroll, or offset
|
|
1708
|
+
lays content over the park point. Register it with `afterEach` before a hover
|
|
1709
|
+
or hold. A second release sends no button-up event. Holds must not overlap. A rejected press
|
|
1710
|
+
leaves no marker. A rejected release keeps the marker for a retry and still attempts the park. If
|
|
1711
|
+
that park also rejects, the park's error surfaces with the release rejection attached in the
|
|
1712
|
+
aggregate's errors; the park rejection is its cause.
|
|
1713
|
+
- **`stageMedia` overrides the provider page, and `releaseMedia` restores the first stage's
|
|
1714
|
+
readings.** Pin a base with `{ motion: true }` rather than assuming the host prefers motion.
|
|
1715
|
+
`motion: false` stages reduced motion; `print: true` stages print, and `print: false` stages
|
|
1716
|
+
screen; `forced: true` stages active forced colours, and `forced: false` stages none. An omitted
|
|
1717
|
+
print, motion, or forced axis keeps its effective reading, as does `prefers-color-scheme`. Any
|
|
1718
|
+
other emulated feature the provider configured is cleared. An empty `{}`
|
|
1719
|
+
refuses. Each staged query has a 1000 ms read-back budget with a 10 ms poll interval; refusal
|
|
1720
|
+
restores the carried pre-call readings before throwing and can take two budgets. A restoration
|
|
1721
|
+
failure is attached as the refusal's cause. The read-back exhaustion path can't be driven from
|
|
1722
|
+
inert input against a conforming engine; its restoration is covered by review and by the shared
|
|
1723
|
+
payload code, not by a case. The first stage records print, reduced motion, dark colour scheme,
|
|
1724
|
+
and forced colours on the tester root under `MEDIA_STAGE`, as a bit string in that order (`1` for
|
|
1725
|
+
a match, `0` otherwise). Later stages keep that marker. Release restores those readings as
|
|
1726
|
+
explicit emulation rather than removing every override, waits per axis for its recorded value, and
|
|
1727
|
+
removes the marker. A release whose wait exhausts keeps `MEDIA_STAGE` on the tester root, so the
|
|
1728
|
+
next `releaseMedia` retries from the same recorded readings. A release with nothing staged clears
|
|
1729
|
+
every override and compares readings taken strictly after the reset until they are stable.
|
|
1730
|
+
Stability doesn't prove the engine's own baseline. Each release read-back has a 1000 ms budget and
|
|
1731
|
+
waits for a frame per poll, with 10 ms between polls. The budget is checked between polls, so a
|
|
1732
|
+
frame that never paints is not bounded by it. Media scopes must not overlap. The browser project
|
|
1733
|
+
runs files serially.
|
|
1734
|
+
- **`readStyle` refuses a pseudo argument rather than ignoring it.** The argument must start with
|
|
1735
|
+
`::` before the engine's selector-support check runs. A supported pseudo-element with no value for
|
|
1736
|
+
the requested property still reads as an empty string. `readPixels` uses the same guard.
|
|
1737
|
+
- **`sendProtocol` reaches a Chromium-family provider alone.** It needs the provider's DevTools
|
|
1738
|
+
session, passes the named command and parameters through, and discards the response. Browser
|
|
1739
|
+
termination cannot promise cleanup. Edge observations belong to a separate run.
|
|
1561
1740
|
- **`parseCSSColor` resolves an undeclared token to the inherited color.** A `var()` naming a custom
|
|
1562
1741
|
property nothing declares is not a parse failure: the cascade accepts it and computes the
|
|
1563
1742
|
inherited color, so `parseCSSColor('var(--absent)')` hands back channels rather than `undefined`.
|
|
@@ -1597,6 +1776,13 @@ the helper rather than to the host, and each names what to reach for instead.
|
|
|
1597
1776
|
subject: a host the document does not lay out takes the element off the page, and both predicates
|
|
1598
1777
|
refuse it. Read a `true` for a shadow subject as the element's own answer, and ask the host
|
|
1599
1778
|
separately where an ancestor attribute is the subject.
|
|
1779
|
+
- **`isReachable` finds an open modal through the `aria-modal` attribute in the element's own
|
|
1780
|
+
document.** Two arrangements carry no such attribute there, and each leaves the page behind it
|
|
1781
|
+
reachable: a native `<dialog>` opened with `showModal`, which a browser makes modal without
|
|
1782
|
+
marking it, and a dialog declared inside a shadow tree, which a document query does not return.
|
|
1783
|
+
Containment itself does cross a boundary, because the subject's host chain is judged beside the
|
|
1784
|
+
subject, so a dialog holding a host holds that host's shadow content too. Read `:modal` or the
|
|
1785
|
+
dialog's own root where a native or shadow-declared dialog is the subject.
|
|
1600
1786
|
- **`waitForAnimations` waits on the animations a browser reports as running.** A finished animation
|
|
1601
1787
|
filling its target stays in the list and is already at rest, a paused one is at rest too and
|
|
1602
1788
|
nothing here resumes it, and an animation declaring infinite iterations never finishes. Each is
|
|
@@ -2715,6 +2901,81 @@ await traverseAccessible('Evaluate')
|
|
|
2715
2901
|
readPerception('Run') // one visible named region, whitespace collapsed, hidden-but-read text kept
|
|
2716
2902
|
```
|
|
2717
2903
|
|
|
2904
|
+
### Hold a control and read the pressed paint
|
|
2905
|
+
|
|
2906
|
+
Register pointer cleanup before a test can fail. This fixture declares `padding-top: 16px` on its
|
|
2907
|
+
`Apply` button and `32px` under `:active`. Read the paint while the button is held, then release it.
|
|
2908
|
+
|
|
2909
|
+
```ts
|
|
2910
|
+
import {
|
|
2911
|
+
holdAccessible,
|
|
2912
|
+
readPixels,
|
|
2913
|
+
releasePointer,
|
|
2914
|
+
resolveAccessible,
|
|
2915
|
+
} from '@orkestrel/test/browser'
|
|
2916
|
+
import { afterEach } from 'vitest'
|
|
2917
|
+
|
|
2918
|
+
afterEach(releasePointer)
|
|
2919
|
+
const button = resolveAccessible('button', 'Apply')
|
|
2920
|
+
readPixels(button, 'padding-top') // 16
|
|
2921
|
+
await holdAccessible('button', 'Apply')
|
|
2922
|
+
readPixels(button, 'padding-top') // 32
|
|
2923
|
+
await releasePointer()
|
|
2924
|
+
readPixels(button, 'padding-top') // 16
|
|
2925
|
+
```
|
|
2926
|
+
|
|
2927
|
+
Where a twin elsewhere on the page carries the same name, hold the one inside a named region with
|
|
2928
|
+
`holdAccessibleWithin('Ledger', 'button', 'Apply')`, which composes `resolveAccessibleWithin` with
|
|
2929
|
+
`driveHold`; `traverseAccessibleWithin` composes the same resolver with `driveTraversal`, and each
|
|
2930
|
+
refuses before it sends any input.
|
|
2931
|
+
|
|
2932
|
+
### Read a pseudo-element's paint
|
|
2933
|
+
|
|
2934
|
+
This fixture declares `padding-top: 0` on its `Marked` button and `7px` on its generated `::after`
|
|
2935
|
+
pseudo-element. Keep the element reading beside the pseudo reading so dropping the argument fails.
|
|
2936
|
+
|
|
2937
|
+
```ts
|
|
2938
|
+
import { readPixels, readStyle, resolveAccessible } from '@orkestrel/test/browser'
|
|
2939
|
+
|
|
2940
|
+
const button = resolveAccessible('Marked')
|
|
2941
|
+
readStyle(button, 'padding-top', '::after') // '7px'
|
|
2942
|
+
readPixels(button, 'padding-top', '::after') // 7
|
|
2943
|
+
readStyle(button, 'padding-top') // '0px'
|
|
2944
|
+
```
|
|
2945
|
+
|
|
2946
|
+
### Emulate reduced motion and print
|
|
2947
|
+
|
|
2948
|
+
This fixture's `Media` button declares `padding-top: 1px`, `2px` under reduced motion, and `3px`
|
|
2949
|
+
under print. Pin the base preference, then read each staged axis. Stage the inverse of the observed
|
|
2950
|
+
motion reading before release so the restore comparison can fail. The initial unstaged release
|
|
2951
|
+
clears overrides and waits for stable readings. The final release returns the readings observed
|
|
2952
|
+
before the first stage, kept as explicit emulation. A further unstaged release clears that emulation.
|
|
2953
|
+
|
|
2954
|
+
```ts
|
|
2955
|
+
import { readPixels, releaseMedia, resolveAccessible, stageMedia } from '@orkestrel/test/browser'
|
|
2956
|
+
import { afterEach } from 'vitest'
|
|
2957
|
+
|
|
2958
|
+
afterEach(releaseMedia)
|
|
2959
|
+
await releaseMedia()
|
|
2960
|
+
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
2961
|
+
const button = resolveAccessible('Media')
|
|
2962
|
+
await stageMedia({ motion: true })
|
|
2963
|
+
readPixels(button, 'padding-top') // 1
|
|
2964
|
+
await stageMedia({ motion: false })
|
|
2965
|
+
readPixels(button, 'padding-top') // 2
|
|
2966
|
+
matchMedia('(prefers-reduced-motion: reduce)').matches // true
|
|
2967
|
+
await stageMedia({ motion: reduced })
|
|
2968
|
+
matchMedia('(prefers-reduced-motion: reduce)').matches === !reduced // true
|
|
2969
|
+
readPixels(button, 'padding-top') === (reduced ? 1 : 2) // true
|
|
2970
|
+
await stageMedia({ print: true })
|
|
2971
|
+
readPixels(button, 'padding-top') // 3
|
|
2972
|
+
matchMedia('print').matches // true
|
|
2973
|
+
await releaseMedia()
|
|
2974
|
+
matchMedia('print').matches // false
|
|
2975
|
+
matchMedia('(prefers-reduced-motion: reduce)').matches === reduced // true
|
|
2976
|
+
readPixels(button, 'padding-top') === (reduced ? 2 : 1) // true
|
|
2977
|
+
```
|
|
2978
|
+
|
|
2718
2979
|
### Send a key to what holds focus
|
|
2719
2980
|
|
|
2720
2981
|
Bring focus about through a verb, then send the sequence. A key sent while the document body holds
|
|
@@ -3210,7 +3471,13 @@ The second pair is what the reading exists for. Every box a document exposes —
|
|
|
3210
3471
|
content and the pane, so a caller that has staged too tall a pane reads that pane back and cannot
|
|
3211
3472
|
descend from it. `measureContent` walks the elements inside the body instead, so it descends. Where
|
|
3212
3473
|
the document is laid out against the viewport, it moves with the viewport and reports what the
|
|
3213
|
-
reflow produced rather than what the pane claimed
|
|
3474
|
+
reflow produced rather than what the pane claimed, except inside a frame that clips its overflow,
|
|
3475
|
+
where a viewport-bound child ends at the frame's clip edge. The `clipsOverflow` helper names the
|
|
3476
|
+
frames that count, and the `readClipEdge` helper reads each frame's clip edge. An `overflow-y`
|
|
3477
|
+
value of the `hidden` keyword, the `auto` keyword, or the `scroll` keyword ends at the padding box.
|
|
3478
|
+
The `clip` keyword and a paint containment over a
|
|
3479
|
+
`visible` overflow end at the box the frame's `overflow-clip-margin` value selects, the padding box
|
|
3480
|
+
by default, expanded by the margin the `readClipMargin` helper reads.
|
|
3214
3481
|
|
|
3215
3482
|
### Read a written frame back
|
|
3216
3483
|
|
|
@@ -3332,11 +3599,16 @@ Each entry names the contracts its file proves. The test names carry the cases.
|
|
|
3332
3599
|
that stays there, and `isOutsideViewport` takes a rectangle wholly beyond each edge and one
|
|
3333
3600
|
straddling an edge. `isReachable` takes a plain control and each condition it drops, a control the
|
|
3334
3601
|
document no longer holds, a focusable SVG against an element from a foreign namespace, and the
|
|
3335
|
-
refused summary that proves it is the one filter the acting verbs apply
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3602
|
+
refused summary that proves it is the one filter the acting verbs apply. It takes the open modal
|
|
3603
|
+
dialog through the readings that fix it: the masthead control the dialog leaves behind against the
|
|
3604
|
+
same name inside it, which resolves and traverses unambiguously; the control a plain dialog, a
|
|
3605
|
+
folded modal, and a blanked modal each leave standing, as the control; the nested dialog and the
|
|
3606
|
+
shadow subject that pin containment on the flat tree; and the native `showModal` dialog and the
|
|
3607
|
+
shadow-declared modal it reports nothing about, which is the bound the guide states. `isRendered`
|
|
3608
|
+
takes each removal a browser honours and, as the split from `isReachable`, a zero-size announced
|
|
3609
|
+
control. Each predicate also takes a subject inside an open and a closed shadow root beside a host
|
|
3610
|
+
that carries its own ancestor attribute — `[inert]` for one and `aria-hidden` for the other — and a
|
|
3611
|
+
host the flat tree does not lay out, which pins where the boundary falls for each.
|
|
3340
3612
|
`pressKeys` takes a sequence reaching the control a traversal focused and, as the control, the same
|
|
3341
3613
|
sequence refused while the document body holds focus with no keystroke recorded. `waitForState`
|
|
3342
3614
|
takes a state a timer flips after the act, a node replaced mid-wait and still resolved by role and
|
|
@@ -3434,10 +3706,32 @@ Each entry names the contracts its file proves. The test names carry the cases.
|
|
|
3434
3706
|
read — 1322, then 1561 — and its frame lands on 1800, which is the fixed point written out rather
|
|
3435
3707
|
than read back from the capture that staged it. The same panel uncapped grows with every pane and
|
|
3436
3708
|
reaches the refusal, whose written-out restaging bound reddens when the source's bound moves and
|
|
3437
|
-
whose pane and viewport are handed back anyway.
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3709
|
+
whose pane and viewport are handed back anyway. An element frame takes a fixed `30vh` panel whose
|
|
3710
|
+
top lies past the runner's window, shot from a scrolled tester, whose frame is 30% of the declared
|
|
3711
|
+
height on the panel's own color with the scroll handed back; an element below both the pane and
|
|
3712
|
+
the window, whole on the document's floor with the scroll handed back; a `50vh` element and a
|
|
3713
|
+
`30vh` element below the fold, at the declared pane rather than a grown one; an element taller
|
|
3714
|
+
than the pane, in a pane of its own height; an element that outgrows every pane, refused with the
|
|
3715
|
+
pane and the scroll handed back; a second tester frame the offset leaves in place; and a frame
|
|
3716
|
+
carrying no `style` attribute, handed back without one. After the `releasePointer` function, an
|
|
3717
|
+
element frame takes no `mouseover` event for an element inside the window, which also takes no `scroll` event;
|
|
3718
|
+
for a flush-left element the scroll brings to the top, one the offset brings to the left edge, and
|
|
3719
|
+
one too large for the window; for an element past both window edges, whose frame has the
|
|
3720
|
+
element's size; for an element touching the origin and one filling the window; across a scroll
|
|
3721
|
+
the staging clamps, with the scroll handed back; and for an `svg` element in the shadow tree of a
|
|
3722
|
+
fixed host and a fixed `svg` element under a containing-block ancestor. A hover placed after
|
|
3723
|
+
staging on an element at the tester's top-left corner stays in the frame. The `computeOffset`
|
|
3724
|
+
function takes an element inside the window, one touching the origin or starting above it, one
|
|
3725
|
+
filling the window, an element past the bottom edge, a fractional bottom edge rounded up, a
|
|
3726
|
+
fractional top and a fractional left each moved only as far as the window start, a box already
|
|
3727
|
+
ending inside a fractional window left where it is, an element past the right edge,
|
|
3728
|
+
one past both edges, and an element too large for the window.
|
|
3729
|
+
The `readFrame` function takes a written
|
|
3730
|
+
frame's size and floor, read a second way through the cascade's own answer for the same canvas, a
|
|
3731
|
+
bottom row split between two colors reported as no floor at all, a path holding no file,
|
|
3732
|
+
a file holding no image and refused without a size, and a PNG header over no image data, refused
|
|
3733
|
+
with the size the header declares. The `readCascade` function takes class tokens collected from
|
|
3734
|
+
plain and grouped rules and only real ones;
|
|
3441
3735
|
`readRows` takes a row joined from its own text nodes rather than from run-together content, and
|
|
3442
3736
|
an empty list; `extractOrphans` takes a child class rendered outside its container with a nested
|
|
3443
3737
|
one left alone, nothing reported when every child sits inside one, and, as the control, an element
|