diff options
Diffstat (limited to 'tools/acceptance/e2e_acceptance.py')
| -rwxr-xr-x | tools/acceptance/e2e_acceptance.py | 89 |
1 files changed, 88 insertions, 1 deletions
diff --git a/tools/acceptance/e2e_acceptance.py b/tools/acceptance/e2e_acceptance.py index ee75ee6..1b3ae6c 100755 --- a/tools/acceptance/e2e_acceptance.py +++ b/tools/acceptance/e2e_acceptance.py @@ -253,7 +253,12 @@ def stale_load_regression(browser, base_url, track, results, evidence): Intercepts steps GLB requests and fulfills them late, out of order, or with errors to prove stale responses from superseded runs never repaint - geometry or notes, while the current owner's loads keep working. + geometry or notes, while the current owner's loads keep working. Also + covers: user retry of a failed current latest-step load after run + completion (same owner/path, no SSE reconnect), same-run older-step + responses resolving after the newer step, and pending live responses + ignored after entering replay. All still-held responses are drained and + the route handler is removed before the page closes. """ page = browser.new_page(viewport={"width": 1280, "height": 900}) @@ -403,6 +408,88 @@ def stale_load_regression(browser, base_url, track, results, evidence): "stale-load: current owner load unaffected by stale error")) (evidence / "stale-scene-final.json").write_text(json.dumps(g5, indent=2, ensure_ascii=False)) page.screenshot(path=str(evidence / "stale-page-final.png"), full_page=True) + + # Scenario D: current latest-step failure after run completion retries the + # same owner/path via the visible note affordance, without new SSE events. + run_k = f"run-stale-{stamp}-k" + start_run(run_k) + release(held_for(run_k, "steps/01-foundation.glb")) + wait_text(page, "#viewport-note", "管线几何 · steps/01-foundation.glb") + ek3 = held_for(run_k, "steps/03-cabin.glb") + wait_text(page, "#status-label", "已完成") + release(ek3, status=500) + wait_text(page, "#viewport-note", "管线几何加载失败 · steps/03-cabin.glb") + cursor = page.evaluate( + "() => getComputedStyle(document.querySelector('#viewport-note')).cursor") + results.append((cursor == "pointer", + f"stale-load: failed current load exposes retry affordance (cursor={cursor})")) + page.screenshot(path=str(evidence / "stale-page-retry-failed.png"), full_page=True) + page.click("#viewport-note") + ek4 = held_for(run_k, "steps/03-cabin.glb") + retries = [e for e in held if e["run"] == run_k and e["path"] == "steps/03-cabin.glb"] + results.append((len(retries) == 2 and ek4 is retries[1], + f"stale-load: user retry re-requests same owner/path ({len(retries)} requests)")) + stream = page.evaluate("() => document.querySelector('#stream-state').textContent") + results.append(("已连接" in stream, + f"stale-load: retry works without SSE reconnect (stream='{stream[:30]}')")) + release(ek4) + wait_text(page, "#viewport-note", "管线几何 · steps/03-cabin.glb") + page.wait_for_timeout(600) + gk = page.evaluate(SCENE_JS) + cursor_after = page.evaluate( + "() => getComputedStyle(document.querySelector('#viewport-note')).cursor") + results.append((gk["ready"] and is_glb(gk) and box_ok(gk) and cursor_after != "pointer", + f"stale-load: retry displays latest step and clears affordance " + f"(kids={gk.get('kids')})")) + page.screenshot(path=str(evidence / "stale-page-retry-ok.png"), full_page=True) + + # Scenario E: same-run older step resolving after the newer step must be ignored. + run_h = f"run-stale-{stamp}-h" + start_run(run_h) + held_for(run_h, "steps/01-foundation.glb") + eh3 = held_for(run_h, "steps/03-cabin.glb") + release(eh3) + wait_text(page, "#viewport-note", "管线几何 · steps/03-cabin.glb") + page.wait_for_timeout(600) + gh = page.evaluate(SCENE_JS) + results.append((gh["ready"] and is_glb(gh) and box_ok(gh) + and "steps/03-cabin.glb" in gh["note"], + f"stale-load: newer same-run step displayed after supersede " + f"(kids={gh.get('kids')})")) + note_h = gh["note"] + release(held_for(run_h, "steps/01-foundation.glb")) + page.wait_for_timeout(600) + gh2 = page.evaluate(SCENE_JS) + results.append((gh2["ready"] and gh2["kids"] == gh["kids"] and gh2["note"] == note_h, + f"stale-load: late older same-run step ignored (note='{gh2['note'][:40]}')")) + + # Scenario F: live GLB response still pending after entering replay must + # not repaint the selected replay frame view. + run_j = f"run-stale-{stamp}-j" + start_run(run_j) + release(held_for(run_j, "steps/01-foundation.glb")) + wait_text(page, "#viewport-note", "管线几何 · steps/01-foundation.glb") + ej3 = held_for(run_j, "steps/03-cabin.glb") + wait_text(page, "#status-label", "已完成") + page.click("#replay-button") + page.wait_for_timeout(300) + page.click("#replay-button") + page.wait_for_timeout(300) + gj = page.evaluate(SCENE_JS) + results.append((gj["ready"] and is_frame(gj) and box_ok(gj), + f"stale-load: replay entry repaints frame view while GLB pending " + f"(kids={gj.get('kids')})")) + page.screenshot(path=str(evidence / "stale-page-replay.png"), full_page=True) + release(ej3) + page.wait_for_timeout(600) + gj2 = page.evaluate(SCENE_JS) + results.append((gj2["ready"] and gj2["kids"] == gj["kids"] and gj2["note"] == gj["note"], + f"stale-load: live GLB after replay ignored (note='{gj2['note'][:40]}')")) + + for entry in held: + if not entry["done"]: + release(entry) + page.unroute("**/api/artifacts/steps*") page.close() |
