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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/AUTHORS +2 -0
  2. package/CHANGELOG.md +281 -0
  3. package/README.md +19 -41
  4. package/dist/client/addon/components/Addon.js +18 -8
  5. package/dist/client/addon/components/Addon.js.map +1 -1
  6. package/dist/client/addon/components/Panel.js +2 -2
  7. package/dist/client/addon/components/Panel.js.map +1 -1
  8. package/dist/client/addon/components/TestSelect.js +2 -2
  9. package/dist/client/addon/components/TestSelect.js.map +1 -1
  10. package/dist/client/addon/components/Tools.js +19 -9
  11. package/dist/client/addon/components/Tools.js.map +1 -1
  12. package/dist/client/addon/controller.d.ts +1 -1
  13. package/dist/client/addon/controller.js +3 -3
  14. package/dist/client/addon/controller.js.map +1 -1
  15. package/dist/client/addon/decorator.d.ts +1 -1
  16. package/dist/client/addon/makeDecorator.d.ts +9 -0
  17. package/dist/client/addon/makeDecorator.js +48 -0
  18. package/dist/client/addon/makeDecorator.js.map +1 -0
  19. package/dist/client/addon/manager.js +38 -39
  20. package/dist/client/addon/manager.js.map +1 -1
  21. package/dist/client/addon/preset.d.ts +0 -1
  22. package/dist/client/addon/preset.js +3 -2
  23. package/dist/client/addon/preset.js.map +1 -1
  24. package/dist/client/addon/preview.d.ts +1 -1
  25. package/dist/client/addon/withCreevey.d.ts +5 -3
  26. package/dist/client/addon/withCreevey.js +5 -20
  27. package/dist/client/addon/withCreevey.js.map +1 -1
  28. package/dist/client/shared/components/ImagesView/BlendView.d.ts +2 -2
  29. package/dist/client/shared/components/ImagesView/BlendView.js +18 -8
  30. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  31. package/dist/client/shared/components/ImagesView/ImagesView.js +1 -1
  32. package/dist/client/shared/components/ImagesView/ImagesView.js.map +1 -1
  33. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +2 -2
  34. package/dist/client/shared/components/ImagesView/SideBySideView.js +19 -9
  35. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  36. package/dist/client/shared/components/ImagesView/SlideView.d.ts +2 -2
  37. package/dist/client/shared/components/ImagesView/SlideView.js +19 -9
  38. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  39. package/dist/client/shared/components/ImagesView/SwapView.d.ts +2 -2
  40. package/dist/client/shared/components/ImagesView/SwapView.js +19 -9
  41. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  42. package/dist/client/shared/components/ImagesView/common.d.ts +1 -1
  43. package/dist/client/shared/components/PageFooter/PageFooter.js +1 -1
  44. package/dist/client/shared/components/PageFooter/PageFooter.js.map +1 -1
  45. package/dist/client/shared/components/PageFooter/Paging.js +1 -1
  46. package/dist/client/shared/components/PageFooter/Paging.js.map +1 -1
  47. package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +2 -2
  48. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -1
  49. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  50. package/dist/client/shared/components/PageHeader/PageHeader.js +34 -13
  51. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  52. package/dist/client/shared/components/ResultsPage.d.ts +2 -2
  53. package/dist/client/shared/components/ResultsPage.js +22 -10
  54. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  55. package/dist/client/shared/creeveyClientApi.js +18 -1
  56. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  57. package/dist/client/shared/helpers.d.ts +1 -3
  58. package/dist/client/shared/helpers.js +4 -19
  59. package/dist/client/shared/helpers.js.map +1 -1
  60. package/dist/client/web/CreeveyApp.d.ts +1 -0
  61. package/dist/client/web/CreeveyApp.js +22 -9
  62. package/dist/client/web/CreeveyApp.js.map +1 -1
  63. package/dist/client/web/CreeveyContext.d.ts +1 -0
  64. package/dist/client/web/CreeveyContext.js +18 -7
  65. package/dist/client/web/CreeveyContext.js.map +1 -1
  66. package/dist/client/web/CreeveyLoader.d.ts +1 -1
  67. package/dist/client/web/CreeveyLoader.js +3 -3
  68. package/dist/client/web/CreeveyLoader.js.map +1 -1
  69. package/dist/client/web/CreeveyView/SideBar/Checkbox.d.ts +4 -4
  70. package/dist/client/web/CreeveyView/SideBar/Checkbox.js +36 -6
  71. package/dist/client/web/CreeveyView/SideBar/Checkbox.js.map +1 -1
  72. package/dist/client/web/CreeveyView/SideBar/Search.js +18 -8
  73. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  74. package/dist/client/web/CreeveyView/SideBar/SideBar.js +26 -12
  75. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  76. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +28 -17
  77. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  78. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +32 -12
  79. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  80. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +6 -6
  81. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +20 -11
  82. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  83. package/dist/client/web/CreeveyView/SideBar/TestLink.js +20 -11
  84. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  85. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +2 -2
  86. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js +2 -2
  87. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js.map +1 -1
  88. package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +2 -2
  89. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +3 -2
  90. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  91. package/dist/client/web/CreeveyView/SideBar/Toggle.js +1 -1
  92. package/dist/client/web/CreeveyView/SideBar/Toggle.js.map +1 -1
  93. package/dist/client/web/KeyboardEventsContext.js +17 -7
  94. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  95. package/dist/client/web/assets/index-CtSq3IhG.js +518 -0
  96. package/dist/client/web/index.html +1 -1
  97. package/dist/client/web/index.js +26 -11
  98. package/dist/client/web/index.js.map +1 -1
  99. package/dist/client/web/themes.d.ts +2 -0
  100. package/dist/client/web/themes.js +22 -0
  101. package/dist/client/web/themes.js.map +1 -0
  102. package/dist/creevey.d.ts +1 -1
  103. package/dist/creevey.js +122 -41
  104. package/dist/creevey.js.map +1 -1
  105. package/dist/index.d.ts +1 -0
  106. package/dist/playwright/generator.d.ts +25 -0
  107. package/dist/playwright/generator.js +243 -0
  108. package/dist/playwright/generator.js.map +1 -0
  109. package/dist/playwright/helpers.d.ts +2 -0
  110. package/dist/playwright/helpers.js +29 -0
  111. package/dist/playwright/helpers.js.map +1 -0
  112. package/dist/playwright/reporter.d.ts +83 -0
  113. package/dist/playwright/reporter.js +334 -0
  114. package/dist/playwright/reporter.js.map +1 -0
  115. package/dist/playwright/setup.d.ts +3 -0
  116. package/dist/playwright/setup.js +72 -0
  117. package/dist/playwright/setup.js.map +1 -0
  118. package/dist/playwright.d.ts +1 -0
  119. package/dist/playwright.js +3 -1
  120. package/dist/playwright.js.map +1 -1
  121. package/dist/server/compare.d.ts +18 -0
  122. package/dist/server/compare.js +182 -0
  123. package/dist/server/compare.js.map +1 -0
  124. package/dist/server/config.d.ts +3 -3
  125. package/dist/server/config.js +75 -8
  126. package/dist/server/config.js.map +1 -1
  127. package/dist/server/connection.d.ts +3 -0
  128. package/dist/server/connection.js +28 -0
  129. package/dist/server/connection.js.map +1 -0
  130. package/dist/server/docker.d.ts +1 -1
  131. package/dist/server/docker.js +54 -32
  132. package/dist/server/docker.js.map +1 -1
  133. package/dist/server/index.d.ts +2 -2
  134. package/dist/server/index.js +161 -64
  135. package/dist/server/index.js.map +1 -1
  136. package/dist/server/master/api.d.ts +11 -6
  137. package/dist/server/master/api.js +88 -25
  138. package/dist/server/master/api.js.map +1 -1
  139. package/dist/server/master/handlers/capture-handler.d.ts +5 -0
  140. package/dist/server/master/handlers/capture-handler.js +25 -0
  141. package/dist/server/master/handlers/capture-handler.js.map +1 -0
  142. package/dist/server/master/handlers/index.d.ts +4 -0
  143. package/dist/server/master/handlers/index.js +21 -0
  144. package/dist/server/master/handlers/index.js.map +1 -0
  145. package/dist/server/master/handlers/ping-handler.d.ts +2 -0
  146. package/dist/server/master/handlers/ping-handler.js +8 -0
  147. package/dist/server/master/handlers/ping-handler.js.map +1 -0
  148. package/dist/server/master/handlers/static-handler.d.ts +1 -0
  149. package/dist/server/master/handlers/static-handler.js +20 -0
  150. package/dist/server/master/handlers/static-handler.js.map +1 -0
  151. package/dist/server/master/handlers/stories-handler.d.ts +4 -0
  152. package/dist/server/master/handlers/stories-handler.js +24 -0
  153. package/dist/server/master/handlers/stories-handler.js.map +1 -0
  154. package/dist/server/master/master.js +7 -24
  155. package/dist/server/master/master.js.map +1 -1
  156. package/dist/server/master/pool.d.ts +1 -0
  157. package/dist/server/master/pool.js +5 -3
  158. package/dist/server/master/pool.js.map +1 -1
  159. package/dist/server/master/queue.d.ts +1 -1
  160. package/dist/server/master/queue.js +14 -6
  161. package/dist/server/master/queue.js.map +1 -1
  162. package/dist/server/master/runner.d.ts +6 -6
  163. package/dist/server/master/runner.js +98 -130
  164. package/dist/server/master/runner.js.map +1 -1
  165. package/dist/server/master/server.d.ts +1 -1
  166. package/dist/server/master/server.js +193 -88
  167. package/dist/server/master/server.js.map +1 -1
  168. package/dist/server/master/start.d.ts +1 -2
  169. package/dist/server/master/start.js +13 -29
  170. package/dist/server/master/start.js.map +1 -1
  171. package/dist/server/master/testsManager.d.ts +81 -0
  172. package/dist/server/master/testsManager.js +282 -0
  173. package/dist/server/master/testsManager.js.map +1 -0
  174. package/dist/server/playwright/docker-file.d.ts +1 -1
  175. package/dist/server/playwright/docker-file.js +17 -8
  176. package/dist/server/playwright/docker-file.js.map +1 -1
  177. package/dist/server/playwright/docker.d.ts +2 -1
  178. package/dist/server/playwright/docker.js +10 -2
  179. package/dist/server/playwright/docker.js.map +1 -1
  180. package/dist/server/playwright/index-source.mjs +16 -0
  181. package/dist/server/playwright/internal.d.ts +7 -7
  182. package/dist/server/playwright/internal.js +137 -79
  183. package/dist/server/playwright/internal.js.map +1 -1
  184. package/dist/server/playwright/webdriver.d.ts +3 -3
  185. package/dist/server/playwright/webdriver.js +0 -6
  186. package/dist/server/playwright/webdriver.js.map +1 -1
  187. package/dist/server/providers/browser.js +4 -3
  188. package/dist/server/providers/browser.js.map +1 -1
  189. package/dist/server/providers/hybrid.js +2 -2
  190. package/dist/server/providers/hybrid.js.map +1 -1
  191. package/dist/server/report.d.ts +10 -0
  192. package/dist/server/report.js +45 -0
  193. package/dist/server/report.js.map +1 -0
  194. package/dist/server/reporters/creevey.d.ts +7 -0
  195. package/dist/server/reporters/creevey.js +63 -0
  196. package/dist/server/reporters/creevey.js.map +1 -0
  197. package/dist/server/reporters/index.d.ts +2 -0
  198. package/dist/server/reporters/index.js +16 -0
  199. package/dist/server/reporters/index.js.map +1 -0
  200. package/dist/server/reporters/junit.d.ts +16 -0
  201. package/dist/server/reporters/junit.js +167 -0
  202. package/dist/server/reporters/junit.js.map +1 -0
  203. package/dist/server/reporters/teamcity.d.ts +7 -0
  204. package/dist/server/reporters/teamcity.js +60 -0
  205. package/dist/server/reporters/teamcity.js.map +1 -0
  206. package/dist/server/selenium/internal.d.ts +3 -3
  207. package/dist/server/selenium/internal.js +48 -34
  208. package/dist/server/selenium/internal.js.map +1 -1
  209. package/dist/server/selenium/selenoid.js +12 -6
  210. package/dist/server/selenium/selenoid.js.map +1 -1
  211. package/dist/server/selenium/webdriver.d.ts +3 -3
  212. package/dist/server/selenium/webdriver.js +4 -8
  213. package/dist/server/selenium/webdriver.js.map +1 -1
  214. package/dist/server/shutdown.d.ts +1 -0
  215. package/dist/server/shutdown.js +23 -0
  216. package/dist/server/shutdown.js.map +1 -0
  217. package/dist/server/stories.d.ts +0 -1
  218. package/dist/server/stories.js +0 -12
  219. package/dist/server/stories.js.map +1 -1
  220. package/dist/server/telemetry.js +3 -3
  221. package/dist/server/telemetry.js.map +1 -1
  222. package/dist/server/testsFiles/parser.js +45 -5
  223. package/dist/server/testsFiles/parser.js.map +1 -1
  224. package/dist/server/utils.d.ts +23 -0
  225. package/dist/server/utils.js +113 -13
  226. package/dist/server/utils.js.map +1 -1
  227. package/dist/server/webdriver.d.ts +1 -1
  228. package/dist/server/worker/context.d.ts +3 -0
  229. package/dist/server/worker/context.js +15 -0
  230. package/dist/server/worker/context.js.map +1 -0
  231. package/dist/server/worker/match-image.d.ts +8 -12
  232. package/dist/server/worker/match-image.js +11 -178
  233. package/dist/server/worker/match-image.js.map +1 -1
  234. package/dist/server/worker/start.d.ts +2 -2
  235. package/dist/server/worker/start.js +27 -63
  236. package/dist/server/worker/start.js.map +1 -1
  237. package/dist/shared/index.d.ts +1 -1
  238. package/dist/shared/index.js +9 -7
  239. package/dist/shared/index.js.map +1 -1
  240. package/dist/types.d.ts +84 -43
  241. package/dist/types.js +65 -1
  242. package/dist/types.js.map +1 -1
  243. package/docs/cli.md +80 -0
  244. package/docs/config.md +179 -165
  245. package/docs/examples/playwright-reporer/playwright.config.ts +37 -0
  246. package/docs/migration-0.9-to-0.10.md +144 -0
  247. package/docs/playwright-reporter.md +357 -0
  248. package/docs/storybook.md +60 -0
  249. package/docs/tests.md +50 -45
  250. package/package.json +78 -83
  251. package/playwright.config.mts +46 -0
  252. package/src/client/addon/components/Addon.tsx +1 -1
  253. package/src/client/addon/components/Panel.tsx +2 -2
  254. package/src/client/addon/components/TestSelect.tsx +2 -2
  255. package/src/client/addon/components/Tools.tsx +2 -2
  256. package/src/client/addon/controller.ts +4 -4
  257. package/src/client/addon/makeDecorator.ts +69 -0
  258. package/src/client/addon/manager.ts +38 -37
  259. package/src/client/addon/preset.ts +2 -1
  260. package/src/client/addon/withCreevey.ts +10 -18
  261. package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
  262. package/src/client/shared/components/ImagesView/ImagesView.tsx +1 -1
  263. package/src/client/shared/components/ImagesView/SideBySideView.tsx +2 -2
  264. package/src/client/shared/components/ImagesView/SlideView.tsx +2 -2
  265. package/src/client/shared/components/ImagesView/SwapView.tsx +2 -2
  266. package/src/client/shared/components/ImagesView/common.ts +1 -1
  267. package/src/client/shared/components/PageFooter/PageFooter.tsx +1 -1
  268. package/src/client/shared/components/PageFooter/Paging.tsx +1 -1
  269. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -1
  270. package/src/client/shared/components/PageHeader/PageHeader.tsx +23 -7
  271. package/src/client/shared/components/ResultsPage.tsx +6 -4
  272. package/src/client/shared/creeveyClientApi.ts +19 -1
  273. package/src/client/shared/helpers.ts +4 -24
  274. package/src/client/web/CreeveyApp.tsx +5 -2
  275. package/src/client/web/CreeveyContext.tsx +2 -0
  276. package/src/client/web/CreeveyLoader.tsx +2 -2
  277. package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +3 -3
  278. package/src/client/web/CreeveyView/SideBar/Search.tsx +1 -1
  279. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +11 -6
  280. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +21 -19
  281. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +20 -5
  282. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +10 -8
  283. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +9 -7
  284. package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +2 -2
  285. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +3 -2
  286. package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -1
  287. package/src/client/web/index.tsx +10 -5
  288. package/src/client/web/themes.ts +24 -0
  289. package/src/creevey.ts +92 -38
  290. package/src/playwright/generator.ts +322 -0
  291. package/src/playwright/helpers.ts +31 -0
  292. package/src/playwright/reporter.ts +381 -0
  293. package/src/playwright/setup.ts +84 -0
  294. package/src/playwright.ts +1 -0
  295. package/src/server/compare.ts +260 -0
  296. package/src/server/config.ts +52 -9
  297. package/src/server/connection.ts +26 -0
  298. package/src/server/docker.ts +62 -34
  299. package/src/server/index.ts +161 -79
  300. package/src/server/master/api.ts +94 -28
  301. package/src/server/master/handlers/capture-handler.ts +20 -0
  302. package/src/server/master/handlers/index.ts +4 -0
  303. package/src/server/master/handlers/ping-handler.ts +6 -0
  304. package/src/server/master/handlers/static-handler.ts +16 -0
  305. package/src/server/master/handlers/stories-handler.ts +20 -0
  306. package/src/server/master/master.ts +10 -27
  307. package/src/server/master/pool.ts +7 -3
  308. package/src/server/master/queue.ts +21 -7
  309. package/src/server/master/runner.ts +123 -134
  310. package/src/server/master/server.ts +214 -101
  311. package/src/server/master/start.ts +19 -41
  312. package/src/server/master/testsManager.ts +316 -0
  313. package/src/server/playwright/docker-file.ts +20 -8
  314. package/src/server/playwright/docker.ts +16 -3
  315. package/src/server/playwright/index-source.mjs +16 -0
  316. package/src/server/playwright/internal.ts +169 -96
  317. package/src/server/playwright/webdriver.ts +4 -10
  318. package/src/server/providers/browser.ts +4 -3
  319. package/src/server/providers/hybrid.ts +2 -3
  320. package/src/server/report.ts +51 -0
  321. package/src/server/reporters/creevey.ts +71 -0
  322. package/src/server/reporters/index.ts +11 -0
  323. package/src/server/reporters/junit.ts +207 -0
  324. package/src/server/reporters/teamcity.ts +74 -0
  325. package/src/server/selenium/internal.ts +62 -45
  326. package/src/server/selenium/selenoid.ts +13 -6
  327. package/src/server/selenium/webdriver.ts +8 -12
  328. package/src/server/shutdown.ts +19 -0
  329. package/src/server/stories.ts +1 -12
  330. package/src/server/telemetry.ts +3 -3
  331. package/src/server/testsFiles/parser.ts +52 -4
  332. package/src/server/utils.ts +123 -14
  333. package/src/server/webdriver.ts +1 -1
  334. package/src/server/worker/context.ts +14 -0
  335. package/src/server/worker/match-image.ts +16 -248
  336. package/src/server/worker/start.ts +32 -75
  337. package/src/shared/index.ts +10 -8
  338. package/src/types.ts +91 -58
  339. package/types/global.d.ts +1 -0
  340. package/dist/client/web/assets/index-BE9CL5_G.js +0 -591
  341. package/dist/server/reporter.d.ts +0 -26
  342. package/dist/server/reporter.js +0 -108
  343. package/dist/server/reporter.js.map +0 -1
  344. package/dist/server/update.d.ts +0 -2
  345. package/dist/server/update.js +0 -53
  346. package/dist/server/update.js.map +0 -1
  347. package/src/server/reporter.ts +0 -139
  348. package/src/server/update.ts +0 -74
