/** * P0/P2 place commit 诊断探针 * * PowerShell: * $env:RPA_PLACE_DIAG="true" * $env:RPA_HEADLESS="false" * npx tsx scripts/probe-place-diagnostic.ts */ process.env.RPA_PLACE_DIAG = "true"; 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 type { QuoteRequest } from "@/modules/providers/quote-provider"; import { getSelector } from "@/lib/rpa/selectors"; import { AxelSelectionBridge } from "@/workers/rpa/axel-selection-bridge"; import { ensureAddressSuggestions, } from "@/workers/rpa/address-suggestions"; import { openAddressSide, visibleStreetInput } from "@/workers/rpa/address-side"; import { commitPlaceWithStrategy } from "@/workers/rpa/fix-place"; import { collectPlaceDiagSnapshot, interpretPlaceDiagnostic, setPlaceDiagMark, } from "@/workers/rpa/fix-place/place-diagnostic-probe"; import { P0_PLACE_DIAG_INIT, P2_FETCH_HIJACK_INIT, } from "@/workers/rpa/fix-place/place-diagnostic-init"; import { RPAContext } from "@/workers/rpa/kernel/context"; import { stabilizeQuoteLandingPage, waitForQuoterWidgetHydrated, } from "@/workers/rpa/page-prep"; import { closeBrowser, openQuotePage } from "@/workers/rpa/session-manager"; import { quotePageAdapter } from "@/workers/rpa/quote-page-adapter"; loadDevEnv(); process.env.RPA_MOCK_MODE = "false"; process.env.RPA_ADDRESS_MODE = "real"; if (!process.env.RPA_HEADLESS) { process.env.RPA_HEADLESS = "false"; } const BASE = process.env.PROVE_BASE_URL ?? "http://localhost:3000"; const TOKEN = "demo-host-token"; const CUSTOMER_ID = "CUST_001"; const OUT_DIR = join(process.cwd(), ".rpa", "place-diagnostic"); const PAIRS = { pickup: { street: "Washington State Convention Center Main Garage, Pike Street", city: "Seattle", state: "WA", }, delivery: { street: "Ocean Drive", city: "Miami Beach", state: "FL", }, } as const; function draftAddress(side: keyof typeof PAIRS) { const d = PAIRS[side]; return { street: d.street, city: d.city, state: d.state, zip: "", place_id: `draft_${side}`, formatted_address: `${d.street}, ${d.city}, ${d.state}`, selected_from_suggestions: true, }; } async function main(): Promise { const runId = randomUUID().slice(0, 8); const side = (process.env.PROBE_PLACE_SIDE === "delivery" ? "delivery" : "pickup") as | "pickup" | "delivery"; mkdirSync(OUT_DIR, { recursive: true }); const candRes = await hostFetchMothershipCandidates( BASE, TOKEN, CUSTOMER_ID, draftAddress("pickup"), draftAddress("delivery"), ); if (candRes.code !== 0 || !candRes.data) { throw new Error(candRes.message ?? "候选 API 失败"); } const candidate = side === "pickup" ? candRes.data.pickup_candidates[0] : candRes.data.delivery_candidates[0]; if (!candidate) { throw new Error(`${side} 无候选`); } const applied = applyMothershipCandidate(draftAddress(side), candidate); const address: QuoteRequest["pickup"] = { street: applied.street, city: applied.city, state: applied.state, zip: applied.zip ?? "", placeId: candidate.option_id, formattedAddress: applied.formatted_address, selectedFromSuggestions: true, mothershipOptionId: candidate.option_id, mothershipDisplayLabel: candidate.display_label, selectedFromMothership: true, }; const queryHint = { street: address.street, city: address.city ?? "", state: address.state ?? "", zip: address.zip ?? "", }; console.log(`\n=== place-diagnostic probe run=${runId} side=${side} ===`); console.log(`label: ${address.mothershipDisplayLabel}`); console.log(`placeId: ${candidate.option_id?.slice(0, 32)}…\n`); 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" }); const ctx = RPAContext.fromSession(opened.page, opened.context); await stabilizeQuoteLandingPage(ctx); await waitForQuoterWidgetHydrated(ctx.page, 60_000); await setPlaceDiagMark(ctx.page, `${runId}-page-ready`); let baseline = await collectPlaceDiagSnapshot(ctx.page); if (!baseline?.ok) { console.warn("[probe] baseline 快照失败,P0 可能未就绪", baseline); baseline = baseline ?? { ok: false, reason: "snapshot-undefined" }; } writeFileSync( join(OUT_DIR, `${runId}-baseline.json`), JSON.stringify(baseline, null, 2), "utf8", ); for (const line of interpretPlaceDiagnostic(baseline, "baseline")) { console.log(line); } await openAddressSide(ctx, side); const street = await visibleStreetInput(ctx, side, { deliverySectionOpened: side === "delivery", }); if (!street) { throw new Error("地址输入框不可见"); } const bridge = new AxelSelectionBridge(); bridge.attach(ctx.page); bridge.clearCandidates(); await ensureAddressSuggestions(ctx.page, street, queryHint, { mothershipLabel: address.mothershipDisplayLabel!, preferConfirmedLabel: true, }); await bridge.waitForSearchResults(12_000); const binding = bridge.resolveSelection(address.mothershipDisplayLabel!, queryHint); if (!binding?.placeId) { throw new Error("search 未绑定 placeId"); } await setPlaceDiagMark(ctx.page, `${runId}-before-fix-place`); const fixResult = await commitPlaceWithStrategy( ctx.page, bridge, binding.placeId, binding.description, side, undefined, { input: street, query: queryHint }, ); const finalSnap = await collectPlaceDiagSnapshot(ctx.page); const report = { runId, side, address: { label: address.mothershipDisplayLabel, placeId: binding.placeId, }, fixResult, bridge: { placeNetworkLog: bridge.getPlaceNetworkLog(), hasPlaceCommit: bridge.hasPlaceCommit(binding.placeId), httpOk: bridge.hasSuccessfulPlaceNetworkRequest(binding.placeId), }, interpretation: interpretPlaceDiagnostic(finalSnap, "after-fix-place"), snapshot: finalSnap, finishedAt: new Date().toISOString(), }; const reportPath = join(OUT_DIR, `${runId}-report.json`); writeFileSync(reportPath, JSON.stringify(report, null, 2), "utf8"); console.log("\n=== fix-place 结果 ==="); console.log(JSON.stringify(fixResult, null, 2)); console.log("\n=== P0/P2 结论 ==="); for (const line of report.interpretation) { console.log(line); } console.log(`\n报告:${reportPath}`); await opened.page.close().catch(() => undefined); await opened.context.close().catch(() => undefined); await closeBrowser(); } main().catch((error) => { console.error(error); process.exit(1); });