creevey 0.10.0-beta.9 → 0.10.0-rc.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 (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 +25 -0
  107. package/dist/playwright/generator.js +243 -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 +161 -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 +37 -0
  246. package/docs/migration-0.9-to-0.10.md +144 -0
  247. package/docs/playwright-reporter.md +357 -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 +322 -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 +161 -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 +71 -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,4 +1,4 @@
1
- import React, { Suspense } from 'react';
1
+ import React, { Suspense, lazy } from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { CreeveyApp } from './CreeveyApp.js';
4
4
 
@@ -24,7 +24,7 @@ function loadCreeveyData(): Promise<CreeveyStatus['tests']> {
24
24
  });
25
25
  }
26
26
 
27
- const CreeveyAppAsync = React.lazy(async () => {
27
+ const CreeveyAppAsync = lazy(async () => {
28
28
  let isReport = false;
29
29
  let creeveyStatus: CreeveyStatus;
30
30
  let creeveyApi: CreeveyClientApi | undefined;
@@ -36,11 +36,11 @@ const CreeveyAppAsync = React.lazy(async () => {
36
36
  // NOTE: Failed to get status from API
37
37
  // NOTE: It might happen on circle ci from artifact
38
38
  isReport = true;
39
- creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
39
+ creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
40
40
  }
41
41
  } else {
42
42
  isReport = true;
43
- creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
43
+ creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
44
44
  }
45
45
 
46
46
  return {
@@ -48,7 +48,12 @@ const CreeveyAppAsync = React.lazy(async () => {
48
48
  return (
49
49
  <CreeveyApp
50
50
  api={creeveyApi}
51
- initialState={{ isReport, isRunning: creeveyStatus.isRunning, tests: treeifyTests(creeveyStatus.tests) }}
51
+ initialState={{
52
+ isReport,
53
+ isRunning: creeveyStatus.isRunning,
54
+ tests: treeifyTests(creeveyStatus.tests),
55
+ isUpdateMode: creeveyStatus.isUpdateMode,
56
+ }}
52
57
  />
53
58
  );
54
59
  },
@@ -0,0 +1,24 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { themes, ThemeVars } from 'storybook/theming';
3
+ import { isDefined } from '../../types.js';
4
+
5
+ const CREEVEY_THEME = 'Creevey_theme';
6
+
7
+ function isTheme(theme?: string | null): theme is ThemeVars['base'] {
8
+ return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
9
+ }
10
+
11
+ function initialTheme(): ThemeVars['base'] {
12
+ const theme = localStorage.getItem(CREEVEY_THEME);
13
+ return isTheme(theme) ? theme : 'light';
14
+ }
15
+
16
+ export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
17
+ const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
18
+
19
+ useEffect(() => {
20
+ localStorage.setItem(CREEVEY_THEME, theme);
21
+ }, [theme]);
22
+
23
+ return [theme, setTheme];
24
+ }
package/src/creevey.ts CHANGED
@@ -1,54 +1,108 @@
1
1
  import cluster from 'cluster';
2
- import minimist from 'minimist';
3
- import creevey from './server/index.js';
4
- import { Options } from './types.js';
5
- import { emitWorkerMessage } from './server/messages.js';
6
- import { isShuttingDown, shutdownWorkers } from './server/utils.js';
2
+ import * as v from 'valibot';
7
3
  import Logger from 'loglevel';
4
+ import { cac } from 'cac';
5
+ import { Options, OptionsSchema, WorkerOptions, WorkerOptionsSchema } from './types.js';
6
+ import { version } from '../package.json';
8
7
  import { logger, setRootName } from './server/logger.js';
8
+ import creevey from './server/index.js';
9
+ import './server/shutdown.js';
10
+
11
+ const workerCli = cac('worker');
12
+ workerCli
13
+ .command('worker', 'Start worker')
14
+ .option('--browser <browser>', 'Specify browser to run tests')
15
+ .option('--grid-url <url>', 'Selenium grid URL')
16
+ .option('-d, --debug', 'Enable debug mode')
17
+ .option('-c, --config <config>', 'Path to config file')
18
+ .option('-p, --port <port>', 'Port for UI server', { default: 3000 })
19
+ .option('--trace', 'Enable trace mode (more verbose than debug)')
20
+ .option('--report-dir <dir>', 'Directory for test reports')
21
+ .option('--screen-dir <dir>', 'Directory for reference images')
22
+ .option('--storybook-url <url>', 'Storybook server URL')
23
+ .option('--odiff', 'Use odiff for image comparison');
24
+ workerCli.parse();
9
25
 
10
- function shutdownOnException(reason: unknown): void {
11
- if (isShuttingDown.current) return;
26
+ const cli = cac('creevey');
12
27
 
13
- const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
28
+ cli
29
+ .command('report [reportDir]', 'Launch web UI to review and approve test results')
30
+ .option('-c, --config <config>', 'Path to config file')
31
+ .option('-p, --port <port>', 'Port for UI server', { default: 3000 })
32
+ .option('--screen-dir <dir>', 'Directory for reference images');
14
33
 
15
- logger().error(error);
34
+ // TODO Add ability to start specific tests/files/stories
35
+ cli
36
+ .command('test [options]', 'Run tests')
37
+ .option('--ui', 'Launch web UI for running tests and reviewing test results')
38
+ .option('-s, --storybook-start [cmd]', 'Start Storybook automatically')
39
+ .option('-c, --config <config>', 'Path to config file')
40
+ .option('-d, --debug', 'Enable debug mode')
41
+ .option('-p, --port <port>', 'Port for UI server', { default: 3000 })
42
+ .option('--fail-fast', 'Stop tests after first failure')
43
+ .option('--report-dir <dir>', 'Directory for test reports')
44
+ .option('--screen-dir <dir>', 'Directory for reference images')
45
+ .option('--storybook-url <url>', 'Storybook server URL')
46
+ .option('--storybook-port <port>', 'Storybook server port')
47
+ .option('--reporter <reporter>', '[DEPRECATED] Use config file instead')
48
+ .option('--odiff', 'Use odiff for image comparison')
49
+ .option('--trace', 'Enable trace mode (more verbose than debug)')
50
+ .option('--no-docker', 'Disable Docker usage');
51
+ cli.version(process.env.npm_package_version ?? version);
52
+ cli.help();
53
+ cli.parse();
16
54
 
17
- process.exitCode = -1;
18
- if (cluster.isWorker) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error } });
19
- if (cluster.isPrimary) void shutdownWorkers();
55
+ if (
56
+ process.argv.includes('--help') ||
57
+ process.argv.includes('-h') ||
58
+ process.argv.includes('--version') ||
59
+ process.argv.includes('-v')
60
+ ) {
61
+ process.exit(0);
20
62
  }
21
63
 
22
- process.on('uncaughtException', shutdownOnException);
23
- process.on('unhandledRejection', shutdownOnException);
24
- // TODO SIGINT Stuck with selenium
25
- process.on('SIGINT', () => {
26
- if (isShuttingDown.current) {
27
- process.exit(-1);
64
+ const command = cluster.isWorker ? workerCli.matchedCommandName : cli.matchedCommandName;
65
+ const args = cluster.isWorker ? workerCli.args : cli.args;
66
+ let options: Options | WorkerOptions;
67
+
68
+ if (!command || (command !== 'report' && command !== 'test' && command !== 'worker')) {
69
+ console.error('Error: No known command specified\n');
70
+ cli.outputHelp();
71
+ process.exit(1);
72
+ }
73
+
74
+ try {
75
+ options = cluster.isWorker ? v.parse(WorkerOptionsSchema, workerCli.options) : v.parse(OptionsSchema, cli.options);
76
+ } catch (error: unknown) {
77
+ if (v.isValiError(error)) {
78
+ console.error('Options validation failed:');
79
+ for (const issue of error.issues) {
80
+ const path = issue.path?.map((p) => p.key).join('.');
81
+ console.error(` ${path ? `${path}: ` : ''}${issue.message}`);
82
+ }
83
+ } else {
84
+ console.error(error);
28
85
  }
29
- isShuttingDown.current = true;
30
- });
86
+ console.log();
87
+ cli.matchedCommand?.outputHelp();
88
+
89
+ process.exit(1);
90
+ }
31
91
 
32
- const argv = minimist<Options>(process.argv.slice(2), {
33
- string: ['browser', 'config', 'reporter', 'reportDir', 'screenDir', 'gridUrl', 'storybookUrl'],
34
- boolean: ['debug', 'trace', 'ui', 'odiff'],
35
- default: { port: 3000 },
36
- alias: { port: 'p', config: 'c', debug: 'd', update: 'u' },
37
- });
92
+ if (v.is(OptionsSchema, options) && command == 'report' && args.length > 0) {
93
+ options.reportDir = args[0];
94
+ options.ui = true;
95
+ }
38
96
 
39
- if ('port' in argv && !isNaN(argv.port)) argv.port = Number(argv.port);
40
- if ('browser' in argv && argv.browser) setRootName(argv.browser);
97
+ // Handle browser name for logging
98
+ if (v.is(WorkerOptionsSchema, options)) setRootName(options.browser);
41
99
 
42
- // eslint-disable-next-line @typescript-eslint/no-deprecated
43
- if (cluster.isPrimary && argv.reporter) {
100
+ if (cluster.isPrimary && 'reporter' in options) {
44
101
  logger().warn(`--reporter option has been removed please describe reporter in config file:
45
102
  import { reporters } from 'mocha';
46
103
 
47
104
  const config = {
48
- reporter: reporters.${
49
- // eslint-disable-next-line @typescript-eslint/no-deprecated
50
- argv.reporter
51
- },
105
+ reporter: reporters.${options.reporter},
52
106
  };
53
107
 
54
108
  export default config;
@@ -56,11 +110,11 @@ if (cluster.isPrimary && argv.reporter) {
56
110
  }
57
111
 
58
112
  // @ts-expect-error: define log level for storybook
59
- global.LOGLEVEL = argv.trace ? 'trace' : argv.debug ? 'debug' : 'warn';
60
- if (argv.trace) {
113
+ global.LOGLEVEL = options.trace ? 'trace' : options.debug ? 'debug' : 'warn';
114
+ if (options.trace) {
61
115
  logger().setDefaultLevel(Logger.levels.TRACE);
62
116
  Logger.setDefaultLevel(Logger.levels.TRACE);
63
- } else if (argv.debug) {
117
+ } else if (options.debug) {
64
118
  logger().setDefaultLevel(Logger.levels.DEBUG);
65
119
  Logger.setDefaultLevel(Logger.levels.DEBUG);
66
120
  } else {
@@ -68,4 +122,4 @@ if (argv.trace) {
68
122
  Logger.setDefaultLevel(Logger.levels.INFO);
69
123
  }
70
124
 
71
- void creevey(argv);
125
+ void creevey(command, options);
@@ -0,0 +1,322 @@
1
+ import path from 'path';
2
+ import assert from 'assert';
3
+ import { readFileSync } from 'fs';
4
+ import type { PixelmatchOptions } from 'pixelmatch';
5
+ import type { ODiffOptions } from 'odiff-bin';
6
+ import { test, Page } from '@playwright/test';
7
+ import isEqual from 'lodash/isEqual.js';
8
+ import { CreeveyStoryParams, isObject, StoriesRaw, StorybookEvents, StorybookGlobals } from '../types';
9
+ import { getOdiffAssert, getPixelmatchAssert, ImageContext } from '../server/compare';
10
+ import { appendIframePath } from '../server/webdriver';
11
+ import { waitForStorybookReady } from './helpers';
12
+
13
+ export interface TestsConfig {
14
+ /**
15
+ * Define pixelmatch diff options
16
+ * @default { threshold: 0.1, includeAA: false }
17
+ */
18
+ diffOptions: PixelmatchOptions;
19
+ /**
20
+ * Define odiff diff options
21
+ * @default { threshold: 0.1, antialiasing: true }
22
+ */
23
+ odiffOptions: ODiffOptions;
24
+ /**
25
+ * Define matcher for visual regression assertion
26
+ * @default 'pixelmatch'
27
+ */
28
+ comparisonLibrary: 'pixelmatch' | 'odiff';
29
+ /**
30
+ * Enables page context reuse across tests for faster execution, though this breaks test isolation.
31
+ * @default true
32
+ */
33
+ reusePageContext: boolean;
34
+ }
35
+
36
+ const cacheDir = process.env.CREEVEY_CACHE_DIR;
37
+ const defaultConfig: TestsConfig = {
38
+ diffOptions: { threshold: 0.1, includeAA: false },
39
+ odiffOptions: { threshold: 0.1, antialiasing: true },
40
+ comparisonLibrary: 'pixelmatch',
41
+ reusePageContext: true,
42
+ };
43
+
44
+ // TODO: Use this Storybook function for building args for query params
45
+ // export const buildArgsParam = (initialArgs: Args | undefined, args: Args): string => {
46
+
47
+ // TODO: Pass globals to story
48
+ function appendStoryQueryParams(url: string, storyId: string): string {
49
+ return `${url}?args=&globals=&id=${storyId}`;
50
+ }
51
+
52
+ function assertWrapper(
53
+ assert: (actual: Buffer, imageName?: string) => Promise<string | undefined>,
54
+ ): (actual: Buffer, imageName?: string) => Promise<void> {
55
+ return async function assertImage(actual, imageName) {
56
+ try {
57
+ const errorMessage = await assert(actual, imageName);
58
+ if (errorMessage) {
59
+ throw new Error(errorMessage);
60
+ }
61
+ } catch (error) {
62
+ if (error instanceof Error) {
63
+ error.stack = error.stack
64
+ ?.split('\n')
65
+ .filter((line) => !line.includes('at assertImage'))
66
+ .join('\n');
67
+ }
68
+ throw error;
69
+ }
70
+ };
71
+ }
72
+
73
+ async function takeScreenshot(
74
+ page: Page,
75
+ storyId: string,
76
+ captureElement?: string | null,
77
+ ignoreElements?: string | string[] | null,
78
+ ): Promise<Buffer> {
79
+ const ignore = ignoreElements ? (Array.isArray(ignoreElements) ? ignoreElements : [ignoreElements]) : [];
80
+ const mask = ignore.map((selector) => page.locator(selector));
81
+
82
+ if (captureElement) {
83
+ const element = await page.$(captureElement);
84
+ if (!element) throw new Error(`Capture element '${captureElement}' not found for story '${storyId}'`);
85
+ return element.screenshot({
86
+ style: ':root { overflow: hidden !important; }',
87
+ animations: 'disabled',
88
+ mask,
89
+ });
90
+ } else {
91
+ return page.screenshot({
92
+ animations: 'disabled',
93
+ mask,
94
+ });
95
+ }
96
+ }
97
+
98
+ // TODO: To support parallel tests, we need to define each test suite in separate file
99
+ // TODO: How to support custom interactions for different tests
100
+ // Main function to define tests using Playwright's API
101
+ export function definePlaywrightTests(config?: Partial<TestsConfig>): void {
102
+ assert(cacheDir, 'Cache directory not found');
103
+
104
+ const stories = JSON.parse(readFileSync(path.join(cacheDir, 'stories.json'), 'utf-8')) as StoriesRaw;
105
+ let globals: StorybookGlobals = {};
106
+ let reusedPage: Page;
107
+
108
+ const { diffOptions, odiffOptions, comparisonLibrary, reusePageContext } = {
109
+ ...defaultConfig,
110
+ ...config,
111
+ };
112
+
113
+ async function updateGlobals(page: Page, storybookGlobals: unknown): Promise<void> {
114
+ if (storybookGlobals && typeof storybookGlobals === 'object' && !isEqual(globals, storybookGlobals)) {
115
+ globals = storybookGlobals as StorybookGlobals;
116
+ await page.evaluate((globals) => {
117
+ window.__STORYBOOK_ADDONS_CHANNEL__.emit(StorybookEvents.UPDATE_GLOBALS, { globals });
118
+ }, globals);
119
+ }
120
+ }
121
+
122
+ test.describe('Creevey Tests', () => {
123
+ const imagesContext: ImageContext = {
124
+ attachments: [],
125
+ testFullPath: [],
126
+ images: {},
127
+ };
128
+ let assertImage: (actual: Buffer, imageName?: string) => Promise<void>;
129
+
130
+ test.beforeAll('Setup images context', async ({ browser }, { project }) => {
131
+ const { snapshotDir, outputDir } = project;
132
+ if (reusePageContext) {
133
+ const storybookUrl = project.use.baseURL;
134
+
135
+ assert(storybookUrl, 'Storybook URL not found');
136
+
137
+ reusedPage = await browser.newPage();
138
+ await reusedPage.goto(appendIframePath(storybookUrl), { waitUntil: 'networkidle', timeout: 60000 });
139
+ await waitForStorybookReady(reusedPage);
140
+ }
141
+ if (comparisonLibrary === 'pixelmatch') {
142
+ const { default: pixelmatch } = await import('pixelmatch');
143
+ assertImage = assertWrapper(
144
+ getPixelmatchAssert(pixelmatch, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, diffOptions }),
145
+ );
146
+ } else {
147
+ const { compare } = await import('odiff-bin');
148
+ assertImage = assertWrapper(
149
+ getOdiffAssert(compare, imagesContext, { screenDir: snapshotDir, reportDir: outputDir, odiffOptions }),
150
+ );
151
+ }
152
+ });
153
+
154
+ test.beforeEach('Switch story', async ({ page }, { annotations, project }) => {
155
+ const { description: storyId } = annotations.find((annotation) => annotation.type === 'storyId') ?? {};
156
+
157
+ assert(storyId, 'Cannot get storyId. It seems like inner test annotation is missing');
158
+
159
+ const story = stories[storyId];
160
+
161
+ assert(story, `Story '${storyId}' not found in stories cache`);
162
+
163
+ const { title, name, parameters } = story;
164
+ const { waitForReady: shouldWaitForReady } = (parameters.creevey ?? {}) as CreeveyStoryParams;
165
+
166
+ const storybookGlobals: unknown = project.metadata.storybookGlobals;
167
+
168
+ imagesContext.attachments = [];
169
+ imagesContext.testFullPath = [...title.split('/').map((x) => x.trim()), name, project.name];
170
+ imagesContext.images = {};
171
+
172
+ if (!reusePageContext) {
173
+ const storybookUrl = project.use.baseURL;
174
+
175
+ assert(storybookUrl, 'Storybook URL not found');
176
+
177
+ await page.goto(appendStoryQueryParams(appendIframePath(storybookUrl), storyId), {
178
+ waitUntil: 'networkidle',
179
+ timeout: 60000,
180
+ });
181
+ await waitForStorybookReady(page);
182
+ // TODO: Pass globals to story
183
+ await updateGlobals(page, storybookGlobals);
184
+
185
+ return;
186
+ }
187
+
188
+ // 1. Update Storybook Globals
189
+ await updateGlobals(reusedPage, storybookGlobals);
190
+
191
+ // 2. Reset Mouse Position
192
+ await reusedPage.mouse.move(0, 0);
193
+
194
+ // 3. Select Story
195
+ const errorMessage = await reusedPage.evaluate<
196
+ string | null,
197
+ { storyId: string; StorybookEvents: typeof StorybookEvents; shouldWaitForReady?: boolean }
198
+ >(
199
+ async ({ storyId, StorybookEvents, shouldWaitForReady }) => {
200
+ // TODO: DRY with withCreevey.ts
201
+ // NOTE: Copy-pasted from withCreevey.ts
202
+ const channel = window.__STORYBOOK_ADDONS_CHANNEL__;
203
+
204
+ const waitForReady = shouldWaitForReady
205
+ ? new Promise<void>((resolve) => (window.__CREEVEY_SET_READY_FOR_CAPTURE__ = resolve))
206
+ : Promise.resolve();
207
+
208
+ let rejectCallback: (reason?: unknown) => void;
209
+ const renderErrorPromise = new Promise<void>((_resolve, reject) => (rejectCallback = reject));
210
+
211
+ function errorHandler({ title, description }: { title: string; description: string }): void {
212
+ rejectCallback({
213
+ message: title,
214
+ stack: description,
215
+ });
216
+ }
217
+ function exceptionHandler(exception: Error): void {
218
+ rejectCallback(exception);
219
+ }
220
+ function removeErrorHandlers(): void {
221
+ channel.off(StorybookEvents.STORY_ERRORED, errorHandler);
222
+ channel.off(StorybookEvents.STORY_THREW_EXCEPTION, errorHandler);
223
+ }
224
+
225
+ channel.once(StorybookEvents.STORY_ERRORED, errorHandler);
226
+ channel.once(StorybookEvents.STORY_THREW_EXCEPTION, exceptionHandler);
227
+
228
+ let resolveCallback: () => void;
229
+ const storyRenderedPromise = new Promise<void>((resolve) => (resolveCallback = resolve));
230
+ function renderHandler(): void {
231
+ resolveCallback();
232
+ }
233
+ function removeRenderHandlers(): void {
234
+ channel.off(StorybookEvents.STORY_RENDERED, renderHandler);
235
+ }
236
+
237
+ channel.once(StorybookEvents.STORY_RENDERED, renderHandler);
238
+
239
+ setTimeout(() => {
240
+ channel.emit(StorybookEvents.SET_CURRENT_STORY, { storyId });
241
+ }, 0);
242
+
243
+ try {
244
+ await Promise.race([renderErrorPromise, Promise.all([storyRenderedPromise, waitForReady])]);
245
+ } catch (reason) {
246
+ // NOTE Event `STORY_THREW_EXCEPTION` triggered only in react and vue frameworks and return Error instance
247
+ // NOTE Event `STORY_ERRORED` return error-like object without `name` field
248
+ const errorMessage =
249
+ reason instanceof Error
250
+ ? (reason.stack ?? reason.message)
251
+ : isObject(reason)
252
+ ? `${reason.message as string}\n ${reason.stack as string}`
253
+ : (reason as string);
254
+ return errorMessage;
255
+ } finally {
256
+ removeErrorHandlers();
257
+ removeRenderHandlers();
258
+ }
259
+
260
+ return null;
261
+ },
262
+ { storyId: story.id, StorybookEvents, shouldWaitForReady },
263
+ );
264
+
265
+ if (errorMessage) {
266
+ throw new Error(`Failed to select story '${story.id}': ${errorMessage}`);
267
+ }
268
+ });
269
+
270
+ test.afterEach('Save screenshot', () => {
271
+ const { name: projectName } = test.info().project;
272
+
273
+ // TODO: Use another way to handle attachments
274
+
275
+ // NOTE: Don't need to copy files for assertImage, because it's done internally
276
+ const { actual, diff, expect } = imagesContext.images[projectName] ?? {};
277
+ for (const image of imagesContext.attachments) {
278
+ switch (true) {
279
+ case image.includes('actual') && !!actual: {
280
+ test.info().attachments.push({ name: actual, path: image, contentType: 'image/png' });
281
+ // await test.info().attach(actual, { path: image });
282
+ break;
283
+ }
284
+ case image.includes('expect') && !!expect: {
285
+ test.info().attachments.push({ name: expect, path: image, contentType: 'image/png' });
286
+ // await test.info().attach(expect, { path: image });
287
+ break;
288
+ }
289
+ case image.includes('diff') && !!diff: {
290
+ test.info().attachments.push({ name: diff, path: image, contentType: 'image/png' });
291
+ // await test.info().attach(diff, { path: image });
292
+ break;
293
+ }
294
+ }
295
+ }
296
+ });
297
+
298
+ for (const story of Object.values(stories)) {
299
+ const { name, title, parameters } = story;
300
+ const { captureElement, ignoreElements } = (parameters.creevey ?? {}) as CreeveyStoryParams;
301
+
302
+ test.describe(title, () => {
303
+ // TODO: Support creevey.skip
304
+ test(name, { annotation: [{ type: 'storyId', description: story.id }] }, async ({ page }) => {
305
+ // 4. Take Screenshot
306
+ const screenshot = await takeScreenshot(
307
+ reusePageContext ? reusedPage : page,
308
+ story.id,
309
+ captureElement,
310
+ ignoreElements,
311
+ );
312
+ // TODO: Support this
313
+ // NOTE: Bear in mind that page.locator('#root > *') is not working
314
+ // await expect(page.locator(captureElement)).toHaveScreenshot(name);
315
+
316
+ // 5. Assert Image
317
+ await assertImage(screenshot);
318
+ });
319
+ });
320
+ }
321
+ });
322
+ }
@@ -0,0 +1,31 @@
1
+ import { StorybookEvents } from '../types';
2
+ import { Page } from '@playwright/test';
3
+
4
+ export async function waitForStorybookReady(page: Page, timeout = 60000): Promise<void> {
5
+ const isStorybookInitialized = await page.evaluate(
6
+ ({ timeout, event }: { timeout: number; event: string }) => {
7
+ return new Promise<boolean>((resolve, reject) => {
8
+ let attempts = 0;
9
+ const maxAttempts = timeout / 100;
10
+ function check() {
11
+ if (
12
+ typeof window.__STORYBOOK_ADDONS_CHANNEL__ !== 'undefined' &&
13
+ window.__STORYBOOK_ADDONS_CHANNEL__.last(event) !== undefined
14
+ ) {
15
+ resolve(true);
16
+ } else if (attempts++ < maxAttempts) {
17
+ setTimeout(check, 100);
18
+ } else {
19
+ reject(new Error('Storybook initialization timed out. Required Storybook functions not found on window.'));
20
+ }
21
+ }
22
+ check();
23
+ });
24
+ },
25
+ { timeout, event: StorybookEvents.SET_GLOBALS },
26
+ );
27
+
28
+ if (!isStorybookInitialized) {
29
+ throw new Error('Failed to confirm Storybook API is ready after extended wait.');
30
+ }
31
+ }