creevey 0.10.0-beta.42 → 0.10.0-beta.44
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.
- package/CHANGELOG.md +282 -0
- package/dist/client/addon/controller.js +1 -1
- package/dist/client/addon/controller.js.map +1 -1
- package/dist/client/addon/decorator.d.ts +1 -1
- package/dist/client/addon/makeDecorator.d.ts +9 -0
- package/dist/client/addon/makeDecorator.js +48 -0
- package/dist/client/addon/makeDecorator.js.map +1 -0
- package/dist/client/addon/preview.d.ts +1 -1
- package/dist/client/addon/withCreevey.d.ts +2 -1
- package/dist/client/addon/withCreevey.js +3 -20
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/PageHeader/PageHeader.js +13 -4
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +10 -0
- package/dist/client/shared/creeveyClientApi.js.map +1 -1
- package/dist/client/web/CreeveyApp.d.ts +1 -0
- package/dist/client/web/CreeveyApp.js +1 -0
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +1 -0
- package/dist/client/web/CreeveyContext.js +1 -0
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +9 -8
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +13 -3
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +2 -3
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +2 -3
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.js +1 -0
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.js.map +1 -1
- package/dist/client/web/assets/{index-C47njyZV.js → index-BU4jjKVC.js} +68 -68
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +8 -3
- package/dist/client/web/index.js.map +1 -1
- package/dist/creevey.d.ts +1 -1
- package/dist/creevey.js +1 -22
- package/dist/creevey.js.map +1 -1
- package/dist/playwright-reporter.d.ts +2 -0
- package/dist/playwright-reporter.js +5 -0
- package/dist/playwright-reporter.js.map +1 -0
- package/dist/playwright.d.ts +1 -1
- package/dist/server/config.js +8 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/index.js +10 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/master/api.d.ts +15 -5
- package/dist/server/master/api.js +89 -27
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/handlers/capture-handler.d.ts +2 -0
- package/dist/server/master/handlers/capture-handler.js +35 -0
- package/dist/server/master/handlers/capture-handler.js.map +1 -0
- package/dist/server/master/handlers/index.d.ts +4 -0
- package/dist/server/master/handlers/index.js +21 -0
- package/dist/server/master/handlers/index.js.map +1 -0
- package/dist/server/master/handlers/ping-handler.d.ts +2 -0
- package/dist/server/master/handlers/ping-handler.js +7 -0
- package/dist/server/master/handlers/ping-handler.js.map +1 -0
- package/dist/server/master/handlers/static-handler.d.ts +2 -0
- package/dist/server/master/handlers/static-handler.js +32 -0
- package/dist/server/master/handlers/static-handler.js.map +1 -0
- package/dist/server/master/handlers/stories-handler.d.ts +2 -0
- package/dist/server/master/handlers/stories-handler.js +38 -0
- package/dist/server/master/handlers/stories-handler.js.map +1 -0
- package/dist/server/master/master.js +7 -24
- package/dist/server/master/master.js.map +1 -1
- package/dist/server/master/runner.d.ts +4 -6
- package/dist/server/master/runner.js +30 -127
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +77 -87
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.d.ts +1 -2
- package/dist/server/master/start.js +11 -29
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/master/testsManager.d.ts +81 -0
- package/dist/server/master/testsManager.js +281 -0
- package/dist/server/master/testsManager.js.map +1 -0
- package/dist/server/playwright/reporter.d.ts +87 -0
- package/dist/server/playwright/reporter.js +351 -0
- package/dist/server/playwright/reporter.js.map +1 -0
- package/dist/server/selenium/internal.js +20 -2
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +4 -0
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/shutdown.d.ts +1 -0
- package/dist/server/shutdown.js +23 -0
- package/dist/server/shutdown.js.map +1 -0
- package/dist/server/stories.d.ts +0 -1
- package/dist/server/stories.js +0 -12
- package/dist/server/stories.js.map +1 -1
- package/dist/server/ui-update.d.ts +10 -0
- package/dist/server/ui-update.js +39 -0
- package/dist/server/ui-update.js.map +1 -0
- package/dist/server/utils.d.ts +6 -0
- package/dist/server/utils.js +39 -8
- package/dist/server/utils.js.map +1 -1
- package/dist/server/worker/start.js +1 -1
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +14 -8
- package/dist/types.js.map +1 -1
- package/docs/examples/playwright-reporter-example.ts +202 -0
- package/docs/migration-0.9-to-0.10.md +144 -0
- package/docs/playwright-reporter.md +357 -0
- package/package.json +9 -13
- package/src/client/addon/controller.ts +1 -1
- package/src/client/addon/makeDecorator.ts +69 -0
- package/src/client/addon/withCreevey.ts +4 -17
- package/src/client/shared/components/PageHeader/PageHeader.tsx +18 -4
- package/src/client/shared/creeveyClientApi.ts +10 -0
- package/src/client/web/CreeveyApp.tsx +2 -0
- package/src/client/web/CreeveyContext.tsx +2 -0
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +19 -17
- package/src/client/web/CreeveyView/SideBar/SideBarHeader.tsx +18 -3
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +9 -7
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +8 -6
- package/src/client/web/CreeveyView/SideBar/TestsStatus.tsx +1 -0
- package/src/client/web/index.tsx +8 -3
- package/src/creevey.ts +1 -24
- package/src/playwright-reporter.ts +3 -0
- package/src/server/config.ts +9 -1
- package/src/server/index.ts +11 -4
- package/src/server/master/api.ts +95 -26
- package/src/server/master/handlers/capture-handler.ts +39 -0
- package/src/server/master/handlers/index.ts +4 -0
- package/src/server/master/handlers/ping-handler.ts +5 -0
- package/src/server/master/handlers/static-handler.ts +29 -0
- package/src/server/master/handlers/stories-handler.ts +48 -0
- package/src/server/master/master.ts +10 -27
- package/src/server/master/runner.ts +38 -132
- package/src/server/master/server.ts +93 -97
- package/src/server/master/start.ts +17 -41
- package/src/server/master/testsManager.ts +315 -0
- package/src/server/playwright/reporter.ts +386 -0
- package/src/server/selenium/internal.ts +23 -3
- package/src/server/selenium/selenoid.ts +5 -0
- package/src/server/shutdown.ts +19 -0
- package/src/server/stories.ts +1 -12
- package/src/server/ui-update.ts +46 -0
- package/src/server/utils.ts +40 -9
- package/src/server/worker/start.ts +1 -1
- package/src/types.ts +14 -8
@@ -1,5 +1,5 @@
|
|
1
|
-
import type { Renderer
|
2
|
-
import {
|
1
|
+
import type { Renderer } from '@storybook/types';
|
2
|
+
import type { PreviewWeb, StoryStore } from '@storybook/preview-api';
|
3
3
|
import { Channel } from '@storybook/channels';
|
4
4
|
import {
|
5
5
|
CaptureOptions,
|
@@ -13,6 +13,7 @@ import {
|
|
13
13
|
import { serializeRawStories } from '../../shared/index.js';
|
14
14
|
import { getConnectionUrl } from '../shared/helpers.js';
|
15
15
|
import isEqual from 'lodash/isEqual.js';
|
16
|
+
import { makeDecorator } from './makeDecorator.js';
|
16
17
|
|
17
18
|
declare global {
|
18
19
|
interface Window {
|
@@ -120,7 +121,6 @@ function initCreeveyState(): void {
|
|
120
121
|
|
121
122
|
if (prevState.creeveyHost) window.__CREEVEY_SERVER_HOST__ = prevState.creeveyHost;
|
122
123
|
if (prevState.creeveyPort) window.__CREEVEY_SERVER_PORT__ = prevState.creeveyPort;
|
123
|
-
if (prevState.setStoriesCounter) setStoriesCounter = prevState.setStoriesCounter;
|
124
124
|
if (prevState.isTestBrowser) isTestBrowser = prevState.isTestBrowser;
|
125
125
|
|
126
126
|
window.addEventListener('beforeunload', () => {
|
@@ -129,7 +129,6 @@ function initCreeveyState(): void {
|
|
129
129
|
JSON.stringify({
|
130
130
|
creeveyHost: window.__CREEVEY_SERVER_HOST__,
|
131
131
|
creeveyPort: window.__CREEVEY_SERVER_PORT__,
|
132
|
-
setStoriesCounter,
|
133
132
|
isTestBrowser,
|
134
133
|
} as CreeveyTestsState),
|
135
134
|
);
|
@@ -140,7 +139,6 @@ let isTestBrowser = false;
|
|
140
139
|
let captureResolver: () => void;
|
141
140
|
let waitForCreevey: Promise<void>;
|
142
141
|
let creeveyReady: () => void;
|
143
|
-
let setStoriesCounter = 0;
|
144
142
|
let globals = {};
|
145
143
|
|
146
144
|
export function withCreevey(): ReturnType<typeof makeDecorator> {
|
@@ -159,20 +157,9 @@ export function withCreevey(): ReturnType<typeof makeDecorator> {
|
|
159
157
|
document.head.appendChild(style);
|
160
158
|
}
|
161
159
|
|
160
|
+
// TODO Find a way to send stories updates to the server
|
162
161
|
async function getStories(): Promise<StoriesRaw | undefined> {
|
163
162
|
const stories = serializeRawStories(await window.__STORYBOOK_PREVIEW__.extract());
|
164
|
-
const storiesByFiles = new Map<string, StoryContextForEnhancers[]>();
|
165
|
-
Object.values(stories).forEach((story) => {
|
166
|
-
const fileName = story.parameters.fileName as string;
|
167
|
-
const storiesFromFile = storiesByFiles.get(fileName);
|
168
|
-
if (storiesFromFile) storiesFromFile.push(story);
|
169
|
-
else storiesByFiles.set(fileName, [story]);
|
170
|
-
});
|
171
|
-
void fetch(`http://${getConnectionUrl()}/stories`, {
|
172
|
-
method: 'POST',
|
173
|
-
headers: { 'Content-Type': 'application/json' },
|
174
|
-
body: JSON.stringify({ setStoriesCounter, stories: [...storiesByFiles.entries()] }),
|
175
|
-
});
|
176
163
|
return stories;
|
177
164
|
}
|
178
165
|
|
@@ -21,14 +21,14 @@ interface PageHeaderProps {
|
|
21
21
|
}
|
22
22
|
|
23
23
|
const Container = styled.div({
|
24
|
-
|
24
|
+
marginTop: '24px',
|
25
25
|
});
|
26
26
|
|
27
27
|
const ErrorContainer = withTheme(
|
28
28
|
styled.div<{ theme: Theme }>(({ theme }) => ({
|
29
29
|
marginTop: '8px',
|
30
30
|
padding: '8px',
|
31
|
-
background: theme.background.negative
|
31
|
+
background: `${theme.background.negative}20`,
|
32
32
|
color: theme.color.negative,
|
33
33
|
borderRadius: '2px',
|
34
34
|
display: 'flex',
|
@@ -48,8 +48,19 @@ const ErrorContainer = withTheme(
|
|
48
48
|
})),
|
49
49
|
);
|
50
50
|
|
51
|
+
const UpdateModeBanner = withTheme(
|
52
|
+
styled.div(({ theme }) => ({
|
53
|
+
padding: '8px 32px',
|
54
|
+
backgroundColor: `${theme.color.positive}20`,
|
55
|
+
color: theme.color.positive,
|
56
|
+
fontSize: '12px',
|
57
|
+
textAlign: 'center',
|
58
|
+
fontWeight: 'bold',
|
59
|
+
})),
|
60
|
+
);
|
61
|
+
|
51
62
|
const H1 = styled.h1({
|
52
|
-
|
63
|
+
marginLeft: '44px',
|
53
64
|
marginBottom: '8px',
|
54
65
|
});
|
55
66
|
|
@@ -77,7 +88,7 @@ export function PageHeader({
|
|
77
88
|
onImageChange,
|
78
89
|
onViewModeChange,
|
79
90
|
}: PageHeaderProps): JSX.Element | null {
|
80
|
-
const { isReport } = useContext(CreeveyContext);
|
91
|
+
const { isReport, isUpdateMode } = useContext(CreeveyContext);
|
81
92
|
const imageEntires = Object.entries(images) as [string, Images][];
|
82
93
|
|
83
94
|
const handleViewModeChange = (mode: string): void => {
|
@@ -120,6 +131,9 @@ export function PageHeader({
|
|
120
131
|
))}
|
121
132
|
</ImagesEntriesContainer>
|
122
133
|
) : null}
|
134
|
+
{isUpdateMode && (
|
135
|
+
<UpdateModeBanner>Update Mode: Review and approve screenshots from previous test runs</UpdateModeBanner>
|
136
|
+
)}
|
123
137
|
{showViewModes && (
|
124
138
|
<Tabs selected={viewMode} actions={{ onSelect: handleViewModeChange }}>
|
125
139
|
{viewModes.map((x) => (
|
@@ -16,6 +16,7 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
16
16
|
const updateListeners = new Set<(update: CreeveyUpdate) => void>();
|
17
17
|
let statusRequest: Promise<CreeveyStatus> | null = null;
|
18
18
|
let statusResolver: (status: CreeveyStatus) => void = noop;
|
19
|
+
let isUpdateMode = false;
|
19
20
|
|
20
21
|
const ws = new WebSocket(`ws://${getConnectionUrl()}`);
|
21
22
|
|
@@ -30,9 +31,17 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
30
31
|
ws.addEventListener('open', () => {
|
31
32
|
clientApiResolver({
|
32
33
|
start(ids: string[]) {
|
34
|
+
if (isUpdateMode) {
|
35
|
+
console.warn('Tests cannot be started in Update Mode. This mode is for approving screenshots only.');
|
36
|
+
return;
|
37
|
+
}
|
33
38
|
send({ type: 'start', payload: ids });
|
34
39
|
},
|
35
40
|
stop() {
|
41
|
+
if (isUpdateMode) {
|
42
|
+
console.warn('Tests cannot be stopped in Update Mode. This mode is for approving screenshots only.');
|
43
|
+
return;
|
44
|
+
}
|
36
45
|
send({ type: 'stop' });
|
37
46
|
},
|
38
47
|
approve(id: string, retry: number, image: string) {
|
@@ -62,6 +71,7 @@ export async function initCreeveyClientApi(): Promise<CreeveyClientApi> {
|
|
62
71
|
fn(data.payload);
|
63
72
|
});
|
64
73
|
if (data.type == 'status') {
|
74
|
+
isUpdateMode = data.payload.isUpdateMode;
|
65
75
|
statusResolver(data.payload);
|
66
76
|
statusResolver = noop;
|
67
77
|
statusRequest = null;
|
@@ -29,6 +29,7 @@ export interface CreeveyAppProps {
|
|
29
29
|
tests: CreeveySuite;
|
30
30
|
isRunning: boolean;
|
31
31
|
isReport: boolean;
|
32
|
+
isUpdateMode: boolean;
|
32
33
|
};
|
33
34
|
}
|
34
35
|
|
@@ -214,6 +215,7 @@ export function CreeveyApp({ api, initialState }: CreeveyAppProps): JSX.Element
|
|
214
215
|
onSuiteToggle: handleSuiteToggle,
|
215
216
|
sidebarFocusedItem,
|
216
217
|
setSidebarFocusedItem,
|
218
|
+
isUpdateMode: initialState.isUpdateMode,
|
217
219
|
}}
|
218
220
|
>
|
219
221
|
<ThemeProvider theme={ensure(themes[theme])}>
|
@@ -7,6 +7,7 @@ export type FocusableItem = null | SuitePath;
|
|
7
7
|
export interface CreeveyContextType {
|
8
8
|
isReport: boolean;
|
9
9
|
isRunning: boolean;
|
10
|
+
isUpdateMode: boolean;
|
10
11
|
onStop: () => void;
|
11
12
|
onImageNext?: () => void;
|
12
13
|
onImageApprove?: () => void;
|
@@ -21,6 +22,7 @@ export interface CreeveyContextType {
|
|
21
22
|
export const CreeveyContext = React.createContext<CreeveyContextType>({
|
22
23
|
isReport: true,
|
23
24
|
isRunning: false,
|
25
|
+
isUpdateMode: false,
|
24
26
|
onImageNext: noop,
|
25
27
|
onImageApprove: noop,
|
26
28
|
onApproveAll: noop,
|
@@ -48,23 +48,25 @@ export function SideBarFooter(): JSX.Element {
|
|
48
48
|
}, [handleKeyDown, handleKeyUp]);
|
49
49
|
|
50
50
|
return (
|
51
|
-
|
52
|
-
<
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
<>
|
52
|
+
<Sticky>
|
53
|
+
<Container>
|
54
|
+
{isAlt ? (
|
55
|
+
<Button variant="outline" size="medium" onClick={onImageNext} disabled={!onImageApprove}>
|
56
|
+
Next
|
57
|
+
<ChevronRightIcon />
|
58
|
+
</Button>
|
59
|
+
) : (
|
60
|
+
<Button variant="solid" size="medium" onClick={onImageApprove} disabled={!onImageApprove}>
|
61
|
+
Approve
|
62
|
+
<ChevronRightIcon />
|
63
|
+
</Button>
|
64
|
+
)}
|
65
|
+
<Button variant="outline" size="medium" onClick={onApproveAll}>
|
66
|
+
Approve all
|
57
67
|
</Button>
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
<ChevronRightIcon />
|
62
|
-
</Button>
|
63
|
-
)}
|
64
|
-
<Button variant="outline" size="medium" onClick={onApproveAll}>
|
65
|
-
Approve all
|
66
|
-
</Button>
|
67
|
-
</Container>
|
68
|
-
</Sticky>
|
68
|
+
</Container>
|
69
|
+
</Sticky>
|
70
|
+
</>
|
69
71
|
);
|
70
72
|
}
|
@@ -36,6 +36,7 @@ const Container = styled.div({
|
|
36
36
|
const Header = styled.h2({
|
37
37
|
fontWeight: 'normal',
|
38
38
|
margin: 0,
|
39
|
+
padding: '2px 6px',
|
39
40
|
});
|
40
41
|
|
41
42
|
const Button = withTheme(
|
@@ -58,12 +59,23 @@ const Button = withTheme(
|
|
58
59
|
})),
|
59
60
|
);
|
60
61
|
|
62
|
+
const UpdateModeDescription = withTheme(
|
63
|
+
styled.div(({ theme }) => ({
|
64
|
+
fontSize: '0.8em',
|
65
|
+
marginTop: '4px',
|
66
|
+
padding: '2px 6px',
|
67
|
+
color: theme.color.positive,
|
68
|
+
backgroundColor: `${theme.color.positive}20`,
|
69
|
+
})),
|
70
|
+
);
|
71
|
+
|
61
72
|
const MarginContainer = styled.div<{ left?: string; right?: string; top?: string; bottom?: string }>(
|
62
73
|
({ left, right, top, bottom }) => ({
|
63
74
|
marginLeft: left ?? 0,
|
64
75
|
marginRight: right ?? 0,
|
65
76
|
marginTop: top ?? 0,
|
66
77
|
marginBottom: bottom ?? 0,
|
78
|
+
padding: '2px 6px',
|
67
79
|
}),
|
68
80
|
);
|
69
81
|
|
@@ -92,7 +104,7 @@ export function SideBarHeader({
|
|
92
104
|
onFilterChange,
|
93
105
|
canStart,
|
94
106
|
}: SideBarHeaderProps): JSX.Element {
|
95
|
-
const { isReport, isRunning } = useContext(CreeveyContext);
|
107
|
+
const { isReport, isRunning, isUpdateMode } = useContext(CreeveyContext);
|
96
108
|
const [filterInput, setFilterInput] = useState('');
|
97
109
|
|
98
110
|
const handleClickByStatus = (status: TestStatus): void => {
|
@@ -115,9 +127,12 @@ export function SideBarHeader({
|
|
115
127
|
<Container>
|
116
128
|
<div>
|
117
129
|
<Header>colin.creevey</Header>
|
130
|
+
{isUpdateMode && (
|
131
|
+
<UpdateModeDescription>Review and approve screenshots from previous test runs</UpdateModeDescription>
|
132
|
+
)}
|
118
133
|
<TestsStatus {...testsStatus} onClickByStatus={handleClickByStatus} />
|
119
134
|
</div>
|
120
|
-
{isReport ? null : (
|
135
|
+
{isReport || isUpdateMode ? null : (
|
121
136
|
<MarginContainer top="10px">
|
122
137
|
{isRunning ? (
|
123
138
|
<Button variant="outline" onClick={onStop}>
|
@@ -131,7 +146,7 @@ export function SideBarHeader({
|
|
131
146
|
</MarginContainer>
|
132
147
|
)}
|
133
148
|
</Container>
|
134
|
-
<MarginContainer top="
|
149
|
+
<MarginContainer top="12px" bottom="12px">
|
135
150
|
<Search onChange={handleInputFilterChange} value={filterInput} />
|
136
151
|
</MarginContainer>
|
137
152
|
</Sticky>
|
@@ -78,7 +78,7 @@ export const SuiteTitle = styled.span({
|
|
78
78
|
});
|
79
79
|
|
80
80
|
export function SuiteLink({ title, suite, 'data-testid': dataTid }: SuiteLinkProps): JSX.Element {
|
81
|
-
const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
|
81
|
+
const { onSuiteOpen, onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem, isUpdateMode } = useCreeveyContext();
|
82
82
|
const checkboxRef = useRef<Checkbox>(null);
|
83
83
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
84
84
|
|
@@ -117,12 +117,14 @@ export function SuiteLink({ title, suite, 'data-testid': dataTid }: SuiteLinkPro
|
|
117
117
|
return (
|
118
118
|
<Container focused={isSuiteFocused}>
|
119
119
|
<CheckboxContainer>
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
120
|
+
{!isUpdateMode && (
|
121
|
+
<Checkbox
|
122
|
+
ref={checkboxRef}
|
123
|
+
checked={suite.skip ? false : suite.checked}
|
124
|
+
disabled={Boolean(suite.skip)}
|
125
|
+
onValueChange={handleCheck}
|
126
|
+
/>
|
127
|
+
)}
|
126
128
|
</CheckboxContainer>
|
127
129
|
<Button onClick={handleOpen} onFocus={handleFocus} data-testid={dataTid} ref={buttonRef}>
|
128
130
|
<SuiteContainer padding={(suite.path.length - 1) * 8}>
|
@@ -19,7 +19,7 @@ const TestContainer = styled(SuiteContainer)({
|
|
19
19
|
});
|
20
20
|
|
21
21
|
export function TestLink({ title, opened, test }: TestLinkProps): JSX.Element {
|
22
|
-
const { onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem } = useCreeveyContext();
|
22
|
+
const { onSuiteToggle, sidebarFocusedItem, setSidebarFocusedItem, isUpdateMode } = useCreeveyContext();
|
23
23
|
const { onOpenTest } = useContext(SideBarContext);
|
24
24
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
25
25
|
|
@@ -55,11 +55,13 @@ export function TestLink({ title, opened, test }: TestLinkProps): JSX.Element {
|
|
55
55
|
{/* NOTE Little hack to allow click on checkbox and don't trigger Button click */}
|
56
56
|
{/* We can use other approach, but checkbox has vertical-align: top */}
|
57
57
|
<CheckboxContainer>
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
{!isUpdateMode && (
|
59
|
+
<Checkbox
|
60
|
+
checked={test.skip ? false : test.checked}
|
61
|
+
disabled={Boolean(test.skip)}
|
62
|
+
onValueChange={handleCheck}
|
63
|
+
/>
|
64
|
+
)}
|
63
65
|
</CheckboxContainer>
|
64
66
|
<Button onClick={handleOpen} disabled={emptyResults} ref={buttonRef}>
|
65
67
|
<TestContainer padding={(testPath.length + 1) * 8}>
|
package/src/client/web/index.tsx
CHANGED
@@ -36,11 +36,11 @@ const CreeveyAppAsync = React.lazy(async () => {
|
|
36
36
|
// NOTE: Failed to get status from API
|
37
37
|
// NOTE: It might happen on circle ci from artifact
|
38
38
|
isReport = true;
|
39
|
-
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
|
39
|
+
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
|
40
40
|
}
|
41
41
|
} else {
|
42
42
|
isReport = true;
|
43
|
-
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [] };
|
43
|
+
creeveyStatus = { isRunning: false, tests: await loadCreeveyData(), browsers: [], isUpdateMode: false };
|
44
44
|
}
|
45
45
|
|
46
46
|
return {
|
@@ -48,7 +48,12 @@ const CreeveyAppAsync = React.lazy(async () => {
|
|
48
48
|
return (
|
49
49
|
<CreeveyApp
|
50
50
|
api={creeveyApi}
|
51
|
-
initialState={{
|
51
|
+
initialState={{
|
52
|
+
isReport,
|
53
|
+
isRunning: creeveyStatus.isRunning,
|
54
|
+
tests: treeifyTests(creeveyStatus.tests),
|
55
|
+
isUpdateMode: creeveyStatus.isUpdateMode,
|
56
|
+
}}
|
52
57
|
/>
|
53
58
|
);
|
54
59
|
},
|
package/src/creevey.ts
CHANGED
@@ -2,32 +2,9 @@ import cluster from 'cluster';
|
|
2
2
|
import minimist from 'minimist';
|
3
3
|
import creevey from './server/index.js';
|
4
4
|
import { Options } from './types.js';
|
5
|
-
import { emitWorkerMessage } from './server/messages.js';
|
6
|
-
import { isShuttingDown, shutdownWorkers } from './server/utils.js';
|
7
5
|
import Logger from 'loglevel';
|
8
6
|
import { logger, setRootName } from './server/logger.js';
|
9
|
-
|
10
|
-
function shutdownOnException(reason: unknown): void {
|
11
|
-
if (isShuttingDown.current) return;
|
12
|
-
|
13
|
-
const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
|
14
|
-
|
15
|
-
logger().error(error);
|
16
|
-
|
17
|
-
process.exitCode = -1;
|
18
|
-
if (cluster.isWorker) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error } });
|
19
|
-
if (cluster.isPrimary) void shutdownWorkers();
|
20
|
-
}
|
21
|
-
|
22
|
-
process.on('uncaughtException', shutdownOnException);
|
23
|
-
process.on('unhandledRejection', shutdownOnException);
|
24
|
-
// TODO SIGINT Stuck with selenium
|
25
|
-
process.on('SIGINT', () => {
|
26
|
-
if (isShuttingDown.current) {
|
27
|
-
process.exit(-1);
|
28
|
-
}
|
29
|
-
isShuttingDown.current = true;
|
30
|
-
});
|
7
|
+
import './server/shutdown.js';
|
31
8
|
|
32
9
|
const argv = minimist<Options>(process.argv.slice(2), {
|
33
10
|
string: ['browser', 'config', 'reporter', 'reportDir', 'screenDir', 'gridUrl', 'storybookUrl', 'storybookPort'],
|
package/src/server/config.ts
CHANGED
@@ -15,7 +15,7 @@ export const defaultConfig: Omit<Config, 'gridUrl' | 'tsConfig' | 'webdriver'> =
|
|
15
15
|
disableTelemetry: false,
|
16
16
|
useWorkerQueue: false,
|
17
17
|
useDocker: true,
|
18
|
-
dockerImage: 'aerokube/selenoid:latest
|
18
|
+
dockerImage: 'aerokube/selenoid:latest', // TODO What about playwright?
|
19
19
|
dockerImagePlatform: '',
|
20
20
|
pullImages: true,
|
21
21
|
failFast: false,
|
@@ -83,6 +83,14 @@ export async function readConfig(options: Options): Promise<Config> {
|
|
83
83
|
configData.webdriver = SeleniumWebdriver;
|
84
84
|
}
|
85
85
|
|
86
|
+
for (const key in configData) {
|
87
|
+
const configKey = key as keyof typeof configData;
|
88
|
+
if (configData[configKey] === undefined) {
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
90
|
+
delete configData[configKey];
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
86
94
|
Object.assign(userConfig, configData);
|
87
95
|
}
|
88
96
|
|
package/src/server/index.ts
CHANGED
@@ -87,6 +87,7 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
|
|
87
87
|
}
|
88
88
|
}
|
89
89
|
|
90
|
+
// TODO Why docker containers are not deleting after stop?
|
90
91
|
export default async function (options: Options): Promise<void> {
|
91
92
|
const config = await readConfig(options);
|
92
93
|
const { browser = defaultBrowser, update, ui, port } = options;
|
@@ -104,7 +105,7 @@ export default async function (options: Options): Promise<void> {
|
|
104
105
|
gridUrl = await startWebdriverServer(browser, config, options);
|
105
106
|
}
|
106
107
|
|
107
|
-
if (cluster.isPrimary) {
|
108
|
+
if (cluster.isPrimary && !update) {
|
108
109
|
const [localUrl, remoteUrl] = getStorybookUrl(config, options);
|
109
110
|
|
110
111
|
if (options.storybookStart) {
|
@@ -145,7 +146,15 @@ export default async function (options: Options): Promise<void> {
|
|
145
146
|
}
|
146
147
|
}
|
147
148
|
|
149
|
+
await import('./shutdown.js');
|
150
|
+
|
148
151
|
switch (true) {
|
152
|
+
case Boolean(update) && Boolean(ui): {
|
153
|
+
// New UI Update mode
|
154
|
+
const { uiUpdate } = await import('./ui-update.js');
|
155
|
+
await uiUpdate(config, port);
|
156
|
+
return;
|
157
|
+
}
|
149
158
|
case Boolean(update): {
|
150
159
|
(await import('./update.js')).update(config, typeof update == 'string' ? update : undefined);
|
151
160
|
return;
|
@@ -168,9 +177,7 @@ export default async function (options: Options): Promise<void> {
|
|
168
177
|
}
|
169
178
|
logger().info('Starting Master Process');
|
170
179
|
|
171
|
-
|
172
|
-
|
173
|
-
return (await import('./master/start.js')).start(gridUrl, config, options, resolveApi);
|
180
|
+
return (await import('./master/start.js')).start(gridUrl, config, options);
|
174
181
|
}
|
175
182
|
default: {
|
176
183
|
logger().info(`Starting Worker for ${browser}`);
|
package/src/server/master/api.ts
CHANGED
@@ -1,59 +1,128 @@
|
|
1
|
-
import WebSocket from 'ws';
|
2
1
|
import Runner from './runner.js';
|
3
2
|
import { Request, Response, CreeveyUpdate } from '../../types.js';
|
4
3
|
import { logger } from '../logger.js';
|
4
|
+
import { TestsManager } from './testsManager.js';
|
5
|
+
import HyperExpress from 'hyper-express';
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
interface CustomWSServer {
|
8
|
+
clients: Set<HyperExpress.Websocket>;
|
9
|
+
publish: (message: string) => void;
|
9
10
|
}
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
16
|
-
});
|
12
|
+
// Helper function for HyperExpress WebSocket broadcasting
|
13
|
+
function broadcastHyperExpress(wss: CustomWSServer, message: Response): void {
|
14
|
+
const serializedMessage = JSON.stringify(message);
|
15
|
+
wss.publish(serializedMessage);
|
17
16
|
}
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
// The class-based implementation of CreeveyApi
|
19
|
+
export class CreeveyApi {
|
20
|
+
private runner: Runner | null = null;
|
21
|
+
private testsManager: TestsManager;
|
22
|
+
private wss: CustomWSServer | null = null;
|
23
|
+
|
24
|
+
constructor(testsManager: TestsManager, runner?: Runner) {
|
25
|
+
this.testsManager = testsManager;
|
26
|
+
|
27
|
+
// Use the provided runner in normal mode, or keep as null in update mode
|
28
|
+
if (runner) {
|
29
|
+
this.runner = runner;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
subscribe(wss: CustomWSServer): void {
|
34
|
+
this.wss = wss;
|
35
|
+
|
36
|
+
// If we have a runner, subscribe to its updates
|
37
|
+
if (this.runner) {
|
38
|
+
this.runner.on('update', (payload: CreeveyUpdate) => {
|
39
|
+
this.broadcastUpdate(payload);
|
40
|
+
});
|
41
|
+
} else {
|
42
|
+
// Subscribe to TestsManager updates
|
43
|
+
this.testsManager.on('update', (update: CreeveyUpdate) => {
|
44
|
+
this.broadcastUpdate(update);
|
24
45
|
});
|
25
|
-
}
|
46
|
+
}
|
47
|
+
}
|
26
48
|
|
27
|
-
|
28
|
-
|
49
|
+
handleMessage(ws: HyperExpress.Websocket, message: string | Buffer): void {
|
50
|
+
if (typeof message != 'string') {
|
51
|
+
if (Buffer.isBuffer(message)) {
|
52
|
+
message = message.toString('utf-8');
|
53
|
+
} else {
|
29
54
|
logger().info('unhandled message', message);
|
30
55
|
return;
|
31
56
|
}
|
57
|
+
}
|
32
58
|
|
33
|
-
|
59
|
+
const command = JSON.parse(message) as Request;
|
60
|
+
const sendResponse = (response: Response) => {
|
61
|
+
ws.send(JSON.stringify(response));
|
62
|
+
};
|
34
63
|
|
64
|
+
if (this.runner) {
|
65
|
+
// Normal mode handling with runner
|
35
66
|
switch (command.type) {
|
36
67
|
case 'status': {
|
37
|
-
|
68
|
+
sendResponse({ type: 'status', payload: this.runner.status });
|
38
69
|
return;
|
39
70
|
}
|
40
71
|
case 'start': {
|
41
|
-
runner.start(command.payload);
|
72
|
+
this.runner.start(command.payload);
|
42
73
|
return;
|
43
74
|
}
|
44
75
|
case 'stop': {
|
45
|
-
runner.stop();
|
76
|
+
this.runner.stop();
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
case 'approve': {
|
80
|
+
void this.runner.approve(command.payload);
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
case 'approveAll': {
|
84
|
+
void this.runner.approveAll();
|
46
85
|
return;
|
47
86
|
}
|
87
|
+
}
|
88
|
+
} else {
|
89
|
+
// In update mode, only approve and approveAll commands are allowed
|
90
|
+
switch (command.type) {
|
48
91
|
case 'approve': {
|
49
|
-
void
|
92
|
+
void this.testsManager.approve(command.payload);
|
50
93
|
return;
|
51
94
|
}
|
52
95
|
case 'approveAll': {
|
53
|
-
void
|
96
|
+
void this.testsManager.approveAll();
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
case 'status': {
|
100
|
+
// In update mode, respond with static status including tests data
|
101
|
+
sendResponse({
|
102
|
+
type: 'status',
|
103
|
+
payload: {
|
104
|
+
isRunning: false,
|
105
|
+
tests: this.testsManager.getTestsData(),
|
106
|
+
browsers: [],
|
107
|
+
isUpdateMode: true,
|
108
|
+
},
|
109
|
+
});
|
110
|
+
return;
|
111
|
+
}
|
112
|
+
default: {
|
113
|
+
// Ignore other commands in update mode
|
114
|
+
logger().debug(`Command ${command.type} is not available in update mode`);
|
54
115
|
return;
|
55
116
|
}
|
56
117
|
}
|
57
|
-
}
|
58
|
-
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
private broadcastUpdate(payload: CreeveyUpdate): void {
|
122
|
+
if (!this.wss) return;
|
123
|
+
|
124
|
+
const message: Response = { type: 'update', payload };
|
125
|
+
|
126
|
+
broadcastHyperExpress(this.wss, message);
|
127
|
+
}
|
59
128
|
}
|