@mercuryo-ai/agentbrowse 0.2.50
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/CHANGELOG.md +15 -0
- package/README.md +335 -0
- package/dist/assistive-runtime.d.ts +110 -0
- package/dist/assistive-runtime.d.ts.map +1 -0
- package/dist/assistive-runtime.js +79 -0
- package/dist/assistive-runtime.test-support.d.ts +7 -0
- package/dist/assistive-runtime.test-support.d.ts.map +1 -0
- package/dist/assistive-runtime.test-support.js +106 -0
- package/dist/assistive-stagehand.d.ts +12 -0
- package/dist/assistive-stagehand.d.ts.map +1 -0
- package/dist/assistive-stagehand.js +10 -0
- package/dist/browser-session-state.d.ts +95 -0
- package/dist/browser-session-state.d.ts.map +1 -0
- package/dist/browser-session-state.js +279 -0
- package/dist/client-bindings.d.ts +10 -0
- package/dist/client-bindings.d.ts.map +1 -0
- package/dist/client-bindings.js +18 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +63 -0
- package/dist/command-api-tracing.d.ts +20 -0
- package/dist/command-api-tracing.d.ts.map +1 -0
- package/dist/command-api-tracing.js +149 -0
- package/dist/command-name.d.ts +3 -0
- package/dist/command-name.d.ts.map +1 -0
- package/dist/command-name.js +11 -0
- package/dist/commands/act.d.ts +43 -0
- package/dist/commands/act.d.ts.map +1 -0
- package/dist/commands/act.js +1107 -0
- package/dist/commands/action-acceptance.d.ts +93 -0
- package/dist/commands/action-acceptance.d.ts.map +1 -0
- package/dist/commands/action-acceptance.js +1938 -0
- package/dist/commands/action-artifacts.d.ts +33 -0
- package/dist/commands/action-artifacts.d.ts.map +1 -0
- package/dist/commands/action-artifacts.js +104 -0
- package/dist/commands/action-execution-guards.d.ts +5 -0
- package/dist/commands/action-execution-guards.d.ts.map +1 -0
- package/dist/commands/action-execution-guards.js +3 -0
- package/dist/commands/action-executor-helpers.d.ts +21 -0
- package/dist/commands/action-executor-helpers.d.ts.map +1 -0
- package/dist/commands/action-executor-helpers.js +265 -0
- package/dist/commands/action-executor.d.ts +14 -0
- package/dist/commands/action-executor.d.ts.map +1 -0
- package/dist/commands/action-executor.js +46 -0
- package/dist/commands/action-fallbacks.d.ts +6 -0
- package/dist/commands/action-fallbacks.d.ts.map +1 -0
- package/dist/commands/action-fallbacks.js +43 -0
- package/dist/commands/action-result-resolution.d.ts +17 -0
- package/dist/commands/action-result-resolution.d.ts.map +1 -0
- package/dist/commands/action-result-resolution.js +132 -0
- package/dist/commands/action-value-projection.d.ts +32 -0
- package/dist/commands/action-value-projection.d.ts.map +1 -0
- package/dist/commands/action-value-projection.js +151 -0
- package/dist/commands/attach.d.ts +41 -0
- package/dist/commands/attach.d.ts.map +1 -0
- package/dist/commands/attach.js +103 -0
- package/dist/commands/browse-actions.d.ts +4 -0
- package/dist/commands/browse-actions.d.ts.map +1 -0
- package/dist/commands/browse-actions.js +4 -0
- package/dist/commands/browser-status.d.ts +57 -0
- package/dist/commands/browser-status.d.ts.map +1 -0
- package/dist/commands/browser-status.js +243 -0
- package/dist/commands/click-action-executor.d.ts +12 -0
- package/dist/commands/click-action-executor.d.ts.map +1 -0
- package/dist/commands/click-action-executor.js +111 -0
- package/dist/commands/click-activation-policy.d.ts +5 -0
- package/dist/commands/click-activation-policy.d.ts.map +1 -0
- package/dist/commands/click-activation-policy.js +13 -0
- package/dist/commands/close.d.ts +26 -0
- package/dist/commands/close.d.ts.map +1 -0
- package/dist/commands/close.js +124 -0
- package/dist/commands/datepicker-action-executor.d.ts +12 -0
- package/dist/commands/datepicker-action-executor.d.ts.map +1 -0
- package/dist/commands/datepicker-action-executor.js +218 -0
- package/dist/commands/descriptor-validation.d.ts +27 -0
- package/dist/commands/descriptor-validation.d.ts.map +1 -0
- package/dist/commands/descriptor-validation.js +192 -0
- package/dist/commands/extract-scope-resolution.d.ts +20 -0
- package/dist/commands/extract-scope-resolution.d.ts.map +1 -0
- package/dist/commands/extract-scope-resolution.js +109 -0
- package/dist/commands/extract-scoped-dialog-text.d.ts +3 -0
- package/dist/commands/extract-scoped-dialog-text.d.ts.map +1 -0
- package/dist/commands/extract-scoped-dialog-text.js +210 -0
- package/dist/commands/extract-snapshot-sanitizer.d.ts +5 -0
- package/dist/commands/extract-snapshot-sanitizer.d.ts.map +1 -0
- package/dist/commands/extract-snapshot-sanitizer.js +98 -0
- package/dist/commands/extract-stagehand-executor.d.ts +17 -0
- package/dist/commands/extract-stagehand-executor.d.ts.map +1 -0
- package/dist/commands/extract-stagehand-executor.js +112 -0
- package/dist/commands/extract.d.ts +57 -0
- package/dist/commands/extract.d.ts.map +1 -0
- package/dist/commands/extract.js +668 -0
- package/dist/commands/interaction-kernel.d.ts +46 -0
- package/dist/commands/interaction-kernel.d.ts.map +1 -0
- package/dist/commands/interaction-kernel.js +215 -0
- package/dist/commands/launch.d.ts +41 -0
- package/dist/commands/launch.d.ts.map +1 -0
- package/dist/commands/launch.js +182 -0
- package/dist/commands/navigate.d.ts +31 -0
- package/dist/commands/navigate.d.ts.map +1 -0
- package/dist/commands/navigate.js +202 -0
- package/dist/commands/observe-accessibility.d.ts +22 -0
- package/dist/commands/observe-accessibility.d.ts.map +1 -0
- package/dist/commands/observe-accessibility.js +566 -0
- package/dist/commands/observe-display-label.d.ts +4 -0
- package/dist/commands/observe-display-label.d.ts.map +1 -0
- package/dist/commands/observe-display-label.js +26 -0
- package/dist/commands/observe-dom-label-contract.d.ts +2 -0
- package/dist/commands/observe-dom-label-contract.d.ts.map +1 -0
- package/dist/commands/observe-dom-label-contract.js +564 -0
- package/dist/commands/observe-fallback-semantics.d.ts +6 -0
- package/dist/commands/observe-fallback-semantics.d.ts.map +1 -0
- package/dist/commands/observe-fallback-semantics.js +86 -0
- package/dist/commands/observe-inventory.d.ts +149 -0
- package/dist/commands/observe-inventory.d.ts.map +1 -0
- package/dist/commands/observe-inventory.js +3545 -0
- package/dist/commands/observe-label-policy.d.ts +8 -0
- package/dist/commands/observe-label-policy.d.ts.map +1 -0
- package/dist/commands/observe-label-policy.js +21 -0
- package/dist/commands/observe-page-state.d.ts +11 -0
- package/dist/commands/observe-page-state.d.ts.map +1 -0
- package/dist/commands/observe-page-state.js +89 -0
- package/dist/commands/observe-persistence.d.ts +15 -0
- package/dist/commands/observe-persistence.d.ts.map +1 -0
- package/dist/commands/observe-persistence.js +238 -0
- package/dist/commands/observe-projection.d.ts +119 -0
- package/dist/commands/observe-projection.d.ts.map +1 -0
- package/dist/commands/observe-projection.js +726 -0
- package/dist/commands/observe-protected.d.ts +6 -0
- package/dist/commands/observe-protected.d.ts.map +1 -0
- package/dist/commands/observe-protected.js +31 -0
- package/dist/commands/observe-semantics.d.ts +10 -0
- package/dist/commands/observe-semantics.d.ts.map +1 -0
- package/dist/commands/observe-semantics.js +535 -0
- package/dist/commands/observe-signals.d.ts +48 -0
- package/dist/commands/observe-signals.d.ts.map +1 -0
- package/dist/commands/observe-signals.js +461 -0
- package/dist/commands/observe-stagehand.d.ts +49 -0
- package/dist/commands/observe-stagehand.d.ts.map +1 -0
- package/dist/commands/observe-stagehand.js +94 -0
- package/dist/commands/observe-surfaces.d.ts +11 -0
- package/dist/commands/observe-surfaces.d.ts.map +1 -0
- package/dist/commands/observe-surfaces.js +290 -0
- package/dist/commands/observe.d.ts +113 -0
- package/dist/commands/observe.d.ts.map +1 -0
- package/dist/commands/observe.js +556 -0
- package/dist/commands/screenshot.d.ts +37 -0
- package/dist/commands/screenshot.d.ts.map +1 -0
- package/dist/commands/screenshot.js +269 -0
- package/dist/commands/select-action-executor.d.ts +10 -0
- package/dist/commands/select-action-executor.d.ts.map +1 -0
- package/dist/commands/select-action-executor.js +156 -0
- package/dist/commands/semantic-observe-lexical.d.ts +31 -0
- package/dist/commands/semantic-observe-lexical.d.ts.map +1 -0
- package/dist/commands/semantic-observe-lexical.js +186 -0
- package/dist/commands/semantic-observe.d.ts +37 -0
- package/dist/commands/semantic-observe.d.ts.map +1 -0
- package/dist/commands/semantic-observe.js +1339 -0
- package/dist/commands/structured-grid-action-executor.d.ts +3 -0
- package/dist/commands/structured-grid-action-executor.d.ts.map +1 -0
- package/dist/commands/structured-grid-action-executor.js +4 -0
- package/dist/commands/target-resolution.d.ts +4 -0
- package/dist/commands/target-resolution.d.ts.map +1 -0
- package/dist/commands/target-resolution.js +33 -0
- package/dist/commands/text-input-action-executor.d.ts +5 -0
- package/dist/commands/text-input-action-executor.d.ts.map +1 -0
- package/dist/commands/text-input-action-executor.js +116 -0
- package/dist/commands/user-actionable.d.ts +4 -0
- package/dist/commands/user-actionable.d.ts.map +1 -0
- package/dist/commands/user-actionable.js +216 -0
- package/dist/control-semantics.d.ts +30 -0
- package/dist/control-semantics.d.ts.map +1 -0
- package/dist/control-semantics.js +419 -0
- package/dist/diagnostics.d.ts +132 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +120 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +350 -0
- package/dist/library.d.ts +17 -0
- package/dist/library.d.ts.map +1 -0
- package/dist/library.js +14 -0
- package/dist/output.d.ts +32 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +33 -0
- package/dist/owned-browser.d.ts +12 -0
- package/dist/owned-browser.d.ts.map +1 -0
- package/dist/owned-browser.js +69 -0
- package/dist/owned-process.d.ts +19 -0
- package/dist/owned-process.d.ts.map +1 -0
- package/dist/owned-process.js +145 -0
- package/dist/playwright-runtime.d.ts +43 -0
- package/dist/playwright-runtime.d.ts.map +1 -0
- package/dist/playwright-runtime.js +339 -0
- package/dist/protected-fill-browser.d.ts +22 -0
- package/dist/protected-fill-browser.d.ts.map +1 -0
- package/dist/protected-fill-browser.js +52 -0
- package/dist/protected-fill.d.ts +82 -0
- package/dist/protected-fill.d.ts.map +1 -0
- package/dist/protected-fill.js +20 -0
- package/dist/runtime-metrics.d.ts +27 -0
- package/dist/runtime-metrics.d.ts.map +1 -0
- package/dist/runtime-metrics.js +66 -0
- package/dist/runtime-page-state.d.ts +11 -0
- package/dist/runtime-page-state.d.ts.map +1 -0
- package/dist/runtime-page-state.js +62 -0
- package/dist/runtime-protected-state.d.ts +14 -0
- package/dist/runtime-protected-state.d.ts.map +1 -0
- package/dist/runtime-protected-state.js +148 -0
- package/dist/runtime-resolution.d.ts +9 -0
- package/dist/runtime-resolution.d.ts.map +1 -0
- package/dist/runtime-resolution.js +19 -0
- package/dist/runtime-state.d.ts +251 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +599 -0
- package/dist/secrets/catalog-applicability.d.ts +5 -0
- package/dist/secrets/catalog-applicability.d.ts.map +1 -0
- package/dist/secrets/catalog-applicability.js +59 -0
- package/dist/secrets/field-policy.d.ts +3 -0
- package/dist/secrets/field-policy.d.ts.map +1 -0
- package/dist/secrets/field-policy.js +3 -0
- package/dist/secrets/fill-ordering.d.ts +10 -0
- package/dist/secrets/fill-ordering.d.ts.map +1 -0
- package/dist/secrets/fill-ordering.js +41 -0
- package/dist/secrets/form-matcher.d.ts +60 -0
- package/dist/secrets/form-matcher.d.ts.map +1 -0
- package/dist/secrets/form-matcher.js +948 -0
- package/dist/secrets/protected-artifact-guard.d.ts +39 -0
- package/dist/secrets/protected-artifact-guard.d.ts.map +1 -0
- package/dist/secrets/protected-artifact-guard.js +44 -0
- package/dist/secrets/protected-bindings.d.ts +14 -0
- package/dist/secrets/protected-bindings.d.ts.map +1 -0
- package/dist/secrets/protected-bindings.js +29 -0
- package/dist/secrets/protected-exact-value-redaction.d.ts +14 -0
- package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -0
- package/dist/secrets/protected-exact-value-redaction.js +360 -0
- package/dist/secrets/protected-field-semantics.d.ts +9 -0
- package/dist/secrets/protected-field-semantics.d.ts.map +1 -0
- package/dist/secrets/protected-field-semantics.js +154 -0
- package/dist/secrets/protected-field-values.d.ts +15 -0
- package/dist/secrets/protected-field-values.d.ts.map +1 -0
- package/dist/secrets/protected-field-values.js +131 -0
- package/dist/secrets/protected-fill.d.ts +47 -0
- package/dist/secrets/protected-fill.d.ts.map +1 -0
- package/dist/secrets/protected-fill.js +446 -0
- package/dist/secrets/protected-value-adapters.d.ts +4 -0
- package/dist/secrets/protected-value-adapters.d.ts.map +1 -0
- package/dist/secrets/protected-value-adapters.js +118 -0
- package/dist/secrets/types.d.ts +70 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +30 -0
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +120 -0
- package/dist/solver/browser-launcher.d.ts +14 -0
- package/dist/solver/browser-launcher.d.ts.map +1 -0
- package/dist/solver/browser-launcher.js +799 -0
- package/dist/solver/config.d.ts +18 -0
- package/dist/solver/config.d.ts.map +1 -0
- package/dist/solver/config.js +67 -0
- package/dist/solver/fingerprint.d.ts +9 -0
- package/dist/solver/fingerprint.d.ts.map +1 -0
- package/dist/solver/fingerprint.js +96 -0
- package/dist/solver/profile-manager.d.ts +8 -0
- package/dist/solver/profile-manager.d.ts.map +1 -0
- package/dist/solver/profile-manager.js +74 -0
- package/dist/solver/turnstile-challenge.d.ts +3 -0
- package/dist/solver/turnstile-challenge.d.ts.map +1 -0
- package/dist/solver/turnstile-challenge.js +173 -0
- package/dist/solver/types.d.ts +67 -0
- package/dist/solver/types.d.ts.map +1 -0
- package/dist/solver/types.js +1 -0
- package/dist/stagehand-runtime.d.ts +4 -0
- package/dist/stagehand-runtime.d.ts.map +1 -0
- package/dist/stagehand-runtime.js +10 -0
- package/dist/stagehand.d.ts +15 -0
- package/dist/stagehand.d.ts.map +1 -0
- package/dist/stagehand.js +19 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +4 -0
- package/dist/update-check.d.ts +14 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +182 -0
- package/dist/workflow-session-state.d.ts +30 -0
- package/dist/workflow-session-state.d.ts.map +1 -0
- package/dist/workflow-session-state.js +74 -0
- package/docs/README.md +25 -0
- package/docs/api-reference.md +242 -0
- package/docs/assistive-runtime.md +148 -0
- package/docs/configuration.md +287 -0
- package/docs/getting-started.md +237 -0
- package/docs/integration-checklist.md +36 -0
- package/docs/protected-fill.md +112 -0
- package/docs/testing.md +50 -0
- package/docs/troubleshooting.md +71 -0
- package/examples/README.md +18 -0
- package/examples/attach.ts +27 -0
- package/examples/basic.ts +36 -0
- package/examples/extract.ts +50 -0
- package/package.json +83 -0
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browse act <targetRef> <action> [value] — Perform a deterministic action on a stored target.
|
|
3
|
+
*/
|
|
4
|
+
import { saveSession } from '../session.js';
|
|
5
|
+
import { getSurface, getTarget, markTargetLifecycle, setTargetAvailability, updateTarget, } from '../runtime-state.js';
|
|
6
|
+
import { incrementMetric, recordActionResult } from '../runtime-metrics.js';
|
|
7
|
+
import { bumpPageScopeEpoch, registerPage, setCurrentPage } from '../runtime-page-state.js';
|
|
8
|
+
import { clearProtectedExposure, getProtectedExposure } from '../runtime-protected-state.js';
|
|
9
|
+
import { outputContractFailure, outputFailure, outputJSON, } from '../output.js';
|
|
10
|
+
import { captureDiagnosticSnapshotBestEffort, finishDiagnosticStepBestEffort, recordDiagnosticArtifactManifestBestEffort, recordCommandLifecycleEventBestEffort, startDiagnosticStep, } from '../diagnostics.js';
|
|
11
|
+
import { capturePageObservation, captureLocatorContextHash, captureLocatorState, createAcceptanceProbe, diagnoseNoObservableProgress, genericClickObservationChanged, locatorStateChanged, pageObservationChanged, shouldVerifyObservableProgress, waitForAcceptanceProbe, } from './action-acceptance.js';
|
|
12
|
+
import { captureActionFailureArtifacts, startActionTrace } from './action-artifacts.js';
|
|
13
|
+
import { clickActivationStrategyForTarget } from './click-activation-policy.js';
|
|
14
|
+
import { resolveSubmitResult } from './action-result-resolution.js';
|
|
15
|
+
import { projectActionValue } from './action-value-projection.js';
|
|
16
|
+
import { applyActionWithFallbacks } from './action-executor.js';
|
|
17
|
+
import { BROWSE_ACTIONS, isBrowseAction } from './browse-actions.js';
|
|
18
|
+
import { isCompatibleMutableFieldBinding, normalizePageSignature, readLocatorBindingSnapshot, readLocatorDomSignature, } from './descriptor-validation.js';
|
|
19
|
+
import { assertStoredBindingStillValid, resolvePreparedLocatorCandidates, resolveInteractionRoots, targetUsesSurfaceAsPrimaryLocator, } from './interaction-kernel.js';
|
|
20
|
+
import { isLocatorUserActionable } from './user-actionable.js';
|
|
21
|
+
import { resolveSurfaceScopeRoot } from './target-resolution.js';
|
|
22
|
+
import { buildProtectedArtifactsSuppressed } from '../secrets/protected-artifact-guard.js';
|
|
23
|
+
import { scrubProtectedExactValues } from '../secrets/protected-exact-value-redaction.js';
|
|
24
|
+
import { connectPlaywright, disconnectPlaywright, listPages, resolvePageByRef, syncSessionPage, } from '../playwright-runtime.js';
|
|
25
|
+
import { withApiTraceContext } from '../command-api-tracing.js';
|
|
26
|
+
function ensureValue(action, value) {
|
|
27
|
+
if (action === 'click')
|
|
28
|
+
return undefined;
|
|
29
|
+
if (typeof value === 'string' && value.length > 0)
|
|
30
|
+
return value;
|
|
31
|
+
throw new Error(`Act value is required for action: ${action}`);
|
|
32
|
+
}
|
|
33
|
+
const MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS = [0, 25, 50, 100];
|
|
34
|
+
const PARTIAL_SELECTION_PROGRESS_MESSAGE = 'Text was entered and the related choice list remained open.';
|
|
35
|
+
const NO_OBSERVABLE_PROGRESS_MESSAGE = 'The action ran, but no visible page change was detected.';
|
|
36
|
+
const NO_OBSERVABLE_PROGRESS_REASON = 'No visible page or control state change was detected within the wait window.';
|
|
37
|
+
const VALIDATION_BLOCKED_MESSAGE = 'The action surfaced validation errors that must be fixed before continuing.';
|
|
38
|
+
const VALIDATION_BLOCKED_REASON = 'The submit action was processed, but the page surfaced validation errors that block progress.';
|
|
39
|
+
/** Stable top-level error codes returned by `act(...)`. */
|
|
40
|
+
export const ACT_ERROR_CODES = [
|
|
41
|
+
'act_failed',
|
|
42
|
+
'action_not_allowed_for_target',
|
|
43
|
+
'browser_connection_failed',
|
|
44
|
+
'no_observable_progress',
|
|
45
|
+
'stale_target',
|
|
46
|
+
'stale_target_ref',
|
|
47
|
+
'target_disabled',
|
|
48
|
+
'target_gated',
|
|
49
|
+
'target_not_actionable',
|
|
50
|
+
'target_readonly',
|
|
51
|
+
'target_surface_inactive',
|
|
52
|
+
'target_surface_not_live',
|
|
53
|
+
'target_surface_unavailable',
|
|
54
|
+
'unknown_target_ref',
|
|
55
|
+
'validation_blocked',
|
|
56
|
+
];
|
|
57
|
+
/** Stable outcome categories emitted by `act(...)`. */
|
|
58
|
+
export const ACT_OUTCOME_TYPES = [
|
|
59
|
+
'action_completed',
|
|
60
|
+
'binding_stale',
|
|
61
|
+
'blocked',
|
|
62
|
+
'partial_progress',
|
|
63
|
+
'unsupported',
|
|
64
|
+
];
|
|
65
|
+
async function readExpandedState(locator) {
|
|
66
|
+
if (!locator) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const state = await captureLocatorState(locator, ['expanded']).catch(() => null);
|
|
70
|
+
return typeof state?.expanded === 'boolean' ? state.expanded : null;
|
|
71
|
+
}
|
|
72
|
+
async function partialProgressForAliasedSelection(args) {
|
|
73
|
+
const { requestedAction, probe } = args;
|
|
74
|
+
if ((requestedAction !== 'fill' && requestedAction !== 'type') || probe?.policy !== 'selection') {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const readExpanded = await readExpandedState(probe.readLocator);
|
|
78
|
+
if (readExpanded === true) {
|
|
79
|
+
return {
|
|
80
|
+
outcomeType: 'partial_progress',
|
|
81
|
+
message: PARTIAL_SELECTION_PROGRESS_MESSAGE,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const fallbackExpanded = probe.readLocator === probe.locator ? readExpanded : await readExpandedState(probe.locator);
|
|
85
|
+
if (fallbackExpanded === true) {
|
|
86
|
+
return {
|
|
87
|
+
outcomeType: 'partial_progress',
|
|
88
|
+
message: PARTIAL_SELECTION_PROGRESS_MESSAGE,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
async function buildActPreflightFailureResult(params) {
|
|
94
|
+
await finishDiagnosticStepBestEffort({
|
|
95
|
+
step: params.step,
|
|
96
|
+
success: false,
|
|
97
|
+
outcomeType: params.outcomeType,
|
|
98
|
+
message: params.message,
|
|
99
|
+
reason: params.reason,
|
|
100
|
+
});
|
|
101
|
+
captureDiagnosticSnapshotBestEffort({
|
|
102
|
+
session: params.session,
|
|
103
|
+
step: params.step,
|
|
104
|
+
phase: 'point-in-time',
|
|
105
|
+
pageRef: params.session.runtime?.currentPageRef,
|
|
106
|
+
});
|
|
107
|
+
recordCommandLifecycleEventBestEffort({
|
|
108
|
+
step: params.step,
|
|
109
|
+
phase: 'failed',
|
|
110
|
+
attributes: {
|
|
111
|
+
outcomeType: params.outcomeType,
|
|
112
|
+
targetRef: params.targetRef,
|
|
113
|
+
reason: params.reason,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
failureSurface: 'contract',
|
|
119
|
+
error: params.error,
|
|
120
|
+
outcomeType: params.outcomeType,
|
|
121
|
+
message: params.message,
|
|
122
|
+
reason: params.reason,
|
|
123
|
+
targetRef: params.targetRef,
|
|
124
|
+
action: params.action,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function describeActFailure(params) {
|
|
128
|
+
if (params.staleReason) {
|
|
129
|
+
const reason = params.staleReason === 'page-signature-mismatch'
|
|
130
|
+
? 'The page changed after the target was observed.'
|
|
131
|
+
: params.staleReason === 'dom-signature-mismatch'
|
|
132
|
+
? 'The element changed after the target was observed.'
|
|
133
|
+
: 'The saved target no longer points to a live element on the page.';
|
|
134
|
+
return {
|
|
135
|
+
error: 'stale_target',
|
|
136
|
+
outcomeType: 'binding_stale',
|
|
137
|
+
message: 'The saved target is outdated.',
|
|
138
|
+
reason,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (params.failureMessage === 'Act failed: no_observable_progress') {
|
|
142
|
+
return {
|
|
143
|
+
error: 'no_observable_progress',
|
|
144
|
+
outcomeType: 'blocked',
|
|
145
|
+
message: NO_OBSERVABLE_PROGRESS_MESSAGE,
|
|
146
|
+
reason: NO_OBSERVABLE_PROGRESS_REASON,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (params.failureMessage === 'Act failed: validation_blocked') {
|
|
150
|
+
return {
|
|
151
|
+
error: 'validation_blocked',
|
|
152
|
+
outcomeType: 'blocked',
|
|
153
|
+
message: VALIDATION_BLOCKED_MESSAGE,
|
|
154
|
+
reason: VALIDATION_BLOCKED_REASON,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const rawReason = params.failureMessage.replace(/^Act failed:\s*/, '');
|
|
158
|
+
if (rawReason === 'target_disabled') {
|
|
159
|
+
return {
|
|
160
|
+
error: 'target_disabled',
|
|
161
|
+
outcomeType: 'blocked',
|
|
162
|
+
message: 'The requested action cannot continue because the target is disabled.',
|
|
163
|
+
reason: 'The runtime resolved the target, but the browser marked it as disabled.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (rawReason === 'target_readonly') {
|
|
167
|
+
return {
|
|
168
|
+
error: 'target_readonly',
|
|
169
|
+
outcomeType: 'blocked',
|
|
170
|
+
message: 'The requested action cannot continue because the target is read-only.',
|
|
171
|
+
reason: 'The runtime resolved the target, but the browser marked it as read-only.',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (rawReason === 'target_surface_inactive') {
|
|
175
|
+
return {
|
|
176
|
+
error: 'target_surface_inactive',
|
|
177
|
+
outcomeType: 'blocked',
|
|
178
|
+
message: 'The requested action cannot continue because the target surface is inactive.',
|
|
179
|
+
reason: 'The target belongs to a surface that is no longer active or available.',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
error: 'act_failed',
|
|
184
|
+
outcomeType: 'blocked',
|
|
185
|
+
message: 'The requested action could not be completed.',
|
|
186
|
+
reason: rawReason,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export { BROWSE_ACTIONS, isBrowseAction };
|
|
190
|
+
function hasMeaningfulNoObservableProgressObservations(observations) {
|
|
191
|
+
return Boolean(observations &&
|
|
192
|
+
(observations.visibleMessages.length > 0 ||
|
|
193
|
+
observations.invalidFields.length > 0 ||
|
|
194
|
+
observations.targetState));
|
|
195
|
+
}
|
|
196
|
+
function hasValidationBlockedObservations(observations) {
|
|
197
|
+
return Boolean(observations && observations.invalidFields.length > 0);
|
|
198
|
+
}
|
|
199
|
+
function sanitizePublicAttempts(attempts) {
|
|
200
|
+
return attempts.filter((attempt) => !attempt.startsWith('stale.') &&
|
|
201
|
+
!attempt.startsWith('no-progress.diagnosis:') &&
|
|
202
|
+
attempt !== 'outcome.partial-progress:selection-not-complete');
|
|
203
|
+
}
|
|
204
|
+
function buildActArtifactManifest(params) {
|
|
205
|
+
if ('suppressed' in params.artifacts) {
|
|
206
|
+
return {
|
|
207
|
+
artifactManifestId: `${params.stepId}-artifacts`,
|
|
208
|
+
stepId: params.stepId,
|
|
209
|
+
screenshots: [],
|
|
210
|
+
htmlSnapshots: [],
|
|
211
|
+
traces: [],
|
|
212
|
+
logs: [],
|
|
213
|
+
suppressed: [
|
|
214
|
+
{ kind: 'screenshot', reason: 'protected_exposure_active' },
|
|
215
|
+
{ kind: 'html', reason: 'protected_exposure_active' },
|
|
216
|
+
{ kind: 'trace', reason: 'protected_exposure_active' },
|
|
217
|
+
{ kind: 'log', reason: 'protected_exposure_active' },
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
artifactManifestId: `${params.stepId}-artifacts`,
|
|
223
|
+
stepId: params.stepId,
|
|
224
|
+
screenshots: params.artifacts.screenshotPath
|
|
225
|
+
? [{ path: params.artifacts.screenshotPath, purpose: 'failure_screenshot' }]
|
|
226
|
+
: [],
|
|
227
|
+
htmlSnapshots: params.artifacts.htmlPath
|
|
228
|
+
? [{ path: params.artifacts.htmlPath, purpose: 'failure_html_snapshot' }]
|
|
229
|
+
: [],
|
|
230
|
+
traces: params.artifacts.tracePath
|
|
231
|
+
? [{ path: params.artifacts.tracePath, purpose: 'failure_trace' }]
|
|
232
|
+
: [],
|
|
233
|
+
logs: [{ path: params.artifacts.actionLogPath, purpose: 'failure_action_log' }],
|
|
234
|
+
suppressed: [],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function persistActArtifactManifestBestEffort(runId, step, stepId, artifacts) {
|
|
238
|
+
if (!runId || !stepId || !artifacts) {
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const manifest = buildActArtifactManifest({
|
|
243
|
+
stepId,
|
|
244
|
+
artifacts,
|
|
245
|
+
});
|
|
246
|
+
return await recordDiagnosticArtifactManifestBestEffort({
|
|
247
|
+
runId,
|
|
248
|
+
step,
|
|
249
|
+
manifest,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function buildActSnapshotArtifactRefs(artifacts) {
|
|
257
|
+
if (!artifacts || 'suppressed' in artifacts) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
...(artifacts.screenshotPath ? { screenshotPath: artifacts.screenshotPath } : {}),
|
|
262
|
+
...(artifacts.htmlPath ? { htmlPath: artifacts.htmlPath } : {}),
|
|
263
|
+
...(artifacts.tracePath ? { tracePath: artifacts.tracePath } : {}),
|
|
264
|
+
...(artifacts.actionLogPath ? { logPath: artifacts.actionLogPath } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function finalizeActStepBestEffort(step, options) {
|
|
268
|
+
return finishDiagnosticStepBestEffort({
|
|
269
|
+
step,
|
|
270
|
+
success: options.success,
|
|
271
|
+
outcomeType: options.outcomeType,
|
|
272
|
+
message: options.message,
|
|
273
|
+
reason: options.reason,
|
|
274
|
+
artifactManifestId: options.artifactManifestId,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function isEditableLikeTarget(target) {
|
|
278
|
+
if (target.controlFamily === 'text-input' ||
|
|
279
|
+
target.controlFamily === 'select' ||
|
|
280
|
+
target.controlFamily === 'datepicker') {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
const kind = (target.kind ?? '').toLowerCase();
|
|
284
|
+
const role = (target.semantics?.role ?? '').toLowerCase();
|
|
285
|
+
return (['input', 'textarea', 'select', 'combobox'].includes(kind) ||
|
|
286
|
+
['textbox', 'combobox', 'searchbox', 'spinbutton'].includes(role) ||
|
|
287
|
+
target.allowedActions.includes('fill') ||
|
|
288
|
+
target.allowedActions.includes('type') ||
|
|
289
|
+
target.allowedActions.includes('select'));
|
|
290
|
+
}
|
|
291
|
+
function shouldWatchForNewPageAfterAction(target, action) {
|
|
292
|
+
if (action === 'click') {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (action !== 'press') {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return !isEditableLikeTarget(target);
|
|
299
|
+
}
|
|
300
|
+
function shouldDeferSurfaceResolutionForEditablePress(target, action) {
|
|
301
|
+
return (action === 'press' &&
|
|
302
|
+
isEditableLikeTarget(target) &&
|
|
303
|
+
!target.locatorCandidates.some((candidate) => candidate.scope === 'surface'));
|
|
304
|
+
}
|
|
305
|
+
async function recoverLocatorFromSurfaceRoot(locatorRoot, target, action, attempts) {
|
|
306
|
+
if (action !== 'press' ||
|
|
307
|
+
!(target.controlFamily === 'text-input' ||
|
|
308
|
+
target.controlFamily === 'select' ||
|
|
309
|
+
target.controlFamily === 'datepicker') ||
|
|
310
|
+
typeof locatorRoot.locator !== 'function') {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const descendants = locatorRoot.locator('input:not([type="hidden"]), textarea, select, [contenteditable="true"], [role="textbox"], [role="combobox"]');
|
|
314
|
+
const count = await descendants.count().catch(() => 0);
|
|
315
|
+
const visibleDescendants = [];
|
|
316
|
+
for (let index = 0; index < count; index += 1) {
|
|
317
|
+
const descendant = descendants.nth(index);
|
|
318
|
+
const visible = await isLocatorUserActionable(descendant);
|
|
319
|
+
if (!visible) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
visibleDescendants.push(descendant);
|
|
323
|
+
}
|
|
324
|
+
if (visibleDescendants.length === 1) {
|
|
325
|
+
attempts.push('resolve.surface-descendant:press');
|
|
326
|
+
return visibleDescendants[0] ?? null;
|
|
327
|
+
}
|
|
328
|
+
if (visibleDescendants.length > 1) {
|
|
329
|
+
attempts.push(`resolve.skip:surface-descendant-ambiguous:${visibleDescendants.length}`);
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
async function capturePopupIfOpened(session, beforePages, afterPages, currentPageRef, attempts) {
|
|
334
|
+
const popup = afterPages.find((page) => !beforePages.includes(page));
|
|
335
|
+
if (!popup)
|
|
336
|
+
return null;
|
|
337
|
+
const page = registerPage(session, {
|
|
338
|
+
openerPageRef: currentPageRef,
|
|
339
|
+
makeCurrent: false,
|
|
340
|
+
});
|
|
341
|
+
attempts.push('popup-captured');
|
|
342
|
+
return {
|
|
343
|
+
page,
|
|
344
|
+
popup,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async function waitForPopup(context) {
|
|
348
|
+
try {
|
|
349
|
+
return await context.waitForEvent('page', { timeout: 500 });
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function waitForLatePage(browser, beforePages, timeoutMs = 2_000) {
|
|
356
|
+
const startedAt = Date.now();
|
|
357
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
358
|
+
const candidate = listPages(browser).find((page) => !beforePages.includes(page));
|
|
359
|
+
if (candidate) {
|
|
360
|
+
return candidate;
|
|
361
|
+
}
|
|
362
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
export async function actBrowser(session, targetRef, action, value) {
|
|
367
|
+
const requestedAction = action;
|
|
368
|
+
const runId = session.activeRunId;
|
|
369
|
+
const actStep = startDiagnosticStep({
|
|
370
|
+
runId,
|
|
371
|
+
command: 'act',
|
|
372
|
+
input: {
|
|
373
|
+
targetRef,
|
|
374
|
+
action: requestedAction,
|
|
375
|
+
valueSupplied: typeof value === 'string' && value.length > 0,
|
|
376
|
+
},
|
|
377
|
+
refs: {
|
|
378
|
+
targetRef,
|
|
379
|
+
},
|
|
380
|
+
}, { session });
|
|
381
|
+
captureDiagnosticSnapshotBestEffort({
|
|
382
|
+
session,
|
|
383
|
+
step: actStep,
|
|
384
|
+
phase: 'before',
|
|
385
|
+
pageRef: session.runtime?.currentPageRef,
|
|
386
|
+
});
|
|
387
|
+
recordCommandLifecycleEventBestEffort({
|
|
388
|
+
step: actStep,
|
|
389
|
+
phase: 'started',
|
|
390
|
+
attributes: {
|
|
391
|
+
targetRef,
|
|
392
|
+
action: requestedAction,
|
|
393
|
+
valueSupplied: typeof value === 'string' && value.length > 0,
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
return withApiTraceContext({
|
|
397
|
+
runId,
|
|
398
|
+
stepId: actStep?.stepId,
|
|
399
|
+
command: 'act',
|
|
400
|
+
}, async () => {
|
|
401
|
+
const target = getTarget(session, targetRef);
|
|
402
|
+
if (!target) {
|
|
403
|
+
return buildActPreflightFailureResult({
|
|
404
|
+
session,
|
|
405
|
+
step: actStep,
|
|
406
|
+
error: 'unknown_target_ref',
|
|
407
|
+
outcomeType: 'blocked',
|
|
408
|
+
message: 'The requested targetRef is unknown.',
|
|
409
|
+
reason: `No stored target matches targetRef ${targetRef}.`,
|
|
410
|
+
targetRef,
|
|
411
|
+
action,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (target.lifecycle !== 'live') {
|
|
415
|
+
return buildActPreflightFailureResult({
|
|
416
|
+
session,
|
|
417
|
+
step: actStep,
|
|
418
|
+
error: 'stale_target_ref',
|
|
419
|
+
outcomeType: 'binding_stale',
|
|
420
|
+
message: 'The requested target is no longer live.',
|
|
421
|
+
reason: `Target ${targetRef} is ${target.lifecycle}${target.lifecycleReason ? ` because ${target.lifecycleReason}` : ''}.`,
|
|
422
|
+
targetRef,
|
|
423
|
+
action,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (target.capability !== 'actionable') {
|
|
427
|
+
return buildActPreflightFailureResult({
|
|
428
|
+
session,
|
|
429
|
+
step: actStep,
|
|
430
|
+
error: 'target_not_actionable',
|
|
431
|
+
outcomeType: 'unsupported',
|
|
432
|
+
message: 'The requested target cannot be used for actions.',
|
|
433
|
+
reason: `Target ${targetRef} has capability ${target.capability}, not actionable.`,
|
|
434
|
+
targetRef,
|
|
435
|
+
action: requestedAction,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
if (!target.allowedActions.includes(action)) {
|
|
439
|
+
const canAliasFillLikeToSelect = (requestedAction === 'fill' || requestedAction === 'type') &&
|
|
440
|
+
target.controlFamily === 'select' &&
|
|
441
|
+
target.allowedActions.includes('select') &&
|
|
442
|
+
typeof value === 'string' &&
|
|
443
|
+
value.length > 0;
|
|
444
|
+
if (canAliasFillLikeToSelect) {
|
|
445
|
+
action = 'select';
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
return buildActPreflightFailureResult({
|
|
449
|
+
session,
|
|
450
|
+
step: actStep,
|
|
451
|
+
error: 'action_not_allowed_for_target',
|
|
452
|
+
outcomeType: 'unsupported',
|
|
453
|
+
message: 'The requested action is not allowed for this target.',
|
|
454
|
+
reason: `Target ${targetRef} allows ${target.allowedActions.join(', ')}, not ${requestedAction}.`,
|
|
455
|
+
targetRef,
|
|
456
|
+
action: requestedAction,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (target.availability.state === 'gated' &&
|
|
461
|
+
(target.availability.reason === 'occupied' ||
|
|
462
|
+
target.availability.reason === 'not-selectable')) {
|
|
463
|
+
return buildActPreflightFailureResult({
|
|
464
|
+
session,
|
|
465
|
+
step: actStep,
|
|
466
|
+
error: 'target_gated',
|
|
467
|
+
outcomeType: 'blocked',
|
|
468
|
+
message: 'The requested target is currently gated.',
|
|
469
|
+
reason: `Target ${targetRef} is gated${target.availability.reason ? ` because ${target.availability.reason}` : ''}.`,
|
|
470
|
+
targetRef,
|
|
471
|
+
action: requestedAction,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const surface = target.surfaceRef ? getSurface(session, target.surfaceRef) : null;
|
|
475
|
+
if (surface && surface.lifecycle !== 'live') {
|
|
476
|
+
setTargetAvailability(session, targetRef, 'surface-inactive', surface.lifecycleReason ?? `surface-${surface.lifecycle}`);
|
|
477
|
+
return buildActPreflightFailureResult({
|
|
478
|
+
session,
|
|
479
|
+
step: actStep,
|
|
480
|
+
error: 'target_surface_not_live',
|
|
481
|
+
outcomeType: 'blocked',
|
|
482
|
+
message: 'The requested target surface is no longer live.',
|
|
483
|
+
reason: `Surface ${surface.ref} is ${surface.lifecycle}${surface.lifecycleReason ? ` because ${surface.lifecycleReason}` : ''}.`,
|
|
484
|
+
targetRef,
|
|
485
|
+
action: requestedAction,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (surface && surface.availability.state !== 'available') {
|
|
489
|
+
setTargetAvailability(session, targetRef, 'surface-inactive', surface.availability.reason ?? `surface-${surface.availability.state}`);
|
|
490
|
+
return buildActPreflightFailureResult({
|
|
491
|
+
session,
|
|
492
|
+
step: actStep,
|
|
493
|
+
error: 'target_surface_unavailable',
|
|
494
|
+
outcomeType: 'blocked',
|
|
495
|
+
message: 'The requested target surface is not currently available.',
|
|
496
|
+
reason: `Surface ${surface.ref} is ${surface.availability.state}${surface.availability.reason ? ` because ${surface.availability.reason}` : ''}.`,
|
|
497
|
+
targetRef,
|
|
498
|
+
action: requestedAction,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const actionValue = ensureValue(action, value);
|
|
502
|
+
const attempts = [];
|
|
503
|
+
if (action !== requestedAction) {
|
|
504
|
+
attempts.push(`action.alias:${requestedAction}->${action}`);
|
|
505
|
+
}
|
|
506
|
+
const startedAt = Date.now();
|
|
507
|
+
let browser = null;
|
|
508
|
+
let failureMessage = null;
|
|
509
|
+
let failureArtifacts;
|
|
510
|
+
let currentPage = null;
|
|
511
|
+
let currentPageRef = target.pageRef;
|
|
512
|
+
const startingPageUrl = session.runtime?.pages?.[target.pageRef]?.url ?? null;
|
|
513
|
+
const protectedExposureAtStart = getProtectedExposure(session, target.pageRef);
|
|
514
|
+
let locatorStrategy = null;
|
|
515
|
+
let recoveredAfterError = false;
|
|
516
|
+
let recoveredAcceptancePolicy = null;
|
|
517
|
+
let recoveredProgressProbe = null;
|
|
518
|
+
let staleReason = null;
|
|
519
|
+
let progressProbe = null;
|
|
520
|
+
let noProgressObservations = null;
|
|
521
|
+
let partialProgressResult = null;
|
|
522
|
+
let liveTarget = target;
|
|
523
|
+
let trace = {
|
|
524
|
+
finishSuccess: async () => { },
|
|
525
|
+
finishFailure: async (_artifactDir) => undefined,
|
|
526
|
+
};
|
|
527
|
+
try {
|
|
528
|
+
browser = await connectPlaywright(session.cdpUrl);
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
return buildActPreflightFailureResult({
|
|
532
|
+
session,
|
|
533
|
+
step: actStep,
|
|
534
|
+
error: 'browser_connection_failed',
|
|
535
|
+
outcomeType: 'blocked',
|
|
536
|
+
message: 'The action could not start because AgentBrowse failed to connect to the browser.',
|
|
537
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
538
|
+
targetRef,
|
|
539
|
+
action,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const page = await resolvePageByRef(browser, session, target.pageRef);
|
|
544
|
+
currentPage = page;
|
|
545
|
+
setCurrentPage(session, target.pageRef);
|
|
546
|
+
const { url } = await syncSessionPage(session, target.pageRef, page);
|
|
547
|
+
trace = await startActionTrace(page, {
|
|
548
|
+
suppressSensitiveArtifacts: Boolean(protectedExposureAtStart),
|
|
549
|
+
});
|
|
550
|
+
if (liveTarget.pageSignature && normalizePageSignature(url) !== liveTarget.pageSignature) {
|
|
551
|
+
staleReason = 'page-signature-mismatch';
|
|
552
|
+
throw new Error('stale_target_page_signature_changed');
|
|
553
|
+
}
|
|
554
|
+
const tryRebindMutableFieldTarget = async (resolvedLocator, strategy) => {
|
|
555
|
+
if (!liveTarget.domSignature) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
for (let attemptIndex = 0; attemptIndex < MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS.length; attemptIndex += 1) {
|
|
559
|
+
const delayMs = MUTABLE_FIELD_REBIND_RETRY_DELAYS_MS[attemptIndex] ?? 0;
|
|
560
|
+
if (attemptIndex > 0) {
|
|
561
|
+
attempts.push(`domSignature.rebind.retry:${strategy}:${attemptIndex + 1}`);
|
|
562
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
563
|
+
}
|
|
564
|
+
const snapshot = await readLocatorBindingSnapshot(resolvedLocator).catch(() => null);
|
|
565
|
+
if (!snapshot?.domSignature) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (snapshot.domSignature === liveTarget.domSignature) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
if (!isCompatibleMutableFieldBinding(liveTarget, snapshot)) {
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
attempts.push(`domSignature.rebound:${strategy}`);
|
|
575
|
+
const updatedTarget = updateTarget(session, targetRef, {
|
|
576
|
+
domSignature: snapshot.domSignature,
|
|
577
|
+
label: snapshot.label ?? liveTarget.label,
|
|
578
|
+
lifecycle: 'live',
|
|
579
|
+
lifecycleReason: undefined,
|
|
580
|
+
availability: { state: 'available' },
|
|
581
|
+
semantics: {
|
|
582
|
+
...liveTarget.semantics,
|
|
583
|
+
name: snapshot.label ?? liveTarget.semantics?.name,
|
|
584
|
+
role: snapshot.role ?? liveTarget.semantics?.role,
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
if (updatedTarget) {
|
|
588
|
+
liveTarget = updatedTarget;
|
|
589
|
+
}
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
return false;
|
|
593
|
+
};
|
|
594
|
+
const assertResolvedTargetStillValid = async (resolvedLocator, stage) => {
|
|
595
|
+
await assertStoredBindingStillValid(page, resolvedLocator, liveTarget, stage, {
|
|
596
|
+
onReason: async (reason, staleStage) => {
|
|
597
|
+
switch (reason) {
|
|
598
|
+
case 'page_signature_mismatch':
|
|
599
|
+
attempts.push(`stale.page-signature:${staleStage}`);
|
|
600
|
+
staleReason = 'page-signature-mismatch';
|
|
601
|
+
return false;
|
|
602
|
+
case 'locator_resolution_failed':
|
|
603
|
+
attempts.push(`stale.locator:${staleStage}`);
|
|
604
|
+
staleReason = 'locator-resolution-failed';
|
|
605
|
+
return false;
|
|
606
|
+
case 'dom_signature_mismatch': {
|
|
607
|
+
const rebound = await tryRebindMutableFieldTarget(resolvedLocator, staleStage);
|
|
608
|
+
if (rebound) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
attempts.push(`stale.dom-signature:${staleStage}`);
|
|
612
|
+
staleReason = 'dom-signature-mismatch';
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
errorForReason: (reason) => {
|
|
618
|
+
switch (reason) {
|
|
619
|
+
case 'page_signature_mismatch':
|
|
620
|
+
return 'stale_target_page_signature_changed';
|
|
621
|
+
case 'locator_resolution_failed':
|
|
622
|
+
return 'stale_target_locator_resolution_failed';
|
|
623
|
+
case 'dom_signature_mismatch':
|
|
624
|
+
return 'stale_target_dom_signature_changed';
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
let resolvedBy = null;
|
|
630
|
+
const beforePages = listPages(browser);
|
|
631
|
+
const shouldCheckProgress = shouldVerifyObservableProgress(target, action);
|
|
632
|
+
const beforePageObservation = shouldCheckProgress
|
|
633
|
+
? await capturePageObservation(page)
|
|
634
|
+
: null;
|
|
635
|
+
const deferSurfaceResolution = shouldDeferSurfaceResolutionForEditablePress(target, action);
|
|
636
|
+
let baseRoot = page;
|
|
637
|
+
let locatorRoot = baseRoot;
|
|
638
|
+
let surfaceRoot = null;
|
|
639
|
+
if (!deferSurfaceResolution) {
|
|
640
|
+
({ baseRoot, locatorRoot, surfaceRoot } = await resolveInteractionRoots(page, target, surface, attempts, {
|
|
641
|
+
recordSelfTargetReuse: true,
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
let lastError = null;
|
|
645
|
+
let sawDomSignatureMismatch = false;
|
|
646
|
+
let sawDisabledTarget = false;
|
|
647
|
+
let sawReadonlyTarget = false;
|
|
648
|
+
const attemptResolvedLocator = async (resolvedLocator, strategy, options) => {
|
|
649
|
+
if (!options?.skipDomSignature && liveTarget.domSignature) {
|
|
650
|
+
const rebound = await tryRebindMutableFieldTarget(resolvedLocator, strategy);
|
|
651
|
+
if (!rebound) {
|
|
652
|
+
const liveSignature = await readLocatorDomSignature(resolvedLocator);
|
|
653
|
+
if (liveSignature && liveSignature !== liveTarget.domSignature) {
|
|
654
|
+
attempts.push(`domSignature.mismatch:${strategy}`);
|
|
655
|
+
sawDomSignatureMismatch = true;
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
let acceptanceProbe = null;
|
|
661
|
+
const tryRecoverActionErrorAcceptance = async () => {
|
|
662
|
+
if (!acceptanceProbe) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
const acceptance = await waitForAcceptanceProbe(acceptanceProbe).catch(() => null);
|
|
666
|
+
if (acceptance?.polls && acceptance.polls > 1) {
|
|
667
|
+
attempts.push(`acceptance.polled:${acceptance.polls}`);
|
|
668
|
+
}
|
|
669
|
+
if (!acceptance?.accepted) {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
if (acceptanceProbe.policy === 'submit') {
|
|
673
|
+
const submitResolution = await resolveSubmitResult(acceptanceProbe, acceptance.afterPageObservation);
|
|
674
|
+
if (!submitResolution.acceptAsProgress) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
attempts.push(`submit-resolution:${submitResolution.finalVerdict}`);
|
|
678
|
+
if (submitResolution.claims.some((claim) => claim.kind === 'soft_result_candidate')) {
|
|
679
|
+
attempts.push('submit-resolution:soft-result-candidate');
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
attempts.push(`acceptance.recovered:${acceptanceProbe.policy}`);
|
|
683
|
+
incrementMetric(session, 'fallbackActions');
|
|
684
|
+
resolvedBy = 'playwright-locator';
|
|
685
|
+
locatorStrategy = strategy;
|
|
686
|
+
recoveredProgressProbe = acceptanceProbe;
|
|
687
|
+
progressProbe = null;
|
|
688
|
+
lastError = null;
|
|
689
|
+
recoveredAfterError = true;
|
|
690
|
+
recoveredAcceptancePolicy = acceptanceProbe.policy;
|
|
691
|
+
setTargetAvailability(session, targetRef, 'available');
|
|
692
|
+
return true;
|
|
693
|
+
};
|
|
694
|
+
try {
|
|
695
|
+
const valueProjection = await projectActionValue({
|
|
696
|
+
target,
|
|
697
|
+
action,
|
|
698
|
+
actionValue,
|
|
699
|
+
locator: resolvedLocator,
|
|
700
|
+
attempts,
|
|
701
|
+
});
|
|
702
|
+
const executionValue = valueProjection?.executionValue ?? actionValue;
|
|
703
|
+
const acceptanceValue = valueProjection?.acceptanceValue ?? actionValue;
|
|
704
|
+
acceptanceProbe = await createAcceptanceProbe({
|
|
705
|
+
session,
|
|
706
|
+
page,
|
|
707
|
+
target,
|
|
708
|
+
action,
|
|
709
|
+
actionValue: acceptanceValue,
|
|
710
|
+
locator: resolvedLocator,
|
|
711
|
+
beforePageObservation,
|
|
712
|
+
});
|
|
713
|
+
attempts.push(`resolve:${strategy}`);
|
|
714
|
+
const usedFallback = await applyActionWithFallbacks(page, locatorRoot, resolvedLocator, action, executionValue, attempts, target.controlFamily, {
|
|
715
|
+
clickActivationStrategy: clickActivationStrategyForTarget(target, action),
|
|
716
|
+
guards: {
|
|
717
|
+
assertStillValid: async (stage) => {
|
|
718
|
+
await assertResolvedTargetStillValid(resolvedLocator, stage);
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
if (usedFallback) {
|
|
723
|
+
incrementMetric(session, 'fallbackActions');
|
|
724
|
+
}
|
|
725
|
+
resolvedBy = 'playwright-locator';
|
|
726
|
+
locatorStrategy = strategy;
|
|
727
|
+
progressProbe = acceptanceProbe;
|
|
728
|
+
setTargetAvailability(session, targetRef, 'available');
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
catch (err) {
|
|
732
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
733
|
+
const shouldAttemptAcceptanceRecovery = acceptanceProbe !== null &&
|
|
734
|
+
(acceptanceProbe.policy === 'value-change' ||
|
|
735
|
+
acceptanceProbe.policy === 'selection' ||
|
|
736
|
+
acceptanceProbe.policy === 'date-selection' ||
|
|
737
|
+
acceptanceProbe.policy === 'navigation' ||
|
|
738
|
+
acceptanceProbe.policy === 'submit');
|
|
739
|
+
if (shouldAttemptAcceptanceRecovery && (await tryRecoverActionErrorAcceptance())) {
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
if (staleReason) {
|
|
743
|
+
throw lastError;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
await assertResolvedTargetStillValid(resolvedLocator, `after-error:${strategy}`);
|
|
747
|
+
}
|
|
748
|
+
catch (validationError) {
|
|
749
|
+
if ((acceptanceProbe?.policy === 'navigation' ||
|
|
750
|
+
acceptanceProbe?.policy === 'submit') &&
|
|
751
|
+
validationError instanceof Error &&
|
|
752
|
+
validationError.message === 'stale_target_page_signature_changed' &&
|
|
753
|
+
(await tryRecoverActionErrorAcceptance())) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
throw validationError;
|
|
757
|
+
}
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
const watchForNewPage = shouldWatchForNewPageAfterAction(target, action);
|
|
762
|
+
const popupPromise = watchForNewPage
|
|
763
|
+
? waitForPopup(page.context())
|
|
764
|
+
: Promise.resolve(null);
|
|
765
|
+
const tryRankedCandidates = async () => {
|
|
766
|
+
const resolution = await resolvePreparedLocatorCandidates({
|
|
767
|
+
target,
|
|
768
|
+
action,
|
|
769
|
+
baseRoot,
|
|
770
|
+
locatorRoot,
|
|
771
|
+
surfaceRoot,
|
|
772
|
+
attempts,
|
|
773
|
+
prepareOptions: {
|
|
774
|
+
allowReadonlyFallback: action === 'fill' && target.controlFamily === 'datepicker',
|
|
775
|
+
allowDescendantPressFallback: action === 'press' &&
|
|
776
|
+
(target.controlFamily === 'text-input' ||
|
|
777
|
+
target.controlFamily === 'select' ||
|
|
778
|
+
target.controlFamily === 'datepicker'),
|
|
779
|
+
isUserActionable: isLocatorUserActionable,
|
|
780
|
+
},
|
|
781
|
+
onPreparedLocator: async (resolvedLocator, strategy) => attemptResolvedLocator(resolvedLocator, strategy),
|
|
782
|
+
});
|
|
783
|
+
if (resolution.sawDisabledTarget) {
|
|
784
|
+
sawDisabledTarget = true;
|
|
785
|
+
}
|
|
786
|
+
if (resolution.sawReadonlyTarget) {
|
|
787
|
+
sawReadonlyTarget = true;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
await tryRankedCandidates();
|
|
791
|
+
if (!resolvedBy && !lastError && deferSurfaceResolution && surface) {
|
|
792
|
+
const deferredSurfaceRoot = await resolveSurfaceScopeRoot(page, surface, attempts);
|
|
793
|
+
if (deferredSurfaceRoot) {
|
|
794
|
+
surfaceRoot = deferredSurfaceRoot;
|
|
795
|
+
locatorRoot = targetUsesSurfaceAsPrimaryLocator(target, surface)
|
|
796
|
+
? baseRoot
|
|
797
|
+
: surfaceRoot;
|
|
798
|
+
await tryRankedCandidates();
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (!resolvedBy && !lastError && surfaceRoot) {
|
|
802
|
+
const recoveredLocator = await recoverLocatorFromSurfaceRoot(surfaceRoot, target, action, attempts);
|
|
803
|
+
if (recoveredLocator) {
|
|
804
|
+
await attemptResolvedLocator(recoveredLocator, 'surface-descendant', {
|
|
805
|
+
skipDomSignature: true,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (!resolvedBy) {
|
|
810
|
+
if (sawDomSignatureMismatch) {
|
|
811
|
+
staleReason = 'dom-signature-mismatch';
|
|
812
|
+
throw new Error('stale_target_dom_signature_changed');
|
|
813
|
+
}
|
|
814
|
+
if (sawDisabledTarget) {
|
|
815
|
+
setTargetAvailability(session, targetRef, 'gated', 'disabled');
|
|
816
|
+
throw new Error('target_disabled');
|
|
817
|
+
}
|
|
818
|
+
if (sawReadonlyTarget && (action === 'fill' || action === 'type')) {
|
|
819
|
+
setTargetAvailability(session, targetRef, 'gated', 'readonly');
|
|
820
|
+
throw new Error('target_readonly');
|
|
821
|
+
}
|
|
822
|
+
if (!lastError &&
|
|
823
|
+
target.surfaceRef &&
|
|
824
|
+
(target.acceptancePolicy === 'selection' ||
|
|
825
|
+
target.acceptancePolicy === 'date-selection')) {
|
|
826
|
+
setTargetAvailability(session, targetRef, 'surface-inactive', 'surface-not-active');
|
|
827
|
+
throw new Error('target_surface_inactive');
|
|
828
|
+
}
|
|
829
|
+
if (!resolvedBy) {
|
|
830
|
+
if (!lastError &&
|
|
831
|
+
(target.controlFamily === 'text-input' ||
|
|
832
|
+
target.controlFamily === 'select' ||
|
|
833
|
+
target.controlFamily === 'datepicker')) {
|
|
834
|
+
staleReason = 'locator-resolution-failed';
|
|
835
|
+
throw new Error('stale_target_locator_resolution_failed');
|
|
836
|
+
}
|
|
837
|
+
if (lastError) {
|
|
838
|
+
throw lastError;
|
|
839
|
+
}
|
|
840
|
+
throw new Error('deterministic_target_resolution_failed');
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const popup = await popupPromise;
|
|
844
|
+
const latePage = !popup && watchForNewPage ? await waitForLatePage(browser, beforePages) : null;
|
|
845
|
+
if (latePage) {
|
|
846
|
+
attempts.push('late-page-captured');
|
|
847
|
+
}
|
|
848
|
+
const discoveredPage = popup ?? latePage;
|
|
849
|
+
const afterPages = discoveredPage ? [...beforePages, discoveredPage] : listPages(browser);
|
|
850
|
+
const capturedPopup = await capturePopupIfOpened(session, beforePages, afterPages, target.pageRef, attempts);
|
|
851
|
+
let finalPageRef = target.pageRef;
|
|
852
|
+
if (capturedPopup) {
|
|
853
|
+
await syncSessionPage(session, capturedPopup.page.pageRef, capturedPopup.popup, {
|
|
854
|
+
settleTimeoutMs: 1_500,
|
|
855
|
+
});
|
|
856
|
+
setCurrentPage(session, capturedPopup.page.pageRef);
|
|
857
|
+
finalPageRef = capturedPopup.page.pageRef;
|
|
858
|
+
currentPageRef = finalPageRef;
|
|
859
|
+
}
|
|
860
|
+
else {
|
|
861
|
+
const syncedPage = await syncSessionPage(session, target.pageRef, page);
|
|
862
|
+
currentPageRef = target.pageRef;
|
|
863
|
+
if (startingPageUrl && syncedPage.url && syncedPage.url !== startingPageUrl) {
|
|
864
|
+
clearProtectedExposure(session, target.pageRef);
|
|
865
|
+
}
|
|
866
|
+
const progressProbeForVerification = progressProbe;
|
|
867
|
+
if (progressProbeForVerification) {
|
|
868
|
+
const finalProgressProbe = progressProbeForVerification;
|
|
869
|
+
const acceptance = await waitForAcceptanceProbe(finalProgressProbe);
|
|
870
|
+
const afterPageObservation = acceptance.afterPageObservation;
|
|
871
|
+
const accepted = acceptance.accepted;
|
|
872
|
+
if (acceptance.polls > 1) {
|
|
873
|
+
attempts.push(`acceptance.polled:${acceptance.polls}`);
|
|
874
|
+
}
|
|
875
|
+
if (!accepted) {
|
|
876
|
+
if (finalProgressProbe.policy === 'value-change') {
|
|
877
|
+
attempts.push(`acceptance.failed:${finalProgressProbe.policy}`);
|
|
878
|
+
throw new Error('action_postcondition_failed:value-change');
|
|
879
|
+
}
|
|
880
|
+
if ((finalProgressProbe.policy === 'selection' ||
|
|
881
|
+
finalProgressProbe.policy === 'date-selection') &&
|
|
882
|
+
finalProgressProbe.expectedValue !== null) {
|
|
883
|
+
attempts.push(`acceptance.failed:${finalProgressProbe.policy}`);
|
|
884
|
+
throw new Error(`action_postcondition_failed:${finalProgressProbe.policy}`);
|
|
885
|
+
}
|
|
886
|
+
if (finalProgressProbe.policy === 'submit') {
|
|
887
|
+
const submitResolution = await resolveSubmitResult(finalProgressProbe, afterPageObservation);
|
|
888
|
+
if (submitResolution.acceptAsProgress) {
|
|
889
|
+
attempts.push(`submit-resolution:${submitResolution.finalVerdict}`);
|
|
890
|
+
if (submitResolution.claims.some((claim) => claim.kind === 'soft_result_candidate')) {
|
|
891
|
+
attempts.push('submit-resolution:soft-result-candidate');
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
attempts.push(`acceptance.failed:${finalProgressProbe.policy}`);
|
|
896
|
+
noProgressObservations = await diagnoseNoObservableProgress(page, finalProgressProbe.locator);
|
|
897
|
+
if (submitResolution.finalVerdict === 'blocked' ||
|
|
898
|
+
hasValidationBlockedObservations(noProgressObservations)) {
|
|
899
|
+
attempts.push('submit-resolution:blocked');
|
|
900
|
+
throw new Error('validation_blocked');
|
|
901
|
+
}
|
|
902
|
+
attempts.push('no-progress.detected');
|
|
903
|
+
throw new Error('no_observable_progress');
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
const afterLocatorObservation = finalProgressProbe.trackedStateKeys.length > 0
|
|
908
|
+
? await captureLocatorState(finalProgressProbe.locator, finalProgressProbe.trackedStateKeys)
|
|
909
|
+
: null;
|
|
910
|
+
const afterContextHash = await captureLocatorContextHash(finalProgressProbe.locator);
|
|
911
|
+
const hasComparableSignal = finalProgressProbe.trackedStateKeys.length > 0 ||
|
|
912
|
+
Boolean(finalProgressProbe.beforeContextHash || afterContextHash) ||
|
|
913
|
+
Boolean(finalProgressProbe.beforePage || afterPageObservation);
|
|
914
|
+
const pageProgressChanged = finalProgressProbe.policy === 'generic-click'
|
|
915
|
+
? genericClickObservationChanged(finalProgressProbe.beforePage, afterPageObservation)
|
|
916
|
+
: pageObservationChanged(finalProgressProbe.beforePage, afterPageObservation);
|
|
917
|
+
if (hasComparableSignal &&
|
|
918
|
+
!pageProgressChanged &&
|
|
919
|
+
finalProgressProbe.beforeContextHash === afterContextHash &&
|
|
920
|
+
!locatorStateChanged(finalProgressProbe.beforeLocator, afterLocatorObservation)) {
|
|
921
|
+
attempts.push('no-progress.detected');
|
|
922
|
+
noProgressObservations = await diagnoseNoObservableProgress(page, finalProgressProbe.locator);
|
|
923
|
+
throw new Error('no_observable_progress');
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
partialProgressResult = await partialProgressForAliasedSelection({
|
|
929
|
+
requestedAction,
|
|
930
|
+
probe: finalProgressProbe,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else if (recoveredProgressProbe) {
|
|
935
|
+
partialProgressResult = await partialProgressForAliasedSelection({
|
|
936
|
+
requestedAction,
|
|
937
|
+
probe: recoveredProgressProbe,
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (resolvedBy === 'playwright-locator') {
|
|
942
|
+
incrementMetric(session, 'deterministicActions');
|
|
943
|
+
}
|
|
944
|
+
bumpPageScopeEpoch(session, target.pageRef);
|
|
945
|
+
recordActionResult(session, true, Date.now() - startedAt);
|
|
946
|
+
await trace.finishSuccess();
|
|
947
|
+
captureDiagnosticSnapshotBestEffort({
|
|
948
|
+
session,
|
|
949
|
+
step: actStep,
|
|
950
|
+
phase: 'after',
|
|
951
|
+
pageRef: finalPageRef,
|
|
952
|
+
});
|
|
953
|
+
recordCommandLifecycleEventBestEffort({
|
|
954
|
+
step: actStep,
|
|
955
|
+
phase: 'completed',
|
|
956
|
+
attributes: {
|
|
957
|
+
outcomeType: partialProgressResult?.outcomeType ?? 'action_completed',
|
|
958
|
+
targetRef,
|
|
959
|
+
action: requestedAction,
|
|
960
|
+
pageRef: finalPageRef,
|
|
961
|
+
},
|
|
962
|
+
});
|
|
963
|
+
await finalizeActStepBestEffort(actStep, {
|
|
964
|
+
success: true,
|
|
965
|
+
outcomeType: partialProgressResult?.outcomeType ?? 'action_completed',
|
|
966
|
+
message: partialProgressResult?.message ?? 'The requested action completed successfully.',
|
|
967
|
+
});
|
|
968
|
+
return scrubProtectedExactValues(session, {
|
|
969
|
+
success: true,
|
|
970
|
+
targetRef,
|
|
971
|
+
action: requestedAction,
|
|
972
|
+
...(action !== requestedAction ? { executedAs: action } : {}),
|
|
973
|
+
value: actionValue,
|
|
974
|
+
resolvedBy,
|
|
975
|
+
locatorStrategy,
|
|
976
|
+
pageRef: finalPageRef,
|
|
977
|
+
attempts: sanitizePublicAttempts(attempts),
|
|
978
|
+
popup: Boolean(capturedPopup),
|
|
979
|
+
overlayHandled: attempts.includes('overlay.dismissed'),
|
|
980
|
+
iframe: Boolean(target.framePath?.length),
|
|
981
|
+
jsFallback: attempts.some((attempt) => attempt.startsWith('locator.evaluate.')),
|
|
982
|
+
...(recoveredAfterError
|
|
983
|
+
? {
|
|
984
|
+
recoveredAfterError: true,
|
|
985
|
+
recoveredAcceptancePolicy: recoveredAcceptancePolicy ?? undefined,
|
|
986
|
+
}
|
|
987
|
+
: {}),
|
|
988
|
+
...(partialProgressResult ?? {}),
|
|
989
|
+
durationMs: Date.now() - startedAt,
|
|
990
|
+
metrics: session.runtime?.metrics,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
catch (err) {
|
|
994
|
+
failureMessage = `Act failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
995
|
+
recordActionResult(session, false, Date.now() - startedAt);
|
|
996
|
+
if (staleReason) {
|
|
997
|
+
markTargetLifecycle(session, targetRef, 'stale', staleReason);
|
|
998
|
+
}
|
|
999
|
+
if (currentPage) {
|
|
1000
|
+
try {
|
|
1001
|
+
const protectedExposure = getProtectedExposure(session, currentPageRef);
|
|
1002
|
+
if (protectedExposure) {
|
|
1003
|
+
await trace.finishSuccess();
|
|
1004
|
+
failureArtifacts = buildProtectedArtifactsSuppressed(protectedExposure);
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
failureArtifacts = await captureActionFailureArtifacts({
|
|
1008
|
+
page: currentPage,
|
|
1009
|
+
targetRef,
|
|
1010
|
+
action: requestedAction,
|
|
1011
|
+
pageRef: currentPageRef,
|
|
1012
|
+
attempts,
|
|
1013
|
+
locatorStrategy,
|
|
1014
|
+
popup: attempts.includes('popup-captured'),
|
|
1015
|
+
overlayHandled: attempts.includes('overlay.dismissed'),
|
|
1016
|
+
iframe: Boolean(target.framePath?.length),
|
|
1017
|
+
jsFallback: attempts.some((attempt) => attempt.startsWith('locator.evaluate.')),
|
|
1018
|
+
durationMs: Date.now() - startedAt,
|
|
1019
|
+
error: failureMessage,
|
|
1020
|
+
finishTrace: (artifactDir) => trace.finishFailure(artifactDir),
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
// Best effort only. Preserve the original action failure.
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
finally {
|
|
1030
|
+
if (browser) {
|
|
1031
|
+
await disconnectPlaywright(browser);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
if (!failureMessage) {
|
|
1035
|
+
throw new Error('unreachable_action_completion_state');
|
|
1036
|
+
}
|
|
1037
|
+
const failureContract = describeActFailure({
|
|
1038
|
+
failureMessage,
|
|
1039
|
+
staleReason,
|
|
1040
|
+
});
|
|
1041
|
+
const outputObservations = hasMeaningfulNoObservableProgressObservations(noProgressObservations)
|
|
1042
|
+
? noProgressObservations
|
|
1043
|
+
: undefined;
|
|
1044
|
+
const artifactManifestId = await persistActArtifactManifestBestEffort(runId, actStep, actStep?.stepId, failureArtifacts ?? null);
|
|
1045
|
+
captureDiagnosticSnapshotBestEffort({
|
|
1046
|
+
session,
|
|
1047
|
+
step: actStep,
|
|
1048
|
+
phase: 'point-in-time',
|
|
1049
|
+
pageRef: currentPageRef,
|
|
1050
|
+
artifactRefs: buildActSnapshotArtifactRefs(failureArtifacts ?? null),
|
|
1051
|
+
});
|
|
1052
|
+
recordCommandLifecycleEventBestEffort({
|
|
1053
|
+
step: actStep,
|
|
1054
|
+
phase: 'failed',
|
|
1055
|
+
attributes: {
|
|
1056
|
+
outcomeType: failureContract.outcomeType,
|
|
1057
|
+
targetRef,
|
|
1058
|
+
action: requestedAction,
|
|
1059
|
+
pageRef: currentPageRef,
|
|
1060
|
+
...(artifactManifestId ? { artifactManifestId } : {}),
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
await finalizeActStepBestEffort(actStep, {
|
|
1064
|
+
success: false,
|
|
1065
|
+
outcomeType: failureContract.outcomeType,
|
|
1066
|
+
message: failureContract.message,
|
|
1067
|
+
reason: failureContract.reason,
|
|
1068
|
+
artifactManifestId,
|
|
1069
|
+
});
|
|
1070
|
+
return scrubProtectedExactValues(session, {
|
|
1071
|
+
success: false,
|
|
1072
|
+
failureSurface: 'output',
|
|
1073
|
+
error: failureContract.error,
|
|
1074
|
+
outcomeType: failureContract.outcomeType,
|
|
1075
|
+
message: failureContract.message,
|
|
1076
|
+
reason: failureContract.reason,
|
|
1077
|
+
targetRef,
|
|
1078
|
+
action: requestedAction,
|
|
1079
|
+
...(action !== requestedAction ? { executedAs: action } : {}),
|
|
1080
|
+
value: actionValue,
|
|
1081
|
+
pageRef: currentPageRef,
|
|
1082
|
+
locatorStrategy,
|
|
1083
|
+
attempts: sanitizePublicAttempts(attempts),
|
|
1084
|
+
popup: attempts.includes('popup-captured'),
|
|
1085
|
+
overlayHandled: attempts.includes('overlay.dismissed'),
|
|
1086
|
+
iframe: Boolean(target.framePath?.length),
|
|
1087
|
+
jsFallback: attempts.some((attempt) => attempt.startsWith('locator.evaluate.')),
|
|
1088
|
+
durationMs: Date.now() - startedAt,
|
|
1089
|
+
staleTarget: Boolean(staleReason),
|
|
1090
|
+
observations: outputObservations,
|
|
1091
|
+
artifacts: failureArtifacts,
|
|
1092
|
+
metrics: session.runtime?.metrics,
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
export async function act(session, targetRef, action, value) {
|
|
1097
|
+
const result = await actBrowser(session, targetRef, action, value);
|
|
1098
|
+
saveSession(session);
|
|
1099
|
+
if (result.success) {
|
|
1100
|
+
return outputJSON(result);
|
|
1101
|
+
}
|
|
1102
|
+
const { success: _success, failureSurface, ...failure } = result;
|
|
1103
|
+
if (failureSurface === 'contract') {
|
|
1104
|
+
return outputContractFailure(failure);
|
|
1105
|
+
}
|
|
1106
|
+
return outputFailure(failure);
|
|
1107
|
+
}
|