|
| 1 | +"""End-to-end run of Case Study 8 in **notebook mode**. |
| 2 | +
|
| 3 | +CS8 ("ProvSQL as a Probability Calculator") is the notebook-first case study: |
| 4 | +a self-contained sequence of one-line probability queries. This test opens the |
| 5 | +bundled ``cs8`` example in Studio's notebook mode and runs it end to end |
| 6 | +against a live kernel, asserting that every cell executes and none produces an |
| 7 | +error banner -- a regression guard that the shipped notebook still runs on the |
| 8 | +current ProvSQL. |
| 9 | +
|
| 10 | +Backed by the `cs8_studio_url` session fixture (Studio on a fresh provsql |
| 11 | +database) and pytest-playwright's `page`.""" |
| 12 | +from __future__ import annotations |
| 13 | + |
| 14 | +import re |
| 15 | + |
| 16 | +from playwright.sync_api import Page, expect |
| 17 | + |
| 18 | + |
| 19 | +def test_cs8_notebook_runs_end_to_end(page: Page, cs8_studio_url: str) -> None: |
| 20 | + page.goto(cs8_studio_url + "/notebook") |
| 21 | + expect(page.locator("body")).to_have_class( |
| 22 | + re.compile(r"\bmode-notebook\b"), timeout=8000) |
| 23 | + # Start from a pristine notebook, not a previous run's autosaved draft. |
| 24 | + page.evaluate("localStorage.removeItem('ps.nb.autosave');" |
| 25 | + "localStorage.removeItem('ps.nb.tabs')") |
| 26 | + page.reload() |
| 27 | + page.wait_for_selector("#notebook-pane", timeout=8000) |
| 28 | + |
| 29 | + # Open the bundled CS8 example; it loads its ~27 code cells. |
| 30 | + page.locator("#nb-example").select_option("cs8") |
| 31 | + cells = page.locator(".nb-cell--sql") |
| 32 | + expect(cells.nth(20)).to_be_attached(timeout=15000) |
| 33 | + total = cells.count() |
| 34 | + assert total >= 20, total |
| 35 | + |
| 36 | + # Run the whole notebook against a live kernel (binds + creates its own |
| 37 | + # tables); wait until the last cell carries an execution count. |
| 38 | + page.locator("#nb-run-all").click() |
| 39 | + expect(cells.last.locator(".nb-cell__count")).to_have_text( |
| 40 | + re.compile(r"\[\d+\]"), timeout=180000) |
| 41 | + |
| 42 | + # A real kernel served the run (not a cached render). |
| 43 | + expect(page.locator("#nb-kernel-label")).to_contain_text("pid", timeout=8000) |
| 44 | + # Every code cell ran (each shows an execution count) and none errored. |
| 45 | + counted = cells.locator(".nb-cell__count").filter( |
| 46 | + has_text=re.compile(r"\[\d+\]")).count() |
| 47 | + assert counted == total, f"{counted}/{total} cells executed" |
| 48 | + assert page.locator(".nb-out .wp-error").count() == 0, "error banner(s)" |
| 49 | + # Sanity that real probabilities were computed: the base-rate example's |
| 50 | + # unconditional P(positive) = 0.0585 appears in the executed output. |
| 51 | + expect(page.locator("#notebook-pane")).to_contain_text("0.0585", timeout=8000) |
| 52 | + |
| 53 | + # --- Exercise the interactive affordances CS8 showcases --------------- |
| 54 | + # CS8's headline is the `|` ("given") operator. Click the conditioned |
| 55 | + # provenance token it produces (the insulin_resistance-given-obesity row) |
| 56 | + # to render its circuit inline, then Evaluate it. |
| 57 | + cond_cell = page.locator( |
| 58 | + ".nb-cell--sql", has_text="factor = 'insulin_resistance'").first |
| 59 | + token = cond_cell.locator(".nb-out [data-circuit-uuid]").first |
| 60 | + expect(token).to_be_visible(timeout=8000) |
| 61 | + token.scroll_into_view_if_needed() |
| 62 | + token.click() |
| 63 | + circ = page.locator(".nb-cell--circuit").first |
| 64 | + expect(circ.locator(".nb-circ__svg .node-group").first).to_be_visible( |
| 65 | + timeout=15000) |
| 66 | + |
| 67 | + # The circuit cell's Evaluate button inserts an evaluation cell; run it |
| 68 | + # (defaults to marginal probability / exact) and read back a value. |
| 69 | + circ.locator(".nb-circ__eval").click() |
| 70 | + ev = page.locator(".nb-cell--eval").first |
| 71 | + ev.locator(".nb-cell__run").click() |
| 72 | + value = ev.locator(".nb-eval__value") |
| 73 | + expect(value).not_to_have_text("", timeout=30000) |
| 74 | + # P(insulin_resistance | obesity) = 0.5 * 0.6 = 0.3 (casestudy8.rst). |
| 75 | + got = float(re.search(r"[0-9]*\.?[0-9]+", value.inner_text()).group()) |
| 76 | + assert abs(got - 0.3) < 5e-3, value.inner_text() |
0 commit comments