@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verdict of `reticle compare`: what the candidate shows that the reference does not. Pure.
|
|
3
|
+
*
|
|
4
|
+
* The reference is read two times. A line that differs between those two runs is noise (a clock, a
|
|
5
|
+
* random id) and is ignored in that sequence. A sequence the reference did on only one of its runs
|
|
6
|
+
* is not compared. A step the candidate cannot do where the reference could is a difference.
|
|
7
|
+
* Differences with the same lines are one difference, with one id and the number of sequences it
|
|
8
|
+
* was seen in.
|
|
9
|
+
*
|
|
10
|
+
* With a base build (the app before the change), each difference also gets a tag:
|
|
11
|
+
* - `regression`: base and reference show the same page, and the candidate changed it.
|
|
12
|
+
* - `target-missed`: the reference changed the page, and the candidate still shows the base page.
|
|
13
|
+
* - `changed`: the candidate differs from both, or the base did not show this sequence.
|
|
14
|
+
* The score is targets matched / targets x (1 - kept sequences changed / kept). A target is a
|
|
15
|
+
* sequence where the reference changed the base page; a kept sequence is one where it did not.
|
|
16
|
+
*/
|
|
17
|
+
import { DifferenceKind } from './compare-diff.js';
|
|
18
|
+
import type { BuildReadings } from './build-readings.js';
|
|
19
|
+
import type { PlanDropped } from './action-plan.js';
|
|
20
|
+
export declare const CompareVerdict: {
|
|
21
|
+
readonly SAME: "same";
|
|
22
|
+
readonly DIFFERENT: "different";
|
|
23
|
+
/** The run proves nothing: the driver lost its session, or nothing could be compared. */
|
|
24
|
+
readonly INVALID: "invalid";
|
|
25
|
+
};
|
|
26
|
+
export type CompareVerdict = (typeof CompareVerdict)[keyof typeof CompareVerdict];
|
|
27
|
+
export declare const DifferenceTag: {
|
|
28
|
+
readonly REGRESSION: "regression";
|
|
29
|
+
readonly TARGET_MISSED: "target-missed";
|
|
30
|
+
readonly CHANGED: "changed";
|
|
31
|
+
};
|
|
32
|
+
export type DifferenceTag = (typeof DifferenceTag)[keyof typeof DifferenceTag];
|
|
33
|
+
export interface BehaviourDifference {
|
|
34
|
+
/** A hash of the difference's lines: the same lines give the same id on every run. */
|
|
35
|
+
id: string;
|
|
36
|
+
kind: DifferenceKind;
|
|
37
|
+
/** The steps that first reached it. */
|
|
38
|
+
actions: string[];
|
|
39
|
+
/** Lines only the reference shows. */
|
|
40
|
+
removed: string[];
|
|
41
|
+
/** Lines only the candidate shows. */
|
|
42
|
+
added: string[];
|
|
43
|
+
/** For `unreached`: the step the candidate could not do. */
|
|
44
|
+
failedAt?: string;
|
|
45
|
+
/** In how many sequences this same difference was seen. */
|
|
46
|
+
seen: number;
|
|
47
|
+
/** With a base build only. */
|
|
48
|
+
tag?: DifferenceTag;
|
|
49
|
+
}
|
|
50
|
+
export interface BehaviourScore {
|
|
51
|
+
/** Null when the plan reached no target: the score cannot see the change. */
|
|
52
|
+
score: number | null;
|
|
53
|
+
targets: number;
|
|
54
|
+
matched: number;
|
|
55
|
+
kept: number;
|
|
56
|
+
changed: number;
|
|
57
|
+
}
|
|
58
|
+
export interface BehaviourComparison {
|
|
59
|
+
verdict: CompareVerdict;
|
|
60
|
+
/** Planned sequences. */
|
|
61
|
+
sequences: number;
|
|
62
|
+
/** Sequences both builds showed, and were compared. */
|
|
63
|
+
compared: number;
|
|
64
|
+
/** Sequences the reference did on only one of its two runs, so they were not compared. */
|
|
65
|
+
unstable: number;
|
|
66
|
+
/** Sequences the reference could not do on its first run, so they were not compared. */
|
|
67
|
+
referenceFailed: number;
|
|
68
|
+
/** Lines that differed but were ignored, because they also differed between the reference runs. */
|
|
69
|
+
ignored: number;
|
|
70
|
+
differences: BehaviourDifference[];
|
|
71
|
+
score?: BehaviourScore;
|
|
72
|
+
/** What the plan left out because of a cap. Absent when no plan was made. */
|
|
73
|
+
planDropped?: PlanDropped;
|
|
74
|
+
/** Why the run is invalid. */
|
|
75
|
+
reason?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Sequences where a candidate step failed once and was done on a retry from a fresh page. They do not change the
|
|
78
|
+
* verdict, and they are never hidden: a fault that comes and goes is reported here. Absent when there were none.
|
|
79
|
+
*/
|
|
80
|
+
intermittent?: {
|
|
81
|
+
actions: string[];
|
|
82
|
+
step: string;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
export declare const MSG_NOTHING_COMPARED: string;
|
|
86
|
+
/** A comparison that proves nothing. */
|
|
87
|
+
export declare function invalidComparison(reason: string, sequences: number): BehaviourComparison;
|
|
88
|
+
interface Input {
|
|
89
|
+
/** The sequence keys, in plan order. */
|
|
90
|
+
order: readonly string[];
|
|
91
|
+
reference: BuildReadings;
|
|
92
|
+
again: BuildReadings;
|
|
93
|
+
candidate: BuildReadings;
|
|
94
|
+
base?: BuildReadings;
|
|
95
|
+
}
|
|
96
|
+
export declare function compareBehaviour(input: Input): BehaviourComparison;
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verdict of `reticle compare`: what the candidate shows that the reference does not. Pure.
|
|
3
|
+
*
|
|
4
|
+
* The reference is read two times. A line that differs between those two runs is noise (a clock, a
|
|
5
|
+
* random id) and is ignored in that sequence. A sequence the reference did on only one of its runs
|
|
6
|
+
* is not compared. A step the candidate cannot do where the reference could is a difference.
|
|
7
|
+
* Differences with the same lines are one difference, with one id and the number of sequences it
|
|
8
|
+
* was seen in.
|
|
9
|
+
*
|
|
10
|
+
* With a base build (the app before the change), each difference also gets a tag:
|
|
11
|
+
* - `regression`: base and reference show the same page, and the candidate changed it.
|
|
12
|
+
* - `target-missed`: the reference changed the page, and the candidate still shows the base page.
|
|
13
|
+
* - `changed`: the candidate differs from both, or the base did not show this sequence.
|
|
14
|
+
* The score is targets matched / targets x (1 - kept sequences changed / kept). A target is a
|
|
15
|
+
* sequence where the reference changed the base page; a kept sequence is one where it did not.
|
|
16
|
+
*/
|
|
17
|
+
import { DifferenceKind, differenceId, lineDiff, maskedDiff } from './compare-diff.js';
|
|
18
|
+
import { isLoadedRead } from './outside/effect-lines.js';
|
|
19
|
+
export const CompareVerdict = {
|
|
20
|
+
SAME: 'same',
|
|
21
|
+
DIFFERENT: 'different',
|
|
22
|
+
/** The run proves nothing: the driver lost its session, or nothing could be compared. */
|
|
23
|
+
INVALID: 'invalid',
|
|
24
|
+
};
|
|
25
|
+
export const DifferenceTag = {
|
|
26
|
+
REGRESSION: 'regression',
|
|
27
|
+
TARGET_MISSED: 'target-missed',
|
|
28
|
+
CHANGED: 'changed',
|
|
29
|
+
};
|
|
30
|
+
export const MSG_NOTHING_COMPARED = 'Nothing was compared, so nothing was proved. The reference did no sequence two times the same way. ' +
|
|
31
|
+
'Make sure the reference url opens, and that its page does not change on each load.';
|
|
32
|
+
/** A comparison that proves nothing. */
|
|
33
|
+
export function invalidComparison(reason, sequences) {
|
|
34
|
+
return {
|
|
35
|
+
verdict: CompareVerdict.INVALID,
|
|
36
|
+
sequences,
|
|
37
|
+
compared: 0,
|
|
38
|
+
unstable: 0,
|
|
39
|
+
referenceFailed: 0,
|
|
40
|
+
ignored: 0,
|
|
41
|
+
differences: [],
|
|
42
|
+
reason,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const DIGITS = /\d+/g;
|
|
46
|
+
const HAS_DIGIT = /\d/;
|
|
47
|
+
const DIGIT_MARK = '#';
|
|
48
|
+
/** A line with its numbers folded: `ticker: 76` and `ticker: 77` have one shape. */
|
|
49
|
+
const shapeOf = (line) => line.replace(DIGITS, DIGIT_MARK);
|
|
50
|
+
const runNoise = (ours, repeat) => {
|
|
51
|
+
if (ours?.lines === undefined || repeat?.lines === undefined) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const d = lineDiff(ours.lines, repeat.lines);
|
|
55
|
+
return [...d.removed, ...d.added];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* What is noise in every sequence: a read that loaded (`isLoadedRead`), and the shapes of the noise lines that
|
|
59
|
+
* hold a number, over every sequence. A live counter read the
|
|
60
|
+
* same on both reference runs in one sequence by chance, and was then a difference there. When the
|
|
61
|
+
* reference runs showed it changing in any sequence, it is noise in all of them.
|
|
62
|
+
*/
|
|
63
|
+
function counterShapes(input) {
|
|
64
|
+
const shapes = new Set();
|
|
65
|
+
for (const key of input.order) {
|
|
66
|
+
for (const line of runNoise(input.reference.get(key), input.again.get(key))) {
|
|
67
|
+
if (HAS_DIGIT.test(line)) {
|
|
68
|
+
shapes.add(shapeOf(line));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return (line) => shapes.has(shapeOf(line)) || isLoadedRead(line);
|
|
73
|
+
}
|
|
74
|
+
/** The noise of one sequence: what its reference runs disagree on, and every counter shape. */
|
|
75
|
+
const noiseOf = (ours, repeat, counters) => {
|
|
76
|
+
const own = new Set(runNoise(ours, repeat));
|
|
77
|
+
return (line) => own.has(line) || counters(line);
|
|
78
|
+
};
|
|
79
|
+
/** The visible page without its noise lines, as one string, or undefined when it was not shown. */
|
|
80
|
+
const masked = (reading, isNoise) => reading?.lines?.filter((line) => !isNoise(line)).join('\n');
|
|
81
|
+
function tagOf(input, key, noise) {
|
|
82
|
+
const b = masked(input.base?.get(key), noise);
|
|
83
|
+
if (b === undefined) {
|
|
84
|
+
return DifferenceTag.CHANGED;
|
|
85
|
+
}
|
|
86
|
+
if (b === masked(input.reference.get(key), noise)) {
|
|
87
|
+
return DifferenceTag.REGRESSION;
|
|
88
|
+
}
|
|
89
|
+
return b === masked(input.candidate.get(key), noise)
|
|
90
|
+
? DifferenceTag.TARGET_MISSED
|
|
91
|
+
: DifferenceTag.CHANGED;
|
|
92
|
+
}
|
|
93
|
+
function scoreOf(input, base, counters) {
|
|
94
|
+
let targets = 0;
|
|
95
|
+
let matched = 0;
|
|
96
|
+
let kept = 0;
|
|
97
|
+
let changed = 0;
|
|
98
|
+
for (const key of input.order) {
|
|
99
|
+
const ours = input.reference.get(key);
|
|
100
|
+
const repeat = input.again.get(key);
|
|
101
|
+
if (ours?.lines === undefined || repeat?.lines === undefined) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const noise = noiseOf(ours, repeat, counters);
|
|
105
|
+
const r = masked(ours, noise);
|
|
106
|
+
const b = masked(base.get(key), noise);
|
|
107
|
+
if (b === undefined) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const got = masked(input.candidate.get(key), noise);
|
|
111
|
+
if (b === r) {
|
|
112
|
+
kept += 1;
|
|
113
|
+
changed += got === r ? 0 : 1;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
targets += 1;
|
|
117
|
+
matched += got === r ? 1 : 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const progress = 0 === targets ? null : matched / targets;
|
|
121
|
+
const score = null === progress ? null : progress * (0 === kept ? 1 : 1 - changed / kept);
|
|
122
|
+
return { score, targets, matched, kept, changed };
|
|
123
|
+
}
|
|
124
|
+
export function compareBehaviour(input) {
|
|
125
|
+
const counters = counterShapes(input);
|
|
126
|
+
const found = new Map();
|
|
127
|
+
let compared = 0;
|
|
128
|
+
let unstable = 0;
|
|
129
|
+
let referenceFailed = 0;
|
|
130
|
+
let ignored = 0;
|
|
131
|
+
const intermittent = [];
|
|
132
|
+
const note = (key, difference) => {
|
|
133
|
+
const id = differenceId(DifferenceKind.UNREACHED === difference.kind
|
|
134
|
+
? { unreached: difference.failedAt }
|
|
135
|
+
: { removed: difference.removed, added: difference.added });
|
|
136
|
+
const known = found.get(id);
|
|
137
|
+
if (known !== undefined) {
|
|
138
|
+
known.seen += 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const tag = input.base === undefined
|
|
142
|
+
? undefined
|
|
143
|
+
: tagOf(input, key, noiseOf(input.reference.get(key), input.again.get(key), counters));
|
|
144
|
+
found.set(id, { id, seen: 1, ...difference, ...(tag === undefined ? {} : { tag }) });
|
|
145
|
+
};
|
|
146
|
+
for (const key of input.order) {
|
|
147
|
+
const ours = input.reference.get(key);
|
|
148
|
+
if (ours?.lines === undefined) {
|
|
149
|
+
referenceFailed += 1;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const repeat = input.again.get(key)?.lines;
|
|
153
|
+
if (repeat === undefined) {
|
|
154
|
+
unstable += 1;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const got = input.candidate.get(key);
|
|
158
|
+
if (got?.retriedAt !== undefined) {
|
|
159
|
+
intermittent.push({ actions: got.actions, step: got.retriedAt });
|
|
160
|
+
}
|
|
161
|
+
if (got?.lines === undefined) {
|
|
162
|
+
const failedAt = got?.failedAt ?? ours.actions.at(-1) ?? '';
|
|
163
|
+
note(key, {
|
|
164
|
+
kind: DifferenceKind.UNREACHED,
|
|
165
|
+
actions: ours.actions,
|
|
166
|
+
removed: [],
|
|
167
|
+
added: [],
|
|
168
|
+
failedAt,
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
compared += 1;
|
|
173
|
+
const diff = maskedDiff(ours.lines, repeat, got.lines, counters);
|
|
174
|
+
ignored += diff.ignored;
|
|
175
|
+
if (diff.removed.length > 0 || diff.added.length > 0) {
|
|
176
|
+
note(key, {
|
|
177
|
+
kind: DifferenceKind.CHANGED,
|
|
178
|
+
actions: ours.actions,
|
|
179
|
+
removed: diff.removed,
|
|
180
|
+
added: diff.added,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const differences = [...found.values()];
|
|
185
|
+
const counts = {
|
|
186
|
+
sequences: input.order.length,
|
|
187
|
+
compared,
|
|
188
|
+
unstable,
|
|
189
|
+
referenceFailed,
|
|
190
|
+
ignored,
|
|
191
|
+
};
|
|
192
|
+
if (0 === compared && 0 === differences.length) {
|
|
193
|
+
return { ...invalidComparison(MSG_NOTHING_COMPARED, input.order.length), ...counts };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
verdict: 0 === differences.length ? CompareVerdict.SAME : CompareVerdict.DIFFERENT,
|
|
197
|
+
...counts,
|
|
198
|
+
differences,
|
|
199
|
+
...(input.base === undefined ? {} : { score: scoreOf(input, input.base, counters) }),
|
|
200
|
+
...(0 === intermittent.length ? {} : { intermittent }),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `reticle compare` prints and exits with.
|
|
3
|
+
*
|
|
4
|
+
* The text shows the verdict first, then the counts, the score when a base was given, and the top
|
|
5
|
+
* differences. Each difference shows the steps that reproduce it and its `-` and `+` lines. The text
|
|
6
|
+
* shows the first `TOP_DIFFERENCES` differences and the first `MAX_DIFFERENCE_LINES` lines of each,
|
|
7
|
+
* and says how many it left out. `--json` gives everything.
|
|
8
|
+
*/
|
|
9
|
+
import { Verified } from '@reticlehq/core';
|
|
10
|
+
import { type BehaviourComparison } from './behaviour-diff.js';
|
|
11
|
+
export declare const CompareExit: {
|
|
12
|
+
readonly SAME: 0;
|
|
13
|
+
readonly DIFFERENT: 1;
|
|
14
|
+
readonly INVALID: 2;
|
|
15
|
+
};
|
|
16
|
+
export declare const COMPARE_SCHEMA_VERSION = 1;
|
|
17
|
+
export declare const TOP_DIFFERENCES = 5;
|
|
18
|
+
export declare const MAX_DIFFERENCE_LINES = 12;
|
|
19
|
+
export interface CompareUrls {
|
|
20
|
+
reference: string;
|
|
21
|
+
candidate: string;
|
|
22
|
+
base?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Seconds spent on each part of the run. */
|
|
25
|
+
export interface CompareTimings {
|
|
26
|
+
plan: number;
|
|
27
|
+
reference: number;
|
|
28
|
+
candidate: number;
|
|
29
|
+
base?: number;
|
|
30
|
+
again: number;
|
|
31
|
+
}
|
|
32
|
+
export declare const compareExitCode: (result: BehaviourComparison) => number;
|
|
33
|
+
export declare function compareReportLines(urls: CompareUrls, result: BehaviourComparison, timings?: CompareTimings): string[];
|
|
34
|
+
/** The `--json` object: every difference and every count the text gives. */
|
|
35
|
+
export declare function compareReportJson(urls: CompareUrls, result: BehaviourComparison, timings?: CompareTimings): CompareUrls & BehaviourComparison & {
|
|
36
|
+
schemaVersion: number;
|
|
37
|
+
verified: Verified;
|
|
38
|
+
exitCode: number;
|
|
39
|
+
seconds?: CompareTimings;
|
|
40
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `reticle compare` prints and exits with.
|
|
3
|
+
*
|
|
4
|
+
* The text shows the verdict first, then the counts, the score when a base was given, and the top
|
|
5
|
+
* differences. Each difference shows the steps that reproduce it and its `-` and `+` lines. The text
|
|
6
|
+
* shows the first `TOP_DIFFERENCES` differences and the first `MAX_DIFFERENCE_LINES` lines of each,
|
|
7
|
+
* and says how many it left out. `--json` gives everything.
|
|
8
|
+
*/
|
|
9
|
+
import { Verified } from '@reticlehq/core';
|
|
10
|
+
import { NOTHING_DROPPED } from './action-plan.js';
|
|
11
|
+
import { DifferenceKind } from './compare-diff.js';
|
|
12
|
+
import { CompareVerdict, } from './behaviour-diff.js';
|
|
13
|
+
export const CompareExit = { SAME: 0, DIFFERENT: 1, INVALID: 2 };
|
|
14
|
+
export const COMPARE_SCHEMA_VERSION = 1;
|
|
15
|
+
export const TOP_DIFFERENCES = 5;
|
|
16
|
+
export const MAX_DIFFERENCE_LINES = 12;
|
|
17
|
+
const INDENT = ' ';
|
|
18
|
+
const SCORE_DIGITS = 2;
|
|
19
|
+
const VERDICT_WORD = {
|
|
20
|
+
[CompareVerdict.SAME]: 'SAME',
|
|
21
|
+
[CompareVerdict.DIFFERENT]: 'DIFFERENT',
|
|
22
|
+
[CompareVerdict.INVALID]: 'INVALID',
|
|
23
|
+
};
|
|
24
|
+
const EXIT = {
|
|
25
|
+
[CompareVerdict.SAME]: CompareExit.SAME,
|
|
26
|
+
[CompareVerdict.DIFFERENT]: CompareExit.DIFFERENT,
|
|
27
|
+
[CompareVerdict.INVALID]: CompareExit.INVALID,
|
|
28
|
+
};
|
|
29
|
+
export const compareExitCode = (result) => EXIT[result.verdict];
|
|
30
|
+
/** The verdict in Reticle's own word: a run that proves nothing is `unknown`, never `no`. */
|
|
31
|
+
const VERIFIED = {
|
|
32
|
+
[CompareVerdict.SAME]: Verified.YES,
|
|
33
|
+
[CompareVerdict.DIFFERENT]: Verified.NO,
|
|
34
|
+
[CompareVerdict.INVALID]: Verified.UNKNOWN,
|
|
35
|
+
};
|
|
36
|
+
const plural = (n, one, many) => `${String(n)} ${1 === n ? one : many}`;
|
|
37
|
+
function headline(urls, result) {
|
|
38
|
+
const word = VERDICT_WORD[result.verdict];
|
|
39
|
+
if (CompareVerdict.INVALID === result.verdict) {
|
|
40
|
+
return `${word}: ${result.reason ?? ''}`;
|
|
41
|
+
}
|
|
42
|
+
if (CompareVerdict.SAME === result.verdict) {
|
|
43
|
+
return `${word}: ${urls.candidate} shows what ${urls.reference} shows after every sequence.`;
|
|
44
|
+
}
|
|
45
|
+
return `${word}: ${urls.candidate} does not show what ${urls.reference} shows. ${plural(result.differences.length, 'difference', 'differences')}.`;
|
|
46
|
+
}
|
|
47
|
+
function timingLine(t) {
|
|
48
|
+
const parts = [
|
|
49
|
+
`plan ${String(t.plan)} s`,
|
|
50
|
+
`reference ${String(t.reference)} s`,
|
|
51
|
+
`candidate ${String(t.candidate)} s`,
|
|
52
|
+
...(t.base === undefined ? [] : [`base ${String(t.base)} s`]),
|
|
53
|
+
`reference again ${String(t.again)} s`,
|
|
54
|
+
];
|
|
55
|
+
return `Time: ${parts.join(', ')}.`;
|
|
56
|
+
}
|
|
57
|
+
function scoreLine(result) {
|
|
58
|
+
const s = result.score;
|
|
59
|
+
if (s === undefined) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const tail = `${String(s.matched)} of ${String(s.targets)} targets matched, ${String(s.changed)} of ${String(s.kept)} kept sequences changed.`;
|
|
63
|
+
if (null === s.score) {
|
|
64
|
+
return `score: none. The plan reached no page that the reference changed from the base. ${tail}`;
|
|
65
|
+
}
|
|
66
|
+
return `score ${s.score.toFixed(SCORE_DIGITS)}: ${tail}`;
|
|
67
|
+
}
|
|
68
|
+
function differenceLines(index, d) {
|
|
69
|
+
const tag = d.tag === undefined ? '' : `[${d.tag}] `;
|
|
70
|
+
const where = plural(d.seen, 'sequence', 'sequences');
|
|
71
|
+
const out = [`${String(index + 1)}. ${tag}difference ${d.id} (seen in ${where})`];
|
|
72
|
+
out.push(...d.actions.map((a, i) => `${INDENT}${INDENT}${String(i + 1)}. ${a}`));
|
|
73
|
+
if (DifferenceKind.UNREACHED === d.kind) {
|
|
74
|
+
out.push(`${INDENT}The candidate could not do this step. The reference could: ${d.failedAt ?? ''}`);
|
|
75
|
+
}
|
|
76
|
+
const marked = [...d.removed.map((l) => `- ${l}`), ...d.added.map((l) => `+ ${l}`)];
|
|
77
|
+
out.push(...marked.slice(0, MAX_DIFFERENCE_LINES).map((l) => `${INDENT}${l}`));
|
|
78
|
+
if (marked.length > MAX_DIFFERENCE_LINES) {
|
|
79
|
+
out.push(`${INDENT}... ${String(marked.length - MAX_DIFFERENCE_LINES)} more lines (--json prints all)`);
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
export function compareReportLines(urls, result, timings) {
|
|
84
|
+
const lines = [headline(urls, result)];
|
|
85
|
+
if (result.sequences > 0) {
|
|
86
|
+
lines.push(`${plural(result.sequences, 'sequence', 'sequences')}, ${String(result.compared)} compared, ` +
|
|
87
|
+
`${String(result.unstable)} not compared (the reference did them on one of its two runs only), ` +
|
|
88
|
+
`${String(result.referenceFailed)} the reference could not do, ` +
|
|
89
|
+
`${String(result.ignored)} lines ignored as noise.`);
|
|
90
|
+
}
|
|
91
|
+
const dropped = Object.values(result.planDropped ?? NOTHING_DROPPED);
|
|
92
|
+
const capped = dropped.reduce((sum, n) => sum + n, 0);
|
|
93
|
+
if (capped > 0) {
|
|
94
|
+
lines.push(`The plan left out ${plural(capped, 'item', 'items')} at its caps. --json "planDropped" gives the count for each cap.`);
|
|
95
|
+
}
|
|
96
|
+
const score = scoreLine(result);
|
|
97
|
+
if (score !== undefined) {
|
|
98
|
+
lines.push(score);
|
|
99
|
+
}
|
|
100
|
+
if (timings !== undefined) {
|
|
101
|
+
lines.push(timingLine(timings));
|
|
102
|
+
}
|
|
103
|
+
// A retry never hides a fault that comes and goes: each such step is listed, whatever the verdict.
|
|
104
|
+
const flaky = result.intermittent ?? [];
|
|
105
|
+
if (flaky.length > 0) {
|
|
106
|
+
lines.push('', `${plural(flaky.length, 'step', 'steps')} failed once on the candidate and was done on a retry (check for a fault that comes and goes):`, ...flaky.map((f) => `${INDENT}${f.step} after: ${f.actions.join(' > ')}`));
|
|
107
|
+
}
|
|
108
|
+
const shown = result.differences.slice(0, TOP_DIFFERENCES);
|
|
109
|
+
for (const [i, d] of shown.entries()) {
|
|
110
|
+
lines.push('', ...differenceLines(i, d));
|
|
111
|
+
}
|
|
112
|
+
const left = result.differences.length - shown.length;
|
|
113
|
+
if (left > 0) {
|
|
114
|
+
lines.push('', `${plural(left, 'more difference', 'more differences')} (--json prints all).`);
|
|
115
|
+
}
|
|
116
|
+
return lines;
|
|
117
|
+
}
|
|
118
|
+
/** The `--json` object: every difference and every count the text gives. */
|
|
119
|
+
export function compareReportJson(urls, result, timings) {
|
|
120
|
+
return {
|
|
121
|
+
schemaVersion: COMPARE_SCHEMA_VERSION,
|
|
122
|
+
...urls,
|
|
123
|
+
...result,
|
|
124
|
+
verified: VERIFIED[result.verdict],
|
|
125
|
+
exitCode: compareExitCode(result),
|
|
126
|
+
...(timings === undefined ? {} : { seconds: timings }),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive one build through an action plan and read what it shows after each planned sequence.
|
|
3
|
+
*
|
|
4
|
+
* The page is read only after it settles: read, wait, read again, until two reads in a row match,
|
|
5
|
+
* up to `SETTLE_READS` reads. A page that never settles (a slideshow) gives its last read.
|
|
6
|
+
*
|
|
7
|
+
* A sequence that starts another planned sequence is not driven on its own: the longer one is
|
|
8
|
+
* driven, and the page is read after each of its steps. Each planned sequence is read once, in the
|
|
9
|
+
* first run that reaches it, and the order is the same on every build.
|
|
10
|
+
*
|
|
11
|
+
* Each reading also holds what the app did since the sequence's first action: its network calls and
|
|
12
|
+
* console errors and warnings (`effect-lines.ts`), read from the cursor that first action returned.
|
|
13
|
+
*
|
|
14
|
+
* A step that could not be done (the control is not there, or the action was refused) is recorded
|
|
15
|
+
* for that sequence and each longer one in the run. A driver that lost its browser session throws
|
|
16
|
+
* `DriverLostError`: that says nothing about the build, so the caller makes the whole run invalid.
|
|
17
|
+
*/
|
|
18
|
+
import { ActionType } from '@reticlehq/core';
|
|
19
|
+
import { type Probe, type Sequence } from './action-plan.js';
|
|
20
|
+
/** Visible text with no ref: the act tool finds the element by its text. */
|
|
21
|
+
export interface TextAnchor {
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
/** One request fault, in the shape `reticle_network_mock` takes. */
|
|
25
|
+
export interface FaultRule {
|
|
26
|
+
urlContains: string;
|
|
27
|
+
method: string;
|
|
28
|
+
status?: number;
|
|
29
|
+
body?: string;
|
|
30
|
+
abort?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** A browser tab on one build. */
|
|
33
|
+
export interface PageDriver {
|
|
34
|
+
/** Load the start page fresh. False when it did not load. */
|
|
35
|
+
open(): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Open the start page with `query` (`?scenario=deferred`) on the same tab. False when it did not
|
|
38
|
+
* load. A driver without it cannot do a step that opens a url parameter value.
|
|
39
|
+
*/
|
|
40
|
+
goto?(query: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Make requests fail on this tab, until the next `open`, which clears them. False when the faults
|
|
43
|
+
* were not set. A driver without it cannot do a step that makes a request fail.
|
|
44
|
+
*/
|
|
45
|
+
mock?(rules: readonly FaultRule[]): Promise<boolean>;
|
|
46
|
+
/** The whole page as an accessibility tree, with refs and test ids. */
|
|
47
|
+
read(): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Act on a control by ref, or click a text probe by its text. `ok` is false when the action was
|
|
50
|
+
* refused or did not land. `since` is the event cursor from before the action, when the driver
|
|
51
|
+
* gives one.
|
|
52
|
+
*/
|
|
53
|
+
act(at: string | TextAnchor, action: 'click' | 'fill' | 'select' | typeof ActionType.PRESS, value?: string): Promise<{
|
|
54
|
+
ok: boolean;
|
|
55
|
+
since?: number;
|
|
56
|
+
}>;
|
|
57
|
+
/** What the app did since the cursor, as comparable lines (`effect-lines.ts`). */
|
|
58
|
+
effects(since: number): Promise<string[]>;
|
|
59
|
+
}
|
|
60
|
+
/** The driver lost its browser session. The run says nothing about the build. */
|
|
61
|
+
export declare class DriverLostError extends Error {
|
|
62
|
+
name: string;
|
|
63
|
+
}
|
|
64
|
+
/** What one build showed after one planned sequence. */
|
|
65
|
+
export interface Reading {
|
|
66
|
+
/** The steps that reach it, as a person reads them, starting with the start page. */
|
|
67
|
+
actions: string[];
|
|
68
|
+
/** The visible lines. Absent when a step could not be done. */
|
|
69
|
+
lines?: string[];
|
|
70
|
+
/** The step that could not be done. */
|
|
71
|
+
failedAt?: string;
|
|
72
|
+
/** The step that failed once and was done on a retry from a fresh page. The comparison reports it. */
|
|
73
|
+
retriedAt?: string;
|
|
74
|
+
}
|
|
75
|
+
/** Readings by sequence key. */
|
|
76
|
+
export type BuildReadings = Map<string, Reading>;
|
|
77
|
+
export declare const SETTLE_READS = 4;
|
|
78
|
+
export declare const SETTLE_GAP_MS = 400;
|
|
79
|
+
/** The first action of every sequence. */
|
|
80
|
+
export declare const START_STEP = "open the start page";
|
|
81
|
+
type Sleep = (ms: number) => Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* What a person sees on the page, one line for each element, with the role of each control and
|
|
84
|
+
* structure element kept (`structureLines`) and sub-second times rounded.
|
|
85
|
+
*/
|
|
86
|
+
export declare function pageLines(tree: string): string[];
|
|
87
|
+
/** Read the page once it stops changing. */
|
|
88
|
+
export declare function settledRead(driver: PageDriver, sleep: Sleep): Promise<{
|
|
89
|
+
tree: string;
|
|
90
|
+
lines: string[];
|
|
91
|
+
settled: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Drive one sequence from a fresh load, for the plan, or undefined when a step could not be done.
|
|
95
|
+
*
|
|
96
|
+
* For a sequence that ends in a wait, the page is also read, settled, before the wait. The wait is
|
|
97
|
+
* `active` when the page did not settle before it, a network call was still pending before it, or
|
|
98
|
+
* the page changed during it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function probeSequence(driver: PageDriver, seq: Sequence, sleep: Sleep): Promise<Probe | undefined>;
|
|
101
|
+
/**
|
|
102
|
+
* Drive the plan on one build. With `readState`, the app's stored data is read after each sequence
|
|
103
|
+
* is read, and its lines (`outside/state-sources.ts`) follow the page and effect lines.
|
|
104
|
+
*/
|
|
105
|
+
export declare function readBuild(driver: PageDriver, plan: readonly Sequence[], sleep: Sleep, readState?: () => Promise<string[]>): Promise<BuildReadings>;
|
|
106
|
+
export {};
|