T0.2 - baseline captured; all six rendering defects confirmed present
Runs the app from a clean database, captures the before images, and records which of F1-F6 actually still reproduce. All six do. Rather than eyeball screenshots, each defect is measured in a browser by tests/f_items.py, which reports REPRODUCES / FIXED / INCONCLUSIVE and never a silent pass. That makes it both the wave 0 record and the wave 1-3 regression check: an item is done when its probe flips to FIXED. F1 hero says "Job A", app bar still says "Select a project", no reload F2 "Sign out" spans x382-432 in a 390px viewport - cut in half, 3 rows F3 chrome paints over the logo by 106x32px; .header-left collapses to 0 F4 comments drawer overlaps the header by 380x91px in the standalone creator F5 5 of 5 ENABLED wizard inputs compute #f4f4f4 on #e0e0e0 F6 11 cards in one 5,017px scroll, 0 tabs (review said ~4,700px; it grew) Three probes needed care to avoid reporting a false pass, and the traps are worth knowing before anyone verifies a fix: F1 disappears if localStorage is primed first, because then both sources of truth agree. The probe clears it and drives the real picker. F3 needs a long project name that is long IN THE DATABASE - any page reached with ?project= re-pulls it and overwrites a locally-faked one. It also cannot be measured by comparing .header-left to the chrome: under the long name .header-left (flex:1, min-width:0) collapses to clientWidth 0, so that comparison reports a tidy zero gap while the chrome paints across the logo. It measures against .logo, which is flex-shrink:0. My first two attempts at this probe both reported FIXED for those reasons; the screenshot did not. F5 must ignore genuinely disabled inputs or a fix looks done while real fields stay grey. 14 screenshots, not the 12 the plan asks for, because there are 7 pages (file-map D1). Capture also measures horizontal overflow, which is how BL-001 was found. Tooling: cdp.py gains viewport() and screenshot() - it could do neither, and T0.2 requires 390px and 1440px images. 390px sets the mobile flag rather than just narrowing the window, since every page declares width=device-width and Chrome otherwise lays out at 980px and no media query under test fires. Both new scripts reuse browser_check.py's seed() and start_server() instead of growing a second fixture. Existing browser_check still passes 71/71. No application code changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
tests/cdp.py
42
tests/cdp.py
@@ -296,6 +296,48 @@ class Page:
|
||||
self.eval(f"document.dispatchEvent(new KeyboardEvent('keydown',{{key:{name!r}}}))")
|
||||
time.sleep(settle)
|
||||
|
||||
def viewport(self, width, height=900, mobile=False, scale=1):
|
||||
"""Emulate a viewport width. The two that matter are 390 (the gloved-hands
|
||||
field tablet, where the worst rendering was found) and 1440 (the desk).
|
||||
|
||||
`mobile` also sets the mobile flag and a meta-viewport-aware layout, which
|
||||
is what a tablet actually reports; without it a 390px-wide desktop window
|
||||
is not the same test."""
|
||||
self.ws.call("Emulation.setDeviceMetricsOverride", {
|
||||
"width": int(width), "height": int(height),
|
||||
"deviceScaleFactor": scale, "mobile": bool(mobile),
|
||||
})
|
||||
time.sleep(0.35) # let media queries and reflow settle
|
||||
self.ws.drain(0.2)
|
||||
return self
|
||||
|
||||
def screenshot(self, path, full_page=True):
|
||||
"""Write a PNG to `path`, creating parent directories. Returns the path.
|
||||
|
||||
full_page captures the whole document rather than the visible box: the
|
||||
creator form is roughly 4,700px tall and a viewport-sized shot of it would
|
||||
hide the thing being compared. Chrome refuses beyond 16,384px, so an
|
||||
over-tall page is clamped rather than failing the capture."""
|
||||
params = {"format": "png"}
|
||||
if full_page:
|
||||
try:
|
||||
m = self.ws.call("Page.getLayoutMetrics")
|
||||
size = m.get("cssContentSize") or m.get("contentSize") or {}
|
||||
w, h = size.get("width"), size.get("height")
|
||||
if w and h:
|
||||
params["clip"] = {"x": 0, "y": 0, "width": w,
|
||||
"height": min(h, 16384), "scale": 1}
|
||||
params["captureBeyondViewport"] = True
|
||||
except (RuntimeError, TimeoutError):
|
||||
pass # fall back to a viewport-sized shot
|
||||
shot = self.ws.call("Page.captureScreenshot", params, timeout=45)
|
||||
parent = os.path.dirname(os.path.abspath(path))
|
||||
if parent:
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
with open(path, "wb") as fh:
|
||||
fh.write(base64.b64decode(shot["data"]))
|
||||
return path
|
||||
|
||||
def js_errors(self):
|
||||
"""Everything that means 'this page did not boot cleanly': uncaught
|
||||
exceptions, console.error calls, and browser-logged errors.
|
||||
|
||||
Reference in New Issue
Block a user