import type { Page } from "playwright"; import { resolveRpaQuoteCaptureMs } from "@/lib/rpa/env"; import { RpaError } from "@/modules/rpa/errors"; /** submit 后期望落点(匿名流 sign-up-quote 亦为合法结果页) */ export const POST_SUBMIT_URL_PATTERN = /sign-up-quote|\/quote(?:\/|$|\?)/i; export function isPostSubmitDestination(url: string): boolean { return POST_SUBMIT_URL_PATTERN.test(url); } /** MotherShip 首页内嵌报价弹层(不跳转 sign-up-quote) */ export async function isInlineQuotePanelVisible(page: Page): Promise { const text = await page.locator("body").innerText().catch(() => ""); const hasQuoteChrome = /选择您的报价|Select your quote|shared truck|共享卡车|专属卡车|最优性价比|Best value|Verified carriers/i.test( text, ); const hasPrice = /\$\s?\d[\d,]*(?:\.\d{2})?/.test(text); return hasQuoteChrome && hasPrice; } const POST_SUBMIT_POLL_MS = 250; export async function waitForPostSubmitDestination( page: Page, timeoutMs = 20_000, ): Promise { const effectiveTimeoutMs = resolveRpaQuoteCaptureMs(timeoutMs); const deadline = Date.now() + effectiveTimeoutMs; while (Date.now() < deadline) { const url = page.url(); if (isPostSubmitDestination(url)) { return url; } if (await isInlineQuotePanelVisible(page)) { console.log("[rpa] post-submit: 首页内嵌报价弹层已出现"); return url; } await page.waitForTimeout(POST_SUBMIT_POLL_MS); } throw new RpaError( "QUOTE_ENTRY_UNAVAILABLE", `submit 后未到达报价结果页(url=${page.url()})`, { retryable: true }, ); }