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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/AUTHORS +2 -0
  2. package/CHANGELOG.md +281 -0
  3. package/README.md +19 -41
  4. package/dist/client/addon/components/Addon.js +18 -8
  5. package/dist/client/addon/components/Addon.js.map +1 -1
  6. package/dist/client/addon/components/Panel.js +2 -2
  7. package/dist/client/addon/components/Panel.js.map +1 -1
  8. package/dist/client/addon/components/TestSelect.js +2 -2
  9. package/dist/client/addon/components/TestSelect.js.map +1 -1
  10. package/dist/client/addon/components/Tools.js +19 -9
  11. package/dist/client/addon/components/Tools.js.map +1 -1
  12. package/dist/client/addon/controller.d.ts +1 -1
  13. package/dist/client/addon/controller.js +3 -3
  14. package/dist/client/addon/controller.js.map +1 -1
  15. package/dist/client/addon/decorator.d.ts +1 -1
  16. package/dist/client/addon/makeDecorator.d.ts +9 -0
  17. package/dist/client/addon/makeDecorator.js +48 -0
  18. package/dist/client/addon/makeDecorator.js.map +1 -0
  19. package/dist/client/addon/manager.js +38 -39
  20. package/dist/client/addon/manager.js.map +1 -1
  21. package/dist/client/addon/preset.d.ts +0 -1
  22. package/dist/client/addon/preset.js +3 -2
  23. package/dist/client/addon/preset.js.map +1 -1
  24. package/dist/client/addon/preview.d.ts +1 -1
  25. package/dist/client/addon/withCreevey.d.ts +5 -3
  26. package/dist/client/addon/withCreevey.js +5 -20
  27. package/dist/client/addon/withCreevey.js.map +1 -1
  28. package/dist/client/shared/components/ImagesView/BlendView.d.ts +2 -2
  29. package/dist/client/shared/components/ImagesView/BlendView.js +18 -8
  30. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  31. package/dist/client/shared/components/ImagesView/ImagesView.js +1 -1
  32. package/dist/client/shared/components/ImagesView/ImagesView.js.map +1 -1
  33. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +2 -2
  34. package/dist/client/shared/components/ImagesView/SideBySideView.js +19 -9
  35. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  36. package/dist/client/shared/components/ImagesView/SlideView.d.ts +2 -2
  37. package/dist/client/shared/components/ImagesView/SlideView.js +19 -9
  38. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  39. package/dist/client/shared/components/ImagesView/SwapView.d.ts +2 -2
  40. package/dist/client/shared/components/ImagesView/SwapView.js +19 -9
  41. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  42. package/dist/client/shared/components/ImagesView/common.d.ts +1 -1
  43. package/dist/client/shared/components/PageFooter/PageFooter.js +1 -1
  44. package/dist/client/shared/components/PageFooter/PageFooter.js.map +1 -1
  45. package/dist/client/shared/components/PageFooter/Paging.js +1 -1
  46. package/dist/client/shared/components/PageFooter/Paging.js.map +1 -1
  47. package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +2 -2
  48. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -1
  49. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  50. package/dist/client/shared/components/PageHeader/PageHeader.js +34 -13
  51. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  52. package/dist/client/shared/components/ResultsPage.d.ts +2 -2
  53. package/dist/client/shared/components/ResultsPage.js +22 -10
  54. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  55. package/dist/client/shared/creeveyClientApi.js +18 -1
  56. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  57. package/dist/client/shared/helpers.d.ts +1 -3
  58. package/dist/client/shared/helpers.js +4 -19
  59. package/dist/client/shared/helpers.js.map +1 -1
  60. package/dist/client/web/CreeveyApp.d.ts +1 -0
  61. package/dist/client/web/CreeveyApp.js +22 -9
  62. package/dist/client/web/CreeveyApp.js.map +1 -1
  63. package/dist/client/web/CreeveyContext.d.ts +1 -0
  64. package/dist/client/web/CreeveyContext.js +18 -7
  65. package/dist/client/web/CreeveyContext.js.map +1 -1
  66. package/dist/client/web/CreeveyLoader.d.ts +1 -1
  67. package/dist/client/web/CreeveyLoader.js +3 -3
  68. package/dist/client/web/CreeveyLoader.js.map +1 -1
  69. package/dist/client/web/CreeveyView/SideBar/Checkbox.d.ts +4 -4
  70. package/dist/client/web/CreeveyView/SideBar/Checkbox.js +36 -6
  71. package/dist/client/web/CreeveyView/SideBar/Checkbox.js.map +1 -1
  72. package/dist/client/web/CreeveyView/SideBar/Search.js +18 -8
  73. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  74. package/dist/client/web/CreeveyView/SideBar/SideBar.js +26 -12
  75. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  76. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +28 -17
  77. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  78. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +32 -12
  79. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  80. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +6 -6
  81. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +20 -11
  82. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  83. package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -11
  84. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  85. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +2 -2
  86. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js +2 -2
  87. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js.map +1 -1
  88. package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +2 -2
  89. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +3 -2
  90. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  91. package/dist/client/web/CreeveyView/SideBar/Toggle.js +1 -1
  92. package/dist/client/web/CreeveyView/SideBar/Toggle.js.map +1 -1
  93. package/dist/client/web/KeyboardEventsContext.js +17 -7
  94. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  95. package/dist/client/web/assets/index-CtSq3IhG.js +518 -0
  96. package/dist/client/web/index.html +1 -1
  97. package/dist/client/web/index.js +26 -11
  98. package/dist/client/web/index.js.map +1 -1
  99. package/dist/client/web/themes.d.ts +2 -0
  100. package/dist/client/web/themes.js +22 -0
  101. package/dist/client/web/themes.js.map +1 -0
  102. package/dist/creevey.d.ts +1 -1
  103. package/dist/creevey.js +122 -41
  104. package/dist/creevey.js.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/playwright/generator.d.ts +25 -0
  107. package/dist/playwright/generator.js +243 -0
  108. package/dist/playwright/generator.js.map +1 -0
  109. package/dist/playwright/helpers.d.ts +2 -0
  110. package/dist/playwright/helpers.js +29 -0
  111. package/dist/playwright/helpers.js.map +1 -0
  112. package/dist/playwright/reporter.d.ts +83 -0
  113. package/dist/playwright/reporter.js +334 -0
  114. package/dist/playwright/reporter.js.map +1 -0
  115. package/dist/playwright/setup.d.ts +3 -0
  116. package/dist/playwright/setup.js +72 -0
  117. package/dist/playwright/setup.js.map +1 -0
  118. package/dist/playwright.d.ts +1 -0
  119. package/dist/playwright.js +3 -1
  120. package/dist/playwright.js.map +1 -1
  121. package/dist/server/compare.d.ts +18 -0
  122. package/dist/server/compare.js +182 -0
  123. package/dist/server/compare.js.map +1 -0
  124. package/dist/server/config.d.ts +3 -3
  125. package/dist/server/config.js +75 -8
  126. package/dist/server/config.js.map +1 -1
  127. package/dist/server/connection.d.ts +3 -0
  128. package/dist/server/connection.js +28 -0
  129. package/dist/server/connection.js.map +1 -0
  130. package/dist/server/docker.d.ts +1 -1
  131. package/dist/server/docker.js +54 -32
  132. package/dist/server/docker.js.map +1 -1
  133. package/dist/server/index.d.ts +2 -2
  134. package/dist/server/index.js +161 -64
  135. package/dist/server/index.js.map +1 -1
  136. package/dist/server/master/api.d.ts +11 -6
  137. package/dist/server/master/api.js +88 -25
  138. package/dist/server/master/api.js.map +1 -1
  139. package/dist/server/master/handlers/capture-handler.d.ts +5 -0
  140. package/dist/server/master/handlers/capture-handler.js +25 -0
  141. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  142. package/dist/server/master/handlers/index.d.ts +4 -0
  143. package/dist/server/master/handlers/index.js +21 -0
  144. package/dist/server/master/handlers/index.js.map +1 -0
  145. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  146. package/dist/server/master/handlers/ping-handler.js +8 -0
  147. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  148. package/dist/server/master/handlers/static-handler.d.ts +1 -0
  149. package/dist/server/master/handlers/static-handler.js +20 -0
  150. package/dist/server/master/handlers/static-handler.js.map +1 -0
  151. package/dist/server/master/handlers/stories-handler.d.ts +4 -0
  152. package/dist/server/master/handlers/stories-handler.js +24 -0
  153. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  154. package/dist/server/master/master.js +7 -24
  155. package/dist/server/master/master.js.map +1 -1
  156. package/dist/server/master/pool.d.ts +1 -0
  157. package/dist/server/master/pool.js +5 -3
  158. package/dist/server/master/pool.js.map +1 -1
  159. package/dist/server/master/queue.d.ts +1 -1
  160. package/dist/server/master/queue.js +14 -6
  161. package/dist/server/master/queue.js.map +1 -1
  162. package/dist/server/master/runner.d.ts +6 -6
  163. package/dist/server/master/runner.js +98 -130
  164. package/dist/server/master/runner.js.map +1 -1
  165. package/dist/server/master/server.d.ts +1 -1
  166. package/dist/server/master/server.js +193 -88
  167. package/dist/server/master/server.js.map +1 -1
  168. package/dist/server/master/start.d.ts +1 -2
  169. package/dist/server/master/start.js +13 -29
  170. package/dist/server/master/start.js.map +1 -1
  171. package/dist/server/master/testsManager.d.ts +81 -0
  172. package/dist/server/master/testsManager.js +282 -0
  173. package/dist/server/master/testsManager.js.map +1 -0
  174. package/dist/server/playwright/docker-file.d.ts +1 -1
  175. package/dist/server/playwright/docker-file.js +17 -8
  176. package/dist/server/playwright/docker-file.js.map +1 -1
  177. package/dist/server/playwright/docker.d.ts +2 -1
  178. package/dist/server/playwright/docker.js +10 -2
  179. package/dist/server/playwright/docker.js.map +1 -1
  180. package/dist/server/playwright/index-source.mjs +16 -0
  181. package/dist/server/playwright/internal.d.ts +7 -7
  182. package/dist/server/playwright/internal.js +137 -79
  183. package/dist/server/playwright/internal.js.map +1 -1
  184. package/dist/server/playwright/webdriver.d.ts +3 -3
  185. package/dist/server/playwright/webdriver.js +0 -6
  186. package/dist/server/playwright/webdriver.js.map +1 -1
  187. package/dist/server/providers/browser.js +4 -3
  188. package/dist/server/providers/browser.js.map +1 -1
  189. package/dist/server/providers/hybrid.js +2 -2
  190. package/dist/server/providers/hybrid.js.map +1 -1
  191. package/dist/server/report.d.ts +10 -0
  192. package/dist/server/report.js +45 -0
  193. package/dist/server/report.js.map +1 -0
  194. package/dist/server/reporters/creevey.d.ts +7 -0
  195. package/dist/server/reporters/creevey.js +63 -0
  196. package/dist/server/reporters/creevey.js.map +1 -0
  197. package/dist/server/reporters/index.d.ts +2 -0
  198. package/dist/server/reporters/index.js +16 -0
  199. package/dist/server/reporters/index.js.map +1 -0
  200. package/dist/server/reporters/junit.d.ts +16 -0
  201. package/dist/server/reporters/junit.js +167 -0
  202. package/dist/server/reporters/junit.js.map +1 -0
  203. package/dist/server/reporters/teamcity.d.ts +7 -0
  204. package/dist/server/reporters/teamcity.js +60 -0
  205. package/dist/server/reporters/teamcity.js.map +1 -0
  206. package/dist/server/selenium/internal.d.ts +3 -3
  207. package/dist/server/selenium/internal.js +48 -34
  208. package/dist/server/selenium/internal.js.map +1 -1
  209. package/dist/server/selenium/selenoid.js +12 -6
  210. package/dist/server/selenium/selenoid.js.map +1 -1
  211. package/dist/server/selenium/webdriver.d.ts +3 -3
  212. package/dist/server/selenium/webdriver.js +4 -8
  213. package/dist/server/selenium/webdriver.js.map +1 -1
  214. package/dist/server/shutdown.d.ts +1 -0
  215. package/dist/server/shutdown.js +23 -0
  216. package/dist/server/shutdown.js.map +1 -0
  217. package/dist/server/stories.d.ts +0 -1
  218. package/dist/server/stories.js +0 -12
  219. package/dist/server/stories.js.map +1 -1
  220. package/dist/server/telemetry.js +3 -3
  221. package/dist/server/telemetry.js.map +1 -1
  222. package/dist/server/testsFiles/parser.js +45 -5
  223. package/dist/server/testsFiles/parser.js.map +1 -1
  224. package/dist/server/utils.d.ts +23 -0
  225. package/dist/server/utils.js +113 -13
  226. package/dist/server/utils.js.map +1 -1
  227. package/dist/server/webdriver.d.ts +1 -1
  228. package/dist/server/worker/context.d.ts +3 -0
  229. package/dist/server/worker/context.js +15 -0
  230. package/dist/server/worker/context.js.map +1 -0
  231. package/dist/server/worker/match-image.d.ts +8 -12
  232. package/dist/server/worker/match-image.js +11 -178
  233. package/dist/server/worker/match-image.js.map +1 -1
  234. package/dist/server/worker/start.d.ts +2 -2
  235. package/dist/server/worker/start.js +27 -63
  236. package/dist/server/worker/start.js.map +1 -1
  237. package/dist/shared/index.d.ts +1 -1
  238. package/dist/shared/index.js +9 -7
  239. package/dist/shared/index.js.map +1 -1
  240. package/dist/types.d.ts +84 -43
  241. package/dist/types.js +65 -1
  242. package/dist/types.js.map +1 -1
  243. package/docs/cli.md +80 -0
  244. package/docs/config.md +179 -165
  245. package/docs/examples/playwright-reporer/playwright.config.ts +37 -0
  246. package/docs/migration-0.9-to-0.10.md +144 -0
  247. package/docs/playwright-reporter.md +357 -0
  248. package/docs/storybook.md +60 -0
  249. package/docs/tests.md +50 -45
  250. package/package.json +78 -83
  251. package/playwright.config.mts +46 -0
  252. package/src/client/addon/components/Addon.tsx +1 -1
  253. package/src/client/addon/components/Panel.tsx +2 -2
  254. package/src/client/addon/components/TestSelect.tsx +2 -2
  255. package/src/client/addon/components/Tools.tsx +2 -2
  256. package/src/client/addon/controller.ts +4 -4
  257. package/src/client/addon/makeDecorator.ts +69 -0
  258. package/src/client/addon/manager.ts +38 -37
  259. package/src/client/addon/preset.ts +2 -1
  260. package/src/client/addon/withCreevey.ts +10 -18
  261. package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
  262. package/src/client/shared/components/ImagesView/ImagesView.tsx +1 -1
  263. package/src/client/shared/components/ImagesView/SideBySideView.tsx +2 -2
  264. package/src/client/shared/components/ImagesView/SlideView.tsx +2 -2
  265. package/src/client/shared/components/ImagesView/SwapView.tsx +2 -2
  266. package/src/client/shared/components/ImagesView/common.ts +1 -1
  267. package/src/client/shared/components/PageFooter/PageFooter.tsx +1 -1
  268. package/src/client/shared/components/PageFooter/Paging.tsx +1 -1
  269. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -1
  270. package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
  271. package/src/client/shared/components/ResultsPage.tsx +6 -4
  272. package/src/client/shared/creeveyClientApi.ts +19 -1
  273. package/src/client/shared/helpers.ts +4 -24
  274. package/src/client/web/CreeveyApp.tsx +5 -2
  275. package/src/client/web/CreeveyContext.tsx +2 -0
  276. package/src/client/web/CreeveyLoader.tsx +2 -2
  277. package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +3 -3
  278. package/src/client/web/CreeveyView/SideBar/Search.tsx +1 -1
  279. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
  280. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +21 -19
  281. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
  282. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +10 -8
  283. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +9 -7
  284. package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +2 -2
  285. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +3 -2
  286. package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -1
  287. package/src/client/web/index.tsx +10 -5
  288. package/src/client/web/themes.ts +24 -0
  289. package/src/creevey.ts +92 -38
  290. package/src/playwright/generator.ts +322 -0
  291. package/src/playwright/helpers.ts +31 -0
  292. package/src/playwright/reporter.ts +381 -0
  293. package/src/playwright/setup.ts +84 -0
  294. package/src/playwright.ts +1 -0
  295. package/src/server/compare.ts +260 -0
  296. package/src/server/config.ts +52 -9
  297. package/src/server/connection.ts +26 -0
  298. package/src/server/docker.ts +62 -34
  299. package/src/server/index.ts +161 -79
  300. package/src/server/master/api.ts +94 -28
  301. package/src/server/master/handlers/capture-handler.ts +20 -0
  302. package/src/server/master/handlers/index.ts +4 -0
  303. package/src/server/master/handlers/ping-handler.ts +6 -0
  304. package/src/server/master/handlers/static-handler.ts +16 -0
  305. package/src/server/master/handlers/stories-handler.ts +20 -0
  306. package/src/server/master/master.ts +10 -27
  307. package/src/server/master/pool.ts +7 -3
  308. package/src/server/master/queue.ts +21 -7
  309. package/src/server/master/runner.ts +123 -134
  310. package/src/server/master/server.ts +214 -101
  311. package/src/server/master/start.ts +19 -41
  312. package/src/server/master/testsManager.ts +316 -0
  313. package/src/server/playwright/docker-file.ts +20 -8
  314. package/src/server/playwright/docker.ts +16 -3
  315. package/src/server/playwright/index-source.mjs +16 -0
  316. package/src/server/playwright/internal.ts +169 -96
  317. package/src/server/playwright/webdriver.ts +4 -10
  318. package/src/server/providers/browser.ts +4 -3
  319. package/src/server/providers/hybrid.ts +2 -3
  320. package/src/server/report.ts +51 -0
  321. package/src/server/reporters/creevey.ts +71 -0
  322. package/src/server/reporters/index.ts +11 -0
  323. package/src/server/reporters/junit.ts +207 -0
  324. package/src/server/reporters/teamcity.ts +74 -0
  325. package/src/server/selenium/internal.ts +62 -45
  326. package/src/server/selenium/selenoid.ts +13 -6
  327. package/src/server/selenium/webdriver.ts +8 -12
  328. package/src/server/shutdown.ts +19 -0
  329. package/src/server/stories.ts +1 -12
  330. package/src/server/telemetry.ts +3 -3
  331. package/src/server/testsFiles/parser.ts +52 -4
  332. package/src/server/utils.ts +123 -14
  333. package/src/server/webdriver.ts +1 -1
  334. package/src/server/worker/context.ts +14 -0
  335. package/src/server/worker/match-image.ts +16 -248
  336. package/src/server/worker/start.ts +32 -75
  337. package/src/shared/index.ts +10 -8
  338. package/src/types.ts +91 -58
  339. package/types/global.d.ts +1 -0
  340. package/dist/client/web/assets/index-BE9CL5_G.js +0 -591
  341. package/dist/server/reporter.d.ts +0 -26
  342. package/dist/server/reporter.js +0 -108
  343. package/dist/server/reporter.js.map +0 -1
  344. package/dist/server/update.d.ts +0 -2
  345. package/dist/server/update.js +0 -53
  346. package/dist/server/update.js.map +0 -1
  347. package/src/server/reporter.ts +0 -139
  348. package/src/server/update.ts +0 -74
