@riddledc/riddle-proof 0.5.42 → 0.5.43
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/{chunk-DWQK7J4U.js → chunk-XNRMMQAY.js} +3 -1
- package/dist/diagnostics.cjs +3 -1
- package/dist/diagnostics.js +1 -1
- package/dist/index.cjs +3 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/runtime/lib/author.py +1 -0
- package/runtime/lib/riddle_core_call.mjs +22 -0
- package/runtime/lib/util.py +6 -1
- package/runtime/lib/verify.py +126 -0
- package/runtime/tests/recon_verify_smoke.py +44 -0
|
@@ -96,14 +96,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
96
96
|
const record = isRecord(payload) ? payload : {};
|
|
97
97
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
98
98
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
99
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
99
100
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
100
101
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
101
102
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
103
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
102
104
|
return {
|
|
103
105
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
104
106
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
105
107
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
106
|
-
result_keys:
|
|
108
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
107
109
|
artifact_json: sortedKeys(artifactJson),
|
|
108
110
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
109
111
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/diagnostics.cjs
CHANGED
|
@@ -127,14 +127,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
127
127
|
const record = isRecord(payload) ? payload : {};
|
|
128
128
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
129
129
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
130
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
130
131
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
131
132
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
132
133
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
134
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
133
135
|
return {
|
|
134
136
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
135
137
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
136
138
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
137
|
-
result_keys:
|
|
139
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
138
140
|
artifact_json: sortedKeys(artifactJson),
|
|
139
141
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
140
142
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/diagnostics.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -5172,14 +5172,16 @@ function summarizeCaptureArtifacts(payload) {
|
|
|
5172
5172
|
const record = isRecord(payload) ? payload : {};
|
|
5173
5173
|
const artifactJson = isRecord(record._artifact_json) ? record._artifact_json : {};
|
|
5174
5174
|
const proofJson = isRecord(record._proof_json) ? record._proof_json : isRecord(artifactJson["proof.json"]) ? artifactJson["proof.json"] : {};
|
|
5175
|
+
const visualDiffJson = isRecord(record.visual_diff) ? record.visual_diff : isRecord(record.visualDiff) ? record.visualDiff : isRecord(artifactJson["visual-diff.json"]) ? artifactJson["visual-diff.json"] : {};
|
|
5175
5176
|
const consoleJson = isRecord(record.console) ? record.console : isRecord(artifactJson["console.json"]) ? artifactJson["console.json"] : {};
|
|
5176
5177
|
const result = isRecord(record.result) ? record.result : isRecord(proofJson.result) ? proofJson.result : isRecord(proofJson.script_result) ? proofJson.script_result : isRecord(proofJson.return_value) ? proofJson.return_value : isRecord(proofJson.value) ? proofJson.value : {};
|
|
5177
5178
|
const consoleSummary = isRecord(consoleJson.summary) ? redactForProofDiagnostics(consoleJson.summary, { string_limit: 500 }) : void 0;
|
|
5179
|
+
const resultKeys = /* @__PURE__ */ new Set([...sortedKeys(result), ...sortedKeys(visualDiffJson)]);
|
|
5178
5180
|
return {
|
|
5179
5181
|
outputs: artifactItems(record.outputs).slice(0, 20).map((item) => artifactSummary(item, "outputs")),
|
|
5180
5182
|
screenshots: artifactItems(record.screenshots).slice(0, 10).map((item) => artifactSummary(item, "screenshots")),
|
|
5181
5183
|
artifacts: artifactItems(record.artifacts).slice(0, 20).map((item) => artifactSummary(item, "artifacts")),
|
|
5182
|
-
result_keys:
|
|
5184
|
+
result_keys: Array.from(resultKeys).sort(),
|
|
5183
5185
|
artifact_json: sortedKeys(artifactJson),
|
|
5184
5186
|
artifact_errors: artifactErrorMap(record._artifact_errors),
|
|
5185
5187
|
proof_script_error: Boolean(proofJson.script_error),
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/runtime/lib/author.py
CHANGED
|
@@ -154,6 +154,7 @@ def author_request_payload(state, reference, baselines, current_plan, hypothesis
|
|
|
154
154
|
'For playable/gameplay proof, start the experience, send keyboard or pointer input, sample state before/after, measure non-HUD playfield/canvas pixel deltas across time, and set window.__riddleProofEvidence.playability or playability_evidence with version riddle-proof.playability.v1.',
|
|
155
155
|
'For data/audio/log/metric/custom proof, screenshots are optional; set window.__riddleProofEvidence inside page.evaluate to a JSON-serializable object with the measured observations the verifier should judge.',
|
|
156
156
|
'Do not assign globalThis.__riddleProofEvidence, window.__riddleProofEvidence, or self.__riddleProofEvidence outside page.evaluate; the Riddle worker context may not expose those globals safely.',
|
|
157
|
+
'When page.evaluate needs data from the outer Playwright script, pass exactly one serializable argument object; do not pass multiple positional arguments.',
|
|
157
158
|
'Do not begin capture_script with page.goto unless an in-app navigation is genuinely required after the preview opens the target route.',
|
|
158
159
|
'Only escalate to the human after the supervising agent concludes the workflow is genuinely stuck or not converging.',
|
|
159
160
|
],
|
|
@@ -136,6 +136,28 @@ async function run(tool, args) {
|
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
if (tool === "riddle_visual_diff") {
|
|
140
|
+
const {
|
|
141
|
+
async,
|
|
142
|
+
options,
|
|
143
|
+
stealth,
|
|
144
|
+
timeout_sec,
|
|
145
|
+
...visualDiffOptions
|
|
146
|
+
} = args;
|
|
147
|
+
const script = `return await visualDiff(${JSON.stringify(visualDiffOptions)});`;
|
|
148
|
+
return core.runWithDefaults(config, {
|
|
149
|
+
url: args.url_before,
|
|
150
|
+
script,
|
|
151
|
+
options: { ...(options ?? {}), returnResult: true },
|
|
152
|
+
stealth,
|
|
153
|
+
timeout_sec: timeout_sec ?? 60,
|
|
154
|
+
async,
|
|
155
|
+
}, {
|
|
156
|
+
include: ["result", "console", "visual_diff"],
|
|
157
|
+
returnAsync: !!async,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
if (tool === "riddle_run") {
|
|
140
162
|
return core.runWithDefaults(config, args.payload ?? args, {
|
|
141
163
|
include: ["screenshot", "console", "result"],
|
package/runtime/lib/util.py
CHANGED
|
@@ -14,6 +14,7 @@ RIDDLE_DIRECT_TOOLS = {
|
|
|
14
14
|
'riddle_build_preview',
|
|
15
15
|
'riddle_build_preview_status',
|
|
16
16
|
'riddle_script',
|
|
17
|
+
'riddle_visual_diff',
|
|
17
18
|
'riddle_run',
|
|
18
19
|
}
|
|
19
20
|
CAPTURE_ARTIFACT_JSON_LIMIT = 256 * 1024
|
|
@@ -767,7 +768,7 @@ def enrich_capture_payload(payload):
|
|
|
767
768
|
artifact_json = dict(enriched.get('_artifact_json') or {})
|
|
768
769
|
artifact_errors = dict(enriched.get('_artifact_errors') or {})
|
|
769
770
|
|
|
770
|
-
for name in ('console.json', 'proof.json'):
|
|
771
|
+
for name in ('console.json', 'proof.json', 'visual-diff.json'):
|
|
771
772
|
if name in artifact_json or name in artifact_errors:
|
|
772
773
|
continue
|
|
773
774
|
item = capture_output_item(enriched, name)
|
|
@@ -798,6 +799,10 @@ def enrich_capture_payload(payload):
|
|
|
798
799
|
enriched['result'] = result
|
|
799
800
|
break
|
|
800
801
|
|
|
802
|
+
visual_diff_json = artifact_json.get('visual-diff.json')
|
|
803
|
+
if isinstance(visual_diff_json, dict) and not enriched.get('visual_diff'):
|
|
804
|
+
enriched['visual_diff'] = visual_diff_json
|
|
805
|
+
|
|
801
806
|
return enriched
|
|
802
807
|
|
|
803
808
|
|
package/runtime/lib/verify.py
CHANGED
|
@@ -55,13 +55,24 @@ MIN_VISUAL_CHANGED_PIXELS = 5000
|
|
|
55
55
|
VISUAL_DELTA_PERCENT_KEYS = {
|
|
56
56
|
'change_pct', 'change_percent', 'changed_percent', 'percent_changed',
|
|
57
57
|
'diff_percent', 'visual_delta_percent', 'pixel_change_percent',
|
|
58
|
+
'changepercent', 'changedpercent', 'percentchanged', 'diffpercent',
|
|
59
|
+
'visualdeltapercent', 'pixelchangepercent', 'difference_percent',
|
|
60
|
+
'differencepercent', 'diff_percentage', 'diffpercentage',
|
|
61
|
+
'mismatch_percent', 'mismatchpercent', 'percentage', 'percent',
|
|
58
62
|
}
|
|
59
63
|
VISUAL_DELTA_RATIO_KEYS = {
|
|
60
64
|
'change_ratio', 'changed_ratio', 'diff_ratio', 'visual_delta_ratio',
|
|
65
|
+
'changeratio', 'changedratio', 'diffratio', 'visualdeltaratio',
|
|
66
|
+
'difference_ratio', 'differenceratio', 'mismatch_ratio', 'mismatchratio',
|
|
61
67
|
}
|
|
62
68
|
VISUAL_CHANGED_PIXEL_KEYS = {
|
|
63
69
|
'changed_pixels', 'changed_pixel_count', 'changedpixels',
|
|
64
70
|
'diff_pixels', 'pixel_delta', 'visual_delta_pixels',
|
|
71
|
+
'diffpixels', 'pixeldelta', 'visualdeltapixels', 'different_pixels',
|
|
72
|
+
'differentpixels', 'mismatch_pixels', 'mismatchpixels', 'pixels_changed',
|
|
73
|
+
'pixelschanged', 'diff_pixel_count', 'diffpixelcount',
|
|
74
|
+
'pixel_diff_count', 'pixeldiffcount', 'different_pixel_count',
|
|
75
|
+
'differentpixelcount', 'num_different_pixels', 'numdifferentpixels',
|
|
65
76
|
}
|
|
66
77
|
VISUAL_TOTAL_PIXEL_KEYS = {
|
|
67
78
|
'total_pixels', 'total_pixel_count', 'pixel_count', 'totalpixels',
|
|
@@ -800,6 +811,7 @@ def extract_visual_delta(payload):
|
|
|
800
811
|
payload = enrich_capture_payload(payload)
|
|
801
812
|
result = payload.get('result') if isinstance(payload, dict) else {}
|
|
802
813
|
proof_json = payload.get('_proof_json') if isinstance(payload, dict) else {}
|
|
814
|
+
artifact_json = payload.get('_artifact_json') if isinstance(payload, dict) else {}
|
|
803
815
|
proof_evidence = extract_proof_evidence(payload)
|
|
804
816
|
screenshot_url = extract_screenshot_url(payload)
|
|
805
817
|
artifact_summary = summarize_capture_artifacts(payload)
|
|
@@ -807,6 +819,11 @@ def extract_visual_delta(payload):
|
|
|
807
819
|
payload if isinstance(payload, dict) else {},
|
|
808
820
|
result if isinstance(result, dict) else {},
|
|
809
821
|
proof_json if isinstance(proof_json, dict) else {},
|
|
822
|
+
payload.get('visual_diff') if isinstance(payload, dict) and isinstance(payload.get('visual_diff'), dict) else {},
|
|
823
|
+
payload.get('visualDiff') if isinstance(payload, dict) and isinstance(payload.get('visualDiff'), dict) else {},
|
|
824
|
+
result.get('visual_diff') if isinstance(result, dict) and isinstance(result.get('visual_diff'), dict) else {},
|
|
825
|
+
result.get('visualDiff') if isinstance(result, dict) and isinstance(result.get('visualDiff'), dict) else {},
|
|
826
|
+
artifact_json.get('visual-diff.json') if isinstance(artifact_json, dict) and isinstance(artifact_json.get('visual-diff.json'), dict) else {},
|
|
810
827
|
proof_evidence,
|
|
811
828
|
]
|
|
812
829
|
|
|
@@ -899,6 +916,114 @@ def extract_visual_delta(payload):
|
|
|
899
916
|
}
|
|
900
917
|
|
|
901
918
|
|
|
919
|
+
def visual_delta_baseline_candidate(state, results):
|
|
920
|
+
reference = str(state.get('requested_reference') or state.get('reference') or 'both').strip().lower()
|
|
921
|
+
baseline = (results.get('baseline') or {}) if isinstance(results, dict) else {}
|
|
922
|
+
candidates = []
|
|
923
|
+
if reference in ('before', 'both', ''):
|
|
924
|
+
candidates.append(('before', (baseline.get('before') or {}).get('url') if isinstance(baseline.get('before'), dict) else ''))
|
|
925
|
+
if reference in ('prod', 'production', 'both'):
|
|
926
|
+
candidates.append(('prod', (baseline.get('prod') or {}).get('url') if isinstance(baseline.get('prod'), dict) else ''))
|
|
927
|
+
candidates.extend([
|
|
928
|
+
('before', state.get('before_cdn') or ''),
|
|
929
|
+
('prod', state.get('prod_cdn') or ''),
|
|
930
|
+
])
|
|
931
|
+
for label, url in candidates:
|
|
932
|
+
text = str(url or '').strip()
|
|
933
|
+
if text:
|
|
934
|
+
return {'label': label, 'url': text}
|
|
935
|
+
return {'label': '', 'url': ''}
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def add_visual_delta_diagnostic(visual_delta, key, value):
|
|
939
|
+
updated = dict(visual_delta or {})
|
|
940
|
+
diagnostic = dict(updated.get('diagnostic') or {})
|
|
941
|
+
diagnostic[key] = value
|
|
942
|
+
updated['diagnostic'] = diagnostic
|
|
943
|
+
return updated
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def measure_visual_delta_against_baseline(state, results, after_payload, current_visual_delta):
|
|
947
|
+
if not visual_delta_applies(state.get('verification_mode')):
|
|
948
|
+
return current_visual_delta
|
|
949
|
+
if isinstance(current_visual_delta, dict) and current_visual_delta.get('status') == 'measured':
|
|
950
|
+
return current_visual_delta
|
|
951
|
+
|
|
952
|
+
baseline = visual_delta_baseline_candidate(state, results)
|
|
953
|
+
before_url = baseline.get('url') or ''
|
|
954
|
+
after_url = extract_screenshot_url(after_payload, 'after-proof') or str(state.get('after_cdn') or '').strip()
|
|
955
|
+
if not before_url or not after_url:
|
|
956
|
+
missing = []
|
|
957
|
+
if not before_url:
|
|
958
|
+
missing.append('baseline screenshot')
|
|
959
|
+
if not after_url:
|
|
960
|
+
missing.append('after screenshot')
|
|
961
|
+
return add_visual_delta_diagnostic(
|
|
962
|
+
current_visual_delta,
|
|
963
|
+
'visual_diff_fallback',
|
|
964
|
+
{
|
|
965
|
+
'status': 'skipped',
|
|
966
|
+
'reason': 'missing ' + ' and '.join(missing),
|
|
967
|
+
'baseline_label': baseline.get('label') or '',
|
|
968
|
+
'before_url_present': bool(before_url),
|
|
969
|
+
'after_url_present': bool(after_url),
|
|
970
|
+
},
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
args = {
|
|
974
|
+
'url_before': before_url,
|
|
975
|
+
'url_after': after_url,
|
|
976
|
+
'delay_ms': 250,
|
|
977
|
+
'timeout_sec': 60,
|
|
978
|
+
}
|
|
979
|
+
try:
|
|
980
|
+
payload = invoke_retry('riddle_visual_diff', args, retries=2, timeout=180)
|
|
981
|
+
append_capture_diagnostic(state, 'visual_delta', 'riddle_visual_diff', args, payload)
|
|
982
|
+
except Exception as exc:
|
|
983
|
+
return add_visual_delta_diagnostic(
|
|
984
|
+
current_visual_delta,
|
|
985
|
+
'visual_diff_fallback',
|
|
986
|
+
{
|
|
987
|
+
'status': 'error',
|
|
988
|
+
'error': type(exc).__name__ + ': ' + str(exc)[:300],
|
|
989
|
+
'baseline_label': baseline.get('label') or '',
|
|
990
|
+
},
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
measured = extract_visual_delta(payload)
|
|
994
|
+
if isinstance(measured, dict) and measured.get('status') == 'measured':
|
|
995
|
+
measured['source'] = 'riddle_visual_diff'
|
|
996
|
+
measured['comparison'] = {
|
|
997
|
+
'baseline_label': baseline.get('label') or '',
|
|
998
|
+
'before_url': before_url,
|
|
999
|
+
'after_url': after_url,
|
|
1000
|
+
}
|
|
1001
|
+
if isinstance(payload, dict):
|
|
1002
|
+
diff_url = extract_screenshot_url(payload, 'visual-diff') or extract_screenshot_url(payload, 'diff')
|
|
1003
|
+
if diff_url:
|
|
1004
|
+
measured['comparison']['diff_url'] = diff_url
|
|
1005
|
+
measured['artifact_summary'] = summarize_capture_artifacts(payload)
|
|
1006
|
+
return measured
|
|
1007
|
+
|
|
1008
|
+
updated = add_visual_delta_diagnostic(
|
|
1009
|
+
current_visual_delta,
|
|
1010
|
+
'visual_diff_fallback',
|
|
1011
|
+
{
|
|
1012
|
+
'status': 'unmeasured',
|
|
1013
|
+
'baseline_label': baseline.get('label') or '',
|
|
1014
|
+
'before_url': before_url,
|
|
1015
|
+
'after_url': after_url,
|
|
1016
|
+
'payload_ok': payload.get('ok') if isinstance(payload, dict) else None,
|
|
1017
|
+
'payload_error': str(payload.get('error') or payload.get('stderr') or '')[:500] if isinstance(payload, dict) else '',
|
|
1018
|
+
'artifact_summary': summarize_capture_artifacts(payload) if isinstance(payload, dict) else {},
|
|
1019
|
+
},
|
|
1020
|
+
)
|
|
1021
|
+
reason = str(updated.get('reason') or '').strip()
|
|
1022
|
+
suffix = ' Riddle visual_diff fallback ran but did not publish a recognizable numeric delta.'
|
|
1023
|
+
updated['reason'] = (reason + suffix).strip() if reason else suffix.strip()
|
|
1024
|
+
return updated
|
|
1025
|
+
|
|
1026
|
+
|
|
902
1027
|
def visual_delta_applies(verification_mode):
|
|
903
1028
|
return screenshot_required_for_mode(verification_mode)
|
|
904
1029
|
|
|
@@ -1443,6 +1568,7 @@ def build_evidence_bundle(state, results, after_payload, after_observation, requ
|
|
|
1443
1568
|
if visual_delta_applies(state.get('verification_mode'))
|
|
1444
1569
|
else {'status': 'not_applicable', 'passed': None, 'reason': 'Verification mode does not require visual delta gating.'}
|
|
1445
1570
|
)
|
|
1571
|
+
visual_delta = measure_visual_delta_against_baseline(state, results, after_payload, visual_delta)
|
|
1446
1572
|
semantic_context = build_semantic_context(state, results, after_observation, expected_path)
|
|
1447
1573
|
artifact_contract = artifact_contract_for_mode(state.get('verification_mode'))
|
|
1448
1574
|
artifact_production = artifact_production_summary(after_payload, supporting)
|
|
@@ -569,6 +569,50 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
|
|
|
569
569
|
assert unmeasured_delta['diagnostic']['after_screenshot_present'] is True
|
|
570
570
|
assert 'After screenshot artifact is present' in unmeasured_delta['reason']
|
|
571
571
|
|
|
572
|
+
visual_diff_delta = namespace['extract_visual_delta']({
|
|
573
|
+
'ok': True,
|
|
574
|
+
'outputs': [{'name': 'visual-diff.json', 'url': 'https://cdn.example.com/visual-diff.json'}],
|
|
575
|
+
'_artifact_json': {
|
|
576
|
+
'visual-diff.json': {
|
|
577
|
+
'changePercent': '1.45',
|
|
578
|
+
'diffPixelCount': 14094,
|
|
579
|
+
'totalPixels': 972000,
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
})
|
|
583
|
+
assert visual_diff_delta['status'] == 'measured'
|
|
584
|
+
assert visual_diff_delta['passed'] is True
|
|
585
|
+
assert visual_diff_delta['change_percent'] == 1.45
|
|
586
|
+
assert visual_diff_delta['changed_pixels'] == 14094
|
|
587
|
+
|
|
588
|
+
fallback_calls = []
|
|
589
|
+
|
|
590
|
+
def fake_invoke_retry(tool, args, retries=3, timeout=180):
|
|
591
|
+
fallback_calls.append({'tool': tool, 'args': args, 'retries': retries, 'timeout': timeout})
|
|
592
|
+
return {
|
|
593
|
+
'ok': True,
|
|
594
|
+
'visual_diff': {
|
|
595
|
+
'diffPercentage': 0.82,
|
|
596
|
+
'differentPixels': 7970,
|
|
597
|
+
'totalPixels': 972000,
|
|
598
|
+
},
|
|
599
|
+
'outputs': [{'name': 'visual-diff.png', 'url': 'https://cdn.example.com/visual-diff.png'}],
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
namespace['invoke_retry'] = fake_invoke_retry
|
|
603
|
+
namespace['append_capture_diagnostic'] = lambda *args, **kwargs: None
|
|
604
|
+
fallback_delta = namespace['measure_visual_delta_against_baseline'](
|
|
605
|
+
{'verification_mode': 'visual', 'requested_reference': 'before', 'before_cdn': 'https://cdn.example.com/before.png'},
|
|
606
|
+
{'baseline': {'before': {'url': 'https://cdn.example.com/before.png'}}},
|
|
607
|
+
{'ok': True, 'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after.png'}]},
|
|
608
|
+
unmeasured_delta,
|
|
609
|
+
)
|
|
610
|
+
assert fallback_delta['status'] == 'measured'
|
|
611
|
+
assert fallback_delta['source'] == 'riddle_visual_diff'
|
|
612
|
+
assert fallback_delta['comparison']['before_url'] == 'https://cdn.example.com/before.png'
|
|
613
|
+
assert fallback_delta['comparison']['after_url'] == 'https://cdn.example.com/after.png'
|
|
614
|
+
assert fallback_calls[0]['tool'] == 'riddle_visual_diff'
|
|
615
|
+
|
|
572
616
|
canvas_payload = {
|
|
573
617
|
'bodyTextLength': 7,
|
|
574
618
|
'visibleTextSample': 'Luge',
|