creevey 0.8.0-beta.0 → 0.9.0-beta.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.
Files changed (149) hide show
  1. package/CHANGELOG.md +5 -9
  2. package/lib/cjs/client/addon/Manager.js +0 -1
  3. package/lib/cjs/client/addon/preset.js +1 -0
  4. package/lib/cjs/client/addon/readyForCapture.js +12 -0
  5. package/lib/cjs/client/addon/withCreevey.js +313 -41
  6. package/lib/cjs/client/shared/components/ImagesView/BlendView.js +3 -3
  7. package/lib/cjs/client/shared/components/ImagesView/SideBySideView.js +3 -3
  8. package/lib/cjs/client/shared/components/ImagesView/SlideView.js +4 -3
  9. package/lib/cjs/client/shared/components/ImagesView/SwapView.js +3 -3
  10. package/lib/cjs/client/shared/helpers.js +1 -1
  11. package/lib/cjs/client/web/main.js +6 -6
  12. package/lib/cjs/index.js +27 -9
  13. package/lib/cjs/server/config.js +7 -3
  14. package/lib/cjs/server/extract.js +11 -4
  15. package/lib/cjs/server/index.js +2 -4
  16. package/lib/cjs/server/master/index.js +3 -9
  17. package/lib/cjs/server/master/master.js +1 -0
  18. package/lib/cjs/server/master/pool.js +29 -29
  19. package/lib/cjs/server/master/server.js +75 -3
  20. package/lib/cjs/server/messages.js +124 -12
  21. package/lib/cjs/server/parser.js +85 -0
  22. package/lib/cjs/server/selenium/browser.js +119 -21
  23. package/lib/cjs/server/stories.js +49 -46
  24. package/lib/cjs/server/storybook/providers/browser.js +78 -0
  25. package/lib/cjs/server/storybook/providers/hybrid.js +79 -0
  26. package/lib/cjs/server/storybook/{nodejs-provider.js → providers/nodejs.js} +32 -13
  27. package/lib/cjs/server/utils.js +7 -0
  28. package/lib/cjs/server/worker/helpers.js +2 -6
  29. package/lib/cjs/server/worker/worker.js +15 -3
  30. package/lib/cjs/shared.js +78 -6
  31. package/lib/cjs/types.js +5 -0
  32. package/lib/esm/client/addon/Manager.js +0 -1
  33. package/lib/esm/client/addon/preset.js +1 -0
  34. package/lib/esm/client/addon/readyForCapture.js +5 -0
  35. package/lib/esm/client/addon/withCreevey.js +303 -41
  36. package/lib/esm/client/shared/components/ImagesView/BlendView.js +2 -3
  37. package/lib/esm/client/shared/components/ImagesView/SideBySideView.js +2 -3
  38. package/lib/esm/client/shared/components/ImagesView/SlideView.js +3 -3
  39. package/lib/esm/client/shared/components/ImagesView/SwapView.js +2 -3
  40. package/lib/esm/client/shared/helpers.js +1 -1
  41. package/lib/esm/index.js +6 -3
  42. package/lib/esm/server/config.js +7 -5
  43. package/lib/esm/server/extract.js +8 -4
  44. package/lib/esm/server/index.js +2 -3
  45. package/lib/esm/server/master/index.js +4 -10
  46. package/lib/esm/server/master/master.js +1 -0
  47. package/lib/esm/server/master/pool.js +31 -31
  48. package/lib/esm/server/master/server.js +73 -5
  49. package/lib/esm/server/messages.js +118 -12
  50. package/lib/esm/server/parser.js +63 -0
  51. package/lib/esm/server/selenium/browser.js +116 -23
  52. package/lib/esm/server/stories.js +50 -46
  53. package/lib/esm/server/storybook/providers/browser.js +61 -0
  54. package/lib/esm/server/storybook/providers/hybrid.js +63 -0
  55. package/lib/esm/server/storybook/{nodejs-provider.js → providers/nodejs.js} +30 -13
  56. package/lib/esm/server/utils.js +6 -1
  57. package/lib/esm/server/worker/helpers.js +2 -6
  58. package/lib/esm/server/worker/worker.js +16 -4
  59. package/lib/esm/shared.js +59 -5
  60. package/lib/esm/types.js +3 -0
  61. package/lib/types/cli.d.ts +1 -1
  62. package/lib/types/client/addon/Manager.d.ts +37 -37
  63. package/lib/types/client/addon/components/Addon.d.ts +8 -8
  64. package/lib/types/client/addon/components/Icons.d.ts +7 -7
  65. package/lib/types/client/addon/components/Panel.d.ts +9 -9
  66. package/lib/types/client/addon/components/TestSelect.d.ts +9 -9
  67. package/lib/types/client/addon/components/Tools.d.ts +6 -6
  68. package/lib/types/client/addon/decorator.d.ts +1 -1
  69. package/lib/types/client/addon/preset.d.ts +24 -22
  70. package/lib/types/client/addon/readyForCapture.d.ts +6 -0
  71. package/lib/types/client/addon/register.d.ts +3 -3
  72. package/lib/types/client/addon/utils.d.ts +2 -2
  73. package/lib/types/client/addon/withCreevey.d.ts +24 -13
  74. package/lib/types/client/shared/components/ImagesView/BlendView.d.ts +3 -3
  75. package/lib/types/client/shared/components/ImagesView/ImagesView.d.ts +25 -25
  76. package/lib/types/client/shared/components/ImagesView/SideBySideView.d.ts +3 -3
  77. package/lib/types/client/shared/components/ImagesView/SlideView.d.ts +3 -3
  78. package/lib/types/client/shared/components/ImagesView/SwapView.d.ts +3 -3
  79. package/lib/types/client/shared/components/ImagesView/index.d.ts +5 -5
  80. package/lib/types/client/shared/components/PageFooter/PageFooter.d.ts +9 -9
  81. package/lib/types/client/shared/components/PageFooter/Paging.d.ts +8 -8
  82. package/lib/types/client/shared/components/PageHeader/ImagePreview.d.ts +12 -12
  83. package/lib/types/client/shared/components/PageHeader/PageHeader.d.ts +17 -17
  84. package/lib/types/client/shared/components/ResultsPage.d.ts +18 -18
  85. package/lib/types/client/shared/creeveyClientApi.d.ts +9 -9
  86. package/lib/types/client/shared/helpers.d.ts +46 -46
  87. package/lib/types/client/shared/viewMode.d.ts +4 -4
  88. package/lib/types/client/web/CreeveyApp.d.ts +12 -12
  89. package/lib/types/client/web/CreeveyContext.d.ts +11 -11
  90. package/lib/types/client/web/CreeveyLoader.d.ts +3 -3
  91. package/lib/types/client/web/CreeveyView/SideBar/Checkbox.d.ts +19 -19
  92. package/lib/types/client/web/CreeveyView/SideBar/Search.d.ts +6 -6
  93. package/lib/types/client/web/CreeveyView/SideBar/SideBar.d.ts +14 -14
  94. package/lib/types/client/web/CreeveyView/SideBar/SideBarHeader.d.ts +13 -13
  95. package/lib/types/client/web/CreeveyView/SideBar/SuiteLink.d.ts +33 -33
  96. package/lib/types/client/web/CreeveyView/SideBar/TestLink.d.ts +8 -8
  97. package/lib/types/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +10 -10
  98. package/lib/types/client/web/CreeveyView/SideBar/TestsStatus.d.ts +9 -9
  99. package/lib/types/client/web/CreeveyView/SideBar/Toggle.d.ts +6 -6
  100. package/lib/types/client/web/CreeveyView/SideBar/index.d.ts +1 -1
  101. package/lib/types/client/web/KeyboardEventsContext.d.ts +13 -13
  102. package/lib/types/client/web/index.d.ts +4 -4
  103. package/lib/types/creevey.d.ts +1 -1
  104. package/lib/types/index.d.ts +2 -1
  105. package/lib/types/server/config.d.ts +4 -4
  106. package/lib/types/server/docker.d.ts +7 -7
  107. package/lib/types/server/extract.d.ts +2 -2
  108. package/lib/types/server/index.d.ts +2 -2
  109. package/lib/types/server/loaders/babel/creevey-plugin.d.ts +1 -1
  110. package/lib/types/server/loaders/babel/helpers.d.ts +19 -19
  111. package/lib/types/server/loaders/babel/register.d.ts +5 -5
  112. package/lib/types/server/loaders/hooks/mdx.d.ts +1 -1
  113. package/lib/types/server/loaders/hooks/svelte.d.ts +1 -1
  114. package/lib/types/server/loaders/webpack/compile.d.ts +2 -2
  115. package/lib/types/server/loaders/webpack/creevey-loader.d.ts +2 -2
  116. package/lib/types/server/loaders/webpack/dummy-hmr.d.ts +10 -10
  117. package/lib/types/server/loaders/webpack/mdx-loader.d.ts +6 -6
  118. package/lib/types/server/loaders/webpack/start.d.ts +1 -1
  119. package/lib/types/server/logger.d.ts +6 -6
  120. package/lib/types/server/master/api.d.ts +7 -7
  121. package/lib/types/server/master/index.d.ts +3 -3
  122. package/lib/types/server/master/master.d.ts +7 -6
  123. package/lib/types/server/master/pool.d.ts +31 -30
  124. package/lib/types/server/master/runner.d.ts +26 -26
  125. package/lib/types/server/master/server.d.ts +2 -2
  126. package/lib/types/server/messages.d.ts +28 -18
  127. package/lib/types/server/parser.d.ts +12 -0
  128. package/lib/types/server/selenium/browser.d.ts +17 -14
  129. package/lib/types/server/selenium/index.d.ts +2 -2
  130. package/lib/types/server/selenium/selenoid.d.ts +3 -3
  131. package/lib/types/server/stories.d.ts +8 -8
  132. package/lib/types/server/storybook/entry.d.ts +18 -18
  133. package/lib/types/server/storybook/helpers.d.ts +24 -24
  134. package/lib/types/server/storybook/providers/browser.d.ts +4 -0
  135. package/lib/types/server/storybook/providers/hybrid.d.ts +4 -0
  136. package/lib/types/server/storybook/providers/nodejs.d.ts +9 -0
  137. package/lib/types/server/update.d.ts +2 -2
  138. package/lib/types/server/utils.d.ts +20 -19
  139. package/lib/types/server/worker/chai-image.d.ts +6 -6
  140. package/lib/types/server/worker/helpers.d.ts +8 -7
  141. package/lib/types/server/worker/index.d.ts +1 -1
  142. package/lib/types/server/worker/reporter.d.ts +8 -8
  143. package/lib/types/server/worker/worker.d.ts +4 -4
  144. package/lib/types/shared.d.ts +16 -4
  145. package/lib/types/types.d.ts +488 -459
  146. package/package.json +12 -6
  147. package/storybook-static/stories.json +21 -0
  148. package/types/mocha.d.ts +1 -0
  149. package/lib/types/server/storybook/nodejs-provider.d.ts +0 -5
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
2
2
  import { getBorderColor, themeBorderColors } from './ImagesView';
