STATUS: Partial Implementation - Core features implemented, future sections in design.
This document captures the design for the next evolution of the scenetest CLI, including implemented features and future considerations.
warnIf) don’t fail tests but surface unexpected pathsScenes can be expressed as arrays of strings, making them readable by non-engineers and serializable:
const checkoutFlow = [
'openTo /cart',
'see cart-items',
'click checkout-button',
'see payment-form',
'typeInto card-number 4242424242424242',
'click submit',
'seeToast success-toast',
]
await runDsl(user, checkoutFlow)
Named, reusable action sequences with variable substitution:
defineMacro('login', [
'see login-form',
'typeInto username ',
'typeInto password ',
'click submit-button',
'see dashboard',
])
await runMacro(user, 'login', { username: 'alice', password: 'secret' })
For the full text DSL grammar, .spec.md markdown scene format, and complete syntax reference, see Text DSL Reference.
For selector priority order, nested selectors, implicit key matching, sigil prefixes, and alias configuration, see Selector Reference.
see() updates the current scope. Subsequent actions operate within that scope:
await user
.see('playlist-row 12345') // Scope → this playlist row (key matched on same element)
.click('like-button') // Click within that row
.see('liked-indicator') // Verify within that row
up(selector) - Navigate to an ancestor matching the selectorprev() - Return to the previous scopeawait user
.see('modal')
.see('form')
.typeInto('name', 'Test')
.prev() // Back to modal scope
.click('close') // Click modal's close button
warnIf() registers a warning trigger for unexpected paths in your test script. If the selector appears during subsequent actions, a warning is recorded but the test continues.
// This user should have dismissed the welcome modal
user.warnIf('welcome-modal', 'user should have dismiss flag set')
await user.openTo('/dashboard')
if() watchers which clear after each awaitSceneReport.warnings, not assertionsWarnings show in the summary:
⚡ 2 script warning(s)
└─ [user] welcome-modal: user should have dismiss flag set
during: openTo(/dashboard)
Every action emits console warnings if it exceeds the warnAfter threshold (default: 500ms):
⏱ 523ms - see('dashboard') - still waiting...
⏱ 1247ms - see('dashboard') - still waiting...
✓ 1892ms - see('dashboard') - completed
defineConfig({
actionTimeout: 5000, // Fail after 5s
warnAfter: 500, // Warn after 500ms
})
When a selector fails to match, use explainSelector() to debug:
import { explainSelector } from '@scenetest/scenes'
const result = await explainSelector(page, 'my-selector')
// {
// found: false,
// count: 0,
// matches: [],
// suggestions: ['my-selector-v2', 'my-selectors']
// }
Section Status: Design Only
Purpose: Environment control, not implementation testing.
test('handles API failure', async ({ actor, network }) => {
const user = await actor('user')
// Inject failure
network.fail('/api/profile')
await user.openTo('/profile')
await user.see('error-state')
})
test('loads large dataset', async ({ actor, network }) => {
const user = await actor('user')
// Mock response
network.mock('/api/items', {
items: generateItems(1000),
})
await user.openTo('/items')
await user.see('pagination')
})
test('handles slow network', async ({ actor, network }) => {
const user = await actor('user')
// Add latency
network.delay('/api/*', 3000)
await user.openTo('/dashboard')
await user.see('loading-skeleton')
})
Section Status: Design Only
Purpose: Assert state hasn’t changed unexpectedly.
// Capture state
const before = await user.snapshot('profile-card')
// Do something
await user.click('edit')
await user.typeInto('name', 'New Name')
await user.click('cancel')
// Verify restored
await user.expectSnapshot('profile-card', before)
Single method for snapshot comparison:
interface SequentialActorHandle {
// Capture current state of selector
snapshot(selector: Selector): Promise<Snapshot>
// Assert current state matches snapshot
expectSnapshot(selector: Selector, expected: Snapshot): ActionChain
}
defineConfig({
baseUrl: 'http://localhost:3000',
// Browser settings
browser: 'chromium', // 'chromium' | 'firefox' | 'webkit'
headed: false,
slowMo: 0,
// Timing
timeout: 30000, // Scene timeout
actionTimeout: 5000, // Individual action timeout
warnAfter: 500, // Console warning threshold
// Reporting
reportDir: './scenetest-reports',
reportFormat: 'html', // 'html' | 'json' | 'both'
// Actor teams are auto-discovered from actors.ts or actors/*.ts
// (not defined in config)
// Hooks
beforeAll: async () => { /* setup */ },
afterAll: async () => { /* teardown */ },
beforeEach: async (scene) => { /* per-scene setup */ },
afterEach: async (scene, report) => { /* per-scene teardown */ },
})
Section Status: Conceptual - User-requested feature, needs design.
The user mentioned wanting a visualization tool that shows:
“a shape/overview/lets you play the song of each of your different scenes and shows your actors move through them, each with their own shape and instrument playing along with the others”
This would be a separate tool that:
Implementation would be a separate package (@scenetest/visualizer) consuming the JSON reports.