creevey 0.9.1 → 0.10.0-beta.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 (259) hide show
  1. package/chromatic.config.json +5 -0
  2. package/dist/client/addon/components/Addon.d.ts +1 -0
  3. package/dist/client/addon/components/Addon.js.map +1 -1
  4. package/dist/client/addon/components/Icons.d.ts +1 -0
  5. package/dist/client/addon/components/Icons.js.map +1 -1
  6. package/dist/client/addon/components/Panel.d.ts +1 -0
  7. package/dist/client/addon/components/Panel.js.map +1 -1
  8. package/dist/client/addon/components/TestSelect.d.ts +1 -0
  9. package/dist/client/addon/components/TestSelect.js +4 -3
  10. package/dist/client/addon/components/TestSelect.js.map +1 -1
  11. package/dist/client/addon/components/Tools.d.ts +1 -0
  12. package/dist/client/addon/components/Tools.js +7 -8
  13. package/dist/client/addon/components/Tools.js.map +1 -1
  14. package/dist/client/addon/controller.d.ts +1 -1
  15. package/dist/client/addon/controller.js.map +1 -1
  16. package/dist/client/addon/decorator.d.ts +1 -1
  17. package/dist/client/addon/manager.js +3 -2
  18. package/dist/client/addon/manager.js.map +1 -1
  19. package/dist/client/addon/preview.d.ts +1 -1
  20. package/dist/client/addon/withCreevey.d.ts +6 -8
  21. package/dist/client/addon/withCreevey.js +21 -19
  22. package/dist/client/addon/withCreevey.js.map +1 -1
  23. package/dist/client/shared/components/ImagesView/BlendView.d.ts +1 -1
  24. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  25. package/dist/client/shared/components/ImagesView/ImagesView.d.ts +1 -0
  26. package/dist/client/shared/components/ImagesView/ImagesView.js.map +1 -1
  27. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
  28. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  29. package/dist/client/shared/components/ImagesView/SlideView.d.ts +1 -1
  30. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  31. package/dist/client/shared/components/ImagesView/SwapView.d.ts +1 -1
  32. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  33. package/dist/client/shared/components/PageFooter/PageFooter.d.ts +1 -0
  34. package/dist/client/shared/components/PageFooter/PageFooter.js +1 -1
  35. package/dist/client/shared/components/PageFooter/PageFooter.js.map +1 -1
  36. package/dist/client/shared/components/PageFooter/Paging.d.ts +2 -2
  37. package/dist/client/shared/components/PageFooter/Paging.js +8 -6
  38. package/dist/client/shared/components/PageFooter/Paging.js.map +1 -1
  39. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  40. package/dist/client/shared/components/PageHeader/PageHeader.d.ts +1 -0
  41. package/dist/client/shared/components/PageHeader/PageHeader.js +2 -1
  42. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  43. package/dist/client/shared/components/ResultsPage.d.ts +2 -2
  44. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  45. package/dist/client/web/CreeveyApp.d.ts +1 -0
  46. package/dist/client/web/CreeveyApp.js.map +1 -1
  47. package/dist/client/web/CreeveyLoader.d.ts +1 -0
  48. package/dist/client/web/CreeveyLoader.js.map +1 -1
  49. package/dist/client/web/CreeveyView/SideBar/Checkbox.d.ts +1 -1
  50. package/dist/client/web/CreeveyView/SideBar/Checkbox.js +4 -4
  51. package/dist/client/web/CreeveyView/SideBar/Checkbox.js.map +1 -1
  52. package/dist/client/web/CreeveyView/SideBar/Search.d.ts +1 -0
  53. package/dist/client/web/CreeveyView/SideBar/Search.js +4 -4
  54. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  55. package/dist/client/web/CreeveyView/SideBar/SideBar.d.ts +1 -1
  56. package/dist/client/web/CreeveyView/SideBar/SideBar.js +1 -7
  57. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  58. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.d.ts +1 -0
  59. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +5 -4
  60. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  61. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.d.ts +1 -0
  62. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +4 -3
  63. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  64. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +3 -7
  65. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +6 -5
  66. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  67. package/dist/client/web/CreeveyView/SideBar/TestLink.d.ts +1 -0
  68. package/dist/client/web/CreeveyView/SideBar/TestLink.js +5 -1
  69. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  70. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js +15 -8
  71. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.js.map +1 -1
  72. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +5 -4
  73. package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
  74. package/dist/client/web/CreeveyView/SideBar/Toggle.d.ts +1 -0
  75. package/dist/client/web/CreeveyView/SideBar/Toggle.js.map +1 -1
  76. package/dist/client/web/KeyboardEventsContext.d.ts +3 -4
  77. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  78. package/dist/client/web/assets/index-DkmZfG9C.js +591 -0
  79. package/dist/client/web/index.html +1 -1
  80. package/dist/client/web/index.js +5 -6
  81. package/dist/client/web/index.js.map +1 -1
  82. package/dist/creevey.js +21 -9
  83. package/dist/creevey.js.map +1 -1
  84. package/dist/index.js +7 -3
  85. package/dist/index.js.map +1 -1
  86. package/dist/server/config.d.ts +1 -1
  87. package/dist/server/config.js +9 -5
  88. package/dist/server/config.js.map +1 -1
  89. package/dist/server/docker.d.ts +2 -2
  90. package/dist/server/docker.js +46 -40
  91. package/dist/server/docker.js.map +1 -1
  92. package/dist/server/index.js +54 -15
  93. package/dist/server/index.js.map +1 -1
  94. package/dist/server/master/master.d.ts +1 -5
  95. package/dist/server/master/master.js +3 -3
  96. package/dist/server/master/master.js.map +1 -1
  97. package/dist/server/master/pool.d.ts +2 -1
  98. package/dist/server/master/pool.js +13 -7
  99. package/dist/server/master/pool.js.map +1 -1
  100. package/dist/server/master/runner.d.ts +1 -1
  101. package/dist/server/master/runner.js +4 -2
  102. package/dist/server/master/runner.js.map +1 -1
  103. package/dist/server/master/server.js +1 -0
  104. package/dist/server/master/server.js.map +1 -1
  105. package/dist/server/master/start.d.ts +3 -0
  106. package/dist/server/master/{index.js → start.js} +6 -9
  107. package/dist/server/master/start.js.map +1 -0
  108. package/dist/server/messages.d.ts +4 -10
  109. package/dist/server/messages.js +4 -58
  110. package/dist/server/messages.js.map +1 -1
  111. package/dist/server/playwright/docker-file.d.ts +1 -0
  112. package/dist/server/playwright/docker-file.js +26 -0
  113. package/dist/server/playwright/docker-file.js.map +1 -0
  114. package/dist/server/playwright/docker.d.ts +1 -0
  115. package/dist/server/playwright/docker.js +31 -0
  116. package/dist/server/playwright/docker.js.map +1 -0
  117. package/dist/server/playwright/internal.d.ts +25 -0
  118. package/dist/server/playwright/internal.js +319 -0
  119. package/dist/server/playwright/internal.js.map +1 -0
  120. package/dist/server/playwright/webdriver.d.ts +16 -0
  121. package/dist/server/playwright/webdriver.js +105 -0
  122. package/dist/server/playwright/webdriver.js.map +1 -0
  123. package/dist/server/providers/browser.d.ts +2 -0
  124. package/dist/server/{storybook/providers → providers}/browser.js +6 -7
  125. package/dist/server/providers/browser.js.map +1 -0
  126. package/dist/server/providers/hybrid.d.ts +2 -0
  127. package/dist/server/{storybook/providers → providers}/hybrid.js +8 -8
  128. package/dist/server/providers/hybrid.js.map +1 -0
  129. package/dist/server/reporter.d.ts +26 -0
  130. package/dist/server/{worker/reporter.js → reporter.js} +34 -56
  131. package/dist/server/reporter.js.map +1 -0
  132. package/dist/server/selenium/internal.d.ts +31 -0
  133. package/dist/server/selenium/internal.js +606 -0
  134. package/dist/server/selenium/internal.js.map +1 -0
  135. package/dist/server/selenium/selenoid.js +6 -13
  136. package/dist/server/selenium/selenoid.js.map +1 -1
  137. package/dist/server/selenium/webdriver.d.ts +24 -0
  138. package/dist/server/selenium/webdriver.js +106 -0
  139. package/dist/server/selenium/webdriver.js.map +1 -0
  140. package/dist/server/stories.js +16 -9
  141. package/dist/server/stories.js.map +1 -1
  142. package/dist/server/telemetry.d.ts +1 -1
  143. package/dist/server/telemetry.js +4 -4
  144. package/dist/server/telemetry.js.map +1 -1
  145. package/dist/server/utils.d.ts +3 -4
  146. package/dist/server/utils.js +10 -9
  147. package/dist/server/utils.js.map +1 -1
  148. package/dist/server/webdriver.d.ts +19 -0
  149. package/dist/server/webdriver.js +79 -0
  150. package/dist/server/webdriver.js.map +1 -0
  151. package/dist/server/worker/chai-image.d.ts +2 -5
  152. package/dist/server/worker/chai-image.js +14 -102
  153. package/dist/server/worker/chai-image.js.map +1 -1
  154. package/dist/server/worker/match-image.d.ts +14 -0
  155. package/dist/server/worker/match-image.js +231 -0
  156. package/dist/server/worker/match-image.js.map +1 -0
  157. package/dist/server/worker/start.d.ts +2 -0
  158. package/dist/server/worker/start.js +258 -0
  159. package/dist/server/worker/start.js.map +1 -0
  160. package/dist/types.d.ts +127 -64
  161. package/dist/types.js +15 -9
  162. package/dist/types.js.map +1 -1
  163. package/package.json +108 -110
  164. package/src/client/addon/components/Addon.tsx +1 -1
  165. package/src/client/addon/components/Icons.tsx +1 -1
  166. package/src/client/addon/components/Panel.tsx +1 -1
  167. package/src/client/addon/components/TestSelect.tsx +5 -5
  168. package/src/client/addon/components/Tools.tsx +9 -9
  169. package/src/client/addon/controller.ts +1 -1
  170. package/src/client/addon/manager.ts +4 -4
  171. package/src/client/addon/withCreevey.ts +26 -28
  172. package/src/client/shared/components/ImagesView/BlendView.tsx +1 -1
  173. package/src/client/shared/components/ImagesView/ImagesView.tsx +2 -2
  174. package/src/client/shared/components/ImagesView/SideBySideView.tsx +1 -1
  175. package/src/client/shared/components/ImagesView/SlideView.tsx +1 -1
  176. package/src/client/shared/components/ImagesView/SwapView.tsx +1 -1
  177. package/src/client/shared/components/PageFooter/PageFooter.tsx +2 -2
  178. package/src/client/shared/components/PageFooter/Paging.tsx +13 -13
  179. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -1
  180. package/src/client/shared/components/PageHeader/PageHeader.tsx +4 -3
  181. package/src/client/shared/components/ResultsPage.tsx +1 -1
  182. package/src/client/web/CreeveyApp.tsx +1 -1
  183. package/src/client/web/CreeveyLoader.tsx +1 -1
  184. package/src/client/web/CreeveyView/SideBar/Checkbox.tsx +6 -7
  185. package/src/client/web/CreeveyView/SideBar/Search.tsx +4 -4
  186. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +3 -10
  187. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +7 -6
  188. package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +7 -6
  189. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +8 -6
  190. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +8 -3
  191. package/src/client/web/CreeveyView/SideBar/TestStatusIcon.tsx +18 -10
  192. package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +7 -10
  193. package/src/client/web/CreeveyView/SideBar/Toggle.tsx +1 -2
  194. package/src/client/web/KeyboardEventsContext.tsx +3 -4
  195. package/src/client/web/index.html +1 -1
  196. package/src/client/web/index.tsx +4 -3
  197. package/src/creevey.ts +25 -8
  198. package/src/index.ts +4 -2
  199. package/src/server/config.ts +12 -8
  200. package/src/server/docker.ts +58 -44
  201. package/src/server/index.ts +57 -18
  202. package/src/server/master/master.ts +3 -6
  203. package/src/server/master/pool.ts +25 -9
  204. package/src/server/master/runner.ts +4 -2
  205. package/src/server/master/server.ts +1 -0
  206. package/src/server/master/{index.ts → start.ts} +13 -11
  207. package/src/server/messages.ts +11 -75
  208. package/src/server/playwright/docker-file.ts +21 -0
  209. package/src/server/playwright/docker.ts +41 -0
  210. package/src/server/playwright/internal.ts +387 -0
  211. package/src/server/playwright/webdriver.ts +126 -0
  212. package/src/server/{storybook/providers → providers}/browser.ts +7 -8
  213. package/src/server/{storybook/providers → providers}/hybrid.ts +19 -19
  214. package/src/server/{worker/reporter.ts → reporter.ts} +40 -72
  215. package/src/server/selenium/internal.ts +785 -0
  216. package/src/server/selenium/selenoid.ts +12 -17
  217. package/src/server/selenium/webdriver.ts +136 -0
  218. package/src/server/stories.ts +18 -11
  219. package/src/server/telemetry.ts +2 -2
  220. package/src/server/utils.ts +9 -9
  221. package/src/server/webdriver.ts +127 -0
  222. package/src/server/worker/chai-image.ts +21 -133
  223. package/src/server/worker/match-image.ts +303 -0
  224. package/src/server/worker/start.ts +303 -0
  225. package/src/types.ts +162 -60
  226. package/dist/client/web/202.js +0 -1
  227. package/dist/client/web/270.js +0 -43
  228. package/dist/client/web/752.js +0 -1
  229. package/dist/client/web/main.js +0 -79
  230. package/dist/client/web/main.js.LICENSE.txt +0 -34
  231. package/dist/server/master/index.d.ts +0 -3
  232. package/dist/server/master/index.js.map +0 -1
  233. package/dist/server/selenium/browser.d.ts +0 -19
  234. package/dist/server/selenium/browser.js +0 -640
  235. package/dist/server/selenium/browser.js.map +0 -1
  236. package/dist/server/selenium/index.d.ts +0 -2
  237. package/dist/server/selenium/index.js +0 -19
  238. package/dist/server/selenium/index.js.map +0 -1
  239. package/dist/server/storybook/providers/browser.d.ts +0 -2
  240. package/dist/server/storybook/providers/browser.js.map +0 -1
  241. package/dist/server/storybook/providers/hybrid.d.ts +0 -2
  242. package/dist/server/storybook/providers/hybrid.js.map +0 -1
  243. package/dist/server/worker/helpers.d.ts +0 -8
  244. package/dist/server/worker/helpers.js +0 -57
  245. package/dist/server/worker/helpers.js.map +0 -1
  246. package/dist/server/worker/index.d.ts +0 -1
  247. package/dist/server/worker/index.js +0 -6
  248. package/dist/server/worker/index.js.map +0 -1
  249. package/dist/server/worker/reporter.d.ts +0 -8
  250. package/dist/server/worker/reporter.js.map +0 -1
  251. package/dist/server/worker/worker.d.ts +0 -4
  252. package/dist/server/worker/worker.js +0 -212
  253. package/dist/server/worker/worker.js.map +0 -1
  254. package/src/server/selenium/browser.ts +0 -840
  255. package/src/server/selenium/index.ts +0 -2
  256. package/src/server/worker/helpers.ts +0 -61
  257. package/src/server/worker/index.ts +0 -1
  258. package/src/server/worker/worker.ts +0 -240
  259. package/types/mocha.d.ts +0 -20
