Files
interactive-story/backend/tests/test_v11_c_browser_helpers.py
JesseMarkowitzandClaude Opus 5 59b5ebc2d8 v1.1 WP-C: browser release coverage
Drives in a real browser the reader workflows v1 proved only through the API
or the component suite, including an export that leaves the browser as a
file. Final run: 91 checks (the 38 existing M11 checks plus 53 new), 0 failed,
0 skipped, on the production build over trusted-LAN HTTPS.

- tools/m11_browser.py: scenarios for Retry and takes, Save Point create /
  restore / Redo, state correction (accepted, and a refused correction with
  its reason), narration length reaching each turn's prompt, failed
  generation (an unserved model blocked up front; a listed model that cannot
  narrate failing in the open) and recovery, and export download from the
  library and from campaign settings, imported into a fresh application.
  Rows are tagged M11 / WP-C and counted separately; --only for development.
  The M11 checks now wait on conditions instead of sleeping.
- tools/m11_webdriver.py: Firefox download preferences, a $HOME-only
  download folder, a download wait that ignores partial, empty, pre-existing
  and still-growing files, centred real clicks, tabs, and condition waits.
- tests/test_v11_c_browser_helpers.py: the download wait, prefs and $HOME
  guard, without a browser.
- frontend: a correction the story refused was presented as "Generation
  failed" with a Retry offer and a typed-input claim. It is now "That
  correction was not applied", not retryable, with the reason kept
  (errors.js, FailureNotice.jsx; 3 regression tests).
- DEVELOPMENT.md: the harness command, download profile and $HOME rule,
  what counts as a finished download, and the no-sleep rule.
- docs: V1.1-PLAN, VERSION v4.4, planning README,
  reports/v1.1/V1.1-WP-C-REPORT.md.

Open for the owner: "Correct" on an Important Facts row is always refused
(K1), and a partly refused correction is not reachable from the reader UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvegagkhuCZoFPdv4M1egY
2026-09-15 18:29:45 -04:00

92 lines
3.3 KiB
Python

"""v1.1 WP-C: the browser harness's download helpers, without a browser.
`tools/m11_webdriver.wait_for_download` is what decides that an export actually
left the browser as a file. It must never call a download finished because a
file appeared, because it is still being written, or because it is empty — each
of those would make "the export works" a claim the evidence does not support.
python -m pytest tests/test_v11_c_browser_helpers.py -v
"""
import threading
import time
from pathlib import Path
import pytest
from tools import m11_webdriver as wd
def later(seconds, action):
timer = threading.Timer(seconds, action)
timer.start()
return timer
def test_a_finished_file_is_returned(tmp_path):
later(0.2, lambda: (tmp_path / "campaign.json").write_text('{"format": "x"}'))
found = wd.wait_for_download(tmp_path, set(), timeout=5, poll=0.05)
assert found == tmp_path / "campaign.json"
def test_a_file_that_was_already_there_is_not_the_download(tmp_path):
(tmp_path / "old.json").write_text("{}")
with pytest.raises(wd.WebDriverError):
wd.wait_for_download(tmp_path, {"old.json"}, timeout=0.6, poll=0.05)
def test_an_empty_file_never_counts(tmp_path):
(tmp_path / "empty.json").write_bytes(b"")
with pytest.raises(wd.WebDriverError):
wd.wait_for_download(tmp_path, set(), timeout=0.6, poll=0.05)
def test_nothing_counts_while_firefox_is_still_writing(tmp_path):
"""Firefox writes `<name>.part` beside the final name until it is done."""
(tmp_path / "campaign.json").write_text('{"format": "x"}')
(tmp_path / "campaign.json.part").write_text("")
with pytest.raises(wd.WebDriverError):
wd.wait_for_download(tmp_path, set(), timeout=0.6, poll=0.05)
later(0.1, lambda: (tmp_path / "campaign.json.part").unlink())
assert wd.wait_for_download(tmp_path, set(), timeout=5, poll=0.05).name == "campaign.json"
def test_a_file_that_is_still_growing_is_not_finished(tmp_path):
target = tmp_path / "big.json"
target.write_text("{")
stop = threading.Event()
def grow():
for _ in range(8):
if stop.is_set():
return
with target.open("a") as fh:
fh.write("x" * 100)
time.sleep(0.05)
writer = threading.Thread(target=grow)
started = time.monotonic()
writer.start()
found = wd.wait_for_download(tmp_path, set(), timeout=5, poll=0.05, stable_polls=3)
writer.join()
# Returned only once the size held still, so after the last write.
assert found == target
assert target.stat().st_size == 1 + 8 * 100
assert time.monotonic() - started >= 0.4
def test_the_prefs_save_downloads_unasked_to_the_folder_given(tmp_path):
prefs = wd.firefox_download_prefs(tmp_path)
assert prefs["browser.download.folderList"] == 2
assert prefs["browser.download.dir"] == str(tmp_path)
assert prefs["browser.download.useDownloadDir"] is True
assert prefs["browser.download.always_ask_before_handling_new_types"] is False
assert "application/json" in prefs["browser.helperApps.neverAsk.saveToDisk"]
def test_a_download_folder_must_be_under_home():
with pytest.raises(wd.WebDriverError):
wd.require_under_home(Path("/tmp/wp-c-downloads"))
inside = Path.home() / "v11-evidence" / "wp-c" / "downloads"
assert wd.require_under_home(inside) == inside.resolve()