creevey 0.10.0-beta.8 → 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 (350) 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 +4 -4
  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 +14 -21
  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 +31 -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 +2 -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 +45 -15
  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 +44 -15
  62. package/dist/client/web/CreeveyApp.js.map +1 -1
  63. package/dist/client/web/CreeveyContext.d.ts +6 -0
  64. package/dist/client/web/CreeveyContext.js +21 -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 +20 -10
  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 +67 -13
  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 -13
  82. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  83. package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -13
  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.d.ts +1 -8
  94. package/dist/client/web/KeyboardEventsContext.js +79 -64
  95. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  96. package/dist/client/web/assets/index-CtSq3IhG.js +518 -0
  97. package/dist/client/web/index.html +1 -1
  98. package/dist/client/web/index.js +26 -11
  99. package/dist/client/web/index.js.map +1 -1
  100. package/dist/client/web/themes.d.ts +2 -0
  101. package/dist/client/web/themes.js +22 -0
  102. package/dist/client/web/themes.js.map +1 -0
  103. package/dist/creevey.d.ts +1 -1
  104. package/dist/creevey.js +122 -41
  105. package/dist/creevey.js.map +1 -1
  106. package/dist/index.d.ts +1 -0
  107. package/dist/playwright/generator.d.ts +25 -0
  108. package/dist/playwright/generator.js +243 -0
  109. package/dist/playwright/generator.js.map +1 -0
  110. package/dist/playwright/helpers.d.ts +2 -0
  111. package/dist/playwright/helpers.js +29 -0
  112. package/dist/playwright/helpers.js.map +1 -0
  113. package/dist/playwright/reporter.d.ts +83 -0
  114. package/dist/playwright/reporter.js +334 -0
  115. package/dist/playwright/reporter.js.map +1 -0
  116. package/dist/playwright/setup.d.ts +3 -0
  117. package/dist/playwright/setup.js +72 -0
  118. package/dist/playwright/setup.js.map +1 -0
  119. package/dist/playwright.d.ts +1 -0
  120. package/dist/playwright.js +3 -1
  121. package/dist/playwright.js.map +1 -1
  122. package/dist/server/compare.d.ts +18 -0
  123. package/dist/server/compare.js +182 -0
  124. package/dist/server/compare.js.map +1 -0
  125. package/dist/server/config.d.ts +3 -3
  126. package/dist/server/config.js +75 -8
  127. package/dist/server/config.js.map +1 -1
  128. package/dist/server/connection.d.ts +3 -0
  129. package/dist/server/connection.js +28 -0
  130. package/dist/server/connection.js.map +1 -0
  131. package/dist/server/docker.d.ts +1 -1
  132. package/dist/server/docker.js +54 -32
  133. package/dist/server/docker.js.map +1 -1
  134. package/dist/server/index.d.ts +2 -2
  135. package/dist/server/index.js +165 -64
  136. package/dist/server/index.js.map +1 -1
  137. package/dist/server/master/api.d.ts +11 -6
  138. package/dist/server/master/api.js +88 -25
  139. package/dist/server/master/api.js.map +1 -1
  140. package/dist/server/master/handlers/capture-handler.d.ts +5 -0
  141. package/dist/server/master/handlers/capture-handler.js +25 -0
  142. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  143. package/dist/server/master/handlers/index.d.ts +4 -0
  144. package/dist/server/master/handlers/index.js +21 -0
  145. package/dist/server/master/handlers/index.js.map +1 -0
  146. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  147. package/dist/server/master/handlers/ping-handler.js +8 -0
  148. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  149. package/dist/server/master/handlers/static-handler.d.ts +1 -0
  150. package/dist/server/master/handlers/static-handler.js +20 -0
  151. package/dist/server/master/handlers/static-handler.js.map +1 -0
  152. package/dist/server/master/handlers/stories-handler.d.ts +4 -0
  153. package/dist/server/master/handlers/stories-handler.js +24 -0
  154. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  155. package/dist/server/master/master.js +7 -24
  156. package/dist/server/master/master.js.map +1 -1
  157. package/dist/server/master/pool.d.ts +1 -0
  158. package/dist/server/master/pool.js +5 -3
  159. package/dist/server/master/pool.js.map +1 -1
  160. package/dist/server/master/queue.d.ts +1 -1
  161. package/dist/server/master/queue.js +14 -6
  162. package/dist/server/master/queue.js.map +1 -1
  163. package/dist/server/master/runner.d.ts +6 -6
  164. package/dist/server/master/runner.js +98 -130
  165. package/dist/server/master/runner.js.map +1 -1
  166. package/dist/server/master/server.d.ts +1 -1
  167. package/dist/server/master/server.js +193 -88
  168. package/dist/server/master/server.js.map +1 -1
  169. package/dist/server/master/start.d.ts +1 -2
  170. package/dist/server/master/start.js +13 -29
  171. package/dist/server/master/start.js.map +1 -1
  172. package/dist/server/master/testsManager.d.ts +81 -0
  173. package/dist/server/master/testsManager.js +282 -0
  174. package/dist/server/master/testsManager.js.map +1 -0
  175. package/dist/server/playwright/docker-file.d.ts +1 -1
  176. package/dist/server/playwright/docker-file.js +17 -8
  177. package/dist/server/playwright/docker-file.js.map +1 -1
  178. package/dist/server/playwright/docker.d.ts +2 -1
  179. package/dist/server/playwright/docker.js +10 -2
  180. package/dist/server/playwright/docker.js.map +1 -1
  181. package/dist/server/playwright/index-source.mjs +16 -0
  182. package/dist/server/playwright/internal.d.ts +7 -7
  183. package/dist/server/playwright/internal.js +144 -84
  184. package/dist/server/playwright/internal.js.map +1 -1
  185. package/dist/server/playwright/webdriver.d.ts +3 -3
  186. package/dist/server/playwright/webdriver.js +0 -6
  187. package/dist/server/playwright/webdriver.js.map +1 -1
  188. package/dist/server/providers/browser.js +4 -3
  189. package/dist/server/providers/browser.js.map +1 -1
  190. package/dist/server/providers/hybrid.js +2 -2
  191. package/dist/server/providers/hybrid.js.map +1 -1
  192. package/dist/server/report.d.ts +10 -0
  193. package/dist/server/report.js +45 -0
  194. package/dist/server/report.js.map +1 -0
  195. package/dist/server/reporters/creevey.d.ts +7 -0
  196. package/dist/server/reporters/creevey.js +63 -0
  197. package/dist/server/reporters/creevey.js.map +1 -0
  198. package/dist/server/reporters/index.d.ts +2 -0
  199. package/dist/server/reporters/index.js +16 -0
  200. package/dist/server/reporters/index.js.map +1 -0
  201. package/dist/server/reporters/junit.d.ts +16 -0
  202. package/dist/server/reporters/junit.js +167 -0
  203. package/dist/server/reporters/junit.js.map +1 -0
  204. package/dist/server/reporters/teamcity.d.ts +7 -0
  205. package/dist/server/reporters/teamcity.js +60 -0
  206. package/dist/server/reporters/teamcity.js.map +1 -0
  207. package/dist/server/selenium/internal.d.ts +4 -4
  208. package/dist/server/selenium/internal.js +56 -40
  209. package/dist/server/selenium/internal.js.map +1 -1
  210. package/dist/server/selenium/selenoid.js +12 -6
  211. package/dist/server/selenium/selenoid.js.map +1 -1
  212. package/dist/server/selenium/webdriver.d.ts +3 -3
  213. package/dist/server/selenium/webdriver.js +4 -8
  214. package/dist/server/selenium/webdriver.js.map +1 -1
  215. package/dist/server/shutdown.d.ts +1 -0
  216. package/dist/server/shutdown.js +23 -0
  217. package/dist/server/shutdown.js.map +1 -0
  218. package/dist/server/stories.d.ts +0 -1
  219. package/dist/server/stories.js +0 -12
  220. package/dist/server/stories.js.map +1 -1
  221. package/dist/server/telemetry.js +3 -3
  222. package/dist/server/telemetry.js.map +1 -1
  223. package/dist/server/testsFiles/parser.js +45 -5
  224. package/dist/server/testsFiles/parser.js.map +1 -1
  225. package/dist/server/utils.d.ts +23 -0
  226. package/dist/server/utils.js +114 -15
  227. package/dist/server/utils.js.map +1 -1
  228. package/dist/server/webdriver.d.ts +1 -1
  229. package/dist/server/worker/context.d.ts +3 -0
  230. package/dist/server/worker/context.js +15 -0
  231. package/dist/server/worker/context.js.map +1 -0
  232. package/dist/server/worker/match-image.d.ts +8 -12
  233. package/dist/server/worker/match-image.js +11 -178
  234. package/dist/server/worker/match-image.js.map +1 -1
  235. package/dist/server/worker/start.d.ts +2 -2
  236. package/dist/server/worker/start.js +41 -64
  237. package/dist/server/worker/start.js.map +1 -1
  238. package/dist/shared/index.d.ts +1 -1
  239. package/dist/shared/index.js +9 -7
  240. package/dist/shared/index.js.map +1 -1
  241. package/dist/types.d.ts +84 -43
  242. package/dist/types.js +65 -1
  243. package/dist/types.js.map +1 -1
  244. package/docs/cli.md +80 -0
  245. package/docs/config.md +179 -165
  246. package/docs/examples/playwright-reporer/playwright.config.ts +37 -0
  247. package/docs/migration-0.9-to-0.10.md +144 -0
  248. package/docs/playwright-reporter.md +357 -0
  249. package/docs/storybook.md +60 -0
  250. package/docs/tests.md +50 -45
  251. package/package.json +78 -83
  252. package/playwright.config.mts +46 -0
  253. package/src/client/addon/components/Addon.tsx +1 -1
  254. package/src/client/addon/components/Panel.tsx +4 -4
  255. package/src/client/addon/components/TestSelect.tsx +2 -2
  256. package/src/client/addon/components/Tools.tsx +2 -2
  257. package/src/client/addon/controller.ts +4 -4
  258. package/src/client/addon/makeDecorator.ts +69 -0
  259. package/src/client/addon/manager.ts +38 -37
  260. package/src/client/addon/preset.ts +2 -1
  261. package/src/client/addon/withCreevey.ts +16 -19
  262. package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
  263. package/src/client/shared/components/ImagesView/ImagesView.tsx +1 -1
  264. package/src/client/shared/components/ImagesView/SideBySideView.tsx +2 -2
  265. package/src/client/shared/components/ImagesView/SlideView.tsx +2 -2
  266. package/src/client/shared/components/ImagesView/SwapView.tsx +20 -2
  267. package/src/client/shared/components/ImagesView/common.ts +1 -1
  268. package/src/client/shared/components/PageFooter/PageFooter.tsx +1 -1
  269. package/src/client/shared/components/PageFooter/Paging.tsx +1 -1
  270. package/src/client/shared/components/PageHeader/ImagePreview.tsx +2 -1
  271. package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
  272. package/src/client/shared/components/ResultsPage.tsx +33 -10
  273. package/src/client/shared/creeveyClientApi.ts +19 -1
  274. package/src/client/shared/helpers.ts +4 -24
  275. package/src/client/web/CreeveyApp.tsx +30 -9
  276. package/src/client/web/CreeveyContext.tsx +11 -0
  277. package/src/client/web/CreeveyLoader.tsx +2 -2
  278. package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +3 -3
  279. package/src/client/web/CreeveyView/SideBar/Search.tsx +4 -4
  280. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
  281. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +48 -15
  282. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
  283. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +12 -12
  284. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +10 -10
  285. package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +2 -2
  286. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +3 -2
  287. package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -1
  288. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  289. package/src/client/web/index.tsx +10 -5
  290. package/src/client/web/themes.ts +24 -0
  291. package/src/creevey.ts +92 -38
  292. package/src/playwright/generator.ts +322 -0
  293. package/src/playwright/helpers.ts +31 -0
  294. package/src/playwright/reporter.ts +381 -0
  295. package/src/playwright/setup.ts +84 -0
  296. package/src/playwright.ts +1 -0
  297. package/src/server/compare.ts +260 -0
  298. package/src/server/config.ts +52 -9
  299. package/src/server/connection.ts +26 -0
  300. package/src/server/docker.ts +62 -34
  301. package/src/server/index.ts +166 -79
  302. package/src/server/master/api.ts +94 -28
  303. package/src/server/master/handlers/capture-handler.ts +20 -0
  304. package/src/server/master/handlers/index.ts +4 -0
  305. package/src/server/master/handlers/ping-handler.ts +6 -0
  306. package/src/server/master/handlers/static-handler.ts +16 -0
  307. package/src/server/master/handlers/stories-handler.ts +20 -0
  308. package/src/server/master/master.ts +10 -27
  309. package/src/server/master/pool.ts +7 -3
  310. package/src/server/master/queue.ts +21 -7
  311. package/src/server/master/runner.ts +123 -134
  312. package/src/server/master/server.ts +214 -101
  313. package/src/server/master/start.ts +19 -41
  314. package/src/server/master/testsManager.ts +316 -0
  315. package/src/server/playwright/docker-file.ts +20 -8
  316. package/src/server/playwright/docker.ts +16 -3
  317. package/src/server/playwright/index-source.mjs +16 -0
  318. package/src/server/playwright/internal.ts +176 -103
  319. package/src/server/playwright/webdriver.ts +4 -10
  320. package/src/server/providers/browser.ts +4 -3
  321. package/src/server/providers/hybrid.ts +2 -3
  322. package/src/server/report.ts +51 -0
  323. package/src/server/reporters/creevey.ts +71 -0
  324. package/src/server/reporters/index.ts +11 -0
  325. package/src/server/reporters/junit.ts +207 -0
  326. package/src/server/reporters/teamcity.ts +74 -0
  327. package/src/server/selenium/internal.ts +70 -53
  328. package/src/server/selenium/selenoid.ts +13 -6
  329. package/src/server/selenium/webdriver.ts +8 -12
  330. package/src/server/shutdown.ts +19 -0
  331. package/src/server/stories.ts +1 -12
  332. package/src/server/telemetry.ts +3 -3
  333. package/src/server/testsFiles/parser.ts +52 -4
  334. package/src/server/utils.ts +124 -16
  335. package/src/server/webdriver.ts +1 -1
  336. package/src/server/worker/context.ts +14 -0
  337. package/src/server/worker/match-image.ts +16 -248
  338. package/src/server/worker/start.ts +49 -79
  339. package/src/shared/index.ts +10 -8
  340. package/src/types.ts +91 -58
  341. package/types/global.d.ts +1 -0
  342. package/dist/client/web/assets/index-DB8lHlJw.js +0 -591
  343. package/dist/server/reporter.d.ts +0 -26
  344. package/dist/server/reporter.js +0 -108
  345. package/dist/server/reporter.js.map +0 -1
  346. package/dist/server/update.d.ts +0 -2
  347. package/dist/server/update.js +0 -53
  348. package/dist/server/update.js.map +0 -1
  349. package/src/server/reporter.ts +0 -139
  350. package/src/server/update.ts +0 -74
