feat(javaTest): visualize source-aligned task phases

This commit is contained in:
zgw
2026-08-10 12:28:14 +08:00
parent c8c4498ed1
commit 49c374b4b3
12 changed files with 660 additions and 74 deletions

View File

@@ -91,17 +91,71 @@ def verify_map_interactions(page):
page.locator("#show-move-trace").check()
def verify_historical_mode(page):
"""Run 240 tasks and inspect one real cross-floor dispatch without drawing a fake edge."""
def select_task_with_phase(page, phase_name):
"""Select the first visible business task that exposes the requested backend phase."""
rows = page.locator("#history-task-body tr")
for index in range(rows.count()):
rows.nth(index).click()
if page.locator("#phase-list [data-phase='%s']" % phase_name).count():
return
raise AssertionError("no task exposed business phase " + phase_name)
def assert_no_cross_floor_route_segments(page):
"""Verify that transfer jobs use port markers rather than fake map edges."""
cross_floor_segments = page.evaluate("""
async () => {
const bootstrap = await fetch('/api/bootstrap').then(response => response.json());
const floors = Object.fromEntries(
bootstrap.sites.map(site => [site.siteId, Number(site.floor)]));
return Array.from(document.querySelectorAll(
'#dynamic-route-layer .route-segment title'))
.map(title => title.textContent.split(' → '))
.filter(parts => parts.length === 2
&& floors[parts[0]] !== undefined
&& floors[parts[1]] !== undefined
&& floors[parts[0]] !== floors[parts[1]]);
}
""")
require(not cross_floor_segments,
"normal route layer drew cross-floor edges: " + str(cross_floor_segments))
def verify_historical_mode(page, switch_raw=True):
"""Run business replay, inspect phase/transfer paths, then verify RAW compatibility."""
page.locator(".mode-tab[data-mode='history']").click()
require(page.locator("#simulation-mode-business").is_checked(),
"business replay must be the default historical mode")
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")
require(page.locator("#raw-snapshot-count").inner_text() == "240",
"input audit did not expose 240 raw snapshots")
require(page.locator("#business-task-count").inner_text() == "76",
"input audit did not expose 76 business parent tasks")
require("76" in page.locator("#task-filter-count").inner_text(),
"business replay did not expose 76 parent tasks")
require(page.locator("#history-task-body tr").count() == 76,
"business replay table row count is not 76")
select_task_with_phase(page, "EMPTY_RETURN_TO_TARGET_FRONT")
require(page.locator("#phase-list [data-phase]").count() >= 2,
"business task did not expose its multi-stage route evidence")
require(page.locator("#business-completed-at").inner_text() != "--",
"business completion time is missing")
require("车辆已释放" in page.locator("#vehicle-released-at").inner_text(),
"vehicle release evidence is incomplete")
selected_phase = page.locator(
"#phase-list [data-phase='EMPTY_RETURN_TO_TARGET_FRONT']"
)
selected_phase.click()
require(page.locator("#dynamic-route-layer .business-phase").count() > 0,
"selected business phase did not render a same-floor route")
require(page.locator(".map-floor-layer:not([hidden]) .map-edge").count() > 20,
"phase selection replaced the authoritative full map")
assert_no_cross_floor_route_segments(page)
transfer_rows = page.locator(
"#history-task-body tr[data-assignment-mode='FLOOR_TRANSFER']"
)
@@ -132,22 +186,21 @@ def verify_historical_mode(page):
).inner_text() == "换层完成",
"transfer completion event did not receive a business label")
cross_floor_segments = page.evaluate("""
async () => {
const bootstrap = await fetch('/api/bootstrap').then(response => response.json());
const floors = Object.fromEntries(
bootstrap.sites.map(site => [site.siteId, Number(site.floor)]));
return Array.from(document.querySelectorAll(
'#dynamic-route-layer .route-segment title'))
.map(title => title.textContent.split(' → '))
.filter(parts => parts.length === 2
&& floors[parts[0]] !== undefined
&& floors[parts[1]] !== undefined
&& floors[parts[0]] !== floors[parts[1]]);
}
""")
require(not cross_floor_segments,
"normal route layer drew cross-floor edges: " + str(cross_floor_segments))
assert_no_cross_floor_route_segments(page)
if not switch_raw:
return
page.locator("label[for='simulation-mode-raw']").click()
expect(page.locator("#task-filter-count")).to_have_text("240 条", timeout=180_000)
require(page.locator("#history-task-body tr").count() == 240,
"RAW compatibility replay did not expose all 240 snapshots")
require(page.locator("#replay-mode-readout").inner_text() == "RAW 快照",
"replay mode readout did not switch to RAW")
require(not page.locator("#show-historical").is_disabled(),
"RAW mode did not restore the historical path layer")
page.locator("#history-task-body tr").first.click()
require(page.locator("#route-difference").inner_text() != "共同 -- / 新增 -- / 历史 --",
"RAW task did not render historical/new path difference evidence")
def verify_viewport(browser, viewport, filename, run_history):
@@ -166,7 +219,7 @@ def verify_viewport(browser, viewport, filename, run_history):
run_point_to_point(page)
verify_map_interactions(page)
if run_history:
verify_historical_mode(page)
verify_historical_mode(page, switch_raw=viewport["width"] > 580)
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))
@@ -186,7 +239,7 @@ def main():
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)
verify_viewport(browser, {"width": 390, "height": 844}, "mobile.png", True)
browser.close()
print("visualizer browser QA passed: desktop=1440x1000 mobile=390x844")