Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/runtime-core/src/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,9 @@ export const commandRegistry = [
{ name: "presentation-url", description: "Optional frontend URL corresponding to the edited content. Enables bounded frontend-to-editor presentation evidence.", format: "path or URL" },
{ name: "presentation-frontend-selector", description: "Optional frontend content selector for presentation evidence; defaults to body and is bounded to 512 characters.", format: "CSS selector" },
{ name: "presentation-editor-selector", description: "Optional editor canvas selector for presentation evidence; defaults to the block list layout and is bounded to 512 characters.", format: "CSS selector" },
{ name: "presentation-threshold", description: "Maximum geometry delta ratio accepted by presentation evidence; defaults to 0.02.", format: "number between 0 and 1" },
{ name: "presentation-pixel-threshold", description: "Maximum pixelmatch per-pixel color-distance threshold; defaults to 0.02.", format: "number between 0 and 1" },
{ name: "presentation-overlap-mismatch-threshold", description: "Maximum mismatched-pixel ratio across the shared canvas area; defaults to 0.1.", format: "number between 0 and 1" },
{ name: "presentation-dimension-drift-threshold", description: "Maximum canvas-dimension delta ratio; defaults to 0.01.", format: "number between 0 and 1" },
{ name: "capture", description: "Comma-separated artifacts to capture after opening the editor.", format: "steps,console,errors,html,screenshot,editor-state,editor-validity" },
{ name: "artifact-prefix", description: "Optional artifact directory relative to the runtime artifact root for this invocation; defaults to files/browser. Use files/browser/editor-open/<name> to isolate per-fixture editor-open evidence in a batch.", format: "relative artifact directory" },
],
Expand Down
16 changes: 16 additions & 0 deletions packages/runtime-playground/src/browser-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,8 @@ export interface BrowserEditorPresentationMatchSummary {
diagnostic?: string
equivalentCanvasWidths?: boolean
majorGeometryDrift?: boolean
majorVisualDivergence?: boolean
comparison?: BrowserEditorPresentationComparison
unreadableContent?: boolean
hiddenContent?: boolean
unresolvedAssetCount?: number
Expand All @@ -329,6 +331,20 @@ export interface BrowserEditorPresentationMatchSummary {
}
}

export interface BrowserEditorPresentationComparison {
pixelThreshold: number
overlapMismatchThreshold: number
dimensionDriftThreshold: number
mismatchPixels: number
totalPixels: number
mismatchRatio: number
overlapMismatchPixels: number
overlapPixels: number
overlapMismatchRatio: number
dimensionDeltaPixels: number
dimensionDeltaRatio: number
}

