@applitools/eyes-storybook 3.34.1 → 3.35.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
@@ -3,6 +3,21 @@
3
3
  ## Unreleased
4
4
 
5
5
 
6
+
7
+ ## 3.35.0 - 2023/6/13
8
+
9
+ ### Features
10
+ ### Bug fixes
11
+ - Add support for variations in storybook 7
12
+ - Increase timeout for rendering results polling
13
+ - Add the option to not fail on visual differences using exitcode `nodiffs`
14
+
15
+ ## 3.34.2 - 2023/5/16
16
+
17
+ ### Features
18
+ - Add support for reloading the page when using layoutBreakpoints
19
+ ### Bug fixes
20
+
6
21
  ## 3.34.1 - 2023/5/9
7
22
 
8
23
  ### Features
package/README.md CHANGED
@@ -154,7 +154,7 @@ In addition to command-line arguments, it's possible to define the following con
154
154
  | `storybookStaticDir` | undefined | Path to Storybook's static files folder (also available as command-line argument). |
155
155
  | `showStorybookOutput` | undefined | Whether or not you want to see Storybook output (also available as command-line argument). |
156
156
  | `viewportSize` | { width: 1024, height: 600} | The size of the puppeteer browser's window. 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.|
157
- | `exitcode` | true | If tests failed or has visual differences close with non-zero exit code (also available as command-line argument).|
157
+ | `exitcode` | true | When set to `true` - if there were visual differences or other errors, close with non-zero exit code. When set to `false` - always close with zero exit code (i.e. never fail the build). When set to `nodiffs` - close with non-zero exit code only for errors and **not** for visual differences. (also available as command-line argument: `--exitcode true/false/nodiffs`) |
158
158
  | `browser` | { width: 1024, height: 768, name: 'chrome' } | The size and browser of the generated screenshots. For more info and possible values, see the [browser section below](#configuring-the-browser).|
159
159
  | `showLogs` | false | Whether or not you want to see logs of the Eyes-Storybook plugin. |
160
160
  | `batch` | undefined | An object which describes different aspects of the batch. The following lines in this table depict the various ways to configure the batch. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/eyes-storybook",
3
- "version": "3.34.1",
3
+ "version": "3.35.0",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "applitools",
@@ -42,6 +42,7 @@
42
42
  "test:e2e": "mocha --no-timeouts --trace-warnings 'test/e2e/*.e2e.test.js'",
43
43
  "test:e2e:versions": "npx mocha --no-timeouts --trace-warnings --require test/fixtures/storybook-versions/mochaHooks.js test/versions/*.e2e.versions.test.js",
44
44
  "test:e2e:versions_local": "STORYBOOK_VERSION=latest npx mocha --no-timeouts --trace-warnings --require test/fixtures/storybook-versions/mochaHooks.js test/versions/*.e2e.versions.test.js",
45
+ "test:heavy": "node bin/eyes-storybook.js -f test/fixtures/heavyStorybook/applitools.config.js",
45
46
  "storybook": "start-storybook -c test/fixtures/appWithStorybook -p 9001 -s test/fixtures",
46
47
  "storybook:httpHeaders": "start-storybook -c test/fixtures/appWithCustomHttpHeaders -p 9007",
47
48
  "storybook:jslayout": "start-storybook -c test/fixtures/jsLayoutStorybookLocal -p 9001 -s test/fixtures",
@@ -73,15 +74,15 @@
73
74
  }
74
75
  },
