creevey 0.10.0-beta.9 → 0.10.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/AUTHORS +2 -0
  2. package/CHANGELOG.md +281 -0
  3. package/README.md +19 -41
  4. package/dist/client/addon/components/Addon.js +18 -8
  5. package/dist/client/addon/components/Addon.js.map +1 -1
  6. package/dist/client/addon/components/Panel.js +2 -2
  7. package/dist/client/addon/components/Panel.js.map +1 -1
  8. package/dist/client/addon/components/TestSelect.js +2 -2
  9. package/dist/client/addon/components/TestSelect.js.map +1 -1
  10. package/dist/client/addon/components/Tools.js +19 -9
  11. package/dist/client/addon/components/Tools.js.map +1 -1
  12. package/dist/client/addon/controller.d.ts +1 -1
  13. package/dist/client/addon/controller.js +3 -3
  14. package/dist/client/addon/controller.js.map +1 -1
  15. package/dist/client/addon/decorator.d.ts +1 -1
  16. package/dist/client/addon/makeDecorator.d.ts +9 -0
  17. package/dist/client/addon/makeDecorator.js +48 -0
  18. package/dist/client/addon/makeDecorator.js.map +1 -0
  19. package/dist/client/addon/manager.js +38 -39
  20. package/dist/client/addon/manager.js.map +1 -1
  21. package/dist/client/addon/preset.d.ts +0 -1
  22. package/dist/client/addon/preset.js +3 -2
  23. package/dist/client/addon/preset.js.map +1 -1
  24. package/dist/client/addon/preview.d.ts +1 -1
  25. package/dist/client/addon/withCreevey.d.ts +5 -3
  26. package/dist/client/addon/withCreevey.js +5 -20
  27. package/dist/client/addon/withCreevey.js.map +1 -1
  28. package/dist/client/shared/components/ImagesView/BlendView.d.ts +2 -2
  29. package/dist/client/shared/components/ImagesView/BlendView.js +18 -8
  30. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  31. package/dist/client/shared/components/ImagesView/ImagesView.js +1 -1
  32. package/dist/client/shared/components/ImagesView/ImagesView.js.map +1 -1
  33. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +2 -2
  34. package/dist/client/shared/components/ImagesView/SideBySideView.js +19 -9
  35. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  36. package/dist/client/shared/components/ImagesView/SlideView.d.ts +2 -2
  37. package/dist/client/shared/components/ImagesView/SlideView.js +19 -9
  38. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  39. package/dist/client/shared/components/ImagesView/SwapView.d.ts +2 -2
  40. package/dist/client/shared/components/ImagesView/SwapView.js +19 -9
  41. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  42. package/dist/client/shared/components/ImagesView/common.d.ts +1 -1
  43. package/dist/client/shared/components/PageFooter/PageFooter.js +1 -1
  44. package/dist/client/shared/components/PageFooter/PageFooter.js.map +1 -1
  45. package/dist/client/shared/components/PageFooter/Paging.js +1 -1
  46. package/dist/client/shared/components/PageFooter/Paging.js.map +1 -1
  47. package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +2 -2
  48. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -1
  49. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  50. package/dist/client/shared/components/PageHeader/PageHeader.js +34 -13
  51. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  52. package/dist/client/shared/components/ResultsPage.d.ts +2 -2
  53. package/dist/client/shared/components/ResultsPage.js +22 -10
  54. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  55. package/dist/client/shared/creeveyClientApi.js +18 -1
  56. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  57. package/dist/client/shared/helpers.d.ts +1 -3
  58. package/dist/client/shared/helpers.js +4 -19
  59. package/dist/client/shared/helpers.js.map +1 -1
  60. package/dist/client/web/CreeveyApp.d.ts +1 -0
  61. package/dist/client/web/CreeveyApp.js +22 -9
  62. package/dist/client/web/CreeveyApp.js.map +1 -1
  63. package/dist/client/web/CreeveyContext.d.ts +1 -0
  64. package/dist/client/web/CreeveyContext.js +18 -7
  65. package/dist/client/web/CreeveyContext.js.map +1 -1
  66. package/dist/client/web/CreeveyLoader.d.ts +1 -1
  67. package/dist/client/web/CreeveyLoader.js +3 -3
  68. package/dist/client/web/CreeveyLoader.js.map +1 -1
  69. package/dist/client/web/CreeveyView/SideBar/Checkbox.d.ts +4 -4
  70. package/dist/client/web/CreeveyView/SideBar/Checkbox.js +36 -6
  71. package/dist/client/web/CreeveyView/SideBar/Checkbox.js.map +1 -1
  72. package/dist/client/web/CreeveyView/SideBar/Search.js +18 -8
  73. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  74. package/dist/client/web/CreeveyView/SideBar/SideBar.js +26 -12
  75. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  76. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +28 -17
  77. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  78. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +32 -12
  79. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  80. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +6 -6
  81. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +20 -11
  82. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  83. package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -11
  84. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  85. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +2 -2
  86. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js +2 -2
  87. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js.map +1 -1
  88. package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +2 -2
  89. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +3 -2
  90. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  91. package/dist/client/web/CreeveyView/SideBar/Toggle.js +1 -1
  92. package/dist/client/web/CreeveyView/SideBar/Toggle.js.map +1 -1
  93. package/dist/client/web/KeyboardEventsContext.js +17 -7
  94. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  95. package/dist/client/web/assets/index-CtSq3IhG.js +518 -0
  96. package/dist/client/web/index.html +1 -1
  97. package/dist/client/web/index.js +26 -11
  98. package/dist/client/web/index.js.map +1 -1
  99. package/dist/client/web/themes.d.ts +2 -0
  100. package/dist/client/web/themes.js +22 -0
  101. package/dist/client/web/themes.js.map +1 -0
  102. package/dist/creevey.d.ts +1 -1
  103. package/dist/creevey.js +122 -41
  104. package/dist/creevey.js.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/playwright/generator.d.ts +34 -0
  107. package/dist/playwright/generator.js +267 -0
  108. package/dist/playwright/generator.js.map +1 -0
  109. package/dist/playwright/helpers.d.ts +2 -0
  110. package/dist/playwright/helpers.js +29 -0
  111. package/dist/playwright/helpers.js.map +1 -0
  112. package/dist/playwright/reporter.d.ts +83 -0
  113. package/dist/playwright/reporter.js +334 -0
  114. package/dist/playwright/reporter.js.map +1 -0
  115. package/dist/playwright/setup.d.ts +3 -0
  116. package/dist/playwright/setup.js +72 -0
  117. package/dist/playwright/setup.js.map +1 -0
  118. package/dist/playwright.d.ts +1 -0
  119. package/dist/playwright.js +3 -1
  120. package/dist/playwright.js.map +1 -1
  121. package/dist/server/compare.d.ts +18 -0
  122. package/dist/server/compare.js +182 -0
  123. package/dist/server/compare.js.map +1 -0
  124. package/dist/server/config.d.ts +3 -3
  125. package/dist/server/config.js +75 -8
  126. package/dist/server/config.js.map +1 -1
  127. package/dist/server/connection.d.ts +3 -0
  128. package/dist/server/connection.js +28 -0
  129. package/dist/server/connection.js.map +1 -0
  130. package/dist/server/docker.d.ts +1 -1
  131. package/dist/server/docker.js +54 -32
  132. package/dist/server/docker.js.map +1 -1
  133. package/dist/server/index.d.ts +2 -2
  134. package/dist/server/index.js +166 -64
  135. package/dist/server/index.js.map +1 -1
  136. package/dist/server/master/api.d.ts +11 -6
  137. package/dist/server/master/api.js +88 -25
  138. package/dist/server/master/api.js.map +1 -1
  139. package/dist/server/master/handlers/capture-handler.d.ts +5 -0
  140. package/dist/server/master/handlers/capture-handler.js +25 -0
  141. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  142. package/dist/server/master/handlers/index.d.ts +4 -0
  143. package/dist/server/master/handlers/index.js +21 -0
  144. package/dist/server/master/handlers/index.js.map +1 -0
  145. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  146. package/dist/server/master/handlers/ping-handler.js +8 -0
  147. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  148. package/dist/server/master/handlers/static-handler.d.ts +1 -0
  149. package/dist/server/master/handlers/static-handler.js +20 -0
  150. package/dist/server/master/handlers/static-handler.js.map +1 -0
  151. package/dist/server/master/handlers/stories-handler.d.ts +4 -0
  152. package/dist/server/master/handlers/stories-handler.js +24 -0
  153. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  154. package/dist/server/master/master.js +7 -24
  155. package/dist/server/master/master.js.map +1 -1
  156. package/dist/server/master/pool.d.ts +1 -0
  157. package/dist/server/master/pool.js +5 -3
  158. package/dist/server/master/pool.js.map +1 -1
  159. package/dist/server/master/queue.d.ts +1 -1
  160. package/dist/server/master/queue.js +14 -6
  161. package/dist/server/master/queue.js.map +1 -1
  162. package/dist/server/master/runner.d.ts +6 -6
  163. package/dist/server/master/runner.js +98 -130
  164. package/dist/server/master/runner.js.map +1 -1
  165. package/dist/server/master/server.d.ts +1 -1
  166. package/dist/server/master/server.js +193 -88
  167. package/dist/server/master/server.js.map +1 -1
  168. package/dist/server/master/start.d.ts +1 -2
  169. package/dist/server/master/start.js +13 -29
  170. package/dist/server/master/start.js.map +1 -1
  171. package/dist/server/master/testsManager.d.ts +81 -0
  172. package/dist/server/master/testsManager.js +282 -0
  173. package/dist/server/master/testsManager.js.map +1 -0
  174. package/dist/server/playwright/docker-file.d.ts +1 -1
  175. package/dist/server/playwright/docker-file.js +17 -8
  176. package/dist/server/playwright/docker-file.js.map +1 -1
  177. package/dist/server/playwright/docker.d.ts +2 -1
  178. package/dist/server/playwright/docker.js +10 -2
  179. package/dist/server/playwright/docker.js.map +1 -1
  180. package/dist/server/playwright/index-source.mjs +16 -0
  181. package/dist/server/playwright/internal.d.ts +7 -7
  182. package/dist/server/playwright/internal.js +137 -79
  183. package/dist/server/playwright/internal.js.map +1 -1
  184. package/dist/server/playwright/webdriver.d.ts +3 -3
  185. package/dist/server/playwright/webdriver.js +0 -6
  186. package/dist/server/playwright/webdriver.js.map +1 -1
  187. package/dist/server/providers/browser.js +4 -3
  188. package/dist/server/providers/browser.js.map +1 -1
  189. package/dist/server/providers/hybrid.js +2 -2
  190. package/dist/server/providers/hybrid.js.map +1 -1
  191. package/dist/server/report.d.ts +10 -0
  192. package/dist/server/report.js +45 -0
  193. package/dist/server/report.js.map +1 -0
  194. package/dist/server/reporters/creevey.d.ts +7 -0
  195. package/dist/server/reporters/creevey.js +63 -0
  196. package/dist/server/reporters/creevey.js.map +1 -0
  197. package/dist/server/reporters/index.d.ts +2 -0
  198. package/dist/server/reporters/index.js +16 -0
  199. package/dist/server/reporters/index.js.map +1 -0
  200. package/dist/server/reporters/junit.d.ts +16 -0
  201. package/dist/server/reporters/junit.js +167 -0
  202. package/dist/server/reporters/junit.js.map +1 -0
  203. package/dist/server/reporters/teamcity.d.ts +7 -0
  204. package/dist/server/reporters/teamcity.js +60 -0
  205. package/dist/server/reporters/teamcity.js.map +1 -0
  206. package/dist/server/selenium/internal.d.ts +3 -3
  207. package/dist/server/selenium/internal.js +48 -34
  208. package/dist/server/selenium/internal.js.map +1 -1
  209. package/dist/server/selenium/selenoid.js +12 -6
  210. package/dist/server/selenium/selenoid.js.map +1 -1
  211. package/dist/server/selenium/webdriver.d.ts +3 -3
  212. package/dist/server/selenium/webdriver.js +4 -8
  213. package/dist/server/selenium/webdriver.js.map +1 -1
  214. package/dist/server/shutdown.d.ts +1 -0
  215. package/dist/server/shutdown.js +23 -0
  216. package/dist/server/shutdown.js.map +1 -0
  217. package/dist/server/stories.d.ts +0 -1
  218. package/dist/server/stories.js +0 -12
  219. package/dist/server/stories.js.map +1 -1
  220. package/dist/server/telemetry.js +3 -3
  221. package/dist/server/telemetry.js.map +1 -1
  222. package/dist/server/testsFiles/parser.js +45 -5
  223. package/dist/server/testsFiles/parser.js.map +1 -1
  224. package/dist/server/utils.d.ts +23 -0
  225. package/dist/server/utils.js +113 -13
  226. package/dist/server/utils.js.map +1 -1
  227. package/dist/server/webdriver.d.ts +1 -1
  228. package/dist/server/worker/context.d.ts +3 -0
  229. package/dist/server/worker/context.js +15 -0
  230. package/dist/server/worker/context.js.map +1 -0
  231. package/dist/server/worker/match-image.d.ts +8 -12
  232. package/dist/server/worker/match-image.js +11 -178
  233. package/dist/server/worker/match-image.js.map +1 -1
  234. package/dist/server/worker/start.d.ts +2 -2
  235. package/dist/server/worker/start.js +27 -63
  236. package/dist/server/worker/start.js.map +1 -1
  237. package/dist/shared/index.d.ts +1 -1
  238. package/dist/shared/index.js +9 -7
  239. package/dist/shared/index.js.map +1 -1
  240. package/dist/types.d.ts +84 -43
  241. package/dist/types.js +65 -1
  242. package/dist/types.js.map +1 -1
  243. package/docs/cli.md +80 -0
  244. package/docs/config.md +179 -165
  245. package/docs/examples/playwright-reporer/playwright.config.ts +36 -0
  246. package/docs/migration-0.9-to-0.10.md +182 -0
  247. package/docs/playwright-reporter.md +170 -0
  248. package/docs/storybook.md +60 -0
  249. package/docs/tests.md +50 -45
  250. package/package.json +78 -83
  251. package/playwright.config.mts +46 -0
  252. package/src/client/addon/components/Addon.tsx +1 -1
  253. package/src/client/addon/components/Panel.tsx +2 -2
  254. package/src/client/addon/components/TestSelect.tsx +2 -2
  255. package/src/client/addon/components/Tools.tsx +2 -2
  256. package/src/client/addon/controller.ts +4 -4
  257. package/src/client/addon/makeDecorator.ts +69 -0
  258. package/src/client/addon/manager.ts +38 -37
  259. package/src/client/addon/preset.ts +2 -1
  260. package/src/client/addon/withCreevey.ts +10 -18
  261. package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
  262. package/src/client/shared/components/ImagesView/ImagesView.tsx +1 -1
  263. package/src/client/shared/components/ImagesView/SideBySideView.tsx +2 -2
  264. package/src/client/shared/components/ImagesView/SlideView.tsx +2 -2
  265. package/src/client/shared/components/ImagesView/SwapView.tsx +2 -2
  266. package/src/client/shared/components/ImagesView/common.ts +1 -1
  267. package/src/client/shared/components/PageFooter/PageFooter.tsx +1 -1
  268. package/src/client/shared/components/PageFooter/Paging.tsx +1 -1
  269. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -1
  270. package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
  271. package/src/client/shared/components/ResultsPage.tsx +6 -4
  272. package/src/client/shared/creeveyClientApi.ts +19 -1
  273. package/src/client/shared/helpers.ts +4 -24
  274. package/src/client/web/CreeveyApp.tsx +5 -2
  275. package/src/client/web/CreeveyContext.tsx +2 -0
  276. package/src/client/web/CreeveyLoader.tsx +2 -2
  277. package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +3 -3
  278. package/src/client/web/CreeveyView/SideBar/Search.tsx +1 -1
  279. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
  280. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +21 -19
  281. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
  282. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +10 -8
  283. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +9 -7
  284. package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +2 -2
  285. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +3 -2
  286. package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -1
  287. package/src/client/web/index.tsx +10 -5
  288. package/src/client/web/themes.ts +24 -0
  289. package/src/creevey.ts +92 -38
  290. package/src/playwright/generator.ts +360 -0
  291. package/src/playwright/helpers.ts +31 -0
  292. package/src/playwright/reporter.ts +381 -0
  293. package/src/playwright/setup.ts +84 -0
  294. package/src/playwright.ts +1 -0
  295. package/src/server/compare.ts +260 -0
  296. package/src/server/config.ts +52 -9
  297. package/src/server/connection.ts +26 -0
  298. package/src/server/docker.ts +62 -34
  299. package/src/server/index.ts +165 -79
  300. package/src/server/master/api.ts +94 -28
  301. package/src/server/master/handlers/capture-handler.ts +20 -0
  302. package/src/server/master/handlers/index.ts +4 -0
  303. package/src/server/master/handlers/ping-handler.ts +6 -0
  304. package/src/server/master/handlers/static-handler.ts +16 -0
  305. package/src/server/master/handlers/stories-handler.ts +20 -0
  306. package/src/server/master/master.ts +10 -27
  307. package/src/server/master/pool.ts +7 -3
  308. package/src/server/master/queue.ts +21 -7
  309. package/src/server/master/runner.ts +123 -134
  310. package/src/server/master/server.ts +214 -101
  311. package/src/server/master/start.ts +19 -41
  312. package/src/server/master/testsManager.ts +316 -0
  313. package/src/server/playwright/docker-file.ts +20 -8
  314. package/src/server/playwright/docker.ts +16 -3
  315. package/src/server/playwright/index-source.mjs +16 -0
  316. package/src/server/playwright/internal.ts +169 -96
  317. package/src/server/playwright/webdriver.ts +4 -10
  318. package/src/server/providers/browser.ts +4 -3
  319. package/src/server/providers/hybrid.ts +2 -3
  320. package/src/server/report.ts +51 -0
  321. package/src/server/reporters/creevey.ts +74 -0
  322. package/src/server/reporters/index.ts +11 -0
  323. package/src/server/reporters/junit.ts +207 -0
  324. package/src/server/reporters/teamcity.ts +74 -0
  325. package/src/server/selenium/internal.ts +62 -45
  326. package/src/server/selenium/selenoid.ts +13 -6
  327. package/src/server/selenium/webdriver.ts +8 -12
  328. package/src/server/shutdown.ts +19 -0
  329. package/src/server/stories.ts +1 -12
  330. package/src/server/telemetry.ts +3 -3
  331. package/src/server/testsFiles/parser.ts +52 -4
  332. package/src/server/utils.ts +123 -14
  333. package/src/server/webdriver.ts +1 -1
  334. package/src/server/worker/context.ts +14 -0
  335. package/src/server/worker/match-image.ts +16 -248
  336. package/src/server/worker/start.ts +32 -75
  337. package/src/shared/index.ts +10 -8
  338. package/src/types.ts +91 -58
  339. package/types/global.d.ts +1 -0
  340. package/dist/client/web/assets/index-BE9CL5_G.js +0 -591
  341. package/dist/server/reporter.d.ts +0 -26
  342. package/dist/server/reporter.js +0 -108
  343. package/dist/server/reporter.js.map +0 -1
  344. package/dist/server/update.d.ts +0 -2
  345. package/dist/server/update.js +0 -53
  346. package/dist/server/update.js.map +0 -1
  347. package/src/server/reporter.ts +0 -139
  348. package/src/server/update.ts +0 -74
