@applitools/core-base
Advanced tools
+7
-0
| # Changelog | ||
| ## [1.36.2](https://github.com/Applitools-Dev/sdk/compare/js/core-base@1.36.1...js/core-base@1.36.2) (2026-07-21) | ||
| ### Bug Fixes | ||
| * guard logEventBatch against a logger-less event ([#4007](https://github.com/Applitools-Dev/sdk/issues/4007)) ([bb8c9b2](https://github.com/Applitools-Dev/sdk/commit/bb8c9b242801e08dd573a77e261d5fb4540cd68e)) | ||
| ## [1.36.1](https://github.com/Applitools-Dev/sdk/compare/js/core-base@1.36.0...js/core-base@1.36.1) (2026-07-08) | ||
@@ -4,0 +11,0 @@ |
+40
-13
@@ -32,16 +32,23 @@ "use strict"; | ||
| const queue = []; | ||
| return async function check({ target, settings = {}, logger = mainLogger, }) { | ||
| var _a, _b, _c, _d, _e, _f, _g, _h; | ||
| var _j, _k; | ||
| logger = logger.extend(mainLogger, { tags: [`check-base-${utils.general.shortid()}`] }); | ||
| settings !== null && settings !== void 0 ? settings : (settings = {}); | ||
| (_a = settings.stepIndex) !== null && _a !== void 0 ? _a : (settings.stepIndex = queue.length); | ||
| (_b = settings.normalization) !== null && _b !== void 0 ? _b : (settings.normalization = {}); | ||
| async function prepare(target, settings, logger) { | ||
| var _a, _b, _c, _d, _e; | ||
| (_a = settings.normalization) !== null && _a !== void 0 ? _a : (settings.normalization = {}); | ||
| settings.normalization.limit = { | ||
| maxImageHeight: Math.min((_d = (_c = settings.normalization.limit) === null || _c === void 0 ? void 0 : _c.maxImageHeight) !== null && _d !== void 0 ? _d : Infinity, requests.test.account.maxImageHeight), | ||
| maxImageArea: Math.min((_f = (_e = settings.normalization.limit) === null || _e === void 0 ? void 0 : _e.maxImageArea) !== null && _f !== void 0 ? _f : Infinity, requests.test.account.maxImageArea), | ||
| maxImageHeight: Math.min((_c = (_b = settings.normalization.limit) === null || _b === void 0 ? void 0 : _b.maxImageHeight) !== null && _c !== void 0 ? _c : Infinity, requests.test.account.maxImageHeight), | ||
| maxImageArea: Math.min((_e = (_d = settings.normalization.limit) === null || _d === void 0 ? void 0 : _d.maxImageArea) !== null && _e !== void 0 ? _e : Infinity, requests.test.account.maxImageArea), | ||
| }; | ||
| await (0, transform_dom_mapping_1.transformDomMapping)(settings); | ||
| logger.log('Command "check" is called with settings', settings); | ||
| (_g = queue[_j = settings.stepIndex]) !== null && _g !== void 0 ? _g : (queue[_j] = utils.promises.makeControlledPromise()); | ||
| return (0, transform_target_1.transformTarget)({ target, settings, logger }); | ||
| } | ||
| async function send(target, settings, logger) { | ||
| return requests.check({ target: await prepare(target, settings, logger), settings, logger }); | ||
| } | ||
| return async function check({ target, settings = {}, logger = mainLogger, followup, }) { | ||
| var _a, _b, _c; | ||
| var _d, _e; | ||
| logger = logger.extend(mainLogger, { tags: [`check-base-${utils.general.shortid()}`] }); | ||
| settings !== null && settings !== void 0 ? settings : (settings = {}); | ||
| (_a = settings.stepIndex) !== null && _a !== void 0 ? _a : (settings.stepIndex = queue.length); | ||
| (_b = queue[_d = settings.stepIndex]) !== null && _b !== void 0 ? _b : (queue[_d] = utils.promises.makeControlledPromise()); | ||
| const aborted = new Promise((_, reject) => { | ||
@@ -53,9 +60,29 @@ const abort = () => reject(new Error('Command "check" was aborted due to possible error in previous step')); | ||
| }); | ||
| target = await (0, transform_target_1.transformTarget)({ target, settings, logger }); | ||
| // Start preparing the target eagerly so raw-image fetch/decode overlaps earlier steps' match | ||
| // requests; a rejection is re-surfaced when awaited inside run(), where the step slot is | ||
| // guaranteed to be released. | ||
| const preparedTarget = prepare(target, settings, logger); | ||
| preparedTarget.catch(() => { }); // don't leave an unhandled rejection when the slot wait aborts first | ||
| // The slot is held until both the match request and its optional follow-up are sent, so the | ||
| // backend receives each step's requests adjacently. | ||
| if (settings.stepIndex > 0) { | ||
| await Promise.race([((_h = queue[_k = settings.stepIndex - 1]) !== null && _h !== void 0 ? _h : (queue[_k] = utils.promises.makeControlledPromise())), aborted]); | ||
| await Promise.race([((_c = queue[_e = settings.stepIndex - 1]) !== null && _c !== void 0 ? _c : (queue[_e] = utils.promises.makeControlledPromise())), aborted]); | ||
| } | ||
| return Promise.race([requests.check({ target, settings, logger }), aborted]).finally(queue[settings.stepIndex].resolve); | ||
| const run = async () => { | ||
| const result = await requests.check({ target: await preparedTarget, settings, logger }); | ||
| // Best-effort: the follow-up must neither fail a check whose match already succeeded | ||
| // nor start new work after the session was aborted. | ||
| if (followup && !signal.aborted) { | ||
| try { | ||
| await followup(result, (target, settings) => send(target, settings, logger)); | ||
| } | ||
| catch (error) { | ||
| logger.warn('Check follow-up failed; keeping the primary check result', error); | ||
| } | ||
| } | ||
| return result; | ||
| }; | ||
| return Promise.race([run(), aborted]).finally(queue[settings.stepIndex].resolve); | ||
| }; | ||
| } | ||
| exports.makeCheck = makeCheck; |
@@ -606,5 +606,8 @@ "use strict"; | ||
| async function logEventBatch(batch) { | ||
| // Seed the reduce with `mainLogger` and skip undefined loggers: callers may omit the logger | ||
| // (the public `logEvent` contract types it optional — e.g. the SDK.deprecation event), so an | ||
| // unseeded/unguarded reduce here threw on an undefined accumulator and killed the process. | ||
| const logger = batch | ||
| .map(([{ logger }]) => logger) | ||
| .reduce((acc, logger) => acc.extend(logger)) | ||
| .reduce((acc, logger) => (logger ? acc.extend(logger) : acc), mainLogger) | ||
| .extend({ tags: [`core-request-${utils.general.shortid()}`] }); | ||
@@ -611,0 +614,0 @@ const req = (0, req_eyes_1.makeReqEyes)({ settings: batch[0][0].settings, fetch, logger }); |
+1
-1
| { | ||
| "name": "@applitools/core-base", | ||
| "version": "1.36.1", | ||
| "version": "1.36.2", | ||
| "homepage": "https://applitools.com", | ||
@@ -5,0 +5,0 @@ "bugs": { |
+3
-2
@@ -1,2 +0,2 @@ | ||
| import type { Target, CheckSettings } from './types'; | ||
| import type { Target, CheckSettings, CheckFollowup } from './types'; | ||
| import { type AbortSignal } from 'abort-controller'; | ||
@@ -11,6 +11,7 @@ import { type Logger } from '@applitools/logger'; | ||
| }; | ||
| export declare function makeCheck({ requests, signal, logger: mainLogger }: Options): ({ target, settings, logger, }: { | ||
| export declare function makeCheck({ requests, signal, logger: mainLogger }: Options): ({ target, settings, logger, followup, }: { | ||
| target: Target; | ||
| settings?: CheckSettings<utils.Region> | undefined; | ||
| logger?: Logger | undefined; | ||
| followup?: CheckFollowup | undefined; | ||
| }) => Promise<{ | ||
@@ -17,0 +18,0 @@ asExpected: boolean; |
+14
-0
@@ -76,2 +76,15 @@ import type { MaybeArray, Region, Size, Location } from '@applitools/utils'; | ||
| } | ||
| /** | ||
| * Runs after a check's match request, while its step slot is still held, so any extra match it | ||
| * sends via `recheck` reaches the backend adjacently (before the next step's). Best-effort: it is | ||
| * not invoked once the session's abort signal fired, and a rejection is logged and swallowed — it | ||
| * never fails the check whose match already succeeded. See `@applitools/core` UFG old-infra | ||
| * retry (AD-14675). | ||
| * @internal | ||
| */ | ||
| export type CheckFollowup = (result: { | ||
| asExpected: boolean; | ||
| }, recheck: (target: Target, settings: CheckSettings) => Promise<{ | ||
| asExpected: boolean; | ||
| }>) => Promise<void>; | ||
| export interface Eyes { | ||
@@ -85,2 +98,3 @@ readonly core: Core; | ||
| logger?: Logger; | ||
| followup?: CheckFollowup; | ||
| }): Promise<{ | ||
@@ -87,0 +101,0 @@ asExpected: boolean; |
280396
1.02%4316
1.05%