@applitools/spec-driver-browser-extension 1.5.12 → 1.5.14

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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.14](https://github.com/Applitools-Dev/sdk/compare/js/spec-driver-browser-extension@1.5.13...js/spec-driver-browser-extension@1.5.14) (2026-10-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * wait for a paint before captureVisibleTab | [#4280](https://github.com/Applitools-Dev/sdk/issues/4280) ([#4282](https://github.com/Applitools-Dev/sdk/issues/4282)) ([f4e3459](https://github.com/Applitools-Dev/sdk/commit/f4e34596da89adc12a5683cf2674ab350f262b71))
9
+
10
+ ## [1.5.13](https://github.com/Applitools-Dev/sdk/compare/js/spec-driver-browser-extension@1.5.12...js/spec-driver-browser-extension@1.5.13) (2026-10-13)
11
+
12
+
13
+ ### Dependencies
14
+
15
+ * @applitools/utils bumped to 1.15.3
16
+ #### Bug Fixes
17
+
18
+ * publish once to validate the npm trusted publishers ([#4300](https://github.com/Applitools-Dev/sdk/issues/4300)) ([63ebc90](https://github.com/Applitools-Dev/sdk/commit/63ebc9044b150c6f927f626d0ce9cc7da8861441))
19
+ * @applitools/logger bumped to 2.3.2
20
+ #### Bug Fixes
21
+
22
+ * publish once to validate the npm trusted publishers ([#4300](https://github.com/Applitools-Dev/sdk/issues/4300)) ([63ebc90](https://github.com/Applitools-Dev/sdk/commit/63ebc9044b150c6f927f626d0ce9cc7da8861441))
23
+
24
+
25
+
26
+ * @applitools/dom-shared bumped to 1.2.4
27
+ #### Bug Fixes
28
+
29
+ * publish once to validate the npm trusted publishers ([#4300](https://github.com/Applitools-Dev/sdk/issues/4300)) ([63ebc90](https://github.com/Applitools-Dev/sdk/commit/63ebc9044b150c6f927f626d0ce9cc7da8861441))
30
+ * @applitools/snippets bumped to 2.9.9
31
+
32
+ * @applitools/driver bumped to 1.26.13
33
+
34
+ * @applitools/spec-driver-playwright bumped to 1.9.13
35
+
36
+
3
37
  ## [1.5.12](https://github.com/Applitools-Dev/sdk/compare/js/spec-driver-browser-extension@1.5.11...js/spec-driver-browser-extension@1.5.12) (2026-10-12)
4
38
 
5
39
 
@@ -78,6 +78,7 @@ function throwInjectionError(error) {
78
78
  throw err;
79
79
  }
80
80
  async function executeScript(context, script, arg) {
81
+ touchFrame(context.tabId, context.frameId);
81
82
  // Dispatch a stamped snippet by id through the page registry: executeScript structured-clones args
82
83
  // (so the content-script deref reviver never fires), and the page returns a {status, value|error}
83
84
  // envelope because MV3 nulls in-page throws.
@@ -270,12 +271,76 @@ async function getCookies(_driver) {
270
271
  });
271
272
  }
272
273
  exports.getCookies = getCookies;
274
+ const PAINT_TIMEOUT_MS = 1000;
275
+ // Chrome allows at most 2 captureVisibleTab calls per second (MAX_CAPTURE_VISIBLE_TAB_CALLS_PER_SECOND).
276
+ const MIN_CAPTURE_INTERVAL_MS = 500;
277
+ // Frames where the driver ran a script since the last capture of the tab. A scroll always runs as
278
+ // a script in the frame it scrolls, so these are the frames that can have a pending paint.
279
+ const touchedFrames = new Map();
280
+ let forgetClosedTabs = false;
281
+ function touchFrame(tabId, frameId) {
282
+ if (!forgetClosedTabs) {
283
+ webextension_polyfill_1.default.tabs.onRemoved.addListener(closedTabId => touchedFrames.delete(closedTabId));
284
+ forgetClosedTabs = true;
285
+ }
286
+ let frames = touchedFrames.get(tabId);
287
+ if (!frames)
288
+ touchedFrames.set(tabId, (frames = new Set()));
289
+ frames.add(frameId || 0);
290
+ }
291
+ // captureVisibleTab returns the last frame the compositor produced. After a scroll or a tab switch
292
+ // that frame can still show the old state. So the top frame and every touched frame read their
293
+ // scroll position and layout, which applies a pending scroll, and then wait for two animation
294
+ // frames. This makes a stale frame less likely, but the browser does not promise it. Other frames
295
+ // are not waited for: a hidden or offscreen cross-origin frame runs no animation frames and would
296
+ // always hit the timeout. A frame that is gone or does not allow injection is skipped. The
297
+ // timeout inside the page does not help when the injection itself does not return, so the whole
298
+ // wait also stops after PAINT_TIMEOUT_MS + 500 ms.
299
+ async function waitForPaint(driver) {
300
+ var _a;
301
+ const frameIds = new Set([0, ...((_a = touchedFrames.get(driver.tabId)) !== null && _a !== void 0 ? _a : [])]);
302
+ touchedFrames.delete(driver.tabId);
303
+ let stop = () => { };
304
+ const timeout = new Promise(resolve => (stop = resolve));
305
+ const timer = setTimeout(() => stop(), PAINT_TIMEOUT_MS + 500);
306
+ const waits = Promise.allSettled(Array.from(frameIds, frameId => webextension_polyfill_1.default.scripting.executeScript({
307
+ target: { tabId: driver.tabId, frameIds: [frameId] },
308
+ func: (timeout) => new Promise(resolve => {
309
+ var _a;
310
+ const scrollingElement = (_a = document.scrollingElement) !== null && _a !== void 0 ? _a : document.documentElement;
311
+ void scrollingElement.scrollTop;
312
+ void scrollingElement.getBoundingClientRect();
313
+ const timer = setTimeout(resolve, timeout);
314
+ requestAnimationFrame(() => requestAnimationFrame(() => {
315
+ clearTimeout(timer);
316
+ resolve();
317
+ }));
318
+ }),
319
+ args: [PAINT_TIMEOUT_MS],
320
+ })));
321
+ await Promise.race([waits, timeout]);
322
+ clearTimeout(timer);
323
+ }
324
+ // Captures run one at a time, also across tabs, because the limit is not per tab. A capture
325
+ // waits only for what is left of MIN_CAPTURE_INTERVAL_MS since the previous one.
326
+ let lastCaptureAt = 0;
327
+ let captureQueue = Promise.resolve();
273
328
  async function takeScreenshot(driver) {
274
- const [activeTab] = await webextension_polyfill_1.default.tabs.query({ windowId: driver.windowId, active: true });
275
- await webextension_polyfill_1.default.tabs.update(driver.tabId, { active: true });
276
- const url = await webextension_polyfill_1.default.tabs.captureVisibleTab(driver.windowId, { format: 'png' });
277
- await webextension_polyfill_1.default.tabs.update(activeTab.id, { active: true });
278
- await utils.general.sleep(500);
329
+ const capture = captureQueue.then(async () => {
330
+ const [activeTab] = await webextension_polyfill_1.default.tabs.query({ windowId: driver.windowId, active: true });
331
+ await webextension_polyfill_1.default.tabs.update(driver.tabId, { active: true });
332
+ const gate = utils.general.sleep(Math.max(0, lastCaptureAt + MIN_CAPTURE_INTERVAL_MS - Date.now()));
333
+ await Promise.all([waitForPaint(driver), gate]);
334
+ try {
335
+ return await webextension_polyfill_1.default.tabs.captureVisibleTab(driver.windowId, { format: 'png' });
336
+ }
337
+ finally {
338
+ lastCaptureAt = Date.now();
339
+ await webextension_polyfill_1.default.tabs.update(activeTab.id, { active: true });
340
+ }
341
+ });
342
+ captureQueue = capture.catch(() => undefined);
343
+ const url = await capture;
279
344
  return url.replace(/^data:image\/png;base64,/, '');
280
345
  }
281
346
  exports.takeScreenshot = takeScreenshot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/spec-driver-browser-extension",
3
- "version": "1.5.12",
3
+ "version": "1.5.14",
4
4
  "keywords": [
5
5
  "playwright",
6
6
  "chrome devtools protocol",
@@ -32,14 +32,14 @@
32
32
  "playwright:setup": "yarn playwright install --with-deps chromium"
33
33
  },
34
34
  "dependencies": {
35
- "@applitools/driver": "1.26.12",
36
- "@applitools/utils": "1.15.2",
35
+ "@applitools/driver": "1.26.13",
36
+ "@applitools/utils": "1.15.3",
37
37
  "webextension-polyfill": "0.10.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@applitools/api-extractor": "^1.2.22",
41
- "@applitools/logger": "^2.3.1",
42
- "@applitools/spec-driver-playwright": "^1.9.12",
41
+ "@applitools/logger": "^2.3.2",
42
+ "@applitools/spec-driver-playwright": "^1.9.13",
43
43
  "@applitools/test-utils": "^1.5.17",
44
44
  "@babel/core": "^7.26.0",
45
45
  "@babel/preset-env": "^7.22.5",