creevey 0.10.0-beta.4 → 0.10.0-beta.41

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 (227) hide show
  1. package/README.md +19 -41
  2. package/dist/client/addon/components/Addon.js +17 -7
  3. package/dist/client/addon/components/Addon.js.map +1 -1
  4. package/dist/client/addon/components/Panel.js +2 -2
  5. package/dist/client/addon/components/Panel.js.map +1 -1
  6. package/dist/client/addon/components/Tools.js +17 -7
  7. package/dist/client/addon/components/Tools.js.map +1 -1
  8. package/dist/client/addon/withCreevey.d.ts +2 -1
  9. package/dist/client/addon/withCreevey.js +11 -1
  10. package/dist/client/addon/withCreevey.js.map +1 -1
  11. package/dist/client/shared/components/ImagesView/BlendView.d.ts +1 -1
  12. package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
  13. package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
  14. package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
  15. package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
  16. package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
  17. package/dist/client/shared/components/ImagesView/SlideView.d.ts +1 -1
  18. package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
  19. package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
  20. package/dist/client/shared/components/ImagesView/SwapView.d.ts +1 -1
  21. package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
  22. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  23. package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +1 -1
  24. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
  25. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  26. package/dist/client/shared/components/PageHeader/PageHeader.js +20 -8
  27. package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
  28. package/dist/client/shared/components/ResultsPage.d.ts +1 -1
  29. package/dist/client/shared/components/ResultsPage.js +43 -13
  30. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  31. package/dist/client/shared/creeveyClientApi.js +8 -1
  32. package/dist/client/shared/creeveyClientApi.js.map +1 -1
  33. package/dist/client/shared/helpers.d.ts +1 -3
  34. package/dist/client/shared/helpers.js +4 -19
  35. package/dist/client/shared/helpers.js.map +1 -1
  36. package/dist/client/web/CreeveyApp.js +42 -14
  37. package/dist/client/web/CreeveyApp.js.map +1 -1
  38. package/dist/client/web/CreeveyContext.d.ts +5 -0
  39. package/dist/client/web/CreeveyContext.js +20 -7
  40. package/dist/client/web/CreeveyContext.js.map +1 -1
  41. package/dist/client/web/CreeveyLoader.js +2 -2
  42. package/dist/client/web/CreeveyLoader.js.map +1 -1
  43. package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
  44. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  45. package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
  46. package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
  47. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
  48. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  49. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
  50. package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
  51. package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +2 -2
  52. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
  53. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  54. package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
  55. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  56. package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +1 -1
  57. package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +1 -1
  58. package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
  59. package/dist/client/web/KeyboardEventsContext.js +79 -64
  60. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  61. package/dist/client/web/assets/index-C47njyZV.js +802 -0
  62. package/dist/client/web/index.html +1 -1
  63. package/dist/client/web/index.js +17 -7
  64. package/dist/client/web/index.js.map +1 -1
  65. package/dist/client/web/themes.d.ts +2 -0
  66. package/dist/client/web/themes.js +22 -0
  67. package/dist/client/web/themes.js.map +1 -0
  68. package/dist/creevey.js +16 -9
  69. package/dist/creevey.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/server/config.d.ts +1 -1
  72. package/dist/server/config.js +30 -7
  73. package/dist/server/config.js.map +1 -1
  74. package/dist/server/connection.d.ts +3 -0
  75. package/dist/server/connection.js +28 -0
  76. package/dist/server/connection.js.map +1 -0
  77. package/dist/server/docker.d.ts +1 -1
  78. package/dist/server/docker.js +56 -32
  79. package/dist/server/docker.js.map +1 -1
  80. package/dist/server/index.js +64 -11
  81. package/dist/server/index.js.map +1 -1
  82. package/dist/server/logger.d.ts +2 -1
  83. package/dist/server/logger.js +7 -3
  84. package/dist/server/logger.js.map +1 -1
  85. package/dist/server/master/api.js +1 -1
  86. package/dist/server/master/api.js.map +1 -1
  87. package/dist/server/master/pool.d.ts +4 -3
  88. package/dist/server/master/pool.js +13 -66
  89. package/dist/server/master/pool.js.map +1 -1
  90. package/dist/server/master/queue.d.ts +13 -0
  91. package/dist/server/master/queue.js +71 -0
  92. package/dist/server/master/queue.js.map +1 -0
  93. package/dist/server/master/runner.d.ts +3 -0
  94. package/dist/server/master/runner.js +78 -10
  95. package/dist/server/master/runner.js.map +1 -1
  96. package/dist/server/master/server.js +1 -1
  97. package/dist/server/master/server.js.map +1 -1
  98. package/dist/server/master/start.js +13 -11
  99. package/dist/server/master/start.js.map +1 -1
  100. package/dist/server/playwright/docker-file.d.ts +1 -1
  101. package/dist/server/playwright/docker-file.js +15 -6
  102. package/dist/server/playwright/docker-file.js.map +1 -1
  103. package/dist/server/playwright/docker.d.ts +2 -1
  104. package/dist/server/playwright/docker.js +10 -2
  105. package/dist/server/playwright/docker.js.map +1 -1
  106. package/dist/server/playwright/index-source.mjs +16 -0
  107. package/dist/server/playwright/internal.d.ts +6 -6
  108. package/dist/server/playwright/internal.js +143 -91
  109. package/dist/server/playwright/internal.js.map +1 -1
  110. package/dist/server/playwright/webdriver.d.ts +1 -1
  111. package/dist/server/playwright/webdriver.js +5 -8
  112. package/dist/server/playwright/webdriver.js.map +1 -1
  113. package/dist/server/providers/browser.js +6 -4
  114. package/dist/server/providers/browser.js.map +1 -1
  115. package/dist/server/providers/hybrid.js +1 -1
  116. package/dist/server/providers/hybrid.js.map +1 -1
  117. package/dist/server/reporters/creevey.d.ts +7 -0
  118. package/dist/server/reporters/creevey.js +63 -0
  119. package/dist/server/reporters/creevey.js.map +1 -0
  120. package/dist/server/reporters/index.d.ts +2 -0
  121. package/dist/server/reporters/index.js +16 -0
  122. package/dist/server/reporters/index.js.map +1 -0
  123. package/dist/server/reporters/junit.d.ts +16 -0
  124. package/dist/server/reporters/junit.js +165 -0
  125. package/dist/server/reporters/junit.js.map +1 -0
  126. package/dist/server/reporters/teamcity.d.ts +7 -0
  127. package/dist/server/reporters/teamcity.js +60 -0
  128. package/dist/server/reporters/teamcity.js.map +1 -0
  129. package/dist/server/selenium/internal.d.ts +3 -4
  130. package/dist/server/selenium/internal.js +127 -108
  131. package/dist/server/selenium/internal.js.map +1 -1
  132. package/dist/server/selenium/selenoid.js +8 -6
  133. package/dist/server/selenium/selenoid.js.map +1 -1
  134. package/dist/server/selenium/webdriver.d.ts +1 -1
  135. package/dist/server/selenium/webdriver.js +5 -9
  136. package/dist/server/selenium/webdriver.js.map +1 -1
  137. package/dist/server/telemetry.js +2 -2
  138. package/dist/server/testsFiles/parser.js +45 -5
  139. package/dist/server/testsFiles/parser.js.map +1 -1
  140. package/dist/server/utils.d.ts +19 -1
  141. package/dist/server/utils.js +87 -8
  142. package/dist/server/utils.js.map +1 -1
  143. package/dist/server/webdriver.d.ts +5 -4
  144. package/dist/server/webdriver.js +23 -10
  145. package/dist/server/webdriver.js.map +1 -1
  146. package/dist/server/worker/chai-image.d.ts +1 -2
  147. package/dist/server/worker/chai-image.js +4 -3
  148. package/dist/server/worker/chai-image.js.map +1 -1
  149. package/dist/server/worker/context.d.ts +3 -0
  150. package/dist/server/worker/context.js +15 -0
  151. package/dist/server/worker/context.js.map +1 -0
  152. package/dist/server/worker/match-image.d.ts +4 -4
  153. package/dist/server/worker/match-image.js +7 -4
  154. package/dist/server/worker/match-image.js.map +1 -1
  155. package/dist/server/worker/start.js +47 -73
  156. package/dist/server/worker/start.js.map +1 -1
  157. package/dist/shared/index.d.ts +1 -1
  158. package/dist/types.d.ts +46 -10
  159. package/dist/types.js +2 -0
  160. package/dist/types.js.map +1 -1
  161. package/docs/cli.md +12 -0
  162. package/docs/config.md +179 -165
  163. package/docs/storybook.md +60 -0
  164. package/docs/tests.md +50 -45
  165. package/package.json +64 -63
  166. package/src/client/addon/components/Panel.tsx +2 -2
  167. package/src/client/addon/withCreevey.ts +10 -2
  168. package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
  169. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
  170. package/src/client/shared/components/PageHeader/PageHeader.tsx +4 -2
  171. package/src/client/shared/components/ResultsPage.tsx +31 -8
  172. package/src/client/shared/creeveyClientApi.ts +9 -1
  173. package/src/client/shared/helpers.ts +4 -24
  174. package/src/client/web/CreeveyApp.tsx +27 -8
  175. package/src/client/web/CreeveyContext.tsx +9 -0
  176. package/src/client/web/CreeveyLoader.tsx +1 -1
  177. package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
  178. package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
  179. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
  180. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
  181. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
  182. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  183. package/src/client/web/themes.ts +24 -0
  184. package/src/creevey.ts +16 -10
  185. package/src/server/config.ts +30 -7
  186. package/src/server/connection.ts +26 -0
  187. package/src/server/docker.ts +63 -34
  188. package/src/server/index.ts +72 -14
  189. package/src/server/logger.ts +6 -2
  190. package/src/server/master/api.ts +1 -1
  191. package/src/server/master/pool.ts +23 -59
  192. package/src/server/master/queue.ts +77 -0
  193. package/src/server/master/runner.ts +96 -10
  194. package/src/server/master/server.ts +1 -1
  195. package/src/server/master/start.ts +16 -11
  196. package/src/server/playwright/docker-file.ts +18 -6
  197. package/src/server/playwright/docker.ts +16 -3
  198. package/src/server/playwright/index-source.mjs +16 -0
  199. package/src/server/playwright/internal.ts +182 -111
  200. package/src/server/playwright/webdriver.ts +6 -9
  201. package/src/server/providers/browser.ts +6 -4
  202. package/src/server/providers/hybrid.ts +1 -1
  203. package/src/server/reporters/creevey.ts +71 -0
  204. package/src/server/reporters/index.ts +11 -0
  205. package/src/server/reporters/junit.ts +205 -0
  206. package/src/server/reporters/teamcity.ts +74 -0
  207. package/src/server/selenium/internal.ts +131 -116
  208. package/src/server/selenium/selenoid.ts +8 -6
  209. package/src/server/selenium/webdriver.ts +6 -10
  210. package/src/server/telemetry.ts +2 -2
  211. package/src/server/testsFiles/parser.ts +52 -4
  212. package/src/server/utils.ts +97 -9
  213. package/src/server/webdriver.ts +24 -16
  214. package/src/server/worker/chai-image.ts +4 -4
  215. package/src/server/worker/context.ts +14 -0
  216. package/src/server/worker/match-image.ts +12 -8
  217. package/src/server/worker/start.ts +51 -86
  218. package/src/shared/index.ts +1 -1
  219. package/src/types.ts +50 -11
  220. package/types/global.d.ts +1 -0
  221. package/.yarnrc.yml +0 -1
  222. package/chromatic.config.json +0 -5
  223. package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
  224. package/dist/server/reporter.d.ts +0 -26
  225. package/dist/server/reporter.js +0 -108
  226. package/dist/server/reporter.js.map +0 -1
  227. package/src/server/reporter.ts +0 -138
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "creevey",
3
3
  "description": "Cross-browser screenshot testing tool for Storybook with fancy UI Runner",
