diff --git a/lib/constants/diff-modes.ts b/lib/constants/diff-modes.ts
index af9176002..771c88309 100644
--- a/lib/constants/diff-modes.ts
+++ b/lib/constants/diff-modes.ts
@@ -3,38 +3,38 @@ import {ValueOf} from 'type-fest';
export const DiffModes = {
THREE_UP: {
id: '3-up',
- title: '3-up',
- description: 'images in column'
+ title: 'List',
+ description: 'List. Show images one after another in vertical layout.'
},
THREE_UP_SCALED: {
id: '3-up-scaled',
- title: '3-up scaled',
- description: 'scaled images in row'
+ title: 'SbS',
+ description: 'Side by Side. Show images in one row.'
},
THREE_UP_SCALED_TO_FIT: {
id: '3-up-scaled-to-fit',
- title: '3-up scaled to fit',
- description: 'scaled to browser height images in row'
+ title: 'SbS (fit screen)',
+ description: 'Side by Side. Show images in one row and scale them down if needed to fit the screen.'
},
ONLY_DIFF: {
id: 'only-diff',
- title: 'Only diff',
- description: 'click on image to see area with diff'
+ title: 'Only Diff',
+ description: 'Only Diff. Show only diff image, click to highlight diff areas.'
},
SWITCH: {
id: 'switch',
title: 'Switch',
- description: 'click on image to switch'
+ description: 'Switch. Click to switch between expected and actual images.'
},
SWIPE: {
id: 'swipe',
title: 'Swipe',
- description: 'move divider'
+ description: 'Swipe. Move the divider to compare expected and actual images.'
},
ONION_SKIN: {
id: 'onion-skin',
title: 'Onion skin',
- description: 'move slider'
+ description: 'Onion Skin. Change the image opacity to compare expected and actual images.'
}
} as const;
diff --git a/lib/static/components/state/index.jsx b/lib/static/components/state/index.jsx
index b14ded48a..379f0519b 100644
--- a/lib/static/components/state/index.jsx
+++ b/lib/static/components/state/index.jsx
@@ -16,6 +16,7 @@ import {isAcceptable, isNodeStaged, isNodeSuccessful, isScreenRevertable} from '
import {isSuccessStatus, isFailStatus, isErrorStatus, isUpdatedStatus, isIdleStatus, isStagedStatus, isCommitedStatus} from '../../../common-utils';
import {Disclosure} from '@gravity-ui/uikit';
import {ChevronsExpandUpRight, Check, ArrowUturnCcwDown} from '@gravity-ui/icons';
+import {getDisplayedDiffPercentValue} from '@/static/new-ui/components/DiffViewer/utils';
class State extends Component {
static propTypes = {
@@ -155,18 +156,6 @@ class State extends Component {
);
}
- _getDisplayedDiffPercentValue() {
- const percent = this.props.image.diffRatio * 100;
- const percentRounded = Math.ceil(percent * 100) / 100;
- const percentThreshold = 0.01;
-
- if (percent < percentThreshold) {
- return `< ${percentThreshold}`;
- }
-
- return String(percentRounded);
- }
-
_getStateTitleWithDiffCount() {
const {image} = this.props;
@@ -182,7 +171,7 @@ class State extends Component {
let displayedText = image.stateName;
if (image.differentPixels && image.diffRatio) {
- const displayedDiffPercent = this._getDisplayedDiffPercentValue();
+ const displayedDiffPercent = getDisplayedDiffPercentValue(image.diffRatio);
displayedText += ` (diff: ${image.differentPixels}px, ${displayedDiffPercent}%)`;
}
diff --git a/lib/static/new-ui/components/AssertViewResult/index.module.css b/lib/static/new-ui/components/AssertViewResult/index.module.css
new file mode 100644
index 000000000..1fd3b7988
--- /dev/null
+++ b/lib/static/new-ui/components/AssertViewResult/index.module.css
@@ -0,0 +1,17 @@
+.diff-viewer-container {
+ display: flex;
+ flex-direction: column;
+ padding-left: calc(var(--indent) * 24px);
+ padding-right: 1px
+}
+
+.diff-mode-switcher {
+ --g-color-base-background: #fff;
+ margin: 12px auto;
+}
+
+.screenshot {
+ margin: 8px 0;
+ padding-left: calc(var(--indent) * 24px);
+ padding-right: 1px;
+}
diff --git a/lib/static/new-ui/components/AssertViewResult/index.tsx b/lib/static/new-ui/components/AssertViewResult/index.tsx
new file mode 100644
index 000000000..050c81e45
--- /dev/null
+++ b/lib/static/new-ui/components/AssertViewResult/index.tsx
@@ -0,0 +1,45 @@
+import React, {ReactNode} from 'react';
+import {ImageEntity, State} from '@/static/new-ui/types/store';
+import {DiffModeId, DiffModes, TestStatus} from '@/constants';
+import {DiffViewer} from '../DiffViewer';
+import {RadioButton} from '@gravity-ui/uikit';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import * as actions from '@/static/modules/actions';
+import styles from './index.module.css';
+import {Screenshot} from '@/static/new-ui/components/Screenshot';
+
+interface AssertViewResultProps {
+ result: ImageEntity;
+ style?: React.CSSProperties;
+ actions: typeof actions;
+ diffMode: DiffModeId;
+}
+
+function AssertViewResultInternal({result, actions, diffMode, style}: AssertViewResultProps): ReactNode {
+ if (result.status === TestStatus.FAIL) {
+ const onChangeHandler = (diffMode: DiffModeId): void => {
+ actions.changeDiffMode(diffMode);
+ };
+
+ return
+
+ {Object.values(DiffModes).map(diffMode =>
+
+ )}
+
+
+
;
+ } else if (result.status === TestStatus.ERROR) {
+ return ;
+ } else if (result.status === TestStatus.SUCCESS || result.status === TestStatus.UPDATED) {
+ return ;
+ }
+
+ return null;
+}
+
+export const AssertViewResult = connect((state: State) => ({
+ diffMode: state.view.diffMode
+}), (dispatch) => ({actions: bindActionCreators(actions, dispatch)})
+)(AssertViewResultInternal);
diff --git a/lib/static/new-ui/components/AttemptPicker/index.tsx b/lib/static/new-ui/components/AttemptPicker/index.tsx
index 2225140de..e21d60667 100644
--- a/lib/static/new-ui/components/AttemptPicker/index.tsx
+++ b/lib/static/new-ui/components/AttemptPicker/index.tsx
@@ -28,7 +28,7 @@ function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode {
return
Attempts
-
+
{resultIds.map((resultId, index) => {
const isActive = resultId === currentResultId;
@@ -39,7 +39,7 @@ function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode {
onClick={(): unknown => onClickHandler(resultId, index)}
/>;
})}
-
+
;
}
diff --git a/lib/static/new-ui/components/AttemptPickerItem/index.module.css b/lib/static/new-ui/components/AttemptPickerItem/index.module.css
index 356efdd92..a3ccbe947 100644
--- a/lib/static/new-ui/components/AttemptPickerItem/index.module.css
+++ b/lib/static/new-ui/components/AttemptPickerItem/index.module.css
@@ -1,6 +1,5 @@
.attempt-picker-item {
--g-button-padding: 8px;
- margin-right: 2px;
}
.attempt-picker-item--active {
diff --git a/lib/static/new-ui/components/DiffViewer/index.module.css b/lib/static/new-ui/components/DiffViewer/index.module.css
new file mode 100644
index 000000000..39472559e
--- /dev/null
+++ b/lib/static/new-ui/components/DiffViewer/index.module.css
@@ -0,0 +1,8 @@
+.image-label, .image-label + div {
+ margin-bottom: 8px;
+}
+
+.image-label-subtitle {
+ color: var(--g-color-private-black-400);
+ margin-left: 4px;
+}
diff --git a/lib/static/new-ui/components/DiffViewer/index.tsx b/lib/static/new-ui/components/DiffViewer/index.tsx
new file mode 100644
index 000000000..ac39f643d
--- /dev/null
+++ b/lib/static/new-ui/components/DiffViewer/index.tsx
@@ -0,0 +1,82 @@
+import {ImageFile} from '@/types';
+import {CoordBounds} from 'looks-same';
+import {DiffModeId, DiffModes} from '@/constants';
+import React, {ReactNode} from 'react';
+import {OnlyDiffMode} from '@/static/new-ui/components/DiffViewer/OnlyDiffMode';
+import {SwitchMode} from '@/static/new-ui/components/DiffViewer/SwitchMode';
+import {SwipeMode} from '@/static/new-ui/components/DiffViewer/SwipeMode';
+import {OnionSkinMode} from '@/static/new-ui/components/DiffViewer/OnionSkinMode';
+import {SideBySideMode} from '@/static/new-ui/components/DiffViewer/SideBySideMode';
+import {SideBySideToFitMode} from '@/static/new-ui/components/DiffViewer/SideBySideToFitMode';
+import {ListMode} from '@/static/new-ui/components/DiffViewer/ListMode';
+import {getDisplayedDiffPercentValue} from '@/static/new-ui/components/DiffViewer/utils';
+
+import styles from './index.module.css';
+
+interface DiffViewerProps {
+ actualImg: ImageFile;
+ expectedImg: ImageFile;
+ diffImg: ImageFile;
+ diffClusters: CoordBounds[];
+ diffMode: DiffModeId;
+ /** For cosmetics, will be displayed in diff label. */
+ differentPixels?: number;
+ /** For cosmetics, will be displayed in diff label. */
+ diffRatio?: number;
+ /**
+ * A valid CSS value assignable to height, e.g. `10px` or `calc(100vh - 50px)`.
+ * Images will try to fit the `desiredHeight`, but will only shrink no more than 2 times.
+ * */
+ desiredHeight?: string;
+}
+
+export function DiffViewer(props: DiffViewerProps): ReactNode {
+ const getImageDisplayedSize = (image: ImageFile): string => `${image.size.width}×${image.size.height}`;
+ const getImageLabel = (title: string, subtitle?: string): ReactNode => {
+ return
+ {title}
+ {subtitle && {subtitle}}
+
;
+ };
+
+ const expectedImg = Object.assign({}, props.expectedImg, {
+ label: getImageLabel('Expected', getImageDisplayedSize(props.expectedImg))
+ });
+ const actualImg = Object.assign({}, props.actualImg, {
+ label: getImageLabel('Actual', getImageDisplayedSize(props.actualImg))
+ });
+ let diffSubtitle: string | undefined;
+ if (props.differentPixels !== undefined && props.diffRatio !== undefined) {
+ diffSubtitle = `${props.differentPixels}px ⋅ ${getDisplayedDiffPercentValue(props.diffRatio)}%`;
+ }
+ const diffImg = Object.assign({}, props.diffImg, {
+ label: getImageLabel('Diff', diffSubtitle),
+ diffClusters: props.diffClusters
+ });
+
+ switch (props.diffMode) {
+ case DiffModes.ONLY_DIFF.id:
+ return ;
+
+ case DiffModes.SWITCH.id:
+ return ;
+
+ case DiffModes.SWIPE.id:
+ return ;
+
+ case DiffModes.ONION_SKIN.id:
+ return ;
+
+ case DiffModes.THREE_UP_SCALED.id:
+ return ;
+
+ case DiffModes.THREE_UP_SCALED_TO_FIT.id: {
+ const desiredHeight = props.desiredHeight ?? 'calc(100vh - 180px)';
+
+ return ;
+ }
+ case DiffModes.THREE_UP.id:
+ default:
+ return ;
+ }
+}
diff --git a/lib/static/new-ui/components/DiffViewer/utils.ts b/lib/static/new-ui/components/DiffViewer/utils.ts
index 9c8af1375..744eaaa80 100644
--- a/lib/static/new-ui/components/DiffViewer/utils.ts
+++ b/lib/static/new-ui/components/DiffViewer/utils.ts
@@ -5,3 +5,15 @@ export const getImageSizeCssVars = (size: ImageSize): React.CSSProperties => ({
'--natural-width': size.width,
'--natural-height': size.height
} as React.CSSProperties);
+
+export const getDisplayedDiffPercentValue = (diffRatio: number): string => {
+ const percent = diffRatio * 100;
+ const percentRounded = Math.ceil(percent * 100) / 100;
+ const percentThreshold = 0.01;
+
+ if (percent < percentThreshold) {
+ return `< ${percentThreshold}`;
+ }
+
+ return String(percentRounded);
+};
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
index 97bbc36bd..ff7fd0109 100644
--- a/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
+++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.module.css
@@ -46,6 +46,6 @@
background-color: white;
position: sticky;
top: 0;
- z-index: 1;
+ z-index: 10;
padding-bottom: 4px;
}
diff --git a/lib/static/new-ui/features/suites/components/TestSteps/index.module.css b/lib/static/new-ui/features/suites/components/TestSteps/index.module.css
index 7ac22d4ef..a30d373a1 100644
--- a/lib/static/new-ui/features/suites/components/TestSteps/index.module.css
+++ b/lib/static/new-ui/features/suites/components/TestSteps/index.module.css
@@ -14,6 +14,12 @@
margin: 8px 0;
}
+.page-screenshot {
+ margin: 8px 0;
+ padding-left: 24px;
+ padding-right: 1px;
+}
+
.step-duration {
margin-left: auto;
padding: 0 8px;
diff --git a/lib/static/new-ui/features/suites/components/TestSteps/index.tsx b/lib/static/new-ui/features/suites/components/TestSteps/index.tsx
index 8215794fe..1f4ddb2fb 100644
--- a/lib/static/new-ui/features/suites/components/TestSteps/index.tsx
+++ b/lib/static/new-ui/features/suites/components/TestSteps/index.tsx
@@ -11,7 +11,6 @@ import {bindActionCreators} from 'redux';
import {CollapsibleSection} from '@/static/new-ui/features/suites/components/CollapsibleSection';
import {State} from '@/static/new-ui/types/store';
import {TreeViewItemIcon} from '@/static/new-ui/components/TreeViewItemIcon';
-import {TestStatus} from '@/constants';
import {TestStepArgs} from '@/static/new-ui/features/suites/components/TestStepArgs';
import {getIconByStatus} from '@/static/new-ui/utils';
import {ErrorInfo} from '@/static/new-ui/components/ErrorInfo';
@@ -21,6 +20,10 @@ import {getStepsExpandedById, getTestSteps} from './selectors';
import {Step, StepType} from './types';
import {ListItemViewContentType, TreeViewItem} from '../../../../components/TreeViewItem';
import styles from './index.module.css';
+import {Screenshot} from '@/static/new-ui/components/Screenshot';
+import {AssertViewResult} from '@/static/new-ui/components/AssertViewResult';
+import {getIndentStyle} from '@/static/new-ui/features/suites/components/TestSteps/utils';
+import {isErrorStatus, isFailStatus} from '@/common-utils';
interface TestStepsProps {
resultId: string;
@@ -55,7 +58,7 @@ function TestStepsInternal(props: TestStepsProps): ReactNode {
const item = items.structure.itemsById[itemId];
if (item.type === StepType.Action) {
- const shouldHighlightFail = item.status === TestStatus.ERROR && !item.isGroup;
+ const shouldHighlightFail = (isErrorStatus(item.status) || isFailStatus(item.status)) && !item.isGroup;
return {
@@ -63,7 +66,7 @@ function TestStepsInternal(props: TestStepsProps): ReactNode {
title:
{item.title}
- {item.duration} ms
+ {item.duration !== undefined && {item.duration} ms}
,
startSlot: {getIconByStatus(item.status)}
};
@@ -79,6 +82,10 @@ function TestStepsInternal(props: TestStepsProps): ReactNode {
} else if (item.type === StepType.ErrorInfo) {
const indent = items.structure.itemsState[itemId].indentation;
return ;
+ } else if (item.type === StepType.SingleImage) {
+ return ;
+ } else if (item.type === StepType.AssertViewResult) {
+ return ;
}
return null;
diff --git a/lib/static/new-ui/features/suites/components/TestSteps/selectors.ts b/lib/static/new-ui/features/suites/components/TestSteps/selectors.ts
index 08e8b4cb3..06c00db7e 100644
--- a/lib/static/new-ui/features/suites/components/TestSteps/selectors.ts
+++ b/lib/static/new-ui/features/suites/components/TestSteps/selectors.ts
@@ -1,15 +1,16 @@
import {createSelector} from 'reselect';
-import {getCurrentResult, getExpandedStepsById} from '@/static/new-ui/store/selectors';
-import {Attachment, ErrorInfo, Step, StepType} from './types';
+import {getCurrentResult, getCurrentResultImages, getExpandedStepsById} from '@/static/new-ui/store/selectors';
+import {AssertViewResult, Attachment, ErrorInfo, Step, StepType} from './types';
import {TestStepCompressed, TestStepKey} from '@/types';
import {unstable_ListTreeItemType as ListTreeItemType} from '@gravity-ui/uikit/unstable';
import {TestStatus} from '@/constants';
import {isAssertViewError, isImageDiffError, mergeSnippetIntoErrorStack} from '@/common-utils';
import {traverseTree} from '@/static/new-ui/features/suites/components/TestSteps/utils';
+import {ImageEntityError} from '@/static/new-ui/types/store';
export const getTestSteps = createSelector(
- [getCurrentResult],
- (result): ListTreeItemType[] => {
+ [getCurrentResult, getCurrentResultImages],
+ (result, images): ListTreeItemType[] => {
if (!result || !result.history) {
return [];
}
@@ -50,6 +51,7 @@ export const getTestSteps = createSelector(
const steps = formatTestSteps(result.history, result.id);
+ // Adding error stack
if (result.error && !isImageDiffError(result.error) && !isAssertViewError(result.error)) {
const lastErroredStep = getLastErroredStep(steps);
const error = mergeSnippetIntoErrorStack(result.error);
@@ -76,6 +78,104 @@ export const getTestSteps = createSelector(
}
}
+ // Integrating images into test steps
+ for (const image of images) {
+ if (!image.stateName) {
+ continue;
+ }
+
+ let matchedStep: ListTreeItemType | undefined;
+
+ traverseTree(steps, step => {
+ if (step.data.type === StepType.Action && step.data.title === 'assertView' && step.data.args[0] === image.stateName) {
+ matchedStep = step;
+ }
+ });
+
+ if (!matchedStep) {
+ matchedStep = {
+ id: `${image.id} assertView`,
+ data: {
+ type: StepType.Action,
+ title: 'assertView',
+ status: image.status,
+ args: [image.stateName],
+ isGroup: false
+ }
+ };
+ steps.push(matchedStep);
+ }
+
+ if (image.status === TestStatus.ERROR && image.error) {
+ const errorInfo: ListTreeItemType = {
+ id: `${image.id} assertView error-info`,
+ data: {
+ type: StepType.ErrorInfo,
+ name: image.error.name,
+ stack: image.error.stack
+ }
+ };
+ const errorAttachment = {
+ id: `${image.id} assertView error`,
+ data: {
+ type: StepType.Attachment,
+ title: 'Error'
+ } satisfies Attachment,
+ children: [errorInfo]
+ };
+
+ if (!matchedStep.children) {
+ matchedStep.children = [];
+ }
+
+ matchedStep.children.push(errorAttachment);
+ }
+
+ const imageStep: ListTreeItemType = {
+ id: `${image.id} image`,
+ data: {
+ type: StepType.AssertViewResult,
+ result: image
+ }
+ };
+
+ if (matchedStep.data.type === StepType.Action) {
+ matchedStep.data.status = image.status;
+ }
+
+ if (matchedStep.children && matchedStep.children.length > 0) {
+ matchedStep.children.push({
+ id: `${matchedStep.id} attachment`,
+ data: {
+ type: StepType.Attachment,
+ title: 'Screenshots'
+ },
+ children: [imageStep]
+ });
+ } else {
+ matchedStep.children = [imageStep];
+ }
+ }
+
+ // Adding page screenshot
+ const pageScreenshot = images.find(image => image.status === TestStatus.ERROR && !image.stateName);
+ if (pageScreenshot) {
+ steps.push({
+ id: 'page-screenshot',
+ data: {
+ type: StepType.Attachment,
+ title: 'Page Screenshot'
+ },
+ children: [{
+ id: 'page-screenshot-image',
+ data: {
+ type: StepType.SingleImage,
+ image: (pageScreenshot as ImageEntityError).actualImg
+ }
+ }]
+ });
+ }
+
// TODO: add type-level check that each step has explicit ID
return steps;
}
diff --git a/lib/static/new-ui/features/suites/components/TestSteps/types.ts b/lib/static/new-ui/features/suites/components/TestSteps/types.ts
index 2dc8646c4..3887420ab 100644
--- a/lib/static/new-ui/features/suites/components/TestSteps/types.ts
+++ b/lib/static/new-ui/features/suites/components/TestSteps/types.ts
@@ -1,11 +1,13 @@
import {TestStatus} from '@/constants';
import {ImageFile} from '@/types';
+import {ImageEntity} from '@/static/new-ui/types/store';
export enum StepType {
Action,
Attachment,
ErrorInfo,
- SingleImage
+ SingleImage,
+ AssertViewResult
}
export interface Action {
@@ -13,7 +15,7 @@ export interface Action {
status: TestStatus;
title: string;
args: string[];
- duration: number;
+ duration?: number;
isGroup: boolean;
}
@@ -33,4 +35,9 @@ export interface SingleImage {
image: ImageFile;
}
-export type Step = Action | Attachment | ErrorInfo | SingleImage;
+export interface AssertViewResult {
+ type: StepType.AssertViewResult;
+ result: ImageEntity;
+}
+
+export type Step = Action | Attachment | ErrorInfo | SingleImage | AssertViewResult;
diff --git a/lib/static/new-ui/features/suites/components/TestSteps/utils.ts b/lib/static/new-ui/features/suites/components/TestSteps/utils.ts
index 00708d802..816a5e1c5 100644
--- a/lib/static/new-ui/features/suites/components/TestSteps/utils.ts
+++ b/lib/static/new-ui/features/suites/components/TestSteps/utils.ts
@@ -1,4 +1,5 @@
-import {unstable_ListTreeItemType as ListTreeItemType} from '@gravity-ui/uikit/unstable';
+import {unstable_ListTreeItemType as ListTreeItemType, unstable_UseListResult as UseListResult} from '@gravity-ui/uikit/unstable';
+import React from 'react';
export const traverseTree = (treeItems: ListTreeItemType[], cb: (item: ListTreeItemType) => unknown): void => {
function dfs(step: ListTreeItemType): void {
@@ -15,3 +16,7 @@ export const traverseTree = (treeItems: ListTreeItemType[], cb: (item: Lis
dfs(step);
}
};
+
+export const getIndentStyle = (list: UseListResult, id: string): React.CSSProperties => {
+ return {'--indent': list.structure.itemsState[id].indentation} as React.CSSProperties;
+};
diff --git a/lib/static/new-ui/store/selectors.ts b/lib/static/new-ui/store/selectors.ts
index 093b6f783..74ebbff4c 100644
--- a/lib/static/new-ui/store/selectors.ts
+++ b/lib/static/new-ui/store/selectors.ts
@@ -42,3 +42,9 @@ export const getExpandedStepsById = (state: State): Record => {
return resultId ? state.ui.suitesPage.expandedStepsByResultId[resultId] ?? {} : {};
};
+
+export const getCurrentResultImages = (state: State): ImageEntity[] => {
+ const result = getCurrentResult(state);
+
+ return result?.imageIds.map(imageId => state.tree.images.byId[imageId]) ?? [];
+};
diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts
index 801d12fa5..57b75bcda 100644
--- a/lib/static/new-ui/types/store.ts
+++ b/lib/static/new-ui/types/store.ts
@@ -1,6 +1,7 @@
import {DiffModeId, TestStatus, ViewMode} from '@/constants';
import {BrowserItem, ImageFile, ReporterConfig, TestError, TestStepCompressed} from '@/types';
import {HtmlReporterValues} from '@/plugin-api';
+import {CoordBounds} from 'looks-same';
export interface SuiteEntityNode {
id: string;
@@ -56,22 +57,35 @@ interface ImageEntityCommon {
parentId: string;
}
+export interface ImageEntitySuccess extends ImageEntityCommon {
+ status: TestStatus.SUCCESS;
+ stateName: string;
+ expectedImg: ImageFile;
+}
+
+export interface ImageEntityUpdated extends ImageEntityCommon {
+ status: TestStatus.UPDATED;
+ stateName: string;
+ expectedImg: ImageFile;
+}
+
export interface ImageEntityError extends ImageEntityCommon {
status: TestStatus.ERROR;
- // TODO: can a screenshot in error status even have stateName?
stateName?: string;
actualImg: ImageFile;
+ error?: TestError;
}
export interface ImageEntityFail extends ImageEntityCommon {
status: TestStatus.FAIL;
stateName: string;
+ diffClusters: CoordBounds[];
diffImg: ImageFile;
actualImg: ImageFile;
expectedImg: ImageFile;
}
-export type ImageEntity = ImageEntityError | ImageEntityFail;
+export type ImageEntity = ImageEntityError | ImageEntityFail | ImageEntitySuccess | ImageEntityUpdated;
export const isImageEntityFail = (image: ImageEntity): image is ImageEntityFail => Boolean((image as ImageEntityFail).stateName);
diff --git a/lib/static/new-ui/utils/index.tsx b/lib/static/new-ui/utils/index.tsx
index 79871a0b1..96bd298fb 100644
--- a/lib/static/new-ui/utils/index.tsx
+++ b/lib/static/new-ui/utils/index.tsx
@@ -1,5 +1,5 @@
import {TestStatus} from '@/constants';
-import {ArrowRotateLeft, CircleCheck, CircleDashed, CircleMinus, CircleXmark} from '@gravity-ui/icons';
+import {ArrowRotateLeft, CircleCheck, CircleDashed, CircleMinus, CircleXmark, ArrowsRotateLeft} from '@gravity-ui/icons';
import React from 'react';
export const getIconByStatus = (status: TestStatus): React.JSX.Element => {
@@ -11,6 +11,8 @@ export const getIconByStatus = (status: TestStatus): React.JSX.Element => {
return ;
} else if (status === TestStatus.RETRY) {
return ;
+ } else if (status === TestStatus.UPDATED) {
+ return ;
}
return ;
diff --git a/lib/static/styles.css b/lib/static/styles.css
index 20136ae36..75a6652c6 100644
--- a/lib/static/styles.css
+++ b/lib/static/styles.css
@@ -982,3 +982,7 @@ a:active {
.icon-retry {
color: var(--g-color-private-orange-600-solid);
}
+
+.icon-updated {
+ color: var(--color-pink-600);
+}