3
3
  import { styled, withTheme } from '@storybook/theming';
4
4
  import { useApplyScale, useCalcScale, useLoadImages } from '../../helpers';
5
+ import { readyForCapture } from '../../../addon/readyForCapture';
5
6
  var Container = styled.div({
6
7
  position: 'relative',
7
8
  display: 'flex',
@@ -41,9 +42,7 @@ export var BlendView = withTheme(function (_ref2) {
41
42
  useApplyScale(expectImageRef, scale, loaded);
42
43
  useApplyScale(actualImageRef, scale, loaded);
43
44
  useEffect(function () {
44
- var _window$__CREEVEY_SET, _window;
45
-
46
- if (loaded) (_window$__CREEVEY_SET = (_window = window).__CREEVEY_SET_READY_FOR_CAPTURE__) === null || _window$__CREEVEY_SET === void 0 ? void 0 : _window$__CREEVEY_SET.call(_window);
45
+ if (loaded) readyForCapture();
47
46
  }, [loaded]);
48
47
  return /*#__PURE__*/React.createElement(Container, null, /*#__PURE__*/React.createElement(ImageContainer, null, /*#__PURE__*/React.createElement(Image, {
49
48
  ref: expectImageRef,
@@ -15,6 +15,7 @@ import { getBorderColor, themeBorderColors } from './ImagesView';
15
15
  import { styled, withTheme } from '@storybook/theming';
16
16
  import { useApplyScale, useLoadImages, useResizeObserver, getBorderSize } from '../../helpers';
17
17
  import { Loader } from '@storybook/components';
18
+ import { readyForCapture } from '../../../addon/readyForCapture';
18
19
  var Container = styled.div({
19
20
  display: 'flex',
20
21
  flexWrap: 'nowrap',
@@ -111,9 +112,7 @@ export var SideBySideView = withTheme(function (_ref3) {
111
112
  useApplyScale(expectImageRef, scale);
112
113
  useApplyScale(actualImageRef, scale);
113
114
  useEffect(function () {
114
- var _window$__CREEVEY_SET, _window;
115
-
116
- if (loaded) (_window$__CREEVEY_SET = (_window = window).__CREEVEY_SET_READY_FOR_CAPTURE__) === null || _window$__CREEVEY_SET === void 0 ? void 0 : _window$__CREEVEY_SET.call(_window);
115
+ if (loaded) readyForCapture();
117
116
  }, [loaded]);
118
117
  return /*#__PURE__*/React.createElement(Container, {
119
118
  ref: containerRef
@@ -15,6 +15,7 @@ import { styled, withTheme } from '@storybook/theming';
15
15
  import { getBorderColor, themeBorderColors } from './ImagesView';
16
16
  import { useApplyScale, useCalcScale, useLoadImages } from '../../helpers';
17
17
  import { Loader } from '@storybook/components';
18
+ import { readyForCapture } from '../../../addon/readyForCapture';
18
19
  var Container = styled.div({
19
20
  position: 'relative',
20
21
  display: 'flex'
@@ -118,11 +119,10 @@ export var SlideView = withTheme(function (_ref3) {
118
119
  }
119
120
  }, [loaded]);
120
121
  useEffect(function () {
121
- var _window$__CREEVEY_SET, _window;
122
-
123
- if (loaded) (_window$__CREEVEY_SET = (_window = window).__CREEVEY_SET_READY_FOR_CAPTURE__) === null || _window$__CREEVEY_SET === void 0 ? void 0 : _window$__CREEVEY_SET.call(_window);
122
+ if (loaded) readyForCapture();
124
123
  }, [loaded]);
125
124
  return loaded ? /*#__PURE__*/React.createElement(Container, null, /*#__PURE__*/React.createElement(Input, {
125
+ "data-testid": "slider",
126
126
  type: "range",
127
127
  min: 0,
128
128
  max: 100,
@@ -15,6 +15,7 @@ import { getBorderColor, themeBorderColors } from './ImagesView';
15
15
  import { styled, withTheme } from '@storybook/theming';
16
16
  import { useApplyScale, useCalcScale, useLoadImages } from '../../helpers';
17
17
  import { Loader } from '@storybook/components';
18
+ import { readyForCapture } from '../../../addon/readyForCapture';
18
19
  var Container = styled.div({
19
20
  position: 'relative',
20
21
  display: 'flex'
@@ -60,9 +61,7 @@ export var SwapView = withTheme(function (_ref2) {
60
61
  });
61
62
  }, []);
62
63
  useEffect(function () {
63
- var _window$__CREEVEY_SET, _window;
64
-
65
- if (loaded) (_window$__CREEVEY_SET = (_window = window).__CREEVEY_SET_READY_FOR_CAPTURE__) === null || _window$__CREEVEY_SET === void 0 ? void 0 : _window$__CREEVEY_SET.call(_window);
64
+ if (loaded) readyForCapture();
66
65
  }, [loaded]);
67
66
  return loaded ? /*#__PURE__*/React.createElement(Container, null, /*#__PURE__*/React.createElement(Image, {
68
67
  ref: expectImageRef,
@@ -291,7 +291,7 @@ export function countTestsStatus(suite) {
291
291
  };
292
292
  }
293
293
  export function getConnectionUrl() {
294
- return [window.location.hostname, typeof __CREEVEY_SERVER_PORT__ == 'undefined' ? window.location.port : __CREEVEY_SERVER_PORT__].filter(Boolean).join(':');
294
+ return [typeof __CREEVEY_SERVER_HOST__ == 'undefined' ? window.location.hostname : __CREEVEY_SERVER_HOST__, typeof __CREEVEY_SERVER_PORT__ == 'undefined' ? window.location.port : __CREEVEY_SERVER_PORT__].filter(Boolean).join(':');
295
295
  }
296
296
  export function getImageUrl(path, imageName) {
297
297
  // path => [kind, story, test, browser]
package/lib/esm/index.js CHANGED
@@ -1,4 +1,7 @@
1
- export * from './types';
2
- export * from './client/addon/withCreevey'; // export { loadStories as browserStoriesProvider } from './server/storybook/browser-provider';
1
+ export * from './types'; // export * from './client/addon/withCreevey';
2
+ // export * from './client/addon/readyForCapture';
3
3
 
4
- export { loadStories as nodejsStoriesProvider } from './server/storybook/nodejs-provider';
4
+ export { loadStories as browserStoriesProvider } from './server/storybook/providers/browser';
5
+ export { loadStories as nodejsStoriesProvider } from './server/storybook/providers/nodejs';
6
+ export { loadStories as hybridStoriesProvider } from './server/storybook/providers/hybrid';
7
+ export * from './server/parser';
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { isCSFv3Enabled, isStorybookVersionLessThan, storybookDirRef } from './storybook/helpers';
4
- import { loadStories as nodejsStoriesProvider } from './storybook/nodejs-provider'; // import { loadStories as browserStoriesProvider } from './storybook/browser-provider';
5
-
3
+ import { isCSFv3Enabled, isStorybookVersionGreaterThan, isStorybookVersionLessThan, storybookDirRef } from './storybook/helpers';
4
+ import { loadStories as nodejsStoriesProvider } from './storybook/providers/nodejs';
5
+ import { loadStories as browserStoriesProvider } from './storybook/providers/browser';
6
6
  import { isDefined } from '../types';
7
7
  export const defaultBrowser = 'chrome';
8
8
  export const defaultConfig = {
@@ -25,7 +25,8 @@ export const defaultConfig = {
25
25
  [defaultBrowser]: true
26
26
  },
27
27
  hooks: {},
28
- babelOptions: _ => _
28
+ babelOptions: _ => _,
29
+ testRegex: /.creevey.(t|j)s$/
29
30
  };
30
31
 
31
32
  function normalizeBrowserConfig(name, config) {
@@ -60,7 +61,8 @@ export async function readConfig(options) {
60
61
  if (isDefined(configPath)) Object.assign(userConfig, (await import(configPath)).default);
61
62
  storybookDirRef.current = userConfig.storybookDir;
62
63
  if (isStorybookVersionLessThan(6, 2)) userConfig.useWebpackToExtractTests = true;
63
- if (!userConfig.storiesProvider) userConfig.storiesProvider = (await isCSFv3Enabled()) ? nodejsStoriesProvider : nodejsStoriesProvider;
64
+ if (!userConfig.storiesProvider) userConfig.storiesProvider = (await isCSFv3Enabled()) && isStorybookVersionGreaterThan(5) ? browserStoriesProvider : nodejsStoriesProvider;
65
+ if (userConfig.storiesProvider == browserStoriesProvider && isStorybookVersionLessThan(6)) throw new Error("Creevey browser stories provider doesn't support Storybook 5.x or older versions");
64
66
  if (options.failFast != undefined) userConfig.failFast = Boolean(options.failFast);
65
67
  if (options.reportDir) userConfig.reportDir = path.resolve(options.reportDir);
66
68
  if (options.screenDir) userConfig.screenDir = path.resolve(options.screenDir); // NOTE: Hack to pass typescript checking
@@ -1,5 +1,8 @@
1
+ import { denormalizeStoryParameters } from '../shared';
1
2
  import { subscribeOn } from './messages';
2
3
  import { loadTestsFromStories, saveStoriesJson, saveTestsJson } from './stories';
4
+ import { isStorybookVersionGreaterThan, isStorybookVersionLessThan } from './storybook/helpers';
5
+ import { extractStoriesData } from './storybook/providers/nodejs';
3
6
  export default async function extract(config, options) {
4
7
  if (config.useWebpackToExtractTests && process.env.__CREEVEY_ENV__ != 'test') {
5
8
  await new Promise((resolve, reject) => {
@@ -16,12 +19,13 @@ export default async function extract(config, options) {
16
19
  });
17
20
  }
18
21
 
19
- const tests = await loadTestsFromStories(Object.keys(config.browsers), async listener => {
20
- const stories = await config.storiesProvider(config, {
22
+ const tests = await loadTestsFromStories(Object.keys(config.browsers), async () => {
23
+ const data = await extractStoriesData(config, {
21
24
  watch: false,
22
25
  debug: options.debug
23
- }, listener);
24
- if (options.extract) saveStoriesJson(stories, options.extract);
26
+ });
27
+ const stories = isStorybookVersionLessThan(6) || isStorybookVersionGreaterThan(6, 3) ? data.stories : denormalizeStoryParameters(data);
28
+ if (options.extract) saveStoriesJson(data, options.extract);
25
29
  return stories;
26
30
  });
27
31
  if (options.tests) saveTestsJson(tests); // eslint-disable-next-line no-process-exit
@@ -1,6 +1,5 @@
1
1
  import cluster from 'cluster';
2
2
  import { readConfig, defaultBrowser } from './config';
3
- import { noop } from '../types';
4
3
  import { logger } from './logger'; // NOTE: Impure function, mutate config by adding gridUrl prop
5
4
 
6
5
  async function startWebdriverServer(config, options) {
@@ -22,8 +21,7 @@ export default async function (options) {
22
21
  ui,
23
22
  port
24
23
  } = options;
25
- if (!config) return;
26
- const resolveApi = ui && cluster.isMaster ? (await import('./master/server')).default(config.reportDir, port) : noop; // NOTE: We don't need docker nor selenoid for webpack or update options
24
+ if (!config) return; // NOTE: We don't need docker nor selenoid for webpack or update options
27
25
 
28
26
  if (!(config.gridUrl || Object.values(config.browsers).every(({
29
27
  gridUrl
@@ -51,6 +49,7 @@ export default async function (options) {
51
49
  case cluster.isMaster:
52
50
  {
53
51
  logger.info('Starting Master Process');
52
+ const resolveApi = (await import('./master/server')).default(config.reportDir, port, ui);
54
53
  return (await import('./master')).default(config, options, resolveApi);
55
54
  }
56
55
 
@@ -1,10 +1,10 @@
1
1
  import path from 'path';
2
- import { writeFileSync, copyFile, readdir, mkdir, readdirSync, existsSync } from 'fs';
2
+ import { writeFileSync, copyFile, readdir, mkdir, existsSync } from 'fs';
3
3
  import { promisify } from 'util';
4
4
  import master from './master';
5
5
  import creeveyApi from './api';
6
6
  import { isDefined } from '../../types';
7
- import { shutdown, shutdownWorkers, testsToImages } from '../utils';
7
+ import { shutdown, shutdownWorkers, testsToImages, readDirRecursive } from '../utils';
8
8
  import { subscribeOn } from '../messages';
9
9
  import { logger } from '../logger';
10
10
  const copyFileAsync = promisify(copyFile);
@@ -37,12 +37,6 @@ function reportDataModule(data) {
37
37
  `;
38
38
  }
39
39
 
40
- function readDirRecursive(dirPath) {
41
- return [].concat(...readdirSync(dirPath, {
42
- withFileTypes: true
43
- }).map(dirent => dirent.isDirectory() ? readDirRecursive(`${dirPath}/${dirent.name}`) : [`${dirPath}/${dirent.name}`]));
44
- }
45
-
46
40
  function outputUnnecessaryImages(imagesDir, images) {
47
41
  if (!existsSync(imagesDir)) return;
48
42
  const unnecessaryImages = readDirRecursive(imagesDir).map(imagePath => path.posix.relative(imagesDir, imagePath)).filter(imagePath => !images.has(imagePath));
@@ -86,9 +80,9 @@ export default async function (config, options, resolveApi) {
86
80
  });
87
81
  runner = await master(config, {
88
82
  watch: options.ui,
89
- debug: options.debug
83
+ debug: options.debug,
84
+ port: options.port
90
85
  });
91
- await runner.init();
92
86
 
93
87
  if (options.saveReport) {
94
88
  await copyStatics(config.reportDir);
@@ -27,6 +27,7 @@ export default async function master(config, options) {
27
27
  } catch (error) {// Ignore error
28
28
  }
29
29
 
30
+ await runner.init();
30
31
  const tests = await loadTestsFromStories(Object.keys(config.browsers), listener => config.storiesProvider(config, options, listener), testsDiff => {
31
32
  runner.updateTests(testsDiff);
32
33
  saveTestsJson(runner.tests, config.reportDir);
@@ -2,8 +2,8 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope
2
2
 
3
3
  import cluster from 'cluster';
4
4
  import { EventEmitter } from 'events';
5
- import { isWorkerMessage, isTestMessage } from '../../types';
6
- import { sendTestMessage, sendShutdownMessage } from '../messages';
5
+ import { isWorkerMessage } from '../../types';
6
+ import { sendTestMessage, sendShutdownMessage, subscribeOnWorker } from '../messages';
7
7
  import { isShuttingDown } from '../utils';
8
8
  const FORK_RETRIES = 5;
9
9
  export default class Pool extends EventEmitter {
@@ -151,37 +151,37 @@ export default class Pool extends EventEmitter {
151
151
  return test.retries < this.maxRetries && !this.forcedStop;
152
152
  }
153
153
 
154
+ handleTestResult(worker, test, result) {
155
+ const shouldRetry = result.status == 'failed' && this.shouldRetry(test);
156
+
157
+ if (shouldRetry) {
158
+ test.retries += 1;
159
+ this.queue[this.failFast ? 'unshift' : 'push'](test);
160
+ }
161
+
162
+ this.sendStatus({
163
+ id: test.id,
164
+ status: shouldRetry ? 'retrying' : result.status,
165
+ result
166
+ });
167
+ worker.isRunning = false;
168
+ setImmediate(() => this.process());
169
+ }
170
+
154
171
  subscribe(worker, test) {
155
- worker.once('message', message => {
156
- if (!isWorkerMessage(message) && !isTestMessage(message)) return;
157
- if (message.type != 'end' && message.type != 'error') return;
158
- let result;
159
-
160
- if (message.type == 'error') {
161
- this.gracefullyKill(worker);
162
- result = {
163
- status: 'failed',
164
- ...message.payload
165
- };
166
- } else {
167
- result = message.payload;
168
- }
169
-
170
- const shouldRetry = result.status == 'failed' && this.shouldRetry(test);
171
-
172
- if (shouldRetry) {
173
- test.retries += 1;
174
- this.queue[this.failFast ? 'unshift' : 'push'](test);
175
- }
176
-
177
- this.sendStatus({
178
- id: test.id,
179
- status: shouldRetry ? 'retrying' : result.status,
180
- result
172
+ const subscriptions = [subscribeOnWorker(worker, 'worker', message => {
173
+ if (message.type != 'error') return;
174
+ subscriptions.forEach(unsubscribe => unsubscribe());
175
+ this.gracefullyKill(worker);
176
+ this.handleTestResult(worker, test, {
177
+ status: 'failed',
178
+ ...message.payload
181
179
  });
182
- worker.isRunning = false;
183
- this.process();
184
- });
180
+ }), subscribeOnWorker(worker, 'test', message => {
181
+ if (message.type != 'end') return;
182
+ subscriptions.forEach(unsubscribe => unsubscribe());
183
+ this.handleTestResult(worker, test, message.payload);
184
+ })];
185
185
  }
186
186
 
187
187
  }
@@ -1,22 +1,90 @@
1
1
  import path from 'path';
2
2
  import http from 'http';
3
+ import cluster from 'cluster';
3
4
  import Koa from 'koa';
5
+ import cors from '@koa/cors';
4
6
  import serve from 'koa-static';
5
7
  import mount from 'koa-mount';
8
+ import body from 'koa-bodyparser';
6
9
  import WebSocket from 'ws';
7
- import { subscribeOn } from '../messages';
8
- import { noop } from '../../types';
10
+ import { emitStoriesMessage, sendStoriesMessage, subscribeOn, subscribeOnWorker } from '../messages';
11
+ import { isDefined, noop } from '../../types';
9
12
  import { logger } from '../logger';
10
- export default function server(reportDir, port) {
13
+ import { deserializeStory } from '../../shared';
14
+ export default function server(reportDir, port, ui) {
11
15
  let resolveApi = noop;
16
+ let setStoriesCounter = 0;
12
17
  const creeveyApi = new Promise(resolve => resolveApi = resolve);
13
18
  const app = new Koa();
14
19
  const server = http.createServer(app.callback());
15
20
  const wss = new WebSocket.Server({
16
21
  server
17
22
  });
18
- app.use(async (_, next) => {
19
- await creeveyApi;
23
+ app.use(cors());
24
+ app.use(body());
25
+ app.use(async (ctx, next) => {
26
+ if (ctx.method == 'GET' && ctx.path == '/ping') {
27
+ ctx.body = 'pong';
28
+ return;
29
+ }
30
+
31
+ await next();
32
+ });
33
+
34
+ if (ui) {
35
+ app.use(async (_, next) => {
36
+ await creeveyApi;
37
+ await next();
38
+ });
39
+ }
40
+
41
+ app.use(async (ctx, next) => {
42
+ if (ctx.method == 'POST' && ctx.path == '/stories') {
43
+ const {
44
+ setStoriesCounter: counter,
45
+ stories
46
+ } = ctx.request.body;
47
+ if (setStoriesCounter >= counter) return;
48
+ const deserializedStories = stories.map(([file, stories]) => [file, stories.map(deserializeStory)]);
49
+ setStoriesCounter = counter;
50
+ emitStoriesMessage({
51
+ type: 'update',
52
+ payload: deserializedStories
53
+ });
54
+ Object.values(cluster.workers).filter(isDefined).filter(worker => worker.isConnected()).forEach(worker => sendStoriesMessage(worker, {
55
+ type: 'update',
56
+ payload: deserializedStories
57
+ }));
58
+ return;
59
+ }
60
+
61
+ await next();
62
+ });
63
+ app.use(async (ctx, next) => {
64
+ if (ctx.method == 'POST' && ctx.path == '/capture') {
65
+ const {
66
+ workerId,
67
+ options
68
+ } = ctx.request.body;
69
+ const worker = Object.values(cluster.workers).filter(isDefined).find(worker => worker.process.pid == workerId); // NOTE: Hypothetical case when someone send to us capture req and we don't have a worker with browser session for it
70
+
71
+ if (!worker) return;
72
+ await new Promise(resolve => {
73
+ const unsubscribe = subscribeOnWorker(worker, 'stories', message => {
74
+ if (message.type != 'capture') return;
75
+ unsubscribe();
76
+ resolve();
77
+ });
78
+ sendStoriesMessage(worker, {
79
+ type: 'capture',
80
+ payload: options
81
+ });
82
+ }); // TODO Pass screenshot result to show it in inspector
83
+
84
+ ctx.body = 'Ok';
85
+ return;
86
+ }
87
+
20
88
  await next();
21
89
  });
22
90
  app.use(serve(path.join(__dirname, '../../client/web')));
@@ -14,6 +14,12 @@ export function emitWorkerMessage(message) {
14
14
  ...message
15
15
  });
16
16
  }
17
+ export function emitStoriesMessage(message) {
18
+ return emitMessage({
19
+ scope: 'stories',
20
+ ...message
21
+ });
22
+ }
17
23
  export function emitTestMessage(message) {
18
24
  return emitMessage({
19
25
  scope: 'test',
@@ -37,19 +43,28 @@ export function emitShutdownMessage() {
37
43
  scope: 'shutdown'
38
44
  });
39
45
  }
40
- const handlers = Object.assign(Object.create(null), {
41
- worker: new Set(),
42
- test: new Set(),
43
- webpack: new Set(),
44
- docker: new Set(),
45
- shutdown: new Set()
46
- });
46
+
47
+ function createHandlers() {
48
+ return Object.assign(Object.create(null), {
49
+ worker: new Set(),
50
+ stories: new Set(),
51
+ test: new Set(),
52
+ webpack: new Set(),
53
+ docker: new Set(),
54
+ shutdown: new Set()
55
+ });
56
+ }
57
+
58
+ const handlers = createHandlers();
47
59
 
48
60
  const handler = message => {
49
61
  switch (message.scope) {
50
62
  case 'worker':
51
63
  return handlers.worker.forEach(h => h(message));
52
64
 
65
+ case 'stories':
66
+ return handlers.stories.forEach(h => h(message));
67
+
53
68
  case 'test':
54
69
  return handlers.test.forEach(h => h(message));
55
70
 
@@ -65,26 +80,34 @@ const handler = message => {
65
80
  };
66
81
 
67
82
  process.on('message', handler);
68
- export function sendTestMessage(target, message) {
83
+ export function sendStoriesMessage(target, message) {
69
84
  var _target$send;
70
85
 
71
86
  (_target$send = target.send) === null || _target$send === void 0 ? void 0 : _target$send.call(target, {
72
- scope: 'test',
87
+ scope: 'stories',
73
88
  ...message
74
89
  });
75
90
  }
76
- export function sendDockerMessage(target, message) {
91
+ export function sendTestMessage(target, message) {
77
92
  var _target$send2;
78
93
 
79
94
  (_target$send2 = target.send) === null || _target$send2 === void 0 ? void 0 : _target$send2.call(target, {
80
- scope: 'docker',
95
+ scope: 'test',
81
96
  ...message
82
97
  });
83
98
  }
84
- export function sendShutdownMessage(target) {
99
+ export function sendDockerMessage(target, message) {
85
100
  var _target$send3;
86
101
 
87
102
  (_target$send3 = target.send) === null || _target$send3 === void 0 ? void 0 : _target$send3.call(target, {
103
+ scope: 'docker',
104
+ ...message
105
+ });
106
+ }
107
+ export function sendShutdownMessage(target) {
108
+ var _target$send4;
109
+
110
+ (_target$send4 = target.send) === null || _target$send4 === void 0 ? void 0 : _target$send4.call(target, {
88
111
  scope: 'shutdown'
89
112
  });
90
113
  }
@@ -97,6 +120,13 @@ export function subscribeOn(scope, handler) {
97
120
  return () => handlers.worker.delete(workerHandler);
98
121
  }
99
122
 
123
+ case 'stories':
124
+ {
125
+ const storiesHandler = handler;
126
+ handlers.stories.add(storiesHandler);
127
+ return () => handlers.stories.delete(storiesHandler);
128
+ }
129
+
100
130
  case 'test':
101
131
  {
102
132
  const testHandler = handler;
@@ -125,4 +155,80 @@ export function subscribeOn(scope, handler) {
125
155
  return () => handlers.shutdown.delete(shutdownHandler);
126
156
  }
127
157
  }
158
+ }
159
+ const workers = new Map();
160
+ export function subscribeOnWorker(worker, scope, handler) {
161
+ var _workers$get;
162
+
163
+ const workerHandlers = (_workers$get = workers.get(worker)) !== null && _workers$get !== void 0 ? _workers$get : createHandlers();
164
+
165
+ if (!workers.has(worker)) {
166
+ workers.set(worker, workerHandlers);
167
+ worker.once('exit', () => workers.delete(worker));
168
+ worker.on('message', message => {
169
+ switch (message.scope) {
170
+ case 'worker':
171
+ return workerHandlers.worker.forEach(h => h(message));
172
+
173
+ case 'stories':
174
+ return workerHandlers.stories.forEach(h => h(message));
175
+
176
+ case 'test':
177
+ return workerHandlers.test.forEach(h => h(message));
178
+
179
+ case 'webpack':
180
+ return workerHandlers.webpack.forEach(h => h(message));
181
+
182
+ case 'docker':
183
+ return workerHandlers.docker.forEach(h => h(message));
184
+
185
+ case 'shutdown':
186
+ return workerHandlers.shutdown.forEach(h => h(message));
187
+ }
188
+ });
189
+ }
190
+
191
+ switch (scope) {
192
+ case 'worker':
193
+ {
194
+ const workerHandler = handler;
195
+ workerHandlers.worker.add(workerHandler);
196
+ return () => workerHandlers.worker.delete(workerHandler);
197
+ }
198
+
199
+ case 'stories':
200
+ {
201
+ const storiesHandler = handler;
202
+ workerHandlers.stories.add(storiesHandler);
203
+ return () => workerHandlers.stories.delete(storiesHandler);
204
+ }
205
+
206
+ case 'test':
207
+ {
208
+ const testHandler = handler;
209
+ workerHandlers.test.add(testHandler);
210
+ return () => workerHandlers.test.delete(testHandler);
211
+ }
212
+
213
+ case 'webpack':
214
+ {
215
+ const webpackHandler = handler;
216
+ workerHandlers.webpack.add(webpackHandler);
217
+ return () => workerHandlers.webpack.delete(webpackHandler);
218
+ }
219
+
220
+ case 'docker':
221
+ {
222
+ const dockerHandler = handler;
223
+ workerHandlers.docker.add(dockerHandler);
224
+ return () => workerHandlers.docker.delete(dockerHandler);
225
+ }
226
+
227
+ case 'shutdown':
228
+ {
229
+ const shutdownHandler = handler;
230
+ workerHandlers.shutdown.add(shutdownHandler);
231
+ return () => workerHandlers.shutdown.delete(shutdownHandler);
232
+ }
233
+ }
128
234
  }
@@ -0,0 +1,63 @@
1
+ import { toId, storyNameFromExport } from '@storybook/csf';
2
+ export default async function parse(files) {
3
+ result = {}; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
4
+
5
+ (await import('@babel/register')).default({
6
+ babelrc: false,
7
+ rootMode: 'upward-optional',
8
+ ignore: [/node_modules/],
9
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
10
+ plugins: [['@babel/plugin-transform-runtime']],
11
+ presets: ['@babel/preset-typescript', ['@babel/preset-env', {
12
+ targets: {
13
+ node: '10'
14
+ }
15
+ }]]
16
+ });
17
+ await Promise.all(files.map(async file => import(file)));
18
+ return result;
19
+ }
20
+ let result = {};
21
+ let kindTitle = '';
22
+ let storyTitle = '';
23
+ let storyParams = null;
24
+
25
+ const setStoryParameters = params => {
26
+ storyParams = params;
27
+ };
28
+
29
+ const getStoryId = (kindTitle, storyTitle) => {
30
+ return toId(kindTitle, storyNameFromExport(storyTitle));
31
+ };
32
+
33
+ export const kind = (title, kindFn) => {
34
+ kindTitle = title;
35
+ kindFn();
36
+ kindTitle = '';
37
+ };
38
+ export const story = (title, storyFn) => {
39
+ var _result$storyId;
40
+
41
+ storyTitle = title;
42
+ storyParams = null;
43
+ storyFn({
44
+ setStoryParameters
45
+ });
46
+ const storyId = getStoryId(kindTitle, storyTitle);
47
+ result[storyId] = Object.assign({}, storyParams, {
48
+ tests: (_result$storyId = result[storyId]) === null || _result$storyId === void 0 ? void 0 : _result$storyId.tests
49
+ });
50
+ storyTitle = '';
51
+ storyParams = null;
52
+ };
53
+ export const test = (title, testFn) => {
54
+ const storyId = getStoryId(kindTitle, storyTitle);
55
+
56
+ if (!result[storyId]) {
57
+ result[storyId] = {};
58
+ }
59
+
60
+ result[storyId].tests = Object.assign({}, result[storyId].tests, {
61
+ [title]: testFn
62
+ });
63
+ };