@@ -1,98 +1,175 @@
1
1
  import cluster from 'cluster';
2
+ import path from 'path';
3
+ import sh from 'shelljs';
4
+ import { getUserAgent } from 'package-manager-detector/detect';
5
+ import { resolveCommand } from 'package-manager-detector/commands';
2
6
  import { readConfig, defaultBrowser } from './config.js';
3
- import { Options, Config, BrowserConfigObject, isWorkerMessage } from '../types.js';
7
+ import {
8
+ Options,
9
+ Config,
10
+ BrowserConfigObject,
11
+ isWorkerMessage,
12
+ WorkerOptions,
13
+ OptionsSchema,
14
+ WorkerOptionsSchema,
15
+ } from '../types.js';
4
16
  import { logger } from './logger.js';
17
+ import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
5
18
  import { SeleniumWebdriver } from './selenium/webdriver.js';
6
19
  import { LOCALHOST_REGEXP } from './webdriver.js';
7
- import { isInsideDocker } from './utils.js';
8
- import { sendWorkerMessage } from './messages.js';
9
- import { playwrightDockerFile } from './playwright/docker-file.js';
20
+ import { isInsideDocker, killTree, resolvePlaywrightBrowserType, shutdownWithError } from './utils.js';
21
+ import { sendWorkerMessage, subscribeOn } from './messages.js';
10
22
  import { buildImage } from './docker.js';
