npm.io
4.66.1 • Published 1 week agoCLI

@applitools/core

Licence
SEE LICENSE IN LICENSE
Version
4.66.1
Deps
19
Size
851 kB
Vulns
0
Weekly
0

@applitools/core

The core SDK package that orchestrates visual testing via two distinct check strategies: Classic and UFG (Ultrafast Grid).


Check Strategies Overview

Both strategies share the same entry point (src/check.ts) which normalizes settings and then delegates to the appropriate typed implementation based on the type parameter ('classic' or 'ufg').

eyes.check()
    └── src/check.ts           ← normalizes settings, resolves type
         ├── classic/check.ts  ← screenshot-based flow
         └── ufg/check.ts      ← DOM snapshot + cloud rendering flow

Classic Check

Concept: Capture a screenshot locally (in the test runner's browser/device) and send it to Applitools for comparison against the baseline.

Flow
check()
  │
  ├── [driver target?]
  │     │
  │     ├── beforeScreenshot()
  │     │     ├── setScrollingElement
  │     │     └── waitForLazyLoad (if enabled)
  │     │
  │     ├── [web or native?]
  │     │     ├── takeWebScreenshot()
  │     │     │     ├── takeScreenshot()   ← scrolls, stitches, captures PNG
  │     │     │     └── takeDomCapture()   ← captures DOM (if sendDom=true)
  │     │     │
  │     │     └── takeNMLScreenshot()
  │     │           └── nmlClient.takeScreenshots()  ← native mobile (NML)
  │     │
  │     └── [matchTimeout set?]
  │           ├── YES → checkEnvironment() synchronously, retry until passes
  │           └── NO  → checkInTheBackground() ← fire-and-forget, non-blocking
  │
  └── [image target?]
        └── checkInTheBackground() ← directly queue pre-captured image
Key files
File Purpose
src/classic/check.ts Orchestrates the classic check flow
src/classic/utils/take-screenshots.ts Takes the screenshot and optional DOM capture
src/classic/utils/take-dom-capture.ts Captures the page DOM for RCA
src/automation/utils/take-screenshot.ts Low-level screenshot + stitching logic
What happens on each check
  1. Settings normalization — applied in src/check.ts (defaults for fully, matchLevel, sendDom, etc.)
  2. Scroll root / lazy load — the driver scrolls and waits for deferred content
  3. Screenshot — a full-page or viewport PNG is captured locally
  4. DOM capture (optional) — if sendDom=true, the serialized DOM is captured for Root Cause Analysis (RCA)
  5. Session open (lazy) — getBaseEyes() opens the Applitools test session on first use
  6. Upload + comparebaseEyes.check() uploads the image+DOM and triggers comparison
matchTimeout behavior

When matchTimeout > 0, the SDK retries the entire screenshot→compare cycle until the result matches expectations or the timeout expires. This blocks the test execution thread and is designed to tolerate transient UI states (e.g., loading spinners).

Background queuing

When matchTimeout = 0 (default), checks are queued as background promises. The test thread is not blocked. eyes.close() awaits all queued jobs before resolving results.


UFG Check (Ultrafast Grid)

Concept: Capture a DOM snapshot locally, upload it to the Applitools rendering farm (UFG - Ultrafast Grid), which renders it in multiple browsers and devices in the cloud and then compares each rendered image against the baseline.

Flow
check()
  │
  ├── getUFGClient()  ← initialize methods for UFG purposes
  │
  ├── [driver target?]
  │     └── takeSnapshots()
  │           ├── setScrollingElement
  │           ├── takeDomSnapshots()    ← serialized DOM + resources
  │           │     └── (per environment: browser widths, layout breakpoints)
  │           └── driver.getUrl() / getTitle() / getUserAgent()
  │
  ├── [for each snapshot × environment]
  │     ├── createRenderTargetFromSnapshot()
  │     │     └── ufgClient.uploadResources()  ← CSS, fonts, images → UFG CDN
  │     │
  │     ├── getBaseEyes()   ← open test session (lazy, per environment)
  │     │
  │     ├── ufgClient.render()
  │     │     └── UFG renders DOM in target browser/device → returns rendered image
  │     │
  │     └── baseEyes.check()  ← compare rendered image against baseline
  │
  └── all environment promises queued in eyes.storage
Key files
File Purpose
src/ufg/check.ts Orchestrates the UFG check flow
src/ufg/take-snapshots.ts Takes DOM snapshots per environment
src/ufg/create-render-target-from-snapshot.ts Uploads resources to UFG CDN, builds render target
src/ufg/utils/take-dom-snapshots.ts Serializes DOM including cross-frame content
What happens on each check
  1. Settings normalization — same as Classic (src/check.ts)
  2. DOM snapshot — the page's full DOM (HTML, CSS, iframes) is serialized and resources are collected
  3. Resource upload — CSS, fonts, images are uploaded to the UFG CDN (cached by URL to avoid re-uploads)
  4. Render request — sent to the UFG rendering farm per target environment (browser + viewport or device)
  5. Session open (lazy, per environment) — getBaseEyes() opens one Applitools session per environment
  6. ComparebaseEyes.check() receives the rendered image reference (renderId) and triggers comparison
Environment targeting

UFG supports rendering in multiple environments per check:

settings.environments = [
  { name: 'chrome', width: 1280, height: 800 },
  { name: 'firefox', width: 1280, height: 800 },
  { chromeEmulationInfo: { deviceName: 'Pixel 4 XL' } },
  { iosDeviceInfo: { deviceName: 'iPhone 12', version: 'latest' } },
]

Each environment produces one independent test session, one render, and one comparison result.

Layout breakpoints

UFG supports layoutBreakpoints — the DOM is snapshotted at each specified viewport width to capture responsive layout differences, and UFG renders each environment at the closest matching breakpoint.


Classic vs UFG: Key Differences

Aspect Classic UFG
What's captured locally Screenshot (PNG) DOM snapshot (HTML/CSS/resources)
Where rendering happens Local driver (real browser) Applitools cloud (UFG rendering farm)
Multi-environment Takes one screenshot, reuses for all envs Renders separately per environment
Native mobile Supported via NML (real devices) Supported via cloud device emulation
DOM capture Optional (for RCA) Always — it's the input
Concurrency usage One slot per environment One slot per environment
matchTimeout Supported (retry loop) Not supported
Scroll/stitch Done locally by the SDK Done by UFG renderer

Environment Terminology

Two distinct environment shapes flow through classic check. Confusing them is the source of a known bug.

Name TypeScript type Shape Where it comes from
Requested environment (also called unique environment) RenderEnvironment {iosDeviceInfo: {deviceName: 'iPhone 12'}} or {name: 'chrome', width: 1280, height: 800} Comes from settings.environments (user input). After uniquifyEnvironments(), duplicates get a synthetic environmentId to map to separate sessions.
Actual / exact environment ExactEnvironment {environmentId, deviceName, os, viewportSize, requested: <RenderEnvironment>} Returned by takeWebScreenshot / takeNMLScreenshot after the device is resolved. The flat shape that core-base and the Applitools server understand. Always has requested pointing back to the original RenderEnvironment.

Key invariant: toEnvironmentKey(exactEnvironment) === toEnvironmentKey(requestedEnvironment) because toEnvironmentKey uses env.requested ?? env. This is how the makeGetBaseEyes cache correctly matches an early session-open (keyed on the requested environment) to the later check (keyed on the exact environment).

Known bug: In the matchTimeout path, base.openEyes is called with the requested environment ({iosDeviceInfo: ...}) instead of the exact environment ({deviceName, os, viewportSize, ...}). core-base/server/requests.ts reads the flat fields (os, deviceName, viewportSize) which are undefined on the requested form, so the session opens on the server with empty environment info. This only affects NML (iOS/Android native) environments since web environments are not transformed and exactEnvironment === requestedEnvironment.


Session Lifecycle (Both Strategies)

Sessions are opened lazily — the Applitools test session is not created during openEyes(), but on the first getBaseEyes() call within a check. This avoids charging concurrency for tests that open but never check.

openEyes()         → creates in-memory Eyes object, no server call
check()            → getBaseEyes() → POST /api/sessions (first time only)
check()            → getBaseEyes() → returns cached session (subsequent calls)
close() / abort()  → closes all open sessions, awaits all queued jobs

Storage Model

Both strategies push check promises into eyes.storage, a Map<environmentKey, {eyes, jobs[]}>:

  • eyes — a controlled promise resolving to the base eyes session
  • jobs — array of check promises for that environment

close() waits for all jobs across all environments before collecting results.