@riddledc/riddle-proof 0.7.81 → 0.7.83
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/README.md +31 -8
- package/package.json +1 -1
- package/runtime/lib/verify.py +188 -4
- package/runtime/tests/recon_verify_smoke.py +32 -0
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ without depending on another plugin runtime.
|
|
|
59
59
|
## Durable Loop CLI
|
|
60
60
|
|
|
61
61
|
The package publishes `riddle-proof-loop` as a host-agnostic runner surface for
|
|
62
|
-
|
|
62
|
+
CLI-style and sole-agent testing:
|
|
63
63
|
|
|
64
64
|
```sh
|
|
65
65
|
riddle-proof-loop run --request-json request.json --checkpoint-mode yield
|
|
@@ -88,9 +88,9 @@ Flag-based `respond` refuses to submit generated placeholder payloads. If the
|
|
|
88
88
|
checkpoint template includes `TODO` fields, provide a real `--payload-json`
|
|
89
89
|
file/object before resuming the run.
|
|
90
90
|
|
|
91
|
-
`--agent local` is the generic CLI executor slot.
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
`--agent local` is the generic CLI executor slot. A host can wire that slot to
|
|
92
|
+
Codex, Claude Code, another local CLI, or a managed worker without changing the
|
|
93
|
+
checkpoint, evidence, or proof-assessment contract.
|
|
94
94
|
|
|
95
95
|
Visual/UI proof runs can pass `viewport_matrix` / `viewport_matrix_json` to the
|
|
96
96
|
base loop. The runtime records the requested matrix, captures per-viewport
|
|
@@ -100,9 +100,11 @@ viewport evidence as incomplete capture evidence.
|
|
|
100
100
|
|
|
101
101
|
Measured visual deltas still require numeric before/after evidence. For small
|
|
102
102
|
targeted copy or UI changes, the visual gate can use a lower targeted threshold
|
|
103
|
-
only when route/text semantics match the requested change.
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
only when route/text semantics match the requested change. When before/after
|
|
104
|
+
image artifacts can be compared directly, the runtime also records the changed
|
|
105
|
+
region bounding box and refuses the targeted threshold if that exact region is
|
|
106
|
+
too broad for a localized UI change. Missing or unmeasured visual deltas
|
|
107
|
+
continue through evidence recovery instead of being treated as a passing proof.
|
|
106
108
|
|
|
107
109
|
## CI / Profile Mode
|
|
108
110
|
|
|
@@ -674,7 +676,7 @@ terminal proof failure. If `--wait` exhausts its attempts before a terminal job
|
|
|
674
676
|
status, the command exits non-zero and the result explains the last observed
|
|
675
677
|
status and `submitted_at` state.
|
|
676
678
|
|
|
677
|
-
## OpenClaw
|
|
679
|
+
## Base vs OpenClaw Wrapper Boundary
|
|
678
680
|
|
|
679
681
|
`@riddledc/riddle-proof/openclaw` translates OpenClaw Riddle Proof tool params
|
|
680
682
|
into generic `RiddleProofRunParams`.
|
|
@@ -687,6 +689,27 @@ Generic authenticated proof inputs are preserved as pass-through JSON strings:
|
|
|
687
689
|
those for public integrations; reserve `use_auth` for a configured, site-specific
|
|
688
690
|
auth helper.
|
|
689
691
|
|
|
692
|
+
Keep behavior in base `@riddledc/riddle-proof` when it changes the portable run
|
|
693
|
+
contract or evidence semantics:
|
|
694
|
+
|
|
695
|
+
- checkpoint packets and responses
|
|
696
|
+
- profile, audit/no-diff, and proof-of-change run modes
|
|
697
|
+
- viewport matrices and per-viewport artifact metadata
|
|
698
|
+
- visual-delta thresholds, changed-region evidence, and evidence recovery gates
|
|
699
|
+
- run state, run cards, result objects, proof sessions, and evidence bundles
|
|
700
|
+
- generic auth/header/local storage inputs
|
|
701
|
+
|
|
702
|
+
Keep behavior in the OpenClaw wrapper when it only concerns OpenClaw hosting or
|
|
703
|
+
presentation:
|
|
704
|
+
|
|
705
|
+
- OpenClaw tool registration and schema wording
|
|
706
|
+
- Discord thread/status formatting
|
|
707
|
+
- OpenClaw workflow labels such as interactive, background PR, and continuous
|
|
708
|
+
- PR handoff/status phrasing such as `ship_mode=none`
|
|
709
|
+
- checkpoint packet display and review UX
|
|
710
|
+
- configured site-specific auth helpers
|
|
711
|
+
- wrapper deployment, hot reload, and notification adapters
|
|
712
|
+
|
|
690
713
|
The adapter does not invoke another OpenClaw plugin and does not supply a
|
|
691
714
|
coding agent. It is the reusable mapping layer a future OpenClaw wrapper can
|
|
692
715
|
call before handing the request to its configured implementation, judge, ship,
|
package/package.json
CHANGED
package/runtime/lib/verify.py
CHANGED
|
@@ -61,6 +61,7 @@ MIN_VISUAL_DELTA_PERCENT = 0.5
|
|
|
61
61
|
MIN_VISUAL_CHANGED_PIXELS = 5000
|
|
62
62
|
TARGETED_VISUAL_DELTA_PERCENT = 0.02
|
|
63
63
|
TARGETED_VISUAL_CHANGED_PIXELS = 250
|
|
64
|
+
TARGETED_CHANGED_REGION_MAX_AREA_PERCENT = 20
|
|
64
65
|
TARGETED_SEMANTIC_STOPWORDS = {
|
|
65
66
|
'about', 'after', 'agent', 'again', 'before', 'browser', 'button',
|
|
66
67
|
'change', 'changed', 'click', 'copy', 'delta', 'does', 'from', 'into',
|
|
@@ -95,6 +96,18 @@ VISUAL_TOTAL_PIXEL_KEYS = {
|
|
|
95
96
|
}
|
|
96
97
|
VISUAL_WIDTH_KEYS = {'width', 'image_width', 'screenshot_width'}
|
|
97
98
|
VISUAL_HEIGHT_KEYS = {'height', 'image_height', 'screenshot_height'}
|
|
99
|
+
VISUAL_CHANGED_REGION_KEYS = {
|
|
100
|
+
'changed_region', 'changedregion', 'diff_region', 'diffregion',
|
|
101
|
+
'difference_region', 'differenceregion', 'changed_bounds',
|
|
102
|
+
'changedbounds', 'diff_bounds', 'diffbounds', 'difference_bounds',
|
|
103
|
+
'differencebounds', 'bounding_box', 'boundingbox', 'bbox', 'bounds',
|
|
104
|
+
}
|
|
105
|
+
VISUAL_REGION_X_KEYS = {'x', 'left', 'min_x', 'minx'}
|
|
106
|
+
VISUAL_REGION_Y_KEYS = {'y', 'top', 'min_y', 'miny'}
|
|
107
|
+
VISUAL_REGION_WIDTH_KEYS = {'width', 'w'}
|
|
108
|
+
VISUAL_REGION_HEIGHT_KEYS = {'height', 'h'}
|
|
109
|
+
VISUAL_REGION_RIGHT_KEYS = {'right', 'max_x', 'maxx'}
|
|
110
|
+
VISUAL_REGION_BOTTOM_KEYS = {'bottom', 'max_y', 'maxy'}
|
|
98
111
|
|
|
99
112
|
|
|
100
113
|
def capture_script_saves_screenshot(script):
|
|
@@ -1009,9 +1022,137 @@ def visual_delta_semantic_support(state=None, semantic_context=None):
|
|
|
1009
1022
|
}
|
|
1010
1023
|
|
|
1011
1024
|
|
|
1012
|
-
def
|
|
1025
|
+
def normalize_changed_region(value, total_pixels=None, changed_pixels=None, image_width=None, image_height=None, dimension_mismatch=False):
|
|
1026
|
+
if not isinstance(value, dict):
|
|
1027
|
+
return None
|
|
1028
|
+
|
|
1029
|
+
x = find_metric_value(value, VISUAL_REGION_X_KEYS)
|
|
1030
|
+
y = find_metric_value(value, VISUAL_REGION_Y_KEYS)
|
|
1031
|
+
width = find_metric_value(value, VISUAL_REGION_WIDTH_KEYS)
|
|
1032
|
+
height = find_metric_value(value, VISUAL_REGION_HEIGHT_KEYS)
|
|
1033
|
+
right = find_metric_value(value, VISUAL_REGION_RIGHT_KEYS)
|
|
1034
|
+
bottom = find_metric_value(value, VISUAL_REGION_BOTTOM_KEYS)
|
|
1035
|
+
|
|
1036
|
+
if width is None and x is not None and right is not None and right > x:
|
|
1037
|
+
width = right - x
|
|
1038
|
+
if height is None and y is not None and bottom is not None and bottom > y:
|
|
1039
|
+
height = bottom - y
|
|
1040
|
+
if x is None and width is not None and right is not None:
|
|
1041
|
+
x = right - width
|
|
1042
|
+
if y is None and height is not None and bottom is not None:
|
|
1043
|
+
y = bottom - height
|
|
1044
|
+
|
|
1045
|
+
if x is None or y is None or width is None or height is None:
|
|
1046
|
+
return None
|
|
1047
|
+
if width <= 0 or height <= 0:
|
|
1048
|
+
return None
|
|
1049
|
+
|
|
1050
|
+
x = int(round(x))
|
|
1051
|
+
y = int(round(y))
|
|
1052
|
+
width = int(round(width))
|
|
1053
|
+
height = int(round(height))
|
|
1054
|
+
area_pixels = width * height
|
|
1055
|
+
changed = metric_number(changed_pixels)
|
|
1056
|
+
total = metric_number(total_pixels)
|
|
1057
|
+
image_w = metric_number(image_width)
|
|
1058
|
+
image_h = metric_number(image_height)
|
|
1059
|
+
area_percent = (area_pixels / total) * 100 if total and total > 0 else None
|
|
1060
|
+
width_percent = (width / image_w) * 100 if image_w and image_w > 0 else None
|
|
1061
|
+
height_percent = (height / image_h) * 100 if image_h and image_h > 0 else None
|
|
1062
|
+
density = (changed / area_pixels) if changed is not None and area_pixels > 0 else None
|
|
1063
|
+
absolute_small_region = area_pixels <= max(TARGETED_VISUAL_CHANGED_PIXELS, int(changed or 0), 1)
|
|
1064
|
+
percent_localized = area_percent is not None and area_percent <= TARGETED_CHANGED_REGION_MAX_AREA_PERCENT
|
|
1065
|
+
localized = bool(not dimension_mismatch and (absolute_small_region or percent_localized))
|
|
1066
|
+
if dimension_mismatch:
|
|
1067
|
+
classification = 'geometry_change'
|
|
1068
|
+
elif absolute_small_region or (area_percent is not None and area_percent <= 1):
|
|
1069
|
+
classification = 'point_or_icon'
|
|
1070
|
+
elif localized:
|
|
1071
|
+
classification = 'localized'
|
|
1072
|
+
else:
|
|
1073
|
+
classification = 'broad'
|
|
1074
|
+
|
|
1075
|
+
return {
|
|
1076
|
+
'present': True,
|
|
1077
|
+
'x': x,
|
|
1078
|
+
'y': y,
|
|
1079
|
+
'width': width,
|
|
1080
|
+
'height': height,
|
|
1081
|
+
'right': x + width,
|
|
1082
|
+
'bottom': y + height,
|
|
1083
|
+
'area_pixels': area_pixels,
|
|
1084
|
+
'area_percent': round(area_percent, 4) if area_percent is not None else None,
|
|
1085
|
+
'width_percent': round(width_percent, 4) if width_percent is not None else None,
|
|
1086
|
+
'height_percent': round(height_percent, 4) if height_percent is not None else None,
|
|
1087
|
+
'changed_pixel_density': round(density, 6) if density is not None else None,
|
|
1088
|
+
'localized_for_targeted_change': localized,
|
|
1089
|
+
'classification': classification,
|
|
1090
|
+
'dimension_mismatch': bool(dimension_mismatch),
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
def find_changed_region(value, total_pixels=None, changed_pixels=None, image_width=None, image_height=None, depth=0):
|
|
1095
|
+
if depth > 7:
|
|
1096
|
+
return None
|
|
1097
|
+
if isinstance(value, dict):
|
|
1098
|
+
for raw_key, raw_value in value.items():
|
|
1099
|
+
if normalize_metric_key(raw_key) in VISUAL_CHANGED_REGION_KEYS:
|
|
1100
|
+
region = normalize_changed_region(
|
|
1101
|
+
raw_value,
|
|
1102
|
+
total_pixels=total_pixels,
|
|
1103
|
+
changed_pixels=changed_pixels,
|
|
1104
|
+
image_width=image_width,
|
|
1105
|
+
image_height=image_height,
|
|
1106
|
+
)
|
|
1107
|
+
if region is not None:
|
|
1108
|
+
return region
|
|
1109
|
+
for raw_value in value.values():
|
|
1110
|
+
region = find_changed_region(raw_value, total_pixels, changed_pixels, image_width, image_height, depth + 1)
|
|
1111
|
+
if region is not None:
|
|
1112
|
+
return region
|
|
1113
|
+
elif isinstance(value, list):
|
|
1114
|
+
for item in value[:60]:
|
|
1115
|
+
region = find_changed_region(item, total_pixels, changed_pixels, image_width, image_height, depth + 1)
|
|
1116
|
+
if region is not None:
|
|
1117
|
+
return region
|
|
1118
|
+
return None
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def visual_delta_region_support(changed_region=None):
|
|
1122
|
+
if not isinstance(changed_region, dict) or not changed_region.get('present'):
|
|
1123
|
+
return {
|
|
1124
|
+
'available': False,
|
|
1125
|
+
'supported': None,
|
|
1126
|
+
'reason': 'no changed-region metadata was available for targeted visual-delta localization',
|
|
1127
|
+
}
|
|
1128
|
+
if changed_region.get('dimension_mismatch'):
|
|
1129
|
+
return {
|
|
1130
|
+
'available': True,
|
|
1131
|
+
'supported': False,
|
|
1132
|
+
'reason': 'changed-region metadata reports a screenshot geometry change',
|
|
1133
|
+
'classification': changed_region.get('classification'),
|
|
1134
|
+
}
|
|
1135
|
+
if changed_region.get('localized_for_targeted_change') is True:
|
|
1136
|
+
return {
|
|
1137
|
+
'available': True,
|
|
1138
|
+
'supported': True,
|
|
1139
|
+
'reason': 'changed pixels are localized enough for a targeted visual change',
|
|
1140
|
+
'classification': changed_region.get('classification'),
|
|
1141
|
+
'area_percent': changed_region.get('area_percent'),
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
'available': True,
|
|
1145
|
+
'supported': False,
|
|
1146
|
+
'reason': 'changed pixels are too broad for a targeted visual change',
|
|
1147
|
+
'classification': changed_region.get('classification'),
|
|
1148
|
+
'area_percent': changed_region.get('area_percent'),
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def visual_delta_thresholds_for_context(state=None, semantic_context=None, changed_region=None):
|
|
1013
1153
|
semantic = visual_delta_semantic_support(state, semantic_context)
|
|
1014
|
-
|
|
1154
|
+
localization = visual_delta_region_support(changed_region)
|
|
1155
|
+
if semantic.get('supported') and localization.get('supported') is not False:
|
|
1015
1156
|
return {
|
|
1016
1157
|
'mode': 'targeted_semantic',
|
|
1017
1158
|
'min_change_percent': TARGETED_VISUAL_DELTA_PERCENT,
|
|
@@ -1019,12 +1160,14 @@ def visual_delta_thresholds_for_context(state=None, semantic_context=None):
|
|
|
1019
1160
|
'default_min_change_percent': MIN_VISUAL_DELTA_PERCENT,
|
|
1020
1161
|
'default_min_changed_pixels': MIN_VISUAL_CHANGED_PIXELS,
|
|
1021
1162
|
'semantic_support': semantic,
|
|
1163
|
+
'localization_support': localization,
|
|
1022
1164
|
}
|
|
1023
1165
|
return {
|
|
1024
1166
|
'mode': 'default',
|
|
1025
1167
|
'min_change_percent': MIN_VISUAL_DELTA_PERCENT,
|
|
1026
1168
|
'min_changed_pixels': MIN_VISUAL_CHANGED_PIXELS,
|
|
1027
1169
|
'semantic_support': semantic,
|
|
1170
|
+
'localization_support': localization,
|
|
1028
1171
|
}
|
|
1029
1172
|
|
|
1030
1173
|
|
|
@@ -1088,6 +1231,14 @@ def extract_visual_delta(payload, state=None, semantic_context=None):
|
|
|
1088
1231
|
if percent is None and changed_pixels is not None and total_pixels:
|
|
1089
1232
|
percent = (changed_pixels / total_pixels) * 100
|
|
1090
1233
|
|
|
1234
|
+
changed_region = None
|
|
1235
|
+
for candidate in candidates:
|
|
1236
|
+
if candidate is None:
|
|
1237
|
+
continue
|
|
1238
|
+
changed_region = find_changed_region(candidate, total_pixels, changed_pixels, width, height)
|
|
1239
|
+
if changed_region is not None:
|
|
1240
|
+
break
|
|
1241
|
+
|
|
1091
1242
|
if percent is None and changed_pixels is None:
|
|
1092
1243
|
has_artifacts = bool(
|
|
1093
1244
|
screenshot_url
|
|
@@ -1129,7 +1280,7 @@ def extract_visual_delta(payload, state=None, semantic_context=None):
|
|
|
1129
1280
|
},
|
|
1130
1281
|
}
|
|
1131
1282
|
|
|
1132
|
-
thresholds = visual_delta_thresholds_for_context(state, semantic_context)
|
|
1283
|
+
thresholds = visual_delta_thresholds_for_context(state, semantic_context, changed_region)
|
|
1133
1284
|
min_percent = thresholds['min_change_percent']
|
|
1134
1285
|
min_pixels = thresholds['min_changed_pixels']
|
|
1135
1286
|
percent_pass = percent is not None and percent >= min_percent
|
|
@@ -1145,6 +1296,8 @@ def extract_visual_delta(payload, state=None, semantic_context=None):
|
|
|
1145
1296
|
'min_changed_pixels': min_pixels,
|
|
1146
1297
|
'threshold_mode': thresholds.get('mode'),
|
|
1147
1298
|
'semantic_support': thresholds.get('semantic_support'),
|
|
1299
|
+
'localization_support': thresholds.get('localization_support'),
|
|
1300
|
+
'changed_region': changed_region,
|
|
1148
1301
|
'reason': visual_delta_pass_reason('Measured visual delta', passed, thresholds),
|
|
1149
1302
|
}
|
|
1150
1303
|
|
|
@@ -1295,6 +1448,14 @@ def compare_rgba_images(before_image, after_image, threshold=10):
|
|
|
1295
1448
|
overlap_height = min(before_height, after_height)
|
|
1296
1449
|
total_pixels = max(before_width * before_height, after_width * after_height)
|
|
1297
1450
|
changed_pixels = total_pixels - (overlap_width * overlap_height)
|
|
1451
|
+
min_x = min_y = None
|
|
1452
|
+
max_x = max_y = None
|
|
1453
|
+
dimension_mismatch = before_width != after_width or before_height != after_height
|
|
1454
|
+
if dimension_mismatch and total_pixels > 0:
|
|
1455
|
+
min_x = 0
|
|
1456
|
+
min_y = 0
|
|
1457
|
+
max_x = max(before_width, after_width) - 1
|
|
1458
|
+
max_y = max(before_height, after_height) - 1
|
|
1298
1459
|
for y in range(overlap_height):
|
|
1299
1460
|
before_row = y * before_width * 4
|
|
1300
1461
|
after_row = y * after_width * 4
|
|
@@ -1308,11 +1469,31 @@ def compare_rgba_images(before_image, after_image, threshold=10):
|
|
|
1308
1469
|
abs(before_rgba[b + 3] - after_rgba[a + 3]),
|
|
1309
1470
|
) > threshold:
|
|
1310
1471
|
changed_pixels += 1
|
|
1472
|
+
min_x = x if min_x is None else min(min_x, x)
|
|
1473
|
+
min_y = y if min_y is None else min(min_y, y)
|
|
1474
|
+
max_x = x if max_x is None else max(max_x, x)
|
|
1475
|
+
max_y = y if max_y is None else max(max_y, y)
|
|
1311
1476
|
change_percent = (changed_pixels / total_pixels) * 100 if total_pixels else None
|
|
1477
|
+
changed_region = None
|
|
1478
|
+
if min_x is not None and min_y is not None and max_x is not None and max_y is not None:
|
|
1479
|
+
changed_region = normalize_changed_region(
|
|
1480
|
+
{
|
|
1481
|
+
'x': min_x,
|
|
1482
|
+
'y': min_y,
|
|
1483
|
+
'width': max_x - min_x + 1,
|
|
1484
|
+
'height': max_y - min_y + 1,
|
|
1485
|
+
},
|
|
1486
|
+
total_pixels=total_pixels,
|
|
1487
|
+
changed_pixels=changed_pixels,
|
|
1488
|
+
image_width=max(before_width, after_width),
|
|
1489
|
+
image_height=max(before_height, after_height),
|
|
1490
|
+
dimension_mismatch=dimension_mismatch,
|
|
1491
|
+
)
|
|
1312
1492
|
return {
|
|
1313
1493
|
'changed_pixels': changed_pixels,
|
|
1314
1494
|
'total_pixels': total_pixels,
|
|
1315
1495
|
'change_percent': change_percent,
|
|
1496
|
+
'changed_region': changed_region,
|
|
1316
1497
|
'before_width': before_width,
|
|
1317
1498
|
'before_height': before_height,
|
|
1318
1499
|
'after_width': after_width,
|
|
@@ -1338,7 +1519,8 @@ def measure_visual_delta_from_image_artifacts(before_url, after_url, state=None,
|
|
|
1338
1519
|
changed_pixels = comparison.get('changed_pixels')
|
|
1339
1520
|
total_pixels = comparison.get('total_pixels')
|
|
1340
1521
|
percent = comparison.get('change_percent')
|
|
1341
|
-
|
|
1522
|
+
changed_region = comparison.get('changed_region')
|
|
1523
|
+
thresholds = visual_delta_thresholds_for_context(state, semantic_context, changed_region)
|
|
1342
1524
|
min_percent = thresholds['min_change_percent']
|
|
1343
1525
|
min_pixels = thresholds['min_changed_pixels']
|
|
1344
1526
|
percent_pass = percent is not None and percent >= min_percent
|
|
@@ -1354,6 +1536,8 @@ def measure_visual_delta_from_image_artifacts(before_url, after_url, state=None,
|
|
|
1354
1536
|
'min_changed_pixels': min_pixels,
|
|
1355
1537
|
'threshold_mode': thresholds.get('mode'),
|
|
1356
1538
|
'semantic_support': thresholds.get('semantic_support'),
|
|
1539
|
+
'localization_support': thresholds.get('localization_support'),
|
|
1540
|
+
'changed_region': changed_region,
|
|
1357
1541
|
'source': 'riddle_artifact_image_diff',
|
|
1358
1542
|
'reason': visual_delta_pass_reason('Measured visual delta from before/after screenshot artifacts', passed, thresholds),
|
|
1359
1543
|
'comparison': {
|
|
@@ -663,6 +663,33 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
|
|
|
663
663
|
assert generic_page_token_delta['passed'] is False
|
|
664
664
|
assert generic_page_token_delta['threshold_mode'] == 'default'
|
|
665
665
|
|
|
666
|
+
broad_region_targeted_delta = namespace['extract_visual_delta'](
|
|
667
|
+
{
|
|
668
|
+
'ok': True,
|
|
669
|
+
'result': {
|
|
670
|
+
'proofEvidence': {
|
|
671
|
+
'change_pct': '0.0487',
|
|
672
|
+
'changed_pixels': 2351,
|
|
673
|
+
'total_pixels': 1152000,
|
|
674
|
+
'width': 1280,
|
|
675
|
+
'height': 900,
|
|
676
|
+
'changed_region': {'x': 0, 'y': 0, 'width': 1280, 'height': 720},
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
'change_request': 'Add the Riddle Proof recovery smoke badge',
|
|
682
|
+
'success_criteria': 'The recovery smoke badge is visible after verify.',
|
|
683
|
+
'parsed_assertions': [{'kind': 'text_visible', 'text': 'recovery smoke badge'}],
|
|
684
|
+
},
|
|
685
|
+
{'after': {'visible_text_sample': 'Home page Riddle Proof recovery smoke badge'}},
|
|
686
|
+
)
|
|
687
|
+
assert broad_region_targeted_delta['status'] == 'measured'
|
|
688
|
+
assert broad_region_targeted_delta['passed'] is False
|
|
689
|
+
assert broad_region_targeted_delta['threshold_mode'] == 'default'
|
|
690
|
+
assert broad_region_targeted_delta['changed_region']['classification'] == 'broad'
|
|
691
|
+
assert broad_region_targeted_delta['localization_support']['supported'] is False
|
|
692
|
+
|
|
666
693
|
unmeasured_delta = namespace['extract_visual_delta']({
|
|
667
694
|
'ok': True,
|
|
668
695
|
'screenshots': [{'name': 'after-proof.png', 'url': 'https://cdn.example.com/after-proof.png'}],
|
|
@@ -738,6 +765,11 @@ def run_verify_quality_ignores_proof_telemetry_console_text():
|
|
|
738
765
|
assert artifact_image_delta['source'] == 'riddle_artifact_image_diff'
|
|
739
766
|
assert artifact_image_delta['changed_pixels'] == 1
|
|
740
767
|
assert artifact_image_delta['change_percent'] == 50
|
|
768
|
+
assert artifact_image_delta['changed_region']['x'] == 1
|
|
769
|
+
assert artifact_image_delta['changed_region']['y'] == 0
|
|
770
|
+
assert artifact_image_delta['changed_region']['width'] == 1
|
|
771
|
+
assert artifact_image_delta['changed_region']['height'] == 1
|
|
772
|
+
assert artifact_image_delta['changed_region']['localized_for_targeted_change'] is True
|
|
741
773
|
|
|
742
774
|
fallback_calls = []
|
|
743
775
|
|