creevey 0.10.0-beta.7 → 0.10.0-beta.9

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 (58) hide show
  1. package/dist/client/addon/components/Panel.js +2 -2
  2. package/dist/client/addon/components/Panel.js.map +1 -1
  3. package/dist/client/addon/withCreevey.js +9 -1
  4. package/dist/client/addon/withCreevey.js.map +1 -1
  5. package/dist/client/shared/components/ImagesView/SwapView.js +12 -0
  6. package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
  7. package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
  8. package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
  9. package/dist/client/shared/components/ResultsPage.js +23 -5
  10. package/dist/client/shared/components/ResultsPage.js.map +1 -1
  11. package/dist/client/web/CreeveyApp.js +22 -6
  12. package/dist/client/web/CreeveyApp.js.map +1 -1
  13. package/dist/client/web/CreeveyContext.d.ts +5 -0
  14. package/dist/client/web/CreeveyContext.js +3 -0
  15. package/dist/client/web/CreeveyContext.js.map +1 -1
  16. package/dist/client/web/CreeveyView/SideBar/Search.js +2 -2
  17. package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
  18. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +49 -6
  19. package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
  20. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +1 -3
  21. package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
  22. package/dist/client/web/CreeveyView/SideBar/TestLink.js +1 -3
  23. package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
  24. package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
  25. package/dist/client/web/KeyboardEventsContext.js +62 -57
  26. package/dist/client/web/KeyboardEventsContext.js.map +1 -1
  27. package/dist/client/web/assets/{index-DB8lHlJw.js → index-BE9CL5_G.js} +94 -94
  28. package/dist/client/web/index.html +1 -1
  29. package/dist/server/index.js +4 -0
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/server/playwright/internal.d.ts +2 -2
  32. package/dist/server/playwright/internal.js +10 -8
  33. package/dist/server/playwright/internal.js.map +1 -1
  34. package/dist/server/selenium/internal.d.ts +2 -2
  35. package/dist/server/selenium/internal.js +10 -8
  36. package/dist/server/selenium/internal.js.map +1 -1
  37. package/dist/server/utils.js +2 -3
  38. package/dist/server/utils.js.map +1 -1
  39. package/dist/server/worker/start.js +17 -4
  40. package/dist/server/worker/start.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/client/addon/components/Panel.tsx +2 -2
  43. package/src/client/addon/withCreevey.ts +6 -1
  44. package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
  45. package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
  46. package/src/client/shared/components/ResultsPage.tsx +28 -7
  47. package/src/client/web/CreeveyApp.tsx +25 -7
  48. package/src/client/web/CreeveyContext.tsx +9 -0
  49. package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
  50. package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
  51. package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
  52. package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
  53. package/src/client/web/KeyboardEventsContext.tsx +61 -73
  54. package/src/server/index.ts +5 -0
  55. package/src/server/playwright/internal.ts +9 -9
  56. package/src/server/selenium/internal.ts +9 -9
  57. package/src/server/utils.ts +2 -3
  58. package/src/server/worker/start.ts +18 -5
@@ -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,11 +1,11 @@
1
- import React, { JSX, useState } from 'react';
1
+ import React, { JSX, useCallback, 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
10
 
11
11
  interface ResultsPageProps {
@@ -77,10 +77,31 @@ export function ResultsPageInternal({
77
77
  )
78
78
  : [];
79
79
 
80
- const handleChangeViewMode = (mode: ImagesViewMode): void => {
81
- localStorage.setItem(VIEW_MODE_KEY, mode);
82
- setViewMode(mode);
83
- };
80
+ const handleKeyDown = useCallback(
81
+ (e: KeyboardEvent) => {
82
+ if (!canApprove) return;
83
+ if (e.code === 'Tab') {
84
+ e.preventDefault();
85
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
86
+ if (e.shiftKey) setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) - 1) % viewModes.length)!);
87
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
88
+ else setViewMode((mode) => viewModes.at((viewModes.indexOf(mode) + 1) % viewModes.length)!);
89
+ }
90
+ },
91
+ [canApprove],
92
+ );
93
+
94
+ useEffect(() => {
95
+ localStorage.setItem(VIEW_MODE_KEY, viewMode);
96
+ }, [viewMode]);
97
+
98
+ useEffect(() => {
99
+ document.addEventListener('keydown', handleKeyDown, false);
100
+
101
+ return () => {
102
+ document.removeEventListener('keydown', handleKeyDown, false);
103
+ };
104
+ }, [handleKeyDown]);
84
105
 
