creevey 0.10.0-beta.1 → 0.10.0-beta.11
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/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/controller.js +4 -5
- package/dist/client/addon/controller.js.map +1 -1
- package/dist/client/addon/withCreevey.js +18 -34
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +12 -0
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.js +1 -0
- package/dist/client/shared/components/PageHeader/ImagePreview.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.js +23 -5
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +22 -6
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +3 -0
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +2 -2
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +1 -0
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +49 -6
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +1 -3
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
- package/dist/client/web/KeyboardEventsContext.js +62 -57
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/{index-DkmZfG9C.js → index-BE9CL5_G.js} +94 -94
- package/dist/client/web/index.html +1 -1
- package/dist/creevey.js +13 -5
- package/dist/creevey.js.map +1 -1
- package/dist/server/config.js +4 -3
- package/dist/server/config.js.map +1 -1
- package/dist/server/docker.js +2 -2
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +29 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/logger.d.ts +2 -1
- package/dist/server/logger.js +7 -3
- package/dist/server/logger.js.map +1 -1
- package/dist/server/master/api.js +1 -1
- package/dist/server/master/api.js.map +1 -1
- package/dist/server/master/pool.d.ts +3 -3
- package/dist/server/master/pool.js +10 -63
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +64 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +1 -0
- package/dist/server/master/runner.js +4 -1
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/master/server.js +1 -1
- package/dist/server/master/server.js.map +1 -1
- package/dist/server/master/start.js +4 -4
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.js +12 -2
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/internal.d.ts +2 -2
- package/dist/server/playwright/internal.js +56 -44
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.js +1 -1
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +2 -1
- package/dist/server/providers/browser.js.map +1 -1
- package/dist/server/providers/hybrid.js +1 -1
- package/dist/server/providers/hybrid.js.map +1 -1
- package/dist/server/reporter.js +8 -4
- package/dist/server/reporter.js.map +1 -1
- package/dist/server/selenium/internal.d.ts +2 -3
- package/dist/server/selenium/internal.js +116 -90
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +2 -2
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.js +1 -1
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +7 -3
- package/dist/server/telemetry.js.map +1 -1
- package/dist/server/utils.d.ts +2 -1
- package/dist/server/utils.js +13 -3
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +2 -3
- package/dist/server/webdriver.js +10 -9
- package/dist/server/webdriver.js.map +1 -1
- package/dist/server/worker/chai-image.d.ts +1 -2
- package/dist/server/worker/chai-image.js +4 -3
- package/dist/server/worker/chai-image.js.map +1 -1
- package/dist/server/worker/start.js +24 -14
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +30 -11
- package/dist/types.js +13 -1
- package/dist/types.js.map +1 -1
- package/package.json +36 -42
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/controller.ts +13 -6
- package/src/client/addon/withCreevey.ts +25 -13
- package/src/client/shared/components/ImagesView/SwapView.tsx +18 -0
- package/src/client/shared/components/PageHeader/ImagePreview.tsx +1 -0
- package/src/client/shared/components/ResultsPage.tsx +28 -7
- package/src/client/web/CreeveyApp.tsx +25 -7
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyView/SideBar/Search.tsx +3 -3
- package/src/client/web/CreeveyView/SideBar/SideBar.tsx +1 -0
- package/src/client/web/CreeveyView/SideBar/SideBarFooter.tsx +37 -6
- package/src/client/web/CreeveyView/SideBar/SuiteLink.tsx +3 -5
- package/src/client/web/CreeveyView/SideBar/TestLink.tsx +2 -4
- package/src/client/web/KeyboardEventsContext.tsx +61 -73
- package/src/creevey.ts +13 -6
- package/src/server/config.ts +4 -3
- package/src/server/docker.ts +2 -2
- package/src/server/index.ts +27 -4
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +18 -56
- package/src/server/master/queue.ts +64 -0
- package/src/server/master/runner.ts +4 -1
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +7 -4
- package/src/server/playwright/docker-file.ts +14 -2
- package/src/server/playwright/internal.ts +76 -49
- package/src/server/playwright/webdriver.ts +1 -1
- package/src/server/providers/browser.ts +2 -1
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporter.ts +9 -3
- package/src/server/selenium/internal.ts +119 -92
- package/src/server/selenium/selenoid.ts +2 -2
- package/src/server/selenium/webdriver.ts +1 -1
- package/src/server/telemetry.ts +7 -3
- package/src/server/utils.ts +14 -4
- package/src/server/webdriver.ts +10 -15
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/start.ts +25 -16
- package/src/types.ts +32 -13
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
@@ -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,
|
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 {
|
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 } =
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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,
|
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 {
|
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 } =
|
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 {
|
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 } =
|
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,
|
2
|
-
import { CreeveySuite, isTest
|
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 {
|
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
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
101
|
-
|
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
|
};
|
package/src/creevey.ts
CHANGED
@@ -5,14 +5,14 @@ import { Options } from './types.js';
|
|
5
5
|
import { emitWorkerMessage } from './server/messages.js';
|
6
6
|
import { isShuttingDown, shutdownWorkers } from './server/utils.js';
|
7
7
|
import Logger from 'loglevel';
|
8
|
-
import { logger } from './server/logger.js';
|
8
|
+
import { logger, setRootName } from './server/logger.js';
|
9
9
|
|
10
10
|
function shutdownOnException(reason: unknown): void {
|
11
11
|
if (isShuttingDown.current) return;
|
12
12
|
|
13
13
|
const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
|
14
14
|
|
15
|
-
logger.error(error);
|
15
|
+
logger().error(error);
|
16
16
|
|
17
17
|
process.exitCode = -1;
|
18
18
|
if (cluster.isWorker) emitWorkerMessage({ type: 'error', payload: { subtype: 'unknown', error } });
|
@@ -21,7 +21,11 @@ function shutdownOnException(reason: unknown): void {
|
|
21
21
|
|
22
22
|
process.on('uncaughtException', shutdownOnException);
|
23
23
|
process.on('unhandledRejection', shutdownOnException);
|
24
|
+
// TODO SIGINT Stuck with selenium
|
24
25
|
process.on('SIGINT', () => {
|
26
|
+
if (isShuttingDown.current) {
|
27
|
+
process.exit(-1);
|
28
|
+
}
|
25
29
|
isShuttingDown.current = true;
|
26
30
|
});
|
27
31
|
|
@@ -32,9 +36,12 @@ const argv = minimist<Options>(process.argv.slice(2), {
|
|
32
36
|
alias: { port: 'p', config: 'c', debug: 'd', update: 'u' },
|
33
37
|
});
|
34
38
|
|
39
|
+
if ('port' in argv && !isNaN(argv.port)) argv.port = Number(argv.port);
|
40
|
+
if ('browser' in argv && argv.browser) setRootName(argv.browser);
|
41
|
+
|
35
42
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
36
43
|
if (cluster.isPrimary && argv.reporter) {
|
37
|
-
logger.warn(`--reporter option has been removed please describe reporter in config file:
|
44
|
+
logger().warn(`--reporter option has been removed please describe reporter in config file:
|
38
45
|
import { reporters } from 'mocha';
|
39
46
|
|
40
47
|
const config = {
|
@@ -51,13 +58,13 @@ if (cluster.isPrimary && argv.reporter) {
|
|
51
58
|
// @ts-expect-error: define log level for storybook
|
52
59
|
global.LOGLEVEL = argv.trace ? 'trace' : argv.debug ? 'debug' : 'warn';
|
53
60
|
if (argv.trace) {
|
54
|
-
logger.setDefaultLevel(Logger.levels.TRACE);
|
61
|
+
logger().setDefaultLevel(Logger.levels.TRACE);
|
55
62
|
Logger.setDefaultLevel(Logger.levels.TRACE);
|
56
63
|
} else if (argv.debug) {
|
57
|
-
logger.setDefaultLevel(Logger.levels.DEBUG);
|
64
|
+
logger().setDefaultLevel(Logger.levels.DEBUG);
|
58
65
|
Logger.setDefaultLevel(Logger.levels.DEBUG);
|
59
66
|
} else {
|
60
|
-
logger.setDefaultLevel(Logger.levels.INFO);
|
67
|
+
logger().setDefaultLevel(Logger.levels.INFO);
|
61
68
|
Logger.setDefaultLevel(Logger.levels.INFO);
|
62
69
|
}
|
63
70
|
|
package/src/server/config.ts
CHANGED
@@ -11,6 +11,7 @@ export const defaultBrowser = 'chrome';
|
|
11
11
|
|
12
12
|
export const defaultConfig: Omit<Config, 'gridUrl' | 'testsDir' | 'tsConfig' | 'webdriver'> = {
|
13
13
|
disableTelemetry: false,
|
14
|
+
useWorkerQueue: false,
|
14
15
|
useDocker: true,
|
15
16
|
dockerImage: 'aerokube/selenoid:latest-release',
|
16
17
|
dockerImagePlatform: '',
|
@@ -23,8 +24,8 @@ export const defaultConfig: Omit<Config, 'gridUrl' | 'testsDir' | 'tsConfig' | '
|
|
23
24
|
storiesProvider: browserStoriesProvider,
|
24
25
|
maxRetries: 0,
|
25
26
|
testTimeout: 30000,
|
26
|
-
diffOptions: { threshold: 0.
|
27
|
-
odiffOptions: { threshold: 0.
|
27
|
+
diffOptions: { threshold: 0.1, includeAA: false },
|
28
|
+
odiffOptions: { threshold: 0.1, antialiasing: true },
|
28
29
|
browsers: { [defaultBrowser]: true },
|
29
30
|
hooks: {},
|
30
31
|
testsRegex: /\.creevey\.(t|j)s$/,
|
@@ -73,7 +74,7 @@ export async function readConfig(options: Options): Promise<Config> {
|
|
73
74
|
|
74
75
|
if (!configData.webdriver) {
|
75
76
|
const { SeleniumWebdriver } = await import('./selenium/webdriver.js');
|
76
|
-
logger.warn(
|
77
|
+
logger().warn(
|
77
78
|
"Creevey supports `Selenium` and `Playwright` webdrivers. For backward compatibility `Selenium` is used by default, but it might changed in the future. Please explicitly specify one of webdrivers in your Creevey's config",
|
78
79
|
);
|
79
80
|
configData.webdriver = SeleniumWebdriver;
|
package/src/server/docker.ts
CHANGED
@@ -21,7 +21,7 @@ export async function pullImages(
|
|
21
21
|
if (auth) args.authconfig = auth;
|
22
22
|
if (platform) args.platform = platform;
|
23
23
|
|
24
|
-
logger.info('Pull docker images');
|
24
|
+
logger().info('Pull docker images');
|
25
25
|
// TODO Replace with `import from`
|
26
26
|
const { default: yoctoSpinner } = await import('yocto-spinner');
|
27
27
|
for (const image of images) {
|
@@ -61,7 +61,7 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
61
61
|
const images = await docker.listImages({ filters: { label: [`creevey=${imageName}`] } });
|
62
62
|
|
63
63
|
if (images.at(0)) {
|
64
|
-
logger.info(`Image ${imageName} already exists`);
|
64
|
+
logger().info(`Image ${imageName} already exists`);
|
65
65
|
return;
|
66
66
|
}
|
67
67
|
|
package/src/server/index.ts
CHANGED
@@ -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,11 +9,11 @@ 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 { mkdir, 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) {
|
14
16
|
if (cluster.isPrimary) {
|
15
|
-
// TODO Get random free port
|
16
17
|
const { startSelenoidContainer, startSelenoidStandalone } = await import('./selenium/selenoid.js');
|
17
18
|
const gridUrl = 'http://localhost:4444/wd/hub';
|
18
19
|
if (config.useDocker) {
|
@@ -24,8 +25,9 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
|
|
24
25
|
}
|
25
26
|
// TODO Worker might want to use docker image of browser or start standalone selenium
|
26
27
|
} else {
|
27
|
-
|
28
|
+
if (config.gridUrl) return undefined;
|
28
29
|
|
30
|
+
// TODO start standalone playwright server (useDocker == false)
|
29
31
|
const {
|
30
32
|
default: { version },
|
31
33
|
} = await import('playwright-core/package.json', { with: { type: 'json' } });
|
@@ -33,6 +35,8 @@ async function startWebdriverServer(browser: string, config: Config, options: Op
|
|
33
35
|
if (cluster.isWorker) {
|
34
36
|
// TODO Re-use dockerImage
|
35
37
|
|
38
|
+
// TODO Use https://hub.docker.com/r/playwright/chrome
|
39
|
+
// NOTE It will be possible to use `chrome` browserName
|
36
40
|
const { startPlaywrightContainer } = await import('./playwright/docker.js');
|
37
41
|
const { browserName } = config.browsers[browser] as BrowserConfigObject;
|
38
42
|
|
@@ -77,6 +81,10 @@ export default async function (options: Options): Promise<void> {
|
|
77
81
|
const { browser = defaultBrowser, update, ui, port } = options;
|
78
82
|
let gridUrl = cluster.isPrimary ? config.gridUrl : options.gridUrl;
|
79
83
|
|
84
|
+
// TODO Add package.json with `"type": "commonjs"` as workaround for esm packages to load `data.js`
|
85
|
+
await mkdir(config.reportDir, { recursive: true });
|
86
|
+
await writeFile(path.join(config.reportDir, 'package.json'), '{"type": "commonjs"}');
|
87
|
+
|
80
88
|
// NOTE: We don't need docker nor selenoid for update option
|
81
89
|
if (
|
82
90
|
!(gridUrl || (Object.values(config.browsers) as BrowserConfigObject[]).every(({ gridUrl }) => gridUrl)) &&
|
@@ -91,14 +99,29 @@ export default async function (options: Options): Promise<void> {
|
|
91
99
|
return;
|
92
100
|
}
|
93
101
|
case cluster.isPrimary: {
|
94
|
-
|
102
|
+
if (config.webdriver === SeleniumWebdriver) {
|
103
|
+
try {
|
104
|
+
await import('selenium-webdriver');
|
105
|
+
} catch {
|
106
|
+
logger().error('Failed to start Creevey, missing required dependency: "selenium-webdriver"');
|
107
|
+
process.exit(-1);
|
108
|
+
}
|
109
|
+
} else {
|
110
|
+
try {
|
111
|
+
await import('playwright-core');
|
112
|
+
} catch {
|
113
|
+
logger().error('Failed to start Creevey, missing required dependency: "playwright-core"');
|
114
|
+
process.exit(-1);
|
115
|
+
}
|
116
|
+
}
|
117
|
+
logger().info('Starting Master Process');
|
95
118
|
|
96
119
|
const resolveApi = (await import('./master/server.js')).start(config.reportDir, port, ui);
|
97
120
|
|
98
121
|
return (await import('./master/start.js')).start(gridUrl, config, options, resolveApi);
|
99
122
|
}
|
100
123
|
default: {
|
101
|
-
logger.info(`Starting Worker for ${browser}`);
|
124
|
+
logger().info(`Starting Worker for ${browser}`);
|
102
125
|
|
103
126
|
// NOTE: We assume that we pass `gridUrl` to worker CLI options
|
104
127
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
package/src/server/logger.ts
CHANGED
@@ -10,12 +10,16 @@ export const colors = {
|
|
10
10
|
ERROR: chalk.red,
|
11
11
|
};
|
12
12
|
|
13
|
+
let rootName = 'Creevey';
|
14
|
+
|
13
15
|
prefix.reg(Logger);
|
14
16
|
prefix.apply(Logger, {
|
15
|
-
format(level, name =
|
17
|
+
format(level, name = rootName) {
|
16
18
|
const levelColor = colors[level.toUpperCase() as keyof typeof colors];
|
17
19
|
return `[${name}:${chalk.gray(process.pid)}] ${levelColor(level)} =>`;
|
18
20
|
},
|
19
21
|
});
|
20
22
|
|
21
|
-
export const
|
23
|
+
export const setRootName = (newName: string) => (rootName = newName);
|
24
|
+
|
25
|
+
export const logger = () => Logger.getLogger(rootName);
|
package/src/server/master/api.ts
CHANGED
@@ -26,7 +26,7 @@ export default function creeveyApi(runner: Runner): CreeveyApi {
|
|
26
26
|
|
27
27
|
handleMessage(ws: WebSocket, message: WebSocket.Data) {
|
28
28
|
if (typeof message != 'string') {
|
29
|
-
logger.info('unhandled message', message);
|
29
|
+
logger().info('unhandled message', message);
|
30
30
|
return;
|
31
31
|
}
|
32
32
|
|