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
92 lines
3.3 KiB
Python
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()
|