@@ -3,14 +3,10 @@ import {
3
3
  WorkerMessage,
4
4
  StoriesMessage,
5
5
  TestMessage,
6
- WebpackMessage,
7
- DockerMessage,
8
6
  ProcessMessage,
9
7
  WorkerHandler,
10
8
  StoriesHandler,
11
9
  TestHandler,
12
- WebpackHandler,
13
- DockerHandler,
14
10
  ShutdownHandler,
15
11
  } from '../types.js';
16
12
 
@@ -35,14 +31,6 @@ export function emitTestMessage(message: TestMessage): boolean {
35
31
  return emitMessage({ scope: 'test', ...message });
36
32
  }
37
33
 
38
- export function emitWebpackMessage(message: WebpackMessage): boolean {
39
- return emitMessage({ scope: 'webpack', ...message });
40
- }
41
-
42
- export function emitDockerMessage(message: DockerMessage): boolean {
43
- return emitMessage({ scope: 'docker', ...message });
44
- }
45
-
46
34
  export function emitShutdownMessage(): boolean {
47
35
  return emitMessage({ scope: 'shutdown' });
48
36
  }
@@ -51,8 +39,6 @@ interface Handlers {
51
39
  worker: Set<WorkerHandler>;
52
40
  stories: Set<StoriesHandler>;
53
41
  test: Set<TestHandler>;
54
- webpack: Set<WebpackHandler>;
55
- docker: Set<DockerHandler>;
56
42
  shutdown: Set<ShutdownHandler>;
57
43
  }
