|
|
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<void> {
|
|
|
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<string, unknown>;
|
|
|
},
|
|
|
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" };
|
|
|
}
|