@applitools/spec-driver-browser-extension 1.5.13 → 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 +7 -0
- package/dist/spec-driver.js +70 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
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
|
+
|
|
3
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)
|
|
4
11
|
|
|
5
12
|
|
package/dist/spec-driver.js
CHANGED
|
@@ -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
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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;
|