@@ -0,0 +1,71 @@
1
+ import chalk from 'chalk';
2
+ import Logger from 'loglevel';
3
+ import prefix from 'loglevel-plugin-prefix';
4
+ import { FakeTest, isImageError, TEST_EVENTS } from '../../types.js';
5
+ import EventEmitter from 'events';
6
+
7
+ const testLevels: Record<string, string> = {
8
+ INFO: chalk.green('PASS'),
9
+ WARN: chalk.yellow('START'),
10
+ ERROR: chalk.red('FAIL'),
11
+ };
12
+
13
+ export class CreeveyReporter {
14
+ private logger: Logger.Logger | null = null;
15
+ // TODO Output in better way, like vitest, maybe
16
+ constructor(runner: EventEmitter) {
17
+ runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
18
+ this.getLogger(test.creevey).warn(chalk.cyan(test.fullTitle()));
19
+ });
20
+ runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
21
+ this.getLogger(test.creevey).info(chalk.cyan(test.fullTitle()), chalk.gray(`(${test.duration} ms)`));
22
+ });
23
+ runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
24
+ this.getLogger(test.creevey).error(
25
+ chalk.cyan(test.fullTitle()),
26
+ chalk.gray(`(${test.duration} ms)`),
27
+ '\n ',
28
+ this.getErrors(
29
+ error,
30
+ (error, imageName) => `${chalk.bold(imageName ?? test.creevey.browserName)}:${error}`,
31
+ (error) => error.stack ?? error.message,
32
+ ).join('\n '),
33
+ );
34
+ });
35
+ }
36
+
37
+ private getLogger(options: { sessionId: string; browserName: string }) {
38
+ if (this.logger) return this.logger;
39
+ const { sessionId, browserName } = options;
40
+ const testLogger = Logger.getLogger(sessionId);
41
+
42
+ this.logger = prefix.apply(testLogger, {
43
+ format(level) {
44
+ return `[${browserName}:${chalk.gray(process.pid)}] ${testLevels[level]} => ${chalk.gray(sessionId)}`;
45
+ },
46
+ });
47
+
48
+ return this.logger;
49
+ }
50
+
51
+ private getErrors(
52
+ error: unknown,
53
+ imageErrorToString: (error: string, imageName?: string) => string,
54
+ errorToString: (error: Error) => string,
55
+ ): string[] {
56
+ const errors = [];
57
+ if (!(error instanceof Error)) {
58
+ errors.push(error as string);
59
+ } else if (!isImageError(error)) {
60
+ errors.push(errorToString(error));
61
+ } else if (typeof error.images == 'string') {
62
+ errors.push(imageErrorToString(error.images));
63
+ } else {
64
+ const imageErrors = error.images ?? {};
65
+ Object.keys(imageErrors).forEach((imageName) => {
66
+ errors.push(imageErrorToString(imageErrors[imageName] ?? '', imageName));
67
+ });
68
+ }
69
+ return errors;
70
+ }
71
+ }
@@ -0,0 +1,11 @@
1
+ import { BaseReporter } from '../../types.js';
2
+ import { CreeveyReporter } from './creevey.js';
3
+ import { JUnitReporter } from './junit.js';
4
+ import { TeamcityReporter } from './teamcity.js';
5
+
6
+ export function getReporter(reporter: BaseReporter | 'creevey' | 'teamcity' | 'junit'): BaseReporter {
7
+ if (reporter === 'creevey') return CreeveyReporter;
8
+ if (reporter === 'teamcity') return TeamcityReporter;
9
+ if (reporter === 'junit') return JUnitReporter;
10
+ return reporter;
11
+ }
@@ -0,0 +1,207 @@
1
+ import EventEmitter from 'events';
2
+ import { dirname, resolve } from 'path';
3
+ import { closeSync, existsSync, mkdirSync, openSync, writeFileSync } from 'fs';
4
+ import { TEST_EVENTS, FakeTest } from '../../types.js';
5
+ import { logger } from '../logger.js';
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ class IndentedLogger<T = any> {
9
+ private currentIndent = '';
10
+
11
+ constructor(private baseLog: (text: string) => T) {}
12
+
13
+ indent(): void {
14
+ this.currentIndent += ' ';
15
+ }
16
+
17
+ unindent(): void {
18
+ this.currentIndent = this.currentIndent.substring(0, this.currentIndent.length - 4);
19
+ }
20
+
21
+ log(text: string): T {
22
+ return this.baseLog(this.currentIndent + text);
23
+ }
24
+ }
25
+
26
+ // NOTE: This is a reworked copy of the JUnitReporter class from Vitest.
27
+ export class JUnitReporter {
28
+ private reportFile: string;
29
+ private fileFd?: number;
30
+ private logger: IndentedLogger<void>;
31
+ private suites: Record<string, FakeTest[]> = {};
32
+ // TODO classnameTemplate
33
+ // TODO Output console logs
34
+ // TODO Output attachments
35
+ constructor(runner: EventEmitter, options: { reportDir: string; reporterOptions: { outputFile?: string } }) {
36
+ const { reportDir, reporterOptions } = options;
37
+
38
+ this.reportFile = reporterOptions.outputFile ?? resolve(reportDir, 'junit.xml');
39
+
40
+ this.logger = new IndentedLogger((text) => {
41
+ this.fileFd ??= openSync(this.reportFile, 'w+');
42
+
43
+ writeFileSync(this.fileFd, `${text}\n`);
44
+ });
45
+
46
+ runner.on(TEST_EVENTS.RUN_BEGIN, () => {
47
+ this.suites = {};
48
+
49
+ const outputDirectory = dirname(this.reportFile);
50
+ if (!existsSync(outputDirectory)) {
51
+ mkdirSync(outputDirectory, { recursive: true });
52
+ }
53
+
54
+ this.fileFd = openSync(this.reportFile, 'w+');
55
+ });
56
+ runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
57
+ const suite = (this.suites[test.parent.title] ??= []);
58
+ suite.push(test);
59
+ });
60
+ runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest) => {
61
+ const suite = (this.suites[test.parent.title] ??= []);
62
+ suite.push(test);
63
+ });
64
+ runner.on(TEST_EVENTS.RUN_END, () => {
65
+ this.onFinished();
66
+ });
67
+ }
68
+
69
+ private writeElement(name: string, attrs: Record<string, string | number | undefined>, children?: () => void): void {
70
+ const pairs: string[] = [];
71
+ for (const key in attrs) {
72
+ const attr = attrs[key];
73
+ if (attr === undefined) {
74
+ continue;
75
+ }
76
+
77
+ pairs.push(`${key}="${escapeXML(attr)}"`);
78
+ }
79
+
80
+ this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(' ')}` : ''}>`);
81
+ this.logger.indent();
82
+ children?.call(this);
83
+ this.logger.unindent();
84
+
85
+ this.logger.log(`</${name}>`);
86
+ }
87
+
88
+ private writeTasks(tests: FakeTest[]): void {
89
+ for (const test of tests) {
90
+ const classname = test.parent.title;
91
+
92
+ this.writeElement(
93
+ 'testcase',
94
+ {
95
+ classname,
96
+ name: test.title,
97
+ time: getDuration(test),
98
+ },
99
+ () => {
100
+ if (test.state === 'failed') {
101
+ const error = test.err;
102
+ this.writeElement('failure', { message: error });
103
+ }
104
+ },
105
+ );
106
+ }
107
+ }
108
+
109
+ private onFinished(): void {
110
+ this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
111
+
112
+ const suites = Object.entries(this.suites).map(([name, tests]) => {
113
+ return {
114
+ name,
115
+ tests,
116
+ failures: tests.filter((test) => test.state === 'failed').length,
117
+ time: tests.reduce((acc, test) => acc + (test.duration ?? 0), 0),
118
+ };
119
+ });
120
+ const stats = suites.reduce(
121
+ (s, { tests, failures, time }) => {
122
+ s.tests += tests.length;
123
+ s.failures += failures;
124
+ s.time += time;
125
+ return s;
126
+ },
127
+ { name: 'creevey tests', tests: 0, failures: 0, time: 0 },
128
+ );
129
+
130
+ this.writeElement('testsuites', { ...stats, time: executionTime(stats.time) }, () => {
131
+ suites.forEach(({ name, tests, failures, time }) => {
132
+ this.writeElement(
133
+ 'testsuite',
134
+ {
135
+ name,
136
+ tests: tests.length,
137
+ failures,
138
+ time: executionTime(time),
139
+ },
140
+ () => {
141
+ this.writeTasks(tests);
142
+ },
143
+ );
144
+ });
145
+ });
146
+
147
+ if (this.reportFile) {
148
+ logger().info(`JUNIT report written to ${this.reportFile}`);
149
+ }
150
+
151
+ if (this.fileFd) {
152
+ closeSync(this.fileFd);
153
+ this.fileFd = undefined;
154
+ }
155
+ }
156
+ }
157
+
158
+ // https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
159
+ function removeInvalidXMLCharacters(value: string, removeDiscouragedChars: boolean): string {
160
+ let regex =
161
+ // eslint-disable-next-line no-control-regex
162
+ /([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
163
+ value = String(value).replace(regex, '');
164
+
165
+ if (removeDiscouragedChars) {
166
+ // remove everything discouraged by XML 1.0 specifications
167
+ regex = new RegExp(
168
+ '([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDF' +
169
+ 'FE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uD' +
170
+ 'FFF])|\\uD97F[\\uDFFE\\uDFFF]|\\uD9BF[\\uDFFE\\uDFFF]|\\uD9FF[\\uDFFE\\uDFFF]' +
171
+ '|\\uDA3F[\\uDFFE\\uDFFF]|\\uDA7F[\\uDFFE\\uDFFF]|\\uDABF[\\uDFFE\\uDFFF]|(?:\\' +
172
+ 'uDAFF[\\uDFFE\\uDFFF])|\\uDB3F[\\uDFFE\\uDFFF]|\\uDB7F[\\uDFFE\\uDFFF]|(?:\\uDBBF' +
173
+ '[\\uDFFE\\uDFFF])|\\uDBFF[\\uDFFE\\uDFFF](?:[\\0-\\t\\v\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
174
+ 'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
175
+ '(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
176
+ 'g',
177
+ );
178
+
179
+ value = value.replace(regex, '');
180
+ }
181
+
182
+ return value;
183
+ }
184
+
185
+ function escapeXML(value: string | number): string {
186
+ return removeInvalidXMLCharacters(
187
+ String(value)
188
+ .replace(/&/g, '&amp;')
189
+ .replace(/"/g, '&quot;')
190
+ .replace(/'/g, '&apos;')
191
+ .replace(/</g, '&lt;')
192
+ .replace(/>/g, '&gt;'),
193
+ true,
194
+ );
195
+ }
196
+
197
+ function executionTime(durationMS: number) {
198
+ return (durationMS / 1000).toLocaleString('en-US', {
199
+ useGrouping: false,
200
+ maximumFractionDigits: 10,
201
+ });
202
+ }
203
+
204
+ function getDuration(task: FakeTest): string | undefined {
205
+ const duration = task.duration ?? 0;
206
+ return executionTime(duration);
207
+ }
@@ -0,0 +1,74 @@
1
+ import { FakeTest, Images, isDefined, TEST_EVENTS } from '../../types.js';
2
+ import EventEmitter from 'events';
3
+
4
+ export class TeamcityReporter {
5
+ constructor(runner: EventEmitter, options: { reportDir: string }) {
6
+ const { reportDir } = options;
7
+
8
+ runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
9
+ console.log(`##teamcity[testStarted name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`);
10
+ });
11
+
12
+ runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
13
+ console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`);
14
+ });
15
+
16
+ runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error: Error) => {
17
+ const browserName = this.escape(test.creevey.browserName);
18
+ Object.entries(test.creevey.images).forEach(([name, image]) => {
19
+ if (!image) return;
20
+ const filePath = test
21
+ .titlePath()
22
+ .slice(0, -1)
23
+ .concat(name == browserName ? [] : [browserName])
24
+ .map(this.escape)
25
+ .join('/');
26
+
27
+ const { error: _, ...rest } = image;
28
+ Object.values(rest as Partial<Images>)
29
+ .filter(isDefined)
30
+ .forEach((fileName) => {
31
+ console.log(`##teamcity[publishArtifacts '${reportDir}/${filePath}/${fileName} => report/${filePath}']`);
32
+ console.log(
33
+ `##teamcity[testMetadata testName='${this.escape(
34
+ test.fullTitle(),
35
+ )}' type='image' value='report/${filePath}/${fileName}' flowId='${test.creevey.workerId}']`,
36
+ );
37
+ });
38
+ });
39
+
40
+ // Output failed test as passed due TC don't support retry mechanic
41
+ // https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
42
+
43
+ if (test.creevey.willRetry)
44
+ console.log(
45
+ `##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`,
46
+ );
47
+ else
48
+ console.log(
49
+ `##teamcity[testFailed name='${this.escape(test.fullTitle())}' message='${this.escape(
50
+ error.message,
51
+ )}' details='${this.escape(error.stack ?? '')}' flowId='${test.creevey.workerId}']`,
52
+ );
53
+ });
54
+ }
55
+
56
+ private escape = (str: string): string => {
57
+ if (!str) return '';
58
+ return (
59
+ str
60
+ .toString()
61
+ // eslint-disable-next-line no-control-regex
62
+ .replace(/\x1B.*?m/g, '')
63
+ .replace(/\|/g, '||')
64
+ .replace(/\n/g, '|n')
65
+ .replace(/\r/g, '|r')
66
+ .replace(/\[/g, '|[')
67
+ .replace(/\]/g, '|]')
68
+ .replace(/\u0085/g, '|x')
69
+ .replace(/\u2028/g, '|l')
70
+ .replace(/\u2029/g, '|p')
71
+ .replace(/'/g, "|'")
72
+ );
73
+ };
74
+ }
@@ -1,4 +1,4 @@
1
- import { Args } from '@storybook/csf';
1
+ import type { Args } from 'storybook/internal/types';
2
2
  import chalk from 'chalk';
