- name
- bench-test-suite
- description
Bench Test Suite
When to Use
- Adding a new benchmark view/test suite (like
datatableorpokeboxes) - Extending the Playwright runner with new view operations
- Making UI test results show up in the snapshot UI
Workflow
- Define the view id and ready selector
- Pick a lowercase view id (used in ?view=). - Decide the first stable selector that signals the view is ready.
- Add the blueprint markup
- Create bench/frameworks/blueprint-<view>.html. - The initial DOM must match every framework’s initial render (preflight compares against it). - Reuse the view toggle markup and IDs used by existing blueprints.
- Update the benchmark runner
- Add the view id to OperationView, AVAILABLE_VIEWS, and BLUEPRINTS. - Add a resolveViewReadySelector branch for the new view. - Add new Operation entries that set view: '<view>' and readySelector.
- Add shared operations/data helpers
- Add new helpers in bench/frameworks/shared.js. - Add matching types and exports in bench/frameworks/shared.d.ts.
- Update framework implementations
- Each framework must render the same initial markup as the blueprint for the new view. - Parse ?view= and render the correct view. - Add view toggle buttons and ensure they link to the correct view query param. - Use design system classes from bench/src/design-system.css (max 3 classes per element).
- Expose the suite in results UI
- Add human-friendly labels for new operations in bench/snapshot/index.ts. - Add the new view to suiteOrder and suiteTitles.
- Run UI tests and generate results
- Run the runner with preflight to validate markup and measure timings. - Open the snapshot UI to verify the new suite is visible.
Templates
bench/runner.ts additions
// 1) View type + constants
type OperationView = 'datatable' | 'pokeboxes' | '<view>'
const BLUEPRINTS = {
datatable: path.join(import.meta.dirname, 'frameworks/blueprint-datatable.html'),
pokeboxes: path.join(import.meta.dirname, 'frameworks/blueprint-pokeboxes.html'),
<view>: path.join(import.meta.dirname, 'frameworks/blueprint-<view>.html'),
}
const AVAILABLE_VIEWS: OperationView[] = ['datatable', 'pokeboxes', '<view>']
function resolveViewReadySelector(view: OperationView): string {
if (view === 'pokeboxes') return '#append40'
if (view === '<view>') return '#<ready-id>'
return '#run'
}
// 2) New operations
const operations: Operation[] = [
// ...
{
name: '<operationName>',
view: '<view>',
readySelector: '#<ready-id>',
setup: (page) => /* optional */,
action: (page) => clickAndMeasure(page, '#<action-id>', '<operationName>'),
teardown: (page) => /* optional */,
},
]bench/frameworks/blueprint-<view>.html
<!-- Match initial DOM for *all* frameworks -->
<div class="bench-view-toggle" id="view-toggle">
<!-- include one button per view (datatable/pokeboxes/<view>) -->
</div>
<!-- New view DOM should be here with stable IDs -->bench/frameworks/shared.js + bench/frameworks/shared.d.ts
// shared.js (helpers used by all frameworks)
export function build<Thing>(/* params */) {
// deterministic data helpers for the new view
}
export function <operationHelper>(/* params */) {
// pure data operation (no DOM)
}// shared.d.ts (types + function declarations)
export type <Thing> = {
// ...
}
export declare function build<Thing>(/* params */): <Thing>[]
export declare function <operationHelper>(/* params */): <Thing>[]Framework view routing (each bench/frameworks/*/index.ts[x]|index.js)
const resolveView = () => {
const params = new URLSearchParams(window.location.search)
return params.get('view') === '<view>' ? '<view>' : 'datatable'
}
const navigateToView = (view: string) => {
const url = new URL(window.location.href)
url.searchParams.set('view', view)
window.location.href = url.toString()
}
// Render toggle buttons + new viewbench/snapshot/index.ts updates
const OPERATION_LABELS = {
// ...
<operationName>: {
title: '<human title>',
description: '<what it measures>',
},
}
let suiteOrder = ['datatable', 'pokeboxes', '<view>']
let suiteTitles: Record<string, string> = {
datatable: 'Data Table',
pokeboxes: 'Pokeboxes',
<view>: '<View Title>',
}Commands
- Run the full benchmark suite:
- pnpm --filter benchmarks bench (or pnpm run bench from bench/)
- Run only the new view:
- pnpm --filter benchmarks bench -- --view <view>
- Validate markup only (preflight):
- pnpm --filter benchmarks bench -- --preflight-only --view <view>
- View results UI:
- pnpm --filter benchmarks dev → open http://localhost:4555/snapshot
Notes
- Preflight DOM checks compare each framework against the blueprint for every view.
- If you add new helpers or data shapes, update
bench/frameworks/shared.jsand
bench/frameworks/shared.d.ts.
- Keep button IDs and aria attributes aligned across all frameworks and blueprints.