Skip to content

Use CDP for standalone render smoke - #402

Open
Alek99 wants to merge 8 commits into
mainfrom
agent/fix-394-cdp-render-smoke
Open

Use CDP for standalone render smoke#402
Alek99 wants to merge 8 commits into
mainfrom
agent/fix-394-cdp-render-smoke

Conversation

@Alek99

@Alek99 Alek99 commented Jul 31, 2026

Copy link
Copy Markdown
Member

What changed

  • Replaced Chromium's raw --dump-dom --virtual-time-budget subprocess path with the repository's CDP-based ChromiumSession harness.
  • Kept the 300-second overall deadline while polling the page sentinel without treating an individual poll as the entire run timeout.
  • Close the browser context deterministically and document the runner behavior.

Why

On macOS arm64, the raw headless Chrome process could stay alive until the five-minute timeout even after the page had completed. The CDP harness already provides bounded lifecycle control elsewhere in the repository.

Impact

The no-NumPy standalone smoke completes reliably while preserving rendering, interaction, context-loss, DPR, and cleanup coverage.

Validation

  • .venv/bin/python scripts/render_smoke_nonumpy.py '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' — passed all assertions, including 3 context-loss cycles
  • .venv/bin/python scripts/check_python_floor.py
  • .venv/bin/ruff check scripts/render_smoke_nonumpy.py

Fixes #394

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved Chromium startup, WebSocket communication, and page-session handling with consistent timeouts and deadline enforcement.
    • Ensured failed launches, connections, and page setup operations clean up resources reliably.
    • Improved recovery from browser, socket, and DevTools communication failures.
    • Browser shutdown and temporary-file cleanup are now bounded and more reliable.
    • Render smoke checks now use a deterministic page probe with clearer timeout and page-error reporting.
  • Documentation

    • Clarified that render smoke checks stop the browser after the page reports its result.
  • Tests

    • Expanded coverage for timeout handling, failure recovery, cleanup, and deterministic render smoke checks.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a7adf3a-280b-416b-a246-ccffaaab0d03

📥 Commits

Reviewing files that changed from the base of the PR and between 9eee9da and 9175128.

📒 Files selected for processing (3)
  • python/xy/_chromium.py
  • tests/test_chromium_session.py
  • tests/test_render_smoke_nonumpy.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/xy/_chromium.py

📝 Walkthrough

Walkthrough

Chromium sessions now enforce shared launch, connection, page-setup, and shutdown deadlines. Failed WebSocket, browser, target, and file operations clean up resources. The render smoke check now uses CDP through ChromiumSession and validates page results with bounded polling.

Changes

Chromium timeout and cleanup flow

Layer / File(s) Summary
WebSocket and CDP deadline handling
python/xy/_chromium.py, tests/test_chromium_session.py
WebSocket frame reads and CDP calls now use deadlines. Failed sends, receives, and handshakes close or invalidate the session.
Chromium and page-session cleanup
python/xy/_chromium.py, tests/test_chromium_session.py
Browser launch, endpoint polling, page setup, process reaping, shutdown, and temporary-directory cleanup now use bounded failure handling.
CDP-based render smoke probe
scripts/render_smoke_nonumpy.py, tests/test_render_smoke_nonumpy.py, spec/process/contributing.md
The smoke check uses ChromiumSession, a shared timeout, load waiting, title polling, and documented early browser shutdown.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant run_probe
  participant ChromiumSession
  participant PageSession
  run_probe->>ChromiumSession: launch with shared deadline
  run_probe->>PageSession: navigate and wait for load
  PageSession-->>run_probe: return document.title
  run_probe->>ChromiumSession: stop browser after result publication
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: replacing the standalone render smoke subprocess flow with CDP.
Linked Issues check ✅ Passed The PR addresses issue #394 with CDP execution, shared deadlines, browser cleanup, preserved smoke coverage, and documented runner behavior.
Out of Scope Changes check ✅ Passed The Chromium session hardening, smoke-script migration, documentation, and tests directly support the linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/fix-394-cdp-render-smoke

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 109 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing agent/fix-394-cdp-render-smoke (9175128) with main (d505ef5)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99
Alek99 marked this pull request as ready for review July 31, 2026 03:58

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread scripts/render_smoke_nonumpy.py Outdated
Comment thread scripts/render_smoke_nonumpy.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/_chromium.py Outdated
Comment thread python/xy/_chromium.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/_chromium.py Outdated
Comment thread python/xy/_chromium.py Outdated
Comment thread python/xy/_chromium.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/_chromium.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/_chromium.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/test_render_smoke_nonumpy.py (2)

