"""Run repeatable Chromium checks against the real javaTest visualizer server.""" import os import re import sys from glob import glob from playwright.sync_api import expect, sync_playwright BASE_URL = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:18084" OUTPUT_DIR = sys.argv[2] if len(sys.argv) > 2 else "outputs/browser-qa" def require(condition, message): """Fail the QA run with a business-readable assertion message.""" if not condition: raise AssertionError(message) def horizontal_overflow(page): """Return true when the document spills outside the active viewport.""" return page.evaluate( "document.documentElement.scrollWidth > document.documentElement.clientWidth + 1" ) def run_point_to_point(page): """Execute the real point simulation and verify route evidence is rendered.""" page.locator("#swap-sites").click() page.locator("#run-point").click() page.locator("#task-status").wait_for(state="visible", timeout=120_000) page.locator("#loading-state").wait_for(state="hidden", timeout=120_000) expect(page.locator("#task-status")).to_have_text("COMPLETED", timeout=120_000) require(page.locator("#route-candidate-body tr").count() > 0, "point simulation did not render route candidates") require(page.locator("#route-quality-body tr").count() == 6, "route quality tuple must contain six ordered metrics") require(page.locator("#route-comparability").inner_text() != "--", "historical comparability badge was not populated") def verify_map_interactions(page): """Check full-map authority, route layers, viewport controls and timeline stability.""" map_edges = page.locator(".map-floor-layer:not([hidden]) .map-edge").count() dynamic_edges = page.locator("#dynamic-route-layer .route-segment").count() require(map_edges > 20, "complete floor map is missing") require(map_edges > dynamic_edges, "map background should exceed one task route") full_view = page.locator("#route-map").get_attribute("viewBox") page.locator("#path-fit").click() path_view = page.locator("#route-map").get_attribute("viewBox") require(path_view != full_view, "path fit did not change the map viewport") page.locator("#zoom-in").click() zoomed_view = page.locator("#route-map").get_attribute("viewBox") require(zoomed_view != path_view, "zoom control did not change the viewport") page.locator("#reset-view").click() require(page.locator("#route-map").get_attribute("viewBox") == "0 0 1000 560", "reset did not restore the full-map viewport") page.locator("#show-current-route").uncheck() require(page.locator("#dynamic-route-layer .planned-only").count() == 0, "current route remained visible after its layer was disabled") require(page.locator("#dynamic-route-layer .approach").count() == 0, "approach route remained visible after the current layer was disabled") require(page.locator(".map-floor-layer:not([hidden]) .map-edge").count() == map_edges, "disabling current route changed the authoritative map") page.locator("#show-current-route").check() page.locator("#show-historical").uncheck() require(page.locator("#dynamic-route-layer .historical-only").count() == 0, "historical route remained visible after its layer was disabled") page.locator("#show-historical").check() page.locator("#show-initial-route").uncheck() require(page.locator("#dynamic-route-layer .initial-service").count() == 0, "initial route remained visible after its layer was disabled") page.locator("#show-initial-route").check() before_tick_view = page.locator("#route-map").get_attribute("viewBox") slider = page.locator("#timeline-slider") slider.fill(slider.get_attribute("max")) slider.dispatch_event("input") require(page.locator("#route-map").get_attribute("viewBox") == before_tick_view, "timeline movement unexpectedly changed the user viewport") page.locator("#show-move-trace").uncheck() require(page.locator("#dynamic-route-layer .traversed").count() == 0, "MOVE trace remained visible after its layer was disabled") page.locator("#show-move-trace").check() def verify_historical_mode(page): """Run the real 240-task endpoint and verify task selection keeps decision evidence live.""" page.locator(".mode-tab[data-mode='history']").click() page.locator("#run-history").click() expect(page.locator("#history-run-state")).to_have_text( re.compile(r"^最近运行"), timeout=180_000 ) require("240" in page.locator("#task-filter-count").inner_text(), "historical result did not expose all 240 tasks") require(page.locator("#history-task-body tr").count() == 240, "historical task table row count is not 240") page.locator("#history-task-body tr").nth(1).click() require(page.locator("#route-candidate-body tr").count() > 0, "selected historical task lost route candidates") def verify_viewport(browser, viewport, filename, run_history): """Verify one desktop or mobile viewport and save its full-page evidence image.""" context = browser.new_context(viewport=viewport, locale="zh-CN") page = context.new_page() errors = [] page.on("console", lambda message: errors.append(message.text) if message.type == "error" else None) page.on("pageerror", lambda error: errors.append(str(error))) page.goto(BASE_URL, wait_until="networkidle", timeout=120_000) page.locator(".map-floor-layer:not([hidden]) .map-edge").first.wait_for( state="attached", timeout=60_000 ) require(not horizontal_overflow(page), "page has horizontal overflow before simulation") run_point_to_point(page) verify_map_interactions(page) if run_history: verify_historical_mode(page) require(not horizontal_overflow(page), "page has horizontal overflow after simulation") page.screenshot(path=os.path.join(OUTPUT_DIR, filename), full_page=True) require(not errors, "browser console/page errors: " + " | ".join(errors)) context.close() def main(): """Run desktop and mobile checks using one headless Chromium process.""" os.makedirs(OUTPUT_DIR, exist_ok=True) with sync_playwright() as playwright: cached_candidates = sorted(glob(os.path.join( os.environ.get("LOCALAPPDATA", ""), "ms-playwright", "chromium-*", "chrome-win64", "chrome.exe" )), reverse=True) launch_options = {"headless": True} if cached_candidates: launch_options["executable_path"] = cached_candidates[0] browser = playwright.chromium.launch(**launch_options) verify_viewport(browser, {"width": 1440, "height": 1000}, "desktop.png", True) verify_viewport(browser, {"width": 390, "height": 844}, "mobile.png", False) browser.close() print("visualizer browser QA passed: desktop=1440x1000 mobile=390x844") if __name__ == "__main__": main()