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,103 +1,175 @@
1
1
  import cluster from 'cluster';
2
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';
3
6
  import { readConfig, defaultBrowser } from './config.js';
4
- 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';
5
16
  import { logger } from './logger.js';
17
+ import { getStorybookUrl, checkIsStorybookConnected } from './connection.js';
6
18
  import { SeleniumWebdriver } from './selenium/webdriver.js';
7
19
  import { LOCALHOST_REGEXP } from './webdriver.js';
8
- import { isInsideDocker } from './utils.js';
9
- import { sendWorkerMessage } from './messages.js';
10
- import { playwrightDockerFile } from './playwright/docker-file.js';
20
+ import { isInsideDocker, killTree, resolvePlaywrightBrowserType, shutdownWithError } from './utils.js';
21
+ import { sendWorkerMessage, subscribeOn } from './messages.js';
11
22
  import { buildImage } from './docker.js';
12
- import { writeFile } from 'fs/promises';
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
+ }
13
34
 
14
- async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
15
- if (config.webdriver === SeleniumWebdriver) {
16
- if (cluster.isPrimary) {
17
- const { startSelenoidContainer, startSelenoidStandalone } = await import('./selenium/selenoid.js');
18
- const gridUrl = 'http://localhost:4444/wd/hub';
19
- if (config.useDocker) {
20
- const host = await startSelenoidContainer(config, options.debug);
21
- return isInsideDocker ? gridUrl.replace(LOCALHOST_REGEXP, host) : gridUrl;
22
- }
23
- await startSelenoidStandalone(config, options.debug);
24
- return gridUrl;
25
- }
26
- // TODO Worker might want to use docker image of browser or start standalone selenium
27
- } else {
28
- 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
+ }
29
45
 
30
- // TODO start standalone playwright server (useDocker == false)
31
- const {
32
- default: { version },
33
- } = 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;
34
50
 
35
- if (cluster.isWorker) {
36
- // TODO Re-use dockerImage
51
+ const imageName = `creevey/${browserName}:v${version}`;
52
+ const host = await startPlaywrightContainer(imageName, browser, config, debug);
37
53
 
38
- // TODO Use https://hub.docker.com/r/playwright/chrome
39
- // NOTE It will be possible to use `chrome` browserName
40
- const { startPlaywrightContainer } = await import('./playwright/docker.js');
41
- const { browserName } = config.browsers[browser] as BrowserConfigObject;
54
+ return host;
55
+ }
42
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) => {
43
65
  const imageName = `creevey/${browserName}:v${version}`;
44
- const host = await startPlaywrightContainer(imageName, options.debug);
66
+ const dockerfile = await playwrightDockerFile(browserName, version);
45
67
 
46
- return host;
47
- } else {
48
- const browsers = [...new Set(Object.values(config.browsers).map((c) => (c as BrowserConfigObject).browserName))];
49
- await Promise.all(
50
- browsers.map(async (browserName) => {
51
- const imageName = `creevey/${browserName}:v${version}`;
52
- const dockerfile = playwrightDockerFile(browserName, version);
53
-
54
- await buildImage(imageName, dockerfile);
55
- }),
56
- );
57
-
58
- const { default: getPort } = await import('get-port');
59
-
60
- cluster.on('message', (worker, message: unknown) => {
61
- if (!isWorkerMessage(message)) return;
62
-
63
- const workerMessage = message;
64
- if (workerMessage.type != 'port') return;
65
-
66
- void getPort().then((port) => {
67
- sendWorkerMessage(worker, {
68
- type: 'port',
69
- payload: { port },
70
- });
71
- });
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 },
72
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);
73
99
  }
74
100
  // TODO Support gridUrl for playwright
75
101
  // NOTE: There is no grid for playwright right now
76
102
  }
77
103
  }
78
104
 
79
- export default async function (options: Options): Promise<void> {
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
+ }
143
+ }
144
+ }
145
+
146
+ // TODO Why docker containers are not deleting after stop?
147
+ export default async function (command: 'report' | 'test' | 'worker', options: Options | WorkerOptions): Promise<void> {
80
148
  const config = await readConfig(options);
81
- const { browser = defaultBrowser, update, ui, port } = options;
82
- let gridUrl = cluster.isPrimary ? config.gridUrl : options.gridUrl;
83
149
 
84
150
  // TODO Add package.json with `"type": "commonjs"` as workaround for esm packages to load `data.js`
151
+ await mkdir(config.reportDir, { recursive: true });
85
152
  await writeFile(path.join(config.reportDir, 'package.json'), '{"type": "commonjs"}');
86
153
 
87
- // NOTE: We don't need docker nor selenoid for update option
88
- if (
89
- !(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl)) &&
90
- !update
91
- ) {
92
- gridUrl = await startWebdriverServer(browser, config, options);
93
- }
154
+ await import('./shutdown.js');
94
155
 
95
- switch (true) {
96
- case Boolean(update): {
97
- (await import('./update.js')).update(config, typeof update == 'string' ? update : undefined);
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);
98
162
  return;
99
163
  }
100
- 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
+
101
173
  if (config.webdriver === SeleniumWebdriver) {
102
174
  try {
103
175
  await import('selenium-webdriver');
@@ -115,16 +187,30 @@ export default async function (options: Options): Promise<void> {
115
187
  }
116
188
  logger().info('Starting Master Process');
117
189
 
118
- const resolveApi = (await import('./master/server.js')).start(config.reportDir, port, ui);
119
-
120
- return (await import('./master/start.js')).start(gridUrl, config, options, resolveApi);
190
+ return (await import('./master/start.js')).start(gridUrl, config, options);
121
191
  }
122
- default: {
123
- logger().info(`Starting Worker for ${browser}`);
192
+ }
124
193
 
125
- // NOTE: We assume that we pass `gridUrl` to worker CLI options
126
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
127
- return (await import('./worker/start.js')).start(browser, gridUrl!, config, options);
194
+ if (v.is(WorkerOptionsSchema, options) && cluster.isWorker) {
195
+ let gridUrl = options.gridUrl;
196
+ const { browser = defaultBrowser, debug } = options;
197
+
198
+ if (!gridUrl) {
199
+ if (config.webdriver === PlaywrightWebdriver) {
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)}`;
206
+ }
207
+ } else {
208
+ assert(gridUrl, 'Grid URL is required for Selenium');
209
+ }
128
210
  }
211
+
212
+ logger().info(`Starting Worker for ${browser}`);
213
+
214
+ return (await import('./worker/start.js')).start(browser, gridUrl, config, options);
129
215
  }
130
216
  }
@@ -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;