test: verify dense outbound dispatch workflows

This commit is contained in:
zgw
2026-07-16 12:04:32 +08:00
parent 10ce28f8ac
commit cd2ceef8e6
19 changed files with 513 additions and 35 deletions

View File

@@ -0,0 +1,168 @@
"""Read-only Playwright verification for dense outbound and relocation screens."""
import json
import os
from pathlib import Path
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from playwright.sync_api import sync_playwright
BASE_URL = os.getenv("WMS_TEST_BASE_URL", "http://127.0.0.1")
USERNAME = os.environ["WMS_TEST_USERNAME"]
PASSWORD = os.environ["WMS_TEST_PASSWORD"]
OUTPUT_DIR = Path(os.getenv(
"WMS_TEST_SCREENSHOT_DIR",
"docs/testing/screenshots/2026-07-16-dense-outbound",
)).resolve()
def wait_for_app(page):
try:
page.wait_for_load_state("networkidle", timeout=15000)
except PlaywrightTimeoutError:
page.wait_for_load_state("domcontentloaded")
page.wait_for_timeout(800)
def assert_text(page, *labels):
body = page.locator("body").inner_text()
for label in labels:
if label not in body:
raise AssertionError(f"Missing text {label!r} at {page.url}")
def page_overflow(page):
return page.evaluate("""() => ({
viewport: window.innerWidth,
documentWidth: document.documentElement.scrollWidth,
bodyWidth: document.body.scrollWidth
})""")
def wait_for_preview_dialog(page):
dialog = page.locator(".el-dialog:visible")
dialog.get_by_text("指定通道整托出库", exact=True).wait_for(timeout=10000)
dialog.locator(".el-loading-mask").wait_for(state="hidden", timeout=10000)
dialog.locator("tbody .el-table__row").first.wait_for(timeout=10000)
def main():
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
results = {"pages": {}, "consoleErrors": [], "requestFailures": []}
with sync_playwright() as playwright:
browser = playwright.chromium.launch(channel="chrome", headless=True)
context = browser.new_context(viewport={"width": 1440, "height": 1000})
page = context.new_page()
page.on("console", lambda msg: results["consoleErrors"].append(f"{page.url} :: {msg.text}")
if msg.type == "error" else None)
page.on("requestfailed", lambda request: results["requestFailures"].append(
f"{request.method} {request.url}: {request.failure}"))
page.goto(BASE_URL + "/login", wait_until="domcontentloaded")
wait_for_app(page)
page.get_by_placeholder("账号").fill(USERNAME)
page.get_by_placeholder("密码").fill(PASSWORD)
page.get_by_role("button", name="登 录").click()
page.wait_for_url(lambda url: "/login" not in url, timeout=15000)
wait_for_app(page)
pages = [
("outbound-job", "/out/outJob",
("WCS状态", "自动重试", "最后下发错误", "手动下发 WCS")),
("dense-lane-outbound", "/out/denseLaneOutbound",
("通道组编码", "满托盘数", "可出库托盘", "当前最外侧托盘", "整托出库")),
("kpi-plan", "/kpi/planKpi",
("货位平面图", "指定移库")),
]
for name, route, labels in pages:
page.goto(BASE_URL + route, wait_until="domcontentloaded")
wait_for_app(page)
assert_text(page, *labels)
screenshot = OUTPUT_DIR / f"{name}-desktop.png"
page.screenshot(path=str(screenshot), full_page=True)
results["pages"][name] = {
"url": page.url,
"desktopScreenshot": str(screenshot),
"desktopOverflow": page_overflow(page),
}
if name == "dense-lane-outbound":
lane_buttons = page.get_by_role("button", name="整托出库")
enabled_button = next((lane_buttons.nth(index)
for index in range(lane_buttons.count())
if lane_buttons.nth(index).is_enabled()), None)
if enabled_button is not None:
enabled_button.click()
wait_for_preview_dialog(page)
assert_text(page, "整托数量", "出库口", "本次准确出库托盘")
dialog_shot = OUTPUT_DIR / "dense-lane-outbound-dialog-real.png"
page.screenshot(path=str(dialog_shot), full_page=True)
results["pages"][name]["realDialogScreenshot"] = str(dialog_shot)
page.get_by_role("button", name="取消").last.click()
else:
results["pages"][name]["dialog"] = "skipped-no-available-lane"
synthetic_lane = {
"groupCoding": "VERIFY-LANE-01", "cargoArea": "G", "thePlatoon": 7,
"theFloor": 1, "directionType": "1", "fullTrayCount": 3,
"availableTrayCount": 2, "totalQuantity": 24,
"outermostTrayCode": "A-VERIFY-001", "outermostCargoSpaceId": "VERIFY-CS-003",
}
synthetic_detail = {
"projectNo": "VERIFY-PROJECT", "productId": "VERIFY-MATERIAL",
"productName": "验证物料", "lotNo": "VERIFY-LOT", "stockQuantity": 12,
}
synthetic_tray = {
"cargoSpaceId": "VERIFY-CS-003", "cargoSpaceName": "验证端头货位",
"trayCode": "A-VERIFY-001", "theColumn": 3, "stockQuantity": 12,
"stockDetails": [synthetic_detail],
}
page.route("**/dev-api/out/denseLaneOutbound/list*", lambda route: route.fulfill(
status=200, content_type="application/json",
body=json.dumps({"code": 200, "data": [synthetic_lane]}, ensure_ascii=False)))
page.route("**/dev-api/out/denseLaneOutbound/preview*", lambda route: route.fulfill(
status=200, content_type="application/json",
body=json.dumps({"code": 200, "data": {"trays": [synthetic_tray],
"totalQuantity": 12, "previewToken": "verify-token"}}, ensure_ascii=False)))
page.route("**/dev-api/bas/stationpoint/list*", lambda route: route.fulfill(
status=200, content_type="application/json",
body=json.dumps({"code": 200, "rows": [{"pointId": "VERIFY-OUT-1",
"pointName": "验证出库口", "pointType": "2", "pointStatus": "0",
"status": "0", "trayType": "A"}], "total": 1}, ensure_ascii=False)))
page.set_viewport_size({"width": 1440, "height": 1000})
page.goto(BASE_URL + "/out/denseLaneOutbound", wait_until="domcontentloaded")
wait_for_app(page)
page.get_by_role("button", name="整托出库").click()
wait_for_preview_dialog(page)
page.get_by_placeholder("请选择与托盘类型匹配的出库口").click()
page.get_by_text("验证出库口(VERIFY-OUT-1,出库,A类托盘)", exact=True).wait_for(timeout=5000)
assert_text(page, "VERIFY-LANE-01", "A-VERIFY-001", "VERIFY-CS-003",
"验证出库口", "本次准确出库托盘")
dialog_shot = OUTPUT_DIR / "dense-lane-outbound-dialog.png"
page.screenshot(path=str(dialog_shot), full_page=True)
results["pages"]["dense-lane-outbound"]["mockDialogScreenshot"] = str(dialog_shot)
results["pages"]["dense-lane-outbound"]["mockDialog"] = "verified-with-read-only-mocks"
page.set_viewport_size({"width": 390, "height": 844})
page.goto(BASE_URL + "/out/denseLaneOutbound", wait_until="domcontentloaded")
wait_for_app(page)
assert_text(page, "指定通道", "通道组编码")
narrow_shot = OUTPUT_DIR / "dense-lane-outbound-narrow.png"
page.screenshot(path=str(narrow_shot), full_page=True)
results["pages"]["dense-lane-outbound"]["narrowScreenshot"] = str(narrow_shot)
results["pages"]["dense-lane-outbound"]["narrowOverflow"] = page_overflow(page)
context.close()
browser.close()
ignored_console_fragments = ("ResizeObserver loop",)
results["consoleErrors"] = [message for message in results["consoleErrors"]
if not any(fragment in message for fragment in ignored_console_fragments)]
if results["consoleErrors"] or results["requestFailures"]:
raise AssertionError(json.dumps(results, ensure_ascii=False, indent=2))
print(json.dumps(results, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()