import type { Locator, Page } from "playwright"; import type { AxelSelectionBridge } from "@/workers/rpa/axel-selection-bridge"; import type { AddressLookupInput } from "@/modules/address/types"; import { getRpaPlaceFixMode } from "@/lib/rpa/env"; import { clickMothershipStyledSuggestion, reopenStyledSuggestionDropdown, waitForStyledSuggestionRows, } from "@/workers/rpa/mothership-styled-suggestion-click"; import { commitViaGooglePlaceEvent } from "./google-event-commit"; import { commitViaReactStateInjection } from "./react-state-commit"; import { commitViaBrowserPlaceFetch } from "./browser-place-commit"; import { commitViaCdpDomClick } from "./cdp-pac-commit"; import { commitViaDirectPlaceChangedListener } from "./direct-listener-commit"; import { loadReconVerdict } from "./recon-helpers"; import { isPlaceDiagEnabled, logPlaceDiagnostic, setPlaceDiagMark, writePlaceDiagnosticArtifact, } from "./place-diagnostic-probe"; const STYLED_CLICK_RETRIES = 3; async function diagPhase( page: Page, phase: string, runId: string, ): Promise { if (!isPlaceDiagEnabled()) { return; } const snapshot = await logPlaceDiagnostic(page, phase); if (snapshot.ok) { writePlaceDiagnosticArtifact(runId, phase, snapshot); } } async function tryStyledClickCommit( page: Page, bridge: AxelSelectionBridge, placeId: string, description: string, side: "pickup" | "delivery", input: Locator, query: AddressLookupInput, ): Promise<{ ok: boolean; method: string }> { for (let attempt = 0; attempt < STYLED_CLICK_RETRIES; attempt += 1) { if (attempt > 0) { await reopenStyledSuggestionDropdown(page, input, query, description); await waitForStyledSuggestionRows(page, [], query, 8_000); } const styled = await clickMothershipStyledSuggestion( page, description, query, { input, side, bridge, placeId, skipReopen: attempt > 0, maxStrategyAttempts: 3, }, ); if (!styled.clicked) { console.log( `[rpa] fix-place: styled-click attempt=${attempt + 1} miss reason=${styled.reason ?? "unknown"}`, ); continue; } const committed = await bridge.waitForPlaceCommit(placeId, 8_000); const httpOk = bridge.hasSuccessfulPlaceNetworkRequest(placeId); if (committed && httpOk) { console.log( `[rpa] fix-place: mothership-styled-click 成功 method=${styled.method ?? "styled"}`, ); return { ok: true, method: styled.method ?? "mothership-styled-click" }; } console.log( `[rpa] fix-place: styled-click attempt=${attempt + 1} 无 place 200 committed=${committed} httpOk=${httpOk}`, ); } return { ok: false, method: "mothership-styled-click-failed" }; } export async function commitPlaceWithStrategy( page: Page, bridge: AxelSelectionBridge, placeId: string, description: string, side: "pickup" | "delivery", pickupInfo?: { placeId: string; description: string; location: Record; }, options?: { input?: Locator; query?: AddressLookupInput; }, ): Promise<{ ok: boolean; method: string }> { const mode = getRpaPlaceFixMode(); const verdict = loadReconVerdict(); const diagRunId = `${side}-${placeId.slice(0, 12)}`; const googleAvailable = verdict.googleMapsLoaded && verdict.triggerAvailable && verdict.placeChangedTriggerOk; const fiberAvailable = verdict.reactFiberKey !== "not-found"; if (options?.input && options?.query) { await setPlaceDiagMark(page, `${diagRunId}-before-styled`); const styled = await tryStyledClickCommit( page, bridge, placeId, description, side, options.input, options.query, ); if (styled.ok) { await diagPhase(page, `${diagRunId}-after-styled`, diagRunId); return styled; } await diagPhase(page, `${diagRunId}-styled-miss`, diagRunId); } if (mode === "legacy") { return { ok: false, method: "legacy-skip" }; } if (options?.input && options?.query) { await setPlaceDiagMark(page, `${diagRunId}-before-cdp`); const cdpResult = await commitViaCdpDomClick( page, bridge, placeId, description, { candidateIndex: bridge.getCandidateIndex(placeId), query: options.query, }, ); if ( cdpResult.ok && bridge.hasSuccessfulPlaceNetworkRequest(placeId) ) { await diagPhase(page, `${diagRunId}-after-cdp`, diagRunId); return cdpResult; } await diagPhase(page, `${diagRunId}-cdp-miss`, diagRunId); } await setPlaceDiagMark(page, `${diagRunId}-before-direct-listener`); const directListener = await commitViaDirectPlaceChangedListener( page, bridge, placeId, description, ); if ( directListener.ok && bridge.hasSuccessfulPlaceNetworkRequest(placeId) ) { await diagPhase(page, `${diagRunId}-after-direct-listener`, diagRunId); return directListener; } await diagPhase(page, `${diagRunId}-direct-listener-miss`, diagRunId); await setPlaceDiagMark(page, `${diagRunId}-before-browser-fetch`); const browserFetch = await commitViaBrowserPlaceFetch( page, bridge, placeId, description, side, ); if ( browserFetch.ok && bridge.hasSuccessfulPlaceNetworkRequest(placeId) ) { await diagPhase(page, `${diagRunId}-after-browser-fetch`, diagRunId); return browserFetch; } await diagPhase(page, `${diagRunId}-browser-fetch-miss`, diagRunId); if (mode === "event" && googleAvailable) { await setPlaceDiagMark(page, `${diagRunId}-before-google-event`); const result = await commitViaGooglePlaceEvent( page, bridge, placeId, description, ); if ( result.ok && bridge.hasSuccessfulPlaceNetworkRequest(placeId) ) { await diagPhase(page, `${diagRunId}-after-google-event`, diagRunId); console.log("[rpa] fix-place: google-event 成功"); return { ok: true, method: "google-event" }; } await diagPhase(page, `${diagRunId}-google-event-miss`, diagRunId); console.log("[rpa] fix-place: google-event 失败,降级到 react-state"); } else if (mode === "event" && !googleAvailable) { console.log( "[rpa] fix-place: verdict 无 Google Autocomplete,跳过 google-event", ); } if (fiberAvailable) { const stateResult = await commitViaReactStateInjection( page, bridge, placeId, description, side, pickupInfo, ); if ( stateResult.ok && bridge.hasSuccessfulPlaceNetworkRequest(placeId) ) { console.log("[rpa] fix-place: react-state 成功"); return { ok: true, method: "react-state" }; } } else { console.log("[rpa] fix-place: verdict 无 React Fiber,跳过 react-state"); } return { ok: false, method: "all-failed" }; }