@@ -1,22 +1,39 @@
1
- import { Browser, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
1
+ import path from 'path';
2
+ import assert from 'assert';
3
+ import {
4
+ Browser,
5
+ BrowserContext,
6
+ BrowserContextOptions,
7
+ BrowserType,
8
+ Page,
9
+ chromium,
10
+ firefox,
11
+ webkit,
12
+ } from 'playwright-core';
2
13
  import chalk from 'chalk';
3
14
  import { v4 } from 'uuid';
15
+ import Logger from 'loglevel';
4
16
  import prefix from 'loglevel-plugin-prefix';
17
+ import type { Args } from 'storybook/internal/types';
5
18
  import {
6
19
  BrowserConfigObject,
7
20
  Config,
8
- Options,
9
21
  StoriesRaw,
10
22
  StoryInput,
11
23
  StorybookEvents,
12
24
  StorybookGlobals,
13
- noop,
25
+ WorkerOptions,
14
26
  } from '../../types';
15
- import { subscribeOn } from '../messages';
16
27
  import { appendIframePath, getAddresses, LOCALHOST_REGEXP, resolveStorybookUrl, storybookRootID } from '../webdriver';
17
- import { isShuttingDown, runSequence } from '../utils';
28
+ import { getCreeveyCache, isShuttingDown, resolvePlaywrightBrowserType, runSequence } from '../utils';
18
29
  import { colors, logger } from '../logger';
19
- import { Args } from '@storybook/csf';
30
+ import { removeWorkerContainer } from '../worker/context';
31
+
32
+ const browsers = {
33
+ chromium,
34
+ firefox,
35
+ webkit,
36
+ };
20
37
 
21
38
  async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser | null> {
22
39
  let timeout: NodeJS.Timeout | null = null;
@@ -49,25 +66,59 @@ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser |
49
66
  ]);