@@ -1,244 +1,28 @@
1
- import path from 'path';
2
- import { Stats } from 'fs';
3
- import { mkdir, readdir, readFile, stat, writeFile } from 'fs/promises';
4
- import { compare } from 'odiff-bin';
5
- import { PNG } from 'pngjs';
6
- import { Config, DiffOptions, Images, ImagesError } from '../../types.js';
7
- import assert from 'assert';
1
+ import { Config, ImagesError } from '../../types.js';
2
+ import { getOdiffAssert, getPixelmatchAssert, ImageContext } from '../compare.js';
8
3
 
9
- export interface ImageContext {
10
- attachments: string[];
11
- testFullPath: string[];
12
- images: Partial<Record<string, Images>>;
13
- }
14
-
15
- interface ImagePaths {
16
- imageName: string;
17
- actualImageName: string;
18
- expectImageName: string;
19
- diffImageName: string;
20
- expectImageDir: string;
21
- reportImageDir: string;
22
- }
23
-
24
- async function getStat(filePath: string): Promise<Stats | null> {
25
- try {
26
- return await stat(filePath);
27
- } catch (error) {
28
- if (typeof error == 'object' && error && (error as { code?: unknown }).code === 'ENOENT') {
29
- return null;
30
- }
31
- throw error;
32
- }
33
- }
34
-
35
- async function getLastImageNumber(imageDir: string, imageName: string): Promise<number> {
36
- const actualImagesRegexp = new RegExp(`${imageName}-actual-(\\d+)\\.png`);
37
-
38
- try {
39
- return (
40
- (await readdir(imageDir))
41
- .map((filename) => filename.replace(actualImagesRegexp, '$1'))
42
- .map(Number)
43
- .filter((x) => !isNaN(x))
44
- .sort((a, b) => b - a)[0] ?? 0
45
- );
46
- } catch (_error) {
47
- return 0;
48
- }
49
- }
50
-
51
- async function readExpected(expectImageDir: string, imageName: string): Promise<Buffer> {
52
- const expected = await readFile(path.join(expectImageDir, `${imageName}.png`));
53
-
54
- return expected;
55
- }
56
-
57
- async function saveImages(imageDir: string, images: { name: string; data: Buffer }[]): Promise<string[]> {
58
- const files: string[] = [];
59
- await mkdir(imageDir, { recursive: true });
60
- for (const { name, data } of images) {
61
- const filePath = path.join(imageDir, name);
62
- await writeFile(filePath, data);
63
- files.push(filePath);
64
- }
65
- return files;
66
- }
67
-
68
- async function getImagePaths(config: Config, testFullPath: string[], assertImageName?: string): Promise<ImagePaths> {
69
- const testPath = [...testFullPath];
70
- const imageName = assertImageName ?? testPath.pop();
71
-
72
- assert(typeof imageName === 'string', `Can't get image name from empty test scope`);
73
-
74
- const expectImageDir = path.join(config.screenDir, ...testPath);
75
- const reportImageDir = path.join(config.reportDir, ...testPath);
76
- const imageNumber = (await getLastImageNumber(reportImageDir, imageName)) + 1;
77
- const actualImageName = `${imageName}-actual-${imageNumber}.png`;
78
- const expectImageName = `${imageName}-expect-${imageNumber}.png`;
79
- const diffImageName = `${imageName}-diff-${imageNumber}.png`;
80
-
81
- return { imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir };
82
- }
83
-
84
- async function getExpected(
85
- ctx: ImageContext,
86
- { imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir }: ImagePaths,
87
- ): Promise<{
88
- expected: Buffer | null;
89
- onCompare: (actual: Buffer, expect?: Buffer, diff?: Buffer) => Promise<void>;
90
- }> {
91
- const onCompare = async (actual: Buffer, expect?: Buffer, diff?: Buffer): Promise<void> => {
92
- const imagesMeta: { name: string; data: Buffer }[] = [];
93
- const image = (ctx.images[imageName] = ctx.images[imageName] ?? { actual: actualImageName });
94
-
95
- imagesMeta.push({ name: image.actual, data: actual });
96
-
97
- if (diff && expect) {
98
- image.expect = expectImageName;
99
- image.diff = diffImageName;
100
- imagesMeta.push({ name: image.expect, data: expect });
101
- imagesMeta.push({ name: image.diff, data: diff });
102
- }
103
- ctx.attachments = await saveImages(reportImageDir, imagesMeta);
104
- };
105
-
106
- const expectImageStat = await getStat(path.join(expectImageDir, `${imageName}.png`));
107
- if (!expectImageStat) return { expected: null, onCompare };
108
-
109
- const expected = await readExpected(expectImageDir, imageName);
110
-
111
- return { expected, onCompare };
112
- }
113
-
114
- async function getOdiffExpected(
115
- ctx: ImageContext,
116
- actual: Buffer,
117
- { imageName, actualImageName, expectImageName, diffImageName, expectImageDir, reportImageDir }: ImagePaths,
118
- ): Promise<{ actual: string; expect: string; diff: string }> {
119
- const expected = await readExpected(expectImageDir, imageName);
120
-
121
- const image = (ctx.images[imageName] = ctx.images[imageName] ?? { actual: actualImageName });
122
- image.expect = expectImageName;
123
- image.diff = diffImageName;
124
-
125
- const imagesMeta = [
126
- { name: image.actual, data: actual },
127
- { name: expectImageName, data: expected },
128
- ];
129
-
130
- ctx.attachments = await saveImages(reportImageDir, imagesMeta);
131
-
132
- return {
133
- actual: path.join(reportImageDir, actualImageName),
134
- expect: path.join(reportImageDir, expectImageName),
135
- diff: path.join(reportImageDir, diffImageName),
136
- };
137
- }
138
-
139
- function normalizeImageSize(image: PNG, width: number, height: number): Buffer {
140
- const normalizedImage = Buffer.alloc(4 * width * height);
141
-
142
- for (let y = 0; y < height; y++) {
143
- for (let x = 0; x < width; x++) {
144
- const i = (y * width + x) * 4;
145
- if (x < image.width && y < image.height) {
146
- const j = (y * image.width + x) * 4;
147
- normalizedImage[i + 0] = image.data[j + 0];
148
- normalizedImage[i + 1] = image.data[j + 1];
149
- normalizedImage[i + 2] = image.data[j + 2];
150
- normalizedImage[i + 3] = image.data[j + 3];
151
- } else {
152
- normalizedImage[i + 0] = 0;
153
- normalizedImage[i + 1] = 0;
154
- normalizedImage[i + 2] = 0;
155
- normalizedImage[i + 3] = 0;
156
- }
157
- }
158
- }
159
- return normalizedImage;
160
- }
161
-
162
- function hasDiffPixels(diff: Buffer): boolean {
163
- for (let i = 0; i < diff.length; i += 4) {
164
- if (diff[i + 0] == 255 && diff[i + 1] == 0 && diff[i + 2] == 0 && diff[i + 3] == 255) return true;
165
- }
166
- return false;
167
- }
168
-
169
- function compareImages(
170
- expect: Buffer,
171
- actual: Buffer,
172
- pixelmatch: typeof import('pixelmatch'),
173
- diffOptions: DiffOptions,
174
- ): { isEqual: boolean; diff: Buffer } {
175
- const expectImage = PNG.sync.read(expect);
176
- const actualImage = PNG.sync.read(actual);
177
-
178
- const width = Math.max(actualImage.width, expectImage.width);
179
- const height = Math.max(actualImage.height, expectImage.height);
180
-
181
- const diffImage = new PNG({ width, height });
182
-
183
- let actualImageData = actualImage.data;
184
- if (actualImage.width < width || actualImage.height < height) {
185
- actualImageData = normalizeImageSize(actualImage, width, height);
186
- }
187
-
188
- let expectImageData = expectImage.data;
189
- if (expectImage.width < width || expectImage.height < height) {
190
- expectImageData = normalizeImageSize(expectImage, width, height);
191
- }
192
-
193
- pixelmatch(expectImageData, actualImageData, diffImage.data, width, height, diffOptions);
194
-
195
- return {
196
- isEqual: !hasDiffPixels(diffImage.data),
197
- diff: PNG.sync.write(diffImage),
198
- };
4
+ function toBuffer(bufferOrBase64: Buffer | string) {
5
+ return typeof bufferOrBase64 === 'string' ? Buffer.from(bufferOrBase64, 'base64') : bufferOrBase64;
199
6
  }
200
7
 
201
8
  export async function getMatchers(ctx: ImageContext, config: Config) {
202
9
  // TODO Replace with `import from`
203
10
  const { default: pixelmatch } = await import('pixelmatch');
204
11
 
205
- async function assertImage(actual: Buffer, imageName?: string): Promise<string | undefined> {
206
- const { expected, onCompare } = await getExpected(ctx, await getImagePaths(config, ctx.testFullPath, imageName));
207
-
208
- if (expected == null) {
209
- await onCompare(actual);
210
- return imageName ? `Expected image '${imageName}' does not exists` : 'Expected image does not exists';
211
- }
212
-
213
- if (actual.equals(expected)) {
214
- await onCompare(actual);
215
- return;
216
- }
217
-
218
- const { isEqual, diff } = compareImages(expected, actual, pixelmatch, config.diffOptions);
219
-
220
- if (isEqual) {
221
- await onCompare(actual);
222
- return;
223
- }
224
-
225
- await onCompare(actual, expected, diff);
226
-
227
- return imageName ? `Expected image '${imageName}' to match` : 'Expected image to match';
228
- }
12
+ const assertImage = getPixelmatchAssert(pixelmatch, ctx, config);
229
13
 
230
14
  return {
231
- matchImage: async (image: Buffer, imageName?: string) => {
232
- const errorMessage = await assertImage(image, imageName);
15
+ matchImage: async (image: Buffer | string, imageName?: string) => {
16
+ const errorMessage = await assertImage(toBuffer(image), imageName);
233
17
  if (errorMessage) {
234
18
  throw createImageError(imageName ? { [imageName]: errorMessage } : errorMessage);
235
19
  }
236
20
  },
237
- matchImages: async (images: Record<string, Buffer>) => {
21
+ matchImages: async (images: Record<string, Buffer | string>) => {
238
22
  const errors: Record<string, string> = {};
239
23
  await Promise.all(
240
24
  Object.entries(images).map(async ([imageName, image]) => {
241
- const errorMessage = await assertImage(image, imageName);
25
+ const errorMessage = await assertImage(toBuffer(image), imageName);
242
26
  if (errorMessage) {
243
27
  errors[imageName] = errorMessage;
244
28
  }
@@ -257,39 +41,23 @@ function createImageError(imageErrors: string | Partial<Record<string, string>>)
257
41
  return error;
258
42
  }
259
43
 
260
- export function getOdiffMatchers(ctx: ImageContext, config: Config) {
261
- const diffOptions = {
262
- ...config.odiffOptions,
263
- noFailOnFsErrors: true,
264
- };
44
+ export async function getOdiffMatchers(ctx: ImageContext, config: Config) {
45
+ const { compare } = await import('odiff-bin');
265
46
 
266
- async function assertImage(image: Buffer, imageName?: string): Promise<string | undefined> {
267
- const { actual, expect, diff } = await getOdiffExpected(
268
- ctx,
269
- image,
270
- await getImagePaths(config, ctx.testFullPath, imageName),
271
- );
272
- const result = await compare(actual, expect, diff, diffOptions);
273
- if (!result.match) {
274
- if (result.reason == 'file-not-exists') {
275
- return imageName ? `Expected image '${imageName}' does not exists` : 'Expected image does not exists';
276
- }
277
- return imageName ? `Expected image '${imageName}' to match` : 'Expected image to match';
278
- }
279
- }
47
+ const assertImage = getOdiffAssert(compare, ctx, config);
280
48
 
281
49
  return {
282
- matchImage: async (image: Buffer, imageName?: string) => {
283
- const errorMessage = await assertImage(image, imageName);
50
+ matchImage: async (image: Buffer | string, imageName?: string) => {
51
+ const errorMessage = await assertImage(toBuffer(image), imageName);
284
52
  if (errorMessage) {
285
53
  throw createImageError(imageName ? { [imageName]: errorMessage } : errorMessage);
286
54
  }
287
55
  },
288
- matchImages: async (images: Record<string, Buffer>) => {
56
+ matchImages: async (images: Record<string, Buffer | string>) => {
289
57
  const errors: Record<string, string> = {};
290
58
  await Promise.all(
291
59
  Object.entries(images).map(async ([imageName, image]) => {
292
- const errorMessage = await assertImage(image, imageName);
60
+ const errorMessage = await assertImage(toBuffer(image), imageName);
293
61
  if (errorMessage) {
294
62
  errors[imageName] = errorMessage;
295
63
  }
@@ -1,25 +1,22 @@
1
1
  import chai from 'chai';
2
- import EventEmitter from 'events';
3
2
  import {
4
3
  BaseCreeveyTestContext,
5
4
  Config,
6
5
  CreeveyWebdriver,
7
- FakeSuite,
8
- FakeTest,
9
- Images,
10
- Options,
11
6
  ServerTest,
12
- TEST_EVENTS,
13
7
  TestMessage,
8
+ TestResult,
9
+ WorkerOptions,
14
10
  isDefined,
15
11
  isImageError,
16
12
  } from '../../types.js';
17
13
  import { subscribeOn, emitTestMessage, emitWorkerMessage } from '../messages.js';
18
14
  import chaiImage from './chai-image.js';
19
- import { getMatchers, getOdiffMatchers, ImageContext } from './match-image.js';
15
+ import { getMatchers, getOdiffMatchers } from './match-image.js';
20
16
  import { loadTestsFromStories } from '../stories.js';
21
17
  import { logger } from '../logger.js';
22
18
  import { getTestPath } from '../utils.js';
19
+ import { ImageContext } from '../compare.js';
23
20
 
24
21
  async function getTestsFromStories(
25
22
  config: Config,
@@ -45,9 +42,10 @@ async function getTestsFromStories(
45
42
  return testsById;
46
43
  }
47
44
 
48
- function runHandler(browserName: string, images: Partial<Record<string, Images>>, error?: unknown): void {
45
+ function runHandler(browserName: string, result: Omit<TestResult, 'status'>, error?: unknown): void {
49
46
  // TODO How handle browser corruption?
50
- if (isImageError(error)) {
47
+ const { images } = result;
48
+ if (images != null && isImageError(error)) {
51
49
  if (typeof error.images == 'string') {
52
50
  const image = images[browserName];
53
51
  if (image) image.error = error.images;
@@ -60,25 +58,31 @@ function runHandler(browserName: string, images: Partial<Record<string, Images>>
60
58
  }
61
59
  }
62
60
 
63
- if (error || Object.values(images).some((image) => image?.error != null)) {
64
- const errorMessage = serializeError(error);
61
+ if (error || (images != null && Object.values(images).some((image) => image?.error != null))) {
62
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63
+ const errorMessage = result.error!;
65
64
 
66
65
  const isUnexpectedError =
67
66
  hasTimeout(errorMessage) ||
68
67
  hasDisconnected(errorMessage) ||
69
- Object.values(images).some((image) => hasTimeout(image?.error));
68
+ (images != null && Object.values(images).some((image) => hasTimeout(image?.error)));
70
69
  if (isUnexpectedError) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error: errorMessage } });
71
70
  else
72
71
  emitTestMessage({
73
72
  type: 'end',
74
73
  payload: {
75
74
  status: 'failed',
76
- images,
77
- error: errorMessage,
75
+ ...result,
78
76
  },
79
77
  });
80
78
  } else {
81
- emitTestMessage({ type: 'end', payload: { status: 'success', images } });
79
+ emitTestMessage({
80
+ type: 'end',
81
+ payload: {
82
+ status: 'success',
83
+ ...result,
84
+ },
85
+ });
82
86
  }
83
87
  }
84
88
 
@@ -111,8 +115,7 @@ function hasTimeout(str: string | null | undefined): boolean {
111
115
  return str?.toLowerCase().includes('timeout') ?? false;
112
116
  }
113
117
 
114
- export async function start(browser: string, gridUrl: string, config: Config, options: Options): Promise<void> {
115
- let retries = 0;
118
+ export async function start(browser: string, gridUrl: string, config: Config, options: WorkerOptions): Promise<void> {
116
119
  const imagesContext: ImageContext = {
117
120
  attachments: [],
118
121
  testFullPath: [],
@@ -123,28 +126,8 @@ export async function start(browser: string, gridUrl: string, config: Config, op
123
126
 
124
127
  if (!webdriver || !sessionId) return;
125
128
 
126
- const reporterOptions = {
127
- ...config.reporterOptions,
128
- creevey: {
129
- sessionId,
130
- reportDir: config.reportDir,
131
- browserName: browser,
132
- get willRetry() {
133
- return retries < config.maxRetries;
134
- },
135
- get images() {
136
- return imagesContext.images;
137
- },
138
- },
139
- };
140
-
141
- class FakeRunner extends EventEmitter {}
142
- const runner = new FakeRunner();
143
- const Reporter = config.reporter;
144
- new Reporter(runner, { reporterOptions });
145
-
146
129
  const { matchImage, matchImages } = options.odiff
147
- ? getOdiffMatchers(imagesContext, config)
130
+ ? await getOdiffMatchers(imagesContext, config)
148
131
  : await getMatchers(imagesContext, config);
149
132
  chai.use(chaiImage(matchImage, matchImages));
150
133
 
@@ -196,32 +179,9 @@ export async function start(browser: string, gridUrl: string, config: Config, op
196
179
  imagesContext.testFullPath = getTestPath(test);
197
180
  imagesContext.images = {};
198
181
 
199
- retries = message.payload.retries;
200
182
  let error = undefined;
201
183
 
202
- const fakeSuite: FakeSuite = {
203
- title: test.storyPath.slice(0, -1).join('/'),
204
- fullTitle: () => fakeSuite.title,
205
- titlePath: () => [fakeSuite.title],
206
- tests: [],
207
- };
208
-
209
- const fakeTest: FakeTest = {
210
- parent: fakeSuite,
211
- title: [test.story.name, test.testName, test.browser].filter(isDefined).join('/'),
212
- fullTitle: () => getTestPath(test).join('/'),
213
- titlePath: () => getTestPath(test),
214
- currentRetry: () => retries,
215
- retires: () => config.maxRetries,
216
- slow: () => 1000,
217
- };
218
-
219
- fakeSuite.tests.push(fakeTest);
220
-
221
184
  void (async () => {
222
- runner.emit(TEST_EVENTS.RUN_BEGIN);
223
- runner.emit(TEST_EVENTS.TEST_BEGIN, fakeTest);
224
-
225
185
  let timeout;
226
186
  let isRejected = false;
227
187
  const start = Date.now();
@@ -241,22 +201,9 @@ export async function start(browser: string, gridUrl: string, config: Config, op
241
201
  ]);
242
202
  } catch (testError) {
243
203
  error = testError;
244
- fakeTest.err = error;
245
204
  }
246
205
  const duration = Date.now() - start;
247
206
  clearTimeout(timeout);
248
- fakeTest.attachments = imagesContext.attachments;
249
- fakeTest.state = error ? 'failed' : 'passed';
250
- fakeTest.duration = duration;
251
- fakeTest.speed = duration > fakeTest.slow() ? 'slow' : duration / 2 > fakeTest.slow() ? 'medium' : 'fast';
252
-
253
- if (error) {
254
- runner.emit(TEST_EVENTS.TEST_FAIL, fakeTest, error);
255
- } else {
256
- runner.emit(TEST_EVENTS.TEST_PASS, fakeTest);
257
- }
258
- runner.emit(TEST_EVENTS.TEST_END, fakeTest);
259
- runner.emit(TEST_EVENTS.RUN_END);
260
207
 
261
208
  await webdriver.afterTest(test);
262
209
 
@@ -267,7 +214,17 @@ export async function start(browser: string, gridUrl: string, config: Config, op
267
214
  payload: { subtype: 'unknown', error: serializeError(error) },
268
215
  });
269
216
  } else {
270
- runHandler(baseContext.browserName, imagesContext.images, error);
217
+ const result = {
218
+ sessionId,
219
+ browserName: baseContext.browserName,
220
+ workerId: process.pid,
221
+ images: imagesContext.images,
222
+ error: error ? serializeError(error) : undefined,
223
+ duration,
224
+ attachments: imagesContext.attachments,
225
+ retries: message.payload.retries,
226
+ };
227
+ runHandler(baseContext.browserName, result, error);
271
228
  }
272
229
  })().catch((error: unknown) => {
273
230
  logger().error('Unexpected error:', error);
@@ -1,12 +1,14 @@
1
- import _ from 'lodash';
2
- import { Parameters } from '@storybook/csf';
1
+ import mapValues from 'lodash/mapValues.js';
2
+ import mergeWith from 'lodash/mergeWith.js';
3
+ import cloneDeepWith from 'lodash/cloneDeepWith.js';
4
+ import type { Parameters } from 'storybook/internal/types';
3
5
  import { SetStoriesData, StoriesRaw, CreeveyStoryParams, StoryInput } from '../types.js';
4
6
  import { deserializeRegExp, isSerializedRegExp, isRegExp, serializeRegExp } from './serializeRegExp.js';
5
7
 
6
8
  // NOTE: Copy-paste from storybook/api
7
9
  export const combineParameters = (...parameterSets: Parameters[]): Parameters =>
8
10
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
9
- _.mergeWith({}, ...parameterSets, (_: unknown, srcValue: unknown) => {
11
+ mergeWith({}, ...parameterSets, (_: unknown, srcValue: unknown) => {
10
12
  // Treat arrays as scalars:
11
13
  if (Array.isArray(srcValue)) return srcValue as unknown[];
12
14
 
@@ -19,14 +21,14 @@ export const denormalizeStoryParameters = ({
19
21
  kindParameters,
20
22
  stories,
21
23
  }: SetStoriesData): StoriesRaw => {
22
- return _.mapValues(stories, (storyData) => ({
24
+ return mapValues(stories, (storyData) => ({
23
25
  ...storyData,
24
26
  parameters: combineParameters(globalParameters, kindParameters[storyData.title] ?? {}, storyData.parameters),
25
27
  })) as StoriesRaw;
26
28
  };
27
29
 
28
30
  export const serializeRawStories = (stories: StoriesRaw): StoriesRaw => {
29
- return _.mapValues(stories, (storyData) => {
31
+ return mapValues(stories, (storyData) => {
30
32
  const creevey = storyData.parameters.creevey as CreeveyStoryParams | undefined;
31
33
  if (creevey?.skip) {
32
34
  return {
@@ -35,7 +37,7 @@ export const serializeRawStories = (stories: StoriesRaw): StoriesRaw => {
35
37
  ...storyData.parameters,
36
38
  creevey: {
37
39
  ...creevey,
38
- skip: _.cloneDeepWith(creevey.skip, (value) => {
40
+ skip: cloneDeepWith(creevey.skip, (value) => {
39
41
  if (isRegExp(value)) {
40
42
  return serializeRegExp(value);
41
43
  }
@@ -50,7 +52,7 @@ export const serializeRawStories = (stories: StoriesRaw): StoriesRaw => {
50
52
  };
51
53
 
52
54
  export const deserializeRawStories = (stories: StoriesRaw): StoriesRaw => {
53
- return _.mapValues(stories, deserializeStory);
55
+ return mapValues(stories, deserializeStory);
54
56
  };
55
57
 
56
58
  export const deserializeStory = (story: StoryInput): StoryInput => {
@@ -62,7 +64,7 @@ export const deserializeStory = (story: StoryInput): StoryInput => {
62
64
  ...story.parameters,
63
65
  creevey: {
64
66
  ...creevey,
65
- skip: _.cloneDeepWith(creevey.skip, (value) => {
67
+ skip: cloneDeepWith(creevey.skip, (value) => {
66
68
  if (isSerializedRegExp(value)) {
67
69
  return deserializeRegExp(value);
68
70
  }