@applitools/eyes-storybook 3.61.4 → 3.62.1
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 +45 -0
- package/package.json +4 -4
- package/src/eyesStorybook.js +136 -67
- package/src/getStoryData.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.62.1](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.62.0...js/eyes-storybook@3.62.1) (2025-11-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* waitBeforeCapture from story parameter | FLD-3569 ([#3316](https://github.com/Applitools-Dev/sdk/issues/3316)) ([76f3c5a](https://github.com/Applitools-Dev/sdk/commit/76f3c5a3e52e2d031ad90a0ffab4991a33dce8e5))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* @applitools/dom-snapshot bumped to 4.14.0
|
|
14
|
+
#### Features
|
|
15
|
+
|
|
16
|
+
* logging errors from dom snapshot to the backend | AD-11641 ([#3291](https://github.com/Applitools-Dev/sdk/issues/3291)) ([7f5b487](https://github.com/Applitools-Dev/sdk/commit/7f5b48701ff93bf980924c9346a8241ed87f5a56))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* sandbox prototype pollution | FLD-3738 ([#3310](https://github.com/Applitools-Dev/sdk/issues/3310)) ([3185558](https://github.com/Applitools-Dev/sdk/commit/31855586851d5372169aae7bf0268cec139abc59))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
#### Code Refactoring
|
|
25
|
+
|
|
26
|
+
* blob generation error handling ([#2501](https://github.com/Applitools-Dev/sdk/issues/2501)) ([94bc14f](https://github.com/Applitools-Dev/sdk/commit/94bc14faf3de0fd9a8ca24af4870f839756a8aad))
|
|
27
|
+
* @applitools/ufg-client bumped to 1.18.0
|
|
28
|
+
#### Features
|
|
29
|
+
|
|
30
|
+
* logging errors from dom snapshot to the backend | AD-11641 ([#3291](https://github.com/Applitools-Dev/sdk/issues/3291)) ([7f5b487](https://github.com/Applitools-Dev/sdk/commit/7f5b48701ff93bf980924c9346a8241ed87f5a56))
|
|
31
|
+
* @applitools/core bumped to 4.51.0
|
|
32
|
+
#### Features
|
|
33
|
+
|
|
34
|
+
* logging errors from dom snapshot to the backend | AD-11641 ([#3291](https://github.com/Applitools-Dev/sdk/issues/3291)) ([7f5b487](https://github.com/Applitools-Dev/sdk/commit/7f5b48701ff93bf980924c9346a8241ed87f5a56))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
* @applitools/eyes bumped to 1.36.14
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## [3.62.0](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.61.4...js/eyes-storybook@3.62.0) (2025-10-27)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Features
|
|
45
|
+
|
|
46
|
+
* add a retry mechanism for inconsistent timeouts | AD-11191 ([#3305](https://github.com/Applitools-Dev/sdk/issues/3305)) ([2871d59](https://github.com/Applitools-Dev/sdk/commit/2871d59e41eeb4f078a6ec2e3e717b82b3f064bd))
|
|
47
|
+
|
|
3
48
|
## [3.61.4](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.61.3...js/eyes-storybook@3.61.4) (2025-10-22)
|
|
4
49
|
|
|
5
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/eyes-storybook",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.62.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"applitools",
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"up:framework": "cd test/fixtures/storybook-versions/${APPLITOOLS_FRAMEWORK_VERSION} && npm ci"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@applitools/core": "4.
|
|
62
|
+
"@applitools/core": "4.51.0",
|
|
63
63
|
"@applitools/driver": "1.24.0",
|
|
64
|
-
"@applitools/eyes": "1.36.
|
|
64
|
+
"@applitools/eyes": "1.36.14",
|
|
65
65
|
"@applitools/functional-commons": "1.6.0",
|
|
66
66
|
"@applitools/logger": "2.2.4",
|
|
67
67
|
"@applitools/monitoring-commons": "1.0.19",
|
|
68
68
|
"@applitools/spec-driver-puppeteer": "1.6.6",
|
|
69
|
-
"@applitools/ufg-client": "1.
|
|
69
|
+
"@applitools/ufg-client": "1.18.0",
|
|
70
70
|
"@applitools/utils": "1.12.0",
|
|
71
71
|
"@inquirer/prompts": "7.0.1",
|
|
72
72
|
"boxen": "4.2.0",
|
package/src/eyesStorybook.js
CHANGED
|
@@ -25,6 +25,7 @@ const {extractEnvironment} = require('./extractEnvironment');
|
|
|
25
25
|
const {makeCore} = require('@applitools/core');
|
|
26
26
|
const makeGetStoriesWithConfig = require('./getStoriesWithConfig');
|
|
27
27
|
const {makeNetworkUtils} = require('./utils/pageNetworkUtils');
|
|
28
|
+
const {readStoriesTimeout: defaultReadStoriesTimeout} = require('./defaultConfig');
|
|
28
29
|
|
|
29
30
|
async function eyesStorybook({
|
|
30
31
|
config,
|
|
@@ -48,6 +49,8 @@ async function eyesStorybook({
|
|
|
48
49
|
logger.log(`Running with ${CONCURRENT_TABS} concurrent tabs`);
|
|
49
50
|
|
|
50
51
|
const {storybookUrl, readStoriesTimeout, reloadPagePerStory, navigationWaitUntil} = config;
|
|
52
|
+
const enableTimeoutRetryMechanism = readStoriesTimeout === defaultReadStoriesTimeout;
|
|
53
|
+
const timeoutRetry = 30_000;
|
|
51
54
|
|
|
52
55
|
let iframeUrl;
|
|
53
56
|
try {
|
|
@@ -60,23 +63,6 @@ async function eyesStorybook({
|
|
|
60
63
|
process.env.PUPPETEER_DISABLE_HEADLESS_WARNING = true;
|
|
61
64
|
const browser = await puppeteer.launch(config.puppeteerOptions);
|
|
62
65
|
logger.log('browser launched');
|
|
63
|
-
const page = await browser.newPage();
|
|
64
|
-
|
|
65
|
-
const {startInterception} = makeNetworkUtils({
|
|
66
|
-
page,
|
|
67
|
-
logger,
|
|
68
|
-
});
|
|
69
|
-
// we send http headers here and in init page
|
|
70
|
-
if (config.puppeteerExtraHTTPHeaders) {
|
|
71
|
-
await page.setExtraHTTPHeaders(config.puppeteerExtraHTTPHeaders);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
await startInterception({
|
|
75
|
-
timeout: config.browserRequestsTimeout,
|
|
76
|
-
blockPatterns: config.networkBlockPatterns,
|
|
77
|
-
browserHeadersOverride: config.browserHeadersOverride,
|
|
78
|
-
cache: config.browserCacheRequests,
|
|
79
|
-
});
|
|
80
66
|
|
|
81
67
|
const environment = extractEnvironment(addonVersion);
|
|
82
68
|
const core = await makeCore({spec, agentId: config.agentId, environment, logger});
|
|
@@ -113,6 +99,8 @@ async function eyesStorybook({
|
|
|
113
99
|
});
|
|
114
100
|
const pagePool = createPagePool({initPage, logger});
|
|
115
101
|
|
|
102
|
+
const {stories, page} = await getStoriesWithSpinner();
|
|
103
|
+
|
|
116
104
|
const doTakeDomSnapshots = async ({page, ...settings}) => {
|
|
117
105
|
const driver = await new Driver({spec, driver: page, logger});
|
|
118
106
|
return await core.takeSnapshots({
|
|
@@ -131,8 +119,6 @@ async function eyesStorybook({
|
|
|
131
119
|
},
|
|
132
120
|
});
|
|
133
121
|
try {
|
|
134
|
-
const stories = await getStoriesWithSpinner();
|
|
135
|
-
|
|
136
122
|
const filteredStories = filterStories({stories, config});
|
|
137
123
|
|
|
138
124
|
// Log filtering and sharding results
|
|
@@ -239,63 +225,146 @@ async function eyesStorybook({
|
|
|
239
225
|
await browser.close();
|
|
240
226
|
}
|
|
241
227
|
|
|
242
|
-
async function
|
|
243
|
-
|
|
244
|
-
page
|
|
245
|
-
hasConsoleErr =
|
|
246
|
-
msg.args()[0] &&
|
|
247
|
-
msg.args()[0]._remoteObject &&
|
|
248
|
-
msg.args()[0]._remoteObject.subtype === 'error';
|
|
249
|
-
});
|
|
228
|
+
async function createContext({browser, config, logger}) {
|
|
229
|
+
const context = await browser.createBrowserContext();
|
|
230
|
+
const page = await context.newPage();
|
|
250
231
|
|
|
251
|
-
logger.log('
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
page.
|
|
257
|
-
);
|
|
258
|
-
if (navigateErr) {
|
|
259
|
-
logger.log('Error when loading storybook', navigateErr);
|
|
260
|
-
const failMsg = refineErrorMessage({
|
|
261
|
-
prefix: 'Error when loading storybook.',
|
|
262
|
-
error: navigateErr,
|
|
263
|
-
});
|
|
264
|
-
spinner.fail(failMsg);
|
|
265
|
-
throw new Error();
|
|
232
|
+
logger.log('A new context and page created');
|
|
233
|
+
|
|
234
|
+
// Set up interception and headers, but do not navigate
|
|
235
|
+
const {startInterception} = makeNetworkUtils({page, logger});
|
|
236
|
+
if (config.puppeteerExtraHTTPHeaders) {
|
|
237
|
+
await page.setExtraHTTPHeaders(config.puppeteerExtraHTTPHeaders);
|
|
266
238
|
}
|
|
239
|
+
await startInterception({
|
|
240
|
+
timeout: config.browserRequestsTimeout,
|
|
241
|
+
blockPatterns: config.networkBlockPatterns,
|
|
242
|
+
browserHeadersOverride: config.browserHeadersOverride,
|
|
243
|
+
cache: config.browserCacheRequests,
|
|
244
|
+
});
|
|
267
245
|
|
|
268
|
-
|
|
246
|
+
return {context, page};
|
|
247
|
+
}
|
|
269
248
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
249
|
+
async function getStoriesWithSpinner() {
|
|
250
|
+
let firstAttemptContext;
|
|
251
|
+
let secondAttemptContext;
|
|
252
|
+
async function attemptNavigate(timeout, waitUntil, isSecondAttempt = false) {
|
|
253
|
+
const contextObj = await createContext({browser, config, logger});
|
|
254
|
+
if (isSecondAttempt) secondAttemptContext = contextObj;
|
|
255
|
+
else firstAttemptContext = contextObj;
|
|
256
|
+
const page = contextObj.page;
|
|
257
|
+
logger.log(
|
|
258
|
+
'Attempting navigation to storybook url:',
|
|
259
|
+
storybookUrl,
|
|
260
|
+
'timeout:',
|
|
261
|
+
timeout,
|
|
262
|
+
'waitUntil:',
|
|
263
|
+
waitUntil,
|
|
264
|
+
);
|
|
265
|
+
const [navigateErr] = await presult(page.goto(storybookUrl, {timeout, waitUntil}));
|
|
266
|
+
if (navigateErr) {
|
|
267
|
+
logger.log('Error when loading storybook', navigateErr);
|
|
268
|
+
return {ok: false, error: navigateErr, page};
|
|
269
|
+
}
|
|
270
|
+
return {ok: true, page};
|
|
278
271
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
272
|
+
const spinner = ora({text: 'Reading stories', stream: outputStream});
|
|
273
|
+
spinner.start();
|
|
274
|
+
// Start first attempt immediately with original timeout/waitUntil
|
|
275
|
+
const firstAttempt = attemptNavigate(readStoriesTimeout, navigationWaitUntil);
|
|
276
|
+
logger.log('Started first navigation attempt');
|
|
277
|
+
// Start second attempt after 30 seconds with 2x timeout
|
|
278
|
+
const secondAttempt = enableTimeoutRetryMechanism
|
|
279
|
+
? new Promise(resolve => {
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
attemptNavigate(readStoriesTimeout * 2, navigationWaitUntil, true).then(resolve);
|
|
282
|
+
}, timeoutRetry);
|
|
283
|
+
})
|
|
284
|
+
: Promise.resolve({ok: false});
|
|
285
|
+
|
|
286
|
+
// Helper for reading stories after successful navigation
|
|
287
|
+
async function handleReadStories(page) {
|
|
288
|
+
let hasConsoleErr;
|
|
289
|
+
page.on('console', msg => {
|
|
290
|
+
hasConsoleErr =
|
|
291
|
+
msg.args()[0] &&
|
|
292
|
+
msg.args()[0]._remoteObject &&
|
|
293
|
+
msg.args()[0]._remoteObject.subtype === 'error';
|
|
294
|
+
});
|
|
295
|
+
logger.log('Getting stories from storybook');
|
|
296
|
+
|
|
297
|
+
const [getStoriesErr, stories] = await presult(readStoriesWithRetry(page));
|
|
298
|
+
if (getStoriesErr) {
|
|
299
|
+
logger.log('Error when reading stories:', getStoriesErr);
|
|
300
|
+
const failMsg = refineErrorMessage({
|
|
301
|
+
prefix: 'Error when reading stories:',
|
|
302
|
+
error: getStoriesErr,
|
|
303
|
+
});
|
|
304
|
+
spinner.fail(failMsg);
|
|
305
|
+
throw new Error();
|
|
306
|
+
}
|
|
307
|
+
if (!stories.length && hasConsoleErr) {
|
|
308
|
+
throw new Error(
|
|
283
309
|
'Could not load stories, make sure your storybook renders correctly. Perhaps no stories were rendered?',
|
|
284
|
-
)
|
|
285
|
-
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const badParamsError = stories
|
|
313
|
+
.map(s => s.error)
|
|
314
|
+
.filter(Boolean)
|
|
315
|
+
.join('\n');
|
|
316
|
+
if (badParamsError) {
|
|
317
|
+
console.log(chalk.red(`\n${badParamsError}`));
|
|
318
|
+
}
|
|
319
|
+
spinner.succeed();
|
|
320
|
+
logger.log(`got ${stories.length} stories:`, JSON.stringify(stories));
|
|
321
|
+
return {stories, page};
|
|
286
322
|
}
|
|
287
323
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
324
|
+
async function cleanupContext(winnerPage) {
|
|
325
|
+
if (winnerPage === firstAttemptContext?.page && secondAttemptContext) {
|
|
326
|
+
try {
|
|
327
|
+
await secondAttemptContext.page.close();
|
|
328
|
+
await secondAttemptContext.context.close();
|
|
329
|
+
logger.log('Closed second attempt context/page after first attempt succeeded');
|
|
330
|
+
} catch (err) {
|
|
331
|
+
logger.log('Error closing second attempt context/page:', err);
|
|
332
|
+
}
|
|
333
|
+
} else if (winnerPage === secondAttemptContext?.page && firstAttemptContext) {
|
|
334
|
+
try {
|
|
335
|
+
await firstAttemptContext.page.close();
|
|
336
|
+
await firstAttemptContext.context.close();
|
|
337
|
+
logger.log('Closed first attempt context/page after second attempt succeeded');
|
|
338
|
+
} catch (err) {
|
|
339
|
+
logger.log('Error closing first attempt context/page:', err);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
294
342
|
}
|
|
295
343
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
344
|
+
// Wait for the first successful navigation
|
|
345
|
+
return Promise.race([firstAttempt, secondAttempt]).then(async result => {
|
|
346
|
+
if (result.ok) {
|
|
347
|
+
await cleanupContext(result.page);
|
|
348
|
+
return handleReadStories(result.page);
|
|
349
|
+
}
|
|
350
|
+
// If first finished with error, wait for the other
|
|
351
|
+
return Promise.all([firstAttempt, secondAttempt]).then(async results => {
|
|
352
|
+
const success = results.find(r => r.ok);
|
|
353
|
+
if (success) {
|
|
354
|
+
await cleanupContext(success.page);
|
|
355
|
+
return handleReadStories(success.page);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const lastError = results[1].error || results[0].error;
|
|
359
|
+
logger.log('Error when loading storybook', lastError);
|
|
360
|
+
const failMsg = refineErrorMessage({
|
|
361
|
+
prefix: 'Error when loading storybook.',
|
|
362
|
+
error: lastError,
|
|
363
|
+
});
|
|
364
|
+
spinner.fail(failMsg);
|
|
365
|
+
throw new Error();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
299
368
|
}
|
|
300
369
|
|
|
301
370
|
function getRenderIE() {
|
|
@@ -314,7 +383,7 @@ async function eyesStorybook({
|
|
|
314
383
|
return transitioning;
|
|
315
384
|
}
|
|
316
385
|
|
|
317
|
-
async function readStoriesWithRetry() {
|
|
386
|
+
async function readStoriesWithRetry(page) {
|
|
318
387
|
return executeWithRetry(
|
|
319
388
|
_readStoriesWithRetry,
|
|
320
389
|
{
|
package/src/getStoryData.js
CHANGED
|
@@ -46,7 +46,7 @@ function makeGetStoryData({logger, takeDomSnapshots, reloadPagePerStory, navigat
|
|
|
46
46
|
await renderStoryLegacy();
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const wait = story.config
|
|
49
|
+
const wait = eyesParameters?.waitBeforeCapture ?? story.config?.waitBeforeCapture;
|
|
50
50
|
if (typeof wait === 'number') {
|
|
51
51
|
utils.guard.isGreaterThenOrEqual(wait, 0, {name: 'waitBeforeCapture'});
|
|
52
52
|
}
|