85
106
  return (
86
107
  <Container height={height}>
@@ -92,7 +113,7 @@ export function ResultsPageInternal({
92
113
  errorMessage={result.error}
93
114
  showViewModes={hasDiffAndExpect}
94
115
  viewMode={viewMode}
95
- onViewModeChange={handleChangeViewMode}
116
+ onViewModeChange={setViewMode}
96
117
  onImageChange={onImageChange}
97
118
  imagesWithError={imagesWithError}
98
119
  />
@@ -17,7 +17,7 @@ import {
17
17
  CreeveyViewFilter,
18
18
  getFailedTests,
19
19
  } from '../shared/helpers.js';
20
- import { CreeveyContext } from './CreeveyContext.js';
20
+ import { CreeveyContext, FocusableItem } from './CreeveyContext.js';
21
21
  import { KeyboardEvents } from './KeyboardEventsContext.js';
22
22
  import { SideBar } from './CreeveyView/SideBar/index.js';
23
23
  import { ResultsPage } from '../shared/components/ResultsPage.js';
@@ -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;
@@ -189,12 +204,15 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
189
204
  value={{
190
205
  isReport: initialState.isReport,
191
206
  isRunning,
207
+ onImageNext: canApprove ? handleGoToNextFailedTest : undefined,
192
208
  onImageApprove: canApprove ? handleImageApproveAndGoNext : undefined,
193
209
  onApproveAll: handleApproveAll,
194
210
  onStart: handleStart,
195
211
  onStop: handleStop,
196
212
  onSuiteOpen: handleSuiteOpen,
197
213
  onSuiteToggle: handleSuiteToggle,
214
+ sidebarFocusedItem,
215
+ setSidebarFocusedItem,
198
216
  }}
199
217
  >
200
218
  <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,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
 
@@ -1,4 +1,4 @@
1
- import React, { JSX } from 'react';
1
+ import React, { JSX, useCallback, useEffect, useState } from 'react';
2
2
  import { styled, withTheme } from '@storybook/theming';
3
3
  import { Button } from '@storybook/components';
4
4
  import { ChevronRightIcon } from '@storybook/icons';
@@ -21,15 +21,46 @@ const Container = styled.div({
21
21
  });
22
22
 
23
23
  export function SideBarFooter(): JSX.Element {
24
- const { onApproveAll, onImageApprove } = useCreeveyContext();
24
+ const { onApproveAll, onImageApprove, onImageNext } = useCreeveyContext();
25
+ const [isAlt, setIsAlt] = useState(false);
26
+
27
+ const handleKeyDown = useCallback((e: KeyboardEvent) => {
28
+ if (e.code === 'AltLeft') {
29
+ e.preventDefault();
30
+ setIsAlt(true);
31
+ }
32
+ }, []);
33
+ const handleKeyUp = useCallback((e: KeyboardEvent) => {
34
+ if (e.code === 'AltLeft') {
35
+ e.preventDefault();
36
+ setIsAlt(false);
37
+ }
38
+ }, []);
39
+
40
+ useEffect(() => {
41
+ document.addEventListener('keydown', handleKeyDown, false);
42
+ document.addEventListener('keyup', handleKeyUp, false);
43
+
44
+ return () => {
45
+ document.removeEventListener('keydown', handleKeyDown, false);
46
+ document.removeEventListener('keyup', handleKeyUp, false);
47
+ };
48
+ }, [handleKeyDown, handleKeyUp]);
25
49
 
26
50
  return (
27
51
  <Sticky>
28
52
  <Container>
29
- <Button variant="solid" size="medium" onClick={onImageApprove} disabled={!onImageApprove}>
30
- Approve
31
- <ChevronRightIcon />
32
- </Button>
53
+ {isAlt ? (
54
+ <Button variant="outline" size="medium" onClick={onImageNext} disabled={!onImageApprove}>
55
+ Next
56
+ <ChevronRightIcon />
57
+ </Button>
58
+ ) : (
59
+ <Button variant="solid" size="medium" onClick={onImageApprove} disabled={!onImageApprove}>
60
+ Approve
61
+ <ChevronRightIcon />
62
+ </Button>
63
+ )}
33
64
  <Button variant="outline" size="medium" onClick={onApproveAll}>
34
65
  Approve all
35
66
  </Button>
@@ -1,11 +1,10 @@
1
- import React, { JSX, useRef, useContext, useEffect, useMemo } from 'react';
1
+ import React, { JSX, useRef, useEffect, useMemo } from 'react';
2
2
  import { ChevronDownIcon, ChevronRightIcon } from '@storybook/icons';
3
3
  import { styled, withTheme, Theme } from '@storybook/theming';
4
4
  import { Checkbox, CheckboxContainer } from './Checkbox.js';
5
5
  import { TestStatusIcon } from './TestStatusIcon.js';
6
6
  import { CreeveySuite, isTest } from '../../../../types.js';
7
- import { CreeveyContext } from '../../CreeveyContext.js';
8
- import { KeyboardEventsContext } from '../../KeyboardEventsContext.js';
7
+ import { useCreeveyContext } from '../../CreeveyContext.js';
9
8
 
10
9
  export interface SuiteLinkProps {
11
10
  title: string;
@@ -79,8 +78,7 @@ export const SuiteTitle = styled.span({
79
78
  });
80
79
 
81
80
  export function SuiteLink({ title, suite, 'data-testid': dataTid }: SuiteLinkProps): JSX.Element {
82
- const { onSuiteOpen, onSuiteToggle } = useContext(CreeveyContext);
83
- const { sidebarFocusedItem, setSidebarFocusedItem } = useContext(KeyboardEventsContext);
81
+ const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
84
82
  const checkboxRef = useRef<Checkbox>(null);
85
83
  const buttonRef = useRef<HTMLButtonElement | null>(null);
86
84
 
@@ -1,9 +1,8 @@
1
1
  import React, { JSX, useEffect, useCallback, useContext, useMemo, useRef } from 'react';
2
2
  import { CreeveyTest } from '../../../../types.js';
3
3
  import { TestStatusIcon } from './TestStatusIcon.js';
4
- import { CreeveyContext } from '../../CreeveyContext.js';
4
+ import { useCreeveyContext } from '../../CreeveyContext.js';
5
5
  import { SideBarContext } from './SideBar.js';
6
- import { KeyboardEventsContext } from '../../KeyboardEventsContext.js';
7
6
  import { Button, Container, SuiteContainer, SuiteTitle } from './SuiteLink.js';
8
7
  import { Checkbox, CheckboxContainer } from './Checkbox.js';
9
8
  import { getTestPath } from '../../../shared/helpers.js';
@@ -20,9 +19,8 @@ const TestContainer = styled(SuiteContainer)({
20
19
  });
21
20
 
22
21
  export function TestLink({ title, opened, test }: TestLinkProps): JSX.Element {
23
- const { onSuiteToggle } = useContext(CreeveyContext);
22
+ const { onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
24
23
  const { onOpenTest } = useContext(SideBarContext);
25
- const { sidebarFocusedItem, setSidebarFocusedItem } = useContext(KeyboardEventsContext);
26
24
  const buttonRef = useRef<HTMLButtonElement | null>(null);
27
25
 
28
26
  const emptyResults = (test.results?.length ?? 0) == 0;
@@ -1,20 +1,7 @@
1
- import React, { JSX, useState, useEffect, useCallback, useContext, PropsWithChildren } from 'react';
2
- import { CreeveySuite, isTest, noop } from '../../types.js';
1
+ import React, { JSX, useEffect, useCallback, PropsWithChildren } from 'react';
2
+ import { CreeveySuite, isTest } from '../../types.js';
3
3
  import { CreeveyViewFilter, filterTests, flattenSuite, getSuiteByPath, getTestPath } from '../shared/helpers.js';
4
- import { CreeveyContext } from './CreeveyContext.js';
5
-
6
- export type SuitePath = string[];
7
- export type FocusableItem = null | SuitePath;
8
-
9
- export interface KeyboardEventsContextType {
10
- sidebarFocusedItem: FocusableItem;
11
- setSidebarFocusedItem: (item: FocusableItem) => void;
12
- }
13
-
14
- export const KeyboardEventsContext = React.createContext<KeyboardEventsContextType>({
15
- sidebarFocusedItem: [],
16
- setSidebarFocusedItem: noop,
17
- });
4
+ import { useCreeveyContext } from './CreeveyContext.js';
18
5
 
19
6
  export const KeyboardEvents = ({
20
7
  children,
@@ -24,9 +11,7 @@ export const KeyboardEvents = ({
24
11
  rootSuite: CreeveySuite;
25
12
  filter: CreeveyViewFilter;
26
13
  }>): JSX.Element => {
27
- const [sidebarFocusedItem, setSidebarFocusedItem] = useState<FocusableItem>([]);
28
-
29
- const { onSuiteOpen, onSuiteToggle } = useContext(CreeveyContext);
14
+ const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
30
15
 
31
16
  const suiteList = flattenSuite(filterTests(rootSuite, filter));
32
17
 
@@ -45,63 +30,70 @@ export const KeyboardEvents = ({
45
30
  (e: KeyboardEvent) => {
46
31
  if (sidebarFocusedItem === null) return;
47
32
 
48
- if (e.code === 'Enter') {
49
- if (sidebarFocusedItem.length === 0) return;
50
-
51
- const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
52
- if (!focusedSuite) return;
53
- if (!isTest(focusedSuite)) {
54
- e.preventDefault();
55
- onSuiteOpen(focusedSuite.path, !focusedSuite.opened);
33
+ switch (e.code) {
34
+ case 'Enter': {
35
+ if (sidebarFocusedItem.length === 0) return;
36
+
37
+ const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
38
+ if (!focusedSuite) return;
39
+ if (!isTest(focusedSuite)) {
40
+ e.preventDefault();
41
+ onSuiteOpen(focusedSuite.path, !focusedSuite.opened);
42
+ }
43
+ if (isTest(focusedSuite) && focusedSuite.results?.length == 0) {
44
+ e.preventDefault();
45
+ }
46
+ return;
56
47
  }
57
- if (isTest(focusedSuite) && focusedSuite.results?.length == 0) {
48
+ case 'Space': {
58
49
  e.preventDefault();
50
+ // TODO handle keys in one place
51
+ if (e.altKey) return;
52
+ const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
53
+ if (!focusedSuite) return;
54
+ const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
55
+ onSuiteToggle(path, !focusedSuite.checked);
56
+ return;
59
57
  }
60
- return;
61
- }
62
- if (e.code === 'Space') {
63
- e.preventDefault();
64
- const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
65
- if (!focusedSuite) return;
66
- const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
67
- onSuiteToggle(path, !focusedSuite.checked);
68
- }
69
- if (e.code === 'ArrowDown') {
70
- const currentIndex = sidebarFocusedItem.length === 0 ? -1 : getFocusedItemIndex(sidebarFocusedItem);
71
- if (currentIndex === suiteList.length - 1) return;
72
- const nextSuite = suiteList[currentIndex + 1];
73
- const nextPath = isTest(nextSuite.suite) ? getTestPath(nextSuite.suite) : nextSuite.suite.path;
74
- setSidebarFocusedItem(nextPath);
75
- }
76
- if (e.code === 'ArrowUp') {
77
- const currentIndex = sidebarFocusedItem.length === 0 ? 0 : getFocusedItemIndex(sidebarFocusedItem);
78
- const nextSuite = currentIndex > 0 ? suiteList[currentIndex - 1].suite : rootSuite;
79
- const nextPath = isTest(nextSuite) ? getTestPath(nextSuite) : nextSuite.path;
80
- setSidebarFocusedItem(nextPath);
81
- }
82
-
83
- if (e.code === 'ArrowRight') {
84
- if (sidebarFocusedItem.length === 0) return;
85
- const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
86
-
87
- if (!focusedSuite || isTest(focusedSuite)) return;
88
- onSuiteOpen(focusedSuite.path, true);
89
- }
90
-
91
- if (e.code === 'ArrowLeft') {
92
- if (sidebarFocusedItem.length === 0) return;
93
- const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
94
- if (!focusedSuite) return;
95
- if (!isTest(focusedSuite) && focusedSuite.opened) {
96
- onSuiteOpen(focusedSuite.path, false);
58
+ case 'ArrowDown': {
59
+ const currentIndex = sidebarFocusedItem.length === 0 ? -1 : getFocusedItemIndex(sidebarFocusedItem);
60
+ if (currentIndex === suiteList.length - 1) return;
61
+ const nextSuite = suiteList[currentIndex + 1];
62
+ const nextPath = isTest(nextSuite.suite) ? getTestPath(nextSuite.suite) : nextSuite.suite.path;
63
+ setSidebarFocusedItem(nextPath);
64
+ return;
65
+ }
66
+ case 'ArrowUp': {
67
+ const currentIndex = sidebarFocusedItem.length === 0 ? 0 : getFocusedItemIndex(sidebarFocusedItem);
68
+ const nextSuite = currentIndex > 0 ? suiteList[currentIndex - 1].suite : rootSuite;
69
+ const nextPath = isTest(nextSuite) ? getTestPath(nextSuite) : nextSuite.path;
70
+ setSidebarFocusedItem(nextPath);
97
71
  return;
98
72
  }
73
+ case 'ArrowRight': {
74
+ if (sidebarFocusedItem.length === 0) return;
75
+ const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
99
76
 
100
- const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
101
- setSidebarFocusedItem(path.slice(0, -1));
77
+ if (!focusedSuite || isTest(focusedSuite)) return;
78
+ onSuiteOpen(focusedSuite.path, true);
79
+ return;
80
+ }
81
+ case 'ArrowLeft': {
82
+ if (sidebarFocusedItem.length === 0) return;
83
+ const focusedSuite = getSuiteByPath(rootSuite, sidebarFocusedItem);
84
+ if (!focusedSuite) return;
85
+ if (!isTest(focusedSuite) && focusedSuite.opened) {
86
+ onSuiteOpen(focusedSuite.path, false);
87
+ return;
88
+ }
89
+
90
+ const path = isTest(focusedSuite) ? getTestPath(focusedSuite) : focusedSuite.path;
91
+ setSidebarFocusedItem(path.slice(0, -1));
92
+ return;
93
+ }
102
94
  }
103
95
  },
104
- [onSuiteOpen, onSuiteToggle, rootSuite, suiteList, getFocusedItemIndex, sidebarFocusedItem],
96
+ [onSuiteOpen, onSuiteToggle, rootSuite, suiteList, getFocusedItemIndex, sidebarFocusedItem, setSidebarFocusedItem],
105
97
  );
106
98
 
107
99
  useEffect(() => {
@@ -112,9 +104,5 @@ export const KeyboardEvents = ({
112
104
  };
113
105
  }, [handleKeyDown]);
114
106
 
115
- return (
116
- <KeyboardEventsContext.Provider value={{ sidebarFocusedItem, setSidebarFocusedItem }}>
117
- {children}
118
- </KeyboardEventsContext.Provider>
119
- );
107
+ return <>{children}</>;
120
108
  };
@@ -1,4 +1,5 @@
1
1
  import cluster from 'cluster';
2
+ import path from 'path';
2
3
  import { readConfig, defaultBrowser } from './config.js';
3
4
  import { Options, Config, BrowserConfigObject, isWorkerMessage } from '../types.js';
4
5
  import { logger } from './logger.js';
@@ -8,6 +9,7 @@ import { isInsideDocker } from './utils.js';
8
9
  import { sendWorkerMessage } from './messages.js';
9
10
  import { playwrightDockerFile } from './playwright/docker-file.js';
10
11
  import { buildImage } from './docker.js';
12
+ import { writeFile } from 'fs/promises';
11
13
 
12
14
  async function startWebdriverServer(browser: string, config: Config, options: Options): Promise<string | undefined> {
13
15
  if (config.webdriver === SeleniumWebdriver) {
@@ -79,6 +81,9 @@ export default async function (options: Options): Promise<void> {
79
81
  const { browser = defaultBrowser, update, ui, port } = options;
80
82
  let gridUrl = cluster.isPrimary ? config.gridUrl : options.gridUrl;
81
83
 
84
+ // TODO Add package.json with `"type": "commonjs"` as workaround for esm packages to load `data.js`
85
+ await writeFile(path.join(config.reportDir, 'package.json'), '{"type": "commonjs"}');
86
+
82
87
  // NOTE: We don't need docker nor selenoid for update option
83
88
  if (
84
89
  !(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl)) &&
@@ -56,11 +56,13 @@ export class InternalBrowser {
56
56
  #sessionId: string = v4();
57
57
  #serverHost: string | null = null;
58
58
  #serverPort: number;
59
+ #storybookGlobals?: StorybookGlobals;
59
60
  #unsubscribe: () => void = noop;
60
- constructor(browser: Browser, page: Page, port: number) {
61
+ constructor(browser: Browser, page: Page, port: number, storybookGlobals?: StorybookGlobals) {
61
62
  this.#browser = browser;
62
63
  this.#page = page;
63
64
  this.#serverPort = port;
65
+ this.#storybookGlobals = storybookGlobals;
64
66
  this.#unsubscribe = subscribeOn('shutdown', () => {
65
67
  void this.closeBrowser();
66
68
  });
@@ -112,6 +114,7 @@ export class InternalBrowser {
112
114
 
113
115
  async selectStory(id: string, waitForReady = false): Promise<boolean> {
114
116
  // NOTE: Global variables might be reset after hot reload. I think it's workaround, maybe we need better solution
117
+ await this.updateStorybookGlobals();
115
118
  await this.updateBrowserGlobalVariables();
116
119
  await this.resetMousePosition();
117
120
 
@@ -224,7 +227,7 @@ export class InternalBrowser {
224
227
 
225
228
  // TODO Add debug output
226
229
 
227
- const internalBrowser = new InternalBrowser(browser, page, options.port);
230
+ const internalBrowser = new InternalBrowser(browser, page, options.port, _storybookGlobals);
228
231
 
229
232
  try {
230
233
  if (isShuttingDown.current) return null;
@@ -232,7 +235,6 @@ export class InternalBrowser {
232
235
  browserName,
233
236
  viewport,
234
237
  storybookUrl: address,
235
- storybookGlobals: _storybookGlobals,
236
238
  resolveStorybookUrl: config.resolveStorybookUrl,
237
239
  });
238
240
 
@@ -254,13 +256,11 @@ export class InternalBrowser {
254
256
  browserName,
255
257
  viewport,
256
258
  storybookUrl,
257
- storybookGlobals,
258
259
  resolveStorybookUrl,
259
260
  }: {
260
261
  browserName: string;
261
262
  viewport?: { width: number; height: number };
262
263
  storybookUrl: string;
263
- storybookGlobals?: StorybookGlobals;
264
264
  resolveStorybookUrl?: () => Promise<string>;
265
265
  }) {
266
266
  const sessionId = this.#sessionId;
@@ -278,7 +278,7 @@ export class InternalBrowser {
278
278
  [
279
279
  () => this.openStorybookPage(storybookUrl, resolveStorybookUrl),
280
280
  () => this.waitForStorybook(),
281
- () => this.updateStorybookGlobals(storybookGlobals),
281
+ () => this.updateStorybookGlobals(),
282
282
  () => this.resolveCreeveyHost(),
283
283
  () => this.updateBrowserGlobalVariables(),
284
284
  () => this.resizeViewport(viewport),
@@ -357,13 +357,13 @@ export class InternalBrowser {
357
357
  if (isTimeout) throw new Error('Failed to wait `setStories` event');
358
358
  }
359
359
 
360
- private async updateStorybookGlobals(globals?: StorybookGlobals): Promise<void> {
361
- if (!globals) return;
360
+ private async updateStorybookGlobals(): Promise<void> {
361
+ if (!this.#storybookGlobals) return;
362
362
 
363
363
  logger().debug('Applying storybook globals');
364
364
  await this.#page.evaluate((globals: StorybookGlobals) => {
365
365
  window.__CREEVEY_UPDATE_GLOBALS__(globals);
366
- }, globals);
366
+ }, this.#storybookGlobals);
367
367
  }
368
368
 
369
369
  private async resolveCreeveyHost(): Promise<void> {