/** * 自动化审计:Convention Center pickup → place 网络 + DOM 结论 * 输出:.rpa/pickup-commit-audit.json */ process.env.RPA_PLACE_DIAG = "true"; process.env.RPA_HEADLESS = "true"; process.env.RPA_MOCK_MODE = "false"; process.env.RPA_ADDRESS_MODE = "real"; import { randomUUID } from "node:crypto"; import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { loadDevEnv } from "@/lib/dev-env"; import { hostFetchMothershipCandidates } from "@/lib/frontend/api-client"; import { applyMothershipCandidate } from "@/lib/frontend/mothership-address"; import { MotherShipAddressAdapter } from "@/workers/rpa/address-adapter/mothership-address-adapter"; import { openAddressSide, visibleStreetInput } from "@/workers/rpa/address-side"; import { SCAN_INPUT_GM_AND_DOM, SCAN_SUGGESTION_ROWS, } from "@/workers/rpa/fix-place/axel-anatomy-scripts"; import { P0_PLACE_DIAG_INIT, P2_FETCH_HIJACK_INIT, } from "@/workers/rpa/fix-place/place-diagnostic-init"; import { collectPlaceDiagSnapshot } from "@/workers/rpa/fix-place/place-diagnostic-probe"; import { RPAContext } from "@/workers/rpa/kernel/context"; import { getSelector } from "@/lib/rpa/selectors"; import { stabilizeQuoteLandingPage, waitForQuoterWidgetHydrated, } from "@/workers/rpa/page-prep"; import { closeBrowser, openQuotePage } from "@/workers/rpa/session-manager"; import { quotePageAdapter } from "@/workers/rpa/quote-page-adapter"; import type { QuoteRequest } from "@/modules/providers/quote-provider"; loadDevEnv(); const OUT = join(process.cwd(), ".rpa", "pickup-commit-audit.json"); const BASE = process.env.PROVE_BASE_URL ?? "http://localhost:3000"; async function evalScan(page: import("playwright").Page, fn: string): Promise { return page.evaluate((body) => { const runner = eval(`(${body})`) as () => T; return runner(); }, fn); } async function main(): Promise { const runId = randomUUID().slice(0, 8); const pickupDraft = { street: "Washington State Convention Center Main Garage, Pike Street", city: "Seattle", state: "WA", zip: "", place_id: "draft_pickup", formatted_address: "Washington State Convention Center Main Garage, Pike Street, Seattle, WA", selected_from_suggestions: true, }; const deliveryDraft = { street: "Ocean Drive", city: "Miami Beach", state: "FL", zip: "", place_id: "draft_delivery", formatted_address: "Ocean Drive, Miami Beach, FL", selected_from_suggestions: true, }; const cand = await hostFetchMothershipCandidates( BASE, "demo-host-token", "CUST_001", pickupDraft, deliveryDraft, ); if (cand.code !== 0 || !cand.data?.pickup_candidates[0]) { throw new Error(cand.message ?? "候选失败"); } const pc = cand.data.pickup_candidates[0]; const applied = applyMothershipCandidate(pickupDraft, pc); const pickup: QuoteRequest["pickup"] = { street: applied.street, city: applied.city, state: applied.state, zip: applied.zip ?? "", placeId: pc.option_id, formattedAddress: applied.formatted_address, selectedFromSuggestions: true, mothershipOptionId: pc.option_id, mothershipDisplayLabel: pc.display_label, selectedFromMothership: true, }; const net: Array<{ url: string; status?: number; method: string }> = []; const opened = await openQuotePage(quotePageAdapter, false, undefined, { freshCargoContext: true, }); await opened.context.addInitScript({ content: P0_PLACE_DIAG_INIT }); await opened.context.addInitScript({ content: P2_FETCH_HIJACK_INIT }); await opened.page.reload({ waitUntil: "domcontentloaded" }); opened.page.on("response", (res) => { const url = res.url(); if (url.includes("axel/location/")) { net.push({ url: url.slice(0, 260), status: res.status(), method: res.request().method(), }); } }); const ctx = RPAContext.fromSession(opened.page, opened.context); await stabilizeQuoteLandingPage(ctx); await waitForQuoterWidgetHydrated(ctx.page, 90_000); await openAddressSide(ctx, "pickup"); const street = await visibleStreetInput(ctx, "pickup"); if (!street) throw new Error("无输入框"); const adapter = new MotherShipAddressAdapter(); let adapterError: string | null = null; let result: Awaited> | null = null; try { result = await adapter.selectAddress({ ctx, side: "pickup", address: pickup, street, widgetSelector: getSelector("RPA_SELECTOR_QUOTE_WIDGET"), }); } catch (e) { adapterError = e instanceof Error ? e.message : String(e); } const dom = await evalScan>( ctx.page, SCAN_INPUT_GM_AND_DOM, ); const suggestions = await evalScan>( ctx.page, SCAN_SUGGESTION_ROWS, ); const p0 = await collectPlaceDiagSnapshot(ctx.page); const placeCommits = net.filter( (n) => n.url.includes("/axel/location/place/") && !n.url.includes("reverse-geocode") && n.status === 200, ); const report = { runId, label: pickup.mothershipDisplayLabel, placeId: pc.option_id, verdict: { componentType: (dom as { inputs?: Array<{ gmKeys?: string[] }> }).inputs?.some( (i) => (i.gmKeys?.length ?? 0) > 0, ) ? "google-pac-hybrid" : "axel-styled-search", googleMapsOnWindow: Boolean( await ctx.page.evaluate(() => typeof google !== "undefined"), ), adapterCommitted: result?.committed ?? false, adapterError, commitState: adapter.getCommitState(), diagnostics: adapter.getDiagnostics(), placeCommitHttp200Count: placeCommits.length, placeCommitUrls: placeCommits.map((p) => p.url), p0Listeners: p0.listeners?.total ?? 0, p0HandlerEvents: (p0.handlerEvents ?? []).length, p2FetchPlaceCount: (p0.fetchLog ?? []).filter((f) => String((f as { url?: string }).url ?? "").includes("/place/"), ).length, }, dom, suggestions, network: net, p0, finishedAt: new Date().toISOString(), }; mkdirSync(join(process.cwd(), ".rpa"), { recursive: true }); writeFileSync(OUT, JSON.stringify(report, null, 2), "utf8"); console.log(JSON.stringify(report.verdict, null, 2)); console.log(`\n完整报告: ${OUT}`); await opened.page.close().catch(() => undefined); await opened.context.close().catch(() => undefined); await closeBrowser(); } main().catch((e) => { console.error(e); process.exit(1); });