23
+ import { mkdir, writeFile } from 'fs/promises';
24
+ import assert from 'assert';
25
+ import * as v from 'valibot';
26
+ import { PlaywrightWebdriver } from 'src/playwright.js';
27
+
28
+ async function getPlaywrightVersion(): Promise<string> {
29
+ const {
30
+ default: { version },
31
+ } = await import('playwright-core/package.json', { with: { type: 'json' } });
32
+ return version;
33
+ }
11
34
 
12
- async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
13
- if (config.webdriver === SeleniumWebdriver) {
14
- if (cluster.isPrimary) {
15
- const { startSelenoidContainer, startSelenoidStandalone } = await import('./selenium/selenoid.js');
16
- const gridUrl = 'http://localhost:4444/wd/hub';
17
- if (config.useDocker) {
18
- const host = await startSelenoidContainer(config, options.debug);
19
- return isInsideDocker ? gridUrl.replace(LOCALHOST_REGEXP, host) : gridUrl;
20
- }
21
- await startSelenoidStandalone(config, options.debug);
22
- return gridUrl;
23
- }
24
- // TODO Worker might want to use docker image of browser or start standalone selenium
25
- } else {
26
- if (config.gridUrl) return undefined;
35
+ async function startSelenoid(config: Config, debug = false): Promise<string | undefined> {
36
+ const { startSelenoidContainer, startSelenoidStandalone } = await import('./selenium/selenoid.js');
37
+ const gridUrl = 'http://localhost:4444/wd/hub';
38
+ if (config.useDocker) {
39
+ const host = await startSelenoidContainer(config, debug);
40
+ return isInsideDocker ? gridUrl.replace(LOCALHOST_REGEXP, host) : gridUrl;
41
+ }
42
+ await startSelenoidStandalone(config, debug);
43
+ return gridUrl;
44
+ }
27
45
 
28
- // TODO start standalone playwright server (useDocker == false)
29
- const {
30
- default: { version },
31
- } = await import('playwright-core/package.json', { with: { type: 'json' } });
46
+ async function startPlaywright(config: Config, browser: string, version: string, debug = false): Promise<string> {
47
+ // TODO Re-use dockerImage
48
+ const { startPlaywrightContainer } = await import('./playwright/docker.js');
49
+ const { browserName } = config.browsers[browser] as BrowserConfigObject;
32
50
 
33
- if (cluster.isWorker) {
34
- // TODO Re-use dockerImage
51
+ const imageName = `creevey/${browserName}:v${version}`;
52
+ const host = await startPlaywrightContainer(imageName, browser, config, debug);
35
53
 
36
- // TODO Use https://hub.docker.com/r/playwright/chrome
37
- // NOTE It will be possible to use `chrome` browserName
38
- const { startPlaywrightContainer } = await import('./playwright/docker.js');
39
- const { browserName } = config.browsers[browser] as BrowserConfigObject;
54
+ return host;
55
+ }
40
56
 
57
+ async function buildPlaywright(config: Config, version: string): Promise<void> {
58
+ const { playwrightDockerFile } = await import('./playwright/docker-file.js');
59
+ const {
60
+ default: { version: creeveyVersion },
61
+ } = await import('../../package.json', { with: { type: 'json' } });
62
+ const browsers = [...new Set(Object.values(config.browsers).map((c) => (c as BrowserConfigObject).browserName))];
63
+ await Promise.all(
64
+ browsers.map(async (browserName) => {
41
65
  const imageName = `creevey/${browserName}:v${version}`;
42
- const host = await startPlaywrightContainer(imageName, options.debug);
66
+ const dockerfile = await playwrightDockerFile(browserName, version);
43
67
 
44
- return host;
45
- } else {
46
- const browsers = [...new Set(Object.values(config.browsers).map((c) => (c as BrowserConfigObject).browserName))];
47
- await Promise.all(
48
- browsers.map(async (browserName) => {
49
- const imageName = `creevey/${browserName}:v${version}`;
50
- const dockerfile = playwrightDockerFile(browserName, version);
51
-
52
- await buildImage(imageName, dockerfile);
53
- }),
54
- );
55
-
56
- const { default: getPort } = await import('get-port');
57
-
58
- cluster.on('message', (worker, message: unknown) => {
59
- if (!isWorkerMessage(message)) return;
60
-
61
- const workerMessage = message;
62
- if (workerMessage.type != 'port') return;
63
-
64
- void getPort().then((port) => {
65
- sendWorkerMessage(worker, {
66
- type: 'port',
67
- payload: { port },
68
- });
69
- });
68
+ await buildImage(imageName, creeveyVersion, dockerfile);
69
+ }),
70
+ );
71
+
72
+ const { default: getPort } = await import('get-port');
73
+
74
+ cluster.on('message', (worker, message: unknown) => {
75
+ if (!isWorkerMessage(message)) return;
76
+
77
+ const workerMessage = message;
78
+ if (workerMessage.type != 'port') return;
79
+
80
+ void getPort().then((port) => {
81
+ sendWorkerMessage(worker, {
82
+ type: 'port',
83
+ payload: { port },
70
84
  });
85
+ });
86
+ });
87
+ }
88
+
89
+ async function startWebdriverServer(config: Config, options: Options): Promise<string | undefined> {
90
+ if (config.webdriver === SeleniumWebdriver) {
91
+ return startSelenoid(config, options.debug);
92
+ // TODO Worker might want to use docker image of browser or start standalone selenium
93
+ } else {
94
+ if (config.gridUrl) return undefined;
95
+
96
+ if (config.useDocker) {
97
+ const version = await getPlaywrightVersion();
98
+ await buildPlaywright(config, version);
71
99
  }
72
100
  // TODO Support gridUrl for playwright
73
101
  // NOTE: There is no grid for playwright right now
74
102
  }
75
103
  }
76
104
 
77
- export default async function (options: Options): Promise<void> {
78
- const config = await readConfig(options);
79
- const { browser = defaultBrowser, update, ui, port } = options;
80
- let gridUrl = cluster.isPrimary ? config.gridUrl : options.gridUrl;
81
-
82
- // NOTE: We don't need docker nor selenoid for update option
83
- if (
84
- !(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl)) &&
85
- !update
86
- ) {
87
- gridUrl = await startWebdriverServer(browser, config, options);
105
+ async function waitForStorybook(config: Config, options: Options): Promise<void> {
106
+ const [localUrl, remoteUrl] = getStorybookUrl(config, options);
107
+
108
+ if (options.storybookStart) {
109
+ const pm = getUserAgent();
110
+ assert(pm, new Error('Failed to detect current package manager'));
111
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
112
+ const { command, args } = resolveCommand(pm, 'run', ['storybook', 'dev'])!;
113
+ const storybookPort = new URL(localUrl).port;
114
+ const storybookCommand = `${config.storybookAutorunCmd ?? [command, ...args, '--ci'].join(' ')} -p ${storybookPort}`;
115
+
116
+ logger().info(`Start Storybook via \`${storybookCommand}\`, it should be accessible at:`);
117
+ logger().info(`Local - ${localUrl}`);
118
+ if (remoteUrl && localUrl != remoteUrl) logger().info(`On your network - ${remoteUrl}`);
119
+ logger().info('Waiting Storybook...');
120
+
121
+ const storybook = sh.exec(storybookCommand, { async: true });
122
+ subscribeOn('shutdown', () => {
123
+ if (storybook.pid) void killTree(storybook.pid);
124
+ });
125
+ } else {
126
+ logger().info('Storybook should be started and be accessible at:');
127
+ logger().info(`Local - ${localUrl}`);
128
+ if (remoteUrl && localUrl != remoteUrl) logger().info(`On your network - ${remoteUrl}`);
129
+ logger().info(
130
+ 'Tip: Creevey can start Storybook automatically by using `-s` option at the command line. (e.g., yarn/npm run creevey -s)',
131
+ );
132
+ logger().info('Waiting Storybook...');
133
+ }
134
+
135
+ if (options.storybookStart || process.env.CI !== 'true') {
136
+ const isConnected = await checkIsStorybookConnected(localUrl);
137
+ if (isConnected) {
138
+ logger().info('Storybook connected!\n');
139
+ } else {
140
+ logger().error('Storybook is not responding. Please start Storybook and restart Creevey');
141
+ shutdownWithError();
142
+ }
88
143
  }
144
+ }
89
145
 
90
- switch (true) {
91
- case Boolean(update): {
92
- (await import('./update.js')).update(config, typeof update == 'string' ? update : undefined);
146
+ // TODO Why docker containers are not deleting after stop?
147
+ export default async function (command: 'report' | 'test' | 'worker', options: Options | WorkerOptions): Promise<void> {
148
+ const config = await readConfig(options);
149
+
150
+ // TODO Add package.json with `"type": "commonjs"` as workaround for esm packages to load `data.js`
151
+ await mkdir(config.reportDir, { recursive: true });
152
+ await writeFile(path.join(config.reportDir, 'package.json'), '{"type": "commonjs"}');
153
+
154
+ await import('./shutdown.js');
155
+
156
+ if (v.is(OptionsSchema, options)) {
157
+ const { port, reportDir = config.reportDir } = options;
158
+
159
+ if (command == 'report') {
160
+ const { report } = await import('./report.js');
161
+ report(config, reportDir, port);
93
162
  return;
94
163
  }
95
- case cluster.isPrimary: {
164
+
165
+ if (cluster.isPrimary) {
166
+ let gridUrl: string | undefined = config.gridUrl;
167
+
168
+ if (!(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl))) {
169
+ gridUrl = await startWebdriverServer(config, options);
170
+ }
171
+ await waitForStorybook(config, options);
172
+
96
173
  if (config.webdriver === SeleniumWebdriver) {
97
174
  try {
98
175
  await import('selenium-webdriver');
@@ -110,16 +187,26 @@ export default async function (options: Options): Promise<void> {
110
187
  }
111
188
  logger().info('Starting Master Process');
112
189
 
113
- const resolveApi = (await import('./master/server.js')).start(config.reportDir, port, ui);
114
-
115
- return (await import('./master/start.js')).start(gridUrl, config, options, resolveApi);
190
+ return (await import('./master/start.js')).start(gridUrl, config, options);
116
191
  }
117
- default: {
118
- logger().info(`Starting Worker for ${browser}`);
192
+ }
119
193
 
120
- // NOTE: We assume that we pass `gridUrl` to worker CLI options
121
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
122
- return (await import('./worker/start.js')).start(browser, gridUrl!, config, options);
194
+ if (v.is(WorkerOptionsSchema, options) && cluster.isWorker) {
195
+ if (config.webdriver !== PlaywrightWebdriver) return;
196
+
197
+ let gridUrl = options.gridUrl;
198
+ const { browser = defaultBrowser, debug } = options;
199
+
200
+ if (config.useDocker) {
201
+ const version = await getPlaywrightVersion();
202
+ gridUrl = await startPlaywright(config, browser, version, debug);
203
+ } else {
204
+ const { browserName } = config.browsers[browser] as BrowserConfigObject;
205
+ gridUrl = `creevey://${resolvePlaywrightBrowserType(browserName)}`;
123
206
  }
207
+
208
+ logger().info(`Starting Worker for ${browser}`);
209
+
210
+ return (await import('./worker/start.js')).start(browser, gridUrl, config, options);
124
211
  }
125
212
  }
@@ -1,14 +1,10 @@
1
- import WebSocket from 'ws';
2
- import Runner from './runner.js';
3
- import { Request, Response, CreeveyUpdate } from '../../types.js';
1
+ import { Data, WebSocket, WebSocketServer } from 'ws';
2
+ import type { Request, Response, CreeveyUpdate } from '../../types.js';
3
+ import type { TestsManager } from './testsManager.js';
4
+ import type Runner from './runner.js';
4
5
  import { logger } from '../logger.js';
5
6
 
6
- export interface CreeveyApi {
7
- subscribe: (wss: WebSocket.Server) => void;
8
- handleMessage: (ws: WebSocket, message: WebSocket.Data) => void;
9
- }
10
-
11
- function broadcast(wss: WebSocket.Server, message: Response): void {
7
+ function broadcast(wss: WebSocketServer, message: Response): void {
12
8
  wss.clients.forEach((ws) => {
13
9
  if (ws.readyState === WebSocket.OPEN) {
14
10
  ws.send(JSON.stringify(message));
@@ -16,44 +12,114 @@ function broadcast(wss: WebSocket.Server, message: Response): void {
16
12
  });
17
13
  }
18
14
 
19
- export default function creeveyApi(runner: Runner): CreeveyApi {
20
- return {
21
- subscribe(wss: WebSocket.Server) {
22
- runner.on('update', (payload: CreeveyUpdate) => {
23
- broadcast(wss, { type: 'update', payload });
15
+ function send(ws: WebSocket, message: Response): void {
16
+ if (ws.readyState === WebSocket.OPEN) {
17
+ ws.send(JSON.stringify(message));
18
+ }
19
+ }
20
+
21
+ // The class-based implementation of CreeveyApi for native WebSockets
22
+ export class CreeveyApi {
23
+ private runner: Runner | null = null;
24
+ private testsManager: TestsManager;
25
+ private wss: WebSocketServer | null = null;
26
+
27
+ constructor(testsManager: TestsManager, runner?: Runner) {
28
+ this.testsManager = testsManager;
29
+
30
+ // Use the provided runner in normal mode, or keep as null in update mode
31
+ if (runner) {
32
+ this.runner = runner;
33
+ }
34
+ }
35
+
36
+ subscribe(wss: WebSocketServer): void {
37
+ this.wss = wss;
38
+
39
+ // If we have a runner, subscribe to its updates
40
+ if (this.runner) {
41
+ this.runner.on('update', (payload: CreeveyUpdate) => {
42
+ this.broadcastUpdate(payload);
43
+ });
44
+ } else {
45
+ // Subscribe to TestsManager updates
46
+ this.testsManager.on('update', (update: CreeveyUpdate) => {
47
+ this.broadcastUpdate(update);
24
48
  });
25
- },
49
+ }
50
+ }
26
51
 
27
- handleMessage(ws: WebSocket, message: WebSocket.Data) {
28
- if (typeof message != 'string') {
29
- logger().info('unhandled message', message);
30
- return;
31
- }
52
+ handleMessage(ws: WebSocket, message: Data): void {
53
+ if (typeof message != 'string') {
54
+ logger().info('unhandled message', message);
55
+ return;
56
+ }
32
57
 
33
- const command = JSON.parse(message) as Request;
58
+ const command = JSON.parse(message) as Request;
34
59
 
60
+ if (this.runner) {
61
+ // Normal mode handling with runner
35
62
  switch (command.type) {
36
63
  case 'status': {
37
- ws.send(JSON.stringify({ type: 'status', payload: runner.status }));
64
+ const status = this.runner.status;
65
+ send(ws, { type: 'status', payload: status });
38
66
  return;
39
67
  }
40
68
  case 'start': {
41
- runner.start(command.payload);
69
+ this.runner.start(command.payload);
42
70
  return;
43
71
  }
44
72
  case 'stop': {
45
- runner.stop();
73
+ this.runner.stop();
74
+ return;
75
+ }
76
+ case 'approve': {
77
+ void this.runner.approve(command.payload);
78
+ return;
79
+ }
80
+ case 'approveAll': {
81
+ void this.runner.approveAll();
46
82
  return;
47
83
  }
84
+ }
85
+ } else {
86
+ // In update mode, only approve and approveAll commands are allowed
87
+ switch (command.type) {
48
88
  case 'approve': {
49
- void runner.approve(command.payload);
89
+ void this.testsManager.approve(command.payload);
50
90
  return;
51
91
  }
52
92
  case 'approveAll': {
53
- void runner.approveAll();
93
+ void this.testsManager.approveAll();
94
+ return;
95
+ }
96
+ case 'status': {
97
+ // In update mode, respond with static status including tests data
98
+ send(ws, {
99
+ type: 'status',
100
+ payload: {
101
+ isRunning: false,
102
+ tests: this.testsManager.getTestsData(),
103
+ browsers: [],
104
+ isUpdateMode: true,
105
+ },
106
+ });
107
+ return;
108
+ }
109
+ default: {
110
+ // Ignore other commands in update mode
111
+ logger().debug(`Command ${command.type} is not available in update mode`);
54
112
  return;
55
113
  }
56
114
  }
57
- },
58
- };
115
+ }
116
+ }
117
+
118
+ private broadcastUpdate(payload: CreeveyUpdate): void {
119
+ if (!this.wss) return;
120
+
121
+ const message: Response = { type: 'update', payload };
122
+
123
+ broadcast(this.wss, message);
124
+ }
59
125
  }
@@ -0,0 +1,20 @@
1
+ import cluster from 'cluster';
2
+ import { subscribeOnWorker, sendStoriesMessage } from '../../messages.js';
3
+ import { CaptureOptions, isDefined } from '../../../types.js';
4
+
5
+ export function captureHandler({ workerId, options }: { workerId: number; options?: CaptureOptions }): void {
6
+ const worker = Object.values(cluster.workers ?? {})
7
+ .filter(isDefined)
8
+ .find((worker) => worker.process.pid == workerId);
9
+
10
+ // NOTE: Hypothetical case when someone send to us capture req and we don't have a worker with browser session for it
11
+ if (!worker) {
12
+ return;
13
+ }
14
+
15
+ const unsubscribe = subscribeOnWorker(worker, 'stories', (message) => {
16
+ if (message.type != 'capture') return;
17
+ unsubscribe();
18
+ });
19
+ sendStoriesMessage(worker, { type: 'capture', payload: options });
20
+ }
@@ -0,0 +1,4 @@
1
+ export * from './ping-handler.js';
2
+ export * from './stories-handler.js';
3
+ export * from './capture-handler.js';
4
+ export * from './static-handler.js';
@@ -0,0 +1,6 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+
3
+ export function pingHandler(_request: IncomingMessage, response: ServerResponse): void {
4
+ response.setHeader('Content-Type', 'text/plain');
5
+ response.end('pong');
6
+ }
@@ -0,0 +1,16 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+
4
+ export function staticHandler(baseDir: string, pathPrefix?: string) {
5
+ return (requestedPath: string): string | undefined => {
6
+ const relativePath = pathPrefix ? requestedPath.replace(pathPrefix, '') : requestedPath;
7
+ let filePath = path.join(baseDir, relativePath || 'index.html');
8
+
9
+ // If the path points to a directory, append index.html
10
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
11
+ filePath = path.join(filePath, 'index.html');
12
+ }
13
+
14
+ return fs.existsSync(filePath) ? filePath : undefined;
15
+ };
16
+ }
@@ -0,0 +1,20 @@
1
+ import cluster from 'cluster';
2
+ import { emitStoriesMessage, sendStoriesMessage } from '../../messages.js';
3
+ import { isDefined, StoryInput } from '../../../types.js';
4
+ import { deserializeStory } from '../../../shared/index.js';
5
+
6
+ export function storiesHandler({ stories }: { stories: [string, StoryInput[]][] }): void {
7
+ const deserializedStories = stories.map<[string, StoryInput[]]>(([file, stories]) => [
8
+ file,
9
+ stories.map(deserializeStory),
10
+ ]);
11
+
12
+ emitStoriesMessage({ type: 'update', payload: deserializedStories });
13
+
14
+ Object.values(cluster.workers ?? {})
15
+ .filter(isDefined)
16
+ .filter((worker) => worker.isConnected())
17
+ .forEach((worker) => {
18
+ sendStoriesMessage(worker, { type: 'update', payload: deserializedStories });
19
+ });
20
+ }
@@ -1,44 +1,27 @@
1
- import path from 'path';
2
- import { Config, TestData, isDefined, ServerTest } from '../../types.js';
3
- import { loadTestsFromStories, saveTestsJson } from '../stories.js';
1
+ import { Config } from '../../types.js';
2
+ import { loadTestsFromStories } from '../stories.js';
3
+ import { TestsManager } from './testsManager.js';
4
4
  import Runner from './runner.js';
5
- import { tryToLoadTestsData } from '../utils.js';
6
-
7
- function mergeTests(
8
- testsWithReports: Partial<Record<string, TestData>>,
9
- testsFromStories: Partial<Record<string, ServerTest>>,
10
- ): Partial<Record<string, ServerTest>> {
11
- Object.values(testsFromStories)
12
- .filter(isDefined)
13
- .forEach((test) => {
14
- const testWithReport = testsWithReports[test.id];
15
- if (!testWithReport) return;
16
- test.retries = testWithReport.retries;
17
- if (testWithReport.status == 'success' || testWithReport.status == 'failed') test.status = testWithReport.status;
18
- test.results = testWithReport.results;
19
- test.approved = testWithReport.approved;
20
- });
21
- return testsFromStories;
22
- }
23
5
 
24
6
  export default async function master(config: Config, gridUrl?: string): Promise<Runner> {
25
- const runner = new Runner(config, gridUrl);
26
- const reportDataPath = path.join(config.reportDir, 'data.js');
27
- const testsFromReport = tryToLoadTestsData(reportDataPath) ?? {};
7
+ // Create TestsManager instance
8
+ const testsManager = new TestsManager(config.screenDir, config.reportDir);
9
+
10
+ // Create Runner with TestsManager
11
+ const runner = new Runner(config, testsManager, gridUrl);
28
12
 
29
13
  await runner.init();
30
14
 
15
+ // Load tests from stories and update TestsManager
31
16
  const tests = await loadTestsFromStories(
32
17
  Object.keys(config.browsers),
33
18
  (listener) => config.storiesProvider(config, listener),
34
19
  (testsDiff) => {
35
20
  runner.updateTests(testsDiff);
36
- saveTestsJson(runner.tests, config.reportDir);
37
21
  },
38
22
  );
39
23
 
40
- runner.tests = mergeTests(testsFromReport, tests);
41
- saveTestsJson(runner.tests, config.reportDir);
24
+ testsManager.updateTests(testsManager.loadAndMergeTests(tests));
42
25
 
43
26
  return runner;
44
27
  }
@@ -19,6 +19,7 @@ export default class Pool extends EventEmitter {
19
19
  private forcedStop = false;
20
20
  private failFast: boolean;
21
21
  private gridUrl?: string;
22
+ private storybookUrl: string;
22
23
  public get isRunning(): boolean {
23
24
  return this.workers.length !== this.freeWorkers.length;
24
25
  }
@@ -34,13 +35,16 @@ export default class Pool extends EventEmitter {
34
35
  this.maxRetries = config.maxRetries;
35
36
  this.config = config.browsers[browser] as BrowserConfigObject;
36
37
  this.gridUrl = this.config.gridUrl ?? gridUrl;
38
+ this.storybookUrl = this.config.storybookUrl ?? config.storybookUrl;
37
39
  }
38
40
 
39
41
  async init(): Promise<void> {
40
42
  const poolSize = Math.max(1, this.config.limit ?? 1);
41
43
  this.workers = (
42
44
  await Promise.all(
43
- Array.from({ length: poolSize }).map(() => this.scheduler.forkWorker(this.browser, this.gridUrl)),
45
+ Array.from({ length: poolSize }).map(() =>
46
+ this.scheduler.forkWorker(this.browser, this.storybookUrl, this.gridUrl),
47
+ ),
44
48
  )
45
49
  ).filter((workerOrError): workerOrError is Worker => workerOrError instanceof ClusterWorker);
46
50
  if (this.workers.length != poolSize)
@@ -120,7 +124,7 @@ export default class Pool extends EventEmitter {
120
124
  worker.once('exit', async () => {
121
125
  if (isShuttingDown.current) return;
122
126
 
123
- const workerOrError = await this.scheduler.forkWorker(this.browser, this.gridUrl);
127
+ const workerOrError = await this.scheduler.forkWorker(this.browser, this.storybookUrl, this.gridUrl);
124
128
 
125
129
  if (!(workerOrError instanceof ClusterWorker))
126
130
  throw new Error(`Can't instantiate worker for ${this.browser} due many errors`);
@@ -165,7 +169,7 @@ export default class Pool extends EventEmitter {
165
169
  gracefullyKill(worker);
166
170
  }
167
171
 
168
- this.handleTestResult(worker, test, { status: 'failed', error: message.payload.error });
172
+ this.handleTestResult(worker, test, { status: 'failed', error: message.payload.error, retries: test.retries });
169
173
  }),
170
174
  subscribeOnWorker(worker, 'test', (message) => {
171
175
  if (message.type != 'end') return;