import os from pathlib import Path from urllib.parse import urlparse from playwright.sync_api import sync_playwright FRONTEND_URL = "http://localhost" BACKEND_URL = os.environ.get("DENSE_PLAN_BACKEND_URL", "http://127.0.0.1:8080") TEST_USERNAME = os.environ.get("DENSE_PLAN_USERNAME") TEST_PASSWORD = os.environ.get("DENSE_PLAN_PASSWORD") SCREENSHOT_PATH = Path( os.environ.get( "DENSE_PLAN_SCREENSHOT", r"C:\Users\13623\.codex\visualizations\2026\07\14\019f5e36-a2f0-7751-86cc-3d8ac317f956\dense-cargo-space-plan.png", ) ) if not TEST_USERNAME or not TEST_PASSWORD: raise RuntimeError("请通过 DENSE_PLAN_USERNAME 和 DENSE_PLAN_PASSWORD 提供测试账号") def choose_warehouse(page, warehouse_name): warehouse_field = page.locator(".el-form-item").filter(has_text="仓库").first warehouse_field.locator(".el-select").click() page.locator(".el-select-dropdown:visible .el-select-dropdown__item").filter( has_text=warehouse_name ).click() def assert_no_internal_overflow(page): for selector in (".dense-map__viewport", ".dense-grid"): metrics = page.locator(selector).evaluate( "node => ({ scrollWidth: node.scrollWidth, clientWidth: node.clientWidth, " "scrollHeight: node.scrollHeight, clientHeight: node.clientHeight })" ) assert metrics["scrollWidth"] <= metrics["clientWidth"] + 1, (selector, metrics) assert metrics["scrollHeight"] <= metrics["clientHeight"] + 1, (selector, metrics) with sync_playwright() as playwright: browser = playwright.chromium.launch( headless=True, executable_path=r"C:\Program Files\Google\Chrome\Application\chrome.exe", ) context = browser.new_context(viewport={"width": 1500, "height": 1050}) page = context.new_page() console_errors = [] page.on("console", lambda message: console_errors.append(message.text) if message.type == "error" else None) login_response = page.request.post( f"{BACKEND_URL}/login", data={"username": TEST_USERNAME, "password": TEST_PASSWORD, "code": "", "uuid": ""}, ) login_data = login_response.json() assert login_data.get("code") == 200, login_data token = login_data["token"] context.add_cookies( [{"name": "Admin-Token", "value": token, "url": FRONTEND_URL}] ) def route_backend(route): request = route.request parsed = urlparse(request.url) backend_path = parsed.path.removeprefix("/dev-api") target_url = f"{BACKEND_URL}{backend_path}" if parsed.query: target_url += f"?{parsed.query}" response = page.request.fetch( target_url, method=request.method, headers=request.headers, data=request.post_data, ) route.fulfill(response=response) page.route("**/dev-api/**", route_backend) page.goto(f"{FRONTEND_URL}/kpi/planKpi") page.wait_for_load_state("networkidle") choose_warehouse(page, "密集库") dense_map = page.get_by_test_id("dense-cargo-space-map") dense_map.wait_for(state="visible") page.wait_for_function( "document.querySelectorAll('[data-testid=dense-location]').length === 259" ) floor_one_count = page.get_by_test_id("dense-location").count() mother_count = page.locator('[data-testid="dense-location"][data-status="9"]').count() lift_count = page.locator('[data-testid="dense-location"][data-status="7"]').count() conveyor_count = page.locator('[data-testid="dense-location"][data-status="8"]').count() charging_count = page.locator('[data-testid="dense-location"][data-status="10"]').count() assert (floor_one_count, mother_count, lift_count, conveyor_count, charging_count) == (259, 49, 2, 2, 1) assert page.get_by_text("充电桩", exact=True).count() >= 1 assert page.locator(".axis-label--column").count() == 24 assert page.locator(".axis-label--column", has_text="25").count() == 0 assert page.locator(".search-box").get_by_test_id("floor-switch").count() == 1 assert page.locator(".search-box").get_by_test_id("dense-refresh").count() == 1 assert page.get_by_text("密集库模式:按层展示完整平面坐标", exact=True).count() == 0 assert page.get_by_text("密集库货位平面图", exact=True).count() == 0 assert_no_internal_overflow(page) assert page.locator('[data-testid="dense-location"][data-platoon="7"][data-column="13"]').count() == 1 assert page.locator('[data-testid="dense-location"][data-platoon="1"][data-column="13"]').count() == 1 assert page.locator('[data-testid="dense-location"][data-platoon="13"][data-column="13"]').count() == 1 page.locator('[data-testid="dense-location"][data-platoon="1"][data-column="1"]').click() assert page.get_by_text("货区 A", exact=True).count() == 1 assert page.get_by_text("区域 Z01", exact=True).count() == 0 page.get_by_test_id("floor-switch").get_by_text("2层", exact=True).click() page.wait_for_function( "Array.from(document.querySelectorAll('[data-testid=dense-location]')).every(node => node.textContent.trim().endsWith('02'))" ) floor_two_count = page.get_by_test_id("dense-location").count() assert floor_two_count == 259 assert page.locator('[data-testid="dense-location"][data-status="10"]').count() == 1 page.set_viewport_size({"width": 1280, "height": 800}) page.wait_for_timeout(300) assert_no_internal_overflow(page) SCREENSHOT_PATH.parent.mkdir(parents=True, exist_ok=True) dense_map.screenshot(path=str(SCREENSHOT_PATH)) choose_warehouse(page, "托盘库1") page.locator(".chart-container--normal").wait_for(state="visible") page.locator(".chart-container--normal canvas").first.wait_for(state="visible") assert page.get_by_test_id("dense-cargo-space-map").count() == 0 relevant_errors = [ error for error in console_errors if "favicon" not in error.lower() and "sockjs" not in error.lower() ] assert not relevant_errors, relevant_errors print( "dense-plan-ok " f"floor1={floor_one_count} floor2={floor_two_count} " f"mother={mother_count} lift={lift_count} conveyor={conveyor_count} charging={charging_count} " "legacy-chart=visible console-errors=0" ) browser.close()