75
76
  "dependencies": {
76
- "@applitools/core": "2.5.10",
77
- "@applitools/driver": "1.11.49",
77
+ "@applitools/core": "3.2.1",
78
+ "@applitools/driver": "1.12.1",
78
79
  "@applitools/functional-commons": "1.6.0",
79
- "@applitools/logger": "1.1.52",
80
+ "@applitools/logger": "2.0.2",
80
81
  "@applitools/monitoring-commons": "1.0.19",
81
- "@applitools/spec-driver-puppeteer": "1.1.60",
82
- "@applitools/test-server": "1.1.31",
83
- "@applitools/ufg-client": "1.2.12",
84
- "@applitools/utils": "1.3.35",
82
+ "@applitools/spec-driver-puppeteer": "1.1.64",
83
+ "@applitools/test-server": "1.2.2",
84
+ "@applitools/ufg-client": "1.2.18",
85
+ "@applitools/utils": "1.3.37",
85
86
  "boxen": "4.2.0",
86
87
  "chalk": "3.0.0",
87
88
  "detect-port": "1.3.0",
@@ -94,7 +95,7 @@
94
95
  "yargs": "15.4.1"
95
96
  },
96
97
  "devDependencies": {
97
- "@applitools/bongo": "^3.0.3",
98
+ "@applitools/bongo": "^4.0.0",
98
99
  "@applitools/sdk-shared": "0.9.15",
99
100
  "@applitools/snaptdout": "^1.0.1",
100
101
  "@storybook/addon-interactions": "^6.4.18",
package/src/cli.js CHANGED
@@ -32,14 +32,20 @@ const {performance, timeItAsync} = makeTiming();
32
32
  console.log(`Using @applitools/eyes-storybook version ${VERSION}.\n`);
33
33
  const config = generateConfig({argv, defaultConfig, externalConfigParams});
34
34
  const logger = makeLogger({level: config.showLogs ? 'info' : 'silent', label: 'eyes'});
35
- await validateAndPopulateConfig({config, logger, packagePath: process.cwd()});
35
+ const isVersion7 = await validateAndPopulateConfig({
36
+ config,
37
+ logger,
38
+ packagePath: process.cwd(),
39
+ });
36
40
  logger.log(`Running with the following config:\n${configDigest(config)}`);
37
41
  const [err, results] = await presult(
38
- timeItAsync('eyesStorybook', () => eyesStorybook({config, logger, performance, timeItAsync})),
42
+ timeItAsync('eyesStorybook', () =>
43
+ eyesStorybook({config, logger, performance, timeItAsync, isVersion7}),
44
+ ),
39
45
  );
40
46
  if (err) {
41
47
  console.log(chalk.red(err.message));
42
- process.exit(config.exitcode ? config.exitcode : 0);
48
+ process.exit(config.exitcode ? 1 : 0);
43
49
  } else {
44
50
  const totalTime = performance['eyesStorybook'];
45
51
  const {exitCode, summary, outputStr} = processResults({
@@ -47,6 +53,7 @@ const {performance, timeItAsync} = makeTiming();
47
53
  totalTime,
48
54
  testConcurrency: config.testConcurrency,
49
55
  saveNewTests: config.saveNewTests,
56
+ configExitCode: config.exitcode,
50
57
  });
51
58
  console.log(outputStr);
52
59
  if (config.jsonFilePath) {
@@ -58,7 +65,7 @@ const {performance, timeItAsync} = makeTiming();
58
65
  if (config.xmlFilePath) {
59
66
  handleXmlFile(config.xmlFilePath, summary, {totalTime});
60
67
  }
61
- process.exit(config.exitcode ? exitCode : 0);
68
+ process.exit(exitCode);
62
69
  }
63
70
  } catch (ex) {
64
71
  console.log(ex);
@@ -25,7 +25,6 @@ const {makeCore} = require('@applitools/core');
25
25
  const {makeUFGClient} = require('@applitools/ufg-client');
26
26
  const makeGetStoriesWithConfig = require('./getStoriesWithConfig');
27
27
 
28
- const CONCURRENT_PAGES = 3;
29
28
  const MAX_RETRIES = 10;
30
29
  const RETRY_INTERVAL = 1000;
31
30
 
@@ -35,6 +34,7 @@ async function eyesStorybook({
35
34
  performance,
36
35
  timeItAsync,
37
36
  outputStream = process.stderr,
37
+ isVersion7,
38
38
  }) {
39
39
  let memoryTimeout;
40
40
  let renderIE = false;
@@ -42,14 +42,12 @@ async function eyesStorybook({
42
42
  takeMemLoop();
43
43
  logger.log('eyesStorybook started');
44
44
 
45
- const {
46
- storybookUrl,
47
- waitBeforeCapture,
48
- readStoriesTimeout,
49
- reloadPagePerStory,
50
- proxy,
51
- testConcurrency,
52
- } = config;
45
+ const CONCURRENT_TABS = isNaN(Number(process.env.APPLITOOLS_CONCURRENT_TABS))
46
+ ? 3
47
+ : Number(process.env.APPLITOOLS_CONCURRENT_TABS);
48
+ logger.log(`Running with ${CONCURRENT_TABS} concurrent tabs`);
49
+
50
+ const {storybookUrl, readStoriesTimeout, reloadPagePerStory, proxy, testConcurrency} = config;
53
51
 
54
52
  let iframeUrl;
55
53
  try {
@@ -151,14 +149,21 @@ async function eyesStorybook({
151
149
  },
152
150
  });
153
151
  try {
154
- const [stories] = await Promise.all(
155
- [getStoriesWithSpinner()].concat(
156
- new Array(CONCURRENT_PAGES).fill().map(async () => {
152
+ const stories = await getStoriesWithSpinner();
153
+
154
+ if (CONCURRENT_TABS <= 3) {
155
+ await Promise.all(
156
+ new Array(CONCURRENT_TABS).fill().map(async () => {
157
157
  const {pageId} = await pagePool.createPage();
158
158
  pagePool.addToPool(pageId);
159
159
  }),
160
- ),
161
- );
160
+ );
161
+ } else {
162
+ for (const _x of new Array(CONCURRENT_TABS).fill()) {
163
+ const {pageId} = await pagePool.createPage();
164
+ pagePool.addToPool(pageId);
165
+ }
166
+ }
162
167
 
163
168
  const filteredStories = filterStories({stories, config});
164
169
  const storiesIncludingVariations = addVariationStories({
@@ -209,6 +214,7 @@ async function eyesStorybook({
209
214
  logger,
210
215
  stream: outputStream,
211
216
  pagePool,
217
+ isVersion7,
212
218
  });
213
219
 
214
220
  logger.log('finished creating functions');
@@ -77,7 +77,7 @@ function generateConfig({argv = {}, defaultConfig = {}, externalConfigParams = [
77
77
  );
78
78
  }
79
79
 
80
- transformBrowser(result);
80
+ transformConfig(result);
81
81
  if (!result.renderers) {
82
82
  result.renderers = [{name: 'chrome', width: 1024, height: 768}];
83
83
  }
@@ -85,6 +85,11 @@ function generateConfig({argv = {}, defaultConfig = {}, externalConfigParams = [
85
85
  return result;
86
86
  }
87
87
 
88
+ function transformConfig(result) {
89
+ transformLayoutBreakpoints(result);
90
+ transformBrowser(result);
91
+ }
92
+
88
93
  function transformBrowser(result) {
89
94
  if (result.browser) {
90
95
  result.renderers = [];
@@ -104,4 +109,14 @@ function transformBrowser(result) {
104
109
  delete result.browser;
105
110
  return result;
106
111
  }
107
- module.exports = {generateConfig, transformBrowser};
112
+
113
+ function transformLayoutBreakpoints(result) {
114
+ if (
115
+ utils.types.isBoolean(result.layoutBreakpoints) ||
116
+ utils.types.isArray(result.layoutBreakpoints)
117
+ ) {
118
+ result.layoutBreakpoints = {breakpoints: result.layoutBreakpoints};
119
+ }
120
+ }
121
+
122
+ module.exports = {generateConfig, transformConfig};
@@ -1,6 +1,6 @@
1
1
  const {splitConfigsByBrowser, shouldRenderIE} = require('./shouldRenderIE');
2
2
  const getStoryTitle = require('./getStoryTitle');
3
- const {transformBrowser} = require('./generateConfig');
3
+ const {transformConfig} = require('./generateConfig');
4
4
  const {checkSettingsParams} = require('./checkSettingsParams');
5
5
  const getStoryBaselineName = require('./getStoryBaselineName');
6
6
 
@@ -34,8 +34,11 @@ function makeGetStoriesWithConfig({config}) {
34
34
  }
35
35
  })
36
36
  .filter(Boolean);
37
+
38
+ const storyConfigReduced = allowedProps(storyConfig);
39
+ transformConfig(storyConfigReduced);
37
40
  addConfigToStories({
38
- config: transformBrowser(allowedProps(storyConfig)),
41
+ config: storyConfigReduced,
39
42
  stories: storiesSubset,
40
43
  isStoryConfig: true,
41
44
  });
@@ -70,13 +73,15 @@ function makeGetStoriesWithConfig({config}) {
70
73
 
71
74
  function addConfigToStory({story, config, isIE, isStoryConfig}) {
72
75
  const storiesToUpdate = isIE ? storiesWithConfigIE : storiesWithConfig;
76
+ const storyEyesParameters = {...story.parameters?.eyes};
77
+ transformConfig(storyEyesParameters);
73
78
  storiesToUpdate.set(story.baselineName, {
74
79
  ...story,
75
80
  config: {
76
81
  ...basicConfig,
77
82
  ...storiesToUpdate.get(story.baselineName)?.config,
78
83
  ...config,
79
- ...transformBrowser({...story.parameters?.eyes}),
84
+ ...storyEyesParameters,
80
85
  properties: [
81
86
  ...(isStoryConfig ? basicConfig.properties || [] : []),
82
87
  ...(storiesToUpdate.get(story.baselineName)?.config.properties || []),
@@ -1,10 +1,14 @@
1
1
  'use strict';
2
2
  const getIframeUrl = require('./getIframeUrl');
3
3
 
4
- function getStoryUrl({name, kind, parameters}, baseUrl) {
4
+ function getStoryUrl({name, kind, parameters, id}, baseUrl, isVersion7) {
5
5
  let storyUrl = getIframeUrl(baseUrl);
6
- storyUrl += `&selectedKind=${encodeURIComponent(kind)}`;
7
- storyUrl += `&selectedStory=${encodeURIComponent(name)}`;
6
+ if (!isVersion7) {
7
+ storyUrl += `&selectedKind=${encodeURIComponent(kind)}`;
8
+ storyUrl += `&selectedStory=${encodeURIComponent(name)}`;
9
+ } else {
10
+ storyUrl += `&path=/story/${encodeURIComponent(id)}`;
11
+ }
8
12
 
9
13
  const queryParams = parameters && parameters.eyes && parameters.eyes.queryParams;
10
14
  if (queryParams) {
package/src/initPage.js CHANGED
@@ -3,9 +3,16 @@ const browserLog = require('./browserLog');
3
3
  const fakeIE = require('./fakeIE');
4
4
 
5
5
  function makeInitPage({iframeUrl, config, browser, logger, getTransitiongIntoIE, getRenderIE}) {
6
+ const browserContexts = [browser.defaultBrowserContext()];
7
+
6
8
  return async function initPage({pageId, pagePool}) {
7
9
  logger.log('initializing puppeteer page number ', pageId);
8
- const page = await browser.newPage();
10
+ const pages = await browserContexts[browserContexts.length - 1].pages();
11
+ if (pages.length === 5) {
12
+ const browserContext = await browser.createIncognitoBrowserContext();
13
+ browserContexts.push(browserContext);
14
+ }
15
+ const page = await browserContexts[browserContexts.length - 1].newPage();
9
16
 
10
17
  if (config.viewportSize) {
11
18
  await page.setViewport(config.viewportSize);
@@ -5,7 +5,13 @@ const utils = require('@applitools/utils');
5
5
  const uniq = require('./uniq');
6
6
  const concurrencyMsg = require('./concurrencyMsg');
7
7
 
8
- function processResults({results, totalTime, testConcurrency, saveNewTests = true}) {
8
+ function processResults({
9
+ results,
10
+ totalTime,
11
+ testConcurrency,
12
+ saveNewTests = true,
13
+ configExitCode,
14
+ }) {
9
15
  let outputStr = '\n';
10
16
  const pluralize = utils.general.pluralize;
11
17
  let testResults = flatten(results.summary.results);
@@ -101,8 +107,16 @@ function processResults({results, totalTime, testConcurrency, saveNewTests = tru
101
107
  // TODO require from core
102
108
  outputStr += `\n${concurrencyMsg}\n`;
103
109
  }
104
- const exitCode =
105
- !warnForUnsavedNewTests && passedOrNew.length && !errors.length && !unresolved.length ? 0 : 1;
110
+
111
+ let exitCode;
112
+ if (!configExitCode) {
113
+ exitCode = 0;
114
+ } else if (configExitCode === 'nodiffs') {
115
+ exitCode = errors.length ? 1 : 0;
116
+ } else {
117
+ exitCode =
118
+ !warnForUnsavedNewTests && passedOrNew.length && !errors.length && !unresolved.length ? 0 : 1;
119
+ }
106
120
  return {
107
121
  outputStr,
108
122
  summary: results.summary,
@@ -13,6 +13,7 @@ function makeRenderStories({
13
13
  stream,
14
14
  getClientAPI,
15
15
  maxPageTTL = 60000,
16
+ isVersion7,
16
17
  }) {
17
18
  let newPageIdToAdd;
18
19
 
@@ -56,7 +57,7 @@ function makeRenderStories({
56
57
 
57
58
  async function processStory() {
58
59
  const story = stories[currIndex++];
59
- const storyUrl = getStoryUrl(story, storybookUrl);
60
+ const storyUrl = getStoryUrl(story, storybookUrl, isVersion7);
60
61
  const title = getStoryBaselineName(story);
61
62
  try {
62
63
  let [error, storyData] = await presult(
@@ -15,7 +15,7 @@ function makeRenderStory({
15
15
  const throttle = throat(storyDataGap);
16
16
  return function renderStory({story, snapshots, url}) {
17
17
  const config = story.config;
18
- const {name, kind, parameters, hasPlayFunction} = story;
18
+ const {name, kind, hasPlayFunction} = story;
19
19
  const baselineName = story.baselineName;
20
20
  const title = story.storyTitle;
21
21
  const {
@@ -1,16 +1,8 @@
1
1
  'use strict';
2
- const {resolve} = require('path');
3
2
  const ora = require('ora');
4
3
  const StorybookConnector = require('./storybookConnector');
5
- const fs = require('fs');
6
- const {exec} = require('child_process');
7
- const {promisify: p} = require('util');
8
- const pexec = p(exec);
9
- // eslint-disable-next-line
10
- const semver = require('semver');
11
4
 
12
5
  async function startStorybookServer({
13
- packagePath,
14
6
  storybookPort,
15
7
  storybookHost,
16
8
  storybookConfigDir,
@@ -18,22 +10,10 @@ async function startStorybookServer({
18
10
  showStorybookOutput,
19
11
  logger,
20
12
  startStorybookServerTimeout,
13
+ storybookPath,
14
+ sbArg,
21
15
  }) {
22
16
  const isWindows = process.platform.startsWith('win');
23
- let storybookPath, sbArg;
24
- const storybookPathV6 = resolve(packagePath, 'node_modules/.bin/start-storybook');
25
- const storybookPathV7 = resolve(packagePath, 'node_modules/.bin/sb');
26
- if (fs.existsSync(storybookPathV7)) {
27
- const version = await pexec(`${storybookPathV7} --version`);
28
- if (semver.satisfies(version.stdout, '<7.0.0')) {
29
- storybookPath = storybookPathV6;
30
- } else {
31
- storybookPath = storybookPathV7;
32
- sbArg = 'dev';
33
- }
34
- } else {
35
- storybookPath = storybookPathV6;
36
- }
37
17
 
38
18
  const storybookConnector = new StorybookConnector({
39
19
  storybookPath,
@@ -11,14 +11,25 @@ const {
11
11
  } = require('./errMessages');
12
12
  const startStorybookServer = require('./startStorybookServer');
13
13
  const {isIE} = require('./shouldRenderIE');
14
+ const {resolve} = require('path');
15
+ const {exec} = require('child_process');
16
+ const {promisify: p} = require('util');
17
+ const pexec = p(exec);
18
+ // eslint-disable-next-line
19
+ const semver = require('semver');
20
+ const {makeLogger} = require('@applitools/logger');
14
21
 
15
- async function validateAndPopulateConfig({config, packagePath, logger}) {
22
+ async function validateAndPopulateConfig({config, packagePath = '', logger = makeLogger()}) {
16
23
  if (!config.apiKey) {
17
24
  throw new Error(missingApiKeyFailMsg);
18
25
  }
19
26
 
20
27
  const packageJsonPath = `${packagePath}/package.json`;
21
28
  const packageJson = fs.existsSync(packageJsonPath) ? require(packageJsonPath) : undefined;
29
+ const {storybookPath, isVersion7, sbArg} = await determineStorybookVersion(packagePath);
30
+ logger.log(
31
+ `[validateAndPopulateConfig] storybookPath=${storybookPath} isVersion7=${isVersion7} sbArg=${sbArg}`,
32
+ );
22
33
 
23
34
  if (!config.appName) {
24
35
  if (!packageJson) {
@@ -39,7 +50,9 @@ async function validateAndPopulateConfig({config, packagePath, logger}) {
39
50
  console.log(chalk.red(`couldn't find available port around`, config.storybookPort));
40
51
  }
41
52
 
42
- config.storybookUrl = await startStorybookServer(Object.assign({packagePath, logger}, config));
53
+ config.storybookUrl = await startStorybookServer(
54
+ Object.assign({packagePath, logger, storybookPath, sbArg}, config),
55
+ );
43
56
 
44
57
  // NOTE (Amit): I don't understand why this condition is here. It shouldn't happen. I might have been the one to put it here, but it seems like a mistake. We should take a thorough look and remove it.
45
58
  if (!config.storybookUrl) {
@@ -65,6 +78,29 @@ async function validateAndPopulateConfig({config, packagePath, logger}) {
65
78
  ),
66
79
  );
67
80
  }
81
+ return isVersion7;
68
82
  }
69
83
 
84
+ async function determineStorybookVersion(packagePath) {
85
+ let storybookPath,
86
+ sbArg,
87
+ isVersion7 = false;
88
+ const storybookPathV6 = resolve(packagePath, 'node_modules/.bin/start-storybook');
89
+ const storybookPathV7 = resolve(packagePath, 'node_modules/.bin/sb');
90
+
91
+ if (fs.existsSync(storybookPathV7)) {
92
+ const version = await pexec(`${storybookPathV7} --version`);
93
+ if (semver.satisfies(version.stdout, '<7.0.0')) {
94
+ storybookPath = storybookPathV6;
95
+ } else {
96
+ storybookPath = storybookPathV7;
97
+ sbArg = 'dev';
98
+ isVersion7 = true;
99
+ }
100
+ } else {
101
+ storybookPath = storybookPathV6;
102
+ }
103
+
104
+ return {storybookPath, isVersion7, sbArg};
105
+ }
70
106
  module.exports = validateAndPopulateConfig;