84-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering the exceptionDetails branch.

The fake _call always returns a clean result, so run_probe never reaches the RuntimeError(f"probe page exception: ...") branch in scripts/render_smoke_nonumpy.py line 95. A third test that returns {"exceptionDetails": {...}} would pin that failure path, which is how a broken probe page surfaces to the operator.

💚 Sketch of the added test
def test_run_probe_reports_a_page_exception(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    clock = _Clock()
    page_path = tmp_path / "probe.html"

    class ThrowingSession:
        def __init__(self, _executable: str, **_kwargs) -> None:
            pass

        def __enter__(self):
            return self

        def __exit__(self, _exc_type, _exc, _tb) -> None:  # noqa: ANN001
            pass

        def _page_session(self, _page: str, _timeout_s: float):
            return "target", "session", page_path

        def _call(self, _method: str, _params=None, **_kwargs):  # noqa: ANN001
            return {"exceptionDetails": {"text": "boom"}}

        def _wait_event(self, *_args, **_kwargs) -> None:  # noqa: ANN002, ANN003
            pass

    monkeypatch.setattr(render_smoke.time, "monotonic", clock.monotonic)
    monkeypatch.setattr(render_smoke.time, "sleep", clock.sleep)
    monkeypatch.setattr(render_smoke, "find_chromium", lambda: "/fake/chromium")
    monkeypatch.setattr(render_smoke, "ChromiumSession", ThrowingSession)

    with pytest.raises(RuntimeError, match="probe page exception"):
        render_smoke.run_probe("<title>pending</title>", timeout_s=300.0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_render_smoke_nonumpy.py` at line 84, Extend the run_probe test
coverage with a third test using a fake ChromiumSession whose _call method
returns an exceptionDetails response. Keep the existing session, timing, and
page setup pattern, then assert that run_probe raises RuntimeError matching
“probe page exception” so the page-failure branch is exercised.

118-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the session exits on the timeout path.

The stated goal of this PR is that a real hang leaves no Chrome process behind. StallingSession.__exit__ is a no-op and no assertion records that it ran. Record the call and assert it, so a future refactor that moves run_probe off the context manager fails this test.

♻️ Proposed change
     clock = _Clock()
     calls: list[tuple[str, float]] = []
     page_path = tmp_path / "probe.html"
+    exits: list[bool] = []
 
     class StallingSession:
         def __init__(self, _executable: str, *, launch_timeout_s: float, **_kwargs) -> None:
             calls.append(("launch", launch_timeout_s))
             clock.advance(100.0)
 
         def __enter__(self):
             return self
 
         def __exit__(self, _exc_type, _exc, _tb) -> None:  # noqa: ANN001
-            pass
+            exits.append(True)
     assert calls == [
         ("launch", pytest.approx(300.0)),
         ("page_session", pytest.approx(200.0)),
         ("Page.navigate", pytest.approx(100.0)),
     ]
+    assert exits == [True]

Also applies to: 150-157

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_render_smoke_nonumpy.py` around lines 118 - 122, Update
StallingSession.__exit__ to record that the context manager exited, then assert
this recorded state in the timeout-path test after run_probe completes. Ensure
the assertion covers both referenced timeout test locations and would fail if
run_probe no longer uses the session context manager.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/render_smoke_nonumpy.py`:
- Around line 998-1001: Update the re-bin note near the deterministic probe to
state only the probes that explicitly set `_sampleRebinDisabled`, or apply that
flag consistently to every density-bearing probe, including dv1, dv2, v4, and
v5. Ensure the statement remains valid for v4’s zoomed rendering and does not
claim a broader invariant than the implementation guarantees.

In `@tests/test_render_smoke_nonumpy.py`:
- Around line 10-23: Update _load_render_smoke() to snapshot the relevant
sys.path and sys.modules entries before loading the module, then restore the
original state in a finally block around spec.loader.exec_module(). Remove any
added scripts/python paths and restore or delete render_smoke_nonumpy and
_protocol entries so test imports cannot leak across cases.

---

Nitpick comments:
In `@tests/test_render_smoke_nonumpy.py`:
- Line 84: Extend the run_probe test coverage with a third test using a fake
ChromiumSession whose _call method returns an exceptionDetails response. Keep
the existing session, timing, and page setup pattern, then assert that run_probe
raises RuntimeError matching “probe page exception” so the page-failure branch
is exercised.
- Around line 118-122: Update StallingSession.__exit__ to record that the
context manager exited, then assert this recorded state in the timeout-path test
after run_probe completes. Ensure the assertion covers both referenced timeout
test locations and would fail if run_probe no longer uses the session context
manager.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: da3bb2fa-e5b2-41d9-a0e2-745e397cd98d

📥 Commits

Reviewing files that changed from the base of the PR and between d505ef5 and 9eee9da.

📒 Files selected for processing (5)
  • python/xy/_chromium.py
  • scripts/render_smoke_nonumpy.py
  • spec/process/contributing.md
  • tests/test_chromium_session.py
  • tests/test_render_smoke_nonumpy.py

Comment thread scripts/render_smoke_nonumpy.py
Comment thread tests/test_render_smoke_nonumpy.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
python/xy/_chromium.py (1)

254-265: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Collapse the two identical handlers in _stop_process.

subprocess.TimeoutExpired is a subclass of Exception. The except subprocess.TimeoutExpired block and the except Exception block run the same two statements. One handler expresses the same behavior.

♻️ Proposed simplification
         try:
             proc.wait(timeout=wait_timeout())
-        except subprocess.TimeoutExpired:
-            with contextlib.suppress(Exception):
-                proc.kill()
-            with contextlib.suppress(Exception):
-                proc.wait(timeout=reap_timeout())
         except Exception:
             with contextlib.suppress(Exception):
                 proc.kill()
             with contextlib.suppress(Exception):
                 proc.wait(timeout=reap_timeout())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/_chromium.py` around lines 254 - 265, In _stop_process, collapse
the subprocess.TimeoutExpired and generic Exception handlers into a single
exception handler that preserves the existing proc.kill() suppression and reap
wait behavior.
tests/test_chromium_session.py (1)

258-261: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a shared helper for raw ChromiumSession construction.

Six tests build a bare ChromiumSession with object.__new__ and then set _ws, _next_id, _events, and sometimes _proc or _tmp by hand. Extract a small factory, for example _bare_session(**attrs), to remove this repetition and keep the private-attribute list in one place.

♻️ Example helper
def _bare_session(**attrs) -> _chromium.ChromiumSession:
    session = object.__new__(_chromium.ChromiumSession)
    session._next_id = 0
    session._events = {}
    for name, value in attrs.items():
        setattr(session, name, value)
    return session

Also applies to: 280-281, 337-340, 365-368, 394-398, 438-439

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_chromium_session.py` around lines 258 - 261, Introduce a shared
_bare_session helper in tests/test_chromium_session.py that constructs a raw
ChromiumSession and initializes the common _next_id and _events state, while
accepting optional attributes such as _ws, _proc, and _tmp. Replace the six
duplicated object.__new__ construction blocks with this helper, preserving each
test’s specific attributes and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/xy/_chromium.py`:
- Around line 318-325: Both receive paths must fail closed for every exception:
widen the handler in _call at python/xy/_chromium.py lines 318-325 from OSError
to BaseException while retaining _invalidate_websocket() and re-raising; apply
the same change in _wait_event at python/xy/_chromium.py lines 350-354.

---

Nitpick comments:
In `@python/xy/_chromium.py`:
- Around line 254-265: In _stop_process, collapse the subprocess.TimeoutExpired
and generic Exception handlers into a single exception handler that preserves
the existing proc.kill() suppression and reap wait behavior.

In `@tests/test_chromium_session.py`:
- Around line 258-261: Introduce a shared _bare_session helper in
tests/test_chromium_session.py that constructs a raw ChromiumSession and
initializes the common _next_id and _events state, while accepting optional
attributes such as _ws, _proc, and _tmp. Replace the six duplicated
object.__new__ construction blocks with this helper, preserving each test’s
specific attributes and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9057d674-d893-44f2-a42a-7e243933c2a6

📥 Commits

Reviewing files that changed from the base of the PR and between d505ef5 and 9eee9da.

📒 Files selected for processing (5)
  • python/xy/_chromium.py
  • scripts/render_smoke_nonumpy.py
  • spec/process/contributing.md
  • tests/test_chromium_session.py
  • tests/test_render_smoke_nonumpy.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • spec/process/contributing.md
  • tests/test_render_smoke_nonumpy.py
  • scripts/render_smoke_nonumpy.py

Comment thread python/xy/_chromium.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

render_smoke_nonumpy hangs under headless Chrome --dump-dom on macOS

1 participant