50
67
  }
51
68
 
69
+ async function tryCreateBrowserContext(
70
+ browser: Browser,
71
+ options: BrowserContextOptions,
72
+ ): Promise<{ context: BrowserContext; page: Page }> {
73
+ try {
74
+ const context = await browser.newContext(options);
75
+ const page = await context.newPage();
76
+ return { context, page };
77
+ } catch (error) {
78
+ if (error instanceof Error && error.message.includes('ffmpeg')) {
79
+ logger().warn('Failed to create browser context with video recording. Video recording will be disabled.');
80
+ logger().warn(error);
81
+ const context = await browser.newContext({
82
+ ...options,
83
+ recordVideo: undefined,
84
+ });
85
+ const page = await context.newPage();
86
+ return { context, page };
87
+ }
88
+ throw error;
89
+ }
90
+ }
91
+
52
92
  export class InternalBrowser {
53
93
  #isShuttingDown = false;
54
94
  #browser: Browser;
95
+ #context: BrowserContext;
55
96
  #page: Page;
97
+ #traceDir: string;
56
98
  #sessionId: string = v4();
57
99
  #serverHost: string | null = null;
58
100
  #serverPort: number;
101
+ #debug: boolean;
59
102
  #storybookGlobals?: StorybookGlobals;
60
- #unsubscribe: () => void = noop;
61
- constructor(browser: Browser, page: Page, port: number, storybookGlobals?: StorybookGlobals) {
103
+ constructor(
104
+ browser: Browser,
105
+ context: BrowserContext,
106
+ page: Page,
107
+ traceDir: string,
108
+ port: number,
109
+ debug = false,
110
+ storybookGlobals?: StorybookGlobals,
111
+ ) {
62
112
  this.#browser = browser;
113
+ this.#context = context;
63
114
  this.#page = page;
115
+ this.#traceDir = traceDir;
64
116
  this.#serverPort = port;
117
+ this.#debug = debug;
65
118
  this.#storybookGlobals = storybookGlobals;
66
- this.#unsubscribe = subscribeOn('shutdown', () => {
67
- void this.closeBrowser();
68
- });
69
119
  }
