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
@@ -2,11 +2,12 @@ import path from 'path';
2
2
  import assert from 'assert';
3
3
  import { lstatSync, existsSync } from 'fs';
4
4
  import { mkdir, writeFile, copyFile } from 'fs/promises';
5
- import sh from 'shelljs';
5
+ import { exec, chmod } from 'shelljs';
6
6
  import { Config, BrowserConfigObject } from '../../types.js';
7
- import { downloadBinary, getCreeveyCache } from '../utils.js';
7
+ import { downloadBinary, getCreeveyCache, killTree } from '../utils.js';
8
8
  import { pullImages, runImage } from '../docker.js';
9
9
  import { subscribeOn } from '../messages.js';
10
+ import { removeWorkerContainer } from '../worker/context.js';
10
11
 
11
12
  async function createSelenoidConfig(
12
13
  browsers: BrowserConfigObject[],
@@ -34,7 +35,7 @@ async function createSelenoidConfig(
34
35
  dockerImage = `selenoid/${browserName}:${browserVersion}`,
35
36
  webdriverCommand = [],
36
37
  }) => {
37
- if (!selenoidConfig[browserName]) selenoidConfig[browserName] = { default: browserVersion, versions: {} };
38
+ selenoidConfig[browserName] ??= { default: browserVersion, versions: {} };
38
39
  if (!useDocker && webdriverCommand.length == 0)
39
40
  throw new Error('Please specify "webdriverCommand" browser option with path to browser webdriver');
40
41
  selenoidConfig[browserName].versions[browserVersion] = {
@@ -91,12 +92,12 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
91
92
 
92
93
  // TODO Download browser webdrivers
93
94
  try {
94
- if (process.platform != 'win32') sh.chmod('+x', binaryPath);
95
+ if (process.platform != 'win32') chmod('+x', binaryPath);
95
96
  } catch {
96
97
  /* noop */
97
98
  }
98
99
 
99
- const selenoidProcess = sh.exec(`${binaryPath} -conf ./browsers.json -disable-docker`, {
100
+ const selenoidProcess = exec(`${binaryPath} -conf ./browsers.json -disable-docker`, {
100
101
  async: true,
101
102
  cwd: selenoidConfigDir,
102
103
  });
@@ -106,7 +107,9 @@ export async function startSelenoidStandalone(config: Config, debug: boolean): P
106
107
  selenoidProcess.stderr?.pipe(process.stderr);
107
108
  }
108
109
 
109
- subscribeOn('shutdown', () => selenoidProcess.kill());
110
+ subscribeOn('shutdown', () => {
111
+ if (selenoidProcess.pid) void killTree(selenoidProcess.pid);
112
+ });
110
113
  }
111
114
 
112
115
  export async function startSelenoidContainer(config: Config, debug: boolean): Promise<string> {
@@ -145,5 +148,9 @@ export async function startSelenoidContainer(config: Config, debug: boolean): Pr
145
148
  },
146
149
  };
147
150
 
151
+ subscribeOn('shutdown', () => {
152
+ void removeWorkerContainer();
153
+ });
154
+
148
155
  return runImage(selenoidImage, ['-limit', String(limit)], selenoidOptions, debug);
149
156
  }
@@ -1,10 +1,11 @@
1
1
  /// <reference types="../../../types/selenium-context" />
2
- import { Args } from '@storybook/csf';
3
- import { Config, StorybookGlobals, StoryInput, StoriesRaw, Options, ServerTest } from '../../types.js';
2
+ import type { Args } from 'storybook/internal/types';
3
+ import { Config, StorybookGlobals, StoryInput, StoriesRaw, ServerTest, WorkerOptions } from '../../types.js';
4
4
  import { subscribeOn } from '../messages.js';
5
5
  import { CreeveyWebdriverBase } from '../webdriver.js';
6
6
  import type { InternalBrowser } from './internal.js';
7
7
  import { logger } from '../logger.js';
8
+ import { removeWorkerContainer } from '../worker/context.js';
8
9
 
