Probe geometry in paired captures
This commit is contained in:
1 parent
178102799d
commit
6ab8ed18d5
2 files changed
+321
-11
No files matched your search
+198
-11
@@ -6,7 +6,8 @@ screenshots, records browser versions and timings, and reports raw full-canvas
|
|||||||
pixel diagnostics plus background-tolerant structural-edge diagnostics from
|
pixel diagnostics plus background-tolerant structural-edge diagnostics from
|
||||||
check.py. Browser identity is pinned and both engines record DOM/text
|
check.py. Browser identity is pinned and both engines record DOM/text
|
||||||
fingerprints, viewport geometry, and JS-visible resource-readiness state at
|
fingerprints, viewport geometry, and JS-visible resource-readiness state at
|
||||||
capture time.
|
capture time. Repeatable `--geometry-selector` probes can additionally retain
|
||||||
|
bounded, viewport-relative element rects from that same pre-screenshot sample.
|
||||||
It deliberately emits no aggregate parity verdict. An optional pre-change
|
It deliberately emits no aggregate parity verdict. An optional pre-change
|
||||||
Obscura binary can be captured concurrently so regressions are compared
|
Obscura binary can be captured concurrently so regressions are compared
|
||||||
against the same live-page moment.
|
against the same live-page moment.
|
||||||
@@ -61,6 +62,42 @@ BRAND_PERMUTATIONS = [
|
|||||||
[2, 0, 1],
|
[2, 0, 1],
|
||||||
[2, 1, 0],
|
[2, 1, 0],
|
||||||
]
|
]
|
||||||
|
GEOMETRY_PROBE_RECT_LIMIT = 200
|
||||||
|
|
||||||
|
|
||||||
|
def geometry_probe_javascript(selectors_expression):
|
||||||
|
"""Return bounded, per-selector geometry sampling JavaScript."""
|
||||||
|
return (
|
||||||
|
"const sampleGeometrySelector=selector=>{try{"
|
||||||
|
"const elements=Array.from(document.querySelectorAll(selector));"
|
||||||
|
"const rects=elements.slice(0,"
|
||||||
|
f"{GEOMETRY_PROBE_RECT_LIMIT}"
|
||||||
|
").map((element,index)=>{"
|
||||||
|
"const rect=element.getBoundingClientRect();"
|
||||||
|
"const style=getComputedStyle(element);"
|
||||||
|
"const opacity=Number.parseFloat(style.opacity);"
|
||||||
|
"const clientRectCount=element.getClientRects().length;"
|
||||||
|
"return {index:index,x:rect.left,y:rect.top,"
|
||||||
|
"width:rect.width,height:rect.height,"
|
||||||
|
"visible:clientRectCount>0&&rect.width>0&&rect.height>0"
|
||||||
|
"&&style.display!=='none'&&style.visibility!=='hidden'"
|
||||||
|
"&&style.visibility!=='collapse'"
|
||||||
|
"&&(!Number.isFinite(opacity)||opacity>0),"
|
||||||
|
"client_rect_count:clientRectCount};"
|
||||||
|
"});"
|
||||||
|
"return {selector:selector,valid:true,count:elements.length,"
|
||||||
|
"coordinate_space:'viewport-css-px',"
|
||||||
|
f"rect_limit:{GEOMETRY_PROBE_RECT_LIMIT},"
|
||||||
|
f"rects_truncated:elements.length>{GEOMETRY_PROBE_RECT_LIMIT},"
|
||||||
|
"rects:rects,error:null};"
|
||||||
|
"}catch(error){return {selector:selector,valid:false,count:null,"
|
||||||
|
"coordinate_space:'viewport-css-px',"
|
||||||
|
f"rect_limit:{GEOMETRY_PROBE_RECT_LIMIT},"
|
||||||
|
"rects_truncated:false,rects:[],"
|
||||||
|
"error:{name:error&&error.name?String(error.name):'Error',"
|
||||||
|
"message:error&&error.message?String(error.message):String(error)}};}};"
|
||||||
|
f"const geometryProbes={selectors_expression}.map(sampleGeometrySelector);"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def slug(url):
|
def slug(url):
|
||||||
@@ -104,7 +141,7 @@ def scroll_eval_expression(scroll):
|
|||||||
return obscura_state_eval_expression(scroll)
|
return obscura_state_eval_expression(scroll)
|
||||||
|
|
||||||
|
|
||||||
def obscura_state_eval_expression(scroll):
|
def obscura_state_eval_expression(scroll, geometry_selectors=None):
|
||||||
"""Sample the live page after an optional scroll and immediately before paint."""
|
"""Sample the live page after an optional scroll and immediately before paint."""
|
||||||
scroll_script = ""
|
scroll_script = ""
|
||||||
requested = "null"
|
requested = "null"
|
||||||
@@ -120,6 +157,14 @@ def obscura_state_eval_expression(scroll):
|
|||||||
"window.scrollTo(requestedX,requestedY);"
|
"window.scrollTo(requestedX,requestedY);"
|
||||||
)
|
)
|
||||||
requested = "{x:requestedX,y:requestedY}"
|
requested = "{x:requestedX,y:requestedY}"
|
||||||
|
geometry_setup = ""
|
||||||
|
geometry_result = ""
|
||||||
|
if geometry_selectors:
|
||||||
|
selectors_json = json.dumps(
|
||||||
|
list(geometry_selectors), ensure_ascii=True, separators=(",", ":")
|
||||||
|
)
|
||||||
|
geometry_setup = geometry_probe_javascript(selectors_json)
|
||||||
|
geometry_result = "geometry_probes:geometryProbes,"
|
||||||
return (
|
return (
|
||||||
"(()=>{"
|
"(()=>{"
|
||||||
+ scroll_script
|
+ scroll_script
|
||||||
@@ -136,9 +181,11 @@ def obscura_state_eval_expression(scroll):
|
|||||||
"for(let i=0;i<value.length;i++){h^=value.charCodeAt(i);"
|
"for(let i=0;i<value.length;i++){h^=value.charCodeAt(i);"
|
||||||
"h=Math.imul(h,16777619)}"
|
"h=Math.imul(h,16777619)}"
|
||||||
"return ('00000000'+(h>>>0).toString(16)).slice(-8)};"
|
"return ('00000000'+(h>>>0).toString(16)).slice(-8)};"
|
||||||
"return JSON.stringify({"
|
+ geometry_setup
|
||||||
|
+ "return JSON.stringify({"
|
||||||
"sampled_phase:'immediately-before-screenshot',"
|
"sampled_phase:'immediately-before-screenshot',"
|
||||||
f"requested:{requested},"
|
+ geometry_result
|
||||||
|
+ f"requested:{requested},"
|
||||||
"url:location.href,"
|
"url:location.href,"
|
||||||
"document:{ready_state:document.readyState,"
|
"document:{ready_state:document.readyState,"
|
||||||
"element_count:document.getElementsByTagName('*').length,"
|
"element_count:document.getElementsByTagName('*').length,"
|
||||||
@@ -368,7 +415,15 @@ def probe_obscura_css_media(binary):
|
|||||||
|
|
||||||
|
|
||||||
def capture_obscura(
|
def capture_obscura(
|
||||||
binary, url, screenshot, log, width, height, settle_ms, scroll=None
|
binary,
|
||||||
|
url,
|
||||||
|
screenshot,
|
||||||
|
log,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
settle_ms,
|
||||||
|
scroll=None,
|
||||||
|
geometry_selectors=None,
|
||||||
):
|
):
|
||||||
env = obscura_environment(width, height)
|
env = obscura_environment(width, height)
|
||||||
command = [
|
command = [
|
||||||
@@ -384,7 +439,7 @@ def capture_obscura(
|
|||||||
"--wait",
|
"--wait",
|
||||||
f"{settle_ms / 1000:g}",
|
f"{settle_ms / 1000:g}",
|
||||||
]
|
]
|
||||||
state_expression = obscura_state_eval_expression(scroll)
|
state_expression = obscura_state_eval_expression(scroll, geometry_selectors)
|
||||||
command.extend(["--eval", state_expression])
|
command.extend(["--eval", state_expression])
|
||||||
started = time.time()
|
started = time.time()
|
||||||
try:
|
try:
|
||||||
@@ -476,10 +531,9 @@ def chromium_identity_override(session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def capture_chromium_state(page):
|
def capture_chromium_state(page, geometry_selectors=None):
|
||||||
"""Sample page provenance immediately before the screenshot."""
|
"""Sample page provenance immediately before the screenshot."""
|
||||||
state = page.evaluate(
|
expression = """async () => {
|
||||||
"""async () => {
|
|
||||||
async function sha256(value) {
|
async function sha256(value) {
|
||||||
if (!globalThis.crypto || !crypto.subtle) return null;
|
if (!globalThis.crypto || !crypto.subtle) return null;
|
||||||
const bytes = new TextEncoder().encode(value);
|
const bytes = new TextEncoder().encode(value);
|
||||||
@@ -602,7 +656,25 @@ def capture_chromium_state(page):
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}"""
|
}"""
|
||||||
)
|
if geometry_selectors:
|
||||||
|
expression = expression.replace(
|
||||||
|
"async () => {", "async geometrySelectors => {", 1
|
||||||
|
)
|
||||||
|
expression = expression.replace(
|
||||||
|
" const root = document.documentElement;",
|
||||||
|
geometry_probe_javascript("geometrySelectors")
|
||||||
|
+ " const root = document.documentElement;",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
expression = expression.replace(
|
||||||
|
' return {\n sampled_phase: "immediately-before-screenshot",',
|
||||||
|
' return {\n geometry_probes: geometryProbes,\n'
|
||||||
|
' sampled_phase: "immediately-before-screenshot",',
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
state = page.evaluate(expression, list(geometry_selectors))
|
||||||
|
else:
|
||||||
|
state = page.evaluate(expression)
|
||||||
document_state = state["document"]
|
document_state = state["document"]
|
||||||
if (
|
if (
|
||||||
document_state["outer_html_sha256"] is None
|
document_state["outer_html_sha256"] is None
|
||||||
@@ -711,6 +783,79 @@ def compare_page_states(obscura, chromium):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def compare_geometry_probes(obscura, chromium):
|
||||||
|
"""Report raw per-selector deltas without reducing them to a verdict."""
|
||||||
|
obscura_probes = (obscura or {}).get("geometry_probes") or []
|
||||||
|
chromium_probes = (chromium or {}).get("geometry_probes") or []
|
||||||
|
comparisons = []
|
||||||
|
for index in range(max(len(obscura_probes), len(chromium_probes))):
|
||||||
|
obscura_probe = (
|
||||||
|
obscura_probes[index] if index < len(obscura_probes) else None
|
||||||
|
)
|
||||||
|
chromium_probe = (
|
||||||
|
chromium_probes[index] if index < len(chromium_probes) else None
|
||||||
|
)
|
||||||
|
obscura_rects = (obscura_probe or {}).get("rects") or []
|
||||||
|
chromium_rects = (chromium_probe or {}).get("rects") or []
|
||||||
|
rect_deltas = []
|
||||||
|
for rect_index in range(min(len(obscura_rects), len(chromium_rects))):
|
||||||
|
obscura_rect = obscura_rects[rect_index]
|
||||||
|
chromium_rect = chromium_rects[rect_index]
|
||||||
|
deltas = {}
|
||||||
|
for field in ("x", "y", "width", "height"):
|
||||||
|
left = obscura_rect.get(field)
|
||||||
|
right = chromium_rect.get(field)
|
||||||
|
deltas[field] = (
|
||||||
|
left - right
|
||||||
|
if isinstance(left, (int, float))
|
||||||
|
and isinstance(right, (int, float))
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
rect_deltas.append(
|
||||||
|
{
|
||||||
|
"index": rect_index,
|
||||||
|
"delta": deltas,
|
||||||
|
"visibility": {
|
||||||
|
"obscura": obscura_rect.get("visible"),
|
||||||
|
"chromium": chromium_rect.get("visible"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
obscura_count = (obscura_probe or {}).get("count")
|
||||||
|
chromium_count = (chromium_probe or {}).get("count")
|
||||||
|
comparisons.append(
|
||||||
|
{
|
||||||
|
"index": index,
|
||||||
|
"selector": (
|
||||||
|
(obscura_probe or {}).get("selector")
|
||||||
|
if obscura_probe is not None
|
||||||
|
else (chromium_probe or {}).get("selector")
|
||||||
|
),
|
||||||
|
"valid": {
|
||||||
|
"obscura": (obscura_probe or {}).get("valid"),
|
||||||
|
"chromium": (chromium_probe or {}).get("valid"),
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"obscura": (obscura_probe or {}).get("error"),
|
||||||
|
"chromium": (chromium_probe or {}).get("error"),
|
||||||
|
},
|
||||||
|
"counts": {
|
||||||
|
"obscura": obscura_count,
|
||||||
|
"chromium": chromium_count,
|
||||||
|
"delta": (
|
||||||
|
obscura_count - chromium_count
|
||||||
|
if isinstance(obscura_count, int)
|
||||||
|
and isinstance(chromium_count, int)
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"rects_compared": len(rect_deltas),
|
||||||
|
"rect_deltas": rect_deltas,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return comparisons
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("urls", help="one URL per line; # comments allowed")
|
parser.add_argument("urls", help="one URL per line; # comments allowed")
|
||||||
@@ -724,6 +869,16 @@ def main():
|
|||||||
parser.add_argument("--width", type=int, default=1280)
|
parser.add_argument("--width", type=int, default=1280)
|
||||||
parser.add_argument("--height", type=int, default=1400)
|
parser.add_argument("--height", type=int, default=1400)
|
||||||
parser.add_argument("--settle-ms", type=int, default=3000)
|
parser.add_argument("--settle-ms", type=int, default=3000)
|
||||||
|
parser.add_argument(
|
||||||
|
"--geometry-selector",
|
||||||
|
action="append",
|
||||||
|
default=[],
|
||||||
|
metavar="CSS_SELECTOR",
|
||||||
|
help=(
|
||||||
|
"repeatable selector whose match count and viewport-relative "
|
||||||
|
"bounding rects are sampled in both engines immediately before capture"
|
||||||
|
),
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--scroll-x",
|
"--scroll-x",
|
||||||
type=int,
|
type=int,
|
||||||
@@ -828,6 +983,20 @@ def main():
|
|||||||
},
|
},
|
||||||
"pages": [],
|
"pages": [],
|
||||||
}
|
}
|
||||||
|
if args.geometry_selector:
|
||||||
|
manifest["geometry_probes"] = {
|
||||||
|
"selectors": args.geometry_selector,
|
||||||
|
"coordinate_space": "viewport-css-px",
|
||||||
|
"rect_limit_per_selector": GEOMETRY_PROBE_RECT_LIMIT,
|
||||||
|
"visibility": (
|
||||||
|
"practical rendered-box heuristic: client rect exists, positive "
|
||||||
|
"bounding size, display/visibility permit paint, and opacity > 0"
|
||||||
|
),
|
||||||
|
"comparison_semantics": (
|
||||||
|
"raw per-selector counts, rect deltas in document order, and "
|
||||||
|
"visibility observations; no aggregate parity verdict"
|
||||||
|
),
|
||||||
|
}
|
||||||
manifest["obscura_identity_probe"] = probe_obscura_identity(args.obscura_bin)
|
manifest["obscura_identity_probe"] = probe_obscura_identity(args.obscura_bin)
|
||||||
manifest["obscura_css_media_probe"] = probe_obscura_css_media(args.obscura_bin)
|
manifest["obscura_css_media_probe"] = probe_obscura_css_media(args.obscura_bin)
|
||||||
if args.baseline_bin:
|
if args.baseline_bin:
|
||||||
@@ -915,6 +1084,7 @@ def main():
|
|||||||
args.height,
|
args.height,
|
||||||
args.settle_ms,
|
args.settle_ms,
|
||||||
controlled_scroll,
|
controlled_scroll,
|
||||||
|
args.geometry_selector,
|
||||||
)
|
)
|
||||||
baseline_future = None
|
baseline_future = None
|
||||||
if args.baseline_bin:
|
if args.baseline_bin:
|
||||||
@@ -928,6 +1098,7 @@ def main():
|
|||||||
args.height,
|
args.height,
|
||||||
args.settle_ms,
|
args.settle_ms,
|
||||||
controlled_scroll,
|
controlled_scroll,
|
||||||
|
args.geometry_selector,
|
||||||
)
|
)
|
||||||
|
|
||||||
chrome_started = time.time()
|
chrome_started = time.time()
|
||||||
@@ -948,7 +1119,9 @@ def main():
|
|||||||
page.wait_for_timeout(args.settle_ms)
|
page.wait_for_timeout(args.settle_ms)
|
||||||
chromium_state_ok = False
|
chromium_state_ok = False
|
||||||
try:
|
try:
|
||||||
chromium_state = capture_chromium_state(page)
|
chromium_state = capture_chromium_state(
|
||||||
|
page, args.geometry_selector
|
||||||
|
)
|
||||||
chromium_state["media"]["matches_configured"] = (
|
chromium_state["media"]["matches_configured"] = (
|
||||||
media_matches_configured(chromium_state["media"])
|
media_matches_configured(chromium_state["media"])
|
||||||
)
|
)
|
||||||
@@ -991,6 +1164,13 @@ def main():
|
|||||||
page_result["obscura"].get("state"),
|
page_result["obscura"].get("state"),
|
||||||
chromium_state,
|
chromium_state,
|
||||||
)
|
)
|
||||||
|
if args.geometry_selector:
|
||||||
|
page_result["geometry_probe_comparison"] = (
|
||||||
|
compare_geometry_probes(
|
||||||
|
page_result["obscura"].get("state"),
|
||||||
|
chromium_state,
|
||||||
|
)
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
chrome_ok
|
chrome_ok
|
||||||
and baseline_future
|
and baseline_future
|
||||||
@@ -1002,6 +1182,13 @@ def main():
|
|||||||
chromium_state,
|
chromium_state,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if args.geometry_selector:
|
||||||
|
page_result["baseline_geometry_probe_comparison"] = (
|
||||||
|
compare_geometry_probes(
|
||||||
|
page_result["baseline"].get("state"),
|
||||||
|
chromium_state,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
controlled_scroll is not None
|
controlled_scroll is not None
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -120,5 +121,127 @@ class ControlledScrollTests(unittest.TestCase):
|
|||||||
self.assertEqual(paired_corpus.parse_scroll_y("-20"), -20)
|
self.assertEqual(paired_corpus.parse_scroll_y("-20"), -20)
|
||||||
|
|
||||||
|
|
||||||
|
class GeometryProbeTests(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def chromium_state():
|
||||||
|
return {
|
||||||
|
"document": {
|
||||||
|
"outer_html_sha256": "outer",
|
||||||
|
"visible_text_sha256": "text",
|
||||||
|
},
|
||||||
|
"geometry_probes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakePage:
|
||||||
|
def __init__(self, state):
|
||||||
|
self.state = state
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def evaluate(self, expression, *args):
|
||||||
|
self.calls.append((expression, args))
|
||||||
|
return self.state
|
||||||
|
|
||||||
|
def test_default_state_expressions_do_not_include_probe_work(self):
|
||||||
|
obscura_expression = paired_corpus.obscura_state_eval_expression(None)
|
||||||
|
self.assertNotIn("sampleGeometrySelector", obscura_expression)
|
||||||
|
self.assertNotIn("geometry_probes", obscura_expression)
|
||||||
|
|
||||||
|
page = self.FakePage(self.chromium_state())
|
||||||
|
paired_corpus.capture_chromium_state(page)
|
||||||
|
self.assertEqual(len(page.calls), 1)
|
||||||
|
expression, args = page.calls[0]
|
||||||
|
self.assertEqual(args, ())
|
||||||
|
self.assertNotIn("sampleGeometrySelector", expression)
|
||||||
|
self.assertNotIn("geometry_probes", expression)
|
||||||
|
|
||||||
|
def test_repeatable_selectors_are_passed_safely_in_one_state_expression(self):
|
||||||
|
selectors = ["header nav a", '[data-label="a\\\"b"]', "["]
|
||||||
|
obscura_expression = paired_corpus.obscura_state_eval_expression(
|
||||||
|
(0, 25), selectors
|
||||||
|
)
|
||||||
|
encoded = json.dumps(selectors, ensure_ascii=True, separators=(",", ":"))
|
||||||
|
self.assertIn(encoded, obscura_expression)
|
||||||
|
self.assertIn("sampleGeometrySelector", obscura_expression)
|
||||||
|
self.assertIn("catch(error)", obscura_expression)
|
||||||
|
self.assertIn("geometry_probes:geometryProbes", obscura_expression)
|
||||||
|
|
||||||
|
page = self.FakePage(self.chromium_state())
|
||||||
|
paired_corpus.capture_chromium_state(page, selectors)
|
||||||
|
self.assertEqual(len(page.calls), 1)
|
||||||
|
expression, args = page.calls[0]
|
||||||
|
self.assertEqual(args, (selectors,))
|
||||||
|
self.assertIn("querySelectorAll(selector)", expression)
|
||||||
|
self.assertIn("catch(error)", expression)
|
||||||
|
self.assertIn("geometry_probes: geometryProbes", expression)
|
||||||
|
|
||||||
|
def test_probe_comparison_reports_raw_deltas_and_invalid_errors(self):
|
||||||
|
obscura = {
|
||||||
|
"geometry_probes": [
|
||||||
|
{
|
||||||
|
"selector": ".card",
|
||||||
|
"valid": True,
|
||||||
|
"count": 2,
|
||||||
|
"rects": [
|
||||||
|
{
|
||||||
|
"x": 11,
|
||||||
|
"y": 18,
|
||||||
|
"width": 100,
|
||||||
|
"height": 40,
|
||||||
|
"visible": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"error": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "[",
|
||||||
|
"valid": False,
|
||||||
|
"count": None,
|
||||||
|
"rects": [],
|
||||||
|
"error": {"name": "SyntaxError", "message": "invalid selector"},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
chromium = {
|
||||||
|
"geometry_probes": [
|
||||||
|
{
|
||||||
|
"selector": ".card",
|
||||||
|
"valid": True,
|
||||||
|
"count": 1,
|
||||||
|
"rects": [
|
||||||
|
{
|
||||||
|
"x": 10,
|
||||||
|
"y": 20,
|
||||||
|
"width": 98,
|
||||||
|
"height": 40,
|
||||||
|
"visible": False,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"error": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "[",
|
||||||
|
"valid": False,
|
||||||
|
"count": None,
|
||||||
|
"rects": [],
|
||||||
|
"error": {"name": "SyntaxError", "message": "invalid selector"},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
comparison = paired_corpus.compare_geometry_probes(obscura, chromium)
|
||||||
|
self.assertEqual(comparison[0]["counts"]["delta"], 1)
|
||||||
|
self.assertEqual(
|
||||||
|
comparison[0]["rect_deltas"][0]["delta"],
|
||||||
|
{"x": 1, "y": -2, "width": 2, "height": 0},
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
comparison[0]["rect_deltas"][0]["visibility"],
|
||||||
|
{"obscura": True, "chromium": False},
|
||||||
|
)
|
||||||
|
self.assertEqual(comparison[1]["valid"], {"obscura": False, "chromium": False})
|
||||||
|
self.assertIsNone(comparison[1]["counts"]["delta"])
|
||||||
|
self.assertEqual(comparison[1]["rects_compared"], 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
Reference in new issue
Block a user