personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: fix pinchtab v0.7.x api drifts in verify_browser harness

`make review` was fully broken against pinchtab v0.7.x. Four distinct
drifts, fixed together so the harness becomes deterministic again:

1. Port env var renamed upstream from BRIDGE_PORT to PINCHTAB_PORT —
rename the key in PinchTab.start()'s env dict. Without this,
pinchtab silently bound its default port (9867) and the harness
timed out waiting on /health at 19867.

2. /screenshot now returns raw image/* bytes instead of
{"base64": ...} JSON. PinchTab.screenshot() inspects Content-Type
and returns response.content directly for image/*, falling back
to the legacy base64 path otherwise (forward+backward compat).

3. /health returns status=ok before the default instance is ready —
first POSTs get 503s during warmup. PinchTab.start() now requires
defaultInstance.status == "running" in addition to status=ok
before declaring the bridge live.

4. Pinchtab persists cookies/Local Storage/Session Storage in
~/.pinchtab/profiles/default/ across sessions, so state from one
scenario leaked into every subsequent scenario (and across runs).
PinchTab.start() now rmtrees that profile dir before launching.
extro-linkedin uses a separate `linkedin` profile, so this nuke
is isolated to test state.

Also adds three first-time visual baselines under tests/baselines/visual/:
graph/smoke.jpg, graph/load.jpg, observer/smoke.jpg. Harness has been
dead long enough these scenarios never captured baselines; these are
first-time artifacts to be eyeballed post-ship, not replacements of
known-good images.

Result: `make review` goes from 0/19 to 19/19 PASS, verified from a
deliberately-polluted profile state to confirm the isolation holds.

Refs req_7yqrvikr.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+24 -2
tests/baselines/visual/graph/load.jpg

This is a binary file and will not be displayed.

tests/baselines/visual/graph/smoke.jpg

This is a binary file and will not be displayed.

tests/baselines/visual/observer/smoke.jpg

This is a binary file and will not be displayed.

+24 -2
tests/verify_browser.py
··· 10 10 import json 11 11 import logging 12 12 import os 13 + import shutil 13 14 import signal 14 15 import subprocess 15 16 import time ··· 231 232 232 233 def start(self, timeout: int = 30) -> None: 233 234 """Launch pinchtab and wait for health check.""" 235 + # Pinchtab reads PINCHTAB_PORT (v0.7.x; renamed from BRIDGE_PORT). 236 + # /screenshot may return image/* bytes directly; JSON base64 kept as fallback. 237 + # See pinchtab --help -> ENVIRONMENT. 234 238 env = { 235 239 **os.environ, 236 - "BRIDGE_PORT": str(self.port), 240 + "PINCHTAB_PORT": str(self.port), 237 241 "BRIDGE_HEADLESS": "true", 238 242 } 243 + profile_dir = Path.home() / ".pinchtab" / "profiles" / "default" 244 + if profile_dir.exists(): 245 + # Clear cached default profile for deterministic runs — pinchtab persists 246 + # cookies/storage across sessions. extro-linkedin uses a separate profile 247 + # (~/.pinchtab/profiles/linkedin/) so this nuke is isolated to test state. 248 + try: 249 + shutil.rmtree(profile_dir) 250 + except OSError as exc: 251 + raise RuntimeError( 252 + f"failed to clear pinchtab default profile: {profile_dir}" 253 + ) from exc 239 254 self._stderr_path = f"/tmp/pinchtab-{self.port}.log" 240 255 self._stderr_file = open(self._stderr_path, "w") 241 256 try: ··· 265 280 response = self._session.get(f"{self.base_url}/health", timeout=2) 266 281 if response.status_code == 200: 267 282 health = response.json() 268 - if health.get("status") == "ok": 283 + instance = health.get("defaultInstance") or {} 284 + if ( 285 + health.get("status") == "ok" 286 + and instance.get("status") == "running" 287 + ): 269 288 return 270 289 except requests.ConnectionError: 271 290 pass ··· 313 332 timeout=30, 314 333 ) 315 334 response.raise_for_status() 335 + content_type = response.headers.get("Content-Type", "") 336 + if content_type.startswith("image/"): 337 + return response.content 316 338 payload = response.json() 317 339 return base64.b64decode(payload["base64"]) 318 340