creevey 0.9.0-beta.2 → 0.9.0-beta.20

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 (226) hide show
  1. package/.yarn/install-state.gz +0 -0
  2. package/.yarnrc.yml +1 -0
  3. package/CHANGELOG.md +51 -0
  4. package/README.md +9 -1
  5. package/addon/README.md +3 -0
  6. package/addon/package.json +5 -0
  7. package/docs/config.md +29 -26
  8. package/jest.config.js +6 -0
  9. package/lib/cjs/cli.js +1 -0
  10. package/lib/cjs/client/addon/Manager.js +170 -390
  11. package/lib/cjs/client/addon/components/Addon.js +17 -45
  12. package/lib/cjs/client/addon/components/Icons.js +12 -14
  13. package/lib/cjs/client/addon/components/Panel.js +21 -30
  14. package/lib/cjs/client/addon/components/TestSelect.js +20 -31
  15. package/lib/cjs/client/addon/components/Tools.js +35 -65
  16. package/lib/cjs/client/addon/decorator.js +1 -4
  17. package/lib/cjs/client/addon/index.js +27 -0
  18. package/lib/cjs/client/addon/preset.js +3 -76
  19. package/lib/cjs/client/addon/preview.js +11 -0
  20. package/lib/cjs/client/addon/readyForCapture.js +1 -4
  21. package/lib/cjs/client/addon/register.js +43 -82
  22. package/lib/cjs/client/addon/utils.js +4 -8
  23. package/lib/cjs/client/addon/withCreevey.js +145 -404
  24. package/lib/cjs/client/shared/components/ImagesView/BlendView.js +25 -35
  25. package/lib/cjs/client/shared/components/ImagesView/ImagesView.js +29 -41
  26. package/lib/cjs/client/shared/components/ImagesView/SideBySideView.js +46 -83
  27. package/lib/cjs/client/shared/components/ImagesView/SlideView.js +39 -67
  28. package/lib/cjs/client/shared/components/ImagesView/SwapView.js +26 -57
  29. package/lib/cjs/client/shared/components/ImagesView/index.js +9 -14
  30. package/lib/cjs/client/shared/components/PageFooter/PageFooter.js +13 -16
  31. package/lib/cjs/client/shared/components/PageFooter/Paging.js +16 -37
  32. package/lib/cjs/client/shared/components/PageHeader/ImagePreview.js +42 -34
  33. package/lib/cjs/client/shared/components/PageHeader/PageHeader.js +40 -84
  34. package/lib/cjs/client/shared/components/ResultsPage.js +42 -99
  35. package/lib/cjs/client/shared/creeveyClientApi.js +56 -93
  36. package/lib/cjs/client/shared/helpers.js +149 -274
  37. package/lib/cjs/client/shared/viewMode.js +5 -9
  38. package/lib/cjs/client/web/192.js +1 -0
  39. package/lib/cjs/client/web/632.js +43 -0
  40. package/lib/cjs/client/web/794.js +1 -0
  41. package/lib/cjs/client/web/main.js +79 -38
  42. package/lib/cjs/client/web/main.js.LICENSE.txt +34 -0
  43. package/lib/cjs/creevey.js +15 -30
  44. package/lib/cjs/index.js +0 -15
  45. package/lib/cjs/server/config.js +16 -36
  46. package/lib/cjs/server/docker.js +8 -34
  47. package/lib/cjs/server/index.js +9 -34
  48. package/lib/cjs/server/logger.js +7 -20
  49. package/lib/cjs/server/master/api.js +1 -14
  50. package/lib/cjs/server/master/index.js +25 -49
  51. package/lib/cjs/server/master/master.js +6 -21
  52. package/lib/cjs/server/master/pool.js +10 -53
  53. package/lib/cjs/server/master/runner.js +65 -105
  54. package/lib/cjs/server/master/server.js +10 -29
  55. package/lib/cjs/server/messages.js +14 -62
  56. package/lib/cjs/server/selenium/browser.js +149 -185
  57. package/lib/cjs/server/selenium/index.js +0 -4
  58. package/lib/cjs/server/selenium/selenoid.js +18 -44
  59. package/lib/cjs/server/stories.js +35 -57
  60. package/lib/cjs/server/storybook/providers/browser.js +15 -29
  61. package/lib/cjs/server/storybook/providers/hybrid.js +16 -37
  62. package/lib/cjs/server/telemetry.js +167 -0
  63. package/lib/cjs/server/testsFiles/parser.js +3 -19
  64. package/lib/cjs/server/testsFiles/register.js +8 -14
  65. package/lib/cjs/server/update.js +4 -25
  66. package/lib/cjs/server/utils.js +35 -76
  67. package/lib/cjs/server/worker/chai-image.js +1 -27
  68. package/lib/cjs/server/worker/helpers.js +2 -12
  69. package/lib/cjs/server/worker/index.js +1 -3
  70. package/lib/cjs/server/worker/reporter.js +16 -43
  71. package/lib/cjs/server/worker/worker.js +32 -72
  72. package/lib/cjs/shared/index.js +87 -0
  73. package/lib/cjs/shared/serializeRegExp.js +34 -0
  74. package/lib/cjs/types.js +11 -20
  75. package/lib/esm/cli.js +1 -1
  76. package/lib/esm/client/addon/Manager.js +170 -381
  77. package/lib/esm/client/addon/components/Addon.js +15 -34
  78. package/lib/esm/client/addon/components/Icons.js +10 -6
  79. package/lib/esm/client/addon/components/Panel.js +20 -18
  80. package/lib/esm/client/addon/components/TestSelect.js +19 -23
  81. package/lib/esm/client/addon/components/Tools.js +33 -49
  82. package/lib/esm/client/addon/decorator.js +1 -1
  83. package/lib/esm/client/addon/index.js +2 -0
  84. package/lib/esm/client/addon/preset.js +2 -56
  85. package/lib/esm/client/addon/preview.js +5 -0
  86. package/lib/esm/client/addon/readyForCapture.js +1 -3
  87. package/lib/esm/client/addon/register.js +41 -67
  88. package/lib/esm/client/addon/utils.js +3 -7
  89. package/lib/esm/client/addon/withCreevey.js +142 -388
  90. package/lib/esm/client/shared/components/ImagesView/BlendView.js +22 -18
  91. package/lib/esm/client/shared/components/ImagesView/ImagesView.js +27 -25
  92. package/lib/esm/client/shared/components/ImagesView/SideBySideView.js +43 -63
  93. package/lib/esm/client/shared/components/ImagesView/SlideView.js +36 -47
  94. package/lib/esm/client/shared/components/ImagesView/SwapView.js +23 -40
  95. package/lib/esm/client/shared/components/PageFooter/PageFooter.js +12 -8
  96. package/lib/esm/client/shared/components/PageFooter/Paging.js +15 -29
  97. package/lib/esm/client/shared/components/PageHeader/ImagePreview.js +40 -25
  98. package/lib/esm/client/shared/components/PageHeader/PageHeader.js +38 -66
  99. package/lib/esm/client/shared/components/ResultsPage.js +39 -75
  100. package/lib/esm/client/shared/creeveyClientApi.js +56 -90
  101. package/lib/esm/client/shared/helpers.js +133 -230
  102. package/lib/esm/client/shared/viewMode.js +4 -4
  103. package/lib/esm/client/web/192.js +1 -0
  104. package/lib/esm/client/web/632.js +43 -0
  105. package/lib/esm/client/web/794.js +1 -0
  106. package/lib/esm/client/web/index.html +19 -0
  107. package/lib/esm/client/web/main.js +79 -0
  108. package/lib/esm/client/web/main.js.LICENSE.txt +34 -0
  109. package/lib/esm/creevey.js +13 -16
  110. package/lib/esm/index.js +1 -4
  111. package/lib/esm/server/config.js +9 -16
  112. package/lib/esm/server/docker.js +6 -14
  113. package/lib/esm/server/index.js +8 -22
  114. package/lib/esm/server/logger.js +0 -1
  115. package/lib/esm/server/master/api.js +0 -9
  116. package/lib/esm/server/master/index.js +25 -35
  117. package/lib/esm/server/master/master.js +2 -7
  118. package/lib/esm/server/master/pool.js +8 -41
  119. package/lib/esm/server/master/runner.js +64 -90
  120. package/lib/esm/server/master/server.js +9 -11
  121. package/lib/esm/server/messages.js +8 -42
  122. package/lib/esm/server/selenium/browser.js +147 -163
  123. package/lib/esm/server/selenium/selenoid.js +16 -27
  124. package/lib/esm/server/stories.js +34 -46
  125. package/lib/esm/server/storybook/providers/browser.js +12 -17
  126. package/lib/esm/server/storybook/providers/hybrid.js +11 -22
  127. package/lib/esm/server/telemetry.js +160 -0
  128. package/lib/esm/server/testsFiles/parser.js +0 -6
  129. package/lib/esm/server/testsFiles/register.js +6 -7
  130. package/lib/esm/server/update.js +1 -13
  131. package/lib/esm/server/utils.js +20 -41
  132. package/lib/esm/server/worker/chai-image.js +0 -21
  133. package/lib/esm/server/worker/helpers.js +2 -9
  134. package/lib/esm/server/worker/reporter.js +15 -29
  135. package/lib/esm/server/worker/worker.js +31 -48
  136. package/lib/esm/shared/index.js +77 -0
  137. package/lib/esm/shared/serializeRegExp.js +24 -0
  138. package/lib/esm/types.js +5 -1
  139. package/lib/types/client/addon/Manager.d.ts +3 -3
  140. package/lib/types/client/addon/components/Addon.d.ts +1 -0
  141. package/lib/types/client/addon/components/Icons.d.ts +1 -0
  142. package/lib/types/client/addon/components/Panel.d.ts +1 -0
  143. package/lib/types/client/addon/components/Tools.d.ts +1 -0
  144. package/lib/types/client/addon/decorator.d.ts +1 -1
  145. package/lib/types/client/addon/index.d.ts +2 -0
  146. package/lib/types/client/addon/preset.d.ts +2 -24
  147. package/lib/types/client/addon/preview.d.ts +4 -0
  148. package/lib/types/client/addon/utils.d.ts +1 -0
  149. package/lib/types/client/addon/withCreevey.d.ts +4 -3
  150. package/lib/types/client/shared/components/ImagesView/BlendView.d.ts +3 -1
  151. package/lib/types/client/shared/components/ImagesView/SideBySideView.d.ts +3 -1
  152. package/lib/types/client/shared/components/ImagesView/SlideView.d.ts +3 -1
  153. package/lib/types/client/shared/components/ImagesView/SwapView.d.ts +3 -1
  154. package/lib/types/client/shared/components/PageHeader/ImagePreview.d.ts +3 -1
  155. package/lib/types/client/shared/components/ResultsPage.d.ts +3 -1
  156. package/lib/types/client/web/CreeveyLoader.d.ts +1 -1
  157. package/lib/types/client/web/CreeveyView/SideBar/Checkbox.d.ts +6 -3
  158. package/lib/types/client/web/CreeveyView/SideBar/Search.d.ts +1 -0
  159. package/lib/types/client/web/CreeveyView/SideBar/SuiteLink.d.ts +19 -14
  160. package/lib/types/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +3 -1
  161. package/lib/types/client/web/CreeveyView/SideBar/TestsStatus.d.ts +3 -1
  162. package/lib/types/client/web/CreeveyView/SideBar/Toggle.d.ts +1 -0
  163. package/lib/types/client/web/KeyboardEventsContext.d.ts +4 -2
  164. package/lib/types/index.d.ts +4 -1
  165. package/lib/types/server/logger.d.ts +6 -2
  166. package/lib/types/server/messages.d.ts +14 -12
  167. package/lib/types/server/selenium/browser.d.ts +5 -3
  168. package/lib/types/server/storybook/providers/browser.d.ts +2 -4
  169. package/lib/types/server/storybook/providers/hybrid.d.ts +2 -4
  170. package/lib/types/server/telemetry.d.ts +2 -0
  171. package/lib/types/server/utils.d.ts +5 -1
  172. package/lib/types/shared/index.d.ts +7 -0
  173. package/lib/types/shared/serializeRegExp.d.ts +9 -0
  174. package/lib/types/types.d.ts +29 -36
  175. package/package.json +132 -133
  176. package/types/global.d.ts +5 -0
  177. package/lib/cjs/client/web/1.js +0 -13
  178. package/lib/cjs/client/web/2.js +0 -1
  179. package/lib/cjs/server/extract.js +0 -50
  180. package/lib/cjs/server/loaders/babel/creevey-plugin.js +0 -88
  181. package/lib/cjs/server/loaders/babel/helpers.js +0 -479
  182. package/lib/cjs/server/loaders/babel/register.js +0 -126
  183. package/lib/cjs/server/loaders/hooks/mdx.js +0 -30
  184. package/lib/cjs/server/loaders/hooks/svelte.js +0 -65
  185. package/lib/cjs/server/loaders/webpack/compile.js +0 -286
  186. package/lib/cjs/server/loaders/webpack/creevey-loader.js +0 -174
  187. package/lib/cjs/server/loaders/webpack/dummy-hmr.js +0 -44
  188. package/lib/cjs/server/loaders/webpack/mdx-loader.js +0 -72
  189. package/lib/cjs/server/loaders/webpack/start.js +0 -41
  190. package/lib/cjs/server/storybook/entry.js +0 -68
  191. package/lib/cjs/server/storybook/helpers.js +0 -165
  192. package/lib/cjs/server/storybook/providers/nodejs.js +0 -239
  193. package/lib/cjs/shared.js +0 -124
  194. package/lib/esm/server/extract.js +0 -34
  195. package/lib/esm/server/loaders/babel/creevey-plugin.js +0 -74
  196. package/lib/esm/server/loaders/babel/helpers.js +0 -462
  197. package/lib/esm/server/loaders/babel/register.js +0 -105
  198. package/lib/esm/server/loaders/hooks/mdx.js +0 -15
  199. package/lib/esm/server/loaders/hooks/svelte.js +0 -49
  200. package/lib/esm/server/loaders/webpack/compile.js +0 -263
  201. package/lib/esm/server/loaders/webpack/creevey-loader.js +0 -153
  202. package/lib/esm/server/loaders/webpack/dummy-hmr.js +0 -36
  203. package/lib/esm/server/loaders/webpack/mdx-loader.js +0 -58
  204. package/lib/esm/server/loaders/webpack/start.js +0 -27
  205. package/lib/esm/server/storybook/entry.js +0 -44
  206. package/lib/esm/server/storybook/helpers.js +0 -106
  207. package/lib/esm/server/storybook/providers/nodejs.js +0 -217
  208. package/lib/esm/shared.js +0 -93
  209. package/lib/types/server/extract.d.ts +0 -2
  210. package/lib/types/server/loaders/babel/creevey-plugin.d.ts +0 -1
  211. package/lib/types/server/loaders/babel/helpers.d.ts +0 -19
  212. package/lib/types/server/loaders/babel/register.d.ts +0 -5
  213. package/lib/types/server/loaders/hooks/mdx.d.ts +0 -1
  214. package/lib/types/server/loaders/hooks/svelte.d.ts +0 -1
  215. package/lib/types/server/loaders/webpack/compile.d.ts +0 -2
  216. package/lib/types/server/loaders/webpack/creevey-loader.d.ts +0 -2
  217. package/lib/types/server/loaders/webpack/dummy-hmr.d.ts +0 -10
  218. package/lib/types/server/loaders/webpack/mdx-loader.d.ts +0 -6
  219. package/lib/types/server/loaders/webpack/start.d.ts +0 -1
  220. package/lib/types/server/storybook/entry.d.ts +0 -18
  221. package/lib/types/server/storybook/helpers.d.ts +0 -24
  222. package/lib/types/server/storybook/providers/nodejs.d.ts +0 -9
  223. package/lib/types/shared.d.ts +0 -16
  224. package/preset.js +0 -9
  225. package/storybook-static/stories.json +0 -21
  226. package/types/mdx.d.ts +0 -6
