@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
- Settings normalization — applied in
src/check.ts(defaults forfully,matchLevel,sendDom, etc.) - Scroll root / lazy load — the driver scrolls and waits for deferred content
- Screenshot — a full-page or viewport PNG is captured locally
- DOM capture (optional) — if
sendDom=true, the serialized DOM is captured for Root Cause Analysis (RCA) - Session open (lazy) —
getBaseEyes()opens the Applitools test session on first use - Upload + compare —
baseEyes.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
- Settings normalization — same as Classic (
src/check.ts) - DOM snapshot — the page's full DOM (HTML, CSS, iframes) is serialized and resources are collected
- Resource upload — CSS, fonts, images are uploaded to the UFG CDN (cached by URL to avoid re-uploads)
- Render request — sent to the UFG rendering farm per target environment (browser + viewport or device)
- Session open (lazy, per environment) —
getBaseEyes()opens one Applitools session per environment - Compare —
baseEyes.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 sessionjobs— array of check promises for that environment
close() waits for all jobs across all environments before collecting results.