58
44
 
@@ -61,8 +47,6 @@ function createHandlers(): Handlers {
61
47
  worker: new Set<WorkerHandler>(),
62
48
  stories: new Set<StoriesHandler>(),
63
49
  test: new Set<TestHandler>(),
64
- webpack: new Set<WebpackHandler>(),
65
- docker: new Set<DockerHandler>(),
66
50
  shutdown: new Set<ShutdownHandler>(),
67
51
  });
68
52
  }
@@ -89,18 +73,6 @@ const handler = (message: ProcessMessage): void => {
89
73
  });
90
74
  return;
91
75
  }
92
- case 'webpack': {
93
- handlers.webpack.forEach((h) => {
94
- h(message);
95
- });
96
- return;
97
- }
98
- case 'docker': {
99
- handlers.docker.forEach((h) => {
100
- h(message);
101
- });
102
- return;
103
- }
104
76
  case 'shutdown': {
105
77
  handlers.shutdown.forEach((h) => {
106
78
  h(message);
@@ -117,27 +89,25 @@ export function sendStoriesMessage(target: NodeJS.Process | Worker, message: Sto
117
89
  export function sendTestMessage(target: NodeJS.Process | Worker, message: TestMessage): void {
118
90
  target.send?.({ scope: 'test', ...message });
119
91
  }
120
- export function sendDockerMessage(target: NodeJS.Process | Worker, message: DockerMessage): void {
121
- target.send?.({ scope: 'docker', ...message });
122
- }
123
92
  export function sendShutdownMessage(target: NodeJS.Process | Worker): void {
124
93
  target.send?.({ scope: 'shutdown' });
125
94
  }
95
+ export function sendWorkerMessage(target: NodeJS.Process | Worker, message: WorkerMessage): void {
96
+ target.send?.({ scope: 'worker', ...message });
97
+ }
126
98
 
127
99
  export function subscribeOn(scope: 'worker', handler: WorkerHandler): () => void;
128
100
  export function subscribeOn(scope: 'stories', handler: StoriesHandler): () => void;
129
101
  export function subscribeOn(scope: 'test', handler: TestHandler): () => void;
130
- export function subscribeOn(scope: 'webpack', handler: WebpackHandler): () => void;
131
- export function subscribeOn(scope: 'docker', handler: DockerHandler): () => void;
132
102
  export function subscribeOn(scope: 'shutdown', handler: ShutdownHandler): () => void;
133
103
  export function subscribeOn(
134
- scope: 'worker' | 'stories' | 'test' | 'webpack' | 'docker' | 'shutdown',
135
- handler: WorkerHandler | StoriesHandler | TestHandler | WebpackHandler | DockerHandler | ShutdownHandler,
104
+ scope: 'worker' | 'stories' | 'test' | 'shutdown',
105
+ handler: WorkerHandler | StoriesHandler | TestHandler | ShutdownHandler,
136
106
  ): () => void;
137
107
 
138
108
  export function subscribeOn(
139
- scope: 'worker' | 'stories' | 'test' | 'webpack' | 'docker' | 'shutdown',
140
- handler: WorkerHandler | StoriesHandler | TestHandler | WebpackHandler | DockerHandler | ShutdownHandler,
109
+ scope: 'worker' | 'stories' | 'test' | 'shutdown',
110
+ handler: WorkerHandler | StoriesHandler | TestHandler | ShutdownHandler,
141
111
  ): () => void {
142
112
  switch (scope) {
143
113
  case 'worker': {
@@ -155,16 +125,6 @@ export function subscribeOn(
155
125
  handlers.test.add(testHandler);
156
126
  return () => handlers.test.delete(testHandler);
157
127
  }
158
- case 'webpack': {
159
- const webpackHandler = handler as WebpackHandler;
160
- handlers.webpack.add(webpackHandler);
161
- return () => handlers.webpack.delete(webpackHandler);
162
- }
163
- case 'docker': {
164
- const dockerHandler = handler as DockerHandler;
165
- handlers.docker.add(dockerHandler);
166
- return () => handlers.docker.delete(dockerHandler);
167
- }
168
128
  case 'shutdown': {
169
129
  const shutdownHandler = handler as ShutdownHandler;
170
130
  handlers.shutdown.add(shutdownHandler);
@@ -178,19 +138,17 @@ const workers = new Map<Worker, Handlers>();
178
138
  export function subscribeOnWorker(worker: Worker, scope: 'worker', handler: WorkerHandler): () => void;
179
139
  export function subscribeOnWorker(worker: Worker, scope: 'stories', handler: StoriesHandler): () => void;
180
140
  export function subscribeOnWorker(worker: Worker, scope: 'test', handler: TestHandler): () => void;
181
- export function subscribeOnWorker(worker: Worker, scope: 'webpack', handler: WebpackHandler): () => void;
182
- export function subscribeOnWorker(worker: Worker, scope: 'docker', handler: DockerHandler): () => void;
183
141
  export function subscribeOnWorker(worker: Worker, scope: 'shutdown', handler: ShutdownHandler): () => void;
184
142
  export function subscribeOnWorker(
185
143
  worker: Worker,
186
- scope: 'worker' | 'stories' | 'test' | 'webpack' | 'docker' | 'shutdown',
187
- handler: WorkerHandler | StoriesHandler | TestHandler | WebpackHandler | DockerHandler | ShutdownHandler,
144
+ scope: 'worker' | 'stories' | 'test' | 'shutdown',
145
+ handler: WorkerHandler | StoriesHandler | TestHandler | ShutdownHandler,
188
146
  ): () => void;
189
147
 
190
148
  export function subscribeOnWorker(
191
149
  worker: Worker,
192
- scope: 'worker' | 'stories' | 'test' | 'webpack' | 'docker' | 'shutdown',
193
- handler: WorkerHandler | StoriesHandler | TestHandler | WebpackHandler | DockerHandler | ShutdownHandler,
150
+ scope: 'worker' | 'stories' | 'test' | 'shutdown',
151
+ handler: WorkerHandler | StoriesHandler | TestHandler | ShutdownHandler,
194
152
  ): () => void {
195
153
  const workerHandlers = workers.get(worker) ?? createHandlers();
196
154
  if (!workers.has(worker)) {
@@ -216,18 +174,6 @@ export function subscribeOnWorker(
216
174
  });
217
175
  return;
218
176
  }
219
- case 'webpack': {
220
- workerHandlers.webpack.forEach((h) => {
221
- h(message);
222
- });
223
- return;
224
- }
225
- case 'docker': {
226
- workerHandlers.docker.forEach((h) => {
227
- h(message);
228
- });
229
- return;
230
- }
231
177
  case 'shutdown': {
232
178
  workerHandlers.shutdown.forEach((h) => {
233
179
  h(message);
@@ -254,16 +200,6 @@ export function subscribeOnWorker(
254
200
  workerHandlers.test.add(testHandler);
255
201
  return () => workerHandlers.test.delete(testHandler);
256
202
  }
257
- case 'webpack': {
258
- const webpackHandler = handler as WebpackHandler;
259
- workerHandlers.webpack.add(webpackHandler);
260
- return () => workerHandlers.webpack.delete(webpackHandler);
261
- }
262
- case 'docker': {
263
- const dockerHandler = handler as DockerHandler;
264
- workerHandlers.docker.add(dockerHandler);
265
- return () => workerHandlers.docker.delete(dockerHandler);
266
- }
267
203
  case 'shutdown': {
268
204
  const shutdownHandler = handler as ShutdownHandler;
269
205
  workerHandlers.shutdown.add(shutdownHandler);
@@ -0,0 +1,21 @@
1
+ import semver from 'semver';
2
+
3
+ // TODO Support custom docker images
4
+ export function playwrightDockerFile(browser: string, version: string): string {
5
+ const sv = semver.coerce(version);
6
+
7
+ return `
8
+ FROM mcr.microsoft.com/playwright:v${sv?.format() ?? version}
9
+
10
+ WORKDIR /creevey
11
+
12
+ RUN echo "{ \\"type\\": \\"module\\" }" > package.json && \\
13
+ echo "import { ${browser} as browser } from 'playwright-core';" >> index.js && \\
14
+ echo "const ws = await browser.launchServer({ port: 4444, wsPath: 'creevey' })" >> index.js && \\
15
+ npm i playwright-core${sv ? `@${sv.format()}` : ''}
16
+
17
+ EXPOSE 4444
18
+
19
+ ENTRYPOINT [ "node", "./index.js" ]
20
+ `;
21
+ }
@@ -0,0 +1,41 @@
1
+ import { buildImage, runImage } from '../docker';
2
+ import { emitWorkerMessage, subscribeOn } from '../messages';
3
+ import { isInsideDocker } from '../utils';
4
+ import { LOCALHOST_REGEXP } from '../webdriver';
5
+ import { playwrightDockerFile } from './docker-file';
6
+
7
+ export async function startPlaywrightContainer(browserName: string, debug: boolean): Promise<string> {
8
+ const {
9
+ default: { version },
10
+ } = await import('playwright-core/package.json', { with: { type: 'json' } });
11
+
12
+ const imageName = `creevey/${browserName}:v${version}`;
13
+ const dockerfile = playwrightDockerFile(browserName, version);
14
+
15
+ await buildImage(imageName, dockerfile);
16
+
17
+ const port = await new Promise<number>((resolve) => {
18
+ subscribeOn('worker', (message) => {
19
+ if (message.type == 'port') {
20
+ resolve(message.payload.port);
21
+ }
22
+ });
23
+ emitWorkerMessage({ type: 'port', payload: { port: -1 } });
24
+ });
25
+
26
+ const host = await runImage(
27
+ imageName,
28
+ [],
29
+ {
30
+ ExposedPorts: { [`${port}/tcp`]: {} },
31
+ HostConfig: {
32
+ PortBindings: { ['4444/tcp']: [{ HostPort: `${port}` }] },
33
+ },
34
+ },
35
+ debug,
36
+ );
37
+
38
+ const gridUrl = `ws://localhost:${port}/creevey`;
39
+
40
+ return isInsideDocker ? gridUrl.replace(LOCALHOST_REGEXP, host) : gridUrl;
41
+ }
@@ -0,0 +1,387 @@
1
+ import { Browser, BrowserType, Page, chromium, firefox, webkit } from 'playwright-core';
2
+ import Logger from 'loglevel';
3
+ import chalk from 'chalk';
4
+ import { v4 } from 'uuid';
5
+ import prefix from 'loglevel-plugin-prefix';
6
+ import { SET_GLOBALS, STORY_RENDERED, UPDATE_STORY_ARGS } from '@storybook/core-events';
7
+ import { BrowserConfigObject, Config, Options, StoriesRaw, StoryInput, StorybookGlobals, noop } from '../../types';
8
+ import { subscribeOn } from '../messages';
9
+ import { appendIframePath, getAddresses, LOCALHOST_REGEXP, resolveStorybookUrl, storybookRootID } from '../webdriver';
10
+ import { isShuttingDown, runSequence } from '../utils';
11
+ import { colors, logger } from '../logger';
12
+ import { Args } from '@storybook/csf';
13
+
14
+ async function tryConnect(type: BrowserType, gridUrl: string): Promise<Browser | null> {
15
+ let timeout: NodeJS.Timeout | null = null;
16
+ let isTimeout = false;
17
+ let error: unknown = null;
18
+ return Promise.race([
19
+ new Promise<null>(
20
+ (resolve) =>
21
+ (timeout = setTimeout(() => {
22
+ isTimeout = true;
23
+ logger.error(`Can't connect to ${type.name()} playwright browser`, error);
24
+ resolve(null);
25
+ }, 10000)),
26
+ ),
27
+ (async () => {
28
+ let browser: Browser | null = null;
29
+ do {
30
+ try {
31
+ browser = await type.connect(gridUrl);
32
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
33
+ if (timeout) clearTimeout(timeout);
34
+ break;
35
+ } catch (e: unknown) {
36
+ error = e;
37
+ }
38
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
39
+ } while (!isTimeout);
40
+ return browser;
41
+ })(),
42
+ ]);
43
+ }
44
+
45
+ export class InternalBrowser {
46
+ #isShuttingDown = false;
47
+ #browser: Browser;
48
+ #page: Page;
49
+ #sessionId: string = v4();
50
+ #serverHost: string | null = null;
51
+ #serverPort: number;
52
+ #logger: Logger.Logger;
53
+ #unsubscribe: () => void = noop;
54
+ constructor(browser: Browser, page: Page, port: number) {
55
+ this.#browser = browser;
56
+ this.#page = page;
57
+ this.#serverPort = port;
58
+ this.#logger = Logger.getLogger(this.#sessionId);
59
+ this.#unsubscribe = subscribeOn('shutdown', () => {
60
+ void this.closeBrowser();
61
+ });
62
+ }
63
+
64
+ get browser() {
65
+ return this.#page;
66
+ }
67
+
68
+ get sessionId() {
69
+ return this.#sessionId;
70
+ }
71
+
72
+ async closeBrowser(): Promise<void> {
73
+ if (this.#isShuttingDown) return;
74
+
75
+ this.#isShuttingDown = true;
76
+ this.#unsubscribe();
77
+
78
+ try {
79
+ await this.#page.close();
80
+ await this.#browser.close();
81
+ } catch (_) {
82
+ /* noop */
83
+ }
84
+ }
85
+
86
+ async takeScreenshot(captureElement?: string | null, ignoreElements?: string | string[] | null): Promise<Buffer> {
87
+ // TODO Implement features from selenium `takeScreenshot`
88
+ // TODO Do we need scroll bar hack from selenium?
89
+ const ignore = Array.isArray(ignoreElements) ? ignoreElements : ignoreElements ? [ignoreElements] : [];
90
+ const mask = ignore.map((el) => this.#page.locator(el));
91
+ if (captureElement) {
92
+ const element = await this.#page.$(captureElement);
93
+ if (!element) throw new Error(`Element with selector ${captureElement} not found`);
94
+ return element.screenshot({ animations: 'disabled', mask });
95
+ }
96
+ return this.#page.screenshot({ animations: 'disabled', mask, fullPage: true });
97
+ }
98
+
99
+ waitForComplete(callback: (isCompleted: boolean) => void): void {
100
+ void this.#page.evaluate<boolean>(() => window.__CREEVEY_HAS_PLAY_COMPLETED_YET__()).then(callback);
101
+ }
102
+
103
+ async selectStory(id: string, waitForReady = false): Promise<boolean> {
104
+ // NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
105
+ await this.updateBrowserGlobalVariables();
106
+ await this.resetMousePosition();
107
+
108
+ this.#logger.debug(`Triggering 'SetCurrentStory' event with storyId ${chalk.magenta(id)}`);
109
+
110
+ const result = await this.#page.evaluate<
111
+ [error?: string | null, isCaptureCalled?: boolean] | null,
112
+ [id: string, shouldWaitForReady: boolean]
113
+ >(
114
+ ([id, shouldWaitForReady]) => {
115
+ if (typeof window.__CREEVEY_SELECT_STORY__ == 'undefined') {
116
+ return [
117
+ "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.",
118
+ ];
119
+ }
120
+ return window.__CREEVEY_SELECT_STORY__(id, shouldWaitForReady);
121
+ },
122
+ [id, waitForReady],
123
+ );
124
+
125
+ const [errorMessage, isCaptureCalled = false] = result ?? [];
126
+
127
+ if (errorMessage) throw new Error(errorMessage);
128
+
129
+ return isCaptureCalled;
130
+ }
131
+
132
+ async updateStoryArgs(story: StoryInput, updatedArgs: Args): Promise<void> {
133
+ await this.#page.evaluate(
134
+ ([storyId, updatedArgs, UPDATE_STORY_ARGS, STORY_RENDERED]) => {
135
+ return new Promise((resolve) => {
136
+ window.__STORYBOOK_ADDONS_CHANNEL__.once(STORY_RENDERED, resolve);
137
+ window.__STORYBOOK_ADDONS_CHANNEL__.emit(UPDATE_STORY_ARGS, {
138
+ storyId,
139
+ updatedArgs,
140
+ });
141
+ });
142
+ },
143
+ [story.id, updatedArgs, UPDATE_STORY_ARGS, STORY_RENDERED] as const,
144
+ );
145
+ }
146
+
147
+ async loadStoriesFromBrowser(retry = false): Promise<StoriesRaw> {
148
+ try {
149
+ const stories = await this.#page.evaluate<StoriesRaw | undefined>(() => window.__CREEVEY_GET_STORIES__());
150
+
151
+ if (!stories) throw new Error("Can't get stories, it seems creevey or storybook API isn't available");
152
+
153
+ return stories;
154
+ } catch (error) {
155
+ // TODO Check how other solutions with playwright get stories from storybook
156
+ if (retry) throw error;
157
+ await new Promise((resolve) => setTimeout(resolve, 1000));
158
+ // NOTE: Try one more time because of dynamic nature of vite and storybook
159
+ return this.loadStoriesFromBrowser(true);
160
+ }
161
+ }
162
+
163
+ static async getBrowser(
164
+ browserName: string,
165
+ gridUrl: string,
166
+ config: Config,
167
+ options: Options,
168
+ ): Promise<InternalBrowser | null> {
169
+ const browserConfig = config.browsers[browserName] as BrowserConfigObject;
170
+ const { storybookUrl: address = config.storybookUrl, viewport, _storybookGlobals } = browserConfig;
171
+
172
+ let browser: Browser | null = null;
173
+
174
+ // TODO Support Selenium Grid 4
175
+ switch (browserConfig.browserName) {
176
+ case 'chromium':
177
+ browser = await tryConnect(chromium, gridUrl);
178
+ break;
179
+ case 'firefox':
180
+ browser = await tryConnect(firefox, gridUrl);
181
+ break;
182
+ case 'webkit':
183
+ browser = await tryConnect(webkit, gridUrl);
184
+ break;
185
+
186
+ default:
187
+ throw new Error(
188
+ `Unknown browser ${browserConfig.browserName}. Playwright supports browsers: chromium, firefox, webkit`,
189
+ );
190
+ }
191
+
192
+ if (!browser) {
193
+ return null;
194
+ }
195
+
196
+ const page = await browser.newPage();
197
+
198
+ // TODO Add debug output
199
+
200
+ const internalBrowser = new InternalBrowser(browser, page, options.port);
201
+
202
+ try {
203
+ if (isShuttingDown.current) return null;
204
+ const done = await internalBrowser.init({
205
+ browserName,
206
+ viewport,
207
+ storybookUrl: address,
208
+ storybookGlobals: _storybookGlobals,
209
+ resolveStorybookUrl: config.resolveStorybookUrl,
210
+ });
211
+
212
+ return done ? internalBrowser : null;
213
+ } catch (originalError) {
214
+ void internalBrowser.closeBrowser();
215
+
216
+ const message = originalError instanceof Error ? originalError.message : (originalError as string);
217
+ const error = new Error(`Can't load storybook root page: ${message}`);
218
+ if (originalError instanceof Error) error.stack = originalError.stack;
219
+
220
+ logger.error(error);
221
+
222
+ return null;
223
+ }
224
+ }
225
+
226
+ private async init({
227
+ browserName,
228
+ viewport,
229
+ storybookUrl,
230
+ storybookGlobals,
231
+ resolveStorybookUrl,
232
+ }: {
233
+ browserName: string;
234
+ viewport?: { width: number; height: number };
235
+ storybookUrl: string;
236
+ storybookGlobals?: StorybookGlobals;
237
+ resolveStorybookUrl?: () => Promise<string>;
238
+ }) {
239
+ const sessionId = this.#sessionId;
240
+
241
+ prefix.apply(this.#logger, {
242
+ format(level) {
243
+ const levelColor = colors[level.toUpperCase() as keyof typeof colors];
244
+ return `[${browserName}:${chalk.gray(sessionId)}] ${levelColor(level)} =>`;
245
+ },
246
+ });
247
+
248
+ this.#page.setDefaultNavigationTimeout(10000);
249
+ this.#page.setDefaultTimeout(60000);
250
+
251
+ return await runSequence(
252
+ [
253
+ () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
254
+ () => this.waitForStorybook(),
255
+ () => this.updateStorybookGlobals(storybookGlobals),
256
+ () => this.resolveCreeveyHost(),
257
+ () => this.updateBrowserGlobalVariables(),
258
+ () => this.resizeViewport(viewport),
259
+ ],
260
+ () => !this.#isShuttingDown,
261
+ );
262
+ }
263
+
264
+ private async openStorybookPage(storybookUrl: string, resolver?: () => Promise<string>): Promise<void> {
265
+ if (!LOCALHOST_REGEXP.test(storybookUrl)) {
266
+ await this.#page.goto(appendIframePath(storybookUrl));
267
+ return;
268
+ }
269
+
270
+ try {
271
+ if (resolver) {
272
+ this.#logger.debug('Resolving storybook url with custom resolver');
273
+
274
+ const resolvedUrl = await resolver();
275
+
276
+ this.#logger.debug(`Resolver storybook url ${resolvedUrl}`);
277
+
278
+ await this.#page.goto(appendIframePath(resolvedUrl));
279
+ } else {
280
+ await resolveStorybookUrl(appendIframePath(storybookUrl), (url) => this.checkUrl(url), this.#logger);
281
+ }
282
+ } catch (error) {
283
+ this.#logger.error('Failed to resolve storybook URL', error instanceof Error ? error.message : '');
284
+ throw error;
285
+ }
286
+ }
287
+
288
+ private async checkUrl(url: string): Promise<boolean> {
289
+ try {
290
+ this.#logger.debug(`Opening ${chalk.magenta(url)} and checking the page source`);
291
+ const response = await this.#page.goto(url, { waitUntil: 'commit' });
292
+ const source = await response?.text();
293
+
294
+ this.#logger.debug(`Checking ${chalk.cyan(`#${storybookRootID}`)} existence on ${chalk.magenta(url)}`);
295
+ return source?.includes(`id="${storybookRootID}"`) ?? false;
296
+ } catch {
297
+ return false;
298
+ }
299
+ }
300
+
301
+ private async waitForStorybook(): Promise<void> {
302
+ // TODO Duplicated code with selenium
303
+ this.#logger.debug('Waiting for `setStories` event to make sure that storybook is initiated');
304
+
305
+ const isTimeout = await Promise.race([
306
+ new Promise<boolean>((resolve) => {
307
+ setTimeout(() => {
308
+ resolve(true);
309
+ }, 60000);
310
+ }),
311
+ (async () => {
312
+ let wait = true;
313
+ do {
314
+ try {
315
+ // TODO Research a different way to ensure storybook is initiated
316
+ wait = await this.#page.evaluate((SET_GLOBALS: string) => {
317
+ if (typeof window.__STORYBOOK_ADDONS_CHANNEL__ == 'undefined') return true;
318
+ if (window.__STORYBOOK_ADDONS_CHANNEL__.last(SET_GLOBALS) == undefined) return true;
319
+ return false;
320
+ }, SET_GLOBALS);
321
+ } catch (e: unknown) {
322
+ this.#logger.debug('An error has been caught during the script:', e);
323
+ }
324
+ } while (wait);
325
+ return false;
326
+ })(),
327
+ ]);
328
+
329
+ // TODO Change the message to describe a reason why it might happen
330
+ if (isTimeout) throw new Error('Failed to wait `setStories` event');
331
+ }
332
+
333
+ private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
334
+ if (!globals) return;
335
+
336
+ this.#logger.debug('Applying storybook globals');
337
+ await this.#page.evaluate((globals: StorybookGlobals) => {
338
+ window.__CREEVEY_UPDATE_GLOBALS__(globals);
339
+ }, globals);
340
+ }
341
+
342
+ private async resolveCreeveyHost(): Promise<void> {
343
+ const addresses = getAddresses();
344
+
345
+ this.#serverHost = await this.#page.evaluate(
346
+ ([hosts, port]) => {
347
+ return Promise.all(
348
+ hosts.map((host) => {
349
+ return Promise.race([
350
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
351
+ fetch('http://' + host + ':' + port + '/ping').then((response) => response.text()),
352
+ new Promise((_resolve, reject) => {
353
+ setTimeout(reject, 5000);
354
+ }),
355
+ ])
356
+ .then((pong) => (pong == 'pong' ? host : null))
357
+ .catch(() => null);
358
+ }),
359
+ ).then((hosts) => hosts.find((host) => host != null) ?? null);
360
+ },
361
+ [addresses, this.#serverPort] as const,
362
+ );
363
+
364
+ if (this.#serverHost == null) throw new Error("Can't reach creevey server from a browser");
365
+ }
366
+
367
+ private async updateBrowserGlobalVariables() {
368
+ await this.#page.evaluate(
369
+ ([workerId, creeveyHost, creeveyPort]) => {
370
+ window.__CREEVEY_WORKER_ID__ = workerId;
371
+ window.__CREEVEY_SERVER_HOST__ = creeveyHost ?? 'localhost';
372
+ window.__CREEVEY_SERVER_PORT__ = creeveyPort;
373
+ },
374
+ [process.pid, this.#serverHost, this.#serverPort] as const,
375
+ );
376
+ }
377
+
378
+ private async resizeViewport(viewport?: { width: number; height: number }): Promise<void> {
379
+ if (!viewport) return;
380
+
381
+ await this.#page.setViewportSize(viewport);
382
+ }
383
+
384
+ private async resetMousePosition(): Promise<void> {
385
+ await this.#page.mouse.move(0, 0);
386
+ }
387
+ }