@enricai/barnacle 1.9.10 → 1.9.12
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/browser-click-expr.d.ts +44 -0
- package/dist/scraper/browser-click-expr.d.ts.map +1 -0
- package/dist/scraper/browser-click-expr.js +69 -0
- package/dist/scraper/browser-click-expr.js.map +1 -0
- package/dist/scraper/deep-locator-candidates.d.ts +43 -11
- package/dist/scraper/deep-locator-candidates.d.ts.map +1 -1
- package/dist/scraper/deep-locator-candidates.js +72 -19
- package/dist/scraper/deep-locator-candidates.js.map +1 -1
- package/dist/scraper/deep-locator-click.d.ts +40 -2
- package/dist/scraper/deep-locator-click.d.ts.map +1 -1
- package/dist/scraper/deep-locator-click.js +29 -3
- package/dist/scraper/deep-locator-click.js.map +1 -1
- package/dist/scraper/deep-locator-fake.d.ts.map +1 -1
- package/dist/scraper/deep-locator-fake.js +1 -0
- package/dist/scraper/deep-locator-fake.js.map +1 -1
- package/dist/scraper/deep-locator-scan.d.ts +9 -5
- package/dist/scraper/deep-locator-scan.d.ts.map +1 -1
- package/dist/scraper/deep-locator-scan.js +44 -14
- package/dist/scraper/deep-locator-scan.js.map +1 -1
- package/dist/scraper/deep-query.d.ts +5 -5
- package/dist/scraper/deep-query.d.ts.map +1 -1
- package/dist/scraper/deep-query.js +7 -9
- package/dist/scraper/deep-query.js.map +1 -1
- package/dist/scraper/flow-runner.d.ts +46 -0
- package/dist/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +149 -10
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scraper/submit-control.d.ts +5 -3
- package/dist/scraper/submit-control.d.ts.map +1 -1
- package/dist/scraper/submit-control.js +7 -7
- package/dist/scraper/submit-control.js.map +1 -1
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.TRAILING_GRACE_WINDOW = exports.STEP_WATCHDOG_MS = exports.GOTO_TIMEOUT_MS = void 0;
|
|
18
18
|
exports.mergeResponseHeaders = mergeResponseHeaders;
|
|
19
19
|
exports.wireSignalCapture = wireSignalCapture;
|
|
20
|
+
exports.prepareFailureDumpBody = prepareFailureDumpBody;
|
|
20
21
|
exports.snapshotPage = snapshotPage;
|
|
21
22
|
exports.findRecentBackendError = findRecentBackendError;
|
|
22
23
|
exports.findRecentPageTransition = findRecentPageTransition;
|
|
@@ -32,6 +33,8 @@ exports.shouldVetoFallbackAdvance = shouldVetoFallbackAdvance;
|
|
|
32
33
|
exports.isDomOnlyAdvanceVerified = isDomOnlyAdvanceVerified;
|
|
33
34
|
exports.isClickViewSwapVerified = isClickViewSwapVerified;
|
|
34
35
|
exports.isClickStateToggleVerified = isClickStateToggleVerified;
|
|
36
|
+
exports.selectionCountFromSignature = selectionCountFromSignature;
|
|
37
|
+
exports.isSelectionCounterStalled = isSelectionCounterStalled;
|
|
35
38
|
exports.shouldReadbackFillOnActSuccess = shouldReadbackFillOnActSuccess;
|
|
36
39
|
exports.countNgInvalidContainers = countNgInvalidContainers;
|
|
37
40
|
exports.describeAttemptEffectSignals = describeAttemptEffectSignals;
|
|
@@ -95,6 +98,7 @@ const schemas_1 = require("../lib/llm/schemas");
|
|
|
95
98
|
const logging_1 = require("../lib/logging");
|
|
96
99
|
const call_capture_1 = require("../lib/telemetry/call-capture");
|
|
97
100
|
const call_types_1 = require("../lib/telemetry/call-types");
|
|
101
|
+
const browser_click_expr_1 = require("../scraper/browser-click-expr");
|
|
98
102
|
const deep_locator_actuate_1 = require("../scraper/deep-locator-actuate");
|
|
99
103
|
const deep_locator_candidates_1 = require("../scraper/deep-locator-candidates");
|
|
100
104
|
const deep_locator_click_1 = require("../scraper/deep-locator-click");
|
|
@@ -481,6 +485,35 @@ const INVALID_MARKER_EL_EXPR = `((el) => {
|
|
|
481
485
|
* final Continue/Submit click.
|
|
482
486
|
*/
|
|
483
487
|
exports.TRAILING_GRACE_WINDOW = 2;
|
|
488
|
+
/** Cap on a single attribute value before a failure-bundle DOM capture keeps it. */
|
|
489
|
+
const FAILURE_DUMP_MAX_ATTR_LENGTH = 2_000;
|
|
490
|
+
/** Byte budget for a failure-bundle `bodyOuterHtml` capture after attribute pruning. */
|
|
491
|
+
const FAILURE_DUMP_MAX_BODY_LENGTH = 100_000;
|
|
492
|
+
const FAILURE_DUMP_ATTR_RX = new RegExp(`="([^"]{${FAILURE_DUMP_MAX_ATTR_LENGTH},})"|='([^']{${FAILURE_DUMP_MAX_ATTR_LENGTH},})'`, "g");
|
|
493
|
+
/**
|
|
494
|
+
* Prepares a `document.body.outerHTML` string for a step-failure bundle so it
|
|
495
|
+
* shows RENDERED controls, not a cut-off shell. A single data-carrying attribute
|
|
496
|
+
* (a SPA mount point's `data="{…}"` state blob, a long `value=`) can exceed the
|
|
497
|
+
* whole body budget on its own, pushing the actual `<button>`/`<input>` markup
|
|
498
|
+
* past the truncation point — which reads in the bundle as "empty DOM / 0
|
|
499
|
+
* buttons" when the page is fully hydrated. Collapse any attribute value longer
|
|
500
|
+
* than {@link FAILURE_DUMP_MAX_ATTR_LENGTH} to an elided marker BEFORE the length
|
|
501
|
+
* cap, then slice to {@link FAILURE_DUMP_MAX_BODY_LENGTH}. The double- and
|
|
502
|
+
* single-quoted alternatives are kept separate (rather than a `\1` backreference)
|
|
503
|
+
* so a double-quoted value may contain apostrophes — and vice versa — without
|
|
504
|
+
* ending the match early, which a shared `[^"']` class would wrongly do. Returns
|
|
505
|
+
* null passthrough so callers keep their "evaluate failed → null" contract.
|
|
506
|
+
*/
|
|
507
|
+
function prepareFailureDumpBody(raw) {
|
|
508
|
+
if (typeof raw !== "string")
|
|
509
|
+
return null;
|
|
510
|
+
const pruned = raw.replace(FAILURE_DUMP_ATTR_RX, (_m, dq, sq) => {
|
|
511
|
+
const value = dq ?? sq ?? "";
|
|
512
|
+
const quote = dq !== undefined ? '"' : "'";
|
|
513
|
+
return `=${quote}[elided ${value.length} chars]${quote}`;
|
|
514
|
+
});
|
|
515
|
+
return pruned.slice(0, FAILURE_DUMP_MAX_BODY_LENGTH);
|
|
516
|
+
}
|
|
484
517
|
/**
|
|
485
518
|
* Trust boundary: static string literal, fixed at compile time. No interpolation
|
|
486
519
|
* means no injection surface. Runs in browser context and returns a typed-narrow
|
|
@@ -1008,6 +1041,48 @@ function isClickStateToggleVerified(params) {
|
|
|
1008
1041
|
return false;
|
|
1009
1042
|
return selectionStateChanged;
|
|
1010
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Reads the "N settings/items selected" running count that multi-select wizard
|
|
1046
|
+
* steps render as a live heading above the option grid. Returns the integer, or
|
|
1047
|
+
* `null` when the snapshot has no such counter. Operates on a
|
|
1048
|
+
* {@link StepSnapshot.visibleTextSignature}, which is
|
|
1049
|
+
* `"<len>:<first-200-chars-of-innerText>"` — the counter idiom sits at the top
|
|
1050
|
+
* of these steps, so it is always inside the 200-char window.
|
|
1051
|
+
*
|
|
1052
|
+
* Exists because some SPA multi-selects (obfuscated hashed classes, no
|
|
1053
|
+
* `aria-pressed`/`data-state`) expose no selection-state attribute at all, so
|
|
1054
|
+
* {@link isClickStateToggleVerified}'s fingerprint is structurally blind and a
|
|
1055
|
+
* phantom option click rides the generic DOM-delta gate to a FALSE credit — the
|
|
1056
|
+
* running counter is the only trustworthy "did the selection register" signal.
|
|
1057
|
+
*/
|
|
1058
|
+
function selectionCountFromSignature(visibleTextSignature) {
|
|
1059
|
+
const colon = visibleTextSignature.indexOf(":");
|
|
1060
|
+
const text = colon === -1 ? visibleTextSignature : visibleTextSignature.slice(colon + 1);
|
|
1061
|
+
const match = text.match(/(\d+)\s+(?:(?:settings?|items?|options?)\s+)?selected\b/i);
|
|
1062
|
+
const digits = match?.[1];
|
|
1063
|
+
return digits === undefined ? null : Number.parseInt(digits, 10);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Veto for a SELECTION-intent step whose weak DOM signals (view-swap byte delta,
|
|
1067
|
+
* a non-advance `domVerified` reflow) would otherwise credit a click that did
|
|
1068
|
+
* NOT register the selection. When the step's snapshot exposes a running
|
|
1069
|
+
* "N selected" counter (see {@link selectionCountFromSignature}) and that count
|
|
1070
|
+
* did not increase across the click, the option was not actually selected — a
|
|
1071
|
+
* phantom the fingerprint can't catch — so the caller must NOT treat the weak
|
|
1072
|
+
* signals as verification and should keep healing. Returns `true` only when a
|
|
1073
|
+
* counter is present on BOTH snapshots and did not rise; absent a counter it
|
|
1074
|
+
* returns `false` (no veto), so counter-less selection widgets are untouched.
|
|
1075
|
+
*/
|
|
1076
|
+
function isSelectionCounterStalled(params) {
|
|
1077
|
+
const { isSelectionStep, preVisibleTextSignature, postVisibleTextSignature } = params;
|
|
1078
|
+
if (!isSelectionStep)
|
|
1079
|
+
return false;
|
|
1080
|
+
const pre = selectionCountFromSignature(preVisibleTextSignature);
|
|
1081
|
+
const post = selectionCountFromSignature(postVisibleTextSignature);
|
|
1082
|
+
if (pre === null || post === null)
|
|
1083
|
+
return false;
|
|
1084
|
+
return post <= pre;
|
|
1085
|
+
}
|
|
1011
1086
|
/**
|
|
1012
1087
|
* Whether an act-success step warrants a committed-value read-back before the
|
|
1013
1088
|
* weak view-swap/form-value signals are allowed to accept it. A controlled
|
|
@@ -5383,7 +5458,7 @@ async function executeStepWithHealing(params) {
|
|
|
5383
5458
|
const bodyOuterHtmlRaw = await (frameTarget ?? page)
|
|
5384
5459
|
.evaluate("document.body ? document.body.outerHTML : null")
|
|
5385
5460
|
.catch(() => null);
|
|
5386
|
-
const bodyOuterHtml =
|
|
5461
|
+
const bodyOuterHtml = prepareFailureDumpBody(bodyOuterHtmlRaw);
|
|
5387
5462
|
const probeAbsentObservedUnfocused = await (0, stagehand_guard_1.guardedObserve)(stagehand, undefined, { timeout: exports.STEP_WATCHDOG_MS }, captureFn, frameTarget).catch(() => []);
|
|
5388
5463
|
// observe() is blind to a cross-origin OOPIF, so a frame-scoped empty
|
|
5389
5464
|
// result degrades to the deep-locator resolver for dump evidence — this
|
|
@@ -5841,7 +5916,7 @@ async function executeStepWithHealing(params) {
|
|
|
5841
5916
|
if (selectStep && !selectStep.questionLabel) {
|
|
5842
5917
|
const selectCandidates = await (0, deep_locator_candidates_1.resolveDeepLocatorCandidates)(page, frameTarget?.frameSelector, "select", null, { frameTarget });
|
|
5843
5918
|
const optionPhrases = (0, deep_locator_candidates_1.extractTaggedPhrases)(step);
|
|
5844
|
-
const optionScores = selectCandidates.map((candidate) => (0, deep_locator_candidates_1.scoreCandidate)(candidate.accessibleText, optionPhrases));
|
|
5919
|
+
const optionScores = selectCandidates.map((candidate) => (0, deep_locator_candidates_1.scoreCandidate)(candidate.accessibleText, optionPhrases, candidate.isNav));
|
|
5845
5920
|
const topScore = optionScores.length > 0 ? Math.max(...optionScores) : 0;
|
|
5846
5921
|
const tiedForTop = optionScores.filter((score) => score === topScore).length;
|
|
5847
5922
|
if (tiedForTop > 1) {
|
|
@@ -5878,6 +5953,32 @@ async function executeStepWithHealing(params) {
|
|
|
5878
5953
|
// attempt (not per candidate) — every candidate in this walk is a
|
|
5879
5954
|
// guess at the SAME step's target, so they all actuate the same way.
|
|
5880
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;
|
|
5881
5982
|
const cascadeResult = await (0, deep_locator_click_1.clickFirstActionableCandidate)(deepLocatorCandidates, async (candidate) => {
|
|
5882
5983
|
attemptTriedSelectors.push(candidate.selector);
|
|
5883
5984
|
if (actuation.kind === "select") {
|
|
@@ -5900,7 +6001,7 @@ async function executeStepWithHealing(params) {
|
|
|
5900
6001
|
else {
|
|
5901
6002
|
await (0, deep_locator_candidates_1.clickDeepLocatorCandidate)(page, frameTarget?.frameSelector, deepLocatorInnerSelector, candidate.index, { frameTarget });
|
|
5902
6003
|
}
|
|
5903
|
-
}, { denyCandidate })
|
|
6004
|
+
}, { denyCandidate, readSelectionCount, baselineSelectionCount })
|
|
5904
6005
|
.then((outcome) => ({ outcome, error: null }))
|
|
5905
6006
|
.catch((error) => ({ outcome: null, error }));
|
|
5906
6007
|
triedSelectors.push(...attemptTriedSelectors);
|
|
@@ -5935,11 +6036,14 @@ async function executeStepWithHealing(params) {
|
|
|
5935
6036
|
};
|
|
5936
6037
|
}
|
|
5937
6038
|
else {
|
|
6039
|
+
const counterStalledCount = cascadeResult.outcome?.counterStalledSelectors.length ?? 0;
|
|
5938
6040
|
const failureMessage = cascadeResult.error
|
|
5939
6041
|
? `deepLocator: ${actuation.kind} threw ${(0, errors_1.toErrorMessage)(cascadeResult.error)}`
|
|
5940
|
-
:
|
|
5941
|
-
? `deepLocator:
|
|
5942
|
-
:
|
|
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)";
|
|
5943
6047
|
record.actResultSuccess = false;
|
|
5944
6048
|
record.errorMessage = failureMessage;
|
|
5945
6049
|
attempts.push(record);
|
|
@@ -6512,12 +6616,33 @@ async function executeStepWithHealing(params) {
|
|
|
6512
6616
|
}
|
|
6513
6617
|
}
|
|
6514
6618
|
}
|
|
6619
|
+
// Selection-counter veto. A multi-select option click whose widget exposes
|
|
6620
|
+
// no aria/data-state marker (obfuscated hashed classes) leaves
|
|
6621
|
+
// `clickStateToggleVerified` blind, so a phantom that merely reflowed the
|
|
6622
|
+
// DOM would ride the weak `domVerifiedForStep`/`clickViewSwapVerified`
|
|
6623
|
+
// signals to a FALSE credit — the flow then advances to a "Next" that
|
|
6624
|
+
// no-ops because nothing was actually selected. When the step's running
|
|
6625
|
+
// "N selected" counter did not rise, suppress those weak DOM-delta signals
|
|
6626
|
+
// so the cascade keeps trying a real selection. Strong signals (real
|
|
6627
|
+
// network/URL transition) and the counter-independent state-toggle /
|
|
6628
|
+
// form-value signals — each of which IS the selection registering — are
|
|
6629
|
+
// never vetoed; counter-less widgets are untouched (the helper no-ops).
|
|
6630
|
+
const selectionCounterStalled = isSelectionCounterStalled({
|
|
6631
|
+
isSelectionStep: parseSelectStep(step) !== null,
|
|
6632
|
+
preVisibleTextSignature: pre.visibleTextSignature,
|
|
6633
|
+
postVisibleTextSignature: post.visibleTextSignature,
|
|
6634
|
+
});
|
|
6635
|
+
if (selectionCounterStalled && (domVerifiedForStep || clickViewSwapVerified)) {
|
|
6636
|
+
logger.info(`${formatStepPrefix(stepIndex, totalSteps)} selection step credited only via DOM reflow but the "N selected" counter did not rise — the option did not register; not treating the weak DOM signal as verified`);
|
|
6637
|
+
}
|
|
6638
|
+
const domVerifiedAfterCounter = selectionCounterStalled ? false : domVerifiedForStep;
|
|
6639
|
+
const clickViewSwapAfterCounter = selectionCounterStalled ? false : clickViewSwapVerified;
|
|
6515
6640
|
let verified = networkIsRealAdvance ||
|
|
6516
6641
|
urlChanged ||
|
|
6517
|
-
|
|
6642
|
+
domVerifiedAfterCounter ||
|
|
6518
6643
|
datepickerCommitted ||
|
|
6519
6644
|
(!datepickerRejected &&
|
|
6520
|
-
(
|
|
6645
|
+
(clickViewSwapAfterCounter || formValueVerified || clickStateToggleVerified));
|
|
6521
6646
|
// Final-step submit-verification gate. Replaces the deterministic
|
|
6522
6647
|
// submitEndpointPattern regex with a Haiku 4.5 LLM judgment over
|
|
6523
6648
|
// multi-signal evidence (network captures, page URL/title, DOM
|
|
@@ -6695,7 +6820,7 @@ async function executeStepWithHealing(params) {
|
|
|
6695
6820
|
// default action, but isolated-world page.evaluate() click()s don't
|
|
6696
6821
|
// reliably trigger that default action — same gap N+42 documented
|
|
6697
6822
|
// for direct checkbox/radio clicks.
|
|
6698
|
-
const clickExpr = `(() => { const r = document.evaluate(${JSON.stringify(xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); let el = r.singleNodeValue; if (!el || typeof el.click !== "function") return { fired: false }; if (el.tagName === "LABEL") { const wrapped = el.querySelector("input[type=checkbox], input[type=radio]"); if (wrapped) el = wrapped; } if (el.type === "checkbox" || el.type === "radio") { el.checked = true; el.dispatchEvent(new Event("click", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true })); return { fired: true, kind: "checkbox", checked: el.checked }; }
|
|
6823
|
+
const clickExpr = `(() => { const r = document.evaluate(${JSON.stringify(xpath)}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); let el = r.singleNodeValue; if (!el || typeof el.click !== "function") return { fired: false }; if (el.tagName === "LABEL") { const wrapped = el.querySelector("input[type=checkbox], input[type=radio]"); if (wrapped) el = wrapped; } if (el.type === "checkbox" || el.type === "radio") { el.checked = true; el.dispatchEvent(new Event("click", { bubbles: true })); el.dispatchEvent(new Event("change", { bubbles: true })); return { fired: true, kind: "checkbox", checked: el.checked }; } ${(0, browser_click_expr_1.clickActivationExpr)("el")} return { fired: true, kind: "click" }; })()`;
|
|
6699
6824
|
const n16FallbackTarget = frameTarget ?? (0, frame_target_1.mainFrameTarget)(page);
|
|
6700
6825
|
const probeResult = (await n16FallbackTarget.evaluate(clickExpr));
|
|
6701
6826
|
const fired = probeResult.fired;
|
|
@@ -6802,12 +6927,26 @@ async function executeStepWithHealing(params) {
|
|
|
6802
6927
|
// only a strong signal (network/url) or a verified checkbox state may
|
|
6803
6928
|
// pass. Non-final/submit steps are unaffected.
|
|
6804
6929
|
const weakDomSignalsAllowed = (!isFinalStep && !submitStep) || requireSubmitEndpoint;
|
|
6930
|
+
// Selection-counter veto for the n+16 fallback — the same gate the
|
|
6931
|
+
// primary verifier applies, recomputed against `retryPost`. Without
|
|
6932
|
+
// it this path would re-credit a stalled selection the primary veto
|
|
6933
|
+
// already suppressed: the fallback `el.click()` can reflow the DOM
|
|
6934
|
+
// (html/text/selection-state delta) without registering the option,
|
|
6935
|
+
// and those weak deltas are exactly what the OR below admits for a
|
|
6936
|
+
// non-final selection step. Only the weak-delta disjunct is vetoed;
|
|
6937
|
+
// strong signals (network/url) and a verified checkbox state pass.
|
|
6938
|
+
const retrySelectionCounterStalled = isSelectionCounterStalled({
|
|
6939
|
+
isSelectionStep: parseSelectStep(step) !== null,
|
|
6940
|
+
preVisibleTextSignature: pre.visibleTextSignature,
|
|
6941
|
+
postVisibleTextSignature: retryPost.visibleTextSignature,
|
|
6942
|
+
});
|
|
6805
6943
|
let retryVerified = !clickBlockedByInvalid &&
|
|
6806
6944
|
!fallbackDomOnlyAdvance &&
|
|
6807
6945
|
(retryNetworkFired ||
|
|
6808
6946
|
retryUrlChanged ||
|
|
6809
6947
|
checkboxStateVerified ||
|
|
6810
6948
|
(weakDomSignalsAllowed &&
|
|
6949
|
+
!retrySelectionCounterStalled &&
|
|
6811
6950
|
(retryHtmlDelta !== 0 ||
|
|
6812
6951
|
retryTextChanged ||
|
|
6813
6952
|
retryFormValueChanged ||
|
|
@@ -7070,7 +7209,7 @@ async function executeStepWithHealing(params) {
|
|
|
7070
7209
|
const bodyOuterHtmlRaw = await (frameTarget ?? (0, frame_target_1.mainFrameTarget)(page))
|
|
7071
7210
|
.evaluate("document.body ? document.body.outerHTML : null")
|
|
7072
7211
|
.catch(() => null);
|
|
7073
|
-
const bodyOuterHtml =
|
|
7212
|
+
const bodyOuterHtml = prepareFailureDumpBody(bodyOuterHtmlRaw);
|
|
7074
7213
|
const cascadeExhaustObservedUnfocused = await (0, stagehand_guard_1.guardedObserve)(stagehand, undefined, { timeout: exports.STEP_WATCHDOG_MS }, captureFn, frameTarget).catch(() => []);
|
|
7075
7214
|
const unfocusedObserve = cascadeExhaustObservedUnfocused.length === 0 && frameTarget?.frame
|
|
7076
7215
|
? await deepLocatorCandidatesAsActions(page, frameTarget)
|