@applitools/core 4.63.0 → 4.64.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/CHANGELOG.md CHANGED
@@ -1,5 +1,181 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.64.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.63.1...js/core@4.64.0) (2026-06-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * auto-accept UFG diffs from infra updates | AD-9583 ([#3797](https://github.com/Applitools-Dev/sdk/issues/3797)) ([fde553c](https://github.com/Applitools-Dev/sdk/commit/fde553c947aa3c040660ef5d07855fa4a1412399))
9
+ * support default match level from renderinfo | AD-13499 ([#3799](https://github.com/Applitools-Dev/sdk/issues/3799)) ([c7e8aeb](https://github.com/Applitools-Dev/sdk/commit/c7e8aeb3f231d9b4e6e368d7ae5f87115c5cad09))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * source hideCaret from settings.hideCaret in NML classic capture | AD-14053 ([#3878](https://github.com/Applitools-Dev/sdk/issues/3878)) ([f07b4f5](https://github.com/Applitools-Dev/sdk/commit/f07b4f5c90bb9b7de0300cef93f23dc833268a7c))
15
+
16
+
17
+ ### Dependencies
18
+
19
+ * @applitools/nml-client bumped to 1.11.29
20
+
21
+ * @applitools/core-base bumped to 1.35.0
22
+ #### Features
23
+
24
+ * auto-accept UFG diffs from infra updates | AD-9583 ([#3797](https://github.com/Applitools-Dev/sdk/issues/3797)) ([fde553c](https://github.com/Applitools-Dev/sdk/commit/fde553c947aa3c040660ef5d07855fa4a1412399))
25
+ * @applitools/ec-client bumped to 1.12.31
26
+
27
+
28
+ ## [4.63.1](https://github.com/Applitools-Dev/sdk/compare/js/core@4.63.0...js/core@4.63.1) (2026-05-26)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * prevent BrightCove/video.js player corruption during layoutBreakpoints snapshots ([#3828](https://github.com/Applitools-Dev/sdk/issues/3828)) ([380fbae](https://github.com/Applitools-Dev/sdk/commit/380fbaeb1c6a964c640975f1a109417db51f691c))
34
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
35
+ * truly disable NML broker URL cache between checks | FLD-4283 ([#3837](https://github.com/Applitools-Dev/sdk/issues/3837)) ([da76dd3](https://github.com/Applitools-Dev/sdk/commit/da76dd3227c3297320be0d19558a7437b5733999))
36
+
37
+
38
+ ### Dependencies
39
+
40
+ * @applitools/utils bumped to 1.14.5
41
+ #### Bug Fixes
42
+
43
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
44
+ * @applitools/logger bumped to 2.2.12
45
+ #### Bug Fixes
46
+
47
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
48
+
49
+
50
+
51
+ * @applitools/dom-shared bumped to 1.2.1
52
+ #### Bug Fixes
53
+
54
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
55
+ * @applitools/dom-snapshot bumped to 4.17.2
56
+ #### Bug Fixes
57
+
58
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
59
+
60
+
61
+
62
+ * @applitools/socket bumped to 1.3.13
63
+ #### Bug Fixes
64
+
65
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
66
+
67
+
68
+
69
+ * @applitools/req bumped to 1.10.2
70
+ #### Bug Fixes
71
+
72
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
73
+
74
+
75
+
76
+ * @applitools/image bumped to 1.2.11
77
+ #### Bug Fixes
78
+
79
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
80
+
81
+
82
+
83
+ * @applitools/snippets bumped to 2.9.2
84
+ #### Bug Fixes
85
+
86
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
87
+
88
+
89
+
90
+ * @applitools/dom-capture bumped to 11.8.1
91
+ #### Bug Fixes
92
+
93
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
94
+
95
+
96
+
97
+ * @applitools/driver bumped to 1.26.2
98
+ #### Bug Fixes
99
+
100
+ * redact cookie values from logs to prevent sensitive data exposure | AD-12834 ([#3835](https://github.com/Applitools-Dev/sdk/issues/3835)) ([d883647](https://github.com/Applitools-Dev/sdk/commit/d883647d8e007467bd2770ddb79f89be057067b9))
101
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
102
+
103
+
104
+
105
+ * @applitools/spec-driver-webdriver bumped to 1.6.2
106
+ #### Bug Fixes
107
+
108
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
109
+
110
+
111
+
112
+ * @applitools/spec-driver-selenium bumped to 1.8.2
113
+ #### Bug Fixes
114
+
115
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
116
+
117
+
118
+
119
+ * @applitools/spec-driver-playwright bumped to 1.9.2
120
+ #### Bug Fixes
121
+
122
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
123
+
124
+
125
+
126
+ * @applitools/spec-driver-puppeteer bumped to 1.8.2
127
+ #### Bug Fixes
128
+
129
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
130
+
131
+
132
+
133
+ * @applitools/screenshoter bumped to 3.12.21
134
+ #### Bug Fixes
135
+
136
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
137
+
138
+
139
+
140
+ * @applitools/nml-client bumped to 1.11.28
141
+ #### Bug Fixes
142
+
143
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
144
+
145
+
146
+
147
+ * @applitools/tunnel-client bumped to 1.11.14
148
+ #### Bug Fixes
149
+
150
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
151
+
152
+
153
+
154
+ * @applitools/ufg-client bumped to 1.22.2
155
+ #### Bug Fixes
156
+
157
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
158
+ * strip extra fields from checkResources log output ([#3836](https://github.com/Applitools-Dev/sdk/issues/3836)) ([6d75209](https://github.com/Applitools-Dev/sdk/commit/6d752094abb6fcc0e998cf95e1f266964dbfd6f3))
159
+
160
+
161
+
162
+ * @applitools/core-base bumped to 1.34.1
163
+ #### Bug Fixes
164
+
165
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
166
+
167
+
168
+
169
+ * @applitools/ec-client bumped to 1.12.30
170
+ #### Bug Fixes
171
+
172
+ * strip .d.ts extension from import() type specifiers | FLD-4429 ([#3830](https://github.com/Applitools-Dev/sdk/issues/3830)) ([37aac45](https://github.com/Applitools-Dev/sdk/commit/37aac45958b713702bea87c144623dd3e65fce79))
173
+
174
+
175
+
176
+ * @applitools/test-server bumped to 1.4.4
177
+
178
+
3
179
  ## [4.63.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.62.0...js/core@4.63.0) (2026-05-19)
4
180
 
5
181
 
@@ -4,15 +4,17 @@ exports.makeGetNMLClient = exports.clearNMLClients = void 0;
4
4
  const nml_client_1 = require("@applitools/nml-client");
5
5
  const utils_1 = require("@applitools/utils");
6
6
  const clients = new Map();
7
+ const cacheDisabled = new Set();
7
8
  // for tests
8
9
  function clearNMLClients() {
9
10
  clients.clear();
11
+ cacheDisabled.clear();
10
12
  }
11
13
  exports.clearNMLClients = clearNMLClients;
12
14
  function makeGetNMLClient({ client, logger: mainLogger }) {
13
15
  return {
14
16
  getNMLClient,
15
- clearNMLClientCache,
17
+ disableNMLClientCache,
16
18
  };
17
19
  async function getNMLClient({ driver, settings, logger, }) {
18
20
  if (client)
@@ -20,26 +22,30 @@ function makeGetNMLClient({ client, logger: mainLogger }) {
20
22
  logger = logger.extend(mainLogger);
21
23
  const { sessionId } = await driver.getDriverInfo();
22
24
  const cacheKey = sessionId !== null && sessionId !== void 0 ? sessionId : driver.guid;
23
- const clientFromCache = clients.get(cacheKey);
24
- if (clientFromCache) {
25
- return clientFromCache;
25
+ if (cacheDisabled.has(cacheKey)) {
26
+ logger.log('NML broker url cache is disabled for this session — re-extracting broker url');
26
27
  }
27
28
  else {
28
- const brokerUrl = await driver.extractBrokerUrl();
29
- if (!brokerUrl)
30
- throw new Error('Unable to extract broker url from the device');
31
- if (utils_1.types.has(brokerUrl, 'error')) {
32
- logger.error('Broker url extraction has failed with error', brokerUrl.error);
33
- throw new Error(brokerUrl.error);
34
- }
35
- const nmlClient = (0, nml_client_1.makeNMLClient)({ settings: { brokerUrl, ...settings }, logger });
36
- clients.set(cacheKey, nmlClient);
37
- return nmlClient;
29
+ const clientFromCache = clients.get(cacheKey);
30
+ if (clientFromCache)
31
+ return clientFromCache;
32
+ }
33
+ const brokerUrl = await driver.extractBrokerUrl();
34
+ if (!brokerUrl)
35
+ throw new Error('Unable to extract broker url from the device');
36
+ if (utils_1.types.has(brokerUrl, 'error')) {
37
+ logger.error('Broker url extraction has failed with error', brokerUrl.error);
38
+ throw new Error(brokerUrl.error);
38
39
  }
40
+ const nmlClient = (0, nml_client_1.makeNMLClient)({ settings: { brokerUrl, ...settings }, logger });
41
+ if (!cacheDisabled.has(cacheKey))
42
+ clients.set(cacheKey, nmlClient);
43
+ return nmlClient;
39
44
  }
40
- async function clearNMLClientCache(driver) {
45
+ async function disableNMLClientCache(driver) {
41
46
  const { sessionId } = await driver.getDriverInfo();
42
47
  const cacheKey = sessionId !== null && sessionId !== void 0 ? sessionId : driver.guid;
48
+ cacheDisabled.add(cacheKey);
43
49
  clients.delete(cacheKey);
44
50
  }
45
51
  }
package/dist/check.js CHANGED
@@ -35,8 +35,8 @@ const chalk_1 = __importDefault(require("chalk"));
35
35
  function makeCheck({ type: defaultType = 'classic', eyes, target: defaultTarget, spec, logger: mainLogger, }) {
36
36
  let stepIndex = 0;
37
37
  return async function check({ type = defaultType, target = defaultTarget, settings, config, logger = mainLogger, } = {}) {
38
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
39
- var _s, _t, _u, _v, _w;
38
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
39
+ var _t, _u, _v, _w, _x;
40
40
  logger = logger.extend(mainLogger, { tags: [`check-${type}-${utils.general.shortid()}`] });
41
41
  settings = { ...config === null || config === void 0 ? void 0 : config.screenshot, ...config === null || config === void 0 ? void 0 : config.check, ...settings };
42
42
  (_a = settings.fully) !== null && _a !== void 0 ? _a : (settings.fully = !settings.region && (!settings.frames || settings.frames.length === 0));
@@ -44,9 +44,9 @@ function makeCheck({ type: defaultType = 'classic', eyes, target: defaultTarget,
44
44
  (_c = settings.hideScrollbars) !== null && _c !== void 0 ? _c : (settings.hideScrollbars = true);
45
45
  (_d = settings.hideCaret) !== null && _d !== void 0 ? _d : (settings.hideCaret = true);
46
46
  settings.overlap = { top: 10, bottom: 50, ...settings === null || settings === void 0 ? void 0 : settings.overlap };
47
- (_e = settings.matchLevel) !== null && _e !== void 0 ? _e : (settings.matchLevel = 'Strict');
48
- (_f = settings.ignoreCaret) !== null && _f !== void 0 ? _f : (settings.ignoreCaret = true);
49
- (_g = settings.sendDom) !== null && _g !== void 0 ? _g : (settings.sendDom = eyes.test.account.rcaEnabled ||
47
+ (_e = settings.matchLevel) !== null && _e !== void 0 ? _e : (settings.matchLevel = (_f = eyes.test.account.defaultMatchLevel) !== null && _f !== void 0 ? _f : 'Strict');
48
+ (_g = settings.ignoreCaret) !== null && _g !== void 0 ? _g : (settings.ignoreCaret = true);
49
+ (_h = settings.sendDom) !== null && _h !== void 0 ? _h : (settings.sendDom = eyes.test.account.rcaEnabled ||
50
50
  settings.matchLevel === 'Layout' ||
51
51
  settings.matchLevel === 'Dynamic' ||
52
52
  settings.enablePatterns ||
@@ -54,26 +54,27 @@ function makeCheck({ type: defaultType = 'classic', eyes, target: defaultTarget,
54
54
  !utils.types.isEmpty(settings.dynamicRegions) ||
55
55
  !utils.types.isEmpty(settings.layoutRegions) ||
56
56
  !utils.types.isEmpty(settings.accessibilitySettings));
57
- (_h = settings.autProxy) !== null && _h !== void 0 ? _h : (settings.autProxy = eyes.test.eyesServer.proxy);
58
- (_j = settings.useDom) !== null && _j !== void 0 ? _j : (settings.useDom = false);
59
- (_k = (_s = settings).matchTimeout) !== null && _k !== void 0 ? _k : (_s.matchTimeout = 0);
57
+ (_j = settings.autProxy) !== null && _j !== void 0 ? _j : (settings.autProxy = eyes.test.eyesServer.proxy);
58
+ (_k = settings.useDom) !== null && _k !== void 0 ? _k : (settings.useDom = settings.sendDom // if we send the dom, it just makes sense to use it (unless the user turned it off)
59
+ );
60
+ (_l = (_t = settings).matchTimeout) !== null && _l !== void 0 ? _l : (_t.matchTimeout = 0);
60
61
  settings.lazyLoad = settings.lazyLoad === true ? {} : settings.lazyLoad;
61
62
  if (settings.lazyLoad) {
62
- (_l = (_t = settings.lazyLoad).scrollLength) !== null && _l !== void 0 ? _l : (_t.scrollLength = defaults_1.DEFAULT_LAZY_LOAD.scrollLength);
63
- (_m = (_u = settings.lazyLoad).waitingTime) !== null && _m !== void 0 ? _m : (_u.waitingTime = defaults_1.DEFAULT_LAZY_LOAD.waitingTime);
64
- (_o = (_v = settings.lazyLoad).maxAmountToScroll) !== null && _o !== void 0 ? _o : (_v.maxAmountToScroll = defaults_1.DEFAULT_LAZY_LOAD.maxAmountToScroll);
63
+ (_m = (_u = settings.lazyLoad).scrollLength) !== null && _m !== void 0 ? _m : (_u.scrollLength = defaults_1.DEFAULT_LAZY_LOAD.scrollLength);
64
+ (_o = (_v = settings.lazyLoad).waitingTime) !== null && _o !== void 0 ? _o : (_v.waitingTime = defaults_1.DEFAULT_LAZY_LOAD.waitingTime);
65
+ (_p = (_w = settings.lazyLoad).maxAmountToScroll) !== null && _p !== void 0 ? _p : (_w.maxAmountToScroll = defaults_1.DEFAULT_LAZY_LOAD.maxAmountToScroll);
65
66
  }
66
67
  settings.stepIndex = stepIndex++;
67
- (_p = settings.waitBetweenStitches) !== null && _p !== void 0 ? _p : (settings.waitBetweenStitches = utils.types.isObject(settings.lazyLoad)
68
+ (_q = settings.waitBetweenStitches) !== null && _q !== void 0 ? _q : (settings.waitBetweenStitches = utils.types.isObject(settings.lazyLoad)
68
69
  ? settings.lazyLoad.waitingTime
69
70
  : defaults_1.DEFAULT_WAIT_BEFORE_CAPTURE);
70
71
  if (settings.mobileOptions) {
71
- (_q = (_w = settings.mobileOptions).keepNavigationBar) !== null && _q !== void 0 ? _q : (_w.keepNavigationBar = false);
72
+ (_r = (_x = settings.mobileOptions).keepNavigationBar) !== null && _r !== void 0 ? _r : (_x.keepNavigationBar = false);
72
73
  }
73
74
  if (settings.matchLevel === 'Content') {
74
75
  logger.console.log(chalk_1.default.yellow(lang.matchLevelContentDeprecatedWarning));
75
76
  }
76
- if ((_r = settings.usedDeprecations) === null || _r === void 0 ? void 0 : _r.length) {
77
+ if ((_s = settings.usedDeprecations) === null || _s === void 0 ? void 0 : _s.length) {
77
78
  void eyes.core.logEvent({
78
79
  settings: {
79
80
  ...eyes.test.eyesServer,
@@ -136,7 +136,7 @@ function makeCheckAndClose({ eyes, target: defaultTarget, environments: defaultE
136
136
  fully: settings.fully,
137
137
  stitchMode: settings.stitchMode,
138
138
  hideScrollbars: settings.hideScrollbars,
139
- hideCaret: settings.hideScrollbars,
139
+ hideCaret: settings.hideCaret,
140
140
  overlap: settings.overlap,
141
141
  waitBeforeCapture: settings.waitBeforeCapture,
142
142
  waitBetweenStitches: settings.waitBetweenStitches,
@@ -347,7 +347,7 @@ async function takeNMLScreenshot({ driver, settings, environments, logger, eyes,
347
347
  fully: settings.fully,
348
348
  stitchMode: settings.stitchMode,
349
349
  hideScrollbars: settings.hideScrollbars,
350
- hideCaret: settings.hideScrollbars,
350
+ hideCaret: settings.hideCaret,
351
351
  overlap: settings.overlap,
352
352
  waitBeforeCapture: settings.waitBeforeCapture,
353
353
  waitBetweenStitches: settings.waitBetweenStitches,
package/dist/open-eyes.js CHANGED
@@ -145,7 +145,7 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
145
145
  logger,
146
146
  });
147
147
  if (settings.disableBrokerUrlCache && driver) {
148
- await core.clearNMLClientCache(driver);
148
+ await core.disableNMLClientCache(driver);
149
149
  }
150
150
  const getTypedEyes = (0, get_typed_eyes_1.makeGetTypedEyes)({
151
151
  type,
package/dist/ufg/check.js CHANGED
@@ -37,6 +37,7 @@ const chalk_1 = __importDefault(require("chalk"));
37
37
  const take_snapshots_1 = require("./take-snapshots");
38
38
  const create_render_target_from_snapshot_1 = require("./create-render-target-from-snapshot");
39
39
  const to_base_check_settings_1 = require("../automation/utils/to-base-check-settings");
40
+ const to_old_infra_environment_1 = require("./to-old-infra-environment");
40
41
  function makeCheck({ eyes, target: defaultTarget, environments: defaultEnvironments = [], spec, signal, logger: mainLogger, }) {
41
42
  return async function check({ target = defaultTarget, settings = {}, logger = mainLogger, } = {}) {
42
43
  var _a;
@@ -73,7 +74,7 @@ function makeCheck({ eyes, target: defaultTarget, environments: defaultEnvironme
73
74
  snapshots = !utils.types.isArray(target) ? Array(uniqueEnvironments.length).fill(target) : target;
74
75
  }
75
76
  const promises = snapshots.map(async (snapshot, i) => {
76
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
77
+ var _a, _b, _c, _d, _e, _f, _g, _h;
77
78
  const environmentLogger = logger.extend({ tags: [`environment-${utils.general.shortid()}`] });
78
79
  const { target } = snapshot;
79
80
  const environment = uniqueEnvironments[i];
@@ -116,39 +117,44 @@ function makeCheck({ eyes, target: defaultTarget, environments: defaultEnvironme
116
117
  const { elementReferences: selectors, getBaseCheckSettings } = (0, to_base_check_settings_1.toBaseCheckSettings)({
117
118
  settings: snapshot.settings,
118
119
  });
119
- const { renderId, selectorRegions, ...baseTarget } = await ufgClient.render({
120
- target: renderTarget,
121
- settings: {
122
- ...snapshot.settings,
123
- region: (_e = selectors.target) !== null && _e !== void 0 ? _e : snapshot.settings.region,
124
- scrollRootElement: selectors.scrolling,
125
- selectorsToCalculate: selectors.calculate,
126
- includeFullPageSize: Boolean(snapshot.settings.pageId),
127
- environment: { ...environment, environmentId: baseEyes.test.environment.environmentId },
128
- uploadUrl: baseEyes.test.uploadUrl,
129
- stitchingServiceUrl: baseEyes.test.stitchingServiceUrl,
130
- },
131
- signal,
132
- logger: environmentLogger,
133
- });
134
- const baseSettings = getBaseCheckSettings({
135
- calculatedRegions: selectors.calculate.map((_, index) => {
136
- var _a;
137
- return ({
138
- regions: (_a = selectorRegions === null || selectorRegions === void 0 ? void 0 : selectorRegions[index]) !== null && _a !== void 0 ? _a : [],
139
- });
140
- }),
141
- });
142
- baseSettings.renderId = renderId;
143
- baseTarget.source = snapshot.url;
144
- baseTarget.name = snapshot.title;
120
+ const renderForCheck = async (env) => {
121
+ var _a;
122
+ const { renderId, selectorRegions, ...target } = await ufgClient.render({
123
+ target: renderTarget,
124
+ settings: {
125
+ ...snapshot.settings,
126
+ region: (_a = selectors.target) !== null && _a !== void 0 ? _a : snapshot.settings.region,
127
+ scrollRootElement: selectors.scrolling,
128
+ selectorsToCalculate: selectors.calculate,
129
+ includeFullPageSize: Boolean(snapshot.settings.pageId),
130
+ environment: { ...env, environmentId: baseEyes.test.environment.environmentId },
131
+ uploadUrl: baseEyes.test.uploadUrl,
132
+ stitchingServiceUrl: baseEyes.test.stitchingServiceUrl,
133
+ },
134
+ signal,
135
+ logger: environmentLogger,
136
+ });
137
+ const checkSettings = getBaseCheckSettings({
138
+ calculatedRegions: selectors.calculate.map((_, index) => {
139
+ var _a;
140
+ return ({
141
+ regions: (_a = selectorRegions === null || selectorRegions === void 0 ? void 0 : selectorRegions[index]) !== null && _a !== void 0 ? _a : [],
142
+ });
143
+ }),
144
+ });
145
+ checkSettings.renderId = renderId;
146
+ target.source = snapshot.url;
147
+ target.name = snapshot.title;
148
+ return { target, settings: checkSettings };
149
+ };
150
+ const { target: baseTarget, settings: baseSettings } = await renderForCheck(environment);
145
151
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
146
152
  environmentLogger.warn('Command "check" was aborted after rendering');
147
153
  throw new abort_error_1.AbortError('Command "check" was aborted after rendering');
148
154
  }
149
155
  else if (!baseEyes.running) {
150
- environmentLogger.warn(`Render on environment with id "${(_f = baseEyes.test.environment) === null || _f === void 0 ? void 0 : _f.environmentId}" was aborted during one of the previous steps`);
151
- throw new abort_error_1.AbortError(`Render on environment with id "${(_g = baseEyes.test.environment) === null || _g === void 0 ? void 0 : _g.environmentId}" was aborted during one of the previous steps`);
156
+ environmentLogger.warn(`Render on environment with id "${(_e = baseEyes.test.environment) === null || _e === void 0 ? void 0 : _e.environmentId}" was aborted during one of the previous steps`);
157
+ throw new abort_error_1.AbortError(`Render on environment with id "${(_f = baseEyes.test.environment) === null || _f === void 0 ? void 0 : _f.environmentId}" was aborted during one of the previous steps`);
152
158
  }
153
159
  if (settings.assumesMutability) {
154
160
  void eyes.core.logEvent({
@@ -166,7 +172,7 @@ function makeCheck({ eyes, target: defaultTarget, environments: defaultEnvironme
166
172
  userTestId: baseEyes.test.userTestId,
167
173
  appId: baseEyes.test.appId,
168
174
  baselineId: baseEyes.test.baselineId,
169
- environmentId: (_h = baseEyes.test.environment) === null || _h === void 0 ? void 0 : _h.environmentId,
175
+ environmentId: (_g = baseEyes.test.environment) === null || _g === void 0 ? void 0 : _g.environmentId,
170
176
  },
171
177
  isNew: baseEyes.test.isNew,
172
178
  resultsUrl: baseEyes.test.resultsUrl,
@@ -182,14 +188,35 @@ function makeCheck({ eyes, target: defaultTarget, environments: defaultEnvironme
182
188
  catch (err) {
183
189
  logger.log('Failed to log invalid check settings', err);
184
190
  }
185
- await baseEyes.check({
191
+ const checkResult = await baseEyes.check({
186
192
  target: { ...baseTarget, isTransformed: true },
187
193
  settings: baseSettings,
188
194
  logger: environmentLogger,
189
195
  });
196
+ if (!checkResult.asExpected && baseEyes.test.shouldRetryWithOldInfra) {
197
+ const oldInfraEnvironment = (0, to_old_infra_environment_1.toOldInfraEnvironment)(environment);
198
+ if (oldInfraEnvironment) {
199
+ // Best-effort: the retry adds an extra render + check, and a transient
200
+ // failure there (old browser version unavailable, render timeout, 5xx)
201
+ // must not abort the environment — the primary check already recorded
202
+ // its step. Swallow and log so the retry can only ever help.
203
+ try {
204
+ const { target, settings } = await renderForCheck(oldInfraEnvironment);
205
+ settings.oldInfraRetry = true;
206
+ await baseEyes.check({
207
+ target: { ...target, isTransformed: true },
208
+ settings,
209
+ logger: environmentLogger,
210
+ });
211
+ }
212
+ catch (error) {
213
+ environmentLogger.warn('Old-infra retry failed; keeping the primary check result', error);
214
+ }
215
+ }
216
+ }
190
217
  }
191
218
  catch (error) {
192
- environmentLogger.error(`Render on environment with id "${(_j = baseEyes.test.environment) === null || _j === void 0 ? void 0 : _j.environmentId}" failed due to an error`, error);
219
+ environmentLogger.error(`Render on environment with id "${(_h = baseEyes.test.environment) === null || _h === void 0 ? void 0 : _h.environmentId}" failed due to an error`, error);
193
220
  if (baseEyes.running && !(signal === null || signal === void 0 ? void 0 : signal.aborted))
194
221
  await baseEyes.abort({ logger: environmentLogger, settings: { reason: error } });
195
222
  }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toOldInfraEnvironment = void 0;
4
+ const latestToOneBack = {
5
+ chrome: 'chrome-one-version-back',
6
+ firefox: 'firefox-one-version-back',
7
+ safari: 'safari-one-version-back',
8
+ edgechromium: 'edgechromium-one-version-back',
9
+ };
10
+ /**
11
+ * Returns the same UFG environment retargeted at the previous browser/OS version,
12
+ * or `null` if the input is not at a latest-alias environment (already one-version-back,
13
+ * pinned to a specific version, or an unsupported browser).
14
+ */
15
+ function toOldInfraEnvironment(env) {
16
+ if (!isLatest(env))
17
+ return null;
18
+ if ('iosDeviceInfo' in env)
19
+ return { ...env, iosDeviceInfo: { ...env.iosDeviceInfo, version: 'latest-1' } };
20
+ if ('androidDeviceInfo' in env)
21
+ return { ...env, androidDeviceInfo: { ...env.androidDeviceInfo, version: 'latest-1' } };
22
+ if ('chromeEmulationInfo' in env)
23
+ return { ...env, name: 'chrome-one-version-back' };
24
+ return { ...env, name: latestToOneBack[env.name] };
25
+ }
26
+ exports.toOldInfraEnvironment = toOldInfraEnvironment;
27
+ function isLatest(env) {
28
+ if ('iosDeviceInfo' in env)
29
+ return env.iosDeviceInfo.version !== 'latest-1';
30
+ if ('androidDeviceInfo' in env)
31
+ return env.androidDeviceInfo.version !== 'latest-1';
32
+ if ('chromeEmulationInfo' in env)
33
+ return !env.name || env.name === 'chrome';
34
+ return !!env.name && env.name in latestToOneBack;
35
+ }
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pagePayload = exports.makeMediaPlaybackGuard = void 0;
4
+ /**
5
+ * Prevents media playback side-effects during the layoutBreakpoints snapshot loop.
6
+ *
7
+ * dom-snapshot calls video.play() per breakpoint to capture a video frame. For
8
+ * BrightCove / video.js players this triggers CSS class changes (has-started, is-playing)
9
+ * and subtitle text injection that outlast the corresponding pause() call. With multiple
10
+ * breakpoints the mutations accumulate and corrupt visual snapshots.
11
+ *
12
+ * Lifecycle:
13
+ * const guard = makeMediaPlaybackGuard({context, logger})
14
+ * await guard.setup() // once, before the loop
15
+ * for (const entry of entries) {
16
+ * await guard.beforeResize() // before setViewportSize
17
+ * // setViewportSize / reload / lazyLoad
18
+ * await guard.beforeSnapshot() // after resize, before takeDomSnapshot
19
+ * // takeDomSnapshot
20
+ * await guard.afterSnapshot() // after takeDomSnapshot
21
+ * }
22
+ * // setViewportSize (restore)
23
+ * await guard.teardown() // once, after final viewport restore
24
+ *
25
+ * Implementation notes:
26
+ *
27
+ * The TypeScript layer is just orchestration — `setup` / `beforeResize` / etc. each
28
+ * dispatch a phase name to a single self-contained page-side payload (`pagePayload`
29
+ * below) via `context.execute`. The first call installs a library at `window[ns]`
30
+ * with one handler per lifecycle method; subsequent calls just look up and run
31
+ * `window[ns][phase]()`. Teardown deletes `window[ns]` so a fresh guard starts clean.
32
+ *
33
+ * - `ns` is randomised per guard instance so concurrent guards on the same page
34
+ * (e.g. nested checks) do not collide.
35
+ * - Per-element state lives in a `WeakMap`, not on the elements themselves — no
36
+ * marker properties leak onto the page's DOM.
37
+ * - A single `walkAll(root, fn)` helper handles every Document/ShadowRoot traversal.
38
+ * - A `MutationObserver` covers the gap between phase calls (e.g. videos that
39
+ * BrightCove creates during setViewportSize). The synchronous walks in
40
+ * `beforeSnapshot` remain because the observer fires on a microtask and would
41
+ * otherwise lose the race against dom-snapshot's serialization.
42
+ */
43
+ function makeMediaPlaybackGuard({ context, logger, }) {
44
+ const ns = '__applitools_mpg_' + Math.random().toString(36).slice(2) + '__';
45
+ return {
46
+ setup: () => runPhase('setup'),
47
+ beforeResize: () => runPhase('beforeResize'),
48
+ beforeSnapshot: () => runPhase('beforeSnapshot'),
49
+ afterSnapshot: () => runPhase('afterSnapshot'),
50
+ teardown: () => runPhase('teardown'),
51
+ };
52
+ function runPhase(phase) {
53
+ return context
54
+ .execute(pagePayload, { ns, phase })
55
+ .catch((err) => logger.warn(`media guard: ${phase} failed`, err));
56
+ }
57
+ }
58
+ exports.makeMediaPlaybackGuard = makeMediaPlaybackGuard;
59
+ /**
60
+ * The entire page-side library — a single self-contained function shipped to the
61
+ * browser by `context.execute`. No closure variables from the TypeScript side are
62
+ * visible inside this body.
63
+ *
64
+ * Contract: on first invocation, installs `window[ns]` with `{setup, beforeResize,
65
+ * beforeSnapshot, afterSnapshot, teardown}` handlers; on every invocation, calls
66
+ * `window[ns][phase]()` if present. The handlers all close over the same set of
67
+ * private helpers and a `WeakMap`-backed per-element state.
68
+ */
69
+ /* istanbul ignore next — runs only inside the browser, not in node */
70
+ function pagePayload(arg) {
71
+ const w = window;
72
+ const ns = arg.ns;
73
+ if (!w[ns])
74
+ install();
75
+ const state = w[ns];
76
+ if (state && typeof state[arg.phase] === 'function')
77
+ state[arg.phase]();
78
+ function install() {
79
+ // ----- private state -----
80
+ const videoUndos = new WeakMap();
81
+ const captionUndos = new WeakMap();
82
+ const playerSnapshots = [];
83
+ const originalProtoPlay = HTMLMediaElement.prototype.play;
84
+ const originalCreateElement = document.createElement.bind(document);
85
+ const originalCreateElementNS = document.createElementNS.bind(document);
86
+ let observer = null;
87
+ // ----- helpers -----
88
+ /** Single DOM traversal helper. Visits every element under `root`, recursing into shadow roots. */
89
+ function walkAll(root, fn) {
90
+ const els = root.querySelectorAll('*');
91
+ for (let i = 0; i < els.length; i++) {
92
+ const el = els[i];
93
+ fn(el);
94
+ const sr = el.shadowRoot;
95
+ if (sr)
96
+ walkAll(sr, fn);
97
+ }
98
+ }
99
+ function walkVideos(root, fn) {
100
+ walkAll(root, el => {
101
+ if (el.tagName === 'VIDEO')
102
+ fn(el);
103
+ });
104
+ }
105
+ function walkCaptions(root, fn) {
106
+ walkAll(root, el => {
107
+ const cl = el.classList;
108
+ if (cl && cl.contains('vjs-text-track-display'))
109
+ fn(el);
110
+ });
111
+ }
112
+ /**
113
+ * Replace an own-property on `obj` with `replacement` and return a closure that
114
+ * puts the original back. Restores correctly whether the original lived on the
115
+ * instance or only on the prototype.
116
+ */
117
+ function overrideOwn(obj, key, replacement) {
118
+ const wasOwn = Object.prototype.hasOwnProperty.call(obj, key);
119
+ const original = wasOwn ? obj[key] : undefined;
120
+ obj[key] = replacement;
121
+ return function restore() {
122
+ if (wasOwn)
123
+ obj[key] = original;
124
+ else
125
+ delete obj[key];
126
+ };
127
+ }
128
+ function runUndos(undos) {
129
+ for (let i = undos.length - 1; i >= 0; i--) {
130
+ try {
131
+ undos[i]();
132
+ }
133
+ catch (_) {
134
+ // best-effort restore: a failure here must not block the rest
135
+ }
136
+ }
137
+ }
138
+ // ----- locking primitives -----
139
+ function lockVideo(video) {
140
+ if (videoUndos.has(video))
141
+ return;
142
+ const undos = [];
143
+ // 1. Mask the `autoplay` IDL attribute so reads return false and writes are dropped.
144
+ const hadAutoplay = video.hasAttribute('autoplay');
145
+ if (hadAutoplay)
146
+ video.removeAttribute('autoplay');
147
+ try {
148
+ Object.defineProperty(video, 'autoplay', {
149
+ configurable: true,
150
+ get: () => false,
151
+ set: () => { },
152
+ });
153
+ }
154
+ catch (_) { }
155
+ undos.push(() => {
156
+ try {
157
+ delete video.autoplay;
158
+ }
159
+ catch (_) { }
160
+ if (hadAutoplay)
161
+ video.setAttribute('autoplay', '');
162
+ });
163
+ // 2. setAttribute('autoplay', ...) — BrightCove uses this directly, bypassing the IDL setter.
164
+ undos.push(overrideOwn(video, 'setAttribute', function (name, value) {
165
+ if (name === 'autoplay')
166
+ return;
167
+ return Element.prototype.setAttribute.call(this, name, value);
168
+ }));
169
+ // 3. Make captureStream() return zero video tracks so dom-snapshot's buildBlobVideos
170
+ // skips its play()/record/pause cycle entirely.
171
+ if (video.captureStream) {
172
+ undos.push(overrideOwn(video, 'captureStream', function () {
173
+ return { getVideoTracks: () => [] };
174
+ }));
175
+ }
176
+ // 4. Disable text tracks (caption rendering source).
177
+ const tracks = video.textTracks;
178
+ const savedModes = [];
179
+ for (let i = 0; i < tracks.length; i++) {
180
+ savedModes.push(tracks[i].mode);
181
+ tracks[i].mode = 'disabled';
182
+ }
183
+ undos.push(() => {
184
+ const t = video.textTracks;
185
+ for (let i = 0; i < t.length && i < savedModes.length; i++)
186
+ t[i].mode = savedModes[i];
187
+ });
188
+ videoUndos.set(video, undos);
189
+ }
190
+ function unlockVideo(video) {
191
+ const undos = videoUndos.get(video);
192
+ if (!undos)
193
+ return;
194
+ videoUndos.delete(video);
195
+ runUndos(undos);
196
+ }
197
+ /**
198
+ * Lock a .vjs-text-track-display caption container. Instance-level overrides because
199
+ * BrightCove's event handlers re-inject cue elements in microtasks between our
200
+ * phase call returning and dom-snapshot serializing the DOM — synchronous, persistent
201
+ * overrides intercept every insertion path regardless of timing.
202
+ */
203
+ function lockCaption(c) {
204
+ if (captionUndos.has(c))
205
+ return;
206
+ const undos = [];
207
+ while (c.firstChild)
208
+ c.removeChild(c.firstChild);
209
+ undos.push(overrideOwn(c, 'appendChild', (n) => n));
210
+ undos.push(overrideOwn(c, 'insertBefore', (n) => n));
211
+ undos.push(overrideOwn(c, 'insertAdjacentHTML', () => { }));
212
+ undos.push(overrideOwn(c, 'insertAdjacentElement', (_pos, n) => n));
213
+ try {
214
+ Object.defineProperty(c, 'innerHTML', {
215
+ configurable: true,
216
+ get: () => '',
217
+ set: () => { },
218
+ });
219
+ undos.push(() => {
220
+ try {
221
+ delete c.innerHTML;
222
+ }
223
+ catch (_) { }
224
+ });
225
+ }
226
+ catch (_) { }
227
+ captionUndos.set(c, undos);
228
+ }
229
+ function unlockCaption(c) {
230
+ const undos = captionUndos.get(c);
231
+ if (!undos)
232
+ return;
233
+ captionUndos.delete(c);
234
+ runUndos(undos);
235
+ }
236
+ // ----- player container snapshot / restore -----
237
+ function snapshotPlayers() {
238
+ walkVideos(document, video => {
239
+ const playerEl = video.closest('video-js, [data-vjs-player], .video-js');
240
+ playerSnapshots.push({
241
+ video,
242
+ paused: video.paused,
243
+ currentTime: video.currentTime,
244
+ playerEl,
245
+ playerClasses: playerEl ? Array.from(playerEl.classList) : [],
246
+ });
247
+ });
248
+ }
249
+ function restorePlayers() {
250
+ for (let i = 0; i < playerSnapshots.length; i++) {
251
+ const s = playerSnapshots[i];
252
+ if (s.paused && !s.video.paused) {
253
+ s.video.pause();
254
+ try {
255
+ s.video.currentTime = s.currentTime;
256
+ }
257
+ catch (_) { } // MSE may reject seeks
258
+ }
259
+ if (s.playerEl) {
260
+ const current = Array.from(s.playerEl.classList);
261
+ for (let j = 0; j < current.length; j++) {
262
+ if (s.playerClasses.indexOf(current[j]) === -1)
263
+ s.playerEl.classList.remove(current[j]);
264
+ }
265
+ for (let j = 0; j < s.playerClasses.length; j++)
266
+ s.playerEl.classList.add(s.playerClasses[j]);
267
+ }
268
+ }
269
+ }
270
+ // ----- global patches (live for the entire loop) -----
271
+ function patchGlobals() {
272
+ // video.js calls HTMLMediaElement.prototype.play.call(el) directly — prototype patch is mandatory.
273
+ // Stays alive through teardown's restorePlayers() so BrightCove's pause-event handler
274
+ // cannot call the (now-live) native play() and restart playback.
275
+ ;
276
+ HTMLMediaElement.prototype.play = function () {
277
+ return Promise.resolve();
278
+ };
279
+ document.createElement = function (tag) {
280
+ const el = originalCreateElement(tag);
281
+ if (typeof tag === 'string' && tag.toLowerCase() === 'video') {
282
+ lockVideo(el);
283
+ }
284
+ return el;
285
+ };
286
+ document.createElementNS = function (nsArg, tag) {
287
+ const el = originalCreateElementNS(nsArg, tag);
288
+ if (typeof tag === 'string' && tag.toLowerCase() === 'video') {
289
+ lockVideo(el);
290
+ }
291
+ return el;
292
+ };
293
+ }
294
+ function unpatchGlobals() {
295
+ ;
296
+ HTMLMediaElement.prototype.play = originalProtoPlay;
297
+ document.createElement = originalCreateElement;
298
+ document.createElementNS = originalCreateElementNS;
299
+ }
300
+ // ----- mutation observer (belt-and-suspenders for late-arriving nodes) -----
301
+ function startObserver() {
302
+ observer = new MutationObserver(mutations => {
303
+ for (let i = 0; i < mutations.length; i++) {
304
+ const added = mutations[i].addedNodes;
305
+ for (let j = 0; j < added.length; j++) {
306
+ const node = added[j];
307
+ if (!(node instanceof Element))
308
+ continue;
309
+ if (node.tagName === 'VIDEO')
310
+ lockVideo(node);
311
+ else if (node.classList && node.classList.contains('vjs-text-track-display'))
312
+ lockCaption(node);
313
+ walkVideos(node, lockVideo);
314
+ walkCaptions(node, lockCaption);
315
+ }
316
+ }
317
+ });
318
+ observer.observe(document, { childList: true, subtree: true });
319
+ }
320
+ function stopObserver() {
321
+ if (observer) {
322
+ observer.disconnect();
323
+ observer = null;
324
+ }
325
+ }
326
+ // ----- bootstrap -----
327
+ // Player state is NOT snapshotted here. It is captured fresh in `beforeSnapshot`
328
+ // so that responsive classes (vjs-layout-small, vjs-fluid, orientation classes)
329
+ // applied during setViewportSize are preserved across afterSnapshot's restore.
330
+ // This also automatically covers videos BrightCove injects mid-loop, since the
331
+ // walk re-runs each iteration.
332
+ patchGlobals();
333
+ walkCaptions(document, lockCaption);
334
+ startObserver();
335
+ // ----- phase handlers (called from TS via runPhase) -----
336
+ w[ns] = {
337
+ // `setup` is already complete by the time we get here; the no-op handler keeps the dispatch shape uniform.
338
+ setup() { },
339
+ beforeResize() {
340
+ walkVideos(document, lockVideo);
341
+ walkCaptions(document, lockCaption);
342
+ },
343
+ beforeSnapshot() {
344
+ // Safety net first — pause videos and disable tracks before snapshotting state,
345
+ // so playback classes (vjs-playing) settle to a consistent paused-state.
346
+ walkVideos(document, video => {
347
+ if (!video.paused)
348
+ video.pause();
349
+ const t = video.textTracks;
350
+ for (let i = 0; i < t.length; i++)
351
+ t[i].mode = 'disabled';
352
+ });
353
+ // Capture fresh per-player state — runs after resize so responsive classes
354
+ // reflect the current breakpoint, and so videos BrightCove injected during
355
+ // the resize are included.
356
+ playerSnapshots.length = 0;
357
+ snapshotPlayers();
358
+ // MutationObserver is async; lock new caption containers synchronously before
359
+ // dom-snapshot's next-macrotask serialization runs.
360
+ walkCaptions(document, lockCaption);
361
+ },
362
+ afterSnapshot() {
363
+ walkVideos(document, unlockVideo);
364
+ restorePlayers();
365
+ // Clear after consume — the captured state is breakpoint-specific and must not
366
+ // leak into the next iteration or teardown, where the viewport differs.
367
+ playerSnapshots.length = 0;
368
+ },
369
+ teardown() {
370
+ // Restore players BEFORE unpatching globals: BrightCove's pause-event handler can call
371
+ // play() during pause(), and we need that call to still hit the no-op prototype patch.
372
+ restorePlayers();
373
+ stopObserver();
374
+ unpatchGlobals();
375
+ walkVideos(document, unlockVideo);
376
+ walkCaptions(document, unlockCaption);
377
+ delete w[ns];
378
+ },
379
+ };
380
+ }
381
+ }
382
+ exports.pagePayload = pagePayload;
@@ -34,6 +34,7 @@ const take_dom_snapshot_1 = require("./take-dom-snapshot");
34
34
  const generate_safe_selectors_1 = require("./generate-safe-selectors");
35
35
  const wait_for_lazy_load_1 = require("../../automation/utils/wait-for-lazy-load");
36
36
  const calculate_required_sizes_1 = require("../../automation/utils/calculate-required-sizes");
37
+ const media_playback_guard_1 = require("./media-playback-guard");
37
38
  const chalk_1 = __importDefault(require("chalk"));
38
39
  const utils = __importStar(require("@applitools/utils"));
39
40
  const lang = __importStar(require("../../lang"));
@@ -85,9 +86,12 @@ async function takeDomSnapshots({ driver, settings, hooks, provides, logger, })
85
86
  context: currentContext,
86
87
  elementReferences: settings.elementReferences,
87
88
  });
89
+ const mediaGuard = (0, media_playback_guard_1.makeMediaPlaybackGuard)({ context: currentContext, logger });
90
+ await mediaGuard.setup();
88
91
  const snapshots = Array(settings.environments.length);
89
92
  for (const [index, { viewportSize, browsers }] of entries.entries()) {
90
93
  logger.log(`Taking dom snapshot for viewport size ${viewportSize}`);
94
+ await mediaGuard.beforeResize();
91
95
  if (viewportSize) {
92
96
  try {
93
97
  await driver.setViewportSize(viewportSize);
@@ -121,10 +125,13 @@ async function takeDomSnapshots({ driver, settings, hooks, provides, logger, })
121
125
  await (0, wait_for_lazy_load_1.waitForLazyLoad)({ context: currentContext, settings: settings.lazyLoad, logger });
122
126
  }
123
127
  await beforeEachSnapshot();
128
+ await mediaGuard.beforeSnapshot();
124
129
  const snapshot = await (0, take_dom_snapshot_1.takeDomSnapshot)({ context: currentContext, settings, logger });
125
130
  browsers.forEach(({ index }) => (snapshots[index] = snapshot));
131
+ await mediaGuard.afterSnapshot();
126
132
  }
127
133
  await driver.setViewportSize(initialViewportSize);
134
+ await mediaGuard.teardown();
128
135
  if (settings.layoutBreakpoints.reload) {
129
136
  await driver.reloadPage();
130
137
  await beforeEachSnapshot();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/core",
3
- "version": "4.63.0",
3
+ "version": "4.64.0",
4
4
  "homepage": "https://applitools.com",
5
5
  "bugs": {
6
6
  "url": "https://github.com/applitools/eyes.sdk.javascript1/issues"
@@ -61,19 +61,19 @@
61
61
  "setup:standalone": "sh -c 'yarn chromedriver --port=4444 --verbose &'"
62
62
  },
63
63
  "dependencies": {
64
- "@applitools/core-base": "1.34.0",
65
- "@applitools/dom-capture": "11.8.0",
66
- "@applitools/dom-snapshot": "4.17.1",
67
- "@applitools/driver": "1.26.1",
68
- "@applitools/ec-client": "1.12.29",
69
- "@applitools/logger": "2.2.11",
70
- "@applitools/nml-client": "1.11.27",
71
- "@applitools/req": "1.10.1",
72
- "@applitools/screenshoter": "3.12.20",
73
- "@applitools/snippets": "2.9.1",
74
- "@applitools/socket": "1.3.12",
75
- "@applitools/ufg-client": "1.22.1",
76
- "@applitools/utils": "1.14.4",
64
+ "@applitools/core-base": "1.35.0",
65
+ "@applitools/dom-capture": "11.8.1",
66
+ "@applitools/dom-snapshot": "4.17.2",
67
+ "@applitools/driver": "1.26.2",
68
+ "@applitools/ec-client": "1.12.31",
69
+ "@applitools/logger": "2.2.12",
70
+ "@applitools/nml-client": "1.11.29",
71
+ "@applitools/req": "1.10.2",
72
+ "@applitools/screenshoter": "3.12.21",
73
+ "@applitools/snippets": "2.9.2",
74
+ "@applitools/socket": "1.3.13",
75
+ "@applitools/ufg-client": "1.22.2",
76
+ "@applitools/utils": "1.14.5",
77
77
  "abort-controller": "3.0.0",
78
78
  "chalk": "4.1.2",
79
79
  "node-fetch": "2.6.7",
@@ -82,12 +82,12 @@
82
82
  },
83
83
  "devDependencies": {
84
84
  "@applitools/bongo": "^5.10.0",
85
- "@applitools/spec-driver-playwright": "^1.9.1",
86
- "@applitools/spec-driver-puppeteer": "^1.8.1",
87
- "@applitools/spec-driver-selenium": "^1.8.1",
88
- "@applitools/test-server": "^1.4.3",
85
+ "@applitools/spec-driver-playwright": "^1.9.2",
86
+ "@applitools/spec-driver-puppeteer": "^1.8.2",
87
+ "@applitools/spec-driver-selenium": "^1.8.2",
88
+ "@applitools/test-server": "^1.4.4",
89
89
  "@applitools/test-utils": "^1.5.17",
90
- "@applitools/tunnel-client": "^1.11.13",
90
+ "@applitools/tunnel-client": "^1.11.14",
91
91
  "@swc-node/register": "^1.6.8",
92
92
  "@types/mocha": "^10.0.7",
93
93
  "@types/node": "^12.20.55",
@@ -12,6 +12,6 @@ export declare function makeGetNMLClient({ client, logger: mainLogger }: Options
12
12
  settings: Omit<NMLClientSettings, 'brokerUrl'>;
13
13
  logger: Logger;
14
14
  }) => Promise<NMLClient>;
15
- clearNMLClientCache: (driver: Driver<SpecType>) => Promise<void>;
15
+ disableNMLClientCache: (driver: Driver<SpecType>) => Promise<void>;
16
16
  };
17
17
  export {};
@@ -67,7 +67,7 @@ export interface Core<TSpec extends SpecType> extends Omit<BaseCore.Core, 'openE
67
67
  driver: Driver<TSpec>;
68
68
  logger: Logger;
69
69
  }): Promise<NMLClient>;
70
- clearNMLClientCache(driver: Driver<TSpec>): Promise<void>;
70
+ disableNMLClientCache(driver: Driver<TSpec>): Promise<void>;
71
71
  openEyes(options: {
72
72
  target?: DriverTarget<TSpec>;
73
73
  settings: BaseCore.OpenSettings;
@@ -0,0 +1,7 @@
1
+ import type { Environment as UFGEnvironment } from '@applitools/ufg-client';
2
+ /**
3
+ * Returns the same UFG environment retargeted at the previous browser/OS version,
4
+ * or `null` if the input is not at a latest-alias environment (already one-version-back,
5
+ * pinned to a specific version, or an unsupported browser).
6
+ */
7
+ export declare function toOldInfraEnvironment(env: UFGEnvironment): UFGEnvironment | null;
@@ -0,0 +1,67 @@
1
+ import { type SpecType, type Context } from '@applitools/driver';
2
+ import { type Logger } from '@applitools/logger';
3
+ /**
4
+ * Prevents media playback side-effects during the layoutBreakpoints snapshot loop.
5
+ *
6
+ * dom-snapshot calls video.play() per breakpoint to capture a video frame. For
7
+ * BrightCove / video.js players this triggers CSS class changes (has-started, is-playing)
8
+ * and subtitle text injection that outlast the corresponding pause() call. With multiple
9
+ * breakpoints the mutations accumulate and corrupt visual snapshots.
10
+ *
11
+ * Lifecycle:
12
+ * const guard = makeMediaPlaybackGuard({context, logger})
13
+ * await guard.setup() // once, before the loop
14
+ * for (const entry of entries) {
15
+ * await guard.beforeResize() // before setViewportSize
16
+ * // setViewportSize / reload / lazyLoad
17
+ * await guard.beforeSnapshot() // after resize, before takeDomSnapshot
18
+ * // takeDomSnapshot
19
+ * await guard.afterSnapshot() // after takeDomSnapshot
20
+ * }
21
+ * // setViewportSize (restore)
22
+ * await guard.teardown() // once, after final viewport restore
23
+ *
24
+ * Implementation notes:
25
+ *
26
+ * The TypeScript layer is just orchestration — `setup` / `beforeResize` / etc. each
27
+ * dispatch a phase name to a single self-contained page-side payload (`pagePayload`
28
+ * below) via `context.execute`. The first call installs a library at `window[ns]`
29
+ * with one handler per lifecycle method; subsequent calls just look up and run
30
+ * `window[ns][phase]()`. Teardown deletes `window[ns]` so a fresh guard starts clean.
31
+ *
32
+ * - `ns` is randomised per guard instance so concurrent guards on the same page
33
+ * (e.g. nested checks) do not collide.
34
+ * - Per-element state lives in a `WeakMap`, not on the elements themselves — no
35
+ * marker properties leak onto the page's DOM.
36
+ * - A single `walkAll(root, fn)` helper handles every Document/ShadowRoot traversal.
37
+ * - A `MutationObserver` covers the gap between phase calls (e.g. videos that
38
+ * BrightCove creates during setViewportSize). The synchronous walks in
39
+ * `beforeSnapshot` remain because the observer fires on a microtask and would
40
+ * otherwise lose the race against dom-snapshot's serialization.
41
+ */
42
+ export declare function makeMediaPlaybackGuard<TSpec extends SpecType>({ context, logger, }: {
43
+ context: Context<TSpec>;
44
+ logger: Logger;
45
+ }): {
46
+ setup: () => Promise<void>;
47
+ beforeResize: () => Promise<void>;
48
+ beforeSnapshot: () => Promise<void>;
49
+ afterSnapshot: () => Promise<void>;
50
+ teardown: () => Promise<void>;
51
+ };
52
+ type Phase = 'setup' | 'beforeResize' | 'beforeSnapshot' | 'afterSnapshot' | 'teardown';
53
+ /**
54
+ * The entire page-side library — a single self-contained function shipped to the
55
+ * browser by `context.execute`. No closure variables from the TypeScript side are
56
+ * visible inside this body.
57
+ *
58
+ * Contract: on first invocation, installs `window[ns]` with `{setup, beforeResize,
59
+ * beforeSnapshot, afterSnapshot, teardown}` handlers; on every invocation, calls
60
+ * `window[ns][phase]()` if present. The handlers all close over the same set of
61
+ * private helpers and a `WeakMap`-backed per-element state.
62
+ */
63
+ export declare function pagePayload(arg: {
64
+ ns: string;
65
+ phase: Phase;
66
+ }): void;
67
+ export {};