@applitools/eyes-storybook 3.60.0 → 3.61.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 CHANGED
@@ -1,5 +1,79 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.61.1](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.61.0...js/eyes-storybook@3.61.1) (2025-10-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * various config fixes for storybook-addon ([#3257](https://github.com/Applitools-Dev/sdk/issues/3257)) ([86fd4d1](https://github.com/Applitools-Dev/sdk/commit/86fd4d114122bbaf675b5fa361b0e967595ca296))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * @applitools/dom-snapshot bumped to 4.13.9
14
+ #### Bug Fixes
15
+
16
+ * verbose Unknown CSS object model logging | AD-11542 | FLD-3687 ([#3261](https://github.com/Applitools-Dev/sdk/issues/3261)) ([ba85d32](https://github.com/Applitools-Dev/sdk/commit/ba85d3287a81af109db1a7b407e5ead20f395d9f))
17
+ * @applitools/core-base bumped to 1.28.2
18
+ #### Bug Fixes
19
+
20
+ * various config fixes for storybook-addon ([#3257](https://github.com/Applitools-Dev/sdk/issues/3257)) ([86fd4d1](https://github.com/Applitools-Dev/sdk/commit/86fd4d114122bbaf675b5fa361b0e967595ca296))
21
+ * @applitools/eyes bumped to 1.36.10
22
+ #### Bug Fixes
23
+
24
+ * various config fixes for storybook-addon ([#3257](https://github.com/Applitools-Dev/sdk/issues/3257)) ([86fd4d1](https://github.com/Applitools-Dev/sdk/commit/86fd4d114122bbaf675b5fa361b0e967595ca296))
25
+
26
+
27
+
28
+ * @applitools/nml-client bumped to 1.11.8
29
+
30
+ * @applitools/ec-client bumped to 1.12.10
31
+
32
+ * @applitools/core bumped to 4.50.1
33
+ #### Bug Fixes
34
+
35
+ * various config fixes for storybook-addon ([#3257](https://github.com/Applitools-Dev/sdk/issues/3257)) ([86fd4d1](https://github.com/Applitools-Dev/sdk/commit/86fd4d114122bbaf675b5fa361b0e967595ca296))
36
+
37
+
38
+
39
+
40
+ ## [3.61.0](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.60.0...js/eyes-storybook@3.61.0) (2025-10-01)
41
+
42
+
43
+ ### Features
44
+
45
+ * storybook addon ([#3104](https://github.com/Applitools-Dev/sdk/issues/3104)) ([16e09cb](https://github.com/Applitools-Dev/sdk/commit/16e09cba8928c3a24b9e0d9d41e0936fbaec2773))
46
+
47
+
48
+ ### Dependencies
49
+
50
+ * @applitools/screenshoter bumped to 3.12.6
51
+ #### Bug Fixes
52
+
53
+ * wait after scroll | FLD-3594 ([#3252](https://github.com/Applitools-Dev/sdk/issues/3252)) ([e452422](https://github.com/Applitools-Dev/sdk/commit/e4524229b64e40d9b9596a92bfa94daf5824286a))
54
+ * @applitools/core-base bumped to 1.28.1
55
+ #### Bug Fixes
56
+
57
+ * unexpected concurrency values from server | AD-11465 ([#3248](https://github.com/Applitools-Dev/sdk/issues/3248)) ([0dd28c7](https://github.com/Applitools-Dev/sdk/commit/0dd28c7b297d5ad3aabc6b87e427e3e09a993825))
58
+ * @applitools/nml-client bumped to 1.11.7
59
+
60
+ * @applitools/ec-client bumped to 1.12.9
61
+
62
+ * @applitools/core bumped to 4.49.0
63
+ #### Features
64
+
65
+ * storybook addon ([#3104](https://github.com/Applitools-Dev/sdk/issues/3104)) ([16e09cb](https://github.com/Applitools-Dev/sdk/commit/16e09cba8928c3a24b9e0d9d41e0936fbaec2773))
66
+
67
+
68
+ #### Bug Fixes
69
+
70
+ * duplicate concurrency warnings ([#3255](https://github.com/Applitools-Dev/sdk/issues/3255)) ([ef2f94a](https://github.com/Applitools-Dev/sdk/commit/ef2f94ab4137c78396583f166344285beeb49be7))
71
+
72
+
73
+
74
+ * @applitools/eyes bumped to 1.36.9
75
+
76
+
3
77
  ## [3.60.0](https://github.com/Applitools-Dev/sdk/compare/js/eyes-storybook@3.59.1...js/eyes-storybook@3.60.0) (2025-09-22)
4
78
 
5
79
 
package/dist/index.d.ts CHANGED
@@ -98,6 +98,21 @@ export type ApplitoolsConfig = Omit<ConfigurationPlain, irrelevantToStorybook |
98
98
  * @default false
99
99
  */
100
100
  browserCacheRequests?: boolean;
101
+ /**
102
+ * Headers to override in all browser requests.
103
+ * @default undefined
104
+ */
105
+ browserHeadersOverride?: Record<string, string>;
106
+ /**
107
+ * Timeout in milliseconds for all browser requests.
108
+ * @default undefined
109
+ */
110
+ browserRequestsTimeout?: number;
111
+ /**
112
+ * Array of URL patterns to block requests to.
113
+ * @default undefined
114
+ */
115
+ networkBlockPatterns?: string[];
101
116
  /**
102
117
  * Array of browser configurations for screenshot generation.
103
118
  * Defines size and browser type for generated screenshots.
@@ -241,6 +256,13 @@ export type ApplitoolsConfig = Omit<ConfigurationPlain, irrelevantToStorybook |
241
256
  * @default undefined
242
257
  */
243
258
  shard?: string;
259
+ /**
260
+ * The size of the Puppeteer browser's window.
261
+ * This is the browser window which renders the stories originally (and opens at the size provided in the `viewportSize` parameter), and then a DOM snapshot is uploaded to the server, which renders this snapshot on all the browsers and sizes provided in the browser parameter.
262
+ *
263
+ * Note: Stories will **not** be rendered and tested on this viewport size, unless you also include it in the `browser` parameter.
264
+ */
265
+ viewportSize?: ConfigurationPlain['viewportSize'];
244
266
  };
245
267
  export type configKeys = keyof ApplitoolsConfig;
246
268
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/eyes-storybook",
3
- "version": "3.60.0",
3
+ "version": "3.61.1",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "applitools",
@@ -19,6 +19,7 @@
19
19
  "eyes-setup": "./bin/eyes-setup.js",
20
20
  "eyes-storybook": "./bin/eyes-storybook.js"
21
21
  },
22
+ "main": "./src/main.js",
22
23
  "files": [
23
24
  "src",
24
25
  "bin",
@@ -58,9 +59,9 @@
58
59
  "up:framework": "cd test/fixtures/storybook-versions/${APPLITOOLS_FRAMEWORK_VERSION} && npm ci"
59
60
  },
60
61
  "dependencies": {
61
- "@applitools/core": "4.48.0",
62
+ "@applitools/core": "4.50.1",
62
63
  "@applitools/driver": "1.23.5",
63
- "@applitools/eyes": "1.36.8",
64
+ "@applitools/eyes": "1.36.10",
64
65
  "@applitools/functional-commons": "1.6.0",
65
66
  "@applitools/logger": "2.2.4",
66
67
  "@applitools/monitoring-commons": "1.0.19",
package/src/cli.js CHANGED
@@ -1,22 +1,18 @@
1
1
  'use strict';
2
2
  const yargs = require('yargs');
3
- const {makeLogger} = require('@applitools/logger');
4
- const {configParams: externalConfigParams} = require('./configParams');
5
3
  const VERSION = require('../package.json').version;
6
- const eyesStorybook = require('./eyesStorybook');
7
4
  const processResults = require('./processResults');
8
- const validateAndPopulateConfig = require('./validateAndPopulateConfig');
9
5
  const yargsOptions = require('./yargsOptions');
10
- const {generateConfig} = require('./generateConfig');
11
- const defaultConfig = require('./defaultConfig');
12
- const configDigest = require('./configDigest');
13
6
  const {makeTiming} = require('@applitools/monitoring-commons');
14
7
  const handleJsonFile = require('./handleJsonFile');
15
8
  const handleTapFile = require('./handleTapFile');
16
9
  const handleXmlFile = require('./handleXmlFile');
10
+ const {getConfigAndLogger} = require('./getConfigAndLogger');
17
11
  const {presult} = require('@applitools/functional-commons');
18
12
  const chalk = require('chalk');
19
13
  const utils = require('@applitools/utils');
14
+ const {EyesError} = require('@applitools/eyes');
15
+ const eyesStorybook = require('./eyesStorybook');
20
16
  const {performance, timeItAsync} = makeTiming();
21
17
 
22
18
  (async function () {
@@ -31,7 +27,7 @@ const {performance, timeItAsync} = makeTiming();
31
27
  .options(yargsOptions).argv;
32
28
 
33
29
  console.log(`Using @applitools/eyes-storybook version ${VERSION}.\n`);
34
- const config = generateConfig({argv, defaultConfig, externalConfigParams});
30
+ const {config, logger} = await getConfigAndLogger(argv);
35
31
 
36
32
  if (config.shard) {
37
33
  console.log(`Running with shard: ${config.shard.current}/${config.shard.total}`);
@@ -41,13 +37,6 @@ const {performance, timeItAsync} = makeTiming();
41
37
  }
42
38
  }
43
39
 
44
- const logger = makeLogger({level: config.showLogs ? 'info' : 'silent', label: 'eyes'});
45
- await validateAndPopulateConfig({
46
- config,
47
- logger,
48
- packagePath: process.cwd(),
49
- });
50
- logger.log(`Running with the following config:\n${configDigest(config)}`);
51
40
  const [err, results] = await presult(
52
41
  timeItAsync('eyesStorybook', () => eyesStorybook({config, logger, performance, timeItAsync})),
53
42
  );
@@ -78,7 +67,11 @@ const {performance, timeItAsync} = makeTiming();
78
67
  process.exit(exitCode);
79
68
  }
80
69
  } catch (ex) {
81
- console.log(ex);
70
+ if (utils.types.instanceOf(ex, EyesError)) {
71
+ console.log(ex.message);
72
+ } else {
73
+ console.log(ex);
74
+ }
82
75
  process.exit(1);
83
76
  }
84
77
  })();
@@ -30,10 +30,8 @@ const configParams = [
30
30
  'autProxy',
31
31
  'saveDiffs',
32
32
  'saveFailedTests',
33
- 'saveNewTests',
34
33
  'compareWithParentBranch',
35
34
  'ignoreBaseline',
36
- 'serverUrl',
37
35
  'concurrency',
38
36
  'testConcurrency',
39
37
  'useDom',
@@ -44,6 +42,16 @@ const configParams = [
44
42
  'dontCloseBatches',
45
43
  'showBrowserLogs',
46
44
  'shard',
45
+ 'storybookUrl',
46
+ 'browserCacheRequests',
47
+ 'browserHeadersOverride',
48
+ 'browserRequestsTimeout',
49
+ 'networkBlockPatterns',
50
+ 'navigationWaitUntil',
51
+ 'include',
52
+ 'xmlFilePath',
53
+ 'tapFilePath',
54
+ 'storybookStaticDir',
47
55
  ];
48
56
 
49
57
  module.exports = {configParams};
@@ -5,25 +5,16 @@ module.exports = {
5
5
  storybookPort: 9000,
6
6
  storybookHost: 'localhost',
7
7
  storybookConfigDir: '.storybook',
8
- storybookUrl: undefined,
9
- storybookStaticDir: undefined,
10
8
  showStorybookOutput: false,
11
9
  waitBeforeScreenshot: 50, // backward compatibility
12
10
  waitBeforeScreenshots: 50, // backward compatibility
13
11
  waitBeforeCapture: 50,
14
12
  viewportSize: {width: 1024, height: 768},
15
- tapFilePath: undefined,
16
- xmlFilePath: undefined,
17
13
  exitcode: true,
18
14
  readStoriesTimeout: 60000,
19
15
  reloadPagePerStory: false,
20
- include: undefined,
21
16
  startStorybookServerTimeout: 300,
22
- navigationWaitUntil: undefined,
23
- networkBlockPatterns: undefined,
24
- browserRequestsTimeout: undefined,
25
- browserHeadersOverride: undefined,
26
- browserCacheRequests: undefined,
27
- showBrowserLogs: undefined,
28
- shard: undefined,
17
+ serverUrl: 'https://eyes.applitools.com',
18
+ saveNewTests: true,
19
+ fully: true,
29
20
  };
@@ -1,14 +1,27 @@
1
1
  'use strict';
2
+
2
3
  const chalk = require('chalk');
4
+ const {MissingApiKeyError} = require('@applitools/core');
5
+ const {EyesError} = require('@applitools/eyes');
6
+
7
+ class AbortedByUserError extends EyesError {
8
+ reason = 'abortedByUser';
9
+ }
10
+
11
+ class InvalidConfigFileError extends EyesError {
12
+ reason = 'invalidConfigFile';
3
13
 
4
- const missingApiKeyFailMsg = `
5
- ${chalk.red('Environment variable APPLITOOLS_API_KEY is not set.')}
6
- ${chalk.green(`To fix:
7
- 1. Register for Applitools developer account at www.applitools.com/devreg
8
- 2. Get API key from menu
9
- 3. Set APPLITOOLS_API_KEY environment variable
10
- Mac/Linux: export APPLITOOLS_API_KEY=Your_API_Key_Here
11
- Windows: set APPLITOOLS_API_KEY=Your_API_Key_Here`)}`;
14
+ constructor(error) {
15
+ super();
16
+
17
+ const documentationUrl = 'https://applitools.com/tutorials/sdks/storybook/config#properties';
18
+
19
+ this.message =
20
+ `Your configuration file is invalid. Please review our documentation for valid configuration settings: ${documentationUrl}.
21
+ Additionally, you can generate a new configuration by running 'npx eyes-setup'.
22
+ \n\nError details: ${error.message}`.trim();
23
+ }
24
+ }
12
25
 
13
26
  const missingAppNameAndPackageJsonFailMsg = `
14
27
  ${chalk.red(
@@ -47,9 +60,11 @@ function deprecationWarning({deprecatedThing, newThing, isDead}) {
47
60
  }
48
61
 
49
62
  module.exports = {
50
- missingApiKeyFailMsg,
51
63
  missingAppNameAndPackageJsonFailMsg,
52
64
  missingAppNameInPackageJsonFailMsg,
53
65
  refineErrorMessage,
54
66
  deprecationWarning,
67
+ MissingApiKeyError,
68
+ AbortedByUserError,
69
+ InvalidConfigFileError,
55
70
  };
@@ -1,6 +1,6 @@
1
1
  const {getStorybookFrameworks} = require('./utils/frameworks');
2
2
 
3
- function extractEnvironment() {
3
+ function extractEnvironment(addonVersion) {
4
4
  const versions = {};
5
5
  try {
6
6
  const {name, version} = require('storybook/package.json');
@@ -23,6 +23,9 @@ function extractEnvironment() {
23
23
  peerDependencies,
24
24
  );
25
25
  sdk = {lang: 'js', name, currentVersion: version, framework, dependencyFrameworks};
26
+ if (addonVersion) {
27
+ sdk.addonVersion = addonVersion;
28
+ }
26
29
  } catch {
27
30
  // NOTE: ignore error
28
31
  }
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  const puppeteer = require('puppeteer');
3
+ const configDigest = require('./configDigest');
3
4
  const getStories = require('../dist/getStories');
4
5
  const {presult} = require('@applitools/functional-commons');
5
6
  const {executeWithRetry} = require('./utils/executeWithRetry');
@@ -31,7 +32,12 @@ async function eyesStorybook({
31
32
  performance,
32
33
  timeItAsync,
33
34
  outputStream = process.stderr,
35
+ eventEmitter,
36
+ signal = new AbortController().signal,
37
+ addonVersion,
34
38
  }) {
39
+ logger.log(`Running with the following config:\n${configDigest(config)}`);
40
+
35
41
  let renderIE = false;
36
42
  let transitioning = false;
37
43
  logger.log('eyesStorybook started');
@@ -51,7 +57,6 @@ async function eyesStorybook({
51
57
  logger.log(ex);
52
58
  throw new Error(`Storybook URL is not valid: ${storybookUrl}`);
53
59
  }
54
- const agentId = `eyes-storybook/${require('../package.json').version}`;
55
60
  process.env.PUPPETEER_DISABLE_HEADLESS_WARNING = true;
56
61
  const browser = await puppeteer.launch(config.puppeteerOptions);
57
62
  logger.log('browser launched');
@@ -73,8 +78,8 @@ async function eyesStorybook({
73
78
  cache: config.browserCacheRequests,
74
79
  });
75
80
 
76
- const environment = extractEnvironment();
77
- const core = await makeCore({spec, agentId, environment, logger});
81
+ const environment = extractEnvironment(addonVersion);
82
+ const core = await makeCore({spec, agentId: config.agentId, environment, logger});
78
83
  const manager = await core.makeManager({
79
84
  type: 'ufg',
80
85
  settings: {concurrency: config.testConcurrency, useServerConcurrency: true},
@@ -85,24 +90,15 @@ async function eyesStorybook({
85
90
  settings: {
86
91
  eyesServerUrl: config.eyesServerUrl,
87
92
  apiKey: config.apiKey,
88
- agentId,
93
+ agentId: config.agentId,
89
94
  proxy: config.proxy,
90
95
  useDnsCache: config.useDnsCache,
91
96
  },
92
97
  })
93
98
  .catch(async error => {
94
- if (
95
- error &&
96
- error.message &&
97
- error.message.includes('Please check your API key and try again.')
98
- ) {
99
- const failMsg = 'Incorrect API Key';
100
- logger.log(failMsg);
101
- await browser.close();
102
- throw new Error(failMsg);
103
- } else {
104
- throw error;
105
- }
99
+ logger.error(error?.message);
100
+ await browser.close();
101
+ throw error;
106
102
  });
107
103
 
108
104
  const getStoriesWithConfig = makeGetStoriesWithConfig({config});
@@ -182,6 +178,7 @@ async function eyesStorybook({
182
178
  concurrency: account.serverConcurrency.componentConcurrency,
183
179
  appName: config.appName,
184
180
  serverSettings: account.eyesServer,
181
+ signal,
185
182
  });
186
183
 
187
184
  const renderStories = makeRenderStories({
@@ -192,6 +189,8 @@ async function eyesStorybook({
192
189
  logger,
193
190
  stream: outputStream,
194
191
  pagePool,
192
+ eventEmitter,
193
+ signal,
195
194
  });
196
195
 
197
196
  logger.log('finished creating functions');
@@ -209,6 +208,15 @@ async function eyesStorybook({
209
208
  timeItAsync,
210
209
  }),
211
210
  );
211
+ if (signal.aborted) {
212
+ if (error) {
213
+ const msg = refineErrorMessage({prefix: 'Error in executeRenders:', error});
214
+ logger.log('Error in executeRenders:', error);
215
+ throw new Error(msg);
216
+ } else {
217
+ return {results}; // processResults (which is not used in the addon) doesn't support missing the summary. But it's not a real concern right now.
218
+ }
219
+ }
212
220
  const [errorInGetResults, testResultsSummary] = await presult(
213
221
  manager.getResults({throwErr: false}),
214
222
  );
@@ -219,7 +227,7 @@ async function eyesStorybook({
219
227
 
220
228
  if (error) {
221
229
  const msg = refineErrorMessage({prefix: 'Error in executeRenders:', error});
222
- logger.log(error);
230
+ logger.log('Error in executeRenders:', error);
223
231
  throw new Error(msg);
224
232
  } else {
225
233
  return {summary: testResultsSummary, results};
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const eyesStorybookOrig = require('./eyesStorybook');
4
+ const {EventEmitter} = require('node:events');
5
+
6
+ function eyesStorybookEventEmitter({
7
+ eyesStorybook = eyesStorybookOrig,
8
+ config,
9
+ logger,
10
+ performance,
11
+ timeItAsync,
12
+ signal,
13
+ addonVersion,
14
+ }) {
15
+ const eventEmitter = new EventEmitter();
16
+ const startedAt = Date.now();
17
+
18
+ eyesStorybook({config, logger, performance, timeItAsync, eventEmitter, signal, addonVersion})
19
+ .then(({results}) => {
20
+ eventEmitter.emit('result', {
21
+ startedAt,
22
+ duration: performance['renderStories'],
23
+ storyResults: results.map(({story, resultsOrErr}) => ({
24
+ story: {id: story.id, queryParams: story.config.queryParams},
25
+ [Array.isArray(resultsOrErr) ? 'results' : 'error']: resultsOrErr,
26
+ })),
27
+ });
28
+ })
29
+ .catch(err => {
30
+ eventEmitter.emit('error', err);
31
+ });
32
+
33
+ return eventEmitter;
34
+ }
35
+
36
+ module.exports = {eyesStorybookEventEmitter};
@@ -2,7 +2,7 @@
2
2
  const lodash = require('lodash');
3
3
  const utils = require('@applitools/utils');
4
4
  const {resolve} = require('path');
5
- const {deprecationWarning} = require('./errMessages');
5
+ const {deprecationWarning, InvalidConfigFileError} = require('./errMessages');
6
6
  const uniq = require('./uniq');
7
7
  const {DEFAULT_CONCURRENCY} = require('@applitools/core');
8
8
  const MAX_DATA_GAP = DEFAULT_CONCURRENCY * 2;
@@ -53,10 +53,6 @@ function generateConfig({argv = {}, defaultConfig = {}, externalConfigParams = [
53
53
 
54
54
  result.eyesServerUrl = result.serverUrl;
55
55
 
56
- result.viewportSize = result.viewportSize ? result.viewportSize : {width: 1024, height: 600};
57
-
58
- result.saveNewTests = result.saveNewTests === undefined ? true : result.saveNewTests;
59
-
60
56
  // Auto-enable dontCloseBatches when sharding is used (unless explicitly set by user)
61
57
  if (result.shard && result.dontCloseBatches === undefined) {
62
58
  result.dontCloseBatches = true;
@@ -66,7 +62,6 @@ function generateConfig({argv = {}, defaultConfig = {}, externalConfigParams = [
66
62
  }
67
63
 
68
64
  result.keepBatchOpen = result.dontCloseBatches;
69
- result.fully = result.fully === undefined ? true : false;
70
65
 
71
66
  if (result.batchName) {
72
67
  result.batch = {name: result.batchName, ...result.batch};
@@ -103,12 +98,7 @@ function getAndParseConfig({configPaths, configParams}) {
103
98
  if (error.message.includes('Could not find configuration file')) {
104
99
  return utils.config.populateConfigParams({config: {}, params: configParams});
105
100
  }
106
- const documentationUrl = 'https://applitools.com/tutorials/sdks/storybook/config#properties';
107
- throw new Error(
108
- `Your configuration file is invalid. Please review our documentation for valid configuration settings: ${documentationUrl}.
109
- Additionally, you can generate a new configuration by running 'npx eyes-setup'.
110
- \n\nError details: ${error.message}`.trim(),
111
- );
101
+ throw new InvalidConfigFileError(error);
112
102
  }
113
103
  }
114
104
 
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const {generateConfig} = require('./generateConfig');
4
+ const defaultConfig = require('./defaultConfig');
5
+ const validateAndPopulateConfig = require('./validateAndPopulateConfig');
6
+ const {makeLogger} = require('@applitools/logger');
7
+ const {configParams: externalConfigParams} = require('./configParams');
8
+
9
+ async function getConfigAndLogger(argv = {}) {
10
+ const config = generateConfig({argv, defaultConfig, externalConfigParams});
11
+ const logger = makeLogger({
12
+ handler: argv.logHandler,
13
+ level: argv.logHandler ? 'all' : config.showLogs ? 'info' : 'silent', // if logHandler is passed, let's pass it all the logs. Otherwise, respect the config
14
+ label: 'eyes',
15
+ });
16
+ await validateAndPopulateConfig({
17
+ config,
18
+ logger,
19
+ packagePath: process.cwd(),
20
+ addonVersion: argv.addonVersion,
21
+ });
22
+ return {config, logger, defaultConfig};
23
+ }
24
+
25
+ module.exports = {getConfigAndLogger};
package/src/index.ts CHANGED
@@ -139,6 +139,24 @@ export type ApplitoolsConfig = Omit<ConfigurationPlain, irrelevantToStorybook |
139
139
  */
140
140
  browserCacheRequests?: boolean;
141
141
 
142
+ /**
143
+ * Headers to override in all browser requests.
144
+ * @default undefined
145
+ */
146
+ browserHeadersOverride?: Record<string, string>;
147
+
148
+ /**
149
+ * Timeout in milliseconds for all browser requests.
150
+ * @default undefined
151
+ */
152
+ browserRequestsTimeout?: number;
153
+
154
+ /**
155
+ * Array of URL patterns to block requests to.
156
+ * @default undefined
157
+ */
158
+ networkBlockPatterns?: string[];
159
+
142
160
  /**
143
161
  * Array of browser configurations for screenshot generation.
144
162
  * Defines size and browser type for generated screenshots.
@@ -307,6 +325,14 @@ export type ApplitoolsConfig = Omit<ConfigurationPlain, irrelevantToStorybook |
307
325
  * @default undefined
308
326
  */
309
327
  shard?: string;
328
+
329
+ /**
330
+ * The size of the Puppeteer browser's window.
331
+ * This is the browser window which renders the stories originally (and opens at the size provided in the `viewportSize` parameter), and then a DOM snapshot is uploaded to the server, which renders this snapshot on all the browsers and sizes provided in the browser parameter.
332
+ *
333
+ * Note: Stories will **not** be rendered and tested on this viewport size, unless you also include it in the `browser` parameter.
334
+ */
335
+ viewportSize?: ConfigurationPlain['viewportSize'];
310
336
  }
311
337
 
312
338
  export type configKeys = keyof ApplitoolsConfig;
package/src/main.js ADDED
@@ -0,0 +1,7 @@
1
+ const {eyesStorybookEventEmitter} = require('./eyesStorybookEventEmitter');
2
+ const {getConfigAndLogger} = require('./getConfigAndLogger');
3
+
4
+ module.exports = {
5
+ eyesStorybookEventEmitter,
6
+ getConfigAndLogger,
7
+ };
@@ -2,7 +2,9 @@
2
2
  const getStoryUrl = require('./getStoryUrl');
3
3
  const getStoryBaselineName = require('./getStoryBaselineName');
4
4
  const ora = require('ora');
5
+ const {EventEmitter} = require('node:events');
5
6
  const {presult} = require('@applitools/functional-commons');
7
+ const {AbortedByUserError} = require('./errMessages');
6
8
 
7
9
  function makeRenderStories({
8
10
  getStoryData,
@@ -13,19 +15,23 @@ function makeRenderStories({
13
15
  stream,
14
16
  sanityCheckForPage,
15
17
  maxPageTTL = 60000,
18
+ eventEmitter = new EventEmitter(),
19
+ signal = new AbortController().signal,
16
20
  }) {
17
21
  let newPageIdToAdd;
18
22
 
19
23
  return async function renderStories(stories, isIE) {
20
24
  let doneStories = 0;
25
+ const totalStories = stories.length;
21
26
  const allTestResults = [];
22
27
  let allStoriesPromise = Promise.resolve();
23
28
  let currIndex = 0;
24
29
 
25
30
  const spinner = ora({
26
- text: updateSpinnerText(0, stories.length),
31
+ text: updateSpinnerText(0, totalStories),
27
32
  stream,
28
33
  });
34
+ eventEmitter.emit('progress', {doneStories, totalStories});
29
35
  spinner.start();
30
36
  prepareNewPage();
31
37
 
@@ -35,7 +41,19 @@ function makeRenderStories({
35
41
  return allTestResults;
36
42
 
37
43
  async function processStoryLoop() {
38
- if (currIndex === stories.length) return;
44
+ if (currIndex === totalStories) return;
45
+
46
+ if (signal.aborted) {
47
+ const story = stories[currIndex++];
48
+ const title = getStoryBaselineName(story);
49
+ logger.log('aborting story before processing', title);
50
+ onDoneStory(
51
+ new AbortedByUserError(`${title} aborted before processing ${signal.reason}`),
52
+ story,
53
+ );
54
+ return processStoryLoop();
55
+ }
56
+
39
57
  const {page, pageId, markPageAsFree, removePage, getCreatedAt} = await pagePool.getFreePage();
40
58
  const livedTime = Date.now() - getCreatedAt();
41
59
  logger.log(`[prepareNewPage] got free page: ${pageId}, lived time: ${livedTime}`);
@@ -103,6 +121,12 @@ function makeRenderStories({
103
121
  const errMsg = `[page ${pageId}] Failed to get story data for "${title}". ${error}`;
104
122
  logger.log(errMsg);
105
123
  }
124
+
125
+ if (signal.aborted) {
126
+ logger.log('aborting story before open', title);
127
+ return onDoneStory(new Error(`${title} aborted before open ${signal.reason}`), story);
128
+ }
129
+
106
130
  const testResults = await renderStory({
107
131
  snapshots: storyData,
108
132
  url: storyUrl,
@@ -132,10 +156,14 @@ function makeRenderStories({
132
156
  }
133
157
 
134
158
  function onDoneStory(resultsOrErr, story) {
135
- spinner.text = updateSpinnerText(++doneStories, stories.length, story.config);
159
+ spinner.text = updateSpinnerText(++doneStories, totalStories, story.config);
136
160
  const title = getStoryBaselineName(story);
137
- allTestResults.push({title, resultsOrErr});
138
- return {title, resultsOrErr};
161
+ const result = {title, resultsOrErr, story};
162
+ allTestResults.push(result);
163
+
164
+ eventEmitter.emit('progress', {doneStories, totalStories});
165
+
166
+ return result;
139
167
  }
140
168
 
141
169
  async function prepareNewPage() {
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
+
2
3
  const throat = require('throat');
4
+ const {AbortedByUserError} = require('./errMessages');
3
5
  const {storyToCheckSettings, storyToOpenSettings} = require('./transformSettings');
4
6
 
5
7
  function makeRenderStory({
@@ -11,6 +13,7 @@ function makeRenderStory({
11
13
  appName,
12
14
  serverSettings,
13
15
  concurrency,
16
+ signal = new AbortController().signal,
14
17
  }) {
15
18
  const throttle = throat(storyDataGap);
16
19
  return function renderStory({story, snapshots, url}) {
@@ -24,7 +27,12 @@ function makeRenderStory({
24
27
 
25
28
  return timeItAsync(baselineName, async () => {
26
29
  const eyes = await openEyes({settings: openParams});
27
- return new Promise((resolve, reject) => {
30
+ return new Promise(async (resolve, reject) => {
31
+ if (signal.aborted) {
32
+ return await abortStory();
33
+ } else {
34
+ signal.addEventListener('abort', abortStory);
35
+ }
28
36
  throttle(async () => {
29
37
  try {
30
38
  if (snapshots) {
@@ -44,6 +52,18 @@ function makeRenderStory({
44
52
  reject(err);
45
53
  }
46
54
  });
55
+
56
+ async function abortStory() {
57
+ logger.log('received abort signal for story', title);
58
+
59
+ // Inside core-base this will cause internal operations to be aborted
60
+ await eyes.abort({settings: {environments: checkParams.environments}});
61
+
62
+ // This will intentionally cause not to wait for results.
63
+ // Therefore there will be a "hanging" promise.
64
+ // But for the purpose of the addon, which is a long living process, it doesn't matter that we didn't stop the operation inside core.
65
+ reject(new AbortedByUserError(`${title} aborted after open ${signal.reason}`));
66
+ }
47
67
  });
48
68
  }).then(onDoneStory);
49
69
 
@@ -104,7 +104,6 @@ function storyToCheckSettings({story, url}) {
104
104
  useDom,
105
105
  enablePatterns,
106
106
  ignoreDisplacements,
107
- fully,
108
107
  ignoreCaret,
109
108
  matchLevel,
110
109
  accessibilitySettings: accessibilityValidation
@@ -36,9 +36,6 @@ const applitoolsBaseKeys = [...dynamicBaseKeys, ...knownAliases];
36
36
  * @type {Array<keyof (import('../index').ApplitoolsConfig) |
37
37
  * 'reloadPagePerStory' |
38
38
  * 'startStorybookServerTimeout' |
39
- * 'networkBlockPatterns' |
40
- * 'browserRequestsTimeout' |
41
- * 'browserHeadersOverride' |
42
39
  * 'waitBeforeScreenshot' |
43
40
  * 'waitBeforeScreenshots' |
44
41
  * 'fakeIE' |
@@ -51,11 +48,19 @@ const storybookSpecificKeys = [
51
48
  'variations', // TODO - are we sure it should be supported from the applitools config file? https://applitools.com/tutorials/sdks/storybook/component-config#variations
52
49
  'runInDocker',
53
50
  'showLogs',
51
+ 'storybookUrl',
52
+ 'storybookStaticDir',
53
+ 'include',
54
54
 
55
55
  // Browser & Puppeteer Control
56
56
  'browser',
57
57
  'puppeteerOptions',
58
58
  'puppeteerExtraHTTPHeaders', // created for a specific user, TODO - consider removing
59
+ 'browserCacheRequests',
60
+ 'browserHeadersOverride',
61
+ 'browserRequestsTimeout',
62
+ 'networkBlockPatterns',
63
+ 'navigationWaitUntil',
59
64
 
60
65
  // Region Matching
61
66
  'ignoreRegions',
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const detect = require('detect-port');
5
5
  const {version: packageVersion} = require('../package.json');
6
6
  const {
7
- missingApiKeyFailMsg,
7
+ MissingApiKeyError,
8
8
  missingAppNameAndPackageJsonFailMsg,
9
9
  missingAppNameInPackageJsonFailMsg,
10
10
  startStorybookFailMsg,
@@ -16,9 +16,14 @@ const determineStorybookVersion = require('./utils/determineStorybookVersion');
16
16
  const {logUnrecognizedKeys} = require('./utils/config-validator');
17
17
  const utils = require('@applitools/utils');
18
18
 
19
- async function validateAndPopulateConfig({config, packagePath = '', logger = makeLogger()}) {
19
+ async function validateAndPopulateConfig({
20
+ config,
21
+ packagePath = '',
22
+ logger = makeLogger(),
23
+ addonVersion,
24
+ }) {
20
25
  if (!config.apiKey && !utils.general.getEnvValue('API_KEY')) {
21
- throw new Error(missingApiKeyFailMsg);
26
+ throw new MissingApiKeyError();
22
27
  }
23
28
 
24
29
  const packageJsonPath = `${packagePath}/package.json`;
@@ -62,7 +67,9 @@ async function validateAndPopulateConfig({config, packagePath = '', logger = mak
62
67
  }
63
68
  }
64
69
 
65
- config.agentId = `eyes-storybook/${packageVersion}`;
70
+ config.agentId = `eyes-storybook/${packageVersion} ${
71
+ addonVersion ? `[eyes-storybook-addon/${addonVersion}]` : ''
72
+ }`.trim();
66
73
 
67
74
  if (config.runInDocker) {
68
75
  config.puppeteerOptions = config.puppeteerOptions || {};