@reticlehq/server 3.9.0 → 3.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/SKILL.md +13 -12
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -187
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/cli-doctor.js +1 -1
- package/dist/command/cli/cli-flow-commands.js +2 -9
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +17 -15
- package/dist/command/cli/cli-parse.js +44 -27
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +58 -29
- package/dist/command/cli/cli-verify.d.ts +11 -2
- package/dist/command/cli/cli-verify.js +27 -8
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor-repairs.js +1 -1
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
- package/dist/command/cli/ports/port-holder.d.ts +9 -0
- package/dist/command/cli/ports/port-holder.js +27 -3
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +22 -0
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +7 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +46 -41
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +12 -1
- package/dist/command/daemon/daemon.js +35 -4
- package/dist/command/daemon/lifetime/death-cause.js +21 -2
- package/dist/command/daemon/lifetime/heartbeat.js +7 -2
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-runtime.js +6 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +6 -3
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/version-skew.js +1 -1
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/visual/visual-tools.js +66 -19
- package/dist/index.js +7 -4
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +44 -0
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
- package/dist/language/flows/recording/tape/recordings.js +71 -5
- package/dist/language/flows/replay.d.ts +19 -5
- package/dist/language/flows/replay.js +99 -7
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +28 -9
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +22 -20
- package/dist/portal/input/real-input.js +41 -31
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +75 -18
- package/dist/portal/pool/browser-pool.d.ts +11 -29
- package/dist/portal/pool/browser-pool.js +35 -17
- package/dist/portal/pool/context-permissions.d.ts +17 -3
- package/dist/portal/pool/context-permissions.js +36 -5
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +44 -17
- package/dist/portal/pool/lease-page-error.js +147 -5
- package/dist/portal/pool/page-error-journal.d.ts +7 -1
- package/dist/portal/pool/page-error-journal.js +27 -0
- package/dist/portal/pool/playwright-launcher.js +51 -8
- package/dist/portal/pool/pool-contract.d.ts +27 -1
- package/dist/portal/pool/pool-lease.d.ts +36 -1
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +23 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
- package/dist/portal/session/dev-server/dev-command.js +61 -15
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -25
- package/dist/portal/session/no-session-next-action.d.ts +2 -0
- package/dist/portal/session/no-session-next-action.js +12 -3
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +38 -3
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +174 -63
- package/dist/start-wiring.d.ts +2 -1
- package/dist/start-wiring.js +2 -1
- package/dist/surface/mcp/mcp.js +3 -2
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +1 -1
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/after-match.d.ts +4 -1
- package/dist/surface/tools/act/after-match.js +8 -1
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +15 -1
- package/dist/surface/tools/act/resolve-target.js +24 -1
- package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
- package/dist/surface/tools/act/sequence-grade.js +37 -26
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +78 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +74 -86
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +16 -37
- package/dist/surface/tools/browser-tools.js +12 -6
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +9 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +91 -21
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +3 -2
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +35 -1
- package/dist/surface/tools/lease-hint.js +42 -0
- package/dist/surface/tools/lease-readiness.d.ts +26 -0
- package/dist/surface/tools/lease-readiness.js +79 -0
- package/dist/surface/tools/lease-seed.d.ts +14 -1
- package/dist/surface/tools/lease-seed.js +35 -3
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +62 -110
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +6 -0
- package/dist/surface/tools/tool-kit.js +2 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +28 -5
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +1 -0
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +8 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +1 -1
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/license.mdx +9 -7
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +33 -23
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +2 -2
- package/docs/environment.mdx +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +2 -2
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +13 -13
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { formatStepAddress } from 'open-verification';
|
|
2
2
|
import { assertsState, expectLabel, expectedElementTestid, withoutClauses } from '@reticlehq/core';
|
|
3
3
|
import { span } from '../../trace.js';
|
|
4
|
-
import { ambiguityDrift, anchorLabel, expectElementDrift, reresolved, resolveTestid, testidDrift, } from './flow-anchor.js';
|
|
4
|
+
import { ambiguityDrift, anchorLabel, expectElementDrift, refFor, reresolved, resolveTestid, testidDrift, } from './flow-anchor.js';
|
|
5
5
|
export { nearestIsAmbiguous, nearestTestid, resolveQuery } from './flow-anchor.js';
|
|
6
6
|
import { routeOfEvent, routeOfUrl } from '@reticlehq/engine/question/predicate/predicate-route.js';
|
|
7
7
|
import { evalConsole } from '@reticlehq/engine/question/predicate/predicate-console.js';
|
|
8
8
|
import { appScope, stepEffect } from '@reticlehq/engine/evidence/step-effect.js';
|
|
9
|
+
import { stepContradictions } from './flow-cross-step.js';
|
|
9
10
|
import { AnchorKind, DriftReason, EventType, FlowStepTool, StepEffect, PredicateKind, ReplayStatus, } from '@reticlehq/core';
|
|
10
11
|
import { asString, invokedFlowMissing, isConsequenceDrift } from '@reticlehq/core';
|
|
11
12
|
import { anchorFieldName } from './fields/flow-secret-field.js';
|
|
12
13
|
import { degradedStepResult, isDocumentPressStep, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
|
|
13
14
|
import { inFlightRequestLabels } from '../../surface/tools/act/settle-in-flight.js';
|
|
14
15
|
import { namedNetIsInFlight } from '@reticlehq/engine/evidence/unsettled.js';
|
|
16
|
+
import { captureBaselines } from '@reticlehq/engine/evidence/baseline.js';
|
|
15
17
|
/** What a still-open named request means, in the drift's own words. */
|
|
16
18
|
const IN_FLIGHT_AT_BUDGET_END = 'the request this step declared had not come back when the budget ended — it is still in flight, ' +
|
|
17
19
|
'so nothing here says the app failed. Raise the step timeout, or look at the endpoint';
|
|
18
20
|
import { isDocumentGoneError, SESSION_DISCONNECTED_REASON, } from '../../portal/session/facts/session-replaced.js';
|
|
19
21
|
import { actOnResolvedRef } from './flow-step-runners.js';
|
|
22
|
+
import { isPageStep, runPageStep } from './steps/page-steps.js';
|
|
20
23
|
/**
|
|
21
24
|
* The document this replay was driving went away mid-run.
|
|
22
25
|
*
|
|
@@ -41,6 +44,18 @@ export class DocumentLostDuringReplay extends Error {
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
/*
|
|
48
|
+
* How replay paces itself between steps. A person does not act on a screen that is still changing,
|
|
49
|
+
* and replay did: it acted the moment the last step's consequence held. An htmx row swap puts a new
|
|
50
|
+
* button in the page and binds it a moment later, so a click in between did nothing, and a correct
|
|
51
|
+
* build failed. A busy machine makes that moment longer, so the false fail came under parallel load.
|
|
52
|
+
*
|
|
53
|
+
* Quiet means no network, DOM or animation activity for `STEP_QUIET_MS`, which is shorter than any
|
|
54
|
+
* person takes to act on a changed screen. A page that never goes quiet (a poll, a spinner) gets
|
|
55
|
+
* `STEP_QUIET_BUDGET_MS` and then the step runs anyway: the wait paces replay and decides nothing.
|
|
56
|
+
*/
|
|
57
|
+
const STEP_QUIET_MS = 250;
|
|
58
|
+
const STEP_QUIET_BUDGET_MS = 2_000;
|
|
44
59
|
/**
|
|
45
60
|
* The route (pathname) currently in effect — the page a step runs on. Reads the latest ROUTE_CHANGE
|
|
46
61
|
* from the whole event buffer; mirrors the predicate engine's route field order (pathname → to).
|
|
@@ -58,8 +73,9 @@ function currentRoute(session) {
|
|
|
58
73
|
// pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
|
|
59
74
|
// every step. Sixth place the same reading was wrong; see routeOfEvent.
|
|
60
75
|
const parts = routeOfEvent(last);
|
|
61
|
-
if (parts === undefined || 0 === parts.docPath.length)
|
|
76
|
+
if (parts === undefined || 0 === parts.docPath.length) {
|
|
62
77
|
return undefined;
|
|
78
|
+
}
|
|
63
79
|
return parts.routePath.length > 0 ? parts.routePath : undefined;
|
|
64
80
|
}
|
|
65
81
|
/** Pathname only (drop origin + query) so a net URL stays terse in the journey. */
|
|
@@ -81,19 +97,23 @@ function summarizeConsequence(events) {
|
|
|
81
97
|
const lastRoute = events.filter((e) => e.type === EventType.ROUTE_CHANGE).at(-1);
|
|
82
98
|
if (lastRoute !== undefined) {
|
|
83
99
|
const routed = routeOfEvent(lastRoute);
|
|
84
|
-
if (routed !== undefined && routed.routePath.length > 0)
|
|
100
|
+
if (routed !== undefined && routed.routePath.length > 0) {
|
|
85
101
|
parts.push(`→ ${routed.routePath}`);
|
|
102
|
+
}
|
|
86
103
|
}
|
|
87
104
|
const signals = new Set();
|
|
88
105
|
for (const e of events) {
|
|
89
|
-
if (e.type !== EventType.SIGNAL)
|
|
106
|
+
if (e.type !== EventType.SIGNAL) {
|
|
90
107
|
continue;
|
|
108
|
+
}
|
|
91
109
|
const name = asString((e.data ?? {})['name']);
|
|
92
|
-
if (name !== undefined)
|
|
110
|
+
if (name !== undefined) {
|
|
93
111
|
signals.add(name);
|
|
112
|
+
}
|
|
94
113
|
}
|
|
95
|
-
for (const name of [...signals].slice(0, 2))
|
|
114
|
+
for (const name of [...signals].slice(0, 2)) {
|
|
96
115
|
parts.push(`signal ${name}`);
|
|
116
|
+
}
|
|
97
117
|
for (const n of events.filter((e) => e.type === EventType.NET_REQUEST).slice(0, 2)) {
|
|
98
118
|
const data = n.data ?? {};
|
|
99
119
|
const method = asString(data['method']) ?? 'GET';
|
|
@@ -102,8 +122,9 @@ function summarizeConsequence(events) {
|
|
|
102
122
|
parts.push(`${method} ${path}${status}`.trim());
|
|
103
123
|
}
|
|
104
124
|
const errors = events.filter(isConsoleError).length;
|
|
105
|
-
if (errors > 0)
|
|
125
|
+
if (errors > 0) {
|
|
106
126
|
parts.push(`${errors} console error${errors > 1 ? 's' : ''}`);
|
|
127
|
+
}
|
|
107
128
|
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
108
129
|
}
|
|
109
130
|
const isConsoleError = (e) => e.type === EventType.CONSOLE_ERROR || e.type === EventType.ERROR_UNCAUGHT;
|
|
@@ -119,8 +140,9 @@ function consoleErrorsIn(events) {
|
|
|
119
140
|
}
|
|
120
141
|
/** The console-absent clauses an expect cannot pass without: itself, or any `allOf` member. */
|
|
121
142
|
function requiredConsoleAbsences(expect) {
|
|
122
|
-
if (PredicateKind.ALL_OF === expect.kind)
|
|
143
|
+
if (PredicateKind.ALL_OF === expect.kind) {
|
|
123
144
|
return expect.predicates.flatMap(requiredConsoleAbsences);
|
|
145
|
+
}
|
|
124
146
|
return PredicateKind.CONSOLE === expect.kind && true === expect.absent ? [expect] : [];
|
|
125
147
|
}
|
|
126
148
|
/**
|
|
@@ -132,8 +154,9 @@ function requiredConsoleAbsences(expect) {
|
|
|
132
154
|
function lateConsoleDrift(expect, events) {
|
|
133
155
|
for (const clause of requiredConsoleAbsences(expect)) {
|
|
134
156
|
const read = evalConsole(events, clause);
|
|
135
|
-
if (read.pass)
|
|
157
|
+
if (read.pass) {
|
|
136
158
|
continue;
|
|
159
|
+
}
|
|
137
160
|
return {
|
|
138
161
|
reasonKind: DriftReason.SIGNAL_NOT_OBSERVED,
|
|
139
162
|
reason: read.failureReason ?? "the step's declared consequence did not hold",
|
|
@@ -143,6 +166,13 @@ function lateConsoleDrift(expect, events) {
|
|
|
143
166
|
}
|
|
144
167
|
return undefined;
|
|
145
168
|
}
|
|
169
|
+
/** A console-absent expect read over the start-page load, said as such. See `loadSince`. */
|
|
170
|
+
function loadDrift(expect, loadEvents) {
|
|
171
|
+
const drift = 0 === loadEvents.length ? undefined : lateConsoleDrift(expect, loadEvents);
|
|
172
|
+
return drift === undefined
|
|
173
|
+
? undefined
|
|
174
|
+
: { ...drift, reason: `${drift.reason}, logged while replay loaded the start page` };
|
|
175
|
+
}
|
|
146
176
|
/** Run one testid-anchored step: re-resolve via QUERY, then ACT on the live ref, else drift. */
|
|
147
177
|
export async function runTestidStep(session, step, index, value, dynamic, confirmDangerous, sleep) {
|
|
148
178
|
const { refs, hint } = await resolveTestid(session, value, sleep);
|
|
@@ -170,7 +200,8 @@ export async function runTestidStep(session, step, index, value, dynamic, confir
|
|
|
170
200
|
if (ambiguous !== null) {
|
|
171
201
|
return { step: index, tool: step.tool, anchor: value, ok: false, drift: ambiguous };
|
|
172
202
|
}
|
|
173
|
-
|
|
203
|
+
// The only match, or the recorded position among a shared test id (#2135).
|
|
204
|
+
const ref = refFor(step.anchor, refs) ?? '';
|
|
174
205
|
/*
|
|
175
206
|
* The same dispatch its siblings use, including the one re-resolve on a stale ref (2.3).
|
|
176
207
|
*
|
|
@@ -186,8 +217,9 @@ export async function runTestidStep(session, step, index, value, dynamic, confir
|
|
|
186
217
|
// The field this step types into — from the anchor, so a redacted fill can be supplied from
|
|
187
218
|
// RETICLE_SECRET_<FIELD> without the flow carrying the secret.
|
|
188
219
|
anchorFieldName(step.anchor), async () => reresolved(step.anchor, (await resolveTestid(session, value, sleep)).refs));
|
|
189
|
-
if (!result.ok)
|
|
220
|
+
if (!result.ok) {
|
|
190
221
|
return result;
|
|
222
|
+
}
|
|
191
223
|
// assert the step's expect.element testid is present AFTER the action —
|
|
192
224
|
// unless that testid was marked DYNAMIC (the LLM-output case), in which case its presence/content
|
|
193
225
|
// is NOT asserted (only the action ran). The skip is scoped strictly to the dynamic set.
|
|
@@ -230,7 +262,7 @@ export async function runTestidStep(session, step, index, value, dynamic, confir
|
|
|
230
262
|
* quietly stop agreeing, and the disagreement would land as a flow that replays green against a
|
|
231
263
|
* state it was never meant to run in.
|
|
232
264
|
*/
|
|
233
|
-
export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since) {
|
|
265
|
+
export async function assertStepExpect(session, expect, waitForSignal, timeoutMs, since, baselines) {
|
|
234
266
|
// A testid is already asserted against the live DOM by the step runner. A role/name locator is
|
|
235
267
|
// not that path — stripping every element made a recorded `until` by button name a no-op, so a
|
|
236
268
|
// flow that proved the control at capture time could not go red when it was gone.
|
|
@@ -254,16 +286,19 @@ export async function assertStepExpect(session, expect, waitForSignal, timeoutMs
|
|
|
254
286
|
(clause) => PredicateKind.ELEMENT === clause.kind &&
|
|
255
287
|
clause.query.testid !== undefined &&
|
|
256
288
|
true !== clause.absent);
|
|
257
|
-
if (predicate === undefined)
|
|
289
|
+
if (predicate === undefined) {
|
|
258
290
|
return undefined;
|
|
259
|
-
|
|
260
|
-
|
|
291
|
+
}
|
|
292
|
+
const verdict = await waitForSignal(session, predicate, timeoutMs, since, baselines);
|
|
293
|
+
if (verdict.pass) {
|
|
261
294
|
return undefined;
|
|
295
|
+
}
|
|
262
296
|
// The wait ended because the page stopped being observable, not because the consequence failed:
|
|
263
297
|
// the same fact as a command the dying socket rejected, so it takes the same path (the replay loop
|
|
264
298
|
// reports a lost document). As a drift, a socket that blinked for 26ms read as the app's defect.
|
|
265
|
-
if (true === verdict.observationLost)
|
|
299
|
+
if (true === verdict.observationLost) {
|
|
266
300
|
throw new Error(SESSION_DISCONNECTED_REASON);
|
|
301
|
+
}
|
|
267
302
|
// A request the step NAMED that had not come back yet is a blind spot, not an assertion the app
|
|
268
303
|
// failed. The live verdict path already draws this line (namedNetIsInFlight); replay read the
|
|
269
304
|
// same window and never asked, so a slow endpoint was reported as a consequence that never fired.
|
|
@@ -272,14 +307,17 @@ export async function assertStepExpect(session, expect, waitForSignal, timeoutMs
|
|
|
272
307
|
? []
|
|
273
308
|
: inFlightRequestLabels(session.eventsSince(since), session.url, session.background);
|
|
274
309
|
const namedInFlight = namedNetIsInFlight(predicate, openRequests);
|
|
310
|
+
// The store case keeps its own kind because heal and the run report branch on it; everything
|
|
311
|
+
// else is a consequence that did not hold, and the reason carries observed-vs-expected.
|
|
312
|
+
let reasonKind = DriftReason.SIGNAL_NOT_OBSERVED;
|
|
313
|
+
if (assertsState(expect)) {
|
|
314
|
+
reasonKind = DriftReason.STATE_MISMATCH;
|
|
315
|
+
}
|
|
316
|
+
else if (namedInFlight) {
|
|
317
|
+
reasonKind = DriftReason.NET_STILL_IN_FLIGHT;
|
|
318
|
+
}
|
|
275
319
|
return {
|
|
276
|
-
|
|
277
|
-
// else is a consequence that did not hold, and the reason carries observed-vs-expected.
|
|
278
|
-
reasonKind: assertsState(expect)
|
|
279
|
-
? DriftReason.STATE_MISMATCH
|
|
280
|
-
: namedInFlight
|
|
281
|
-
? DriftReason.NET_STILL_IN_FLIGHT
|
|
282
|
-
: DriftReason.SIGNAL_NOT_OBSERVED,
|
|
320
|
+
reasonKind,
|
|
283
321
|
reason: namedInFlight
|
|
284
322
|
? `${IN_FLIGHT_AT_BUDGET_END} (${openRequests.join(', ')})`
|
|
285
323
|
: withCaseOnly(verdict.failureReason ?? "the step's declared consequence did not hold", verdict),
|
|
@@ -293,8 +331,9 @@ export async function assertStepExpect(session, expect, waitForSignal, timeoutMs
|
|
|
293
331
|
* reading its replay needs the fix (`ignoreCase: true`, or re-record), not a bare "did not hold".
|
|
294
332
|
*/
|
|
295
333
|
export function withCaseOnly(reason, verdict) {
|
|
296
|
-
if (true !== verdict.caseOnlyMiss)
|
|
334
|
+
if (true !== verdict.caseOnlyMiss) {
|
|
297
335
|
return reason;
|
|
336
|
+
}
|
|
298
337
|
const shown = verdict.wouldHold === undefined ? '' : ` ("${verdict.wouldHold}")`;
|
|
299
338
|
return `${reason} — the page shows it in another case${shown}. Text checks match case exactly: if any case is meant, add ignoreCase: true to this check, or re-record the flow.`;
|
|
300
339
|
}
|
|
@@ -310,8 +349,9 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
|
|
|
310
349
|
// cross-flow false green on the exact suite-verify path the regression-cost claim rests on. The
|
|
311
350
|
// replay-start floor excludes prior flows/runs while still seeing this run's adjacent-step signal.
|
|
312
351
|
const verdict = await waitForSignal(session, { kind: PredicateKind.SIGNAL, name }, signalTimeoutMs, since);
|
|
313
|
-
if (verdict.pass)
|
|
352
|
+
if (verdict.pass) {
|
|
314
353
|
return { step: index, tool: step.tool, anchor: name, ok: true };
|
|
354
|
+
}
|
|
315
355
|
return {
|
|
316
356
|
step: index,
|
|
317
357
|
tool: step.tool,
|
|
@@ -332,11 +372,13 @@ async function runSignalStep(session, step, index, name, waitForSignal, signalTi
|
|
|
332
372
|
* is not setup, it is a second charge, a second email, a second record.
|
|
333
373
|
*/
|
|
334
374
|
export function resumeIndex(from, steps) {
|
|
335
|
-
if (from === undefined)
|
|
375
|
+
if (from === undefined) {
|
|
336
376
|
return 0;
|
|
377
|
+
}
|
|
337
378
|
const at = 'number' === typeof from ? from : steps.findIndex((step) => step.id === from);
|
|
338
|
-
if (at < 0 || at >= steps.length || !Number.isInteger(at))
|
|
379
|
+
if (at < 0 || at >= steps.length || !Number.isInteger(at)) {
|
|
339
380
|
return undefined;
|
|
381
|
+
}
|
|
340
382
|
return steps.slice(0, at).some((step) => StepEffect.COMMITS === step.effect) ? 0 : at;
|
|
341
383
|
}
|
|
342
384
|
/**
|
|
@@ -351,8 +393,9 @@ export function replayWindow(args) {
|
|
|
351
393
|
}
|
|
352
394
|
/** A part-way replay's status, from the steps it drove. */
|
|
353
395
|
export function partialStatus(steps) {
|
|
354
|
-
if (steps.some((step) => step.drift !== undefined))
|
|
396
|
+
if (steps.some((step) => step.drift !== undefined)) {
|
|
355
397
|
return ReplayStatus.DRIFT;
|
|
398
|
+
}
|
|
356
399
|
return steps.every((step) => step.ok) ? ReplayStatus.OK : ReplayStatus.ERROR;
|
|
357
400
|
}
|
|
358
401
|
/** The `from` argument, narrowed: a step index or a step id. */
|
|
@@ -361,8 +404,9 @@ export function resumeArg(raw) {
|
|
|
361
404
|
}
|
|
362
405
|
/** Why `from` cannot be resumed at, or undefined when it can (or was not given). */
|
|
363
406
|
export function unresolvedResume(from, steps) {
|
|
364
|
-
if (resumeIndex(from, steps) !== undefined)
|
|
407
|
+
if (resumeIndex(from, steps) !== undefined) {
|
|
365
408
|
return undefined;
|
|
409
|
+
}
|
|
366
410
|
return `cannot resume at ${JSON.stringify(from)}: this flow has ${String(steps.length)} step(s) and no step by that id`;
|
|
367
411
|
}
|
|
368
412
|
/**
|
|
@@ -446,31 +490,70 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
446
490
|
};
|
|
447
491
|
try {
|
|
448
492
|
for (const step of flow.steps) {
|
|
449
|
-
if (options.to !== undefined && index >= options.to)
|
|
493
|
+
if (options.to !== undefined && index >= options.to) {
|
|
450
494
|
break;
|
|
495
|
+
}
|
|
451
496
|
if (options.at !== undefined && index < options.at) {
|
|
452
497
|
index += 1;
|
|
453
498
|
continue;
|
|
454
499
|
}
|
|
500
|
+
if (index > (options.at ?? 0)) {
|
|
501
|
+
await waitForSignal(session, { kind: PredicateKind.SETTLED, quietMs: STEP_QUIET_MS }, STEP_QUIET_BUDGET_MS);
|
|
502
|
+
}
|
|
455
503
|
if (step.invoke !== undefined) {
|
|
456
504
|
results.push(await runInvokeStep(session, flow, step, index, waitForSignal, signalTimeoutMs, confirmDangerous, sleep, options, index < from));
|
|
457
505
|
const last = results[results.length - 1];
|
|
458
|
-
if (last !== undefined && index < from && last.ok)
|
|
506
|
+
if (last !== undefined && index < from && last.ok) {
|
|
459
507
|
results.pop();
|
|
508
|
+
}
|
|
460
509
|
tell(last);
|
|
461
|
-
if (last !== undefined && false === last.ok)
|
|
510
|
+
if (last !== undefined && false === last.ok) {
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
index += 1;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (isPageStep(step)) {
|
|
517
|
+
const ran = await runPageStep(step, index, {
|
|
518
|
+
open: options.navigate,
|
|
519
|
+
send: options.request,
|
|
520
|
+
checked: index >= from,
|
|
521
|
+
confirmDangerous,
|
|
522
|
+
// A navigated page's clock starts at its own connect, so its whole life is the window.
|
|
523
|
+
judge: (on, expectation) => assertStepExpect(on, expectation, waitForSignal, waitFor(step), 0),
|
|
524
|
+
route: currentRoute,
|
|
525
|
+
});
|
|
526
|
+
const result = ran.result;
|
|
527
|
+
if (ran.session !== undefined) {
|
|
528
|
+
// The page that asked for the load is gone; every later step runs on the one that loaded.
|
|
529
|
+
session = ran.session;
|
|
530
|
+
}
|
|
531
|
+
if (index < from && result.ok && result.drift === undefined) {
|
|
532
|
+
index += 1;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (index < from) {
|
|
536
|
+
result.note = `setup step for resuming at step ${String(from)} failed`;
|
|
537
|
+
}
|
|
538
|
+
await options.onStepResult?.(result, flow.name).catch(() => undefined);
|
|
539
|
+
results.push(result);
|
|
540
|
+
tell(result);
|
|
541
|
+
if (!result.ok || result.drift !== undefined) {
|
|
462
542
|
break;
|
|
543
|
+
}
|
|
463
544
|
index += 1;
|
|
464
545
|
continue;
|
|
465
546
|
}
|
|
466
547
|
const label = anchorLabel(step.anchor);
|
|
467
548
|
/** One sub-step's expect, over the window that sub-step opened. See runSequenceStep. */
|
|
468
|
-
const
|
|
549
|
+
const budget = waitFor(step);
|
|
550
|
+
const judgeSubStep = async (expectation, since, before) => (await assertStepExpect(session, expectation, waitForSignal, budget, since, before)) ??
|
|
469
551
|
lateConsoleDrift(expectation, session.eventsSince(since).filter((e) => e.t >= since));
|
|
470
552
|
// The page this step runs on (the journey's "which page") — captured before the action.
|
|
471
553
|
const page = currentRoute(session);
|
|
472
|
-
//
|
|
554
|
+
// This step's event-time floor, and (as act_and_wait) what `unchanged` and kin compare with.
|
|
473
555
|
const cursorBefore = session.elapsed();
|
|
556
|
+
const baselines = index < from ? undefined : await captureBaselines(session, step.expect);
|
|
474
557
|
const subSteps = step.steps;
|
|
475
558
|
// Traced per step, so a slow replay says WHICH step and which anchor kind spent the time. The
|
|
476
559
|
// per-step `durationMs` below is what the agent gets back; this is what a developer profiling
|
|
@@ -493,9 +576,12 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
493
576
|
if (step.anchor.kind === AnchorKind.COMPONENT) {
|
|
494
577
|
return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
495
578
|
}
|
|
496
|
-
if (step.anchor.kind === AnchorKind.ROLE
|
|
497
|
-
//
|
|
498
|
-
//
|
|
579
|
+
if (step.anchor.kind === AnchorKind.ROLE) {
|
|
580
|
+
// Named or nameless, a role anchor resolves by role. The degraded placeholder and the
|
|
581
|
+
// document-key press were handled above, so what reaches here addresses a real control:
|
|
582
|
+
// a nameless one (an icon-only button) by its position among nameless controls of its
|
|
583
|
+
// role. It used to fall through to the testid path, which queried the ROLE as a testid
|
|
584
|
+
// and answered `testid "button" not found` on a working page.
|
|
499
585
|
return runRoleStep(session, step, index, step.anchor, confirmDangerous, sleep);
|
|
500
586
|
}
|
|
501
587
|
return runTestidStep(session, step, index, label, dynamic, confirmDangerous, sleep);
|
|
@@ -516,30 +602,40 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
516
602
|
// A setup step's consequence is not checked: it is re-driven to get somewhere, not to prove it.
|
|
517
603
|
const checked = index < from ? [] : own;
|
|
518
604
|
for (const expectation of checked) {
|
|
519
|
-
if (!result.ok || result.drift !== undefined)
|
|
605
|
+
if (!result.ok || result.drift !== undefined) {
|
|
520
606
|
break;
|
|
521
|
-
|
|
607
|
+
}
|
|
608
|
+
const expectDrift = await assertStepExpect(session, expectation, waitForSignal, waitFor(step), cursorBefore, baselines);
|
|
522
609
|
if (expectDrift !== undefined) {
|
|
523
610
|
result.ok = false;
|
|
524
611
|
result.drift = expectDrift;
|
|
525
612
|
}
|
|
526
613
|
}
|
|
527
|
-
if (page !== undefined)
|
|
614
|
+
if (page !== undefined) {
|
|
528
615
|
result.page = page;
|
|
616
|
+
}
|
|
529
617
|
const endPage = currentRoute(session);
|
|
530
|
-
if (endPage !== undefined && endPage !== page)
|
|
618
|
+
if (endPage !== undefined && endPage !== page) {
|
|
531
619
|
result.endPage = endPage;
|
|
620
|
+
}
|
|
532
621
|
const windowEvents = session.eventsSince(cursorBefore).filter((e) => e.t >= cursorBefore);
|
|
622
|
+
const loadSince = 0 === index ? options.loadSince : undefined;
|
|
623
|
+
const loadEvents = loadSince === undefined
|
|
624
|
+
? []
|
|
625
|
+
: session.eventsSince(loadSince).filter((e) => e.t < cursorBefore);
|
|
533
626
|
const consequence = summarizeConsequence(windowEvents);
|
|
534
|
-
if (consequence !== undefined)
|
|
627
|
+
if (consequence !== undefined) {
|
|
535
628
|
result.consequence = consequence;
|
|
629
|
+
}
|
|
536
630
|
const consoleErrors = consoleErrorsIn(windowEvents);
|
|
537
|
-
if (consoleErrors !== undefined)
|
|
631
|
+
if (consoleErrors !== undefined) {
|
|
538
632
|
result.consoleErrors = consoleErrors;
|
|
633
|
+
}
|
|
539
634
|
for (const expectation of checked) {
|
|
540
|
-
if (!result.ok || result.drift !== undefined)
|
|
635
|
+
if (!result.ok || result.drift !== undefined) {
|
|
541
636
|
break;
|
|
542
|
-
|
|
637
|
+
}
|
|
638
|
+
const late = lateConsoleDrift(expectation, windowEvents) ?? loadDrift(expectation, loadEvents);
|
|
543
639
|
if (late !== undefined) {
|
|
544
640
|
result.ok = false;
|
|
545
641
|
result.drift = late;
|
|
@@ -551,9 +647,21 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
551
647
|
const cursorAfter = session.elapsed();
|
|
552
648
|
// ONE builder, shared with the live act path, so a driven step and a replayed one describe what
|
|
553
649
|
// happened in identical words rather than in two vocabularies that agree by coincidence.
|
|
554
|
-
|
|
555
|
-
// A failure the step declared is its expected result, as it is on the live path.
|
|
556
|
-
|
|
650
|
+
const effect = stepEffect(windowEvents, { since: cursorBefore, until: cursorAfter },
|
|
651
|
+
// A failure the step declared is its expected result, as it is on the live path. The reads
|
|
652
|
+
// that finished before the step let a background read that fails on every tick be left
|
|
653
|
+
// alone, instead of being blamed on each step it happens to fall inside.
|
|
654
|
+
{
|
|
655
|
+
...appScope(session),
|
|
656
|
+
declared,
|
|
657
|
+
before: session
|
|
658
|
+
.eventsSince(0)
|
|
659
|
+
.filter((e) => e.t < cursorBefore && e.type === EventType.NET_REQUEST),
|
|
660
|
+
});
|
|
661
|
+
// "Never settled" is decided once the replay is over, by the whole-span pass (flow-cross-step).
|
|
662
|
+
const stepOwn = stepContradictions(effect.contradictions ?? []);
|
|
663
|
+
delete effect.contradictions;
|
|
664
|
+
Object.assign(result, effect, 0 === stepOwn.length ? {} : { contradictions: stepOwn });
|
|
557
665
|
/*
|
|
558
666
|
* A prefix step is setup and is not reported -- UNLESS it failed.
|
|
559
667
|
*
|
|
@@ -563,14 +671,16 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
563
671
|
*/
|
|
564
672
|
// `tool` is dropped HERE rather than at the ten places that set it, so a new step runner cannot
|
|
565
673
|
// forget the rule and quietly re-introduce the cost. Spelled out only when it is NOT the default.
|
|
566
|
-
if (FlowStepTool.ACT === result.tool)
|
|
674
|
+
if (FlowStepTool.ACT === result.tool) {
|
|
567
675
|
delete result.tool;
|
|
676
|
+
}
|
|
568
677
|
if (index < from && result.ok && result.drift === undefined) {
|
|
569
678
|
index += 1;
|
|
570
679
|
continue;
|
|
571
680
|
}
|
|
572
|
-
if (index < from)
|
|
681
|
+
if (index < from) {
|
|
573
682
|
result.note = `setup step for resuming at step ${String(from)} failed`;
|
|
683
|
+
}
|
|
574
684
|
await options.onStepResult?.(result, flow.name).catch(() => undefined);
|
|
575
685
|
results.push(result);
|
|
576
686
|
tell(result);
|
|
@@ -579,14 +689,21 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
|
|
|
579
689
|
const sweepPast = true === options.sweep &&
|
|
580
690
|
result.drift !== undefined &&
|
|
581
691
|
isConsequenceDrift(result.drift.reasonKind);
|
|
582
|
-
if (!sweepPast && (result.drift !== undefined || !result.ok))
|
|
692
|
+
if (!sweepPast && (result.drift !== undefined || !result.ok)) {
|
|
583
693
|
break;
|
|
694
|
+
}
|
|
584
695
|
index += 1;
|
|
585
696
|
}
|
|
697
|
+
// And once after the last step: a page can satisfy the last expect at once and finish its own
|
|
698
|
+
// work after (a sort header marks itself, then fetches the rows). The journey is judged next.
|
|
699
|
+
if (results.length > 0) {
|
|
700
|
+
await waitForSignal(session, { kind: PredicateKind.SETTLED, quietMs: STEP_QUIET_MS }, STEP_QUIET_BUDGET_MS);
|
|
701
|
+
}
|
|
586
702
|
}
|
|
587
703
|
catch (error) {
|
|
588
|
-
if (!isDocumentGoneError(error))
|
|
704
|
+
if (!isDocumentGoneError(error)) {
|
|
589
705
|
throw error;
|
|
706
|
+
}
|
|
590
707
|
throw new DocumentLostDuringReplay(results, index, error);
|
|
591
708
|
}
|
|
592
709
|
return results;
|
|
@@ -53,6 +53,9 @@ function relax(predicate, money) {
|
|
|
53
53
|
}
|
|
54
54
|
if (PredicateKind.TEXT === predicate.kind &&
|
|
55
55
|
predicate.satisfies === undefined &&
|
|
56
|
+
// An absent count stays exact: made word-level, it says no count is on the page, and a page
|
|
57
|
+
// that shows another count fails it.
|
|
58
|
+
true !== predicate.absent &&
|
|
56
59
|
predicate.contains !== undefined &&
|
|
57
60
|
HAS_NUMBER.test(predicate.contains)) {
|
|
58
61
|
const relaxed = wordLevel(predicate.contains, money);
|
|
@@ -143,7 +146,10 @@ export function replayability(flow) {
|
|
|
143
146
|
(AnchorKind.ROLE === anchor?.kind && DEGRADED_ANCHOR_ROLE === anchor.role))) {
|
|
144
147
|
blocked.push(`step ${at} has no anchor a replay can find`);
|
|
145
148
|
}
|
|
146
|
-
else if (own &&
|
|
149
|
+
else if (own &&
|
|
150
|
+
AnchorKind.TESTID === anchor?.kind &&
|
|
151
|
+
anchor.of !== undefined &&
|
|
152
|
+
anchor.nth === undefined) {
|
|
147
153
|
blocked.push(`step ${at} anchors on test id '${anchor.value}', which ${String(anchor.of)} elements share`);
|
|
148
154
|
}
|
|
149
155
|
if (own &&
|
|
@@ -33,9 +33,15 @@ export interface StartPathArrival {
|
|
|
33
33
|
redirected?: string;
|
|
34
34
|
/** Set when the page's loading kept replay waiting past the step budget. */
|
|
35
35
|
wait?: StartPageWait;
|
|
36
|
+
/** Set when the flow declares a route and replay could not put the tab on it: nothing may run (#2034). */
|
|
37
|
+
unreached?: string;
|
|
36
38
|
}
|
|
37
39
|
/** The replay result, with how long replay waited for its start page when that went past the budget. */
|
|
38
40
|
export declare function withStartPageWait<R extends FlowReplayResult>(result: R, arrival: StartPathArrival): R;
|
|
41
|
+
/** Record where a replay left the tab, so the next reset does not keep that flow's query. */
|
|
42
|
+
export declare function noteReplayEnded(session: StartPathSession & {
|
|
43
|
+
id: string;
|
|
44
|
+
}): void;
|
|
39
45
|
/**
|
|
40
46
|
* Replay's half of the FlowFile contract: put the tab on the flow's `startPath` before step 1, and
|
|
41
47
|
* RELOAD it if it is already there.
|
|
@@ -50,6 +56,10 @@ export declare function withStartPageWait<R extends FlowReplayResult>(result: R,
|
|
|
50
56
|
* or from the wrong page. Silence is not an opt-out: a flow that depends on leftovers without
|
|
51
57
|
* saying so is a flow whose green means nothing on a fresh machine, and CI is a fresh machine.
|
|
52
58
|
*
|
|
59
|
+
* A route in `requires` is not an opt-out. It is the page the flow needs, and replay goes there:
|
|
60
|
+
* see `arriveAtDeclaredRoute`. A suite ran such a flow on the page the previous flow left open,
|
|
61
|
+
* and its anchors then failed, or matched ambiguously, on an unrelated page (#2034).
|
|
62
|
+
*
|
|
53
63
|
* A full-page load tears down the session socket, so the navigation must happen here — before any
|
|
54
64
|
* step runs — and the replay continues on the session the SDK reconnects as (found via the same
|
|
55
65
|
* tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
|
|
@@ -63,6 +73,11 @@ export declare function arriveAtStartPath(sessions: SessionManager, session: Sta
|
|
|
63
73
|
}, flow: FlowFile, timeoutMs?: number, clock?: ArrivalClock,
|
|
64
74
|
/** Whether the tab is still loading. Replay passes one for a pool lease, the only tab it can ask. */
|
|
65
75
|
stillLoading?: () => Promise<boolean>, loadingCapMs?: number): Promise<StartPathArrival>;
|
|
76
|
+
/**
|
|
77
|
+
* The arrival for a replay that continues at a step and so does not move the tab. A declared route
|
|
78
|
+
* claim must still hold where the tab is, as it did when it was a precondition the run checked.
|
|
79
|
+
*/
|
|
80
|
+
export declare function routeClaimsWhereTheTabIs(flow: FlowFile, session: StartPathSession): StartPathArrival;
|
|
66
81
|
/**
|
|
67
82
|
* Send a tab somewhere and wait for the session that comes back.
|
|
68
83
|
*
|
|
@@ -95,14 +110,28 @@ export type NavigateOutcome = {
|
|
|
95
110
|
stillLoading: boolean;
|
|
96
111
|
wait?: StartPageWait;
|
|
97
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Whether assigning `to` from `from` is a jump within the same document. That is the case when the
|
|
115
|
+
* URLs differ at most in the fragment and `to` has a fragment; a browser then loads no page. Without
|
|
116
|
+
* a fragment, the same URL reloads by itself. Undefined or unparsable URLs are not a jump.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isFragmentJump(from: string | undefined, to: string): boolean;
|
|
98
119
|
export declare function navigateAndAwait(sessions: SessionManager, session: {
|
|
99
120
|
id: string;
|
|
100
121
|
/** Optional because a session's URL is, and `carryReticleIdentity` already handles its absence. */
|
|
101
122
|
url?: string;
|
|
102
123
|
command(name: string, args?: Record<string, unknown>): Promise<CommandResult>;
|
|
103
|
-
}, destination: string, expectedPath: string, timeoutMs: number, clock?: ArrivalClock,
|
|
104
|
-
/** True when the destination is the page we are already on — see `arrivedSuccessor`. */
|
|
105
|
-
isReload?: boolean
|
|
106
|
-
/** Whether the navigated page is still loading; absent when it cannot be asked. */
|
|
107
|
-
stillLoading?: () => Promise<boolean
|
|
124
|
+
}, destination: string, expectedPath: string, timeoutMs: number, clock?: ArrivalClock, options?: {
|
|
125
|
+
/** True when the destination is the page we are already on — see `arrivedSuccessor`. */
|
|
126
|
+
isReload?: boolean;
|
|
127
|
+
/** Whether the navigated page is still loading; absent when it cannot be asked. */
|
|
128
|
+
stillLoading?: () => Promise<boolean>;
|
|
129
|
+
loadingCapMs?: number;
|
|
130
|
+
/**
|
|
131
|
+
* Accept the first NEW page at once, on any path. A flow's `navigate` step opens a URL the
|
|
132
|
+
* server may redirect (a paused short link goes to `/404`), and the page that connects is the
|
|
133
|
+
* destination. Start-page arrival leaves this off: it waits for its own path first.
|
|
134
|
+
*/
|
|
135
|
+
acceptRedirect?: boolean;
|
|
136
|
+
}): Promise<NavigateOutcome>;
|
|
108
137
|
export {};
|