@enricai/barnacle 1.9.12 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scraper/deep-locator-candidates.d.ts +42 -12
- package/dist/scraper/deep-locator-candidates.d.ts.map +1 -1
- package/dist/scraper/deep-locator-candidates.js +48 -21
- package/dist/scraper/deep-locator-candidates.js.map +1 -1
- package/dist/scraper/deep-locator-scan.d.ts +23 -13
- package/dist/scraper/deep-locator-scan.d.ts.map +1 -1
- package/dist/scraper/deep-locator-scan.js +23 -13
- package/dist/scraper/deep-locator-scan.js.map +1 -1
- package/dist/scraper/flow-runner.d.ts +7 -7
- package/dist/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +187 -123
- package/dist/scraper/flow-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -5572,6 +5572,114 @@ async function executeStepWithHealing(params) {
|
|
|
5572
5572
|
// observable effect (see classifyPhantomClick). Reroutes attempt 2 to
|
|
5573
5573
|
// deep-submit-locator instead of observe-act, since re-resolving via the
|
|
5574
5574
|
// same light-DOM view cannot reach a target the resolver can't see.
|
|
5575
|
+
// Shared deepLocator actionable-candidate walk, invoked by BOTH the
|
|
5576
|
+
// observe-blind attempt-2/4 branch and the phantom-driven `trusted-click-retry`
|
|
5577
|
+
// branch. Walks ranked `candidates`, skipping wizard-exit and not-actionable
|
|
5578
|
+
// ones, actuating the first that sticks (`clickFirstActionableCandidate`), then
|
|
5579
|
+
// synthesizes a `resolvedAction` the standard verifier consumes. Returns
|
|
5580
|
+
// `resolvedAction` on success, or `null` on failure (the caller `continue`s).
|
|
5581
|
+
// `preferTrustedClick` forwards to `clickDeepLocatorCandidate` so the retry
|
|
5582
|
+
// path activates via the trusted CDP click; `labelPrefix` distinguishes the
|
|
5583
|
+
// two callers in the audit record. On failure the running `triedSelectors`
|
|
5584
|
+
// (which already holds prior attempts' resolved xpath) is written to
|
|
5585
|
+
// `record.triedSelectors`, not just this walk's own — so a walk that resolved
|
|
5586
|
+
// only deny-listed candidates still exposes a prior xpath to
|
|
5587
|
+
// `shouldSkipTechnique`'s `anyXpathResolved` gate, keeping `structured-click`
|
|
5588
|
+
// reachable at attempt 3.
|
|
5589
|
+
const runDeepLocatorClickWalk = async (args) => {
|
|
5590
|
+
const { candidates, innerSelector, preferTrustedClick, labelPrefix, record, attempt, pre } = args;
|
|
5591
|
+
const attemptTriedSelectors = [];
|
|
5592
|
+
const denyCandidate = (candidate) => {
|
|
5593
|
+
const denied = isWizardExitAction(candidate.accessibleText, wizardExitButtonLabels);
|
|
5594
|
+
if (denied) {
|
|
5595
|
+
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} attempt ${attempt}: refused wizard-exit control: "${candidate.accessibleText.slice(0, 60)}"`);
|
|
5596
|
+
}
|
|
5597
|
+
return denied;
|
|
5598
|
+
};
|
|
5599
|
+
const actuation = resolveDeepLocatorActuation(step);
|
|
5600
|
+
const isSelectionIntentClick = actuation.kind === "click" && parseSelectStep(step) !== null;
|
|
5601
|
+
const baselineSelectionCount = isSelectionIntentClick
|
|
5602
|
+
? selectionCountFromSignature(pre.visibleTextSignature)
|
|
5603
|
+
: null;
|
|
5604
|
+
const readSelectionCount = isSelectionIntentClick
|
|
5605
|
+
? async () => {
|
|
5606
|
+
try {
|
|
5607
|
+
const snap = await snapshotPage(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, page);
|
|
5608
|
+
return selectionCountFromSignature(snap.visibleTextSignature);
|
|
5609
|
+
}
|
|
5610
|
+
catch {
|
|
5611
|
+
return null;
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
: undefined;
|
|
5615
|
+
const cascadeResult = await (0, deep_locator_click_1.clickFirstActionableCandidate)(candidates, async (candidate) => {
|
|
5616
|
+
attemptTriedSelectors.push(candidate.selector);
|
|
5617
|
+
if (actuation.kind === "select") {
|
|
5618
|
+
const verified = await (0, deep_locator_actuate_1.selectDeepLocatorCandidateOption)(page, frameTarget?.frameSelector, innerSelector, candidate.index, actuation.value, { frameTarget });
|
|
5619
|
+
// selectDeepLocatorCandidateOption never throws on an ordinary failed
|
|
5620
|
+
// write (see deep-locator-actuate.ts's writeAndVerify) — it resolves
|
|
5621
|
+
// `false` for both a rejected selectOption() and a read-back mismatch.
|
|
5622
|
+
// clickFirstActionableCandidate infers success from "didn't throw", so
|
|
5623
|
+
// a `false` here must become a throw or the walk would wrongly report
|
|
5624
|
+
// this candidate as actuated.
|
|
5625
|
+
if (!verified)
|
|
5626
|
+
throw new Error("-32000 Node does not have a layout object");
|
|
5627
|
+
}
|
|
5628
|
+
else if (actuation.kind === "fill") {
|
|
5629
|
+
const verified = await (0, deep_locator_actuate_1.fillDeepLocatorCandidate)(page, frameTarget?.frameSelector, innerSelector, candidate.index, actuation.value, { frameTarget });
|
|
5630
|
+
if (!verified)
|
|
5631
|
+
throw new Error("-32000 Node does not have a layout object");
|
|
5632
|
+
}
|
|
5633
|
+
else {
|
|
5634
|
+
await (0, deep_locator_candidates_1.clickDeepLocatorCandidate)(page, frameTarget?.frameSelector, innerSelector, candidate.index, {
|
|
5635
|
+
frameTarget,
|
|
5636
|
+
preferTrustedClick,
|
|
5637
|
+
});
|
|
5638
|
+
}
|
|
5639
|
+
}, { denyCandidate, readSelectionCount, baselineSelectionCount })
|
|
5640
|
+
.then((outcome) => ({ outcome, error: null }))
|
|
5641
|
+
.catch((error) => ({ outcome: null, error }));
|
|
5642
|
+
triedSelectors.push(...attemptTriedSelectors);
|
|
5643
|
+
if (cascadeResult.outcome?.clicked && cascadeResult.outcome.candidate) {
|
|
5644
|
+
const acted = cascadeResult.outcome.candidate;
|
|
5645
|
+
record.triedSelectors = [...attemptTriedSelectors];
|
|
5646
|
+
const verb = actuation.kind === "select" ? "selected" : actuation.kind === "fill" ? "filled" : "clicked";
|
|
5647
|
+
record.instruction = `${labelPrefix}: ${acted.accessibleText || "(no accessible text)"}`;
|
|
5648
|
+
record.actResultSuccess = true;
|
|
5649
|
+
record.actResultDescription = `${labelPrefix} ${verb} "${acted.accessibleText || acted.selector}"`;
|
|
5650
|
+
// Synthesize an action matching the actuation kind so downstream
|
|
5651
|
+
// verification (network/url/dom — STATE_CLASS_METHODS treats
|
|
5652
|
+
// fill/selectOption as DOM-verifiable, not click) treats this exactly like
|
|
5653
|
+
// any other resolved action — same idiom as deep-submit-locator/structured-click.
|
|
5654
|
+
const resolvedAction = actuation.kind === "click"
|
|
5655
|
+
? { selector: acted.selector, description: record.actResultDescription, method: "click" }
|
|
5656
|
+
: {
|
|
5657
|
+
selector: acted.selector,
|
|
5658
|
+
description: record.actResultDescription,
|
|
5659
|
+
method: actuation.kind === "select" ? "selectOption" : "fill",
|
|
5660
|
+
arguments: [actuation.value],
|
|
5661
|
+
};
|
|
5662
|
+
return { resolvedAction };
|
|
5663
|
+
}
|
|
5664
|
+
// Failure: carry the running triedSelectors (prior xpath + this walk's own)
|
|
5665
|
+
// into the record so anyXpathResolved stays true even when every resolved
|
|
5666
|
+
// candidate was deny-listed (attemptTriedSelectors would be empty then).
|
|
5667
|
+
record.triedSelectors = [...triedSelectors];
|
|
5668
|
+
const counterStalledCount = cascadeResult.outcome?.counterStalledSelectors.length ?? 0;
|
|
5669
|
+
const failureMessage = cascadeResult.error
|
|
5670
|
+
? `${labelPrefix}: ${actuation.kind} threw ${(0, errors_1.toErrorMessage)(cascadeResult.error)}`
|
|
5671
|
+
: counterStalledCount > 0
|
|
5672
|
+
? `${labelPrefix}: clicked ${counterStalledCount} candidate(s) but the "N selected" counter never rose (no selection registered)`
|
|
5673
|
+
: attemptTriedSelectors.length > 0
|
|
5674
|
+
? `${labelPrefix}: no actionable candidate (${attemptTriedSelectors.length} not-actionable)`
|
|
5675
|
+
: `${labelPrefix}: no actionable candidate (every candidate refused by the wizard-exit deny-list)`;
|
|
5676
|
+
record.actResultSuccess = false;
|
|
5677
|
+
record.errorMessage = failureMessage;
|
|
5678
|
+
attempts.push(record);
|
|
5679
|
+
failureReasons.push(failureMessage);
|
|
5680
|
+
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} attempt ${attempt}: ${failureMessage}`);
|
|
5681
|
+
return { resolvedAction: null };
|
|
5682
|
+
};
|
|
5575
5683
|
let phantomClickAfterAttempt1 = false;
|
|
5576
5684
|
for (let attempt = 1; attempt <= MAX_STEP_ATTEMPTS; attempt++) {
|
|
5577
5685
|
// Telemetry-driven technique-skip: when a cascade technique's
|
|
@@ -5583,7 +5691,9 @@ async function executeStepWithHealing(params) {
|
|
|
5583
5691
|
const wouldBeTechnique = attempt === 2
|
|
5584
5692
|
? phantomClickAfterAttempt1 && (isFinalStep || submitStep)
|
|
5585
5693
|
? "deep-submit-locator"
|
|
5586
|
-
:
|
|
5694
|
+
: phantomClickAfterAttempt1
|
|
5695
|
+
? "trusted-click-retry"
|
|
5696
|
+
: "observe-act"
|
|
5587
5697
|
: attempt === 3
|
|
5588
5698
|
? "structured-click"
|
|
5589
5699
|
: attempt === 4
|
|
@@ -5842,6 +5952,62 @@ async function executeStepWithHealing(params) {
|
|
|
5842
5952
|
}
|
|
5843
5953
|
}
|
|
5844
5954
|
}
|
|
5955
|
+
else if (attempt === 2 && phantomClickAfterAttempt1 && !(isFinalStep || submitStep)) {
|
|
5956
|
+
// Trusted-click retry: attempt 1 phantom-clicked a NON-submit control —
|
|
5957
|
+
// Stagehand reported success but pre/post showed zero effect. On a
|
|
5958
|
+
// design-system widget (React synthetic-event delegation, custom
|
|
5959
|
+
// event-bound wrappers) the likely cause is that the attempt-1 click was
|
|
5960
|
+
// an in-page, `isTrusted=false` activation the handler ignores — the
|
|
5961
|
+
// element resolves fine, but only a REAL user gesture registers. So
|
|
5962
|
+
// re-resolve the target from the step's tagged phrases and re-click it
|
|
5963
|
+
// via the trusted CDP path (`clickDeepLocatorCandidate` →
|
|
5964
|
+
// `deepLocator().nth().click()`, an `Input.dispatchMouseEvent`). This is
|
|
5965
|
+
// the non-submit sibling of the deep-submit-locator escalation above.
|
|
5966
|
+
record.technique = "trusted-click-retry";
|
|
5967
|
+
// Carry forward any selector prior attempts already resolved (e.g.
|
|
5968
|
+
// attempt-1 act-string's xpath) so a trusted-click-retry that can't
|
|
5969
|
+
// resolve a deepLocator candidate doesn't erase the xpath signal
|
|
5970
|
+
// structured-click's precondition (shouldSkipTechnique) depends on —
|
|
5971
|
+
// this attempt is an ADDITIONAL recovery, not a replacement that
|
|
5972
|
+
// demotes the rest of the ladder.
|
|
5973
|
+
if (!frameTarget?.frame) {
|
|
5974
|
+
const failureMessage = "trusted-click-retry: no frame seam available for a trusted deepLocator click";
|
|
5975
|
+
record.actResultSuccess = false;
|
|
5976
|
+
record.errorMessage = failureMessage;
|
|
5977
|
+
record.triedSelectors = [...triedSelectors];
|
|
5978
|
+
attempts.push(record);
|
|
5979
|
+
failureReasons.push(failureMessage);
|
|
5980
|
+
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} attempt ${attempt}: ${failureMessage}`);
|
|
5981
|
+
continue;
|
|
5982
|
+
}
|
|
5983
|
+
await reresolveFrameTargetIfLost();
|
|
5984
|
+
const { candidates: retryCandidates, innerSelector: retryInnerSelector } = await resolveDeepLocatorCandidatesWithWidening(page, frameTarget.frameSelector, step, {
|
|
5985
|
+
frameTarget,
|
|
5986
|
+
});
|
|
5987
|
+
const deepLocatorCandidates = retryCandidates.filter((c) => !triedSelectors.includes(c.selector));
|
|
5988
|
+
if (deepLocatorCandidates.length === 0) {
|
|
5989
|
+
const failureMessage = "trusted-click-retry: no deepLocator candidate resolved for the phantomed target";
|
|
5990
|
+
record.actResultSuccess = false;
|
|
5991
|
+
record.errorMessage = failureMessage;
|
|
5992
|
+
record.triedSelectors = [...triedSelectors];
|
|
5993
|
+
attempts.push(record);
|
|
5994
|
+
failureReasons.push(failureMessage);
|
|
5995
|
+
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} attempt ${attempt}: ${failureMessage}`);
|
|
5996
|
+
continue;
|
|
5997
|
+
}
|
|
5998
|
+
const { resolvedAction: retryResolvedAction } = await runDeepLocatorClickWalk({
|
|
5999
|
+
candidates: deepLocatorCandidates,
|
|
6000
|
+
innerSelector: retryInnerSelector,
|
|
6001
|
+
preferTrustedClick: true,
|
|
6002
|
+
labelPrefix: "trusted-click-retry",
|
|
6003
|
+
record,
|
|
6004
|
+
attempt,
|
|
6005
|
+
pre,
|
|
6006
|
+
});
|
|
6007
|
+
if (!retryResolvedAction)
|
|
6008
|
+
continue;
|
|
6009
|
+
resolvedAction = retryResolvedAction;
|
|
6010
|
+
}
|
|
5845
6011
|
else if (attempt === 2 || attempt === 4) {
|
|
5846
6012
|
record.technique = attempt === 2 ? "observe-act" : "observe-act-exclude";
|
|
5847
6013
|
const observeOptions = attempt === 4 && triedSelectors.length > 0
|
|
@@ -5929,128 +6095,26 @@ async function executeStepWithHealing(params) {
|
|
|
5929
6095
|
continue;
|
|
5930
6096
|
}
|
|
5931
6097
|
}
|
|
5932
|
-
// Actionable-candidate walk
|
|
5933
|
-
//
|
|
5934
|
-
// node) or is refused by
|
|
5935
|
-
//
|
|
5936
|
-
// is tried instead.
|
|
5937
|
-
//
|
|
5938
|
-
//
|
|
5939
|
-
//
|
|
5940
|
-
const
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
// `target.method === "fill"` handling above); the deepLocator
|
|
5951
|
-
// fallback has no such resolved action, so the step prose is the
|
|
5952
|
-
// only place the fill/select value can come from. Derived once per
|
|
5953
|
-
// attempt (not per candidate) — every candidate in this walk is a
|
|
5954
|
-
// guess at the SAME step's target, so they all actuate the same way.
|
|
5955
|
-
const actuation = resolveDeepLocatorActuation(step);
|
|
5956
|
-
// Next-best-candidate recovery for a markerless multi-select phantom:
|
|
5957
|
-
// a click-to-toggle option (not a native <select> write, which
|
|
5958
|
-
// already read-back-verifies) whose "N selected" counter doesn't rise
|
|
5959
|
-
// means the click landed but didn't register — so the walk should try
|
|
5960
|
-
// the next real option before this attempt is scored a failure and a
|
|
5961
|
-
// replan is spent. Only wired for a selection-intent CLICK step; the
|
|
5962
|
-
// reader re-reads the same counter `pre` captured (via
|
|
5963
|
-
// `selectionCountFromSignature`) so no extra pre-snapshot is paid, and
|
|
5964
|
-
// returns `null` on any frame hiccup so a read failure never converts
|
|
5965
|
-
// to a walk-stopping throw. Non-selection steps pass `undefined` and
|
|
5966
|
-
// the walk behaves exactly as before.
|
|
5967
|
-
const isSelectionIntentClick = actuation.kind === "click" && parseSelectStep(step) !== null;
|
|
5968
|
-
const baselineSelectionCount = isSelectionIntentClick
|
|
5969
|
-
? selectionCountFromSignature(pre.visibleTextSignature)
|
|
5970
|
-
: null;
|
|
5971
|
-
const readSelectionCount = isSelectionIntentClick
|
|
5972
|
-
? async () => {
|
|
5973
|
-
try {
|
|
5974
|
-
const snap = await snapshotPage(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page), signalCounter, page);
|
|
5975
|
-
return selectionCountFromSignature(snap.visibleTextSignature);
|
|
5976
|
-
}
|
|
5977
|
-
catch {
|
|
5978
|
-
return null;
|
|
5979
|
-
}
|
|
5980
|
-
}
|
|
5981
|
-
: undefined;
|
|
5982
|
-
const cascadeResult = await (0, deep_locator_click_1.clickFirstActionableCandidate)(deepLocatorCandidates, async (candidate) => {
|
|
5983
|
-
attemptTriedSelectors.push(candidate.selector);
|
|
5984
|
-
if (actuation.kind === "select") {
|
|
5985
|
-
const verified = await (0, deep_locator_actuate_1.selectDeepLocatorCandidateOption)(page, frameTarget?.frameSelector, deepLocatorInnerSelector, candidate.index, actuation.value, { frameTarget });
|
|
5986
|
-
// selectDeepLocatorCandidateOption never throws on an ordinary
|
|
5987
|
-
// failed write (see deep-locator-actuate.ts's writeAndVerify) —
|
|
5988
|
-
// it resolves `false` for both a rejected selectOption() and a
|
|
5989
|
-
// read-back mismatch. clickFirstActionableCandidate infers
|
|
5990
|
-
// success from "didn't throw", so a `false` here must become a
|
|
5991
|
-
// throw or the walk would wrongly report this candidate as
|
|
5992
|
-
// actuated.
|
|
5993
|
-
if (!verified)
|
|
5994
|
-
throw new Error("-32000 Node does not have a layout object");
|
|
5995
|
-
}
|
|
5996
|
-
else if (actuation.kind === "fill") {
|
|
5997
|
-
const verified = await (0, deep_locator_actuate_1.fillDeepLocatorCandidate)(page, frameTarget?.frameSelector, deepLocatorInnerSelector, candidate.index, actuation.value, { frameTarget });
|
|
5998
|
-
if (!verified)
|
|
5999
|
-
throw new Error("-32000 Node does not have a layout object");
|
|
6000
|
-
}
|
|
6001
|
-
else {
|
|
6002
|
-
await (0, deep_locator_candidates_1.clickDeepLocatorCandidate)(page, frameTarget?.frameSelector, deepLocatorInnerSelector, candidate.index, { frameTarget });
|
|
6003
|
-
}
|
|
6004
|
-
}, { denyCandidate, readSelectionCount, baselineSelectionCount })
|
|
6005
|
-
.then((outcome) => ({ outcome, error: null }))
|
|
6006
|
-
.catch((error) => ({ outcome: null, error }));
|
|
6007
|
-
triedSelectors.push(...attemptTriedSelectors);
|
|
6008
|
-
record.triedSelectors = [...attemptTriedSelectors];
|
|
6009
|
-
if (cascadeResult.outcome?.clicked && cascadeResult.outcome.candidate) {
|
|
6010
|
-
const acted = cascadeResult.outcome.candidate;
|
|
6011
|
-
const verb = actuation.kind === "select"
|
|
6012
|
-
? "selected"
|
|
6013
|
-
: actuation.kind === "fill"
|
|
6014
|
-
? "filled"
|
|
6015
|
-
: "clicked";
|
|
6016
|
-
record.instruction = `deepLocator: ${acted.accessibleText || "(no accessible text)"}`;
|
|
6017
|
-
record.actResultSuccess = true;
|
|
6018
|
-
record.actResultDescription = `deepLocator ${verb} "${acted.accessibleText || acted.selector}"`;
|
|
6019
|
-
// Synthesize an action matching the actuation kind so downstream
|
|
6020
|
-
// verification (network/url/dom — STATE_CLASS_METHODS treats
|
|
6021
|
-
// fill/selectOption as DOM-verifiable, not click) treats this
|
|
6022
|
-
// exactly like any other resolved action — same idiom as
|
|
6023
|
-
// deep-submit-locator/structured-click.
|
|
6024
|
-
resolvedAction =
|
|
6025
|
-
actuation.kind === "click"
|
|
6026
|
-
? {
|
|
6027
|
-
selector: acted.selector,
|
|
6028
|
-
description: record.actResultDescription,
|
|
6029
|
-
method: "click",
|
|
6030
|
-
}
|
|
6031
|
-
: {
|
|
6032
|
-
selector: acted.selector,
|
|
6033
|
-
description: record.actResultDescription,
|
|
6034
|
-
method: actuation.kind === "select" ? "selectOption" : "fill",
|
|
6035
|
-
arguments: [actuation.value],
|
|
6036
|
-
};
|
|
6037
|
-
}
|
|
6038
|
-
else {
|
|
6039
|
-
const counterStalledCount = cascadeResult.outcome?.counterStalledSelectors.length ?? 0;
|
|
6040
|
-
const failureMessage = cascadeResult.error
|
|
6041
|
-
? `deepLocator: ${actuation.kind} threw ${(0, errors_1.toErrorMessage)(cascadeResult.error)}`
|
|
6042
|
-
: counterStalledCount > 0
|
|
6043
|
-
? `deepLocator: clicked ${counterStalledCount} candidate(s) but the "N selected" counter never rose (no selection registered)`
|
|
6044
|
-
: attemptTriedSelectors.length > 0
|
|
6045
|
-
? `deepLocator: no actionable candidate (${attemptTriedSelectors.length} not-actionable)`
|
|
6046
|
-
: "deepLocator: no actionable candidate (every candidate refused by the wizard-exit deny-list)";
|
|
6047
|
-
record.actResultSuccess = false;
|
|
6048
|
-
record.errorMessage = failureMessage;
|
|
6049
|
-
attempts.push(record);
|
|
6050
|
-
failureReasons.push(failureMessage);
|
|
6051
|
-
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} attempt ${attempt}: ${failureMessage}`);
|
|
6098
|
+
// Actionable-candidate walk (shared with the trusted-click-retry
|
|
6099
|
+
// branch): a top pick that rejects with the CDP `-32000 Node does not
|
|
6100
|
+
// have a layout object` error (an unrendered node) or is refused by
|
|
6101
|
+
// the wizard-exit deny-list costs only that one candidate, not the
|
|
6102
|
+
// whole attempt — the next ranked candidate is tried instead. The
|
|
6103
|
+
// observe-act path activates via the synthetic fast click
|
|
6104
|
+
// (preferTrustedClick: false); the trusted CDP click is the
|
|
6105
|
+
// phantom-retry escalation.
|
|
6106
|
+
const { resolvedAction: walkResolvedAction } = await runDeepLocatorClickWalk({
|
|
6107
|
+
candidates: deepLocatorCandidates,
|
|
6108
|
+
innerSelector: deepLocatorInnerSelector,
|
|
6109
|
+
preferTrustedClick: false,
|
|
6110
|
+
labelPrefix: "deepLocator",
|
|
6111
|
+
record,
|
|
6112
|
+
attempt,
|
|
6113
|
+
pre,
|
|
6114
|
+
});
|
|
6115
|
+
if (!walkResolvedAction)
|
|
6052
6116
|
continue;
|
|
6053
|
-
|
|
6117
|
+
resolvedAction = walkResolvedAction;
|
|
6054
6118
|
}
|
|
6055
6119
|
else if (candidates.length === 0) {
|
|
6056
6120
|
record.errorMessage = "observe returned no candidates";
|
|
@@ -7149,7 +7213,7 @@ async function executeStepWithHealing(params) {
|
|
|
7149
7213
|
const suppressedCount = getSuppressedAisdkElementIdErrorCount?.();
|
|
7150
7214
|
const escalationTarget = isFinalStep || submitStep
|
|
7151
7215
|
? "escalating attempt 2 to deep-submit-locator"
|
|
7152
|
-
: "non-submit step —
|
|
7216
|
+
: "non-submit step — escalating attempt 2 to trusted-click-retry (trusted CDP click on the resolved target)";
|
|
7153
7217
|
logger.warn(`${formatStepPrefix(stepIndex, totalSteps)} phantom click detected on attempt 1 (${record.technique}): reported success with no network/url/dom change${suppressedCount !== undefined ? `; ${suppressedCount} AISDK elementId errors suppressed this session (corroborating, not causal)` : ""} — ${escalationTarget}`);
|
|
7154
7218
|
}
|
|
7155
7219
|
const postAttemptInvalidCount = await countNgInvalidContainers(frameTarget ?? (0, frame_target_1.mainFrameTarget)(page));
|