9
10
  declare global {
10
11
  interface Window {
@@ -21,8 +22,8 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
21
22
  #browserName: string;
22
23
  #gridUrl: string;
23
24
  #config: Config;
24
- #options: Options;
25
- constructor(browser: string, gridUrl: string, config: Config, options: Options) {
25
+ #options: WorkerOptions;
26
+ constructor(browser: string, gridUrl: string, config: Config, options: WorkerOptions) {
26
27
  super();
27
28
 
28
29
  this.#browserName = browser;
@@ -31,7 +32,9 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
31
32
  this.#options = options;
32
33
 
33
34
  subscribeOn('shutdown', () => {
34
- void this.#browser?.closeBrowser().finally(() => process.exit());
35
+ void this.#browser?.closeBrowser().finally(() => {
36
+ void removeWorkerContainer().finally(() => process.exit());
37
+ });
35
38
  this.#browser = null;
36
39
  });
37
40
  }
@@ -42,7 +45,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
42
45
 
43
46
  getSessionId(): Promise<string> {
44
47
  if (!this.#browser) {
45
- // TODO Describe the error
46
48
  throw new Error('Browser is not initialized');
47
49
  }
48
50
 
@@ -89,7 +91,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
89
91
 
90
92
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
91
93
  if (!this.#browser) {
92
- // TODO Describe the error
93
94
  throw new Error('Browser is not initialized');
94
95
  }
95
96
 
@@ -98,7 +99,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
98
99
 
99
100
  afterTest(test: ServerTest): Promise<void> {
100
101
  if (!this.#browser) {
101
- // TODO Describe the error
102
102
  throw new Error('Browser is not initialized');
103
103
  }
104
104
 
@@ -110,7 +110,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
110
110
  ignoreElements?: string | string[] | null,
111
111
  ): Promise<Buffer> {
112
112
  if (!this.#browser) {
113
- // TODO Describe the error
114
113
  throw new Error('Browser is not initialized');
115
114
  }
116
115
 
@@ -119,7 +118,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
119
118
 
120
119
  protected waitForComplete(callback: (isCompleted: boolean) => void): void {
121
120
  if (!this.#browser) {
122
- // TODO Describe the error
123
121
  throw new Error('Browser is not initialized');
124
122
  }
125
123
 
@@ -128,7 +126,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
128
126
 
129
127
  protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
130
128
  if (!this.#browser) {
131
- // TODO Describe the error
132
129
  throw new Error('Browser is not initialized');
133
130
  }
134
131
 
@@ -137,7 +134,6 @@ export class SeleniumWebdriver extends CreeveyWebdriverBase {
137
134
 
138
135
  protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
139
136
  if (!this.#browser) {
140
- // TODO Describe the error
141
137
  throw new Error('Browser is not initialized');
142
138
  }
143
139
 
@@ -0,0 +1,19 @@
1
+ import cluster from 'cluster';
2
+ import { subscribeOn } from './messages.js';
3
+ import { shutdownOnException, isShuttingDown } from './utils.js';
4
+
5
+ if (cluster.isWorker) {
6
+ subscribeOn('shutdown', () => {
7
+ isShuttingDown.current = true;
8
+ });
9
+ }
10
+
11
+ process.on('uncaughtException', shutdownOnException);
12
+ process.on('unhandledRejection', shutdownOnException);
13
+ // TODO SIGINT Stuck with selenium
14
+ process.on('SIGINT', () => {
15
+ if (isShuttingDown.current) {
16
+ process.exit(-1);
17
+ }
18
+ isShuttingDown.current = true;
19
+ });
@@ -1,5 +1,3 @@
1
- import path from 'path';
2
- import { mkdirSync, writeFileSync } from 'fs';
3
1
  import { createHash } from 'crypto';
4
2
  import _ from 'lodash';
5
3
  import type {
@@ -12,7 +10,7 @@ import type {
12
10
  CreeveyTestFunction,
13
11
  CreeveyTestContext,
14
12
  } from '../types.js';
15
- import { isDefined, isFunction } from '../types.js';
13
+ import { isDefined } from '../types.js';
16
14
  import { shouldSkip } from './utils.js';
17
15
 
18
16
  function storyTestFabric(delay?: number, testFn?: CreeveyTestFunction) {
@@ -129,12 +127,3 @@ export async function loadTestsFromStories(
129
127
 
130
128
  return tests;
131
129
  }
132
-
133
- export function saveTestsJson(tests: Record<string, unknown>, dstPath: string = process.cwd()): void {
134
- mkdirSync(dstPath, { recursive: true });
135
- writeFileSync(
136
- path.join(dstPath, 'tests.json'),
137
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
138
- JSON.stringify(tests, (_, value) => (isFunction(value) ? value.toString() : value), 2),
139
- );
140
- }
@@ -128,7 +128,7 @@ export async function sendScreenshotsCount(
128
128
  repoUrl: repoUrl ?? 'unknown',
129
129
  creeveyVersion: creeveyVersion ?? 'unknown',
130
130
  storybookVersion: storybookVersion ?? 'unknown',
131
- options: options._,
131
+ options,
132
132
  gridUrl,
133
133
  screenDir: config.screenDir ? path.relative(gitRootPath ?? process.cwd(), config.screenDir) : undefined,
134
134
  useDocker: config.useDocker,
@@ -181,8 +181,8 @@ export async function sendScreenshotsCount(
181
181
  const testsMeta = { runId: uuid, tests };
182
182
 
183
183
  const fullPathname = buildPathname('tests', testsMeta);
184
- // NOTE: Keep request path shorter than 32k symbols
185
- const chunksCount = Math.ceil(fullPathname.length / 32_000);
184
+ // NOTE: Keep request path shorter than 24k symbols
185
+ const chunksCount = Math.ceil(fullPathname.length / 24_000);
186
186
  let chunks: string[] = [];
187
187
  if (chunksCount > 1) {
188
188
  const testsString = JSON.stringify(tests);
@@ -1,8 +1,58 @@
1
1
  import { pathToFileURL } from 'url';
2
- import { toId, storyNameFromExport } from '@storybook/csf';
3
2
  import { CreeveyStoryParams, CreeveyTestFunction } from '../../types.js';
4
3
  import { loadThroughTSX } from '../utils.js';
5
4
 
5
+ // NOTE: Copy-pasted from @storybook/csf
6
+ function toStartCaseStr(str: string) {
7
+ return str
8
+ .replace(/_/g, ' ')
9
+ .replace(/-/g, ' ')
10
+ .replace(/\./g, ' ')
11
+ .replace(/([^\n])([A-Z])([a-z])/g, (_, $1, $2, $3) => `${$1} ${$2}${$3}`)
12
+ .replace(/([a-z])([A-Z])/g, (_, $1, $2) => `${$1} ${$2}`)
13
+ .replace(/([a-z])([0-9])/gi, (_, $1, $2) => `${$1} ${$2}`)
14
+ .replace(/([0-9])([a-z])/gi, (_, $1, $2) => `${$1} ${$2}`)
15
+ .replace(/(\s|^)(\w)/g, (_, $1, $2: string) => `${$1}${$2.toUpperCase()}`)
16
+ .replace(/ +/g, ' ')
17
+ .trim();
18
+ }
19
+
20
+ /**
21
+ * Remove punctuation and illegal characters from a story ID.
22
+ *
23
+ * See https://gist.github.com/davidjrice/9d2af51100e41c6c4b4a
24
+ */
25
+ const sanitize = (string: string) => {
26
+ return (
27
+ string
28
+ .toLowerCase()
29
+ // eslint-disable-next-line no-useless-escape
30
+ .replace(/[ ’–—―′¿'`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, '-')
31
+ .replace(/-+/g, '-')
32
+ .replace(/^-+/, '')
33
+ .replace(/-+$/, '')
34
+ );
35
+ };
36
+
37
+ const sanitizeSafe = (string: string, part: string) => {
38
+ const sanitized = sanitize(string);
39
+ if (sanitized === '') {
40
+ throw new Error(`Invalid ${part} '${string}', must include alphanumeric characters`);
41
+ }
42
+ return sanitized;
43
+ };
44
+
45
+ /**
46
+ * Generate a storybook ID from a component/kind and story name.
47
+ */
48
+ const toId = (kind: string, name?: string) =>
49
+ `${sanitizeSafe(kind, 'kind')}${name ? `--${sanitizeSafe(name, 'name')}` : ''}`;
50
+
51
+ /**
52
+ * Transform a CSF named export into a readable story name
53
+ */
54
+ const storyNameFromExport = (key: string) => toStartCaseStr(key);
55
+
6
56
  export type CreeveyParamsByStoryId = Record<string, CreeveyStoryParams>;
7
57
 
8
58
  export default async function parse(files: string[]): Promise<CreeveyParamsByStoryId> {
@@ -55,8 +105,6 @@ export const story = (
55
105
 
56
106
  export const test = (title: string, testFn: CreeveyTestFunction): void => {
57
107
  const storyId = getStoryId(kindTitle, storyTitle);
58
- if (!result[storyId]) {
59
- result[storyId] = {};
60
- }
108
+ result[storyId] ??= {};
61
109
  result[storyId].tests = Object.assign({}, result[storyId].tests, { [title]: testFn });
62
110
  };
@@ -1,13 +1,17 @@
1
1
  import fs from 'fs';
2
- import { get } from 'https';
2
+ import path from 'path';
3
+ import http from 'http';
4
+ import https from 'https';
5
+ import assert from 'assert';
3
6
  import cluster from 'cluster';
4
- import { dirname } from 'path';
7
+ import pidtree from 'pidtree';
5
8
  import { fileURLToPath, pathToFileURL } from 'url';
6
- import { createRequire } from 'module';
7
9
  import { register as esmRegister } from 'tsx/esm/api';
8
10
  import { register as cjsRegister } from 'tsx/cjs/api';
9
11
  import { SkipOptions, SkipOption, isDefined, TestData, noop, ServerTest, Worker } from '../types.js';
10
- import { emitShutdownMessage, sendShutdownMessage } from './messages.js';
12
+ import { emitShutdownMessage, emitWorkerMessage, sendShutdownMessage } from './messages.js';
13
+ import { LOCALHOST_REGEXP } from './webdriver.js';
14
+ import { logger } from './logger.js';
11
15
 
12
16
  const importMetaUrl = pathToFileURL(__filename).href;
13
17
 
@@ -15,6 +19,19 @@ export const isShuttingDown = { current: false };
15
19
 
16
20
  export const configExt = ['.js', '.mjs', '.ts', '.cjs', '.mts', '.cts'];
17
21
 
22
+ const browserTypes = {
23
+ chromium: 'chromium',
24
+ 'chromium-headless-shell': 'chromium',
25
+ chrome: 'chromium',
26
+ 'chrome-beta': 'chromium',
27
+ msedge: 'chromium',
28
+ 'msedge-beta': 'chromium',
29
+ 'msedge-dev': 'chromium',
30
+ 'bidi-chromium': 'chromium',
31
+ firefox: 'firefox',
32
+ webkit: 'webkit',
33
+ } as const;
34
+
18
35
  export const skipOptionKeys = ['in', 'kinds', 'stories', 'tests', 'reason'];
19
36
 
20
37
  function matchBy(pattern: string | string[] | RegExp | undefined, value: string): boolean {
@@ -73,6 +90,18 @@ export function shouldSkipByOption(
73
90
  return skipByBrowser && skipByKind && skipByStory && skipByTest && reason;
74
91
  }
75
92
 
93
+ export function shutdownOnException(reason: unknown): void {
94
+ if (isShuttingDown.current) return;
95
+
96
+ const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
97
+
98
+ logger().error(error);
99
+
100
+ process.exitCode = -1;
101
+ if (cluster.isWorker) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error } });
102
+ if (cluster.isPrimary) void shutdownWorkers();
103
+ }
104
+
76
105
  export async function shutdownWorkers(): Promise<void> {
77
106
  isShuttingDown.current = true;
78
107
  await Promise.all(
@@ -83,13 +112,14 @@ export async function shutdownWorkers(): Promise<void> {
83
112
  (worker) =>
84
113
  new Promise<void>((resolve) => {
85
114
  const timeout = setTimeout(() => {
86
- worker.kill();
87
- }, 10000);
115
+ if (worker.process.pid) void killTree(worker.process.pid);
116
+ }, 10_000);
88
117
  worker.on('exit', () => {
89
118
  clearTimeout(timeout);
90
119
  resolve();
91
120
  });
92
121
  sendShutdownMessage(worker);
122
+ worker.disconnect();
93
123
  }),
94
124
  ),
95
125
  );
@@ -99,7 +129,7 @@ export async function shutdownWorkers(): Promise<void> {
99
129
  export function gracefullyKill(worker: Worker): void {
100
130
  worker.isShuttingDown = true;
101
131
  const timeout = setTimeout(() => {
102
- worker.kill();
132
+ if (worker.process.pid) void killTree(worker.process.pid);
103
133
  }, 10000);
104
134
  worker.on('exit', () => {
105
135
  clearTimeout(timeout);
@@ -107,9 +137,34 @@ export function gracefullyKill(worker: Worker): void {
107
137
  sendShutdownMessage(worker);
108
138
  }
109
139
 
140
+ export async function killTree(rootPid: number): Promise<void> {
141
+ const pids = await pidtree(rootPid, { root: true });
142
+
143
+ pids.forEach((pid) => {
144
+ try {
145
+ process.kill(pid, 'SIGKILL');
146
+ } catch {
147
+ /* noop */
148
+ }
149
+ });
150
+ }
151
+
152
+ export function shutdownWithError(): void {
153
+ process.exit(1);
154
+ }
155
+
156
+ export function resolvePlaywrightBrowserType(browserName: string): (typeof browserTypes)[keyof typeof browserTypes] {
157
+ assert(
158
+ browserName in browserTypes,
159
+ new Error(`Failed to match browser name "${browserName}" to playwright browserType`),
160
+ );
161
+
162
+ return browserTypes[browserName as keyof typeof browserTypes];
163
+ }
164
+
110
165
  export async function getCreeveyCache(): Promise<string | undefined> {
111
166
  const { default: findCacheDir } = await import('find-cache-dir');
112
- return findCacheDir({ name: 'creevey', cwd: dirname(fileURLToPath(importMetaUrl)) });
167
+ return findCacheDir({ name: 'creevey', cwd: path.dirname(fileURLToPath(importMetaUrl)) });
113
168
  }
114
169
 
115
170
  export async function runSequence(seq: (() => unknown)[], predicate: () => boolean): Promise<boolean> {
@@ -142,11 +197,12 @@ export function testsToImages(tests: (TestData | undefined)[]): Set<string> {
142
197
 
143
198
  // https://tuhrig.de/how-to-know-you-are-inside-a-docker-container/
144
199
  export const isInsideDocker =
145
- fs.existsSync('/proc/1/cgroup') && fs.readFileSync('/proc/1/cgroup', 'utf-8').includes('docker');
200
+ (fs.existsSync('/proc/1/cgroup') && fs.readFileSync('/proc/1/cgroup', 'utf-8').includes('docker')) ||
201
+ process.env.DOCKER === 'true';
146
202
 
147
203
  export const downloadBinary = (downloadUrl: string, destination: string): Promise<void> =>
148
204
  new Promise((resolve, reject) =>
149
- get(downloadUrl, (response) => {
205
+ https.get(downloadUrl, (response) => {
150
206
  if (response.statusCode == 302) {
151
207
  const { location } = response.headers;
152
208
  if (!location) {
@@ -186,10 +242,10 @@ export function readDirRecursive(dirPath: string): string[] {
186
242
  );
187
243
  }
188
244
 
189
- const _require = createRequire(importMetaUrl);
190
245
  export function tryToLoadTestsData(filename: string): Partial<Record<string, ServerTest>> | undefined {
191
246
  try {
192
- return _require(filename) as Partial<Record<string, ServerTest>>;
247
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, import-x/no-dynamic-require
248
+ return require(filename) as Partial<Record<string, ServerTest>>;
193
249
  } catch {
194
250
  /* noop */
195
251
  }
@@ -199,19 +255,71 @@ const [nodeVersion] = process.versions.node.split('.').map(Number);
199
255
  export async function loadThroughTSX<T>(
200
256
  callback: (load: (modulePath: string) => Promise<T>) => Promise<T>,
201
257
  ): Promise<T> {
202
- // TODO Check if it work in node18 and type: 'module'
203
- const unregister = nodeVersion > 18 ? esmRegister() : cjsRegister();
258
+ const unregisterESM = nodeVersion > 18 ? esmRegister() : noop;
259
+ const unregisterCJS = cjsRegister();
204
260
 
205
261
  const result = await callback((modulePath) =>
206
262
  nodeVersion > 18
207
263
  ? import(modulePath)
208
- : // eslint-disable-next-line @typescript-eslint/no-require-imports
264
+ : // eslint-disable-next-line @typescript-eslint/no-require-imports, import-x/no-dynamic-require
209
265
  Promise.resolve(require(modulePath) as T),
210
266
  );
211
267
 
212
268
  // NOTE: `unregister` type is `(() => Promise<void>) | (() => void)`
213
269
  // eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
214
- await unregister();
270
+ await unregisterCJS();
271
+ // eslint-disable-next-line @typescript-eslint/await-thenable, @typescript-eslint/no-confusing-void-expression
272
+ await unregisterESM();
215
273
 
216
274
  return result;
217
275
  }
276
+
277
+ export function waitOnUrl(waitUrl: string, timeout: number, delay: number) {
278
+ const urls = [waitUrl];
279
+ if (!LOCALHOST_REGEXP.test(waitUrl)) {
280
+ const parsedUrl = new URL(waitUrl);
281
+ parsedUrl.host = 'localhost';
282
+ urls.push(parsedUrl.toString());
283
+ }
284
+ const startTime = Date.now();
285
+ return Promise.race(
286
+ urls.map(
287
+ (url) =>
288
+ new Promise<void>((resolve, reject) => {
289
+ const interval = setInterval(() => {
290
+ http
291
+ .get(url, (response) => {
292
+ if (response.statusCode === 200) {
293
+ clearInterval(interval);
294
+ resolve();
295
+ }
296
+ })
297
+ .on('error', () => {
298
+ // Ignore HTTP errors
299
+ });
300
+
301
+ if (Date.now() - startTime > timeout) {
302
+ clearInterval(interval);
303
+ reject(new Error(`${url} didn't respond within ${timeout / 1000} seconds`));
304
+ }
305
+ }, delay);
306
+ }),
307
+ ),
308
+ );
309
+ }
310
+
311
+ /**
312
+ * Copies static assets to the report directory
313
+ * @param reportDir Directory where the report will be generated
314
+ */
315
+ export async function copyStatics(reportDir: string): Promise<void> {
316
+ const clientDir = path.join(path.dirname(fileURLToPath(importMetaUrl)), '../../dist/client/web');
317
+ const assets = (await fs.promises.readdir(path.join(clientDir, 'assets'), { withFileTypes: true }))
318
+ .filter((dirent) => dirent.isFile())
319
+ .map((dirent) => dirent.name);
320
+ await fs.promises.mkdir(path.join(reportDir, 'assets'), { recursive: true });
321
+ await fs.promises.copyFile(path.join(clientDir, 'index.html'), path.join(reportDir, 'index.html'));
322
+ for (const asset of assets) {
323
+ await fs.promises.copyFile(path.join(clientDir, 'assets', asset), path.join(reportDir, 'assets', asset));
324
+ }
325
+ }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import { networkInterfaces } from 'os';
3
3
  import { logger } from './logger.js';
4
- import { Args } from '@storybook/csf';
4
+ import type { Args } from 'storybook/internal/types';
5
5
  import {
6
6
  isDefined,
7
7
  StoryInput,
@@ -0,0 +1,14 @@
1
+ import type { Container } from 'dockerode';
2
+
3
+ let workerContainer: Container | null = null;
4
+
5
+ export function setWorkerContainer(container: Container): void {
6
+ workerContainer = container;
7
+ }
8
+
9
+ export async function removeWorkerContainer(): Promise<void> {
10
+ if (workerContainer) {
11
+ await workerContainer.remove({ force: true });
12
+ workerContainer = null;
13
+ }
14
+ }