3
3
  import http from 'http';
4
4
  import https from 'https';
@@ -19,12 +19,12 @@ import {
19
19
  StorybookGlobals,
20
20
  StoryInput,
21
21
  StoriesRaw,
22
- Options,
22
+ WorkerOptions,
23
23
  ServerTest,
24
24
  StorybookEvents,
25
25
  } from '../../types.js';
26
26
  import { colors, logger } from '../logger.js';
27
- import { subscribeOn } from '../messages.js';
27
+ import { emitWorkerMessage, subscribeOn } from '../messages.js';
28
28
  import { getTestPath, isShuttingDown, runSequence } from '../utils.js';
29
29
  import {
30
30
  appendIframePath,
@@ -97,7 +97,7 @@ async function buildWebdriver(
97
97
  browser: string,
98
98
  gridUrl: string,
99
99
  config: Config,
100
- options: Options,
100
+ options: WorkerOptions,
101
101
  ): Promise<WebDriver | null> {
102
102
  const browserConfig = config.browsers[browser] as BrowserConfigObject;
103
103
  const { /*customizeBuilder,*/ seleniumCapabilities, browserName } = browserConfig;
@@ -249,7 +249,7 @@ export class InternalBrowser {
249
249
  const rects = await this.#browser.executeScript<
250
250
  { elementRect: ElementRect; windowRect: ElementRect } | undefined
251
251
  >(function (selector: string): { elementRect: ElementRect; windowRect: ElementRect } | undefined {
252
- window.scrollTo(0, 0); // TODO Maybe we should remove same code from `resetMousePosition`
252
+ window.scrollTo(0, 0);
253
253
  // eslint-disable-next-line no-var
254
254
  var element = document.querySelector(selector);
255
255
  if (!element) return;
@@ -266,9 +266,7 @@ export class InternalBrowser {
266
266
  },
267
267
  // NOTE page_Offset is used only for IE9-11
268
268
  windowRect: {
269
- // eslint-disable-next-line @typescript-eslint/no-deprecated
270
269
  top: Math.round(window.scrollY || window.pageYOffset),
271
- // eslint-disable-next-line @typescript-eslint/no-deprecated
272
270
  left: Math.round(window.scrollX || window.pageXOffset),
273
271
  width: window.innerWidth,
274
272
  height: window.innerHeight,
@@ -297,12 +295,10 @@ export class InternalBrowser {
297
295
  // ? context
298
296
  // ? await context.captureElementScreenshot(await element.getId())
299
297
  // : await browser.findElement(By.css(captureElement)).takeScreenshot()
300
- // : // TODO pointer-events: none, need to research
301
- // await takeCompositeScreenshot(browser, windowRect, elementRect);
298
+ // : await takeCompositeScreenshot(browser, windowRect, elementRect);
302
299
  screenshot = isFitIntoViewport
303
300
  ? await this.#browser.findElement(By.css(captureElement)).takeScreenshot()
304
- : // TODO pointer-events: none, need to research
305
- await this.takeCompositeScreenshot(windowRect, elementRect);
301
+ : await this.takeCompositeScreenshot(windowRect, elementRect);
306
302
 
307
303
  logger().debug(`${chalk.cyan(captureElement)} is captured`);
308
304
  }
@@ -377,12 +373,32 @@ export class InternalBrowser {
377
373
  }
378
374
 
379
375
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
380
- const stories = await this.#browser.executeAsyncScript<StoriesRaw | undefined>(function (
381
- callback: (stories: StoriesRaw | undefined) => void,
376
+ const result = await this.#browser.executeAsyncScript<
377
+ [error?: { message: string; stack?: string } | null, stories?: StoriesRaw]
378
+ >(function (
379
+ callback: (response: [error?: { message: string; stack?: string } | null, stories?: StoriesRaw]) => void,
382
380
  ) {
383
- void window.__CREEVEY_GET_STORIES__().then(callback);
381
+ window
382
+ .__CREEVEY_GET_STORIES__()
383
+ .then((stories) => {
384
+ callback([null, stories]);
385
+ })
386
+ .catch((error: unknown) => {
387
+ const errorInfo = {
388
+ message: error instanceof Error ? error.message : String(error),
389
+ stack: error instanceof Error ? error.stack : undefined,
390
+ };
391
+ callback([errorInfo]);
392
+ });
384
393
  });
385
394
 
395
+ const [error, stories] = result;
396
+
397
+ if (error) {
398
+ const errorObj = new Error(error.message);
399
+ if (error.stack) errorObj.stack = error.stack;
400
+ throw errorObj;
401
+ }
386
402
  if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
387
403
 
388
404
  return stories;
@@ -410,17 +426,24 @@ export class InternalBrowser {
410
426
  browserName: string,
411
427
  gridUrl: string,
412
428
  config: Config,
413
- options: Options,
429
+ options: WorkerOptions,
414
430
  ): Promise<InternalBrowser | null> {
415
431
  const browserConfig = config.browsers[browserName] as BrowserConfigObject;
416
- const { storybookUrl: address = config.storybookUrl, limit, viewport, _storybookGlobals } = browserConfig;
432
+ const {
433
+ storybookUrl: address = config.storybookUrl,
434
+ limit,
435
+ viewport,
436
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
437
+ _storybookGlobals,
438
+ storybookGlobals = _storybookGlobals,
439
+ } = browserConfig;
417
440
  void limit;
418
441
 
419
442
  const browser = await buildWebdriver(browserName, gridUrl, config, options);
420
443
 
421
444
  if (!browser) return null;
422
445
 
423
- const internalBrowser = new InternalBrowser(browser, options.port, _storybookGlobals);
446
+ const internalBrowser = new InternalBrowser(browser, options.port, storybookGlobals);
424
447
 
425
448
  try {
426
449
  if (isShuttingDown.current) return null;
@@ -430,7 +453,6 @@ export class InternalBrowser {
430
453
  gridUrl,
431
454
  viewport,
432
455
  storybookUrl: address,
433
- resolveStorybookUrl: config.resolveStorybookUrl,
434
456
  });
435
457
 
436
458
  return done ? internalBrowser : null;
@@ -453,13 +475,11 @@ export class InternalBrowser {
453
475
  gridUrl,
454
476
  viewport,
455
477
  storybookUrl,
456
- resolveStorybookUrl,
457
478
  }: {
458
479
  browserName: string;
459
480
  gridUrl: string;
460
481
  viewport?: { width: number; height: number };
461
482
  storybookUrl: string;
462
- resolveStorybookUrl?: () => Promise<string>;
463
483
  }): Promise<boolean> {
464
484
  const sessionId = (await this.#browser.getSession()).getId();
465
485
  let browserHost = '';
@@ -483,7 +503,7 @@ export class InternalBrowser {
483
503
  return await runSequence(
484
504
  [
485
505
  () => this.#browser.manage().setTimeouts({ pageLoad: 60000, script: 60000 }),
486
- () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
506
+ () => this.openStorybookPage(storybookUrl),
487
507
  () => this.waitForStorybook(),
488
508
  () => this.updateStorybookGlobals(),
489
509
  () => this.resolveCreeveyHost(),
@@ -500,25 +520,14 @@ export class InternalBrowser {
500
520
  );
501
521
  }
502
522
 
503
- private async openStorybookPage(storybookUrl: string, resolver?: () => Promise<string>): Promise<void> {
523
+ private async openStorybookPage(storybookUrl: string): Promise<void> {
504
524
  if (!LOCALHOST_REGEXP.test(storybookUrl)) {
505
525
  return this.#browser.get(appendIframePath(storybookUrl));
506
526
  }
507
527
 
508
528
  try {
509
- if (resolver) {
510
- logger().debug('Resolving storybook url with custom resolver');
511
-
512
- const resolvedUrl = await resolver();
513
-
514
- logger().debug(`Resolver storybook url ${resolvedUrl}`);
515
-
516
- await this.#browser.get(appendIframePath(resolvedUrl));
517
- } else {
518
- // TODO Pageload timeout 10s
519
- // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
520
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
521
- }
529
+ // NOTE: getUrlChecker already calls `browser.get` so we don't need another one
530
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
522
531
  } catch (error) {
523
532
  logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
524
533
  throw error;
@@ -554,7 +563,7 @@ export class InternalBrowser {
554
563
  }
555
564
 
556
565
  private async waitForStorybook(): Promise<void> {
557
- logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
566
+ logger().debug('Waiting for Storybook to initiate');
558
567
 
559
568
  const isTimeout = await Promise.race([
560
569
  new Promise<boolean>((resolve) => {
@@ -567,9 +576,6 @@ export class InternalBrowser {
567
576
  do {
568
577
  // TODO Research a different way to ensure storybook is initiated
569
578
  wait = await this.#browser.executeScript<boolean>(function (SET_GLOBALS: string): boolean {
570
- // TODO Maybe use
571
- // import { global } from '@storybook/global';
572
- // global.IS_STORYBOOK
573
579
  if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
574
580
  if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
575
581
  return false;
@@ -579,8 +585,7 @@ export class InternalBrowser {
579
585
  })(),
580
586
  ]);
581
587
 
582
- // TODO Change the message to describe a reason why it might happen
583
- if (isTimeout) throw new Error('Failed to wait `setStories` event');
588
+ if (isTimeout) throw new Error('Failed to wait Storybook init');
584
589
  }
585
590
 
586
591
  private async updateStorybookGlobals(): Promise<void> {
@@ -593,7 +598,9 @@ export class InternalBrowser {
593
598
  }
594
599
 
595
600
  private async resolveCreeveyHost(): Promise<void> {
596
- const addresses = getAddresses();
601
+ const storybookUrl = await this.#browser.getCurrentUrl();
602
+ const storybookHost = new URL(storybookUrl).hostname;
603
+ const addresses = [storybookHost, ...getAddresses()];
597
604
 
598
605
  this.#serverHost = await this.#browser.executeAsyncScript(
599
606
  function (hosts: string[], port: number, callback: (host?: string | null) => void) {
@@ -633,6 +640,7 @@ export class InternalBrowser {
633
640
  private async updateBrowserGlobalVariables() {
634
641
  await this.#browser.executeScript(
635
642
  function (workerId: number, creeveyHost: string, creeveyPort: number) {
643
+ window.__CREEVEY_ENV__ = true;
636
644
  window.__CREEVEY_WORKER_ID__ = workerId;
637
645
  window.__CREEVEY_SERVER_HOST__ = creeveyHost;
638
646
  window.__CREEVEY_SERVER_PORT__ = creeveyPort;
@@ -831,9 +839,18 @@ export class InternalBrowser {
831
839
  private keepAlive(): void {
832
840
  this.#keepAliveInterval = setInterval(() => {
833
841
  // NOTE Simple way to keep session alive
834
- void this.#browser.getCurrentUrl().then((url) => {
835
- logger().debug('current url', chalk.magenta(url));
836
- });
842
+ void this.#browser
843
+ .getCurrentUrl()
844
+ .then((url) => {
845
+ logger().debug('current url', chalk.magenta(url));
846
+ })
847
+ .catch((error: unknown) => {
848
+ logger().error(error);
849
+ emitWorkerMessage({
850
+ type: 'error',
851
+ payload: { subtype: 'browser', error: 'Failed to ping browser' },
852
+ });
853
+ });
837
854
  }, 10 * 1000);
838
855
  }
839
856
  }
@@ -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
  }