@applitools/eyes-results 0.2.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +26 -0
  3. package/dist/api/api.js +17 -0
  4. package/dist/api/compare-step.js +20 -0
  5. package/dist/api/diff-fetch.js +13 -0
  6. package/dist/api/dom-diff.js +139 -0
  7. package/dist/api/dom-fetch.js +39 -0
  8. package/dist/api/find-regions.js +24 -0
  9. package/dist/api/fs-cache.js +19 -0
  10. package/dist/api/gzip.js +12 -0
  11. package/dist/api/image-fetch.js +31 -0
  12. package/dist/api/polling.js +117 -0
  13. package/dist/api/req-results.js +77 -0
  14. package/dist/api/same-diff-as.js +25 -0
  15. package/dist/api/selector-mapping.js +15 -0
  16. package/dist/api/session-fetch.js +35 -0
  17. package/dist/api/session-history-fetch.js +73 -0
  18. package/dist/api/session-updates.js +37 -0
  19. package/dist/api/tasks.js +48 -0
  20. package/dist/api/url-parsing.js +33 -0
  21. package/dist/common/common.js +2 -0
  22. package/dist/common/diff-rects.js +64 -0
  23. package/dist/common/region-mapping.js +50 -0
  24. package/dist/geometry/geometry.js +72 -0
  25. package/dist/index.js +14 -0
  26. package/dist/lib/api-key-provider.js +26 -0
  27. package/dist/lib/env.js +26 -0
  28. package/dist/lib/phase-tracker.js +223 -0
  29. package/dist/sessions/batch.js +98 -0
  30. package/dist/sessions/diffs.js +210 -0
  31. package/dist/sessions/dom-history.js +107 -0
  32. package/dist/sessions/dom-search.js +1150 -0
  33. package/dist/sessions/dom.js +513 -0
  34. package/dist/sessions/match.js +56 -0
  35. package/dist/sessions/regions.js +280 -0
  36. package/dist/sessions/session.js +115 -0
  37. package/llm-docs/dom-search.md +164 -0
  38. package/package.json +69 -0
  39. package/types/api/api.d.ts +17 -0
  40. package/types/api/compare-step.d.ts +52 -0
  41. package/types/api/diff-fetch.d.ts +21 -0
  42. package/types/api/dom-diff.d.ts +53 -0
  43. package/types/api/dom-fetch.d.ts +29 -0
  44. package/types/api/find-regions.d.ts +26 -0
  45. package/types/api/fs-cache.d.ts +12 -0
  46. package/types/api/gzip.d.ts +5 -0
  47. package/types/api/image-fetch.d.ts +29 -0
  48. package/types/api/polling.d.ts +41 -0
  49. package/types/api/req-results.d.ts +37 -0
  50. package/types/api/same-diff-as.d.ts +38 -0
  51. package/types/api/selector-mapping.d.ts +28 -0
  52. package/types/api/session-fetch.d.ts +34 -0
  53. package/types/api/session-history-fetch.d.ts +56 -0
  54. package/types/api/session-updates.d.ts +26 -0
  55. package/types/api/tasks.d.ts +33 -0
  56. package/types/api/url-parsing.d.ts +20 -0
  57. package/types/common/common.d.ts +2 -0
  58. package/types/common/diff-rects.d.ts +39 -0
  59. package/types/common/region-mapping.d.ts +26 -0
  60. package/types/geometry/geometry.d.ts +24 -0
  61. package/types/index.d.ts +14 -0
  62. package/types/lib/api-key-provider.d.ts +31 -0
  63. package/types/lib/env.d.ts +14 -0
  64. package/types/lib/phase-tracker.d.ts +146 -0
  65. package/types/sessions/batch.d.ts +67 -0
  66. package/types/sessions/diffs.d.ts +26 -0
  67. package/types/sessions/dom-history.d.ts +119 -0
  68. package/types/sessions/dom-search.d.ts +113 -0
  69. package/types/sessions/dom.d.ts +263 -0
  70. package/types/sessions/match.d.ts +72 -0
  71. package/types/sessions/regions.d.ts +171 -0
  72. package/types/sessions/session.d.ts +97 -0
