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.
- package/README.md +19 -41
- package/dist/client/addon/components/Addon.js +17 -7
- package/dist/client/addon/components/Addon.js.map +1 -1
- package/dist/client/addon/components/Panel.js +2 -2
- package/dist/client/addon/components/Panel.js.map +1 -1
- package/dist/client/addon/components/Tools.js +17 -7
- package/dist/client/addon/components/Tools.js.map +1 -1
- package/dist/client/addon/withCreevey.d.ts +2 -1
- package/dist/client/addon/withCreevey.js +11 -1
- package/dist/client/addon/withCreevey.js.map +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/BlendView.js +17 -7
- package/dist/client/shared/components/ImagesView/BlendView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SideBySideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SideBySideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SlideView.js +17 -7
- package/dist/client/shared/components/ImagesView/SlideView.js.map +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.d.ts +1 -1
- package/dist/client/shared/components/ImagesView/SwapView.js +29 -7
- package/dist/client/shared/components/ImagesView/SwapView.js.map +1 -1
- package/dist/client/shared/components/PageHeader/ImagePreview.d.ts +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/PageHeader/PageHeader.js +20 -8
- package/dist/client/shared/components/PageHeader/PageHeader.js.map +1 -1
- package/dist/client/shared/components/ResultsPage.d.ts +1 -1
- package/dist/client/shared/components/ResultsPage.js +43 -13
- package/dist/client/shared/components/ResultsPage.js.map +1 -1
- package/dist/client/shared/creeveyClientApi.js +8 -1
- package/dist/client/shared/creeveyClientApi.js.map +1 -1
- package/dist/client/shared/helpers.d.ts +1 -3
- package/dist/client/shared/helpers.js +4 -19
- package/dist/client/shared/helpers.js.map +1 -1
- package/dist/client/web/CreeveyApp.js +42 -14
- package/dist/client/web/CreeveyApp.js.map +1 -1
- package/dist/client/web/CreeveyContext.d.ts +5 -0
- package/dist/client/web/CreeveyContext.js +20 -7
- package/dist/client/web/CreeveyContext.js.map +1 -1
- package/dist/client/web/CreeveyLoader.js +2 -2
- package/dist/client/web/CreeveyLoader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/Search.js +19 -9
- package/dist/client/web/CreeveyView/SideBar/Search.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBar.js +18 -7
- package/dist/client/web/CreeveyView/SideBar/SideBar.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js +60 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarFooter.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js +17 -7
- package/dist/client/web/CreeveyView/SideBar/SideBarHeader.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.d.ts +2 -2
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/SuiteLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestLink.js +18 -10
- package/dist/client/web/CreeveyView/SideBar/TestLink.js.map +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestStatusIcon.d.ts +1 -1
- package/dist/client/web/CreeveyView/SideBar/TestsStatus.d.ts +1 -1
- package/dist/client/web/KeyboardEventsContext.d.ts +1 -8
- package/dist/client/web/KeyboardEventsContext.js +79 -64
- package/dist/client/web/KeyboardEventsContext.js.map +1 -1
- package/dist/client/web/assets/index-C47njyZV.js +802 -0
- package/dist/client/web/index.html +1 -1
- package/dist/client/web/index.js +17 -7
- package/dist/client/web/index.js.map +1 -1
- package/dist/client/web/themes.d.ts +2 -0
- package/dist/client/web/themes.js +22 -0
- package/dist/client/web/themes.js.map +1 -0
- package/dist/creevey.js +16 -9
- package/dist/creevey.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/server/config.d.ts +1 -1
- package/dist/server/config.js +30 -7
- package/dist/server/config.js.map +1 -1
- package/dist/server/connection.d.ts +3 -0
- package/dist/server/connection.js +28 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/docker.d.ts +1 -1
- package/dist/server/docker.js +56 -32
- package/dist/server/docker.js.map +1 -1
- package/dist/server/index.js +64 -11
- 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 +4 -3
- package/dist/server/master/pool.js +13 -66
- package/dist/server/master/pool.js.map +1 -1
- package/dist/server/master/queue.d.ts +13 -0
- package/dist/server/master/queue.js +71 -0
- package/dist/server/master/queue.js.map +1 -0
- package/dist/server/master/runner.d.ts +3 -0
- package/dist/server/master/runner.js +78 -10
- 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 +13 -11
- package/dist/server/master/start.js.map +1 -1
- package/dist/server/playwright/docker-file.d.ts +1 -1
- package/dist/server/playwright/docker-file.js +15 -6
- package/dist/server/playwright/docker-file.js.map +1 -1
- package/dist/server/playwright/docker.d.ts +2 -1
- package/dist/server/playwright/docker.js +10 -2
- package/dist/server/playwright/docker.js.map +1 -1
- package/dist/server/playwright/index-source.mjs +16 -0
- package/dist/server/playwright/internal.d.ts +6 -6
- package/dist/server/playwright/internal.js +143 -91
- package/dist/server/playwright/internal.js.map +1 -1
- package/dist/server/playwright/webdriver.d.ts +1 -1
- package/dist/server/playwright/webdriver.js +5 -8
- package/dist/server/playwright/webdriver.js.map +1 -1
- package/dist/server/providers/browser.js +6 -4
- 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/reporters/creevey.d.ts +7 -0
- package/dist/server/reporters/creevey.js +63 -0
- package/dist/server/reporters/creevey.js.map +1 -0
- package/dist/server/reporters/index.d.ts +2 -0
- package/dist/server/reporters/index.js +16 -0
- package/dist/server/reporters/index.js.map +1 -0
- package/dist/server/reporters/junit.d.ts +16 -0
- package/dist/server/reporters/junit.js +165 -0
- package/dist/server/reporters/junit.js.map +1 -0
- package/dist/server/reporters/teamcity.d.ts +7 -0
- package/dist/server/reporters/teamcity.js +60 -0
- package/dist/server/reporters/teamcity.js.map +1 -0
- package/dist/server/selenium/internal.d.ts +3 -4
- package/dist/server/selenium/internal.js +127 -108
- package/dist/server/selenium/internal.js.map +1 -1
- package/dist/server/selenium/selenoid.js +8 -6
- package/dist/server/selenium/selenoid.js.map +1 -1
- package/dist/server/selenium/webdriver.d.ts +1 -1
- package/dist/server/selenium/webdriver.js +5 -9
- package/dist/server/selenium/webdriver.js.map +1 -1
- package/dist/server/telemetry.js +2 -2
- package/dist/server/testsFiles/parser.js +45 -5
- package/dist/server/testsFiles/parser.js.map +1 -1
- package/dist/server/utils.d.ts +19 -1
- package/dist/server/utils.js +87 -8
- package/dist/server/utils.js.map +1 -1
- package/dist/server/webdriver.d.ts +5 -4
- package/dist/server/webdriver.js +23 -10
- 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/context.d.ts +3 -0
- package/dist/server/worker/context.js +15 -0
- package/dist/server/worker/context.js.map +1 -0
- package/dist/server/worker/match-image.d.ts +4 -4
- package/dist/server/worker/match-image.js +7 -4
- package/dist/server/worker/match-image.js.map +1 -1
- package/dist/server/worker/start.js +47 -73
- package/dist/server/worker/start.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/types.d.ts +46 -10
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/docs/cli.md +12 -0
- package/docs/config.md +179 -165
- package/docs/storybook.md +60 -0
- package/docs/tests.md +50 -45
- package/package.json +64 -63
- package/src/client/addon/components/Panel.tsx +2 -2
- package/src/client/addon/withCreevey.ts +10 -2
- 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/PageHeader/PageHeader.tsx +4 -2
- package/src/client/shared/components/ResultsPage.tsx +31 -8
- package/src/client/shared/creeveyClientApi.ts +9 -1
- package/src/client/shared/helpers.ts +4 -24
- package/src/client/web/CreeveyApp.tsx +27 -8
- package/src/client/web/CreeveyContext.tsx +9 -0
- package/src/client/web/CreeveyLoader.tsx +1 -1
- 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/client/web/themes.ts +24 -0
- package/src/creevey.ts +16 -10
- package/src/server/config.ts +30 -7
- package/src/server/connection.ts +26 -0
- package/src/server/docker.ts +63 -34
- package/src/server/index.ts +72 -14
- package/src/server/logger.ts +6 -2
- package/src/server/master/api.ts +1 -1
- package/src/server/master/pool.ts +23 -59
- package/src/server/master/queue.ts +77 -0
- package/src/server/master/runner.ts +96 -10
- package/src/server/master/server.ts +1 -1
- package/src/server/master/start.ts +16 -11
- package/src/server/playwright/docker-file.ts +18 -6
- package/src/server/playwright/docker.ts +16 -3
- package/src/server/playwright/index-source.mjs +16 -0
- package/src/server/playwright/internal.ts +182 -111
- package/src/server/playwright/webdriver.ts +6 -9
- package/src/server/providers/browser.ts +6 -4
- package/src/server/providers/hybrid.ts +1 -1
- package/src/server/reporters/creevey.ts +71 -0
- package/src/server/reporters/index.ts +11 -0
- package/src/server/reporters/junit.ts +205 -0
- package/src/server/reporters/teamcity.ts +74 -0
- package/src/server/selenium/internal.ts +131 -116
- package/src/server/selenium/selenoid.ts +8 -6
- package/src/server/selenium/webdriver.ts +6 -10
- package/src/server/telemetry.ts +2 -2
- package/src/server/testsFiles/parser.ts +52 -4
- package/src/server/utils.ts +97 -9
- package/src/server/webdriver.ts +24 -16
- package/src/server/worker/chai-image.ts +4 -4
- package/src/server/worker/context.ts +14 -0
- package/src/server/worker/match-image.ts +12 -8
- package/src/server/worker/start.ts +51 -86
- package/src/shared/index.ts +1 -1
- package/src/types.ts +50 -11
- package/types/global.d.ts +1 -0
- package/.yarnrc.yml +0 -1
- package/chromatic.config.json +0 -5
- package/dist/client/web/assets/index-DkmZfG9C.js +0 -591
- package/dist/server/reporter.d.ts +0 -26
- package/dist/server/reporter.js +0 -108
- package/dist/server/reporter.js.map +0 -1
- package/src/server/reporter.ts +0 -138
@@ -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
|
};
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { useEffect, useState } from 'react';
|
2
|
+
import { themes, ThemeVars } from '@storybook/theming';
|
3
|
+
import { isDefined } from '../../types.js';
|
4
|
+
|
5
|
+
const CREEVEY_THEME = 'Creevey_theme';
|
6
|
+
|
7
|
+
function isTheme(theme?: string | null): theme is ThemeVars['base'] {
|
8
|
+
return isDefined(theme) && Object.prototype.hasOwnProperty.call(themes, theme);
|
9
|
+
}
|
10
|
+
|
11
|
+
function initialTheme(): ThemeVars['base'] {
|
12
|
+
const theme = localStorage.getItem(CREEVEY_THEME);
|
13
|
+
return isTheme(theme) ? theme : 'light';
|
14
|
+
}
|
15
|
+
|
16
|
+
export function useTheme(): [ThemeVars['base'], (theme: ThemeVars['base']) => void] {
|
17
|
+
const [theme, setTheme] = useState<ThemeVars['base']>(initialTheme());
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
localStorage.setItem(CREEVEY_THEME, theme);
|
21
|
+
}, [theme]);
|
22
|
+
|
23
|
+
return [theme, setTheme];
|
24
|
+
}
|
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 } });
|
@@ -23,19 +23,25 @@ process.on('uncaughtException', shutdownOnException);
|
|
23
23
|
process.on('unhandledRejection', shutdownOnException);
|
24
24
|
// TODO SIGINT Stuck with selenium
|
25
25
|
process.on('SIGINT', () => {
|
26
|
+
if (isShuttingDown.current) {
|
27
|
+
process.exit(-1);
|
28
|
+
}
|
26
29
|
isShuttingDown.current = true;
|
27
30
|
});
|
28
31
|
|
29
32
|
const argv = minimist<Options>(process.argv.slice(2), {
|
30
|
-
string: ['browser', 'config', 'reporter', 'reportDir', 'screenDir', 'gridUrl', 'storybookUrl'],
|
31
|
-
boolean: ['debug', 'trace', 'ui', 'odiff'],
|
32
|
-
default: { port: 3000 },
|
33
|
-
alias: { port: 'p', config: 'c', debug: 'd', update: 'u' },
|
33
|
+
string: ['browser', 'config', 'reporter', 'reportDir', 'screenDir', 'gridUrl', 'storybookUrl', 'storybookPort'],
|
34
|
+
boolean: ['debug', 'trace', 'ui', 'odiff', 'noDocker'],
|
35
|
+
default: { port: '3000' },
|
36
|
+
alias: { port: 'p', config: 'c', debug: 'd', update: 'u', storybookStart: 's' },
|
34
37
|
});
|
35
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
|
+
|
36
42
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
37
43
|
if (cluster.isPrimary && argv.reporter) {
|
38
|
-
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:
|
39
45
|
import { reporters } from 'mocha';
|
40
46
|
|
41
47
|
const config = {
|
@@ -52,13 +58,13 @@ if (cluster.isPrimary && argv.reporter) {
|
|
52
58
|
// @ts-expect-error: define log level for storybook
|
53
59
|
global.LOGLEVEL = argv.trace ? 'trace' : argv.debug ? 'debug' : 'warn';
|
54
60
|
if (argv.trace) {
|
55
|
-
logger.setDefaultLevel(Logger.levels.TRACE);
|
61
|
+
logger().setDefaultLevel(Logger.levels.TRACE);
|
56
62
|
Logger.setDefaultLevel(Logger.levels.TRACE);
|
57
63
|
} else if (argv.debug) {
|
58
|
-
logger.setDefaultLevel(Logger.levels.DEBUG);
|
64
|
+
logger().setDefaultLevel(Logger.levels.DEBUG);
|
59
65
|
Logger.setDefaultLevel(Logger.levels.DEBUG);
|
60
66
|
} else {
|
61
|
-
logger.setDefaultLevel(Logger.levels.INFO);
|
67
|
+
logger().setDefaultLevel(Logger.levels.INFO);
|
62
68
|
Logger.setDefaultLevel(Logger.levels.INFO);
|
63
69
|
}
|
64
70
|
|
package/src/server/config.ts
CHANGED
@@ -1,33 +1,37 @@
|
|
1
1
|
import fs from 'fs';
|
2
2
|
import path from 'path';
|
3
|
+
import cluster from 'cluster';
|
3
4
|
import { pathToFileURL } from 'url';
|
4
|
-
import { loadStories as
|
5
|
+
import { loadStories as hybridStoriesProvider } from './providers/hybrid.js';
|
5
6
|
import { Config, BrowserConfig, BrowserConfigObject, Options, isDefined } from '../types.js';
|
6
7
|
import { configExt, loadThroughTSX } from './utils.js';
|
7
|
-
import { CreeveyReporter
|
8
|
+
import { CreeveyReporter } from './reporters/creevey.js';
|
9
|
+
import { TeamcityReporter } from './reporters/teamcity.js';
|
8
10
|
import { logger } from './logger.js';
|
9
11
|
|
10
12
|
export const defaultBrowser = 'chrome';
|
11
13
|
|
12
|
-
export const defaultConfig: Omit<Config, 'gridUrl' | '
|
14
|
+
export const defaultConfig: Omit<Config, 'gridUrl' | 'tsConfig' | 'webdriver'> = {
|
13
15
|
disableTelemetry: false,
|
16
|
+
useWorkerQueue: false,
|
14
17
|
useDocker: true,
|
15
|
-
dockerImage: 'aerokube/selenoid:latest-release',
|
18
|
+
dockerImage: 'aerokube/selenoid:latest-release', // TODO What about playwright?
|
16
19
|
dockerImagePlatform: '',
|
17
20
|
pullImages: true,
|
18
21
|
failFast: false,
|
19
22
|
storybookUrl: 'http://localhost:6006',
|
20
23
|
screenDir: path.resolve('images'),
|
21
24
|
reportDir: path.resolve('report'),
|
25
|
+
testsDir: path.resolve('src'),
|
22
26
|
reporter: process.env.TEAMCITY_VERSION ? TeamcityReporter : CreeveyReporter,
|
23
|
-
storiesProvider:
|
27
|
+
storiesProvider: hybridStoriesProvider,
|
24
28
|
maxRetries: 0,
|
25
29
|
testTimeout: 30000,
|
26
30
|
diffOptions: { threshold: 0.1, includeAA: false },
|
27
31
|
odiffOptions: { threshold: 0.1, antialiasing: true },
|
28
32
|
browsers: { [defaultBrowser]: true },
|
29
33
|
hooks: {},
|
30
|
-
testsRegex: /\.creevey\.(t|j)s$/,
|
34
|
+
testsRegex: /\.creevey\.(m|c)?(t|j)s$/,
|
31
35
|
};
|
32
36
|
|
33
37
|
function normalizeBrowserConfig(name: string, config: BrowserConfig): BrowserConfigObject {
|
@@ -73,7 +77,7 @@ export async function readConfig(options: Options): Promise<Config> {
|
|
73
77
|
|
74
78
|
if (!configData.webdriver) {
|
75
79
|
const { SeleniumWebdriver } = await import('./selenium/webdriver.js');
|
76
|
-
logger.warn(
|
80
|
+
logger().warn(
|
77
81
|
"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
82
|
);
|
79
83
|
configData.webdriver = SeleniumWebdriver;
|
@@ -82,10 +86,29 @@ export async function readConfig(options: Options): Promise<Config> {
|
|
82
86
|
Object.assign(userConfig, configData);
|
83
87
|
}
|
84
88
|
|
89
|
+
if (userConfig.resolveStorybookUrl && !options.storybookUrl) {
|
90
|
+
userConfig.storybookUrl = await userConfig.resolveStorybookUrl();
|
91
|
+
}
|
92
|
+
|
93
|
+
if (options.noDocker) userConfig.useDocker = false;
|
85
94
|
if (options.failFast != undefined) userConfig.failFast = Boolean(options.failFast);
|
86
95
|
if (options.reportDir) userConfig.reportDir = path.resolve(options.reportDir);
|
87
96
|
if (options.screenDir) userConfig.screenDir = path.resolve(options.screenDir);
|
88
97
|
if (options.storybookUrl) userConfig.storybookUrl = options.storybookUrl;
|
98
|
+
if (options.storybookPort && cluster.isPrimary) {
|
99
|
+
const url = new URL(userConfig.storybookUrl);
|
100
|
+
url.port = options.storybookPort;
|
101
|
+
userConfig.storybookUrl = url.toString();
|
102
|
+
}
|
103
|
+
if (typeof options.storybookStart === 'string') userConfig.storybookAutorunCmd = options.storybookStart;
|
104
|
+
|
105
|
+
if (options.storybookStart && cluster.isPrimary) {
|
106
|
+
const { default: getPort } = await import('get-port');
|
107
|
+
const url = new URL(userConfig.storybookUrl);
|
108
|
+
const port = await getPort({ port: Number(url.port) });
|
109
|
+
url.port = `${port}`;
|
110
|
+
userConfig.storybookUrl = url.toString();
|
111
|
+
}
|
89
112
|
|
90
113
|
// NOTE: Hack to pass typescript checking
|
91
114
|
const config = userConfig as Config;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import type { Config, Options } from '../types';
|
2
|
+
import { waitOnUrl } from './utils.js';
|
3
|
+
import { logger } from './logger.js';
|
4
|
+
|
5
|
+
const RESPONSE_CHECK_TIMEOUT_MS = 10000;
|
6
|
+
const RESPONSE_CHECK_INTERVAL_MS = 200;
|
7
|
+
|
8
|
+
export function getStorybookUrl({ storybookUrl }: Config, { storybookStart }: Options): [string, string | undefined] {
|
9
|
+
if (storybookStart) {
|
10
|
+
const url = new URL(storybookUrl);
|
11
|
+
url.hostname = 'localhost';
|
12
|
+
return [url.toString(), storybookUrl];
|
13
|
+
}
|
14
|
+
return [storybookUrl, undefined];
|
15
|
+
}
|
16
|
+
|
17
|
+
export async function checkIsStorybookConnected(url: string) {
|
18
|
+
try {
|
19
|
+
await waitOnUrl(url, RESPONSE_CHECK_TIMEOUT_MS, RESPONSE_CHECK_INTERVAL_MS);
|
20
|
+
return true;
|
21
|
+
} catch (reason: unknown) {
|
22
|
+
const error = reason instanceof Error ? (reason.stack ?? reason.message) : (reason as string);
|
23
|
+
logger().error(error);
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
}
|
package/src/server/docker.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
import tar from 'tar-stream';
|
2
|
+
import Logger from 'loglevel';
|
2
3
|
import { Writable } from 'stream';
|
3
4
|
import Dockerode, { Container } from 'dockerode';
|
4
5
|
import { DockerAuth } from '../types.js';
|
5
|
-
import { subscribeOn } from './messages.js';
|
6
6
|
import { logger } from './logger.js';
|
7
|
+
import { setWorkerContainer } from './worker/context.js';
|
7
8
|
|
8
9
|
const docker = new Dockerode();
|
9
10
|
|
@@ -21,7 +22,7 @@ export async function pullImages(
|
|
21
22
|
if (auth) args.authconfig = auth;
|
22
23
|
if (platform) args.platform = platform;
|
23
24
|
|
24
|
-
logger.info('Pull docker images');
|
25
|
+
logger().info('Pull docker images');
|
25
26
|
// TODO Replace with `import from`
|
26
27
|
const { default: yoctoSpinner } = await import('yocto-spinner');
|
27
28
|
for (const image of images) {
|
@@ -50,18 +51,46 @@ export async function pullImages(
|
|
50
51
|
function onProgress(event: { id: string; status: string; progress?: string }): void {
|
51
52
|
if (!/^[a-z0-9]{12}$/i.test(event.id)) return;
|
52
53
|
|
53
|
-
spinner.text = `${image}: [${event.id}] ${event.status} ${event.progress
|
54
|
+
spinner.text = `${image}: [${event.id}] ${event.status} ${event.progress ?? ''}`;
|
54
55
|
}
|
55
56
|
});
|
56
57
|
});
|
57
58
|
}
|
58
59
|
}
|
59
60
|
|
60
|
-
export async function buildImage(imageName: string, dockerfile: string): Promise<void> {
|
61
|
+
export async function buildImage(imageName: string, version: string, dockerfile: string): Promise<void> {
|
61
62
|
const images = await docker.listImages({ filters: { label: [`creevey=${imageName}`] } });
|
62
63
|
|
63
|
-
|
64
|
-
|
64
|
+
const containers = await docker.listContainers({ all: true, filters: { label: [`creevey=${imageName}`] } });
|
65
|
+
if (containers.length > 0) {
|
66
|
+
await Promise.all(
|
67
|
+
containers.map(async (info) => {
|
68
|
+
const container = docker.getContainer(info.Id);
|
69
|
+
try {
|
70
|
+
await container.remove({ force: true });
|
71
|
+
} catch {
|
72
|
+
/* noop */
|
73
|
+
}
|
74
|
+
}),
|
75
|
+
);
|
76
|
+
}
|
77
|
+
|
78
|
+
const oldImages = images.filter((info) => info.Labels.version !== version);
|
79
|
+
if (oldImages.length > 0) {
|
80
|
+
await Promise.all(
|
81
|
+
oldImages.map(async (info) => {
|
82
|
+
const image = docker.getImage(info.Id);
|
83
|
+
try {
|
84
|
+
await image.remove({ force: true });
|
85
|
+
} catch {
|
86
|
+
/* noop */
|
87
|
+
}
|
88
|
+
}),
|
89
|
+
);
|
90
|
+
}
|
91
|
+
|
92
|
+
if (oldImages.length !== images.length) {
|
93
|
+
logger().info(`Image ${imageName} already exists`);
|
65
94
|
return;
|
66
95
|
}
|
67
96
|
|
@@ -70,15 +99,20 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
70
99
|
pack.finalize();
|
71
100
|
|
72
101
|
const { default: yoctoSpinner } = await import('yocto-spinner');
|
73
|
-
const spinner = yoctoSpinner({ text: `${imageName}: Build start` })
|
102
|
+
const spinner = yoctoSpinner({ text: `${imageName}: Build start` });
|
103
|
+
if (logger().getLevel() > Logger.levels.DEBUG) {
|
104
|
+
spinner.start();
|
105
|
+
}
|
106
|
+
let isFailed = false;
|
74
107
|
await new Promise<void>((resolve, reject) => {
|
75
108
|
void docker.buildImage(
|
76
109
|
// @ts-expect-error Type incompatibility AsyncIterator and AsyncIterableIterator
|
77
110
|
pack,
|
78
|
-
|
111
|
+
// TODO Support buildkit decode grpc (version: '2')
|
112
|
+
{ t: imageName, labels: { creevey: imageName, version }, version: '1' },
|
79
113
|
(buildError: Error | null, stream) => {
|
80
114
|
if (buildError || !stream) {
|
81
|
-
spinner.error(buildError?.message);
|
115
|
+
// spinner.error(buildError?.message);
|
82
116
|
reject(buildError ?? new Error('Unknown error'));
|
83
117
|
return;
|
84
118
|
}
|
@@ -86,6 +120,8 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
86
120
|
docker.modem.followProgress(stream, onFinished, onProgress);
|
87
121
|
|
88
122
|
function onFinished(error: Error | null): void {
|
123
|
+
if (isFailed) return;
|
124
|
+
|
89
125
|
if (error) {
|
90
126
|
spinner.error(error.message);
|
91
127
|
reject(error);
|
@@ -95,10 +131,23 @@ export async function buildImage(imageName: string, dockerfile: string): Promise
|
|
95
131
|
resolve();
|
96
132
|
}
|
97
133
|
|
98
|
-
function onProgress(
|
99
|
-
|
100
|
-
|
101
|
-
|
134
|
+
function onProgress(
|
135
|
+
event:
|
136
|
+
| { stream: string }
|
137
|
+
| { errorDetail: { code: number; message: string }; error: string }
|
138
|
+
| { id: string; aux: string }, // NOTE: Only with `version: '2'`
|
139
|
+
): void {
|
140
|
+
if ('stream' in event) {
|
141
|
+
if (logger().getLevel() <= Logger.levels.DEBUG) {
|
142
|
+
logger().debug(event.stream.trim());
|
143
|
+
} else {
|
144
|
+
spinner.text = `${imageName}: [Build] - ${event.stream}`;
|
145
|
+
}
|
146
|
+
} else if ('errorDetail' in event) {
|
147
|
+
isFailed = true;
|
148
|
+
spinner.error(event.error);
|
149
|
+
reject(new Error(event.error));
|
150
|
+
}
|
102
151
|
}
|
103
152
|
},
|
104
153
|
);
|
@@ -111,33 +160,13 @@ export async function runImage(
|
|
111
160
|
options: Record<string, unknown>,
|
112
161
|
debug: boolean,
|
113
162
|
): Promise<string> {
|
114
|
-
await Promise.all(
|
115
|
-
(await docker.listContainers({ all: true, filters: { ancestor: [image] } })).map(async (info) => {
|
116
|
-
const container = docker.getContainer(info.Id);
|
117
|
-
try {
|
118
|
-
await container.stop();
|
119
|
-
} catch {
|
120
|
-
/* noop */
|
121
|
-
}
|
122
|
-
await container.remove();
|
123
|
-
}),
|
124
|
-
);
|
125
|
-
|
126
163
|
const hub = docker.run(image, args, debug ? process.stdout : new DevNull(), options, (error) => {
|
127
164
|
if (error) throw error;
|
128
165
|
});
|
129
166
|
|
130
167
|
return new Promise((resolve) => {
|
131
168
|
hub.once('container', (container: Container) => {
|
132
|
-
|
133
|
-
subscribeOn('shutdown', async () => {
|
134
|
-
try {
|
135
|
-
await container.stop();
|
136
|
-
await container.remove();
|
137
|
-
} catch {
|
138
|
-
/* noop */
|
139
|
-
}
|
140
|
-
});
|
169
|
+
setWorkerContainer(container);
|
141
170
|
});
|
142
171
|
hub.once(
|
143
172
|
'start',
|