export interface BrowserEditorPresentationSurfaceGeometry {
width: number
height: number
Expand Down
55 changes: 44 additions & 11 deletions packages/runtime-playground/src/editor-command-runners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1040,7 +1040,7 @@ async function captureEditorPresentationMatch(input: {
if (!presentationUrl) return { schema: "wp-codebox/editor-presentation-match/v1", status: "unavailable", diagnostic: "presentation-url was not supplied" }
const frontendSelector = boundedPresentationSelector(input.args, "presentation-frontend-selector", "body")
const editorSelector = boundedPresentationSelector(input.args, "presentation-editor-selector", EDITOR_CANVAS_DEFAULT_LAYOUT_SELECTOR)
const threshold = presentationThreshold(input.args)
const thresholds = presentationThresholds(input.args)
const frontendPath = input.artifactSession.absolutePath("presentation-frontend.png")
const editorPath = input.artifactSession.absolutePath("presentation-editor.png")
const frontendRef = input.artifactSession.path("presentation-frontend.png")
Expand Down Expand Up @@ -1086,18 +1086,43 @@ async function captureEditorPresentationMatch(input: {
await input.artifactSession.writeGenerated("screenshot", "presentation-editor.png", async (path) => { await editor.screenshot({ path, timeout: input.waitTimeoutMs }); })
let comparison: Awaited<ReturnType<typeof comparePngFiles>> | undefined
await input.artifactSession.writeGenerated("screenshot", "presentation-diff.png", async (path) => {
comparison = await comparePngFiles(frontendPath, editorPath, path, { threshold, includeAA: false, maxRegions: 8 })
comparison = await comparePngFiles(frontendPath, editorPath, path, { threshold: thresholds.pixel, includeAA: false, maxRegions: 8 })
})
if (!comparison) throw new Error("Presentation comparison did not produce metrics")
const equivalentCanvasWidths = comparison.source.width === comparison.candidate.width
// The same bounded threshold applies to canvas extent and the shared
// visual region; equal widths alone must not certify different renders.
const majorGeometryDrift = comparison.dimensionDeltaRatio > threshold || comparison.overlapMismatchRatio > threshold
const majorGeometryDrift = comparison.dimensionDeltaRatio > thresholds.dimensionDrift
const majorVisualDivergence = comparison.overlapMismatchRatio > thresholds.overlapMismatch
const unreadableContent = !frontendEvidence.readable || !editorEvidence.readable
const hiddenContent = !frontendEvidence.visible || !editorEvidence.visible
const unresolvedAssetCount = frontendEvidence.unresolvedAssets + editorEvidence.unresolvedAssets
const passed = equivalentCanvasWidths && !majorGeometryDrift && !unreadableContent && !hiddenContent && unresolvedAssetCount === 0
return { schema: "wp-codebox/editor-presentation-match/v1", status: passed ? "passed" : "failed", equivalentCanvasWidths, majorGeometryDrift, unreadableContent, hiddenContent, unresolvedAssetCount, frontendScreenshot: frontendRef, editorScreenshot: editorRef, diffScreenshot: diffRef, geometry: { frontend: frontendGeometry, liveEditor: liveEditorGeometry, isolatedEditor: isolatedEditorGeometry } }
const passed = equivalentCanvasWidths && !majorGeometryDrift && !majorVisualDivergence && !unreadableContent && !hiddenContent && unresolvedAssetCount === 0
return {
schema: "wp-codebox/editor-presentation-match/v1",
status: passed ? "passed" : "failed",
equivalentCanvasWidths,
majorGeometryDrift,
majorVisualDivergence,
comparison: {
pixelThreshold: thresholds.pixel,
overlapMismatchThreshold: thresholds.overlapMismatch,
dimensionDriftThreshold: thresholds.dimensionDrift,
mismatchPixels: comparison.mismatchPixels,
totalPixels: comparison.totalPixels,
mismatchRatio: comparison.mismatchRatio,
overlapMismatchPixels: comparison.overlapMismatchPixels,
overlapPixels: comparison.overlapPixels,
overlapMismatchRatio: comparison.overlapMismatchRatio,
dimensionDeltaPixels: comparison.dimensionDeltaPixels,
dimensionDeltaRatio: comparison.dimensionDeltaRatio,
},
unreadableContent,
hiddenContent,
unresolvedAssetCount,
frontendScreenshot: frontendRef,
editorScreenshot: editorRef,
diffScreenshot: diffRef,
geometry: { frontend: frontendGeometry, liveEditor: liveEditorGeometry, isolatedEditor: isolatedEditorGeometry },
}
} finally {
await frontendContext.close()
}
Expand Down Expand Up @@ -1227,11 +1252,19 @@ function boundedPresentationSelector(args: string[], name: string, fallback: str
return selector
}

function presentationThreshold(args: string[]): number {
const raw = argValue(args, "presentation-threshold")?.trim()
if (!raw) return 0.02
function presentationThresholds(args: string[]): { pixel: number; overlapMismatch: number; dimensionDrift: number } {
return {
pixel: boundedPresentationThreshold(args, "presentation-pixel-threshold", 0.02),
overlapMismatch: boundedPresentationThreshold(args, "presentation-overlap-mismatch-threshold", 0.1),
dimensionDrift: boundedPresentationThreshold(args, "presentation-dimension-drift-threshold", 0.01),
}
}

function boundedPresentationThreshold(args: string[], name: string, fallback: number): number {
const raw = argValue(args, name)?.trim()
if (!raw) return fallback
const value = Number(raw)
if (!Number.isFinite(value) || value < 0 || value > 1) throw new Error(`wordpress.editor-open presentation-threshold must be between 0 and 1: ${raw}`)
if (!Number.isFinite(value) || value < 0 || value > 1) throw new Error(`wordpress.editor-open ${name} must be between 0 and 1: ${raw}`)
return value
}

Expand Down
Loading
Loading