4
- "version": "0.10.0-beta.4",
4
+ "version": "0.10.0-beta.41",
5
5
  "type": "commonjs",
6
6
  "bin": "dist/cli.js",
7
7
  "main": "./dist/index.js",
@@ -64,7 +64,7 @@
64
64
  "build": "yarn prebuild && yarn build:client && yarn build:creevey && yarn postbuild",
65
65
  "build:client": "vite build",
66
66
  "build:creevey": "tsc --build tsconfig.prod.json",
67
- "postbuild": "cp \"\"scripts/dist/*.d.ts\"\" dist/",
67
+ "postbuild": "cp \"\"scripts/dist/*.d.ts\"\" dist/ && cp \"\"src/server/playwright/index-source.mjs\"\" dist/server/playwright/index-source.mjs",
68
68
  "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
69
69
  "build-storybook": "storybook build",
70
70
  "chromatic": "chromatic --project-token=chpt_80df83ca94e6fb4",
@@ -74,11 +74,11 @@
74
74
  "node": ">=18.0"
75
75
  },
76
76
  "peerDependencies": {
77
- "playwright": "*",
77
+ "playwright-core": "*",
78
78
  "selenium-webdriver": "*"
79
79
  },
80
80
  "peerDependenciesMeta": {
81
- "playwright": {
81
+ "playwright-core": {
82
82
  "optional": true
83
83
  },
84
84
  "selenium-webdriver": {
@@ -87,33 +87,33 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@koa/cors": "^5.0.0",
90
- "@octokit/core": "^6.1.2",
91
- "@storybook/icons": "^1.2.12",
90
+ "@octokit/core": "^6.1.4",
91
+ "@storybook/icons": "^1.4.0",
92
92
  "@types/chai": "^4.3.20",
93
- "@types/dockerode": "^3.3.31",
93
+ "@types/dockerode": "^3.3.37",
94
94
  "@types/koa": "^2.15.0",
95
95
  "@types/koa-bodyparser": "^4.3.12",
96
96
  "@types/koa-mount": "^4.0.5",
97
97
  "@types/koa-static": "^4.0.4",
98
98
  "@types/koa__cors": "^5.0.0",
99
- "@types/lodash": "^4.17.13",
99
+ "@types/lodash": "^4.17.16",
100
100
  "@types/micromatch": "^4.0.9",
101
101
  "@types/minimist": "^1.2.5",
102
102
  "@types/pixelmatch": "^5.2.6",
103
103
  "@types/pngjs": "^6.0.5",
104
- "@types/qs": "^6.9.16",
105
- "@types/react": "^18.3.12",
106
- "@types/react-dom": "^18.3.1",
107
- "@types/selenium-webdriver": "^4.1.27",
104
+ "@types/qs": "^6.9.18",
105
+ "@types/react": "^18.3.20",
106
+ "@types/react-dom": "^18.3.6",
107
+ "@types/selenium-webdriver": "^4.1.28",
108
108
  "@types/shelljs": "^0.8.15",
109
- "@types/ws": "^8.5.13",
109
+ "@types/ws": "^8.18.1",
110
110
  "chai": "^4.5.0",
111
111
  "chalk": "^4.1.2",
112
- "chokidar": "^4.0.1",
113
- "dockerode": "^4.0.2",
112
+ "chokidar": "^4.0.3",
113
+ "dockerode": "^4.0.5",
114
114
  "find-cache-dir": "^5.0.0",
115
115
  "get-port": "^7.1.0",
116
- "koa": "^2.15.3",
116
+ "koa": "^2.16.0",
117
117
  "koa-bodyparser": "^4.4.1",
118
118
  "koa-mount": "^4.0.0",
119
119
  "koa-static": "^5.0.0",
@@ -122,72 +122,73 @@
122
122
  "loglevel-plugin-prefix": "^0.8.4",
123
123
  "micromatch": "^4.0.8",
124
124
  "minimist": "^1.2.8",
125
- "odiff-bin": "^3.1.2",
125
+ "odiff-bin": "^3.2.1",
126
+ "package-manager-detector": "^0.2.11",
127
+ "pidtree": "^0.6.0",
126
128
  "pixelmatch": "^6.0.0",
127
129
  "pngjs": "^7.0.0",
128
130
  "polished": "^4.3.1",
129
- "qs": "^6.13.0",
130
- "semver": "^7.6.3",
131
- "shelljs": "^0.8.5",
131
+ "qs": "^6.14.0",
132
+ "semver": "^7.7.1",
133
+ "shelljs": "^0.9.2",
132
134
  "tar-stream": "^3.1.7",
133
- "tsx": "^4.19.2",
134
- "uuid": "^11.0.2",
135
- "ws": "^8.18.0",
136
- "yocto-spinner": "^0.1.1"
135
+ "tsx": "^4.19.3",
136
+ "uuid": "^11.1.0",
137
+ "ws": "^8.18.1",
138
+ "yocto-spinner": "^0.2.1"
137
139
  },
138
140
  "devDependencies": {
139
- "@chromatic-com/storybook": "^3.2.2",
140
- "@eslint/js": "^9.14.0",
141
- "@storybook/addon-essentials": "^8.4.1",
142
- "@storybook/addon-interactions": "^8.4.1",
143
- "@storybook/blocks": "^8.4.1",
144
- "@storybook/channels": "^8.4.1",
145
- "@storybook/components": "^8.4.1",
146
- "@storybook/csf": "^0.1.11",
147
- "@storybook/manager-api": "^8.4.1",
148
- "@storybook/preview-api": "^8.4.1",
149
- "@storybook/react": "^8.4.1",
150
- "@storybook/react-vite": "^8.4.1",
151
- "@storybook/test": "^8.4.1",
152
- "@storybook/theming": "^8.4.1",
153
- "@storybook/types": "^8.4.1",
141
+ "@chromatic-com/storybook": "^3.2.6",
142
+ "@eslint/js": "^9.23.0",
143
+ "@storybook/addon-essentials": "^8.6.12",
144
+ "@storybook/addon-interactions": "^8.6.12",
145
+ "@storybook/blocks": "^8.6.12",
146
+ "@storybook/channels": "^8.6.12",
147
+ "@storybook/components": "^8.6.12",
148
+ "@storybook/manager-api": "^8.6.12",
149
+ "@storybook/preview-api": "^8.6.12",
150
+ "@storybook/react": "^8.6.12",
151
+ "@storybook/react-vite": "^8.6.12",
152
+ "@storybook/test": "^8.6.12",
153
+ "@storybook/theming": "^8.6.12",
154
+ "@storybook/types": "^8.6.12",
154
155
  "@types/eslint": "^9.6.1",
155
156
  "@types/eslint__js": "^8.42.3",
156
- "@types/node": "^18.19.64",
157
+ "@types/node": "^18.19.86",
157
158
  "@types/resize-observer-browser": "^0.1.11",
158
- "@types/semver": "^7",
159
+ "@types/semver": "^7.7.0",
159
160
  "@types/tar-stream": "^3.1.3",
160
161
  "@types/tmp": "^0.2.6",
161
- "@vitejs/plugin-react-swc": "^3.7.1",
162
- "browserstack-local": "^1.5.5",
163
- "chromatic": "^11.16.3",
164
- "concurrently": "^9.0.1",
162
+ "@vitejs/plugin-react-swc": "^3.8.1",
163
+ "browserstack-local": "^1.5.6",
164
+ "chromatic": "^11.28.0",
165
+ "concurrently": "^9.1.2",
165
166
  "conventional-changelog-cli": "^5.0.0",
166
- "dotenv": "^16.4.5",
167
- "eslint": "^9.14.0",
168
- "eslint-config-prettier": "^9.1.0",
169
- "eslint-plugin-prettier": "^5.2.1",
170
- "eslint-plugin-react": "^7.37.2",
171
- "eslint-plugin-react-hooks": "^5.0.0",
167
+ "dotenv": "^16.4.7",
168
+ "eslint": "^9.23.0",
169
+ "eslint-config-prettier": "^10.1.1",
170
+ "eslint-plugin-prettier": "^5.2.6",
171
+ "eslint-plugin-react": "^7.37.5",
172
+ "eslint-plugin-react-hooks": "^5.2.0",
172
173
  "git-cz": "^4.9.0",
173
- "globals": "^15.11.0",
174
- "husky": "^9.1.6",
174
+ "globals": "^16.0.0",
175
+ "husky": "^9.1.7",
175
176
  "immer": "^10.1.1",
176
- "lint-staged": "^15.2.10",
177
+ "lint-staged": "^15.5.0",
177
178
  "pinst": "^3.0.0",
178
- "playwright-core": "^1.48.0",
179
- "prettier": "^3.3.3",
179
+ "playwright-core": "^1.51.1",
180
+ "prettier": "^3.5.3",
180
181
  "react": "^18.3.1",
181
182
  "react-dom": "^18.3.1",
182
183
  "react-is": "^18.3.1",
183
- "selenium-webdriver": "^4.26.0",
184
- "storybook": "^8.4.1",
184
+ "selenium-webdriver": "^4.30.0",
185
+ "storybook": "^8.6.12",
185
186
  "tmp": "^0.2.3",
186
- "typescript": "^5.6.3",
187
- "typescript-eslint": "^8.12.2",
188
- "use-immer": "^0.10.0",
189
- "vite": "^5.4.10",
190
- "vitest": "^2.1.4"
187
+ "typescript": "^5.8.2",
188
+ "typescript-eslint": "^8.29.0",
189
+ "use-immer": "^0.11.0",
190
+ "vite": "^5.4.17",
191
+ "vitest": "^2.1.9"
191
192
  },
192
193
  "config": {
193
194
  "commitizen": {
@@ -1,11 +1,11 @@
1
1
  import React, { JSX } from 'react';
2
2
  import { Loader } from '@storybook/components';
3
3
  import { styled } from '@storybook/theming';
4
- import { TestData } from '../../../types.js';
4
+ import { noop, TestData } from '../../../types.js';
5
5
  import { ResultsPage } from '../../shared/components/ResultsPage.js';
6
6
  import { getTestPath } from '../../shared/helpers.js';
7
7
  import TestSelect from './TestSelect.js';
8
- import { noop } from 'lodash';
8
+
9
9
  interface PanelProps {
10
10
  tests: TestData[];
11
11
  selectedTestId: string;
@@ -1,4 +1,4 @@
1
- import type { Renderer, StoryContextForEnhancers } from '@storybook/csf';
1
+ import type { Renderer, StoryContextForEnhancers } from '@storybook/types';
2
2
  import { makeDecorator, PreviewWeb, StoryStore } from '@storybook/preview-api';
3
3
  import { Channel } from '@storybook/channels';
4
4
  import {
@@ -12,9 +12,11 @@ import {
12
12
  } from '../../types.js';
13
13
  import { serializeRawStories } from '../../shared/index.js';
14
14
  import { getConnectionUrl } from '../shared/helpers.js';
15
+ import isEqual from 'lodash/isEqual.js';
15
16
 
16
17
  declare global {
17
18
  interface Window {
19
+ __CREEVEY_ENV__: boolean;
18
20
  __CREEVEY_SERVER_HOST__: string;
19
21
  __CREEVEY_SERVER_PORT__: number;
20
22
  __CREEVEY_WORKER_ID__: number;
@@ -139,6 +141,7 @@ let captureResolver: () => void;
139
141
  let waitForCreevey: Promise<void>;
140
142
  let creeveyReady: () => void;
141
143
  let setStoriesCounter = 0;
144
+ let globals = {};
142
145
 
143
146
  export function withCreevey(): ReturnType<typeof makeDecorator> {
144
147
  const addonsChannel = (): Channel => window.__STORYBOOK_ADDONS_CHANNEL__;
@@ -225,7 +228,10 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
225
228
  }
226
229
  }
227
230
 
228
- function updateGlobals(globals: StorybookGlobals): void {
231
+ function updateGlobals(newGlobals: StorybookGlobals): void {
232
+ if (isEqual(globals, newGlobals)) return;
233
+
234
+ globals = newGlobals;
229
235
  addonsChannel().emit(StorybookEvents.UPDATE_GLOBALS, { globals });
230
236
  }
231
237
 
@@ -274,6 +280,7 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
274
280
  });
275
281
  }
276
282
 
283
+ window.__CREEVEY_ENV__ = false;
277
284
  window.__CREEVEY_GET_STORIES__ = getStories;
278
285
  window.__CREEVEY_SELECT_STORY__ = selectStory;
279
286
  window.__CREEVEY_UPDATE_GLOBALS__ = updateGlobals;
@@ -315,6 +322,7 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
315
322
  });
316
323
  }
317
324
 
325
+ // TODO It's not accessible from the outside the package
318
326
  export async function capture(options?: CaptureOptions): Promise<void> {
319
327
  if (!isTestBrowser) return;
320
328
 
@@ -46,6 +46,24 @@ export const SwapView = withTheme(({ theme, expect, actual, diff }: ViewPropsWit
46
46
  setImage((prevImage) => (prevImage == 'actual' ? 'expect' : 'actual'));
47
47
  }, []);
48
48
 
49
+ const handleKeyDown = useCallback(
50
+ (e: KeyboardEvent) => {
51
+ if (e.code === 'Space' && e.altKey) {
52
+ e.preventDefault();
53
+ handleChangeView();
54
+ }
55
+ },
56
+ [handleChangeView],
57
+ );
58
+
59
+ useEffect(() => {
60
+ document.addEventListener('keydown', handleKeyDown, false);
61
+
62
+ return () => {
63
+ document.removeEventListener('keydown', handleKeyDown, false);
64
+ };
65
+ }, [handleKeyDown]);
66
+
49
67
  useEffect(() => {
50
68
  if (loaded) readyForCapture();
51
69
  }, [loaded]);
@@ -70,6 +70,7 @@ export const ImagePreview = withTheme(
70
70
  onClick(imageName);
71
71
  };
72
72
 
73
+ // TODO Add image name as a title
73
74
  return (
74
75
  <Button
75
76
  onClick={handleClick}
@@ -1,4 +1,4 @@
1
- import React, { JSX, useEffect } from 'react';
1
+ import React, { JSX, useContext, useEffect } from 'react';
2
2
  import { Tabs } from '@storybook/components';
3
3
  import { CloseAltIcon } from '@storybook/icons';
4
4
  import { styled, withTheme, Theme } from '@storybook/theming';
@@ -6,6 +6,7 @@ import { ImagesViewMode, Images } from '../../../../types.js';
6
6
  import { getImageUrl } from '../../helpers.js';
7
7
  import { ImagePreview } from './ImagePreview.js';
8
8
  import { viewModes } from '../../viewMode.js';
9
+ import { CreeveyContext } from '../../../web/CreeveyContext.js';
9
10
 
10
11
  interface PageHeaderProps {
11
12
  title: string[];
@@ -76,6 +77,7 @@ export function PageHeader({
76
77
  onImageChange,
77
78
  onViewModeChange,
78
79
  }: PageHeaderProps): JSX.Element | null {
80
+ const { isReport } = useContext(CreeveyContext);
79
81
  const imageEntires = Object.entries(images) as [string, Images][];
80
82
 
81
83
  const handleViewModeChange = (mode: string): void => {
@@ -110,7 +112,7 @@ export function PageHeader({
110
112
  <ImagePreview
111
113
  key={name}
112
114
  imageName={name}
113
- url={`${getImageUrl(title, name)}/${image.actual}`}
115
+ url={`${getImageUrl(title, name, isReport)}/${image.actual}`}
114
116
  isActive={name === imageName}
115
117
  onClick={onImageChange}
116
118
  error={imagesWithError.includes(name)}
@@ -1,12 +1,13 @@
1
- import React, { JSX, useState } from 'react';
1
+ import React, { JSX, useCallback, useContext, useEffect, useState } from 'react';
2
2
  import { Placeholder, ScrollArea } from '@storybook/components';
3
3
  import { styled, withTheme, Theme } from '@storybook/theming';
4
4
  import { ImagesView } from './ImagesView/ImagesView.js';
5
5
  import { PageHeader } from './PageHeader/PageHeader.js';
6
6
  import { PageFooter } from './PageFooter/PageFooter.js';
7
7
  import { getImageUrl } from '../helpers.js';
8
- import { getViewMode, VIEW_MODE_KEY } from '../viewMode.js';
8
+ import { getViewMode, VIEW_MODE_KEY, viewModes } from '../viewMode.js';
9
9
  import { ImagesViewMode, TestResult } from '../../../types.js';
10
+ import { CreeveyContext } from '../../web/CreeveyContext.js';
10
11
 
11
12
  interface ResultsPageProps {
12
13
  path: string[];
@@ -65,8 +66,9 @@ export function ResultsPageInternal({
65
66
  onRetryChange,
66
67
  }: ResultsPageProps): JSX.Element {
67
68
  const result = results[retry - 1] ?? {};
69
+ const { isReport } = useContext(CreeveyContext);
68
70
  const [viewMode, setViewMode] = useState<ImagesViewMode>(getViewMode());
69
- const url = getImageUrl(path, imageName);
71
+ const url = getImageUrl(path, imageName, isReport);
70
72
  const image = result.images?.[imageName];
71
73
  const canApprove = Boolean(image && approved?.[imageName] != retry - 1 && result.status != 'success');
72
74
  const hasDiffAndExpect = canApprove && Boolean(image?.diff && image.expect);
@@ -77,10 +79,31 @@ export function ResultsPageInternal({
77
79
  )
78
80
  : [];
79
81
 
80
- const handleChangeViewMode = (mode: ImagesViewMode): void => {
81
- localStorage.setItem(VIEW_MODE_KEY, mode);
82
- setViewMode(mode);
83
- };
82
+ const handleKeyDown = useCallback(
83
+ (e: KeyboardEvent) => {
84
+ if (!canApprove) return;
85
+ if (e.code === 'Tab') {
86
+ e.preventDefault();
87
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
88
+ if (e.shiftKey) setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) - 1) % viewModes.length)!);
89
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
90
+ else setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) + 1) % viewModes.length)!);
91
+ }
92
+ },
93
+ [canApprove],
94
+ );
95
+
96
+ useEffect(() => {
97
+ localStorage.setItem(VIEW_MODE_KEY, viewMode);
98
+ }, [viewMode]);
99
+
100
+ useEffect(() => {
101
+ document.addEventListener('keydown', handleKeyDown, false);
102
+
103
+ return () => {
104
+ document.removeEventListener('keydown', handleKeyDown, false);
105
+ };
106
+ }, [handleKeyDown]);
84
107
 
85
108
  return (
86
109
  <Container height={height}>
@@ -92,7 +115,7 @@ export function ResultsPageInternal({
92
115
  errorMessage={result.error}
93
116
  showViewModes={hasDiffAndExpect}
94
117
  viewMode={viewMode}
95
- onViewModeChange={handleChangeViewMode}
118
+ onViewModeChange={setViewMode}
96
119
  onImageChange={onImageChange}
97
120
  imagesWithError={imagesWithError}
98
121
  />
@@ -12,6 +12,7 @@ export interface CreeveyClientApi {
12
12
 
13
13
  export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
14
14
  let clientApiResolver: (api: CreeveyClientApi) => void = noop;
15
+ let clientApiRejecter: (error: Error | Event) => void = noop;
15
16
  const updateListeners = new Set<(update: CreeveyUpdate) => void>();
16
17
  let statusRequest: Promise<CreeveyStatus> | null = null;
17
18
  let statusResolver: (status: CreeveyStatus) => void = noop;
@@ -22,6 +23,10 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
22
23
  ws.send(JSON.stringify(request));
23
24
  }
24
25
 
26
+ ws.addEventListener('error', (event) => {
27
+ clientApiRejecter(event);
28
+ });
29
+
25
30
  ws.addEventListener('open', () => {
26
31
  clientApiResolver({
27
32
  start(ids: string[]) {
@@ -64,5 +69,8 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
64
69
  });
65
70
  // TODO Reconnect
66
71
 
67
- return new Promise((resolve) => (clientApiResolver = resolve));
72
+ return new Promise((resolve, reject) => {
73
+ clientApiResolver = resolve;
74
+ clientApiRejecter = reject;
75
+ });
68
76
  }
@@ -1,4 +1,3 @@
1
- import { themes, ThemeVars } from '@storybook/theming';
2
1
  import { parse, stringify } from 'qs';
3
2
  import { RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
4
3
  import { TestData, isTest, isDefined, TestStatus, CreeveySuite, CreeveyTest, CreeveyStatus } from '../../types.js';
@@ -300,12 +299,14 @@ export function getConnectionUrl(): string {
300
299
  .join(':');
301
300
  }
302
301
 
303
- export function getImageUrl(path: string[], imageName: string): string {
302
+ export function getImageUrl(path: string[], imageName: string, isReport?: boolean): string {
304
303
  // path => [title, story, test, browser]
305
304
  const browser = path.slice(-1)[0];
306
305
  const imagesUrl = window.location.host
307
306
  ? `${window.location.protocol}//${getConnectionUrl()}${
308
- window.location.pathname == '/' ? '/report' : window.location.pathname.split('/').slice(0, -1).join('/')
307
+ window.location.pathname == '/' && !isReport
308
+ ? '/report'
309
+ : window.location.pathname.split('/').slice(0, -1).join('/')
309
310
  }/${encodeURI(path.slice(0, -1).join('/'))}`
310
311
  : encodeURI(path.slice(0, -1).join('/'));
311
312
 
@@ -393,27 +394,6 @@ export function useCalcScale(diffImageRef: RefObject<HTMLImageElement>, loaded:
393
394
  return scale;
394
395
  }
395
396
 
396
- const CREEVEY_THEME = 'Creevey_theme';
397
-
398
- function isTheme(theme?: string | null): theme is ThemeVars['base'] {
399
- return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
400
- }
401
-
402
- function initialTheme(): ThemeVars['base'] {
403
- const theme = localStorage.getItem(CREEVEY_THEME);
404
- return isTheme(theme) ? theme : 'light';
405
- }
406
-
407
- export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
408
- const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
409
-
410
- useEffect(() => {
411
- localStorage.setItem(CREEVEY_THEME, theme);
412
- }, [theme]);
413
-
414
- return [theme, setTheme];
415
- }
416
-
417
397
  export function setSearchParams(testPath: string[]): void {
418
398
  const pageUrl = `?${stringify({ testPath })}`;
419
399
  window.history.pushState({ testPath }, '', pageUrl);
@@ -11,17 +11,17 @@ import {
11
11
  getTestByPath,
12
12
  removeTests,
13
13
  getTestPath,
14
- useTheme,
15
14
  setSearchParams,
16
15
  getTestPathFromSearch,
17
16
  CreeveyViewFilter,
18
17
  getFailedTests,
19
18
  } from '../shared/helpers.js';
20
- import { CreeveyContext } from './CreeveyContext.js';
19
+ import { CreeveyContext, FocusableItem } from './CreeveyContext.js';
21
20
  import { KeyboardEvents } from './KeyboardEventsContext.js';
22
21
  import { SideBar } from './CreeveyView/SideBar/index.js';
23
22
  import { ResultsPage } from '../shared/components/ResultsPage.js';
24
23
  import { Toggle } from './CreeveyView/SideBar/Toggle.js';
24
+ import { useTheme } from './themes.js';
25
25
 
26
26
  export interface CreeveyAppProps {
27
27
  api?: CreeveyClientApi;
@@ -59,8 +59,9 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
59
59
  const failedTests = useMemo(() => getFailedTests(tests), [tests]);
60
60
 
61
61
  const [retry, setRetry] = useState(openedTest?.results?.length ?? 0);
62
- const result = openedTest?.results?.[retry - 1] ?? { images: {} };
63
- const [imageName, setImageName] = useState(Object.keys(result.images ?? {})[0] ?? '');
62
+ const result = useMemo(() => openedTest?.results?.[retry - 1], [openedTest, retry]);
63
+ const [imageName, setImageName] = useState(Object.keys(result?.images ?? {})[0] ?? '');
64
+ const [sidebarFocusedItem, setSidebarFocusedItem] = useState<FocusableItem>([]);
64
65
  const canApprove = useMemo(
65
66
  () =>
66
67
  Boolean(
@@ -94,6 +95,7 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
94
95
  (test: CreeveyTest): void => {
95
96
  const testPath = getTestPath(test);
96
97
  setSearchParams(testPath);
98
+ setSidebarFocusedItem(testPath);
97
99
  updateTests((draft) => {
98
100
  openSuite(draft, testPath, true);
99
101
  openTest(testPath);
@@ -103,11 +105,24 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
103
105
  );
104
106
 
105
107
  const handleGoToNextFailedTest = useCallback(() => {
106
- if (failedTests.length <= 1) return;
108
+ if (failedTests.length == 0) return;
107
109
  const currentTest = failedTests.findIndex((t) => t.id === openedTest?.id);
108
- const nextFailedTest = failedTests[currentTest + 1] || failedTests[0];
109
- handleOpenTest(nextFailedTest);
110
- }, [failedTests, handleOpenTest, openedTest?.id]);
110
+ const failedImages = Object.entries(result?.images ?? {})
111
+ .filter(([name, image]) =>
112
+ // TODO Move to helpers, it duplicates in a few places
113
+ Boolean(image?.error != null && openedTest?.approved?.[name] != retry - 1 && result?.status != 'success'),
114
+ )
115
+ .map(([name]) => name);
116
+ if (
117
+ failedImages.length > 1 &&
118
+ (failedTests.length == 1 || failedImages.indexOf(imageName) < failedImages.length - 1)
119
+ ) {
120
+ setImageName((name) => failedImages[failedImages.indexOf(name) + 1] ?? failedImages[0]);
121
+ } else {
122
+ const nextFailedTest = failedTests[currentTest + 1] ?? failedTests[0];
123
+ handleOpenTest(nextFailedTest);
124
+ }
125
+ }, [failedTests, handleOpenTest, openedTest, retry, result, imageName]);
111
126
 
112
127
  const handleImageApproveNew = useCallback((): void => {
113
128
  const id = openedTest?.id;
@@ -122,6 +137,7 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
122
137
  }, [handleImageApproveNew, handleGoToNextFailedTest]);
123
138
 
124
139
  const handleApproveAll = useCallback(() => {
140
+ // TODO Update handled incorrectly
125
141
  api?.approveAll();
126
142
  }, [api]);
127
143
 
@@ -189,12 +205,15 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
189
205
  value={{
190
206
  isReport: initialState.isReport,
191
207
  isRunning,
208
+ onImageNext: canApprove ? handleGoToNextFailedTest : undefined,
192
209
  onImageApprove: canApprove ? handleImageApproveAndGoNext : undefined,
193
210
  onApproveAll: handleApproveAll,
194
211
  onStart: handleStart,
195
212
  onStop: handleStop,
196
213
  onSuiteOpen: handleSuiteOpen,
197
214
  onSuiteToggle: handleSuiteToggle,
215
+ sidebarFocusedItem,
216
+ setSidebarFocusedItem,
198
217
  }}
199
218
  >
200
219
  <ThemeProvider theme={ensure(themes[theme])}>
@@ -1,26 +1,35 @@
1
1
  import React, { useContext } from 'react';
2
2
  import { CreeveySuite, noop } from '../../types.js';
3
3
 
4
+ export type SuitePath = string[];
5
+ export type FocusableItem = null | SuitePath;
6
+
4
7
  export interface CreeveyContextType {
5
8
  isReport: boolean;
6
9
  isRunning: boolean;
7
10
  onStop: () => void;
11
+ onImageNext?: () => void;
8
12
  onImageApprove?: () => void;
9
13
  onApproveAll: () => void;
10
14
  onStart: (rootSuite: CreeveySuite) => void;
11
15
  onSuiteOpen: (path: string[], opened: boolean) => void;
12
16
  onSuiteToggle: (path: string[], checked: boolean) => void;
17
+ sidebarFocusedItem: FocusableItem;
18
+ setSidebarFocusedItem: (item: FocusableItem) => void;
13
19
  }
14
20
 
15
21
  export const CreeveyContext = React.createContext<CreeveyContextType>({
16
22
  isReport: true,
17
23
  isRunning: false,
24
+ onImageNext: noop,
18
25
  onImageApprove: noop,
19
26
  onApproveAll: noop,
20
27
  onStop: noop,
21
28
  onStart: noop,
22
29
  onSuiteOpen: noop,
23
30
  onSuiteToggle: noop,
31
+ sidebarFocusedItem: [],
32
+ setSidebarFocusedItem: noop,
24
33
  });
25
34
 
26
35
  export const useCreeveyContext = () => useContext(CreeveyContext);
@@ -1,6 +1,6 @@
1
1
  import React, { JSX } from 'react';
2
2
  import { styled, withTheme, Theme, keyframes, ensure, ThemeProvider, themes, Keyframes } from '@storybook/theming';
3
- import { useTheme } from '../shared/helpers.js';
3
+ import { useTheme } from './themes.js';
4
4
 
5
5
  const Container = withTheme(
6
6
  styled.div(({ theme }) => ({
@@ -1,7 +1,7 @@
1
- import React, { JSX, ChangeEvent, useContext, useRef, useState } from 'react';
1
+ import React, { JSX, ChangeEvent, useRef, useState } from 'react';
2
2
  import { SearchIcon, CloseAltIcon } from '@storybook/icons';
3
3
  import { styled, Theme, withTheme } from '@storybook/theming';
4
- import { KeyboardEventsContext } from '../../KeyboardEventsContext.js';
4
+ import { useCreeveyContext } from '../../CreeveyContext.js';
5
5
 
6
6
  interface SearchProps {
7
7
  onChange: (arg: string) => void;
@@ -100,7 +100,7 @@ const FilterForm = withTheme(
100
100
  );
101
101
 
102
102
  export const Search = ({ onChange, value }: SearchProps): JSX.Element => {
103
- const { setSidebarFocusedItem } = useContext(KeyboardEventsContext);
103
+ const { setSidebarFocusedItem } = useCreeveyContext();
104
104
  const [focussed, onSetFocussed] = useState(false);
105
105
  const searchRef = useRef<HTMLInputElement>(null);
106
106
 
@@ -62,6 +62,7 @@ const SelectAllContainer = styled.div({
62
62
  });
63
63
 
64
64
  const TestsContainer = styled.div({
65
+ marginBottom: '8px',
65
66
  position: 'relative',
66
67
  height: '100%',
67
68
  });