You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

260 lines
7.8 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 登录后 Quick 表单填完后的页面回读校验(不提交)
*/
import type { Page } from "playwright";
import { FLOCK_LOCATION_ACCESSORIALS } from "@/components/flock/flock-logged-in-quote-form";
import { flockLocationRpaLabels } from "@/lib/flock/logged-in-rpa-options";
import { flockLocatorCount } from "@/workers/rpa/flock/page-alive";
function locationAccessorialFlags(typeId: string): {
liftgate: boolean;
inside: boolean;
palletJack: boolean;
} | null {
const flags =
FLOCK_LOCATION_ACCESSORIALS[
typeId as keyof typeof FLOCK_LOCATION_ACCESSORIALS
];
return flags ?? null;
}
export type LoggedInFormVerifyExpect = {
pickupTypeId: string;
deliveryTypeId: string;
pickupZip: string;
deliveryZip: string;
pickupLiftgate?: boolean;
pickupInside?: boolean;
pickupPalletJack?: boolean;
deliveryLiftgate?: boolean;
deliveryInside?: boolean;
deliveryPalletJack?: boolean;
};
export type LoggedInFormVerifyIssue = {
field: string;
expected: string;
actual: string;
analysis: string;
};
async function readComboboxDisplay(
page: Page,
fieldName: RegExp,
): Promise<string | null> {
for (const role of ["combobox", "button"] as const) {
const loc = page.getByRole(role, { name: fieldName }).first();
if ((await flockLocatorCount(loc)) === 0) continue;
const inner = ((await loc.innerText().catch(() => "")) || "").trim();
if (inner) return inner;
const input = await loc.inputValue().catch(() => "");
if (input.trim()) return input.trim();
}
return null;
}
async function readTextbox(
page: Page,
fieldName: RegExp,
): Promise<string | null> {
const loc = page.getByRole("textbox", { name: fieldName }).first();
if ((await flockLocatorCount(loc)) === 0) return null;
return (await loc.inputValue().catch(() => "")) || null;
}
function matchLocationLabel(
actual: string | null,
typeId: string,
): { ok: boolean; expected: string } {
const candidates = [...flockLocationRpaLabels(typeId)];
const primary = candidates[0] ?? typeId;
if (!actual?.trim()) {
return { ok: false, expected: primary };
}
const norm = actual.replace(/\s+/g, " ").trim();
const ok = candidates.some((c) =>
norm.toLowerCase().includes(c.toLowerCase()),
);
return { ok, expected: candidates.join(" | ") };
}
function pushComboboxIssue(
issues: LoggedInFormVerifyIssue[],
side: "Pickup" | "Delivery",
actual: string | null,
typeId: string,
): void {
const { ok, expected } = matchLocationLabel(actual, typeId);
if (ok) return;
issues.push({
field: `${side} Type`,
expected,
actual: actual ?? "(空/未读到)",
analysis:
actual == null
? `官网未出现 ${side} Type 下拉或 RPA 未点开选项;检查 combobox 名称是否仍为「${side} Type *」`
: `选项文案与映射表不一致:系统 id=${typeId},请核对 lib/flock/logged-in-rpa-options.ts`,
});
}
function pushCheckboxIssue(
issues: LoggedInFormVerifyIssue[],
field: string,
want: boolean | undefined,
visible: boolean,
checked: boolean | null,
index: number,
): void {
if (want === undefined) return;
if (want && !visible) {
issues.push({
field,
expected: "勾选且可见",
actual: "页面上无此 checkbox",
analysis:
"系统要求勾选,但官网该地点类型区块未渲染此选项;可能类型切换后附属项 DOM 不同步或 selector 索引错误",
});
return;
}
if (!want && visible && checked === true) {
issues.push({
field,
expected: "不勾选",
actual: "已勾选",
analysis:
"系统未要求勾选但页面仍为选中态RPA setCheckbox(false) 未生效或官网默认强制勾选",
});
return;
}
if (want && visible && checked !== true) {
issues.push({
field,
expected: "已勾选",
actual: checked === false ? "未勾选" : "状态未知",
analysis:
index > 0
? `同名 checkbox 第 ${index + 1} 个未命中;提货/派送区 Liftgate 或 Pallet Jack 可能需调整 nth 索引`
: "RPA applyLoggedInLocationAccessorials 点击失败,或选项在类型切换后才出现需加长等待",
});
}
}
/** 填表后回读官网 DOM对比系统期望 */
export async function verifyLoggedInFormOnPage(
page: Page,
expect: LoggedInFormVerifyExpect,
): Promise<{ ok: boolean; issues: LoggedInFormVerifyIssue[] }> {
const issues: LoggedInFormVerifyIssue[] = [];
const pickupZip = await readTextbox(page, /Pickup ZIP/i);
const deliveryZip = await readTextbox(page, /Delivery ZIP/i);
if (pickupZip?.trim() !== expect.pickupZip) {
issues.push({
field: "Pickup ZIP",
expected: expect.pickupZip,
actual: pickupZip ?? "(空)",
analysis: "ZIP 未写入或字段名变化",
});
}
if (deliveryZip?.trim() !== expect.deliveryZip) {
issues.push({
field: "Delivery ZIP",
expected: expect.deliveryZip,
actual: deliveryZip ?? "(空)",
analysis: "ZIP 未写入或字段名变化",
});
}
const pickupTypeText = await readComboboxDisplay(page, /Pickup Type/i);
const deliveryTypeText = await readComboboxDisplay(page, /Delivery Type/i);
pushComboboxIssue(issues, "Pickup", pickupTypeText, expect.pickupTypeId);
pushComboboxIssue(issues, "Delivery", deliveryTypeText, expect.deliveryTypeId);
const pickupFlags = locationAccessorialFlags(expect.pickupTypeId);
const deliveryFlags = locationAccessorialFlags(expect.deliveryTypeId);
const liftgatePickup = page.getByRole("checkbox", {
name: /Liftgate Required for Pickup/i,
});
const liftgateDelivery = page.getByRole("checkbox", {
name: /Liftgate Required for Delivery/i,
});
const pallet = page.getByRole("checkbox", {
name: /Location has Pallet Jack and Forklift/i,
});
const palletCount = await flockLocatorCount(pallet);
// 提货侧无 Pallet 时nth(0) 实际是派送侧——禁止误读
const pickupPalletIndex = pickupFlags?.palletJack ? 0 : -1;
const deliveryPalletIndex = deliveryFlags?.palletJack
? pickupFlags?.palletJack && palletCount > 1
? 1
: 0
: -1;
const checks: Array<{
field: string;
loc: ReturnType<Page["getByRole"]>;
want: boolean | undefined;
index: number;
enabled: boolean;
}> = [
{
field: "Pickup Liftgate",
loc: liftgatePickup,
want: expect.pickupLiftgate,
index: 0,
enabled: Boolean(pickupFlags?.liftgate),
},
{
field: "Delivery Liftgate",
loc: liftgateDelivery,
want: expect.deliveryLiftgate,
index: 0,
enabled: Boolean(deliveryFlags?.liftgate),
},
{
field: "Pickup Inside",
loc: page.getByRole("checkbox", { name: /Inside Pickup/i }),
want: expect.pickupInside,
index: 0,
enabled: Boolean(pickupFlags?.inside),
},
{
field: "Delivery Inside",
loc: page.getByRole("checkbox", { name: /Inside Delivery/i }),
want: expect.deliveryInside,
index: 0,
enabled: Boolean(deliveryFlags?.inside),
},
{
field: "Pickup Pallet Jack",
loc: pallet,
want: expect.pickupPalletJack,
index: pickupPalletIndex,
enabled: pickupPalletIndex >= 0,
},
{
field: "Delivery Pallet Jack",
loc: pallet,
want: expect.deliveryPalletJack,
index: deliveryPalletIndex,
enabled: deliveryPalletIndex >= 0,
},
];
for (const c of checks) {
if (!c.enabled || c.want === undefined || c.index < 0) continue;
const box = c.loc.nth(c.index);
const visible = (await flockLocatorCount(box)) > 0;
if (!visible && c.want === false) continue;
const checked =
visible ? await box.isChecked().catch(() => false) : null;
pushCheckboxIssue(issues, c.field, c.want, visible, checked, c.index);
}
return { ok: issues.length === 0, issues };
}