@@ -0,0 +1,52 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ImageRecord } from './selector-mapping.js';
3
+ import type { ImageMatchSettings } from '../sessions/match.js';
4
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
5
+ export interface CompareStepOptions {
6
+ baseUrl: string;
7
+ accountId: string;
8
+ keys: ApiKeyProvider;
9
+ }
10
+ export interface CompareStepBody {
11
+ matchSettings: ImageMatchSettings;
12
+ image1: ImageRecord;
13
+ image2: ImageRecord;
14
+ sessionId: string;
15
+ batchId: string;
16
+ runningSessionId: string;
17
+ stepIndex: number;
18
+ }
19
+ export interface CompareDiffRect {
20
+ left: number;
21
+ top: number;
22
+ width: number;
23
+ height: number;
24
+ diffDescriptions?: unknown[];
25
+ }
26
+ export interface CompareStepResult {
27
+ isMatch: boolean;
28
+ image1Size: {
29
+ width: number;
30
+ height: number;
31
+ };
32
+ image1: CompareDiffRect[];
33
+ image2Size: {
34
+ width: number;
35
+ height: number;
36
+ };
37
+ image2: CompareDiffRect[];
38
+ [key: string]: unknown;
39
+ }
40
+ /**
41
+ * Recomputes a step's visual diff against arbitrary match settings/regions — the same
42
+ * comparison the dashboard's own region editor uses for a live preview, confirmed live to
43
+ * accept both a `{domSelector}` region (resolved against both images' own DOM, since
44
+ * `useDom`/`hasDom` are true) and a plain `{left,top,width,height}` region. `image1`/`image2`
45
+ * in the response are a genuine two-way diff — rects in each image's own coordinate space,
46
+ * not a single shared list — mirroring the request's own `image1`/`image2` sides.
47
+ *
48
+ * Confirmed live to be sometimes synchronous (200) and sometimes an async job (202 +
49
+ * `Location`) depending on whether the exact payload was already computed — `getJsonResult`
50
+ * handles both transparently.
51
+ */
52
+ export declare function compareStep(body: CompareStepBody, options: CompareStepOptions, fetchImpl?: FetchLike): Promise<CompareStepResult>;
@@ -0,0 +1,21 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
3
+ export interface FetchStepDiffOptions {
4
+ baseUrl: string;
5
+ batchId: string;
6
+ sessionId: string;
7
+ stepIndex: number;
8
+ accountId: string;
9
+ keys: ApiKeyProvider;
10
+ }
11
+ export interface StepDiffResult {
12
+ image1: any[];
13
+ image2: any[];
14
+ }
15
+ /**
16
+ * The cheap, already-computed two-way diff for one step, straight off the dashboard's own
17
+ * `GET .../diff` endpoint — used whenever nothing about the step's effective match settings
18
+ * differs from what the test actually ran with (see `eyes-inspect`'s `fetchDiffRects`, this
19
+ * function's only caller, for when the more expensive `compareStep` path is needed instead).
20
+ */
21
+ export declare function fetchStepDiff(options: FetchStepDiffOptions, fetchImpl?: FetchLike): Promise<StepDiffResult>;
@@ -0,0 +1,53 @@
1
+ import type { Fetch } from '@applitools/req';
2
+ import { type FsCacheDeps } from './fs-cache.js';
3
+ import { type ReqResultsLogger } from './req-results.js';
4
+ import type { DomAlignment } from '../sessions/dom.js';
5
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
6
+ export declare const DOM_DIFF_PATH = "/api/images/dom/diff";
7
+ export interface FetchDomDiffDeps extends FsCacheDeps {
8
+ /** Test seam — swaps the configured req's network layer, never its behavior. */
9
+ fetch?: Fetch;
10
+ logger?: ReqResultsLogger;
11
+ }
12
+ export declare const defaultFetchDomDiffDeps: FetchDomDiffDeps;
13
+ export type DomDiffResult = {
14
+ status: 'ready';
15
+ alignment: DomAlignment;
16
+ } | {
17
+ status: 'running';
18
+ taskId: string;
19
+ };
20
+ /**
21
+ * Fetches (or reads from cache) the alignment of two DOM snapshots, computed server-side. The
22
+ * request carries the two domIds alone — the server resolves them against its own snapshot
23
+ * storage, so no DOM travels either way — and a ready result is the compact `{matches, diff}`
24
+ * alignment: `matches` is what lets a caller extract one node's own diff afterwards
25
+ * (`sessions/dom`'s `extractNodeDiff`) without paying for a second alignment of the same pair.
26
+ * Cropping to an area (`filterDiffToArea`), excluding non-visual entries
27
+ * (`excludeNonVisualNodeDiffs`) and serializing (`serializeDiff`) all stay with the caller.
28
+ *
29
+ * No client-side polling: a 202 submit is probed once via `getTaskStatus`; if the task is still
30
+ * running, the taskId is returned and the caller decides when to check again
31
+ * (`checkDomDiffTask`). A direct 200 with the alignment body also parses — forward tolerance for
32
+ * a server answering an already-computed pair synchronously; the current backend always answers
33
+ * 202 (its async-task framework has no synchronous arm).
34
+ *
35
+ * Content-addressed cache, keyed on the two domIds alone — both are already immutable, unlike an
36
+ * area or a step's match settings, so one ready alignment is reused across every caller of that
37
+ * pair. A `running` outcome is never cached.
38
+ *
39
+ * Every alignment is shape-checked at this boundary before it is returned or cached — see
40
+ * `assertDomAlignment`.
41
+ */
42
+ export declare function fetchDomDiff(baseUrl: string, baselineDomId: string, checkpointDomId: string, keys: ApiKeyProvider, deps?: FetchDomDiffDeps): Promise<DomDiffResult>;
43
+ /**
44
+ * The caller's re-check for a `running` `fetchDomDiff` — one `getTaskStatus` probe, with a ready
45
+ * alignment landing in the same domId-pair cache a direct fetch would have written, so later
46
+ * calls for the pair stay cache hits regardless of which path produced the alignment.
47
+ *
48
+ * Cache-first, like `fetchDomDiff`: the pair may already be ready on disk (an earlier probe, or
49
+ * any other caller of the same pair, landed it), and a cached pair must win even when the taskId
50
+ * in hand has since expired — so a re-check with a stale taskId never fails on a result that's
51
+ * already here.
52
+ */
53
+ export declare function checkDomDiffTask(baseUrl: string, taskId: string, baselineDomId: string, checkpointDomId: string, keys: ApiKeyProvider, deps?: FetchDomDiffDeps): Promise<DomDiffResult>;
@@ -0,0 +1,29 @@
1
+ import type { Fetch } from '@applitools/req';
2
+ import { type FsCacheDeps } from './fs-cache.js';
3
+ import { type ReqResultsLogger } from './req-results.js';
4
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
5
+ import type { PrunedDom } from '../sessions/dom.js';
6
+ export declare const PRUNED_DOM_PATH: (domId: string) => string;
7
+ export interface FetchPrunedDomOptions {
8
+ baseUrl: string;
9
+ domId: string;
10
+ keys: ApiKeyProvider;
11
+ }
12
+ export interface FetchPrunedDomDeps extends FsCacheDeps {
13
+ /** Test seam — swaps the configured req's network layer, never its behavior. */
14
+ fetch?: Fetch;
15
+ logger?: ReqResultsLogger;
16
+ }
17
+ export declare const defaultFetchPrunedDomDeps: FetchPrunedDomDeps;
18
+ /**
19
+ * Fetches (or reads from cache) an Applitools DOM snapshot already pruned server-side: URLs
20
+ * normalized, node ids assigned, metadata fields and whitespace-only text nodes gone. Same
21
+ * `eyes-<domId>` cache entry a client-side pruner writes, so entries stay valid either way.
22
+ * Takes no `area` — cropping is a separate, uncached step (`sessions/dom`'s `getDomArea`).
23
+ *
24
+ * The 200 body may be a gzip payload (a gzip *body*, not `Content-Encoding` — which the
25
+ * transport already undoes transparently), so it's gunzipped when it is one and parsed as-is
26
+ * when it isn't. Any non-200 throws via the configured req's expected-status hook, carrying the
27
+ * request name, status, and response body.
28
+ */
29
+ export declare function fetchPrunedDom(options: FetchPrunedDomOptions, deps?: FetchPrunedDomDeps): Promise<PrunedDom>;
@@ -0,0 +1,26 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ImageRecord } from './selector-mapping.js';
3
+ import type { Region } from '../sessions/regions.js';
4
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
5
+ export interface FindRegionsOptions {
6
+ baseUrl: string;
7
+ accountId: string;
8
+ keys: ApiKeyProvider;
9
+ }
10
+ /**
11
+ * Searches every one of `targets` for each of `regions` — regions defined against
12
+ * `sourceImage`'s own DOM or, since `domSelector` is optional, its raw rectangle — and returns
13
+ * a map keyed by each target's `ImageRecord.id`. A target with no match at all is still present
14
+ * in the map, with an empty array (its key is never omitted). When several regions are searched
15
+ * at once, a target's array holds only the ones actually found there — there is no positional
16
+ * correspondence to `regions`, so a caller distinguishes which one matched by comparing
17
+ * `domSelector` back against the region it sent (both confirmed live: a single-region miss
18
+ * comes back as `[]`; a two-region call with one hit and one miss comes back with exactly one
19
+ * entry, the hit).
20
+ *
21
+ * Confirmed live to be an async job: the initial POST returns 202 with a `Location` header
22
+ * to a status URL; polling that returns 201, but the result itself lives at a *second*,
23
+ * separate URL given by that 201 response's own `Location` header — `getJsonResult`/
24
+ * `pollUntilResult` (`./polling.js`) handle this double-redirect.
25
+ */
26
+ export declare function findRegionsInImages(sourceImage: ImageRecord, regions: Region[], targets: ImageRecord[], options: FindRegionsOptions, fetchImpl?: FetchLike, sleepFn?: (ms: number) => Promise<void>): Promise<Record<string, Region[]>>;
@@ -0,0 +1,12 @@
1
+ export interface FsCacheDeps {
2
+ readFile: (path: string) => string | null;
3
+ writeFile: (path: string, contents: string) => void;
4
+ cacheDir: string;
5
+ }
6
+ export declare const defaultFsCacheDeps: FsCacheDeps;
7
+ /**
8
+ * Builds a content-addressed cache file path, sanitizing `key` the same way the
9
+ * repo's existing cache-file naming already does (only `~`/`,` need escaping —
10
+ * these characters can appear in Applitools image/dom ids and are not filesystem-safe).
11
+ */
12
+ export declare function cacheFilePath(deps: FsCacheDeps, prefix: string, key: string, ext?: string): string;
@@ -0,0 +1,5 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import { Buffer } from 'buffer';
4
+ /** Promisified zlib.gunzip. Rejects if the input isn't valid gzip data — callers decide the fallback. */
5
+ export declare function gunzip(buffer: Buffer): Promise<Buffer>;
@@ -0,0 +1,29 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
3
+ import { Buffer } from 'buffer';
4
+ import { type FetchLike } from './polling.js';
5
+ import type { Rect } from '../geometry/geometry.js';
6
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
7
+ export interface FetchCheckpointImageOptions {
8
+ baseUrl: string;
9
+ batchId: string;
10
+ sessionId: string;
11
+ stepIndex: number;
12
+ accountId: string;
13
+ keys: ApiKeyProvider;
14
+ area?: Rect;
15
+ }
16
+ /**
17
+ * A step's checkpoint screenshot, as a raw PNG buffer — a binary response, so this calls `fetch`
18
+ * directly rather than through `postJson`/`getJson` (which assume JSON), the one documented
19
+ * exception among `api/`'s REST wrappers.
20
+ */
21
+ export declare function fetchCheckpointImage(options: FetchCheckpointImageOptions, fetchImpl?: FetchLike): Promise<Buffer>;
22
+ export interface FetchImageByIdOptions {
23
+ baseUrl: string;
24
+ imageId: string;
25
+ keys: ApiKeyProvider;
26
+ area?: Rect;
27
+ }
28
+ /** A baseline or checkpoint screenshot by its own image id, as a raw PNG buffer — same binary-response exception as `fetchCheckpointImage`. */
29
+ export declare function fetchImageById(options: FetchImageByIdOptions, fetchImpl?: FetchLike): Promise<Buffer>;
@@ -0,0 +1,41 @@
1
+ export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
2
+ /**
3
+ * The older api modules' transport seam — a fresh `makeReqResults` instance per invocation, the
4
+ * DOM-diff flow's conventions (standard retry policy, explicit request/connection timeouts). The
5
+ * full retry budget every request gets is guaranteed by `makeReqResults` itself
6
+ * (`isolateRetryPerRequest`), not by this per-invocation build; building here keeps one shape
7
+ * across the package and scopes the resolved server URL to the request it came from.
8
+ * These modules bake the apiKey into their URLs themselves, so it is read back out of the URL
9
+ * here rather than threaded separately; no `expected` statuses are passed, so the instance's
10
+ * unexpected-status hook stays inert and the modules' own status checks remain in charge. Only
11
+ * how a single request executes lives here: the task flow (202/Location → status → result),
12
+ * JSON parsing, and error wording all stay above it.
13
+ */
14
+ export declare const reqTransport: FetchLike;
15
+ /** POST a JSON body. */
16
+ export declare function postJson(url: string, body: unknown, fetchImpl?: FetchLike): Promise<Response>;
17
+ /** GET and parse a JSON response, throwing a descriptive error on a non-ok status. */
18
+ export declare function getJson(url: string, fetchImpl?: FetchLike): Promise<any>;
19
+ /**
20
+ * Polls a job-status URL until it reports done, then fetches and returns the job's result.
21
+ * Confirmed live against the real API — for every async endpoint checked so far (`images/find`,
22
+ * `sessions/batches/{batchId}/updates`), not just the ones that produce a real payload — a
23
+ * `201` isn't itself the result: it carries its *own* `Location` header (distinct from the
24
+ * original 202's) pointing at a separate result URL, which resolves with either a real JSON
25
+ * body (`images/find`) or an empty one (`updates`, `save`, etc. — a 204). Both are handled
26
+ * uniformly here; a caller with nothing to do with the result just ignores the return value.
27
+ * 200 = still running (wait and retry), anything else on the status poll = error.
28
+ */
29
+ export declare function pollUntilResult(statusUrl: string, apiKey: string, fetchImpl?: FetchLike, sleepFn?: (ms: number) => Promise<void>): Promise<any>;
30
+ /**
31
+ * Handles the Eyes dashboard's async-job response convention end to end: a `202` with a
32
+ * `Location` header means the job is running and is polled via `pollUntilResult` through to
33
+ * its actual result; a plain ok response is parsed as JSON directly (tolerating an empty
34
+ * body). One function for every endpoint, whether or not its result is ever read — a caller
35
+ * that only cares that the call succeeded (e.g. `accept`/`reject`/`save`) just discards the
36
+ * return value instead of needing a separate "no result expected" variant.
37
+ *
38
+ * `requestUrl` backs up `response.url` as the base for a relative Location: `response.url` is
39
+ * '' under req's HTTP/2 rewrap, so callers pass the URL they requested.
40
+ */
41
+ export declare function getJsonResult(response: Response, apiKey: string, fetchImpl?: FetchLike, sleepFn?: (ms: number) => Promise<void>, requestUrl?: string): Promise<any>;
@@ -0,0 +1,37 @@
1
+ import { type Fetch, type Options, type Req } from '@applitools/req';
2
+ export interface ReqResultsLogger {
3
+ log(...args: unknown[]): void;
4
+ error(...args: unknown[]): void;
5
+ }
6
+ export interface ReqResultsOptions extends Options {
7
+ /** Response statuses the caller treats as business results; any other status throws. */
8
+ expected?: number | number[];
9
+ /** Names the request in thrown errors. */
10
+ name?: string;
11
+ logger?: ReqResultsLogger;
12
+ }
13
+ export interface MakeReqResultsOptions {
14
+ serverUrl: string;
15
+ /** Baked into every request as the `apiKey` query parameter — the convention all eyes-results endpoints use. */
16
+ apiKey: string;
17
+ /** Test seam — swaps the instance's network layer, never its behavior. */
18
+ fetch?: Fetch;
19
+ logger?: ReqResultsLogger;
20
+ }
21
+ /**
22
+ * Builds a configured `req` — core-base's `makeReqEyes` concept adapted to this package: the
23
+ * instance owns the server URL, the `apiKey` query parameter, the transient-retry policy, and the
24
+ * `expected`-status convention above, so api functions take the configured req plus business
25
+ * parameters and never thread baseUrl/apiKey/transport themselves. Flat options on purpose: an api
26
+ * function's own deps spread straight in — `makeReqResults({serverUrl, apiKey, ...deps})`.
27
+ *
28
+ * **Build one per call, not one per module.** Every call site here does (`api/dom-diff.ts`,
29
+ * `api/dom-fetch.ts`, `api/tasks.ts`, `api/polling.ts`'s `reqTransport`), because req's retry
30
+ * counter lives on the retry object the instance carries — see `isolateRetryPerRequest` above for
31
+ * what that costs a long-lived instance. That hook is what actually guarantees the budget is
32
+ * per-request, so hoisting an instance is safe rather than silently degrading; per-call
33
+ * construction stays the convention because it also keeps the apiKey and server URL scoped to the
34
+ * flow that resolved them. `test/api/req-results.test.ts`'s "retry isolation" suite makes several
35
+ * calls through a single instance and pins both properties.
36
+ */
37
+ export declare function makeReqResults({ serverUrl, apiKey, fetch, logger }: MakeReqResultsOptions): Req<ReqResultsOptions>;
@@ -0,0 +1,38 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ImageMatchSettings } from '../sessions/match.js';
3
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
4
+ export interface SameDiffAsOptions {
5
+ baseUrl: string;
6
+ accountId: string;
7
+ keys: ApiKeyProvider;
8
+ }
9
+ export interface SameDiffAsStep {
10
+ batchId: string;
11
+ sessionId: string;
12
+ stepIndex: number;
13
+ image1: string;
14
+ image2: string;
15
+ matchSettings: ImageMatchSettings;
16
+ }
17
+ export interface SameDiffAsBody {
18
+ reference: SameDiffAsStep;
19
+ steps: SameDiffAsStep[];
20
+ }
21
+ /**
22
+ * Given a reference step and a candidate list of other steps, returns which of `steps` actually
23
+ * have the same diff as `reference` — the real target steps an accept/reject decision should
24
+ * propagate to, as opposed to every step a scenario-name match happened to include. `image1`/
25
+ * `image2` here are plain baseline/checkpoint image id strings, not full `ImageRecord` objects
26
+ * (unlike `compareStep`'s own request shape) — each entry (including `reference`) carries its
27
+ * own step's `matchSettings`, never another step's.
28
+ *
29
+ * Confirmed live: an async job (202 + Location), same as every other endpoint in this file.
30
+ * **The result is `number[]` — zero-based indices into the request's own `steps` array, e.g.
31
+ * `[0, 1]` meaning `steps[0]` and `steps[1]` both matched — never step objects, and never
32
+ * `{sessionId, stepIndex}` identifiers.** A caller must resolve each index back against its own
33
+ * original `steps`/candidate array; the response carries nothing else to identify a match by.
34
+ * When nothing in `steps` matches `reference`, the job's own result is `204 No Content` — an
35
+ * empty body, which `getJsonResult` parses as `undefined` — normalized here to `[]` so callers
36
+ * can always assume an array.
37
+ */
38
+ export declare function findSameDiffSteps(body: SameDiffAsBody, options: SameDiffAsOptions, fetchImpl?: FetchLike): Promise<number[]>;
@@ -0,0 +1,28 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
3
+ export interface ImageRecord {
4
+ id: string;
5
+ domId: string;
6
+ size: {
7
+ width: number;
8
+ height: number;
9
+ };
10
+ hasDom: boolean;
11
+ location: {
12
+ x: number;
13
+ y: number;
14
+ };
15
+ rectangle: string;
16
+ }
17
+ export interface GetCompatibleSelectorsOptions {
18
+ baseUrl: string;
19
+ accountId: string;
20
+ keys: ApiKeyProvider;
21
+ }
22
+ /**
23
+ * Maps each of `selectors` from `image1`'s DOM to its structurally-equivalent selector on
24
+ * `image2`'s DOM — e.g. translating a region's domSelector from a baseline to a checkpoint,
25
+ * or the reverse. Returns one entry per input selector, in the same order; an entry is
26
+ * `null` when no compatible element exists on `image2`.
27
+ */
28
+ export declare function getCompatibleSelectors(options: GetCompatibleSelectorsOptions, image1: ImageRecord, image2: ImageRecord, selectors: string[], fetchImpl?: FetchLike): Promise<(string | null)[]>;
@@ -0,0 +1,34 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import { type SimplifiedSession, type SessionStep } from '../sessions/session.js';
3
+ import { type BatchInfo } from '../sessions/batch.js';
4
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
5
+ export interface SessionUrlOptions {
6
+ baseUrl: string;
7
+ batchId: string;
8
+ accountId: string;
9
+ keys: ApiKeyProvider;
10
+ }
11
+ /** Fetches the raw Eyes session API response — no shaping. */
12
+ export declare function fetchRawSession(options: SessionUrlOptions & {
13
+ sessionId: string;
14
+ }, fetchImpl?: FetchLike): Promise<any>;
15
+ /** Fetches a session and shapes it via `sessions/session`'s `simplifySession`. */
16
+ export declare function fetchSession(options: SessionUrlOptions & {
17
+ sessionId: string;
18
+ }, fetchImpl?: FetchLike): Promise<SimplifiedSession & {
19
+ steps: SessionStep[];
20
+ }>;
21
+ /**
22
+ * Fetches the raw list of sessions in a batch — no shaping.
23
+ * Normalizes the response shape: the Eyes API returns either a bare array or `{ sessions: [...] }`.
24
+ */
25
+ export declare function fetchRawBatchSessions(options: SessionUrlOptions, fetchImpl?: FetchLike): Promise<any[]>;
26
+ /**
27
+ * Fetches the raw batch entity itself (name, timing, and — the reason this exists —
28
+ * its server-maintained aggregate counts, shaped by `sessions/batch`'s `toBatchStats` —
29
+ * see `BatchInfo` there) — a different endpoint from `fetchRawBatchSessions`, which lists
30
+ * the batch's individual sessions. This aggregate is eventually consistent: it can lag
31
+ * behind a resolve/save call by several seconds, unlike a session list (or anything derived
32
+ * from one), which is always current.
33
+ */
34
+ export declare function fetchBatchInfo(options: SessionUrlOptions, fetchImpl?: FetchLike): Promise<BatchInfo>;
@@ -0,0 +1,56 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import { type FsCacheDeps } from './fs-cache.js';
3
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
4
+ export interface SessionHistoryEnv {
5
+ hostingApp: string | null;
6
+ os: string | null;
7
+ displaySize: {
8
+ width: number;
9
+ height: number;
10
+ } | null;
11
+ }
12
+ /**
13
+ * Builds the `where=` filter clauses for a session-history query. Each clause's payload is a
14
+ * plain UTF-8 string, base64-encoded — not `encodeURIComponent(JSON.stringify(...))` — and
15
+ * `IsViewport` in particular is a custom `{width:N,height:N}` literal (unquoted keys, not
16
+ * valid JSON), confirmed by decoding a real sample URL. Returns only the clauses for fields
17
+ * actually present in `env`, `&&`-joined; a caller further `encodeURIComponent`s the whole
18
+ * result when building the request URL — this returns the raw (unencoded) filter text.
19
+ */
20
+ export declare function buildSessionHistoryEnvFilter(env: SessionHistoryEnv): string;
21
+ export interface SessionHistoryOptions {
22
+ baseUrl: string;
23
+ scenarioId: string;
24
+ accountId: string;
25
+ keys: ApiKeyProvider;
26
+ env: SessionHistoryEnv;
27
+ ranAtOrBefore: string;
28
+ count: number;
29
+ }
30
+ export interface FetchSessionHistoryDeps extends FsCacheDeps {
31
+ fetchImpl: FetchLike;
32
+ sleepFn?: (ms: number) => Promise<void>;
33
+ }
34
+ export declare const defaultFetchSessionHistoryDeps: FetchSessionHistoryDeps;
35
+ /**
36
+ * Fetches (or reads from cache) a page of a scenario's historic sessions matching `env`, at or
37
+ * before `ranAtOrBefore`. Confirmed live as an async-job endpoint (a `202` with a `Location`
38
+ * status-poll header, same convention as `images/get-compatible-selectors` in
39
+ * `selector-mapping.ts` — there a POST initiates it, here a plain GET does), so this goes
40
+ * through `getJsonResult`, not `getJson` (which assumes a synchronous body).
41
+ *
42
+ * Named for what it returns — a session history — not for `scenarioId`, which is only one of
43
+ * several inputs (alongside `env`) narrowing which sessions come back; the endpoint itself is
44
+ * scoped by scenario, but the result is a plain list of full raw sessions, not scenario-level
45
+ * aggregate data.
46
+ *
47
+ * Cached indefinitely, keyed by a hash of every input that affects the result — unlike a
48
+ * session/batch fetch (deliberately never cached elsewhere in `api/`, since those can change),
49
+ * a `ranAtOrBefore`-pinned query against the past is immutable once made.
50
+ *
51
+ * Confirmed live: the response is `{ "sessions": [...] }`, each entry a full raw session
52
+ * object (the same shape `fetchRawSession` returns) — but a bare-array response is still
53
+ * normalized defensively, mirroring `fetchRawBatchSessions`, in case an edge case omits the
54
+ * wrapper.
55
+ */
56
+ export declare function fetchSessionHistory(options: SessionHistoryOptions, deps?: FetchSessionHistoryDeps): Promise<any[]>;
@@ -0,0 +1,26 @@
1
+ import { type FetchLike } from './polling.js';
2
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
3
+ export interface BatchScopeOptions {
4
+ baseUrl: string;
5
+ batchId: string;
6
+ accountId: string;
7
+ keys: ApiKeyProvider;
8
+ }
9
+ /**
10
+ * POSTs a batch of per-session step updates (accept/reject decisions, region changes) in one
11
+ * call — confirmed live to accept multiple sessions at once, one entry per session id.
12
+ */
13
+ export declare function postStepUpdates(options: BatchScopeOptions, stepUpdatesBySession: Map<string, any[]>, fetchImpl?: FetchLike): Promise<void>;
14
+ /** Commits every pending accept/reject/region change in the batch to the baseline. The endpoint returns nothing useful to the caller — a resolved promise signals success. */
15
+ export declare function saveBaselines(options: BatchScopeOptions, fetchImpl?: FetchLike): Promise<void>;
16
+ export interface RestoreBaselineOptions {
17
+ baseUrl: string;
18
+ accountId: string;
19
+ baselineId: string;
20
+ baselineRev: string;
21
+ keys: ApiKeyProvider;
22
+ }
23
+ /** Reverts a session's shared baseline object to the revision it originally ran against. */
24
+ export declare function restoreBaseline(options: RestoreBaselineOptions, fetchImpl?: FetchLike): Promise<void>;
25
+ /** Clears every given session's pending (unsaved) accept/reject/region resolution back to unresolved — a no-op, not an error, for a session with nothing pending. */
26
+ export declare function resetBatchSessions(options: BatchScopeOptions, sessionIds: string[], fetchImpl?: FetchLike): Promise<void>;
@@ -0,0 +1,33 @@
1
+ import { type MakeReqResultsOptions } from './req-results.js';
2
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
3
+ export declare const TASK_STATUS_PATH: (taskId: string) => string;
4
+ export declare const TASK_RESULT_PATH: (taskId: string) => string;
5
+ export type TaskDeps = Pick<MakeReqResultsOptions, 'fetch' | 'logger'>;
6
+ /** Extracts the taskId out of a submit response's `Location` (`/api/tasks/{taskId}/status`). */
7
+ export declare function taskIdFromLocation(location: string): string;
8
+ export type TaskStatus<T> = {
9
+ status: 'running';
10
+ taskId: string;
11
+ } | {
12
+ status: 'ready';
13
+ result: T;
14
+ };
15
+ /**
16
+ * One probe of a task's status — no client-side polling loop: `running` hands the taskId back so
17
+ * the caller decides when (and whether) to check again; `201` means done, and the result the
18
+ * response's `Location` points at is fetched and returned in the same call, so a ready task never
19
+ * costs the caller a second round trip. `404` (unknown or expired task) is terminal and throws
20
+ * with the contract's own wording. A *rejected* task also finishes as `201` here — its rejection
21
+ * surfaces as `503` on the result fetch (TasksController puts it there, not on status); transient
22
+ * transport failures are the configured req's standard retry policy, not handled here.
23
+ */
24
+ export declare function getTaskStatus<T = any>(baseUrl: string, taskId: string, keys: ApiKeyProvider, deps?: TaskDeps): Promise<TaskStatus<T>>;
25
+ /**
26
+ * Fetches a finished task's result and parses it as JSON. The response is gzipped on the wire
27
+ * when the request advertises `Accept-Encoding` — the transport negotiates and decompresses that
28
+ * transparently, so no explicit gunzip step exists here (unlike endpoints that return a gzip
29
+ * *body*, e.g. `fetchPrunedDom`). `503` is the result endpoint's "task rejected" — terminal by
30
+ * the task contract (and excluded from the retry policy for exactly that reason), thrown with
31
+ * the contract's own wording.
32
+ */
33
+ export declare function fetchTaskResult(baseUrl: string, resultUrl: string, keys: ApiKeyProvider, deps?: TaskDeps): Promise<any>;
@@ -0,0 +1,20 @@
1
+ export interface ParsedApplitoolsUrl {
2
+ baseUrl: string;
3
+ batchId: string;
4
+ sessionId: string | null;
5
+ accountId: string;
6
+ }
7
+ /**
8
+ * Parses an Applitools dashboard URL (session or batch) into its components.
9
+ * Does not require a sessionId to be present — use parseSessionUrl/parseBatchUrl
10
+ * for callers that need to enforce one shape or the other.
11
+ */
12
+ export declare function parseApplitoolsUrl(url: string): ParsedApplitoolsUrl;
13
+ /** Throws if the URL doesn't include a sessionId segment (i.e. it's a batch-only URL). */
14
+ export declare function parseSessionUrl(url: string): ParsedApplitoolsUrl & {
15
+ sessionId: string;
16
+ };
17
+ /** Batch-scoped: parses the batchId/accountId and always forces sessionId to null, ignoring any session segment. */
18
+ export declare function parseBatchUrl(url: string): ParsedApplitoolsUrl & {
19
+ sessionId: null;
20
+ };
@@ -0,0 +1,2 @@
1
+ export * from './region-mapping.js';
2
+ export * from './diff-rects.js';
@@ -0,0 +1,39 @@
1
+ import { defaultFsCacheDeps, compareStep, fetchStepDiff } from '../api/api.js';
2
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
3
+ import { type Rect } from '../geometry/geometry.js';
4
+ export interface FetchRawDiffsDeps {
5
+ fetchStepDiff: typeof fetchStepDiff;
6
+ compareStep: typeof compareStep;
7
+ readFile: typeof defaultFsCacheDeps.readFile;
8
+ writeFile: typeof defaultFsCacheDeps.writeFile;
9
+ }
10
+ export declare const defaultFetchRawDiffsDeps: FetchRawDiffsDeps;
11
+ /**
12
+ * The two-way visual diff for one step — `image1` in the baseline image's own coordinate
13
+ * space, `image2` in the checkpoint's, exactly mirroring both the plain `GET .../diff`
14
+ * endpoint's and `compareStep`'s own response shape. When the step's resolution state is
15
+ * unchanged from the original test run (`appOutputResolution[stepIndex].isChanged` is not
16
+ * `true`), the cheap `GET .../diff` endpoint is used as-is — nothing about the step's
17
+ * effective match settings differs from what the test already ran with. Otherwise, the step's
18
+ * current effective regions are folded into a fresh `compareStep` request so the returned diff
19
+ * reflects any region added since the test ran — that (more expensive) result is cached on
20
+ * disk, keyed by a hash of the full request body, so repeated calls against the same resolved
21
+ * state don't repeatedly hit the endpoint.
22
+ *
23
+ * Takes an already-fetched `rawSession` rather than fetching its own — `batchId`/`sessionId`
24
+ * are read straight off it (`rawSession.batchId`/`rawSession.id`), so a caller that already has
25
+ * the raw session in hand (e.g. for its own domId lookups) never pays for a second fetch just
26
+ * to hand this function a `url` it would otherwise re-resolve into the same session itself.
27
+ *
28
+ * `matchSettings` comes straight from `getEffectiveMatchSettings` — the raw session's own
29
+ * scalars/regions, shaped directly into the request format, with no baseline/checkpoint
30
+ * pairing and no healing: that shaping only exists for diagnostic region output (which needs
31
+ * both sides shown, healing in whichever one the raw session doesn't encode), never for a
32
+ * compare request. Re-deriving "accepted ⇒ checkpoint, else baseline" here would duplicate
33
+ * logic the raw session's own `annotations` field already resolves — and healing in the other
34
+ * side would cost an extra network round trip per region for nothing a compare request needs.
35
+ */
36
+ export declare function fetchRawDiffs(rawSession: any, stepIndex: number, baseUrl: string, accountId: string, keys: ApiKeyProvider, deps?: FetchRawDiffsDeps): Promise<{
37
+ image1: Rect[];
38
+ image2: Rect[];
39
+ }>;
@@ -0,0 +1,26 @@
1
+ import { getCompatibleSelectors } from '../api/api.js';
2
+ import { type RegionPair } from '../sessions/regions.js';
3
+ import type { ApiKeyProvider } from '../lib/api-key-provider.js';
4
+ export interface MapAnchoredRegionsDeps {
5
+ baseUrl: string;
6
+ accountId: string;
7
+ keys: ApiKeyProvider;
8
+ fetchDomForSide: (rawSession: any, stepIndex: number, side: 'baseline' | 'checkpoint', baseUrl: string) => Promise<any>;
9
+ getCompatibleSelectors: typeof getCompatibleSelectors;
10
+ }
11
+ /**
12
+ * Maps every `anchored` pair's `domSelector` to the other side via one batched
13
+ * get-compatible-selectors call, resolving each mapped selector's accurate rect locally
14
+ * against that side's DOM (the API only returns the mapped selector, never a rect). Falls
15
+ * back to treating every selector as unmapped if the batch call itself fails for some reason
16
+ * other than a bad input selector (`sessions`'s own `checkRegions` already filters those out).
17
+ * Mutates each pair's missing side in place. Returns whether anything was attempted (i.e.
18
+ * `anchored` was non-empty and both sides have an image to map between).
19
+ *
20
+ * Lives here, not in `atoms/sessions` (where the rest of the region-healing pipeline this composes
21
+ * into — `checkRegions`/`clearStaleSelectors`/`healNonAnchoredRegionsBySourceRect` — lives):
22
+ * this is the one step of that pipeline that actually calls the network (`getCompatibleSelectors`),
23
+ * and `atoms` must never depend on `api`/`common` (so it can't reference `ApiKeyProvider` or make
24
+ * network calls) — the orchestration belongs in `common`, which depends on both instead.
25
+ */
26
+ export declare function mapAnchoredRegionsBySelector(rawSession: any, stepIndex: number, anchored: RegionPair[], deps: MapAnchoredRegionsDeps): Promise<boolean>;