70
120
 
121
+ // TODO Expose #browser and #context in tests
71
122
  get browser() {
72
123
  return this.#page;
73
124
  }
@@ -80,32 +131,41 @@ export class InternalBrowser {
80
131
  if (this.#isShuttingDown) return;
81
132
 
82
133
  this.#isShuttingDown = true;
83
- this.#unsubscribe();
84
134
 
85
- try {
86
- await this.#page.close();
87
- await this.#browser.close();
88
- } catch (_) {
89
- /* noop */
135
+ const teardown = [
136
+ this.#debug ? () => this.#context.tracing.stop({ path: path.join(this.#traceDir, 'trace.zip') }) : null,
137
+ () => this.#page.close(),
138
+ this.#debug ? () => this.#page.video()?.saveAs(path.join(this.#traceDir, 'video.webm')) : null,
139
+ () => this.#context.close(),
140
+ () => this.#browser.close(),
141
+ () => removeWorkerContainer(),
142
+ ];
143
+
144
+ for (const fn of teardown) {
145
+ try {
146
+ if (fn) await fn();
147
+ } catch {
148
+ /* noop */
149
+ }
90
150
  }
91
151
  }
92
152
 
93
153
  async takeScreenshot(captureElement?: string | null, ignoreElements?: string | string[] | null): Promise<Buffer> {
94
- // TODO Implement features from selenium `takeScreenshot`
95
- // TODO Do we need scroll bar hack from selenium?
96
154
  const ignore = Array.isArray(ignoreElements) ? ignoreElements : ignoreElements ? [ignoreElements] : [];
97
155
  const mask = ignore.map((el) => this.#page.locator(el));
98
156
  if (captureElement) {
99
157
  const element = await this.#page.$(captureElement);
100
158
  if (!element) throw new Error(`Element with selector ${captureElement} not found`);
101
159
 
160
+ logger().debug(`Capturing ${chalk.cyan(captureElement)} element`);
102
161
  return element.screenshot({
162
+ style: ':root { overflow: hidden !important; }',
103
163
  animations: 'disabled',
104
164
  mask,
105
- style: ':root { overflow: hidden !important; }',
106
165
  });
107
166
  }
108
- return this.#page.screenshot({ animations: 'disabled', mask, fullPage: true });
167
+ logger().debug('Capturing viewport screenshot');
168
+ return this.#page.screenshot({ animations: 'disabled', mask });
109
169
  }
110
170
 
111
171
  waitForComplete(callback: (isCompleted: boolean) => void): void {
@@ -125,6 +185,7 @@ export class InternalBrowser {
125
185
  [id: string, shouldWaitForReady: boolean]
126
186
  >(
127
187
  ([id, shouldWaitForReady]) => {
188
+ // TODO: Don't use creevey related global variables, inline this function to simplify support
128
189
  if (typeof window.__CREEVEY_SELECT_STORY__ == 'undefined') {
129
190
  return [
130
191
  "Creevey can't switch story. This may happened if forget to add `creevey` addon to your storybook config, or storybook not loaded in browser due syntax error.",
@@ -157,58 +218,50 @@ export class InternalBrowser {
157
218
  );
158
219
  }
159
220
 
160
- async loadStoriesFromBrowser(retry = false): Promise<StoriesRaw> {
161
- try {
162
- const stories = await this.#page.evaluate<StoriesRaw | undefined>(() => window.__CREEVEY_GET_STORIES__());
221
+ async loadStoriesFromBrowser(): Promise<StoriesRaw> {
222
+ const stories = await this.#page.evaluate<StoriesRaw | undefined>(() => window.__CREEVEY_GET_STORIES__());
163
223
 
164
- if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
224
+ if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
165
225
 
166
- return stories;
167
- } catch (error) {
168
- // TODO Check how other solutions with playwright get stories from storybook
169
- if (retry) throw error;
170
- await new Promise((resolve) => setTimeout(resolve, 1000));
171
- // NOTE: Try one more time because of dynamic nature of vite and storybook
172
- return this.loadStoriesFromBrowser(true);
173
- }
226
+ return stories;
174
227
  }
175
228
 
176
229
  static async getBrowser(
177
230
  browserName: string,
178
231
  gridUrl: string,
179
232
  config: Config,
180
- options: Options,
233
+ options: WorkerOptions,
181
234
  ): Promise<InternalBrowser | null> {
182
235
  const browserConfig = config.browsers[browserName] as BrowserConfigObject;
183
236
  const {
184
237
  storybookUrl: address = config.storybookUrl,
185
238
  viewport,
239
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
186
240
  _storybookGlobals,
241
+ storybookGlobals = _storybookGlobals,
187
242
  seleniumCapabilities,
188
243
  playwrightOptions,
189
244
  } = browserConfig;
245
+ const parsedUrl = new URL(gridUrl);
246
+ const tracesDir = path.join(
247
+ playwrightOptions?.tracesDir ?? path.join(config.reportDir, 'traces'),
248
+ process.pid.toString(),
249
+ );
250
+ const cacheDir = await getCreeveyCache();
190
251
 
191
- let browser: Browser | null = null;
252
+ assert(cacheDir, "Couldn't get cache directory");
192
253
 
193
- if (new URL(gridUrl).protocol === 'ws:') {
194
- switch (browserConfig.browserName) {
195
- case 'chromium':
196
- browser = await tryConnect(chromium, gridUrl);
197
- break;
198
- case 'firefox':
199
- browser = await tryConnect(firefox, gridUrl);
200
- break;
201
- case 'webkit':
202
- browser = await tryConnect(webkit, gridUrl);
203
- break;
254
+ let browser: Browser | null = null;
204
255
 
205
- default:
206
- logger().error(
207
- `Unknown browser ${browserConfig.browserName}. Playwright supports browsers: chromium, firefox, webkit`,
208
- );
209
- }
256
+ if (parsedUrl.protocol === 'ws:') {
257
+ browser = await tryConnect(browsers[resolvePlaywrightBrowserType(browserConfig.browserName)], gridUrl);
258
+ } else if (parsedUrl.protocol === 'creevey:') {
259
+ browser = await browsers[resolvePlaywrightBrowserType(browserConfig.browserName)].launch({
260
+ ...playwrightOptions,
261
+ tracesDir: path.join(cacheDir, `${process.pid}`),
262
+ });
210
263
  } else {
211
- if (browserConfig.browserName != 'chrome') {
264
+ if (browserConfig.browserName !== 'chrome') {
212
265
  logger().error("Playwright's Selenium Grid feature supports only chrome browser");
213
266
  return null;
214
267
  }
@@ -216,26 +269,50 @@ export class InternalBrowser {
216
269
  process.env.SELENIUM_REMOTE_URL = gridUrl;
217
270
  process.env.SELENIUM_REMOTE_CAPABILITIES = JSON.stringify(seleniumCapabilities);
218
271
 
219
- browser = await chromium.launch(playwrightOptions);
272
+ browser = await chromium.launch({ ...playwrightOptions, tracesDir: path.join(cacheDir, `${process.pid}`) });
220
273
  }
221
274
 
222
275
  if (!browser) {
223
276
  return null;
224
277
  }
225
278
 
226
- const page = await browser.newPage();
279
+ const { context, page } = await tryCreateBrowserContext(browser, {
280
+ recordVideo: options.debug
281
+ ? {
282
+ dir: path.join(cacheDir, `${process.pid}`),
283
+ size: viewport,
284
+ }
285
+ : undefined,
286
+ screen: viewport,
287
+ viewport,
288
+ });
289
+ if (options.debug) {
290
+ await context.tracing.start(
291
+ Object.assign({ screenshots: true, snapshots: true, sources: true }, playwrightOptions?.trace),
292
+ );
293
+ }
227
294
 
228
- // TODO Add debug output
295
+ if (logger().getLevel() <= Logger.levels.DEBUG) {
296
+ page.on('console', (msg) => {
297
+ logger().debug(`Console message: ${msg.text()}`);
298
+ });
299
+ }
229
300
 
230
- const internalBrowser = new InternalBrowser(browser, page, options.port, _storybookGlobals);
301
+ const internalBrowser = new InternalBrowser(
302
+ browser,
303
+ context,
304
+ page,
305
+ tracesDir,
306
+ options.port,
307
+ options.debug,
308
+ storybookGlobals,
309
+ );
231
310
 
232
311
  try {
233
312
  if (isShuttingDown.current) return null;
234
313
  const done = await internalBrowser.init({
235
314
  browserName,
236
- viewport,
237
315
  storybookUrl: address,
238
- resolveStorybookUrl: config.resolveStorybookUrl,
239
316
  });
240
317
 
241
318
  return done ? internalBrowser : null;
@@ -252,17 +329,7 @@ export class InternalBrowser {
252
329
  }
253
330
  }
254
331
 
255
- private async init({
256
- browserName,
257
- viewport,
258
- storybookUrl,
259
- resolveStorybookUrl,
260
- }: {
261
- browserName: string;
262
- viewport?: { width: number; height: number };
263
- storybookUrl: string;
264
- resolveStorybookUrl?: () => Promise<string>;
265
- }) {
332
+ private async init({ browserName, storybookUrl }: { browserName: string; storybookUrl: string }) {
266
333
  const sessionId = this.#sessionId;
267
334
 
268
335
  prefix.apply(logger(), {
@@ -276,36 +343,26 @@ export class InternalBrowser {
276
343
 
277
344
  return await runSequence(
278
345
  [
279
- () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
346
+ () => this.openStorybookPage(storybookUrl),
280
347
  () => this.waitForStorybook(),
348
+ () => this.triggerViteReload(),
281
349
  () => this.updateStorybookGlobals(),
282
350
  () => this.resolveCreeveyHost(),
283
351
  () => this.updateBrowserGlobalVariables(),
284
- () => this.resizeViewport(viewport),
285
352
  ],
286
353
  () => !this.#isShuttingDown,
287
354
  );
288
355
  }
289
356
 
290
- private async openStorybookPage(storybookUrl: string, resolver?: () => Promise<string>): Promise<void> {
357
+ private async openStorybookPage(storybookUrl: string): Promise<void> {
291
358
  if (!LOCALHOST_REGEXP.test(storybookUrl)) {
292
359
  await this.#page.goto(appendIframePath(storybookUrl));
293
360
  return;
294
361
  }
295
362
 
296
363
  try {
297
- if (resolver) {
298
- logger().debug('Resolving storybook url with custom resolver');
299
-
300
- const resolvedUrl = await resolver();
301
-
302
- logger().debug(`Resolver storybook url ${resolvedUrl}`);
303
-
304
- await this.#page.goto(appendIframePath(resolvedUrl));
305
- } else {
306
- // TODO this.#page.setDefaultNavigationTimeout(10000);
307
- await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
308
- }
364
+ const resolvedUrl = await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url));
365
+ await this.#page.goto(resolvedUrl);
309
366
  } catch (error) {
310
367
  logger().error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
311
368
  throw error;
@@ -313,21 +370,23 @@ export class InternalBrowser {
313
370
  }
314
371
 
315
372
  private async checkUrl(url: string): Promise<boolean> {
373
+ const page = await this.#browser.newPage();
316
374
  try {
317
375
  logger().debug(`Opening ${chalk.magenta(url)} and checking the page source`);
318
- const response = await this.#page.goto(url, { waitUntil: 'commit' });
376
+ const response = await page.goto(url, { waitUntil: 'commit' });
319
377
  const source = await response?.text();
320
378
 
321
379
  logger().debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
322
380
  return source?.includes(`id="${storybookRootID}"`) ?? false;
323
381
  } catch {
324
382
  return false;
383
+ } finally {
384
+ await page.close();
325
385
  }
326
386
  }
327
387
 
328
388
  private async waitForStorybook(): Promise<void> {
329
- // TODO Duplicated code with selenium
330
- logger().debug('Waiting for `setStories` event to make sure that storybook is initiated');
389
+ logger().debug('Waiting for Storybook to initiate');
331
390
 
332
391
  const isTimeout = await Promise.race([
333
392
  new Promise<boolean>((resolve) => {
@@ -338,6 +397,7 @@ export class InternalBrowser {
338
397
  (async () => {
339
398
  let wait = true;
340
399
  do {
400
+ if (this.#page.isClosed()) return false;
341
401
  try {
342
402
  // TODO Research a different way to ensure storybook is initiated
343
403
  wait = await this.#page.evaluate((SET_GLOBALS: string) => {
@@ -347,14 +407,28 @@ export class InternalBrowser {
347
407
  }, StorybookEvents.SET_GLOBALS);
348
408
  } catch (e: unknown) {
349
409
  logger().debug('An error has been caught during the script:', e);
410
+ if (this.#page.isClosed()) throw e;
350
411
  }
412
+ if (wait) await new Promise((resolve) => setTimeout(resolve, 1000));
351
413
  } while (wait);
352
414
  return false;
353
415
  })(),
354
416
  ]);
355
417
 
356
- // TODO Change the message to describe a reason why it might happen
357
- if (isTimeout) throw new Error('Failed to wait `setStories` event');
418
+ if (isTimeout) throw new Error('Failed to wait Storybook init');
419
+ }
420
+
421
+ // TODO Doesn't work for some reason, maybe because of race-condition
422
+ private async triggerViteReload(): Promise<void> {
423
+ // NOTE: On the first load, Vite might try to optimize some dependencies and reload the page
424
+ // We need to trigger reload earlier to avoid unnecessary reloads further
425
+ try {
426
+ await this.#page.evaluate(async () => {
427
+ await window.__STORYBOOK_PREVIEW__.extract();
428
+ });
429
+ } catch {
430
+ await this.waitForStorybook();
431
+ }
358
432
  }
359
433
 
360
434
  private async updateStorybookGlobals(): Promise<void> {
@@ -367,7 +441,9 @@ export class InternalBrowser {
367
441
  }
368
442
 
369
443
  private async resolveCreeveyHost(): Promise<void> {
370
- const addresses = getAddresses();
444
+ const storybookUrl = this.#page.url();
445
+ const storybookHost = new URL(storybookUrl).hostname;
446
+ const addresses = [storybookHost, ...getAddresses()];
371
447
 
372
448
  this.#serverHost = await this.#page.evaluate(
373
449
  ([hosts, port]) => {
@@ -392,8 +468,10 @@ export class InternalBrowser {
392
468
  }
393
469
 
394
470
  private async updateBrowserGlobalVariables() {
471
+ logger().debug('Updating browser global variables');
395
472
  await this.#page.evaluate(
396
473
  ([workerId, creeveyHost, creeveyPort]) => {
474
+ window.__CREEVEY_ENV__ = true;
397
475
  window.__CREEVEY_WORKER_ID__ = workerId;
398
476
  window.__CREEVEY_SERVER_HOST__ = creeveyHost ?? 'localhost';
399
477
  window.__CREEVEY_SERVER_PORT__ = creeveyPort;
@@ -402,13 +480,8 @@ export class InternalBrowser {
402
480
  );
403
481
  }
404
482
 
405
- private async resizeViewport(viewport?: { width: number; height: number }): Promise<void> {
406
- if (!viewport) return;
407
-
408
- await this.#page.setViewportSize(viewport);
409
- }
410
-
411
483
  private async resetMousePosition(): Promise<void> {
484
+ logger().debug('Resetting mouse position to (0, 0)');
412
485
  await this.#page.mouse.move(0, 0);
413
486
  }
414
487
  }
@@ -1,6 +1,6 @@
1
1
  /// <reference types="../../../types/playwright-context" />
2
- import { Args } from '@storybook/csf';
3
- import { Config, Options, ServerTest, StoriesRaw, StoryInput } from '../../types';
2
+ import type { Args } from 'storybook/internal/types';
3
+ import { Config, ServerTest, StoriesRaw, StoryInput, WorkerOptions } from '../../types';
4
4
  import { logger } from '../logger';
5
5
  import { subscribeOn } from '../messages';
6
6
  import { CreeveyWebdriverBase } from '../webdriver';
@@ -11,8 +11,8 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
11
11
  #browserName: string;
12
12
  #gridUrl: string;
13
13
  #config: Config;
14
- #options: Options;
15
- constructor(browser: string, gridUrl: string, config: Config, options: Options) {
14
+ #options: WorkerOptions;
15
+ constructor(browser: string, gridUrl: string, config: Config, options: WorkerOptions) {
16
16
  super();
17
17
 
18
18
  this.#browserName = browser;
@@ -32,7 +32,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
32
32
 
33
33
  getSessionId(): Promise<string> {
34
34
  if (!this.#browser) {
35
- // TODO Describe the error
36
35
  throw new Error('Browser is not initialized');
37
36
  }
38
37
 
@@ -79,7 +78,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
79
78
 
80
79
  async loadStoriesFromBrowser(): Promise<StoriesRaw> {
81
80
  if (!this.#browser) {
82
- // TODO Describe the error
83
81
  throw new Error('Browser is not initialized');
84
82
  }
85
83
 
@@ -95,7 +93,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
95
93
  ignoreElements?: string | string[] | null,
96
94
  ): Promise<Buffer> {
97
95
  if (!this.#browser) {
98
- // TODO Describe the error
99
96
  throw new Error('Browser is not initialized');
100
97
  }
101
98
 
@@ -104,7 +101,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
104
101
 
105
102
  protected waitForComplete(callback: (isCompleted: boolean) => void): void {
106
103
  if (!this.#browser) {
107
- // TODO Describe the error
108
104
  throw new Error('Browser is not initialized');
109
105
  }
110
106
 
@@ -113,7 +109,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
113
109
 
114
110
  protected async selectStory(id: string, waitForReady?: boolean): Promise<boolean> {
115
111
  if (!this.#browser) {
116
- // TODO Describe the error
117
112
  throw new Error('Browser is not initialized');
118
113
  }
119
114
 
@@ -122,7 +117,6 @@ export class PlaywrightWebdriver extends CreeveyWebdriverBase {
122
117
 
123
118
  protected async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
124
119
  if (!this.#browser) {
125
- // TODO Describe the error
126
120
  throw new Error('Browser is not initialized');
127
121
  }
128
122
 
@@ -24,7 +24,7 @@ export const loadStories: StoriesProvider = async (_config, storiesListener, web
24
24
  oldTests.join('\n'),
25
25
  );
26
26
  unsubscribe();
27
- resolve(stories);
27
+ resolve(deserializeRawStories(stories));
28
28
  }
29
29
  });
30
30
  sendStoriesMessage(worker, { type: 'get' });
@@ -37,10 +37,11 @@ export const loadStories: StoriesProvider = async (_config, storiesListener, web
37
37
  } else {
38
38
  subscribeOn('stories', (message) => {
39
39
  if (message.type == 'get')
40
- emitStoriesMessage({ type: 'set', payload: { stories, oldTests: storiesWithOldTests } });
40
+ emitStoriesMessage({ type: 'set', payload: { stories: rawStories, oldTests: storiesWithOldTests } });
41
41
  if (message.type == 'update') storiesListener(new Map(message.payload));
42
42
  });
43
- const stories = deserializeRawStories((await webdriver?.loadStoriesFromBrowser()) ?? {});
43
+ const rawStories = (await webdriver?.loadStoriesFromBrowser()) ?? {};
44
+ const stories = deserializeRawStories(rawStories);
44
45
 
45
46
  const storiesWithOldTests: string[] = [];
46
47
 
@@ -1,5 +1,4 @@
1
- import chokidar from 'chokidar';
2
-
1
+ import { watch } from 'chokidar';
3
2
  import { loadStories as browserProvider } from './browser.js';
4
3
  import type { Config, CreeveyStoryParams, CreeveyStory, StoriesProvider } from '../../types.js';
5
4
  import { logger } from '../logger.js';
@@ -53,7 +52,7 @@ async function parseParams(
53
52
  const testFiles = readDirRecursive(config.testsDir).filter((file) => config.testsRegex?.test(file));
54
53
 
55
54
  if (listener) {
56
- chokidar.watch(testFiles).on('change', (filePath) => {
55
+ watch(testFiles).on('change', (filePath) => {
57
56
  logger().debug(`changed: ${filePath}`);
58
57
 
59
58
  // doesn't work, always returns {} due modules caching
@@ -0,0 +1,51 @@
1
+ import open from 'open';
2
+ import { Config } from '../types.js';
3
+ import { logger } from './logger.js';
4
+ import { TestsManager } from './master/testsManager.js';
5
+ import { start as startServer } from './master/server.js';
6
+ import { CreeveyApi } from './master/api.js';
7
+ import { shutdownWorkers } from './utils.js';
8
+
9
+ /**
10
+ * UI Update Mode implementation.
11
+ * This mode allows users to review and approve screenshots from the browser interface.
12
+ * It combines the functionality of both --ui and --update flags.
13
+ *
14
+ * @param config Creevey configuration
15
+ * @param port Port to run the server on
16
+ */
17
+ export function report(config: Config, reportDir: string, port: number): void {
18
+ logger().info('Starting UI Update Mode');
19
+
20
+ process.on('SIGINT', () => void shutdownWorkers());
21
+
22
+ const url = `http://localhost:${port}`;
23
+
24
+ // Initialize TestsManager with the configured directories
25
+ const testsManager = new TestsManager(config.screenDir, reportDir);
26
+
27
+ // Load tests from the report
28
+ const testsFromReport = testsManager.loadTestsFromReport();
29
+
30
+ if (Object.keys(testsFromReport).length === 0) {
31
+ logger().warn('No tests found in report. Run tests first to generate report data.');
32
+ return;
33
+ }
34
+
35
+ // Set tests in the manager
36
+ testsManager.updateTests(testsFromReport);
37
+
38
+ // Start API server with UI enabled
39
+ const resolveApi = startServer(reportDir, port, true);
40
+
41
+ // Initialize API
42
+ const api = new CreeveyApi(testsManager);
43
+
44
+ // Resolve the API for the server
45
+ resolveApi(api);
46
+
47
+ logger().info(`UI Update Mode started on ${url}`);
48
+ logger().info('You can now review and approve screenshots from the browser.');
49
+
50
+ void open(url);
51
+ }