Files
Imhotep/packages/imhotep-playwright/src/property-runner.integration.test.ts
T

887 lines
27 KiB
TypeScript
Raw Normal View History

/**
* Integration tests for the property runner.
*
* Mocks the renderer adapter and scene target so these tests exercise
* the runner orchestration without needing a real browser.
*
* Pattern: Arrange-Act-Assert with Node.js built-in test runner.
*/
import { describe, it } from 'node:test'
import assert from 'node:assert'
import type { SceneTarget, RenderCase } from 'imhotep-core/scene-target'
import type { InputDomain } from 'imhotep-core/property-contracts'
import type { GeometryWorld } from 'imhotep-core/world'
import type { MountedScene, RendererAdapter, PropertyPredicate, PropertyRunnerDeps } from './property-runner.js'
import { runProperty, runEnumeratedProperty, runSampledProperty } from './property-runner.js'
import type { FastCheckAdapter } from './fast-check-adapter.js'
// ---------------------------------------------------------------------------
// Mock Builder
// ---------------------------------------------------------------------------
function createMockRendererAdapter(
opts: { failOnCaseId?: string } = {},
): RendererAdapter {
const mountedScenes: MountedScene[] = []
return {
async mount(_sceneTarget: SceneTarget, renderCase: RenderCase): Promise<MountedScene> {
if (opts.failOnCaseId && renderCase.caseId === opts.failOnCaseId) {
throw new Error(`Simulated mount failure for ${renderCase.caseId}`)
}
const scene: MountedScene = {
caseId: renderCase.caseId,
world: createMockWorld(renderCase.caseId),
}
mountedScenes.push(scene)
return scene
},
async unmount(mounted: MountedScene): Promise<void> {
const idx = mountedScenes.findIndex((s) => s.caseId === mounted.caseId)
if (idx >= 0) mountedScenes.splice(idx, 1)
},
}
}
function createMockWorld(sceneId: string): GeometryWorld {
return {
sceneId,
snapshotId: 'snap-1',
env: {
viewportWidth: 1280,
viewportHeight: 720,
deviceScaleFactor: 1,
colorScheme: 'light',
pointer: 'fine',
hover: false,
reducedMotion: 'no-preference',
locale: 'en',
writingMode: 'horizontal-tb',
},
source: {
url: 'http://localhost/test',
browserName: 'chromium',
browserVersion: '120',
engine: 'chromium-cdp',
},
strings: [],
subjects: [],
dom: { nodes: [], nodeCount: 0 },
frames: [],
matrices: [],
rects: [],
boxes: [],
fragments: [],
transforms: [],
styles: [],
text: [],
topology: { ancestors: [], descendants: [], stacks: [] },
scroll: [],
clipping: [],
paint: [],
visibility: [],
provenance: [],
confidence: [],
} as unknown as GeometryWorld
}
function createMockFastCheckAdapter(): FastCheckAdapter {
return {
assert: async (prop: unknown, params?: Record<string, unknown>) => {
const p = prop as { predicate: (value: unknown) => boolean | Promise<boolean>; arbitrary: unknown }
const numRuns = (params?.numRuns as number) ?? 100
const seed = (params?.seed as number) ?? 42
// Deterministic pseudo-random from seed for replayability
let rngState = seed
const next = () => {
rngState = (rngState * 16807 + 0) % 2147483647
return rngState / 2147483647
}
for (let i = 0; i < numRuns; i++) {
const value = generateValue(p.arbitrary, next)
const passed = await p.predicate(value)
if (!passed) {
const err = new Error(`Property failed at run ${i}`)
;(err as unknown as Record<string, unknown>).counterexample = value
throw err
}
}
},
record: (recordModel: Record<string, unknown>) => recordModel,
constantFrom: (...values: unknown[]) => values,
sample: (arb: unknown, params?: { seed?: number; numRuns?: number }) => {
const numRuns = params?.numRuns ?? 10
const seed = params?.seed ?? 42
let rngState = seed
const next = () => {
rngState = (rngState * 16807 + 0) % 2147483647
return rngState / 2147483647
}
const out: unknown[] = []
for (let i = 0; i < numRuns; i++) {
out.push(generateValue(arb, next))
}
return out
},
property: (arb: unknown, predicate: (value: unknown) => boolean | Promise<boolean>) => {
return { arbitrary: arb, predicate }
},
}
}
function generateValue(arb: unknown, next: () => number): unknown {
if (Array.isArray(arb)) {
const idx = Math.floor(next() * arb.length)
return arb[idx]
}
if (arb && typeof arb === 'object') {
const obj: Record<string, unknown> = {}
for (const [key, val] of Object.entries(arb)) {
obj[key] = generateValue(val, next)
}
return obj
}
return arb
}
function createConcurrencyTrackingAdapter(delayMs = 50) {
let currentConcurrent = 0
let maxConcurrent = 0
const adapter: RendererAdapter = {
async mount(_sceneTarget: SceneTarget, renderCase: RenderCase): Promise<MountedScene> {
currentConcurrent++
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)
await new Promise((r) => setTimeout(r, delayMs))
return {
caseId: renderCase.caseId,
world: createMockWorld(renderCase.caseId),
}
},
async unmount(): Promise<void> {
currentConcurrent--
},
}
return {
adapter,
getMaxConcurrent: () => maxConcurrent,
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
const sceneTarget: SceneTarget = { kind: 'fixture', fixtureId: 'button-fixture' }
describe('property runner — enumerated mode', () => {
it('evaluates every enumerated case deterministically', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = ['sm', 'md', 'lg']
const domain: InputDomain = { mode: 'enumerated', values }
const evaluated: string[] = []
const predicate: PropertyPredicate = async (scene) => {
evaluated.push((scene as MountedScene).caseId)
return true
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps)
assert.strictEqual(result.mode, 'enumerated-determinate')
assert.strictEqual(result.totalCases, 3)
assert.strictEqual(result.passed, true)
assert.strictEqual(evaluated.length, 3)
assert.ok(evaluated.includes('enum-0'))
assert.ok(evaluated.includes('enum-1'))
assert.ok(evaluated.includes('enum-2'))
})
it('stops at first failing case and reports it', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = ['sm', 'md', 'lg']
const domain: InputDomain = { mode: 'enumerated', values }
const predicate: PropertyPredicate = async (scene) => {
return (scene as MountedScene).caseId !== 'enum-1'
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps)
assert.strictEqual(result.mode, 'enumerated-determinate')
assert.strictEqual(result.passed, false)
assert.strictEqual(result.failingCase, 'md')
assert.strictEqual(result.caseIndex, 1)
})
it('enumerated runs all cases concurrently', async () => {
const { adapter } = createConcurrencyTrackingAdapter(50)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = [1, 2, 3, 4, 5]
const domain: InputDomain = { mode: 'enumerated', values }
const start = performance.now()
const result = await runEnumeratedProperty(sceneTarget, domain, async () => true, deps, {
concurrency: 5,
})
const elapsed = performance.now() - start
assert.strictEqual(result.passed, true)
assert.strictEqual(result.totalCases, 5)
assert.ok(elapsed < 200, `Expected concurrent run to be fast, but took ${elapsed}ms`)
})
it('enumerated stops on first failure with failFast', async () => {
const { adapter } = createConcurrencyTrackingAdapter(10)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = [1, 2, 3, 4, 5]
const domain: InputDomain = { mode: 'enumerated', values }
const evaluated: number[] = []
const predicate: PropertyPredicate = async (scene) => {
const idx = parseInt((scene as MountedScene).caseId.replace('enum-', ''))
evaluated.push(idx)
return idx !== 2
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps, {
concurrency: 1,
failFast: true,
})
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 2)
assert.ok(evaluated.includes(0))
assert.ok(evaluated.includes(1))
assert.ok(evaluated.includes(2))
assert.ok(!evaluated.includes(3))
assert.ok(!evaluated.includes(4))
})
it('enumerated runs all cases despite failure without failFast', async () => {
const { adapter } = createConcurrencyTrackingAdapter(10)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = [1, 2, 3, 4, 5]
const domain: InputDomain = { mode: 'enumerated', values }
const evaluated: number[] = []
const predicate: PropertyPredicate = async (scene) => {
const idx = parseInt((scene as MountedScene).caseId.replace('enum-', ''))
evaluated.push(idx)
return idx !== 2
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps, {
concurrency: 1,
failFast: false,
})
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 2)
assert.strictEqual(evaluated.length, 5)
assert.ok(evaluated.includes(0))
assert.ok(evaluated.includes(1))
assert.ok(evaluated.includes(2))
assert.ok(evaluated.includes(3))
assert.ok(evaluated.includes(4))
})
it('enumerated respects concurrency limit', async () => {
const { adapter, getMaxConcurrent } = createConcurrencyTrackingAdapter(50)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = [1, 2, 3, 4, 5]
const domain: InputDomain = { mode: 'enumerated', values }
const result = await runEnumeratedProperty(sceneTarget, domain, async () => true, deps, {
concurrency: 2,
})
assert.strictEqual(result.passed, true)
assert.ok(getMaxConcurrent() <= 2, `Expected max concurrent <= 2, but got ${getMaxConcurrent()}`)
assert.ok(getMaxConcurrent() > 1, `Expected some concurrency, but got ${getMaxConcurrent()}`)
})
})
describe('property runner — sampled mode', () => {
it('runs sampled cases with fast-check and reports seed', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 42,
numRuns: 20,
}
let runCount = 0
const predicate: PropertyPredicate = async () => {
runCount++
return true
}
const result = await runSampledProperty(sceneTarget, domain, predicate, deps, { seed: 42, numRuns: 20 })
assert.strictEqual(result.mode, 'sampled')
assert.strictEqual(result.seed, 42)
assert.strictEqual(result.numRuns, 20)
assert.strictEqual(result.passed, true)
assert.strictEqual(runCount, 20)
})
it('captures counterexample on failure', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 42,
numRuns: 20,
}
let callCount = 0
const predicate: PropertyPredicate = async () => {
callCount++
return callCount < 5
}
const result = await runSampledProperty(sceneTarget, domain, predicate, deps, { seed: 42, numRuns: 20 })
assert.strictEqual(result.mode, 'sampled')
assert.strictEqual(result.passed, false)
assert.ok(result.counterexample !== undefined)
assert.ok(result.diagnostics.length > 0)
})
it('reproduces identical run with same seed', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 123,
numRuns: 10,
}
const run1Inputs: unknown[] = []
const run2Inputs: unknown[] = []
const predicate1: PropertyPredicate = async (_scene, ctx) => {
run1Inputs.push(ctx.renderCase.input)
return true
}
const predicate2: PropertyPredicate = async (_scene, ctx) => {
run2Inputs.push(ctx.renderCase.input)
return true
}
await runSampledProperty(sceneTarget, domain, predicate1, deps, { seed: 123, numRuns: 10 })
await runSampledProperty(sceneTarget, domain, predicate2, deps, { seed: 123, numRuns: 10 })
assert.deepStrictEqual(run1Inputs, run2Inputs)
})
it('sampled runs iterations concurrently', async () => {
const { adapter } = createConcurrencyTrackingAdapter(50)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('a', 'b', 'c'),
seed: 42,
numRuns: 10,
}
const start = performance.now()
const result = await runSampledProperty(sceneTarget, domain, async () => true, deps, {
seed: 42,
numRuns: 10,
concurrency: 10,
})
const elapsed = performance.now() - start
assert.strictEqual(result.passed, true)
assert.strictEqual(result.numRuns, 10)
assert.ok(elapsed < 300, `Expected concurrent sampled run to be fast, but took ${elapsed}ms`)
})
it('sampled respects concurrency limit', async () => {
const { adapter, getMaxConcurrent } = createConcurrencyTrackingAdapter(50)
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('a', 'b', 'c'),
seed: 42,
numRuns: 10,
}
const result = await runSampledProperty(sceneTarget, domain, async () => true, deps, {
seed: 42,
numRuns: 10,
concurrency: 2,
})
assert.strictEqual(result.passed, true)
assert.ok(getMaxConcurrent() <= 2, `Expected max concurrent <= 2, but got ${getMaxConcurrent()}`)
assert.ok(getMaxConcurrent() > 1, `Expected some concurrency, but got ${getMaxConcurrent()}`)
})
it('sampled preserves seed and shrinking', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 42,
numRuns: 20,
}
let callCount = 0
const predicate: PropertyPredicate = async () => {
callCount++
return callCount < 5
}
const result1 = await runSampledProperty(sceneTarget, domain, predicate, deps, {
seed: 42,
numRuns: 20,
})
callCount = 0
const result2 = await runSampledProperty(sceneTarget, domain, predicate, deps, {
seed: 42,
numRuns: 20,
})
assert.strictEqual(result1.passed, false)
assert.strictEqual(result2.passed, false)
assert.deepStrictEqual(result1.counterexample, result2.counterexample)
assert.deepStrictEqual(result1.minimalFailingCase, result2.minimalFailingCase)
})
})
describe('property runner — error handling', () => {
it('error in one case does not crash runner', async () => {
const evaluated: number[] = []
const adapter: RendererAdapter = {
async mount(_sceneTarget, renderCase) {
if (renderCase.caseId === 'enum-2') {
throw new Error('Mount failed')
}
return {
caseId: renderCase.caseId,
world: createMockWorld(renderCase.caseId),
}
},
async unmount() {},
}
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = [1, 2, 3, 4, 5]
const domain: InputDomain = { mode: 'enumerated', values }
const predicate: PropertyPredicate = async (scene) => {
const idx = parseInt((scene as MountedScene).caseId.replace('enum-', ''))
evaluated.push(idx)
return true
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps, {
failFast: false,
})
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 2)
assert.ok(evaluated.includes(0))
assert.ok(evaluated.includes(1))
assert.ok(evaluated.includes(3))
assert.ok(evaluated.includes(4))
})
})
describe('property runner — mount/unmount', () => {
it('mount/unmount called for every case', async () => {
let mountCount = 0
let unmountCount = 0
const adapter: RendererAdapter = {
async mount(_sceneTarget, renderCase) {
mountCount++
return {
caseId: renderCase.caseId,
world: createMockWorld(renderCase.caseId),
}
},
async unmount() {
unmountCount++
},
}
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = ['a', 'b', 'c', 'd', 'e']
const domain: InputDomain = { mode: 'enumerated', values }
const result = await runEnumeratedProperty(sceneTarget, domain, async () => true, deps)
assert.strictEqual(result.passed, true)
assert.strictEqual(mountCount, 5)
assert.strictEqual(unmountCount, 5)
})
})
describe('property runner — dispatch', () => {
it('dispatches to enumerated runner for enumerated domain', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = { mode: 'enumerated', values: [1, 2, 3] }
const result = await runProperty(sceneTarget, domain, async () => true, deps)
assert.strictEqual(result.mode, 'enumerated-determinate')
})
it('dispatches to sampled runner for generated domain', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('a', 'b'),
seed: 1,
numRuns: 5,
}
const result = await runProperty(sceneTarget, domain, async () => true, deps, { seed: 1, numRuns: 5 })
assert.strictEqual(result.mode, 'sampled')
})
it('throws for unsupported domain mode', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain = { mode: 'unknown' } as unknown as InputDomain
await assert.rejects(
async () => runProperty(sceneTarget, domain, async () => true, deps),
/Unsupported InputDomain mode/,
)
})
})
describe('property runner — report mode', () => {
it('compact enumerated result strips verbose fields', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = ['sm', 'md', 'lg']
const domain: InputDomain = { mode: 'enumerated', values }
const predicate: PropertyPredicate = async (scene) => {
return (scene as MountedScene).caseId !== 'enum-1'
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps, {
reportMode: 'compact',
})
assert.strictEqual(result.mode, 'enumerated-determinate')
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 1)
assert.strictEqual(result.minimalFailingCase, 'md')
assert.ok(result.durationMs !== undefined)
assert.ok(result.replayPayload !== undefined)
assert.deepStrictEqual(result.replayPayload!, {
props: 'md',
seed: -1,
caseIndex: 1,
})
assert.strictEqual(result.diagnostics.length, 0)
assert.strictEqual((result as any).failingScene, undefined)
})
it('verbose enumerated result includes full payload', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const values = ['sm', 'md', 'lg']
const domain: InputDomain = { mode: 'enumerated', values }
const predicate: PropertyPredicate = async (scene) => {
return (scene as MountedScene).caseId !== 'enum-1'
}
const result = await runEnumeratedProperty(sceneTarget, domain, predicate, deps, {
reportMode: 'verbose',
})
assert.strictEqual(result.mode, 'enumerated-determinate')
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 1)
assert.strictEqual(result.minimalFailingCase, 'md')
assert.ok(result.durationMs !== undefined)
assert.ok(Array.isArray(result.diagnostics))
assert.ok(result.replayPayload !== undefined)
assert.deepStrictEqual(result.replayPayload!, {
props: 'md',
seed: -1,
caseIndex: 1,
})
})
it('compact sampled result strips verbose fields', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 42,
numRuns: 20,
}
let callCount = 0
const predicate: PropertyPredicate = async () => {
callCount++
return callCount < 5
}
const result = await runSampledProperty(sceneTarget, domain, predicate, deps, {
seed: 42,
numRuns: 20,
reportMode: 'compact',
})
assert.strictEqual(result.mode, 'sampled')
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 4)
assert.ok(result.minimalFailingCase !== undefined)
assert.ok(result.durationMs !== undefined)
assert.ok(result.replayPayload !== undefined)
assert.strictEqual(result.replayPayload!.seed, 42)
assert.strictEqual(result.replayPayload!.caseIndex, 4)
assert.ok(result.replayPayload!.props !== undefined)
assert.strictEqual(result.diagnostics.length, 0)
assert.strictEqual((result as any).failingScene, undefined)
})
it('verbose sampled result includes full payload', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 42,
numRuns: 20,
}
let callCount = 0
const predicate: PropertyPredicate = async () => {
callCount++
return callCount < 5
}
const result = await runSampledProperty(sceneTarget, domain, predicate, deps, {
seed: 42,
numRuns: 20,
reportMode: 'verbose',
})
assert.strictEqual(result.mode, 'sampled')
assert.strictEqual(result.passed, false)
assert.strictEqual(result.caseIndex, 4)
assert.ok(result.minimalFailingCase !== undefined)
assert.ok(result.durationMs !== undefined)
assert.ok(Array.isArray(result.diagnostics))
assert.ok(result.replayPayload !== undefined)
assert.strictEqual(result.replayPayload!.seed, 42)
assert.strictEqual(result.replayPayload!.caseIndex, 4)
assert.ok(result.replayPayload!.props !== undefined)
})
it('reproduces identical run from sampled replay payload', async () => {
const adapter = createMockRendererAdapter()
const fc = createMockFastCheckAdapter()
const deps: PropertyRunnerDeps = {
rendererAdapter: adapter,
worldExtractor: { extract: async (m) => m.world },
fc,
}
const domain: InputDomain = {
mode: 'generated',
arbitrary: fc.constantFrom('sm', 'md', 'lg'),
seed: 123,
numRuns: 10,
}
let callCount = 0
const collectedInputs: unknown[] = []
const predicate: PropertyPredicate = async (_scene, ctx) => {
collectedInputs.push(ctx.renderCase.input)
callCount++
return callCount < 7
}
const result = await runSampledProperty(sceneTarget, domain, predicate, deps, {
seed: 123,
numRuns: 10,
reportMode: 'verbose',
})
assert.strictEqual(result.passed, false)
assert.ok(result.replayPayload !== undefined)
const replayInputs: unknown[] = []
let replayCount = 0
const replayPayload = result.replayPayload!
const replayPredicate: PropertyPredicate = async (_scene, ctx) => {
replayInputs.push(ctx.renderCase.input)
replayCount++
return replayCount < replayPayload.caseIndex + 1
}
await runSampledProperty(sceneTarget, domain, replayPredicate, deps, {
seed: replayPayload.seed,
numRuns: replayPayload.caseIndex + 1,
reportMode: 'verbose',
})
assert.deepStrictEqual(
collectedInputs[replayPayload.caseIndex],
replayInputs[replayPayload.caseIndex],
)
})
})