@@ -2,7 +2,6 @@ import path from 'path';
2
2
  import fs, { mkdirSync } from 'fs';
3
3
  import micromatch from 'micromatch';
4
4
  import { isDefined } from '../types';
5
-
6
5
  function tryToLoadTestsData(filename) {
7
6
  try {
8
7
  // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -11,22 +10,15 @@ function tryToLoadTestsData(filename) {
11
10
  /* noop */
12
11
  }
13
12
  }
14
-
15
13
  const actualRegex = /^(.*)-actual-(\d+)\.png$/i;
16
-
17
14
  function approve(dirents, srcPath, dstPath, testPaths, isMatch) {
18
- dirents.filter(dirent => dirent.isFile()).map(dirent => actualRegex.exec(dirent.name)).filter(isDefined).filter(([fileName, imageName]) => !testPaths || testPaths.find(([token]) => token == imageName) && isMatch(path.join(srcPath, fileName))).reduce((images, [, imageName, retry]) => {
19
- var _images$get;
20
-
21
- return Number(retry) > ((_images$get = images.get(imageName)) !== null && _images$get !== void 0 ? _images$get : -1) ? images.set(imageName, Number(retry)) : images;
22
- }, new Map()).forEach((retry, imageName) => {
15
+ dirents.filter(dirent => dirent.isFile()).map(dirent => actualRegex.exec(dirent.name)).filter(isDefined).filter(([fileName, imageName]) => !testPaths || testPaths.find(([token]) => token == imageName) && isMatch(path.join(srcPath, fileName))).reduce((images, [, imageName, retry]) => Number(retry) > (images.get(imageName) ?? -1) ? images.set(imageName, Number(retry)) : images, new Map()).forEach((retry, imageName) => {
23
16
  mkdirSync(dstPath, {
24
17
  recursive: true
25
18
  });
26
19
  fs.copyFileSync(path.join(srcPath, `${imageName}-actual-${retry}.png`), path.join(dstPath, `${imageName}.png`));
27
20
  });
28
21
  }
29
-
30
22
  function traverse(srcPath, dstPath, testPaths, isMatch) {
31
23
  const dirents = fs.readdirSync(srcPath, {
32
24
  withFileTypes: true
@@ -34,7 +26,6 @@ function traverse(srcPath, dstPath, testPaths, isMatch) {
34
26
  approve(dirents, srcPath, dstPath, testPaths, isMatch);
35
27
  dirents.filter(dirent => dirent.isDirectory()).map(dirent => [dirent.name, testPaths === null || testPaths === void 0 ? void 0 : testPaths.map(([token, ...restPath]) => token == dirent.name ? restPath : null).filter(isDefined)]).filter(([, paths]) => !paths || paths.length > 0).forEach(([dirname, paths]) => traverse(path.join(srcPath, dirname), path.join(dstPath, dirname), paths, isMatch));
36
28
  }
37
-
38
29
  export default function update(config, grepPattern) {
39
30
  const {
40
31
  reportDir,
@@ -46,13 +37,11 @@ export default function update(config, grepPattern) {
46
37
  const testsMeta = tryToLoadTestsData(`${reportDir}/tests.json`);
47
38
  const testsReport = tryToLoadTestsData(`${reportDir}/data`);
48
39
  let testPaths = null;
49
-
50
40
  if (testsMeta && testsReport) {
51
41
  testPaths = Object.values(testsMeta).filter(isDefined).filter(({
52
42
  id
53
43
  }) => {
54
44
  var _testsReport$id;
55
-
56
45
  return ((_testsReport$id = testsReport[id]) === null || _testsReport$id === void 0 ? void 0 : _testsReport$id.status) == 'failed';
57
46
  }).map(({
58
47
  storyPath,
@@ -60,6 +49,5 @@ export default function update(config, grepPattern) {
60
49
  browser
61
50
  }) => [...storyPath, ...(testName ? [testName] : []), browser]);
62
51
  }
63
-
64
52
  traverse(reportDir, screenDir, testPaths, value => isMatch(path.relative(reportDir, value)));
65
53
  }
@@ -10,48 +10,33 @@ export const isShuttingDown = {
10
10
  export const LOCALHOST_REGEXP = /(localhost|127\.0\.0\.1)/i;
11
11
  export const extensions = ['.js', '.jsx', '.ts', '.tsx'];
12
12
  export const skipOptionKeys = ['in', 'kinds', 'stories', 'tests', 'reason'];
13
-
14
13
  function matchBy(pattern, value) {
15
14
  return typeof pattern == 'string' && pattern == value || Array.isArray(pattern) && pattern.includes(value) || pattern instanceof RegExp && pattern.test(value) || !isDefined(pattern);
16
15
  }
17
-
18
16
  export function shouldSkip(browser, meta, skipOptions, test) {
19
17
  if (typeof skipOptions != 'object') {
20
18
  return skipOptions;
21
19
  }
22
-
23
- if (Array.isArray(skipOptions)) {
24
- for (const skip of skipOptions) {
25
- const reason = shouldSkip(browser, meta, skip, test);
26
- if (reason) return reason;
27
- }
28
-
29
- return false;
30
- }
31
-
32
- let hasSkipOptionKeys = false;
33
-
34
20
  for (const skipKey in skipOptions) {
35
- if (skipOptionKeys.includes(skipKey)) {
36
- hasSkipOptionKeys = true;
37
- continue;
38
- }
39
-
40
- const reason = shouldSkip(browser, meta, {
41
- reason: skipKey,
42
- ...skipOptions[skipKey]
43
- }, test);
21
+ const reason = shouldSkipByOption(browser, meta, skipOptions[skipKey], skipKey, test);
44
22
  if (reason) return reason;
45
23
  }
46
-
47
- if (!hasSkipOptionKeys) return false;
24
+ return false;
25
+ }
26
+ export function shouldSkipByOption(browser, meta, skipOption, reason, test) {
27
+ if (Array.isArray(skipOption)) {
28
+ for (const skip of skipOption) {
29
+ const result = shouldSkipByOption(browser, meta, skip, reason, test);
30
+ if (result) return result;
31
+ }
32
+ return false;
33
+ }
48
34
  const {
49
35
  in: browsers,
50
36
  kinds,
51
37
  stories,
52
- tests,
53
- reason = true
54
- } = skipOptions;
38
+ tests
39
+ } = skipOption;
55
40
  const {
56
41
  kind,
57
42
  story
@@ -64,7 +49,7 @@ export function shouldSkip(browser, meta, skipOptions, test) {
64
49
  }
65
50
  export async function shutdownWorkers() {
66
51
  isShuttingDown.current = true;
67
- await Promise.all(Object.values(cluster.workers).filter(isDefined).filter(worker => worker.isConnected()).map(worker => new Promise(resolve => {
52
+ await Promise.all(Object.values(cluster.workers ?? {}).filter(isDefined).filter(worker => worker.isConnected()).map(worker => new Promise(resolve => {
68
53
  const timeout = setTimeout(() => worker.kill(), 10000);
69
54
  worker.on('exit', () => {
70
55
  clearTimeout(timeout);
@@ -96,27 +81,22 @@ export function testsToImages(tests) {
96
81
  storyPath,
97
82
  results
98
83
  }) => {
99
- var _results$slice$0$imag, _results$slice$;
100
-
101
- return Object.keys((_results$slice$0$imag = results === null || results === void 0 ? void 0 : (_results$slice$ = results.slice(-1)[0]) === null || _results$slice$ === void 0 ? void 0 : _results$slice$.images) !== null && _results$slice$0$imag !== void 0 ? _results$slice$0$imag : {}).map(image => `${[...storyPath, testName, browser, browser == image ? undefined : image].filter(isDefined).join('/')}.png`);
84
+ var _results$slice$;
85
+ return Object.keys((results === null || results === void 0 || (_results$slice$ = results.slice(-1)[0]) === null || _results$slice$ === void 0 ? void 0 : _results$slice$.images) ?? {}).map(image => `${[...storyPath, testName, browser, browser == image ? undefined : image].filter(isDefined).join('/')}.png`);
102
86
  })));
103
- } // https://tuhrig.de/how-to-know-you-are-inside-a-docker-container/
87
+ }
104
88
 
89
+ // https://tuhrig.de/how-to-know-you-are-inside-a-docker-container/
105
90
  export const isInsideDocker = existsSync('/proc/1/cgroup') && /docker/.test(readFileSync('/proc/1/cgroup', 'utf8'));
106
91
  export const downloadBinary = (downloadUrl, destination) => new Promise((resolve, reject) => get(downloadUrl, response => {
107
- var _response$statusCode2;
108
-
109
92
  if (response.statusCode == 302) {
110
- var _response$statusCode;
111
-
112
93
  const {
113
94
  location
114
95
  } = response.headers;
115
- if (!location) return reject(new Error(`Couldn't download selenoid. Status code: ${(_response$statusCode = response.statusCode) !== null && _response$statusCode !== void 0 ? _response$statusCode : 'UNKNOWN'}`));
96
+ if (!location) return reject(new Error(`Couldn't download selenoid. Status code: ${response.statusCode ?? 'UNKNOWN'}`));
116
97
  return resolve(downloadBinary(location, destination));
117
98
  }
118
-
119
- if (response.statusCode != 200) return reject(new Error(`Couldn't download selenoid. Status code: ${(_response$statusCode2 = response.statusCode) !== null && _response$statusCode2 !== void 0 ? _response$statusCode2 : 'UNKNOWN'}`));
99
+ if (response.statusCode != 200) return reject(new Error(`Couldn't download selenoid. Status code: ${response.statusCode ?? 'UNKNOWN'}`));
120
100
  const fileStream = createWriteStream(destination);
121
101
  response.pipe(fileStream);
122
102
  fileStream.on('finish', () => {
@@ -130,7 +110,6 @@ export const downloadBinary = (downloadUrl, destination) => new Promise((resolve
130
110
  }));
131
111
  export function removeProps(obj, propPath) {
132
112
  const [prop, ...restPath] = propPath;
133
-
134
113
  if (restPath.length > 0) {
135
114
  if (typeof prop == 'string') obj[prop] && removeProps(obj[prop], restPath);
136
115
  if (isFunction(prop)) Object.keys(obj).filter(prop).forEach(key => obj[key] && removeProps(obj[key], restPath));
@@ -1,13 +1,10 @@
1
1
  import { PNG } from 'pngjs';
2
2
  import pixelmatch from 'pixelmatch';
3
-
4
3
  function normalizeImageSize(image, width, height) {
5
4
  const normalizedImage = Buffer.alloc(4 * width * height);
6
-
7
5
  for (let y = 0; y < height; y++) {
8
6
  for (let x = 0; x < width; x++) {
9
7
  const i = (y * width + x) * 4;
10
-
11
8
  if (x < image.width && y < image.height) {
12
9
  const j = (y * image.width + x) * 4;
13
10
  normalizedImage[i + 0] = image.data[j + 0];
@@ -22,18 +19,14 @@ function normalizeImageSize(image, width, height) {
22
19
  }
23
20
  }
24
21
  }
25
-
26
22
  return normalizedImage;
27
23
  }
28
-
29
24
  function hasDiffPixels(diff) {
30
25
  for (let i = 0; i < diff.length; i += 4) {
31
26
  if (diff[i + 0] == 255 && diff[i + 1] == 0 && diff[i + 2] == 0 && diff[i + 3] == 255) return true;
32
27
  }
33
-
34
28
  return false;
35
29
  }
36
-
37
30
  function compareImages(expect, actual, diffOptions) {
38
31
  const expectImage = PNG.sync.read(expect);
39
32
  const actualImage = PNG.sync.read(actual);
@@ -44,42 +37,34 @@ function compareImages(expect, actual, diffOptions) {
44
37
  height
45
38
  });
46
39
  let actualImageData = actualImage.data;
47
-
48
40
  if (actualImage.width < width || actualImage.height < height) {
49
41
  actualImageData = normalizeImageSize(actualImage, width, height);
50
42
  }
51
-
52
43
  let expectImageData = expectImage.data;
53
-
54
44
  if (expectImage.width < width || expectImage.height < height) {
55
45
  expectImageData = normalizeImageSize(expectImage, width, height);
56
46
  }
57
-
58
47
  pixelmatch(expectImageData, actualImageData, diffImage.data, width, height, diffOptions);
59
48
  return {
60
49
  isEqual: !hasDiffPixels(diffImage.data),
61
50
  diff: PNG.sync.write(diffImage)
62
51
  };
63
52
  }
64
-
65
53
  export default function (getExpected, diffOptions) {
66
54
  return function chaiImage({
67
55
  Assertion
68
56
  }, utils) {
69
57
  async function assertImage(actual, imageName) {
70
58
  let onCompare = () => Promise.resolve();
71
-
72
59
  let expected = await getExpected(imageName);
73
60
  if (!(expected instanceof Buffer) && expected != null) ({
74
61
  expected,
75
62
  onCompare
76
63
  } = expected);
77
-
78
64
  if (expected == null) {
79
65
  await onCompare(actual);
80
66
  return imageName ? `Expected image '${imageName}' does not exists` : 'Expected image does not exists';
81
67
  }
82
-
83
68
  if (actual.equals(expected)) return await onCompare(actual);
84
69
  const {
85
70
  isEqual,
@@ -89,11 +74,9 @@ export default function (getExpected, diffOptions) {
89
74
  await onCompare(actual, expected, diff);
90
75
  return imageName ? `Expected image '${imageName}' to match` : 'Expected image to match';
91
76
  }
92
-
93
77
  utils.addMethod(Assertion.prototype, 'matchImage', async function matchImage(imageName) {
94
78
  const actual = utils.flag(this, 'object');
95
79
  const errorMessage = await assertImage(typeof actual == 'string' ? Buffer.from(actual, 'base64') : actual, imageName);
96
-
97
80
  if (errorMessage) {
98
81
  throw createImageError(imageName ? {
99
82
  [imageName]: errorMessage
@@ -104,25 +87,21 @@ export default function (getExpected, diffOptions) {
104
87
  const errors = {};
105
88
  await Promise.all(Object.entries(utils.flag(this, 'object')).map(async ([imageName, imageOrBase64]) => {
106
89
  let errorMessage;
107
-
108
90
  try {
109
91
  errorMessage = await assertImage(typeof imageOrBase64 == 'string' ? Buffer.from(imageOrBase64, 'base64') : imageOrBase64, imageName);
110
92
  } catch (error) {
111
93
  errorMessage = error.stack;
112
94
  }
113
-
114
95
  if (errorMessage) {
115
96
  errors[imageName] = errorMessage;
116
97
  }
117
98
  }));
118
-
119
99
  if (Object.keys(errors).length > 0) {
120
100
  throw createImageError(errors);
121
101
  }
122
102
  });
123
103
  };
124
104
  }
125
-
126
105
  function createImageError(imageErrors) {
127
106
  const error = new Error('Expected image to match');
128
107
  error.images = imageErrors;
@@ -1,28 +1,23 @@
1
1
  import { Suite, Test } from 'mocha';
2
2
  import { isDefined } from '../../types';
3
3
  import { loadTestsFromStories } from '../stories';
4
-
5
4
  function findOrCreateSuite(name, parent) {
6
5
  const suite = parent.suites.find(({
7
6
  title
8
7
  }) => title == name) || new Suite(name, parent.ctx);
9
-
10
8
  if (!suite.parent) {
11
9
  suite.parent = parent;
12
10
  parent.addSuite(suite);
13
11
  }
14
-
15
12
  return suite;
16
13
  }
17
-
18
14
  function createTest(name, fn, skip = false) {
19
15
  const test = new Test(name, skip ? undefined : fn);
20
- test.pending = Boolean(skip); // NOTE Can't define skip reason in mocha https://github.com/mochajs/mocha/issues/2026
21
-
16
+ test.pending = Boolean(skip);
17
+ // NOTE Can't define skip reason in mocha https://github.com/mochajs/mocha/issues/2026
22
18
  test.skipReason = skip;
23
19
  return test;
24
20
  }
25
-
26
21
  function addTest(rootSuite, test) {
27
22
  const [testName, ...suitePath] = [...test.storyPath, test.testName].reverse().filter(isDefined);
28
23
  const suite = suitePath.reduceRight((subSuite, suiteName) => findOrCreateSuite(suiteName, subSuite), rootSuite);
@@ -34,7 +29,6 @@ function addTest(rootSuite, test) {
34
29
  }, suite.ctx);
35
30
  return mochaTest;
36
31
  }
37
-
38
32
  function removeTestOrSuite(testOrSuite) {
39
33
  const {
40
34
  parent
@@ -44,7 +38,6 @@ function removeTestOrSuite(testOrSuite) {
44
38
  if (testOrSuite instanceof Suite) parent.suites = parent.suites.filter(suite => suite != testOrSuite);
45
39
  if (parent.tests.length == 0 && parent.suites.length == 0) removeTestOrSuite(parent);
46
40
  }
47
-
48
41
  export async function addTestsFromStories(rootSuite, config, {
49
42
  browser,
50
43
  ...options
@@ -1,5 +1,3 @@
1
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
2
-
3
1
  import chalk from 'chalk';
4
2
  import { reporters } from 'mocha';
5
3
  import prefix from 'loglevel-plugin-prefix';
@@ -11,6 +9,7 @@ const testLevels = {
11
9
  ERROR: chalk.red('FAIL')
12
10
  };
13
11
  export class CreeveyReporter extends reporters.Base {
12
+ // TODO Output in better way, like vitest, maybe
14
13
  constructor(runner, options) {
15
14
  super(runner);
16
15
  const {
@@ -22,39 +21,24 @@ export class CreeveyReporter extends reporters.Base {
22
21
  format(level) {
23
22
  return `${testLevels[level]} => (${topLevelSuite}:${chalk.gray(sessionId)})`;
24
23
  }
25
-
26
24
  });
27
25
  runner.on('test', test => testLogger.warn(chalk.cyan(test.titlePath().join('/'))));
28
26
  runner.on('pass', test => testLogger.info(chalk.cyan(test.titlePath().join('/'))));
29
- runner.on('fail', (test, error) => testLogger.error(chalk.cyan(test.titlePath().join('/')), '\n ', getErrors(error, (error, imageName) => `${chalk.bold(imageName !== null && imageName !== void 0 ? imageName : topLevelSuite)}:${error}`, error => {
30
- var _error$stack;
31
-
32
- return `${(_error$stack = error.stack) !== null && _error$stack !== void 0 ? _error$stack : error.message}`;
33
- }).join('\n ')));
27
+ runner.on('fail', (test, error) => testLogger.error(chalk.cyan(test.titlePath().join('/')), '\n ', getErrors(error, (error, imageName) => `${chalk.bold(imageName ?? topLevelSuite)}:${error}`, error => `${error.stack ?? error.message}`).join('\n ')));
34
28
  }
35
-
36
29
  }
37
30
  export class TeamcityReporter extends reporters.Base {
38
31
  constructor(runner, options) {
39
32
  super(runner);
40
-
41
- _defineProperty(this, "escape", str => {
42
- if (!str) return '';
43
- return str.toString() // eslint-disable-next-line no-control-regex
44
- .replace(/\x1B.*?m/g, '').replace(/\|/g, '||').replace(/\n/g, '|n').replace(/\r/g, '|r').replace(/\[/g, '|[').replace(/\]/g, '|]').replace(/\u0085/g, '|x').replace(/\u2028/g, '|l').replace(/\u2029/g, '|p').replace(/'/g, "|'");
45
- });
46
-
47
33
  const topLevelSuite = this.escape(options.reporterOptions.topLevelSuite);
48
34
  const reporterOptions = options.reporterOptions;
49
35
  runner.on('suite', suite => suite.root ? console.log(`##teamcity[testSuiteStarted name='${topLevelSuite}' flowId='${process.pid}']`) : console.log(`##teamcity[testSuiteStarted name='${this.escape(suite.title)}' flowId='${process.pid}']`));
50
36
  runner.on('test', test => console.log(`##teamcity[testStarted name='${this.escape(test.title)}' flowId='${process.pid}']`));
51
37
  runner.on('fail', (test, error) => {
52
- var _error$stack2;
53
-
54
38
  Object.entries(reporterOptions.images).forEach(([name, image]) => {
55
39
  if (!image) return;
56
- const filePath = test.titlePath().concat(name == topLevelSuite ? [] : [topLevelSuite]).map(this.escape).join('/'); // eslint-disable-next-line @typescript-eslint/no-unused-vars
57
-
40
+ const filePath = test.titlePath().concat(name == topLevelSuite ? [] : [topLevelSuite]).map(this.escape).join('/');
41
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
58
42
  const {
59
43
  error,
60
44
  ...rest
@@ -63,22 +47,27 @@ export class TeamcityReporter extends reporters.Base {
63
47
  console.log(`##teamcity[publishArtifacts '${reporterOptions.reportDir}/${filePath}/${fileName} => report/${filePath}']`);
64
48
  console.log(`##teamcity[testMetadata testName='${this.escape(test.title)}' type='image' value='report/${filePath}/${fileName}' flowId='${process.pid}']`);
65
49
  });
66
- }); // Output failed test as passed due TC don't support retry mechanic
50
+ });
51
+
52
+ // Output failed test as passed due TC don't support retry mechanic
67
53
  // https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
68
54
 
69
- reporterOptions.willRetry ? console.log(`##teamcity[testFinished name='${this.escape(test.title)}' flowId='${process.pid}']`) : console.log(`##teamcity[testFailed name='${this.escape(test.title)}' message='${this.escape(error.message)}' details='${this.escape((_error$stack2 = error.stack) !== null && _error$stack2 !== void 0 ? _error$stack2 : '')}' flowId='${process.pid}']`);
55
+ reporterOptions.willRetry ? console.log(`##teamcity[testFinished name='${this.escape(test.title)}' flowId='${process.pid}']`) : console.log(`##teamcity[testFailed name='${this.escape(test.title)}' message='${this.escape(error.message)}' details='${this.escape(error.stack ?? '')}' flowId='${process.pid}']`);
70
56
  });
71
57
  runner.on('pending', test => console.log(`##teamcity[testIgnored name='${this.escape(test.title)}' message='${this.escape(typeof test.skipReason == 'boolean' ? test.title : test.skipReason)}' flowId='${process.pid}']`));
72
58
  runner.on('test end', test => console.log(`##teamcity[testFinished name='${this.escape(test.title)}' flowId='${process.pid}']`));
73
59
  runner.on('suite end', suite => suite.root || console.log(`##teamcity[testSuiteFinished name='${this.escape(suite.title)}' flowId='${process.pid}']`));
74
60
  runner.on('end', () => console.log(`##teamcity[testSuiteFinished name='${topLevelSuite}' flowId='${process.pid}']`));
75
61
  }
76
-
62
+ escape = str => {
63
+ if (!str) return '';
64
+ return str.toString()
65
+ // eslint-disable-next-line no-control-regex
66
+ .replace(/\x1B.*?m/g, '').replace(/\|/g, '||').replace(/\n/g, '|n').replace(/\r/g, '|r').replace(/\[/g, '|[').replace(/\]/g, '|]').replace(/\u0085/g, '|x').replace(/\u2028/g, '|l').replace(/\u2029/g, '|p').replace(/'/g, "|'");
67
+ };
77
68
  }
78
-
79
69
  function getErrors(error, imageErrorToString, errorToString) {
80
70
  const errors = [];
81
-
82
71
  if (!(error instanceof Error)) {
83
72
  errors.push(error);
84
73
  } else if (!isImageError(error)) {
@@ -88,11 +77,8 @@ function getErrors(error, imageErrorToString, errorToString) {
88
77
  } else {
89
78
  const imageErrors = error.images;
90
79
  Object.keys(imageErrors).forEach(imageName => {
91
- var _imageErrors$imageNam;
92
-
93
- errors.push(imageErrorToString((_imageErrors$imageNam = imageErrors[imageName]) !== null && _imageErrors$imageNam !== void 0 ? _imageErrors$imageNam : '', imageName));
80
+ errors.push(imageErrorToString(imageErrors[imageName] ?? '', imageName));
94
81
  });
95
82
  }
96
-
97
83
  return errors;
98
84
  }
@@ -17,7 +17,6 @@ const readdirAsync = promisify(fs.readdir);
17
17
  const readFileAsync = promisify(fs.readFile);
18
18
  const writeFileAsync = promisify(fs.writeFile);
19
19
  const mkdirAsync = promisify(fs.mkdir);
20
-
21
20
  async function getStat(filePath) {
22
21
  try {
23
22
  return await statAsync(filePath);
@@ -25,37 +24,28 @@ async function getStat(filePath) {
25
24
  if (typeof error == 'object' && error && error.code === 'ENOENT') {
26
25
  return null;
27
26
  }
28
-
29
27
  throw error;
30
28
  }
31
29
  }
32
-
33
30
  async function getLastImageNumber(imageDir, imageName) {
34
31
  const actualImagesRegexp = new RegExp(`${imageName}-actual-(\\d+)\\.png`);
35
-
36
32
  try {
37
- var _await$readdirAsync$m;
38
-
39
- return (_await$readdirAsync$m = (await readdirAsync(imageDir)).map(filename => filename.replace(actualImagesRegexp, '$1')).map(Number).filter(x => !isNaN(x)).sort((a, b) => b - a)[0]) !== null && _await$readdirAsync$m !== void 0 ? _await$readdirAsync$m : 0;
33
+ return (await readdirAsync(imageDir)).map(filename => filename.replace(actualImagesRegexp, '$1')).map(Number).filter(x => !isNaN(x)).sort((a, b) => b - a)[0] ?? 0;
40
34
  } catch (_error) {
41
35
  return 0;
42
36
  }
43
- } // FIXME browser options hotfix
44
-
37
+ }
45
38
 
39
+ // FIXME browser options hotfix
46
40
  export default async function worker(config, options) {
47
41
  var _await$browser$getSes;
48
-
49
42
  let retries = 0;
50
43
  let images = {};
51
44
  let error = undefined;
52
45
  const screenshots = [];
53
46
  const testScope = [];
54
-
55
47
  function runHandler(failures) {
56
48
  if (failures > 0 && (error || Object.values(images).some(image => (image === null || image === void 0 ? void 0 : image.error) != null))) {
57
- var _error2;
58
-
59
49
  const isTimeout = hasTimeout(error) || Object.values(images).some(image => hasTimeout(image === null || image === void 0 ? void 0 : image.error));
60
50
  const payload = {
61
51
  status: 'failed',
@@ -65,7 +55,7 @@ export default async function worker(config, options) {
65
55
  isTimeout ? emitWorkerMessage({
66
56
  type: 'error',
67
57
  payload: {
68
- error: (_error2 = error) !== null && _error2 !== void 0 ? _error2 : 'Unknown error'
58
+ error: error ?? 'Unknown error'
69
59
  }
70
60
  }) : emitTestMessage({
71
61
  type: 'end',
@@ -81,12 +71,10 @@ export default async function worker(config, options) {
81
71
  });
82
72
  }
83
73
  }
84
-
85
74
  async function saveImages(imageDir, images) {
86
75
  await mkdirAsync(imageDir, {
87
76
  recursive: true
88
77
  });
89
-
90
78
  for (const {
91
79
  name,
92
80
  data
@@ -94,29 +82,24 @@ export default async function worker(config, options) {
94
82
  await writeFileAsync(path.join(imageDir, name), data);
95
83
  }
96
84
  }
97
-
98
85
  async function getExpected(assertImageName) {
99
- var _images$imageName;
100
-
101
86
  // context => [kind, story, test, browser]
102
87
  // rootSuite -> kindSuite -> storyTest -> [browsers.png]
103
88
  // rootSuite -> kindSuite -> storySuite -> test -> [browsers.png]
104
89
  const testPath = [...testScope];
105
- const imageName = assertImageName !== null && assertImageName !== void 0 ? assertImageName : testPath.pop();
90
+ const imageName = assertImageName ?? testPath.pop();
106
91
  const imagesMeta = [];
107
92
  const reportImageDir = path.join(config.reportDir, ...testPath);
108
93
  const imageNumber = (await getLastImageNumber(reportImageDir, imageName)) + 1;
109
94
  const actualImageName = `${imageName}-actual-${imageNumber}.png`;
110
- const image = images[imageName] = (_images$imageName = images[imageName]) !== null && _images$imageName !== void 0 ? _images$imageName : {
95
+ const image = images[imageName] = images[imageName] ?? {
111
96
  actual: actualImageName
112
97
  };
113
-
114
98
  const onCompare = async (actual, expect, diff) => {
115
99
  imagesMeta.push({
116
100
  name: image.actual,
117
101
  data: actual
118
102
  });
119
-
120
103
  if (diff && expect) {
121
104
  image.expect = `${imageName}-expect-${imageNumber}.png`;
122
105
  image.diff = `${imageName}-diff-${imageNumber}.png`;
@@ -129,12 +112,10 @@ export default async function worker(config, options) {
129
112
  data: diff
130
113
  });
131
114
  }
132
-
133
115
  if (options.saveReport) {
134
116
  await saveImages(reportImageDir, imagesMeta);
135
117
  }
136
118
  };
137
-
138
119
  const expectImageDir = path.join(config.screenDir, ...testPath);
139
120
  const expectImageStat = await getStat(path.join(expectImageDir, `${imageName}.png`));
140
121
  if (!expectImageStat) return {
@@ -147,56 +128,48 @@ export default async function worker(config, options) {
147
128
  onCompare
148
129
  };
149
130
  }
150
-
151
131
  const mochaOptions = {
152
132
  timeout: 30000,
153
133
  reporter: process.env.TEAMCITY_VERSION ? TeamcityReporter : options.reporter || CreeveyReporter,
154
134
  reporterOptions: {
155
135
  reportDir: config.reportDir,
156
136
  topLevelSuite: options.browser,
157
-
158
137
  get willRetry() {
159
138
  return retries < config.maxRetries;
160
139
  },
161
-
162
140
  get images() {
163
141
  return images;
164
142
  },
165
-
166
143
  get sessionId() {
167
144
  return sessionId;
168
145
  }
169
-
170
146
  }
171
147
  };
172
- const mocha = new Mocha(mochaOptions); // @ts-expect-error: @types/mocha has out-dated types
173
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
174
-
148
+ const mocha = new Mocha(mochaOptions);
175
149
  mocha.cleanReferencesAfterRun(false);
176
150
  chai.use(chaiImage(getExpected, config.diffOptions));
177
- if ((await getBrowser(config, options.browser)) == null) return;
151
+ if ((await getBrowser(config, options)) == null) return;
178
152
  await addTestsFromStories(mocha.suite, config, {
179
153
  browser: options.browser,
180
154
  watch: options.ui,
181
155
  debug: options.debug,
182
156
  port: options.port
183
157
  });
184
-
185
158
  try {
186
159
  var _await$getBrowser;
187
-
188
- await ((_await$getBrowser = await getBrowser(config, options.browser)) === null || _await$getBrowser === void 0 ? void 0 : _await$getBrowser.getCurrentUrl());
160
+ await ((_await$getBrowser = await getBrowser(config, options)) === null || _await$getBrowser === void 0 ? void 0 : _await$getBrowser.getCurrentUrl());
189
161
  } catch (_) {
190
162
  await closeBrowser();
191
163
  }
192
-
193
- const browser = await getBrowser(config, options.browser);
164
+ const browser = await getBrowser(config, options);
194
165
  const sessionId = (_await$browser$getSes = await (browser === null || browser === void 0 ? void 0 : browser.getSession())) === null || _await$browser$getSes === void 0 ? void 0 : _await$browser$getSes.getId();
195
166
  if (browser == null) return;
196
- const interval = setInterval(() => void browser.getCurrentUrl().then(url => {
197
- if (options.debug) logger.debug(`${options.browser}:${chalk.gray(sessionId)}`, 'current url', chalk.magenta(url));
198
- }), 10 * 1000);
199
- subscribeOn('shutdown', () => clearInterval(interval));
167
+ if (options.debug) {
168
+ const interval = setInterval(() => void browser.getCurrentUrl().then(url => {
169
+ logger.debug(`${options.browser}:${chalk.gray(sessionId)}`, 'current url', chalk.magenta(url));
170
+ }), 10 * 1000);
171
+ subscribeOn('shutdown', () => clearInterval(interval));
172
+ }
200
173
  mocha.suite.beforeAll(function () {
201
174
  this.config = config;
202
175
  this.browser = browser;
@@ -208,6 +181,18 @@ export default async function worker(config, options) {
208
181
  this.screenshots = screenshots;
209
182
  });
210
183
  mocha.suite.beforeEach(switchStory);
184
+ if (options.trace) {
185
+ mocha.suite.afterEach(async function () {
186
+ const types = await (browser === null || browser === void 0 ? void 0 : browser.manage().logs().getAvailableLogTypes());
187
+ for (const type of types ?? []) {
188
+ const logs = await (browser === null || browser === void 0 ? void 0 : browser.manage().logs().get(type));
189
+ logs.forEach(log => {
190
+ var _this$currentTest;
191
+ return logger.trace(sessionId, (_this$currentTest = this.currentTest) === null || _this$currentTest === void 0 ? void 0 : _this$currentTest.titlePath().join('/'), log.toJSON());
192
+ });
193
+ }
194
+ });
195
+ }
211
196
  subscribeOn('test', message => {
212
197
  if (message.type != 'start') return;
213
198
  const test = message.payload;
@@ -216,15 +201,14 @@ export default async function worker(config, options) {
216
201
  error = undefined;
217
202
  retries = test.retries;
218
203
  mocha.grep(new RegExp(`^${testPath}$`));
219
- const runner = mocha.run(runHandler); // TODO How handle browser corruption?
204
+ const runner = mocha.run(runHandler);
220
205
 
206
+ // TODO How handle browser corruption?
221
207
  runner.on('fail', (_test, reason) => {
222
208
  if (!(reason instanceof Error)) {
223
209
  error = reason;
224
210
  } else if (!isImageError(reason)) {
225
- var _reason$stack;
226
-
227
- error = (_reason$stack = reason.stack) !== null && _reason$stack !== void 0 ? _reason$stack : reason.message;
211
+ error = reason.stack ?? reason.message;
228
212
  } else if (typeof reason.images == 'string') {
229
213
  const image = images[testScope.slice(-1)[0]];
230
214
  if (image) image.error = reason.images;
@@ -242,7 +226,6 @@ export default async function worker(config, options) {
242
226
  type: 'ready'
243
227
  });
244
228
  }
245
-
246
229
  function hasTimeout(str) {
247
230
  return str != null && str.toLowerCase().includes('timeout');
248
231
  }