diff --git a/lib/common-utils.ts b/lib/common-utils.ts
index c52161c5b..671c2b751 100644
--- a/lib/common-utils.ts
+++ b/lib/common-utils.ts
@@ -44,7 +44,7 @@ const statusPriority: TestStatus[] = [
export const logger = pick(console, ['log', 'warn', 'error']);
export const isSuccessStatus = (status: TestStatus): boolean => status === SUCCESS;
-export const isFailStatus = (status: TestStatus): boolean => status === FAIL;
+export const isFailStatus = (status: TestStatus): status is TestStatus.FAIL => status === FAIL;
export const isIdleStatus = (status: TestStatus): boolean => status === IDLE;
export const isRunningStatus = (status: TestStatus): boolean => status === RUNNING;
export const isErrorStatus = (status: TestStatus): boolean => status === ERROR;
@@ -83,6 +83,10 @@ export const getUrlWithBase = (url: string | undefined, base: string | undefined
userUrl.port = baseUrl.port;
userUrl.username = baseUrl.username;
userUrl.password = baseUrl.password;
+
+ for (const [key, value] of baseUrl.searchParams) {
+ userUrl.searchParams.append(key, value);
+ }
}
return userUrl.href;
diff --git a/lib/constants/test-statuses.ts b/lib/constants/test-statuses.ts
index c77538f20..dd92998eb 100644
--- a/lib/constants/test-statuses.ts
+++ b/lib/constants/test-statuses.ts
@@ -18,7 +18,9 @@ export enum TestStatus {
/**
* @note used in new UI only for rendering icons
*/
- RETRY = 'retry'
+ RETRY = 'retry',
+ /** @note used to display tests that have both failed screenshots and errors */
+ FAIL_ERROR = 'fail_error',
}
export const IDLE = TestStatus.IDLE;
@@ -29,6 +31,7 @@ export const FAIL = TestStatus.FAIL;
export const ERROR = TestStatus.ERROR;
export const SKIPPED = TestStatus.SKIPPED;
export const UPDATED = TestStatus.UPDATED;
+export const FAIL_ERROR = TestStatus.FAIL_ERROR;
/**
* @note used by staticImageAccepter only
*/
diff --git a/lib/static/components/modals/screenshot-accepter/meta.jsx b/lib/static/components/modals/screenshot-accepter/meta.jsx
index b4c7f0f1c..f15ea8a60 100644
--- a/lib/static/components/modals/screenshot-accepter/meta.jsx
+++ b/lib/static/components/modals/screenshot-accepter/meta.jsx
@@ -1,7 +1,7 @@
import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
-import MetaInfoContent from '../../section/body/meta-info/content';
+import {MetaInfo as MetaInfoContent} from '@/static/new-ui/components/MetaInfo';
export default class ScreenshotAccepterMeta extends Component {
static propTypes = {
@@ -23,6 +23,7 @@ export default class ScreenshotAccepterMeta extends Component {
diff --git a/lib/static/components/retry-switcher/index.jsx b/lib/static/components/retry-switcher/index.jsx
index 085ba965b..f0239dd0a 100644
--- a/lib/static/components/retry-switcher/index.jsx
+++ b/lib/static/components/retry-switcher/index.jsx
@@ -1,6 +1,6 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
-import RetrySwitcherItem from './item';
+import {AttemptPickerItem} from '@/static/new-ui/components/AttemptPickerItem';
export default class RetrySwitcher extends Component {
static propTypes = {
@@ -22,7 +22,7 @@ export default class RetrySwitcher extends Component {
{resultIds.map((resultId, ind) => {
const isActive = ind === retryIndex;
- return {attempt + 1};
- }
-}
-
-export default connect(
- ({tree, view: {keyToGroupTestsBy}}, {resultId}) => {
- const result = tree.results.byId[resultId];
- const matchedSelectedGroup = get(tree.results.stateById[resultId], 'matchedSelectedGroup', false);
- const {status, attempt, error} = result;
-
- return {
- status: isFailStatus(status) && hasUnrelatedToScreenshotsErrors(error) ? `${status}_${ERROR}` : status,
- attempt,
- keyToGroupTestsBy,
- matchedSelectedGroup
- };
- }
-)(RetrySwitcherItem);
-
diff --git a/lib/static/components/section/body/meta-info/content.jsx b/lib/static/components/section/body/meta-info/content.jsx
deleted file mode 100644
index 77d0291b7..000000000
--- a/lib/static/components/section/body/meta-info/content.jsx
+++ /dev/null
@@ -1,133 +0,0 @@
-import path from 'path';
-import React, {Component} from 'react';
-import {connect} from 'react-redux';
-import {DefinitionList} from '@gravity-ui/components';
-import PropTypes from 'prop-types';
-import {map, mapValues, isObject, omitBy, isEmpty} from 'lodash';
-import {isUrl, getUrlWithBase, getRelativeUrl} from '../../../../../common-utils';
-import {Card} from '@gravity-ui/uikit';
-
-const serializeMetaValues = (metaInfo) => mapValues(metaInfo, (v) => isObject(v) ? JSON.stringify(v) : v);
-
-const resolveUrl = (baseUrl, value) => {
- const parsedBaseUrl = new URL(baseUrl);
- const baseSearchParams = new URLSearchParams(parsedBaseUrl.search);
- if (baseSearchParams) {
- parsedBaseUrl.search = '';
- }
-
- const resolvedUrl = new URL(value, parsedBaseUrl.href);
-
- for (let [key, value] of baseSearchParams) {
- resolvedUrl.searchParams.append(key, value);
- }
-
- return resolvedUrl.href;
-};
-
-const metaToElements = (metaInfo, metaInfoBaseUrls) => {
- return
- {
- let url = value.url;
- value = value.content;
-
- if (isUrl(value)) {
- url = value;
- } else if (metaInfoBaseUrls[key] && metaInfoBaseUrls[key] !== 'auto') {
- const baseUrl = metaInfoBaseUrls[key];
- const link = isUrl(baseUrl) ? resolveUrl(baseUrl, value) : path.join(baseUrl, value);
- url = link;
- } else if (typeof value === 'boolean') {
- value = value.toString();
- }
-
- if (url) {
- value =
- {value}
- ;
- }
-
- return {
- name: key,
- content: {value}
,
- copyText: url || value
- };
- })
- }/>
- ;
-};
-
-class MetaInfoContent extends Component {
- static propTypes = {
- resultId: PropTypes.string.isRequired,
- // from store
- result: PropTypes.shape({
- metaInfo: PropTypes.object.isRequired,
- suiteUrl: PropTypes.string.isRequired
- }).isRequired,
- testName: PropTypes.string.isRequired,
- metaInfoBaseUrls: PropTypes.object.isRequired,
- apiValues: PropTypes.shape({
- extraItems: PropTypes.object.isRequired,
- metaInfoExtenders: PropTypes.object.isRequired
- }).isRequired,
- baseHost: PropTypes.string
- };
-
- getExtraMetaInfo = () => {
- const {testName, apiValues: {extraItems, metaInfoExtenders}} = this.props;
-
- return omitBy(mapValues(metaInfoExtenders, (extender) => {
- const stringifiedFn = extender.startsWith('return') ? extender : `return ${extender}`;
-
- return new Function(stringifiedFn)()({testName}, extraItems);
- }), isEmpty);
- };
-
- render() {
- const {result, metaInfoBaseUrls, baseHost} = this.props;
-
- const serializedMetaValues = serializeMetaValues(result.metaInfo);
- const extraMetaInfo = this.getExtraMetaInfo();
- const formattedMetaInfo = {
- ...serializedMetaValues,
- ...extraMetaInfo
- };
- Object.keys(formattedMetaInfo).forEach((key) => {
- formattedMetaInfo[key] = {content: formattedMetaInfo[key]};
- });
-
- for (const [key, value] of Object.entries(formattedMetaInfo)) {
- if (isUrl(value.content) && (key === 'url' || metaInfoBaseUrls[key] === 'auto')) {
- formattedMetaInfo[key] = {
- content: getRelativeUrl(value.content),
- url: getUrlWithBase(getRelativeUrl(value.content), baseHost)
- };
- }
- }
- if (!formattedMetaInfo.url && result.suiteUrl) {
- formattedMetaInfo.url = {
- content: getRelativeUrl(result.suiteUrl),
- url: getUrlWithBase(getRelativeUrl(result.suiteUrl), baseHost)
- };
- }
-
- return metaToElements(formattedMetaInfo, metaInfoBaseUrls);
- }
-}
-
-export default connect(
- ({tree, config: {metaInfoBaseUrls}, apiValues, view}, {resultId}) => {
- const result = tree.results.byId[resultId];
- const browser = tree.browsers.byId[result.parentId];
-
- return {
- result,
- testName: browser.parentId,
- metaInfoBaseUrls,
- apiValues,
- baseHost: view.baseHost
- };
- }
-)(MetaInfoContent);
diff --git a/lib/static/components/section/body/meta-info/index.jsx b/lib/static/components/section/body/meta-info/index.jsx
index 39e4fd2c6..f9b7cd007 100644
--- a/lib/static/components/section/body/meta-info/index.jsx
+++ b/lib/static/components/section/body/meta-info/index.jsx
@@ -1,8 +1,10 @@
+import {Card} from '@gravity-ui/uikit';
+import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
-import PropTypes from 'prop-types';
-import MetaInfoContent from './content';
+
+import {MetaInfo as MetaInfoContent} from '@/static/new-ui/components/MetaInfo';
import * as actions from '../../../../modules/actions';
import Details from '../../../details';
@@ -21,7 +23,7 @@ class MetaInfo extends Component {
return }
+ content={}
onClick={this.onToggleMetaInfo}
/>;
}
diff --git a/lib/static/icons/testplane-mono.svg b/lib/static/icons/testplane-mono.svg
new file mode 100644
index 000000000..4ac935444
--- /dev/null
+++ b/lib/static/icons/testplane-mono.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/static/modules/action-names.ts b/lib/static/modules/action-names.ts
index 47bbc83ee..1189c0e6a 100644
--- a/lib/static/modules/action-names.ts
+++ b/lib/static/modules/action-names.ts
@@ -60,5 +60,6 @@ export default {
UPDATE_BOTTOM_PROGRESS_BAR: 'UPDATE_BOTTOM_PROGRESS_BAR',
GROUP_TESTS_BY_KEY: 'GROUP_TESTS_BY_KEY',
TOGGLE_BROWSER_CHECKBOX: 'TOGGLE_BROWSER_CHECKBOX',
- SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE'
+ SUITES_PAGE_SET_CURRENT_SUITE: 'SUITES_PAGE_SET_CURRENT_SUITE',
+ SUITES_PAGE_SET_SECTION_EXPANDED: 'SUITES_PAGE_SET_SECTION_EXPANDED'
} as const;
diff --git a/lib/static/modules/actions/index.js b/lib/static/modules/actions/index.js
index f1048d19a..4054b0bc2 100644
--- a/lib/static/modules/actions/index.js
+++ b/lib/static/modules/actions/index.js
@@ -14,6 +14,7 @@ import performanceMarks from '../../../constants/performance-marks';
export * from './static-accepter';
export * from './suites-page';
+export * from './suites';
export const createNotification = (id, status, message, props = {}) => {
const notificationProps = {
@@ -235,7 +236,6 @@ export const suiteBegin = (suite) => ({type: actionNames.SUITE_BEGIN, payload: s
export const testBegin = (test) => ({type: actionNames.TEST_BEGIN, payload: test});
export const testResult = (result) => ({type: actionNames.TEST_RESULT, payload: result});
export const toggleStateResult = (result) => ({type: actionNames.TOGGLE_STATE_RESULT, payload: result});
-export const changeTestRetry = (result) => ({type: actionNames.CHANGE_TEST_RETRY, payload: result});
export const toggleLoading = (payload) => ({type: actionNames.TOGGLE_LOADING, payload});
export const closeSections = (payload) => ({type: actionNames.CLOSE_SECTIONS, payload});
export const runFailed = () => ({type: actionNames.RUN_FAILED_TESTS});
diff --git a/lib/static/modules/actions/suites-page.ts b/lib/static/modules/actions/suites-page.ts
index 38bc211b0..67419c9a8 100644
--- a/lib/static/modules/actions/suites-page.ts
+++ b/lib/static/modules/actions/suites-page.ts
@@ -1,6 +1,22 @@
-import actionNames from '../action-names';
-import {SuitesPageSetCurrentSuiteAction} from '@/static/modules/reducers/suites-page';
+import actionNames from '@/static/modules/action-names';
+import {Action} from '@/static/modules/actions/types';
+
+export type SuitesPageSetCurrentSuiteAction = Action;
export const suitesPageSetCurrentSuite = (suiteId: string): SuitesPageSetCurrentSuiteAction => {
return {type: actionNames.SUITES_PAGE_SET_CURRENT_SUITE, payload: {suiteId}};
};
+
+type SetSectionExpandedStateAction = Action;
+
+export const setSectionExpandedState = (payload: SetSectionExpandedStateAction['payload']): SetSectionExpandedStateAction =>
+ ({type: actionNames.SUITES_PAGE_SET_SECTION_EXPANDED, payload});
+
+export type SuitesPageAction =
+ | SuitesPageSetCurrentSuiteAction
+ | SetSectionExpandedStateAction;
diff --git a/lib/static/modules/actions/suites.ts b/lib/static/modules/actions/suites.ts
new file mode 100644
index 000000000..c919a9e49
--- /dev/null
+++ b/lib/static/modules/actions/suites.ts
@@ -0,0 +1,10 @@
+import actionNames from '@/static/modules/action-names';
+import {Action} from '@/static/modules/actions/types';
+
+interface ChangeTestRetryPayload {
+ browserId: string;
+ retryIndex: number;
+}
+
+export const changeTestRetry = (result: ChangeTestRetryPayload): Action =>
+ ({type: actionNames.CHANGE_TEST_RETRY, payload: result});
diff --git a/lib/static/modules/default-state.ts b/lib/static/modules/default-state.ts
index e0600715c..880fbd270 100644
--- a/lib/static/modules/default-state.ts
+++ b/lib/static/modules/default-state.ts
@@ -52,7 +52,9 @@ export default Object.assign({config: configDefaults}, {
apiValues: {
toolName: ToolName.Testplane,
extraItems: {},
- metaInfoExtenders: {}
+ metaInfoExtenders: {},
+ imagesSaver: {saveImg: () => ''},
+ reportsSaver: {saveReportData: () => ''}
},
loading: {},
modals: [],
@@ -93,5 +95,10 @@ export default Object.assign({config: configDefaults}, {
app: {
isInitialized: false,
currentSuiteId: null
+ },
+ ui: {
+ suitesPage: {
+ expandedSectionsById: {}
+ }
}
}) satisfies State;
diff --git a/lib/static/modules/reducers/grouped-tests/helpers.js b/lib/static/modules/reducers/grouped-tests/helpers.js
index 4b2616375..3f5cbe6b0 100644
--- a/lib/static/modules/reducers/grouped-tests/helpers.js
+++ b/lib/static/modules/reducers/grouped-tests/helpers.js
@@ -14,7 +14,7 @@ export function handleActiveResults({tree = {}, viewMode = ViewMode.ALL, filtere
const browser = tree.browsers.byId[result.parentId];
const testName = browser.parentId;
- if (!isTestNameMatchFilters(testName, testNameFilter, strictMatchFilter)) {
+ if (!isTestNameMatchFilters(testName, browser.name, testNameFilter, strictMatchFilter)) {
continue;
}
diff --git a/lib/static/modules/reducers/suites-page.ts b/lib/static/modules/reducers/suites-page.ts
index 426350894..54377839c 100644
--- a/lib/static/modules/reducers/suites-page.ts
+++ b/lib/static/modules/reducers/suites-page.ts
@@ -1,15 +1,23 @@
-import actionNames from '../action-names';
import {State} from '@/static/new-ui/types/store';
-import {Action} from '@/static/modules/actions/types';
+import {SuitesPageAction} from '@/static/modules/actions/suites-page';
+import actionNames from '@/static/modules/action-names';
+import {applyStateUpdate} from '@/static/modules/utils/state';
-export type SuitesPageSetCurrentSuiteAction = Action;
-
-export default (state: State, action: SuitesPageSetCurrentSuiteAction): State => {
+export default (state: State, action: SuitesPageAction): State => {
switch (action.type) {
case actionNames.SUITES_PAGE_SET_CURRENT_SUITE:
- return {...state, app: {...state.app, currentSuiteId: action.payload.suiteId}};
+ return applyStateUpdate(state, {app: {currentSuiteId: action.payload.suiteId}}) as State;
+ case actionNames.SUITES_PAGE_SET_SECTION_EXPANDED: {
+ return applyStateUpdate(state, {
+ ui: {
+ suitesPage: {
+ expandedSectionsById: {
+ [action.payload.sectionId]: action.payload.isExpanded
+ }
+ }
+ }
+ }) as State;
+ }
default:
return state;
}
diff --git a/lib/static/modules/reducers/tree/nodes/browsers.js b/lib/static/modules/reducers/tree/nodes/browsers.js
index 665b94d7d..7a39b2684 100644
--- a/lib/static/modules/reducers/tree/nodes/browsers.js
+++ b/lib/static/modules/reducers/tree/nodes/browsers.js
@@ -61,10 +61,10 @@ export function calcBrowsersShowness({tree, view, browserIds = [], diff = tree})
const lastResultId = last(getUpdatedProperty(browser, diffBrowser, 'resultIds'));
const lastResultStatus = getUpdatedProperty(tree, diff, ['results', 'byId', lastResultId, 'status']);
- const shouldBeShown = calcBrowserShowness(browser, lastResultStatus, view, diffBrowser);
+ const {shouldBeShown, isHiddenBecauseOfStatus} = calcBrowserShowness(browser, lastResultStatus, view, diffBrowser);
const checkStatus = shouldBeShown ? tree.browsers.stateById[browserId].checkStatus : UNCHECKED;
- changeBrowserState(tree, browserId, {shouldBeShown, checkStatus}, diff);
+ changeBrowserState(tree, browserId, {shouldBeShown, isHiddenBecauseOfStatus, checkStatus}, diff);
});
}
@@ -111,15 +111,19 @@ function calcBrowserOpenness(browser, lastResult, expand, tree) {
function calcBrowserShowness(browser, lastResultStatus, view, diff = browser) {
const {viewMode, filteredBrowsers, testNameFilter, strictMatchFilter} = view;
- if (!isBrowserMatchViewMode(browser, lastResultStatus, viewMode, diff)) {
- return false;
+ const testName = getUpdatedProperty(browser, diff, 'parentId');
+
+ if (!isTestNameMatchFilters(testName, browser.name, testNameFilter, strictMatchFilter)) {
+ return {shouldBeShown: false};
}
- const testName = getUpdatedProperty(browser, diff, 'parentId');
+ if (!shouldShowBrowser(browser, filteredBrowsers, diff)) {
+ return {shouldBeShown: false};
+ }
- if (!isTestNameMatchFilters(testName, testNameFilter, strictMatchFilter)) {
- return false;
+ if (!isBrowserMatchViewMode(browser, lastResultStatus, viewMode, diff)) {
+ return {shouldBeShown: false, isHiddenBecauseOfStatus: true};
}
- return shouldShowBrowser(browser, filteredBrowsers, diff);
+ return {shouldBeShown: true};
}
diff --git a/lib/static/modules/utils/index.js b/lib/static/modules/utils/index.js
index 9b0d54097..522772424 100644
--- a/lib/static/modules/utils/index.js
+++ b/lib/static/modules/utils/index.js
@@ -60,7 +60,7 @@ export function getHttpErrorMessage(error) {
return response ? `(${response.status}) ${response.data}` : message;
}
-export function isTestNameMatchFilters(testName, testNameFilter, strictMatchFilter) {
+export function isTestNameMatchFilters(testName, browserName, testNameFilter, strictMatchFilter) {
if (!testNameFilter) {
return true;
}
@@ -69,7 +69,7 @@ export function isTestNameMatchFilters(testName, testNameFilter, strictMatchFilt
return strictMatchFilter
? new RegExp(`^${filterRegExpStr}$`).test(testName)
- : new RegExp(filterRegExpStr).test(testName);
+ : new RegExp(filterRegExpStr).test(`${testName} ${browserName}`);
}
export function isBrowserMatchViewMode(browser, lastResultStatus, viewMode, diff = browser) {
diff --git a/lib/static/new-ui.css b/lib/static/new-ui.css
index 6de9c78e2..30289f9e2 100644
--- a/lib/static/new-ui.css
+++ b/lib/static/new-ui.css
@@ -1,5 +1,17 @@
+* {
+ box-sizing: border-box;
+}
+
+:root {
+ --color-link: #4f46e5;
+ --color-link-hover: #818cf8;
+ --color-bg-dark: #101827;
+ --color-bg-gray: #eee;
+}
+
.g-root {
--g-font-family-sans: 'Jost', sans-serif;
+ --g-color-base-background: #eee;
}
body {
@@ -8,4 +20,17 @@ body {
.report {
font-family: var(--g-font-family-sans), sans-serif !important;
+ margin-bottom: 0 !important;;
+}
+
+/* Aside header styles */
+:root {
+ --gn-aside-header-item-current-background-color: #1f2937;
+ --gn-aside-header-item-background-color-hover: #1f2937;
+ --gn-aside-header-item-current-icon-color: #fff;
+ --gn-aside-header-item-icon-color: #9ca3af;
+}
+
+.gn-aside-header__header:after {
+ display: none;
}
diff --git a/lib/static/new-ui/components/AttemptPicker/index.tsx b/lib/static/new-ui/components/AttemptPicker/index.tsx
new file mode 100644
index 000000000..2225140de
--- /dev/null
+++ b/lib/static/new-ui/components/AttemptPicker/index.tsx
@@ -0,0 +1,61 @@
+import {Flex} from '@gravity-ui/uikit';
+import React, {ReactNode} from 'react';
+import {connect} from 'react-redux';
+
+import {State} from '@/static/new-ui/types/store';
+import {AttemptPickerItem} from '@/static/new-ui/components/AttemptPickerItem';
+
+interface AttemptPickerProps {
+ onChange?: (browserId: string, resultId: string, attemptIndex: number) => unknown;
+}
+
+interface AttemptPickerInternalProps extends AttemptPickerProps {
+ browserId: string | null;
+ resultIds: string[];
+ currentResultId: string;
+}
+
+function AttemptPickerInternal(props: AttemptPickerInternalProps): ReactNode {
+ const {resultIds, currentResultId} = props;
+
+ const onClickHandler = (resultId: string, attemptIndex: number): void => {
+ if (!props.browserId || currentResultId === resultId) {
+ return;
+ }
+
+ props.onChange?.(props.browserId, resultId, attemptIndex);
+ };
+
+ return
+ Attempts
+
+ {resultIds.map((resultId, index) => {
+ const isActive = resultId === currentResultId;
+
+ return
onClickHandler(resultId, index)}
+ />;
+ })}
+
+ ;
+}
+
+export const AttemptPicker = connect((state: State) => {
+ let resultIds: string[] = [];
+ let currentResultId = '';
+ const browserId = state.app.currentSuiteId;
+
+ if (browserId && state.tree.browsers.byId[browserId]) {
+ resultIds = state.tree.browsers.byId[browserId].resultIds;
+ currentResultId = resultIds[state.tree.browsers.stateById[browserId].retryIndex];
+ }
+
+ return {
+ browserId,
+ resultIds,
+ currentResultId
+ };
+})(AttemptPickerInternal);
diff --git a/lib/static/new-ui/components/AttemptPickerItem/index.module.css b/lib/static/new-ui/components/AttemptPickerItem/index.module.css
new file mode 100644
index 000000000..c883179bc
--- /dev/null
+++ b/lib/static/new-ui/components/AttemptPickerItem/index.module.css
@@ -0,0 +1,34 @@
+.attempt-picker-item {
+ --g-button-padding: 8px;
+ margin-right: 2px;
+}
+
+.attempt-picker-item--active {
+ --g-button-border-width: 1px;
+}
+
+.attempt-picker-item--staged {
+ background-color: rgba(255, 235, 206, 1);
+ border-color: rgba(255, 235, 206, 1);
+}
+
+.attempt-picker-item--commited {
+ background-color: rgba(207, 231, 252, 1);
+ border-color: rgba(207, 231, 252, 1);
+}
+
+.attempt-picker-item--fail {
+ --g-button-background-color: #fce0ff;
+ --g-button-text-color: #bd1993;
+ --g-button-border-color: #bd1993;
+}
+
+.attempt-picker-item--fail_error {
+ --g-button-background-color: #fce0ff;
+ --g-button-text-color: #bd1993;
+ --g-button-border-color: #bd1993;
+}
+
+.attempt-picker-item--non-matched {
+ opacity: 0.5;
+}
diff --git a/lib/static/new-ui/components/AttemptPickerItem/index.tsx b/lib/static/new-ui/components/AttemptPickerItem/index.tsx
new file mode 100644
index 000000000..3de45c473
--- /dev/null
+++ b/lib/static/new-ui/components/AttemptPickerItem/index.tsx
@@ -0,0 +1,99 @@
+import {Button, ButtonProps} from '@gravity-ui/uikit';
+import React, {ReactNode} from 'react';
+import classNames from 'classnames';
+import {connect} from 'react-redux';
+import {get} from 'lodash';
+
+import {hasUnrelatedToScreenshotsErrors, isFailStatus} from '@/common-utils';
+import {TestStatus} from '@/constants';
+import {ResultEntityError, State} from '@/static/new-ui/types/store';
+import styles from './index.module.css';
+
+const getButtonStyleByStatus = (status: TestStatus): Pick => {
+ switch (status) {
+ case TestStatus.SUCCESS:
+ return {
+ view: 'flat-success',
+ selected: true
+ };
+ case TestStatus.UPDATED:
+ return {
+ view: 'flat-success',
+ selected: true
+ };
+ case TestStatus.ERROR:
+ return {
+ view: 'flat-danger',
+ selected: true
+ };
+ case TestStatus.FAIL:
+ return {
+ view: 'flat-utility',
+ selected: true
+ };
+ // eslint-disable-next-line camelcase
+ case TestStatus.FAIL_ERROR:
+ return {
+ view: 'flat-utility',
+ selected: true
+ };
+ case TestStatus.SKIPPED:
+ return {
+ view: 'normal',
+ selected: false
+ };
+ case TestStatus.RUNNING:
+ return {
+ view: 'outlined',
+ selected: false
+ };
+ default:
+ return {
+ view: 'flat',
+ selected: false
+ };
+ }
+};
+
+export interface AttemptPickerItemProps {
+ resultId: string;
+ isActive?: boolean;
+ onClick?: () => unknown;
+ title?: string;
+}
+
+interface AttemptPickerItemInternalProps extends AttemptPickerItemProps{
+ status: TestStatus;
+ attempt: number;
+ keyToGroupTestsBy: string;
+ matchedSelectedGroup: boolean;
+}
+
+function AttemptPickerItemInternal(props: AttemptPickerItemInternalProps): ReactNode {
+ const {status, attempt, isActive, onClick, title, keyToGroupTestsBy, matchedSelectedGroup} = props;
+ const buttonStyle = getButtonStyleByStatus(status);
+
+ const className = classNames(
+ styles.attemptPickerItem,
+ {[styles['attempt-picker-item--active']]: isActive},
+ {[styles[`attempt-picker-item--${status}`]]: status},
+ {[styles['attempt-picker-item--non-matched']]: keyToGroupTestsBy && !matchedSelectedGroup}
+ );
+
+ return ;
+}
+
+export const AttemptPickerItem = connect(
+ ({tree, view: {keyToGroupTestsBy}}: State, {resultId}: AttemptPickerItemProps) => {
+ const result = tree.results.byId[resultId];
+ const matchedSelectedGroup = get(tree.results.stateById[resultId], 'matchedSelectedGroup', false);
+ const {status, attempt} = result;
+
+ return {
+ status: isFailStatus(result.status) && hasUnrelatedToScreenshotsErrors((result as ResultEntityError).error) ? TestStatus.FAIL_ERROR : status,
+ attempt,
+ keyToGroupTestsBy,
+ matchedSelectedGroup
+ };
+ }
+)(AttemptPickerItemInternal);
diff --git a/lib/static/new-ui/components/Card/index.module.css b/lib/static/new-ui/components/Card/index.module.css
new file mode 100644
index 000000000..d3a1bb8ee
--- /dev/null
+++ b/lib/static/new-ui/components/Card/index.module.css
@@ -0,0 +1,3 @@
+.card {
+ box-shadow: rgb(255, 255, 255) 0px 0px 0px 0px, rgba(9, 9, 11, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
+}
diff --git a/lib/static/new-ui/components/Card/index.tsx b/lib/static/new-ui/components/Card/index.tsx
new file mode 100644
index 000000000..b752b4063
--- /dev/null
+++ b/lib/static/new-ui/components/Card/index.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import styles from './index.module.css';
+
+interface CardProps {
+ className?: string;
+ children?: React.ReactNode;
+}
+
+export function Card(props: CardProps): React.ReactNode {
+ return {props.children}
;
+}
diff --git a/lib/static/new-ui/components/MainLayout/index.module.css b/lib/static/new-ui/components/MainLayout/index.module.css
new file mode 100644
index 000000000..d29e5520c
--- /dev/null
+++ b/lib/static/new-ui/components/MainLayout/index.module.css
@@ -0,0 +1,8 @@
+.aside-header-bg-wrapper {
+ width: 100%;
+}
+
+.aside-header-bg {
+ background-color: var(--color-bg-dark);
+ width: 100%;
+}
diff --git a/lib/static/new-ui/components/MainLayout.tsx b/lib/static/new-ui/components/MainLayout/index.tsx
similarity index 78%
rename from lib/static/new-ui/components/MainLayout.tsx
rename to lib/static/new-ui/components/MainLayout/index.tsx
index f0cd44f2f..3bbb6a60f 100644
--- a/lib/static/new-ui/components/MainLayout.tsx
+++ b/lib/static/new-ui/components/MainLayout/index.tsx
@@ -1,7 +1,8 @@
import {AsideHeader, MenuItem as GravityMenuItem} from '@gravity-ui/navigation';
import React from 'react';
import {useNavigate, matchPath, useLocation} from 'react-router-dom';
-import TestplaneIcon from '../../icons/testplane.svg';
+import TestplaneIcon from '../../../icons/testplane-mono.svg';
+import styles from './index.module.css';
interface MenuItem {
title: string;
@@ -27,7 +28,7 @@ export function MainLayout(props: MainLayoutProps): JSX.Element {
}));
return navigate('/')}} compact={true}
- headerDecoration={true} menuItems={gravityMenuItems}
+ headerDecoration={false} menuItems={gravityMenuItems} customBackground={} customBackgroundClassName={styles.asideHeaderBgWrapper}
renderContent={(): React.ReactNode => props.children} hideCollapseButton={true}
/>;
}
diff --git a/lib/static/new-ui/components/MetaInfo/index.module.css b/lib/static/new-ui/components/MetaInfo/index.module.css
new file mode 100644
index 000000000..a45bdff52
--- /dev/null
+++ b/lib/static/new-ui/components/MetaInfo/index.module.css
@@ -0,0 +1,11 @@
+.meta-info {
+ padding-right: 30px;
+}
+
+.meta-info-value {
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
diff --git a/lib/static/new-ui/components/MetaInfo/index.tsx b/lib/static/new-ui/components/MetaInfo/index.tsx
new file mode 100644
index 000000000..7693cc767
--- /dev/null
+++ b/lib/static/new-ui/components/MetaInfo/index.tsx
@@ -0,0 +1,146 @@
+import path from 'path';
+
+import {DefinitionList} from '@gravity-ui/components';
+import {mapValues, isObject, omitBy, isEmpty} from 'lodash';
+import React, {ReactNode} from 'react';
+import {connect} from 'react-redux';
+
+import {isUrl, getUrlWithBase, getRelativeUrl} from '@/common-utils';
+import {ResultEntity, State} from '@/static/new-ui/types/store';
+import {HtmlReporterValues} from '@/plugin-api';
+import {ReporterConfig} from '@/types';
+import styles from './index.module.css';
+
+const serializeMetaValues = (metaInfo: Record
): Record =>
+ mapValues(metaInfo, (v): string => {
+ if (isObject(v)) {
+ return JSON.stringify(v);
+ }
+
+ return v?.toString() ?? 'No value';
+ });
+
+interface MetaInfoItem {
+ label: string;
+ content: string;
+ url?: string;
+ copyText?: string;
+}
+
+export interface MetaInfoProps {
+ resultId: string,
+ qa?: string;
+}
+
+interface MetaInfoInternalProps extends MetaInfoProps {
+ result: ResultEntity;
+ testName: string;
+ metaInfoBaseUrls: ReporterConfig['metaInfoBaseUrls'];
+ apiValues: Pick;
+ baseHost: string;
+}
+
+function MetaInfoInternal(props: MetaInfoInternalProps): ReactNode {
+ const resolveMetaInfoExtenders = (): Record => {
+ const {testName, apiValues: {extraItems, metaInfoExtenders}} = props;
+
+ return omitBy(mapValues(metaInfoExtenders, (extender) => {
+ const stringifiedFn = extender.startsWith('return') ? extender : `return ${extender}`;
+
+ return new Function(stringifiedFn)()({testName}, extraItems);
+ }), isEmpty);
+ };
+
+ const {result, metaInfoBaseUrls, baseHost} = props;
+
+ const serializedMetaValues = serializeMetaValues({
+ ...result.metaInfo,
+ ...resolveMetaInfoExtenders()
+ });
+
+ const metaInfoItems: MetaInfoItem[] = Object.entries(serializedMetaValues).map(([key, value]) => ({
+ label: key,
+ content: value
+ }));
+
+ const metaInfoItemsWithResolvedUrls = metaInfoItems.map((item) => {
+ if (item.label === 'url' || metaInfoBaseUrls[item.label] === 'auto') {
+ const url = getUrlWithBase(getRelativeUrl(item.content), baseHost);
+ return {
+ label: item.label,
+ content: getRelativeUrl(item.content),
+ url,
+ copyText: url
+ };
+ }
+
+ if (metaInfoBaseUrls[item.label]) {
+ const baseUrl = metaInfoBaseUrls[item.label];
+ const resolvedUrl = isUrl(baseUrl) ? getUrlWithBase(item.content, baseUrl) : path.join(baseUrl, item.content);
+ // For file paths/arbitrary strings show them unchanged. If original value was URL, we don't want to mislead
+ // user by showing old URL, actually pointing to resolved URL, so we show an actual resolved URL here.
+
+ const displayName = isUrl(item.content) ? resolvedUrl : item.content;
+ return {
+ label: item.label,
+ content: displayName,
+ copyText: displayName,
+ url: resolvedUrl
+ };
+ }
+
+ if (!isUrl(item.content)) {
+ return item;
+ }
+
+ return {
+ label: item.label,
+ content: item.content,
+ url: item.content
+ };
+ });
+
+ const hasUrlMetaInfoItem = metaInfoItemsWithResolvedUrls.some(item => item.label === 'url');
+ if (!hasUrlMetaInfoItem && result.suiteUrl) {
+ metaInfoItemsWithResolvedUrls.push({
+ label: 'url',
+ content: getRelativeUrl(result.suiteUrl),
+ url: getUrlWithBase(getRelativeUrl(result.suiteUrl), baseHost)
+ });
+ }
+
+ return {
+ if (item.url) {
+ return {
+ name: item.label,
+ content:
+ {item.content}
+ ,
+ copyText: item.copyText ?? item.content
+ };
+ }
+
+ return {
+ name: item.label,
+ content: {item.content},
+ copyText: item.copyText ?? item.content
+ };
+ })
+ }/>;
+}
+
+export const MetaInfo = connect(
+ ({tree, config: {metaInfoBaseUrls}, apiValues, view}: State, {resultId}: MetaInfoProps) => {
+ const result = tree.results.byId[resultId];
+ const browser = tree.browsers.byId[result.parentId];
+
+ return {
+ result,
+ testName: browser.parentId,
+ metaInfoBaseUrls,
+ apiValues,
+ baseHost: view.baseHost
+ };
+ }
+)(MetaInfoInternal);
diff --git a/lib/static/new-ui/features/suites/components/CollapsibleSection/index.module.css b/lib/static/new-ui/features/suites/components/CollapsibleSection/index.module.css
new file mode 100644
index 000000000..9a206edd3
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/CollapsibleSection/index.module.css
@@ -0,0 +1,16 @@
+.expand-arrow {
+ transition: opacity .1s ease, transform .1s ease;
+}
+
+.summary {
+ cursor: pointer;
+ user-select: none;
+}
+
+.summary:hover {
+ opacity: .8;
+}
+
+.expand-arrow--expanded {
+ transform: rotate(180deg);
+}
diff --git a/lib/static/new-ui/features/suites/components/CollapsibleSection/index.tsx b/lib/static/new-ui/features/suites/components/CollapsibleSection/index.tsx
new file mode 100644
index 000000000..409c065c9
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/CollapsibleSection/index.tsx
@@ -0,0 +1,56 @@
+import {Disclosure, Flex} from '@gravity-ui/uikit';
+import React, {ReactElement, ReactNode} from 'react';
+import classNames from 'classnames';
+import {ChevronUp} from '@gravity-ui/icons';
+
+import styles from './index.module.css';
+import {connect} from 'react-redux';
+import {State} from '@/static/new-ui/types/store';
+import {getSectionId} from '@/static/new-ui/features/suites/components/CollapsibleSection/utils';
+import {bindActionCreators} from 'redux';
+import * as actions from '@/static/modules/actions';
+
+interface CollapsibleSectionProps {
+ id: string;
+ title: string;
+ body: ReactNode;
+}
+
+interface CollapsibleSectionInternalProps extends CollapsibleSectionProps{
+ sectionId: string;
+ expanded: boolean;
+ actions: typeof actions;
+}
+
+export function CollapsibleSectionInternal(props: CollapsibleSectionInternalProps): ReactNode {
+ const onUpdateHandler = (): void => {
+ props.actions.setSectionExpandedState({sectionId: props.sectionId, isExpanded: !props.expanded});
+ };
+
+ return
+
+ {(): ReactElement => {
+ return
+ {props.title}
+
+ ;
+ }}
+
+ {props.body}
+ ;
+}
+
+export const CollapsibleSection = connect((state: State, props: CollapsibleSectionProps) => {
+ const browserId = state.app.currentSuiteId;
+ let sectionId = '';
+
+ if (browserId && state.tree.browsers.byId[browserId]) {
+ const attemptIndex = state.tree.browsers.stateById[browserId].retryIndex;
+ sectionId = getSectionId(browserId, attemptIndex, props.id);
+ }
+
+ return {
+ sectionId,
+ expanded: state.ui.suitesPage.expandedSectionsById[sectionId] ?? true
+ };
+}, (dispatch) => ({actions: bindActionCreators(actions, dispatch)}))(CollapsibleSectionInternal);
diff --git a/lib/static/new-ui/features/suites/components/CollapsibleSection/utils.ts b/lib/static/new-ui/features/suites/components/CollapsibleSection/utils.ts
new file mode 100644
index 000000000..1d576321e
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/CollapsibleSection/utils.ts
@@ -0,0 +1,3 @@
+export const getSectionId = (_browserId: string, _attemptIndex: number, id: string): string => {
+ return id;
+};
diff --git a/lib/static/new-ui/features/suites/components/SuiteTitle/index.module.css b/lib/static/new-ui/features/suites/components/SuiteTitle/index.module.css
new file mode 100644
index 000000000..fa41fdf77
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/SuiteTitle/index.module.css
@@ -0,0 +1,30 @@
+.heading {}
+
+.heading:hover .copy-button {
+ opacity: 1;
+}
+
+.separator {
+ margin: 0 4px;
+ display: inline-block;
+ width: 16px;
+ position: relative;
+}
+
+.invisible-space {
+ position: absolute;
+}
+
+.copy-button {
+ margin-left: 8px;
+ opacity: 0;
+ transition: opacity 0.1s ease;
+}
+
+.title-part-wrapper {}
+
+.title-part-wrapper:last-child {
+ align-items: center;
+ display: inline-flex;
+ white-space: nowrap;
+}
diff --git a/lib/static/new-ui/features/suites/components/SuiteTitle/index.tsx b/lib/static/new-ui/features/suites/components/SuiteTitle/index.tsx
new file mode 100644
index 000000000..c9cd05ffc
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/SuiteTitle/index.tsx
@@ -0,0 +1,45 @@
+import React, {ReactNode} from 'react';
+import {connect} from 'react-redux';
+import {State} from '@/static/new-ui/types/store';
+import {ClipboardButton, Flex} from '@gravity-ui/uikit';
+import {ChevronRight} from '@gravity-ui/icons';
+
+import styles from './index.module.css';
+import classNames from 'classnames';
+
+interface SuiteTitleProps {
+ className?: string;
+}
+
+interface SuiteTitlePropsInternal extends SuiteTitleProps {
+ suitePath: string[];
+}
+
+function SuiteTitleInternal(props: SuiteTitlePropsInternal): ReactNode {
+ return
+
+ {props.suitePath.map((item, index) => (
+
+ {item}
+ {index !== props.suitePath.length - 1 ?
+
:
+ }
+
+ ))}
+
+ ;
+}
+
+export const SuiteTitle = connect((state: State) => {
+ let suitePath: string[] = [];
+ const browserId = state.app.currentSuiteId;
+
+ if (browserId && state.tree.browsers.byId[browserId]) {
+ const browserName = state.tree.browsers.byId[browserId].name;
+ const suiteId = state.tree.browsers.byId[browserId].parentId;
+
+ suitePath = [...state.tree.suites.byId[suiteId].suitePath, browserName];
+ }
+
+ return {suitePath};
+})(SuiteTitleInternal);
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 2d0823863..38980d7fa 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
@@ -1,6 +1,37 @@
-.controls-row {
- align-items: center;
+.collapsible-section__body {
+ padding: 10px 30px 10px 2px;
+ word-break: break-all;
+}
+
+.collapsible-section__body a:link, .collapsible-section__body a:visited {
+ color: var(--color-link);
+}
+
+.collapsible-section__body a:hover {
+ color: var(--color-link-hover);
+}
+
+.card {
+ background-color: #fff;
display: flex;
- height: 28px;
- margin-top: var(--g-spacing-2);
+ flex-direction: column;
+ margin: 10px;
+ padding: 15px 20px 20px;
+ border-radius: 10px;
+}
+
+.card__title {
+ margin-bottom: 8px !important;
+}
+
+.tree-view-card {
+ height: calc(100vh - 20px);
+ margin-right: 2px;
+ gap: 8px;
+}
+
+.test-view-card {
+ margin-left: 2px;
+ max-height: calc(100vh - 20px);
+ gap: 12px;
}
diff --git a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
index bc522d40f..af2b144f6 100644
--- a/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
+++ b/lib/static/new-ui/features/suites/components/SuitesPage/index.tsx
@@ -1,30 +1,68 @@
import {Flex} from '@gravity-ui/uikit';
import React, {ReactNode} from 'react';
import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
import {SplitViewLayout} from '@/static/new-ui/components/SplitViewLayout';
import {TestNameFilter} from '@/static/new-ui/features/suites/components/TestNameFilter';
import {SuitesTreeView} from '@/static/new-ui/features/suites/components/SuitesTreeView';
import {TestStatusFilter} from '@/static/new-ui/features/suites/components/TestStatusFilter';
import {BrowsersSelect} from '@/static/new-ui/features/suites/components/BrowsersSelect';
+import {SuiteTitle} from '@/static/new-ui/features/suites/components/SuiteTitle';
+import * as actions from '@/static/modules/actions';
+import {CollapsibleSection} from '@/static/new-ui/features/suites/components/CollapsibleSection';
+import {MetaInfo} from '@/static/new-ui/components/MetaInfo';
+import {State} from '@/static/new-ui/types/store';
+import {Card} from '@/static/new-ui/components/Card';
+import {AttemptPicker} from '../../../../components/AttemptPicker';
-function SuitesPageInternal(): ReactNode {
+import styles from './index.module.css';
+import classNames from 'classnames';
+
+interface SuitesPageProps {
+ actions: typeof actions;
+ currentResultId: string;
+}
+
+function SuitesPageInternal(props: SuitesPageProps): ReactNode {
return
-
- Suites
+
+ Suites
-
-
-
+
-
+
+
+
+
+
+ props.actions.changeTestRetry({browserId, retryIndex})} />
+
+
+ } id={'overview'}/>
+
-
;
}
-export const SuitesPage = connect()(SuitesPageInternal);
+export const SuitesPage = connect(
+ (state: State) => {
+ let resultIds: string[] = [];
+ let currentResultId = '';
+ const browserId = state.app.currentSuiteId;
+
+ if (browserId && state.tree.browsers.byId[browserId]) {
+ resultIds = state.tree.browsers.byId[browserId].resultIds;
+ currentResultId = resultIds[state.tree.browsers.stateById[browserId].retryIndex];
+ }
+
+ return {
+ currentResultId
+ };
+ },
+ (dispatch) => ({actions: bindActionCreators(actions, dispatch)})
+)(SuitesPageInternal);
diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css
index b42f5125a..dc53f528b 100644
--- a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css
+++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.module.css
@@ -1,6 +1,4 @@
-.tree-view {
- margin-top: var(--g-spacing-2);
-}
+.tree-view {}
.tree-view :global(.g-text_ellipsis) {
white-space: normal;
@@ -46,6 +44,10 @@
flex-shrink: 0;
}
+ .tree-view__item :global(.g-list-item-view__icon) {
+ margin-top: 2px;
+ }
+
.tree-view__item > div > div {
align-items: start !important;
}
diff --git a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx
index f0a175191..5e0f05870 100644
--- a/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx
+++ b/lib/static/new-ui/features/suites/components/SuitesTreeView/index.tsx
@@ -19,7 +19,6 @@ import {
TreeViewItemType,
TreeViewSuiteData
} from '@/static/new-ui/features/suites/components/SuitesPage/types';
-import {getIconByStatus} from '@/static/new-ui/utils';
import {TreeViewItemTitle} from '@/static/new-ui/features/suites/components/TreeViewItemTitle';
import {TreeViewItemSubtitle} from '@/static/new-ui/features/suites/components/TreeViewItemSubtitle';
import {State} from '@/static/new-ui/types/store';
@@ -29,6 +28,7 @@ import {
} from '@/static/new-ui/features/suites/components/SuitesTreeView/selectors';
import styles from './index.module.css';
import {TestStatus} from '@/constants';
+import {TreeViewItemIcon} from '@/static/new-ui/features/suites/components/TreeViewItemIcon';
interface SuitesTreeViewProps {
treeViewItems: TreeViewItem[];
@@ -128,7 +128,7 @@ function SuitesTreeViewInternal(props: SuitesTreeViewProps): ReactNode {
onItemClick,
mapItemDataToContentProps: (x) => {
return {
- startSlot: getIconByStatus(x.status),
+ startSlot: ,
title: ,
subtitle:
};
diff --git a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.module.css b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.module.css
index 31f108c27..9215ea418 100644
--- a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.module.css
+++ b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.module.css
@@ -1,3 +1,7 @@
+.test-status-filter {
+ --g-color-base-background: #fff;
+}
+
.test-status-filter-option {
display: flex;
align-items: center;
diff --git a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx
index 3bcca344b..08462bfd7 100644
--- a/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx
+++ b/lib/static/new-ui/features/suites/components/TestStatusFilter/index.tsx
@@ -28,7 +28,7 @@ interface TestStatusFilterProps {
}
function TestStatusFilterInternal({statusCounts, actions, viewMode}: TestStatusFilterProps): ReactNode {
- return void actions.changeViewMode(e.target.value)} value={viewMode}>
+ return void actions.changeViewMode(e.target.value)} value={viewMode}>
} />
} />
} />
diff --git a/lib/static/new-ui/features/suites/components/TestStatusFilter/selectors.ts b/lib/static/new-ui/features/suites/components/TestStatusFilter/selectors.ts
index a8983ad50..694df87ed 100644
--- a/lib/static/new-ui/features/suites/components/TestStatusFilter/selectors.ts
+++ b/lib/static/new-ui/features/suites/components/TestStatusFilter/selectors.ts
@@ -1,5 +1,5 @@
import {createSelector} from 'reselect';
-import {getAllBrowserIds, getResults} from '@/static/new-ui/store/selectors';
+import {getBrowsersState, getResults} from '@/static/new-ui/store/selectors';
export interface StatusCounts {
success: number;
@@ -12,14 +12,17 @@ export interface StatusCounts {
}
export const getStatusCounts = createSelector(
- [getResults, getAllBrowserIds],
- (results, browserIds) => {
+ [getResults, getBrowsersState],
+ (results, browsersState) => {
const latestAttempts: Record = {};
const retriedTests = new Set();
let retries = 0;
Object.values(results).forEach(result => {
const {parentId: testId, attempt, status, timestamp} = result;
+ if (!browsersState[testId].shouldBeShown && !browsersState[testId].isHiddenBecauseOfStatus) {
+ return;
+ }
if (attempt > 0) {
retriedTests.add(testId);
}
@@ -35,7 +38,7 @@ export const getStatusCounts = createSelector(
success: 0,
fail: 0,
skipped: 0,
- total: browserIds.length,
+ total: Object.keys(latestAttempts).length,
retried: retriedTests.size,
retries,
idle: 0
diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.module.css b/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.module.css
new file mode 100644
index 000000000..86c45dd3b
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.module.css
@@ -0,0 +1,5 @@
+.wrapper {
+ align-items: center;
+ display: flex;
+ height: 1lh;
+}
diff --git a/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.tsx b/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.tsx
new file mode 100644
index 000000000..441370db4
--- /dev/null
+++ b/lib/static/new-ui/features/suites/components/TreeViewItemIcon/index.tsx
@@ -0,0 +1,13 @@
+import React, {ReactNode} from 'react';
+import {TestStatus} from '@/constants';
+import {getIconByStatus} from '@/static/new-ui/utils';
+
+import styles from './index.module.css';
+
+interface TreeViewItemIconProps {
+ status: TestStatus;
+}
+
+export function TreeViewItemIcon(props: TreeViewItemIconProps): ReactNode {
+ return {getIconByStatus(props.status)}
;
+}
diff --git a/lib/static/new-ui/types/store.ts b/lib/static/new-ui/types/store.ts
index 05f632234..d2a13cd69 100644
--- a/lib/static/new-ui/types/store.ts
+++ b/lib/static/new-ui/types/store.ts
@@ -1,16 +1,21 @@
import {TestStatus, ViewMode} from '@/constants';
-import {BrowserItem, ImageFile} from '@/types';
+import {BrowserItem, ImageFile, ReporterConfig, TestError} from '@/types';
+import {HtmlReporterValues} from '@/plugin-api';
export interface SuiteEntityNode {
+ id: string;
name: string;
status: TestStatus;
suiteIds: string[];
+ suitePath: string[];
}
export interface SuiteEntityLeaf {
+ id: string;
name: string;
status: TestStatus;
browserIds: string[];
+ suitePath: string[];
}
export type SuiteEntity = SuiteEntityNode | SuiteEntityLeaf;
@@ -18,34 +23,47 @@ export type SuiteEntity = SuiteEntityNode | SuiteEntityLeaf;
export const isSuiteEntityLeaf = (suite: SuiteEntity): suite is SuiteEntityLeaf => Boolean((suite as SuiteEntityLeaf).browserIds);
export interface BrowserEntity {
+ id: string;
name: string;
resultIds: string[];
+ parentId: string;
}
export interface ResultEntityCommon {
+ id: string;
parentId: string;
attempt: number;
imageIds: string[];
status: TestStatus;
timestamp: number;
+ metaInfo: Record;
+ suiteUrl?: string;
}
export interface ResultEntityError extends ResultEntityCommon {
- error: Error;
- status: TestStatus.ERROR;
+ error: TestError;
+ status: TestStatus.ERROR | TestStatus.FAIL;
}
export type ResultEntity = ResultEntityCommon | ResultEntityError;
export const isResultEntityError = (result: ResultEntity): result is ResultEntityError => result.status === TestStatus.ERROR;
-export interface ImageEntityError {
+interface ImageEntityCommon {
+ id: string;
+ parentId: string;
+}
+
+export interface ImageEntityError extends ImageEntityCommon {
status: TestStatus.ERROR;
}
-export interface ImageEntityFail {
+export interface ImageEntityFail extends ImageEntityCommon {
+ status: TestStatus.FAIL;
stateName: string;
diffImg: ImageFile;
+ actualImg: ImageFile;
+ expectedImg: ImageFile;
}
export type ImageEntity = ImageEntityError | ImageEntityFail;
@@ -59,6 +77,33 @@ export interface SuiteState {
export interface BrowserState {
shouldBeShown: boolean;
+ retryIndex: number;
+ // True if test is not shown because of its status. Useful when computing counts by status.
+ isHiddenBecauseOfStatus?: boolean;
+}
+
+export interface ResultState {
+ matchedSelectedGroup: boolean;
+}
+
+export interface TreeEntity {
+ browsers: {
+ allIds: string[];
+ byId: Record;
+ stateById: Record
+ };
+ images: {
+ byId: Record;
+ }
+ results: {
+ byId: Record;
+ stateById: Record;
+ };
+ suites: {
+ allRootIds: string[];
+ byId: Record;
+ stateById: Record;
+ };
}
export interface State {
@@ -66,28 +111,20 @@ export interface State {
isInitialized: boolean;
currentSuiteId: string | null;
};
- browsers: BrowserItem[];
- tree: {
- browsers: {
- allIds: string[];
- byId: Record;
- stateById: Record
- };
- images: {
- byId: Record;
+ ui: {
+ suitesPage: {
+ expandedSectionsById: Record;
}
- results: {
- byId: Record;
- };
- suites: {
- allRootIds: string[];
- byId: Record;
- stateById: Record;
- };
};
+ browsers: BrowserItem[];
+ tree: TreeEntity;
view: {
testNameFilter: string;
viewMode: ViewMode;
filteredBrowsers: BrowserItem[];
+ keyToGroupTestsBy: string;
+ baseHost: string;
};
+ apiValues: HtmlReporterValues;
+ config: ReporterConfig;
}
diff --git a/lib/static/styles.css b/lib/static/styles.css
index 7789588b9..b47233df1 100644
--- a/lib/static/styles.css
+++ b/lib/static/styles.css
@@ -534,31 +534,10 @@ main.container {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath transform='scale(0.027,-0.027) translate(0,-448)' d=' M561.938 289.94L417.94 433.908C387.926 463.922 336 442.903 336 399.968V342.77C293.55 340.89 251.97 336.2200000000001 215.24 324.7800000000001C180.07 313.8300000000001 152.17 297.2000000000001 132.33 275.36C108.22 248.8 96 215.4 96 176.06C96 114.363 129.178 63.605 180.87 31.3C218.416 7.792 266.118 43.951 251.89 87.04C236.375 134.159 234.734 157.963 336 165.8V112C336 69.007 387.968 48.087 417.94 78.06L561.938 222.06C580.688 240.8 580.688 271.2 561.938 289.94zM384 112V215.84C255.309 213.918 166.492 192.65 206.31 72C176.79 90.45 144 123.92 144 176.06C144 285.394 273.14 295.007 384 295.91V400L528 256L384 112zM408.74 27.507A82.658 82.658 0 0 1 429.714 36.81C437.69 41.762 448 35.984 448 26.596V-16C448 -42.51 426.51 -64 400 -64H48C21.49 -64 0 -42.51 0 -16V336C0 362.51 21.49 384 48 384H180C186.627 384 192 378.627 192 372V367.514C192 362.597 189.013 358.145 184.431 356.362C170.729 351.031 158.035 344.825 146.381 337.777A12.138 12.138 0 0 0 140.101 336H54A6 6 0 0 1 48 330V-10A6 6 0 0 1 54 -16H394A6 6 0 0 1 400 -10V15.966C400 21.336 403.579 26.025 408.74 27.507z' /%3E%3C/svg%3E");
}
-.meta-info__item-value {
- display: flex;
- gap: 2px;
- align-items: center;
- word-wrap: break-word;
-}
-
-.custom-icon_view-local{
- display: block;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
.copy-button {
margin-left: 2px;
}
-.meta-info__item .copy-button {
- opacity: 0;
-}
-
-.meta-info__item:hover .copy-button {
- opacity: 1;
-}
-
.error {
padding: 10px;
}
@@ -602,41 +581,6 @@ main.container {
user-select: none;
}
-.tab-switcher__button {
- --g-button-padding: 8px;
- margin-right: 2px;
-}
-
-.tab-switcher__button_status_staged {
- background-color: rgba(255, 235, 206, 1);
- border-color: rgba(255, 235, 206, 1);
-}
-
-.tab-switcher__button_status_commited {
- background-color: rgba(207, 231, 252, 1);
- border-color: rgba(207, 231, 252, 1);
-}
-
-.tab-switcher__button_non-matched {
- opacity: 0.5;
-}
-
-.tab-switcher__button_status_fail {
- --g-button-background-color: #fce0ff;
- --g-button-text-color: #bd1993;
- --g-button-border-color: #bd1993;
-}
-
-.tab-switcher__button_status_fail_error {
- --g-button-background-color: #fce0ff;
- --g-button-text-color: #bd1993;
- --g-button-border-color: #bd1993;
-}
-
-.tab-switcher__button.tab-switcher__button_active {
- --g-button-border-width: 1px;
-}
-
.collapsed,
.invisible {
display: none;
@@ -1015,43 +959,46 @@ a:active {
/* Gravity UI Font Styles */
.text-display-1 {
font-family: Jost, sans-serif;
- font-weight: 600;
- font-size: var(--g-text-display-1-font-size);
- line-height: var(--g-text-display-1-line-height);
+ font-weight: 500;
+ font-size: 24px;
+ line-height: 28px;
margin: 0;
}
.text-display-2 {
font-family: Jost, sans-serif;
- font-weight: 600;
- font-size: 32px;
- line-height: 40px;
+ font-weight: 500;
+ font-size: 18px;
+ line-height: 24px;
+ margin: 0;
}
.text-display-3 {
font-family: Jost, sans-serif;
- font-weight: 600;
+ font-weight: 500;
font-size: var(--g-text-display-3-font-size);
line-height: var(--g-text-display-3-line-height);
+ margin: 0;
}
.text-header-1 {
font-family: Jost, sans-serif;
- font-weight: 600;
+ font-weight: 500;
font-size: var(--g-text-header-1-font-size);
line-height: var(--g-text-header-1-line-height);
+ margin: 0;
}
.text-subheader-1 {
font-family: Jost, sans-serif;
- font-weight: 600;
+ font-weight: 500;
font-size: var(--g-text-subheader-1-font-size);
line-height: var(--g-text-subheader-1-line-height);
}
.text-subheader-3 {
font-family: Jost, sans-serif;
- font-weight: 600;
+ font-weight: 500;
font-size: var(--g-text-subheader-3-font-size);
line-height: var(--g-text-subheader-3-line-height);
}
diff --git a/package-lock.json b/package-lock.json
index b3e7ce906..228932d31 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "html-reporter",
- "version": "10.6.2",
+ "version": "10.6.6",
"license": "MIT",
"workspaces": [
"test/func/fixtures/*",
@@ -63,7 +63,7 @@
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@gravity-ui/components": "^3.7.0",
- "@gravity-ui/uikit": "^6.23.0",
+ "@gravity-ui/uikit": "^6.23.1",
"@playwright/test": "^1.44.1",
"@react-hook/resize-observer": "^2.0.1",
"@swc/core": "^1.3.64",
@@ -89,6 +89,7 @@
"@types/react-dom": "^18.3.0",
"@types/react-virtualized": "^9.21.30",
"@types/react-window": "^1.8.8",
+ "@types/redux-mock-store": "^1.0.6",
"@types/sinon": "^4.3.3",
"@types/tmp": "^0.1.0",
"@types/urijs": "^1.19.19",
@@ -2896,9 +2897,9 @@
}
},
"node_modules/@gravity-ui/uikit": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.0.tgz",
- "integrity": "sha512-v+1QPL+IdaGxv3tuEUHaRSzK5Hp/XNJv3EQxMIMFSV4ojtvio6sQAlzLIYALGN/IULWRwlfF0cqG/TavOo9yog==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.1.tgz",
+ "integrity": "sha512-hR/ckbTlmW9xuFzcjvdCBHhsH0MP1LOdCwylZQnZjUIiismExymQBeWFDpI51OFTas6aZS8jWmkRlKRMzAsKmw==",
"dependencies": {
"@bem-react/classname": "^1.6.0",
"@gravity-ui/i18n": "^1.3.0",
@@ -4479,6 +4480,15 @@
"@types/react": "*"
}
},
+ "node_modules/@types/redux-mock-store": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.6.tgz",
+ "integrity": "sha512-eg5RDfhJTXuoJjOMyXiJbaDb1B8tfTaJixscmu+jOusj6adGC0Krntz09Tf4gJgXeCqCrM5bBMd+B7ez0izcAQ==",
+ "dev": true,
+ "dependencies": {
+ "redux": "^4.0.5"
+ }
+ },
"node_modules/@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -33750,9 +33760,9 @@
}
},
"@gravity-ui/uikit": {
- "version": "6.23.0",
- "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.0.tgz",
- "integrity": "sha512-v+1QPL+IdaGxv3tuEUHaRSzK5Hp/XNJv3EQxMIMFSV4ojtvio6sQAlzLIYALGN/IULWRwlfF0cqG/TavOo9yog==",
+ "version": "6.23.1",
+ "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.23.1.tgz",
+ "integrity": "sha512-hR/ckbTlmW9xuFzcjvdCBHhsH0MP1LOdCwylZQnZjUIiismExymQBeWFDpI51OFTas6aZS8jWmkRlKRMzAsKmw==",
"requires": {
"@bem-react/classname": "^1.6.0",
"@gravity-ui/i18n": "^1.3.0",
@@ -34995,6 +35005,15 @@
"@types/react": "*"
}
},
+ "@types/redux-mock-store": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.6.tgz",
+ "integrity": "sha512-eg5RDfhJTXuoJjOMyXiJbaDb1B8tfTaJixscmu+jOusj6adGC0Krntz09Tf4gJgXeCqCrM5bBMd+B7ez0izcAQ==",
+ "dev": true,
+ "requires": {
+ "redux": "^4.0.5"
+ }
+ },
"@types/responselike": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
diff --git a/package.json b/package.json
index b3334ea9d..bd716e590 100644
--- a/package.json
+++ b/package.json
@@ -128,7 +128,7 @@
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@gravity-ui/components": "^3.7.0",
- "@gravity-ui/uikit": "^6.23.0",
+ "@gravity-ui/uikit": "^6.23.1",
"@playwright/test": "^1.44.1",
"@react-hook/resize-observer": "^2.0.1",
"@swc/core": "^1.3.64",
@@ -154,6 +154,7 @@
"@types/react-dom": "^18.3.0",
"@types/react-virtualized": "^9.21.30",
"@types/react-window": "^1.8.8",
+ "@types/redux-mock-store": "^1.0.6",
"@types/sinon": "^4.3.3",
"@types/tmp": "^0.1.0",
"@types/urijs": "^1.19.19",
diff --git a/test/func/tests/common/test-results-appearance.testplane.js b/test/func/tests/common/test-results-appearance.testplane.js
index a34956cc6..a9a17bace 100644
--- a/test/func/tests/common/test-results-appearance.testplane.js
+++ b/test/func/tests/common/test-results-appearance.testplane.js
@@ -133,14 +133,16 @@ describe('Test results appearance', () => {
}
});
- it('should show message without ansi markup', async ({browser}) => {
- const expectedErrorText = 'expect(received).toMatchObject(expected)';
- const testElem = await browser.$(getTestSectionByNameSelector('failed test with ansi markup'));
+ if (process.env.TOOL === 'testplane') {
+ it('should show message without ansi markup', async ({browser}) => {
+ const expectedErrorText = 'expect(received).toMatchObject(expected)';
+ const testElem = await browser.$(getTestSectionByNameSelector('failed test with ansi markup'));
- const errorText = await testElem.$('.tab .error__item.details__summary').getText();
+ const errorText = await testElem.$('.tab .error__item.details__summary').getText();
- assert.equal(errorText, `message: ${expectedErrorText}`);
- });
+ assert.equal(errorText, `message: ${expectedErrorText}`);
+ });
+ }
});
describe('Test with successful assertView and error', () => {
diff --git a/test/func/tests/common/tests-details.testplane.js b/test/func/tests/common/tests-details.testplane.js
index 6358ce29c..4d27393ec 100644
--- a/test/func/tests/common/tests-details.testplane.js
+++ b/test/func/tests/common/tests-details.testplane.js
@@ -9,14 +9,13 @@ describe('Test details', function() {
const selector = getTestSectionByNameSelector('test with long error message');
await browser.$(selector).waitForDisplayed();
- const erroredTestSection = await browser.$(selector).$('../..');
+ await browser.$(selector).$('.details__summary').scrollIntoView();
+ await browser.$(selector).$('.details__summary').click();
- await erroredTestSection.$('.details__summary').click();
+ const metaInfo = await browser.$(selector).$('dl[data-qa="meta-info"]');
- const fileMetaInfo = await erroredTestSection.$('div.meta-info__item*=failed-describe').$('..');
-
- await expect(fileMetaInfo).toBeDisplayed();
- await expect(await fileMetaInfo.$('span*=file')).toBeDisplayed();
+ await expect(metaInfo.$('dt*=file')).toBeDisplayed();
+ await expect(metaInfo.$('dd*=failed-describe')).toBeDisplayed();
});
it('should prevent details summary overflow', async ({browser}) => {
diff --git a/test/func/tests/package.json b/test/func/tests/package.json
index 8b21881bd..a8d6af648 100644
--- a/test/func/tests/package.json
+++ b/test/func/tests/package.json
@@ -3,18 +3,18 @@
"version": "0.0.0",
"private": true,
"scripts": {
- "gui:testplane-common": "PROJECT_UNDER_TEST=testplane npx testplane --set common gui",
- "gui:testplane-eye": "PROJECT_UNDER_TEST=testplane-eye npx testplane --no --set eye gui",
- "gui:testplane-gui": "PROJECT_UNDER_TEST=testplane-gui npx testplane --no --set common-gui gui",
- "gui:playwright": "PROJECT_UNDER_TEST=playwright npx testplane --set common gui",
- "gui:plugins": "PROJECT_UNDER_TEST=plugins SERVER_PORT=8084 npx testplane --set plugins gui",
- "gui:testplane-tinder": "PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8084 npx testplane --set common-tinder gui",
- "testplane:testplane-common": "PROJECT_UNDER_TEST=testplane SERVER_PORT=8061 npx testplane --set common",
- "testplane:testplane-eye": "PROJECT_UNDER_TEST=testplane-eye SERVER_PORT=8062 npx testplane --set eye",
- "testplane:testplane-gui": "PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8063 npx testplane --no --set common-gui",
- "testplane:playwright": "PROJECT_UNDER_TEST=playwright SERVER_PORT=8065 npx testplane --set common",
- "testplane:plugins": "PROJECT_UNDER_TEST=plugins SERVER_PORT=8064 npx testplane --set plugins",
- "testplane:testplane-tinder": "PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8084 npx testplane --set common-tinder",
+ "gui:testplane-common": "TOOL=testplane PROJECT_UNDER_TEST=testplane npx testplane --set common gui",
+ "gui:testplane-eye": "TOOL=testplane PROJECT_UNDER_TEST=testplane-eye npx testplane --no --set eye gui",
+ "gui:testplane-gui": "TOOL=testplane PROJECT_UNDER_TEST=testplane-gui npx testplane --no --set common-gui gui",
+ "gui:playwright": "TOOL=playwright PROJECT_UNDER_TEST=playwright npx testplane --set common gui",
+ "gui:plugins": "TOOL=testplane PROJECT_UNDER_TEST=plugins SERVER_PORT=8084 npx testplane --set plugins gui",
+ "gui:testplane-tinder": "TOOL=testplane PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8084 npx testplane --set common-tinder gui",
+ "testplane:testplane-common": "TOOL=testplane PROJECT_UNDER_TEST=testplane SERVER_PORT=8061 npx testplane --set common",
+ "testplane:testplane-eye": "TOOL=testplane PROJECT_UNDER_TEST=testplane-eye SERVER_PORT=8062 npx testplane --set eye",
+ "testplane:testplane-gui": "TOOL=testplane PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8063 npx testplane --no --set common-gui",
+ "testplane:playwright": "TOOL=playwright PROJECT_UNDER_TEST=playwright SERVER_PORT=8065 npx testplane --set common",
+ "testplane:plugins": "TOOL=testplane PROJECT_UNDER_TEST=plugins SERVER_PORT=8064 npx testplane --set plugins",
+ "testplane:testplane-tinder": "TOOL=testplane PROJECT_UNDER_TEST=testplane-gui SERVER_PORT=8084 npx testplane --set common-tinder",
"test": "run-s testplane:*"
},
"devDependencies": {
diff --git a/test/func/tests/plugins/tests-redux-plugin.testplane.js b/test/func/tests/plugins/tests-redux-plugin.testplane.js
index d84121490..13e691b38 100644
--- a/test/func/tests/plugins/tests-redux-plugin.testplane.js
+++ b/test/func/tests/plugins/tests-redux-plugin.testplane.js
@@ -14,6 +14,8 @@ describe('Test redux plugin', function() {
await browser.$(screenSelector).waitForDisplayed();
await browser.$(clickSelector).click();
+ // Letting browser handle re-renders
+ await browser.pause(300);
await browser.assertView('redux plugin clicked', screenSelector);
});
});
diff --git a/test/func/tests/screens/3144090/chrome/menu bar plugins clicked.png b/test/func/tests/screens/3144090/chrome/menu bar plugins clicked.png
index c8f5b6150..032ec5eaf 100644
Binary files a/test/func/tests/screens/3144090/chrome/menu bar plugins clicked.png and b/test/func/tests/screens/3144090/chrome/menu bar plugins clicked.png differ
diff --git a/test/setup/css-modules-mock.js b/test/setup/css-modules-mock.js
new file mode 100644
index 000000000..c8def313d
--- /dev/null
+++ b/test/setup/css-modules-mock.js
@@ -0,0 +1,11 @@
+// Mock CSS modules by returning class name, e.g. styles['some-class'] resolves to some-class.
+// __esModule: false is needed due to the way SWC resolves modules at runtime. Becomes apparent at SWC playground.
+export const cssModulesMock = new Proxy({}, {
+ get: (target, prop) => {
+ if (prop === '__esModule') {
+ return false;
+ }
+
+ return typeof prop === 'string' ? prop : '';
+ }
+});
diff --git a/test/setup/globals.js b/test/setup/globals.js
index 310b17fbf..6927ed86f 100644
--- a/test/setup/globals.js
+++ b/test/setup/globals.js
@@ -14,6 +14,9 @@ global.assert = chai.assert;
require.extensions['.styl'] = () => {};
require.extensions['.css'] = () => {};
require.extensions['.less'] = () => {};
+require.extensions['.module.css'] = function(module) {
+ module.exports = require('./css-modules-mock').cssModulesMock;
+};
chai.use(require('chai-as-promised'));
chai.use(require('chai-dom'));
diff --git a/test/unit/lib/common-utils.ts b/test/unit/lib/common-utils.ts
index 82e247147..205a33e5d 100644
--- a/test/unit/lib/common-utils.ts
+++ b/test/unit/lib/common-utils.ts
@@ -1,4 +1,12 @@
-import {determineFinalStatus, getError, hasDiff, getUrlWithBase, getDetailsFileName, trimArray, mergeSnippetIntoErrorStack} from 'lib/common-utils';
+import {
+ determineFinalStatus,
+ getError,
+ hasDiff,
+ getUrlWithBase,
+ getDetailsFileName,
+ trimArray,
+ mergeSnippetIntoErrorStack
+} from 'lib/common-utils';
import {RUNNING, QUEUED, ERROR, FAIL, UPDATED, SUCCESS, IDLE, SKIPPED} from 'lib/constants/test-statuses';
import {ErrorName} from 'lib/errors';
import sinon from 'sinon';
diff --git a/test/unit/lib/static/components/bottom-progress-bar/index.jsx b/test/unit/lib/static/components/bottom-progress-bar/index.jsx
index 581a7d4c9..8fb6c1c97 100644
--- a/test/unit/lib/static/components/bottom-progress-bar/index.jsx
+++ b/test/unit/lib/static/components/bottom-progress-bar/index.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
describe(' component', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/controls/accept-opened-button.jsx b/test/unit/lib/static/components/controls/accept-opened-button.jsx
index 7bca54964..bc4adb98e 100644
--- a/test/unit/lib/static/components/controls/accept-opened-button.jsx
+++ b/test/unit/lib/static/components/controls/accept-opened-button.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkState, mkConnectedComponent} from '../utils';
+import {mkState, mkConnectedComponent} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/controls/custom-gui-controls.jsx b/test/unit/lib/static/components/controls/custom-gui-controls.jsx
index 22571412f..3845044fe 100644
--- a/test/unit/lib/static/components/controls/custom-gui-controls.jsx
+++ b/test/unit/lib/static/components/controls/custom-gui-controls.jsx
@@ -1,7 +1,7 @@
import {expect} from 'chai';
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/controls/find-same-diffs-button.jsx b/test/unit/lib/static/components/controls/find-same-diffs-button.jsx
index e284c2e6f..ad906a782 100644
--- a/test/unit/lib/static/components/controls/find-same-diffs-button.jsx
+++ b/test/unit/lib/static/components/controls/find-same-diffs-button.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import proxyquire from 'proxyquire';
import {defaults} from 'lodash';
-import {mkState, mkConnectedComponent} from '../utils';
+import {mkState, mkConnectedComponent} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/controls/gui-controls.jsx b/test/unit/lib/static/components/controls/gui-controls.jsx
index 7571971f5..af389f0de 100644
--- a/test/unit/lib/static/components/controls/gui-controls.jsx
+++ b/test/unit/lib/static/components/controls/gui-controls.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/controls/menu-bar.jsx b/test/unit/lib/static/components/controls/menu-bar.jsx
index f6dfd61ce..ad98ca7ed 100644
--- a/test/unit/lib/static/components/controls/menu-bar.jsx
+++ b/test/unit/lib/static/components/controls/menu-bar.jsx
@@ -1,6 +1,6 @@
import {expect} from 'chai';
import React from 'react';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import MenuBar from 'lib/static/components/controls/common-controls';
import userEvent from '@testing-library/user-event';
diff --git a/test/unit/lib/static/components/controls/run-button.jsx b/test/unit/lib/static/components/controls/run-button.jsx
index ed70ee66e..a22747a7e 100644
--- a/test/unit/lib/static/components/controls/run-button.jsx
+++ b/test/unit/lib/static/components/controls/run-button.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent, mkState} from '../utils';
+import {mkConnectedComponent, mkState} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/controls/strict-match-filter-input.jsx b/test/unit/lib/static/components/controls/strict-match-filter-input.jsx
index b35ec1d1d..1b39287a7 100644
--- a/test/unit/lib/static/components/controls/strict-match-filter-input.jsx
+++ b/test/unit/lib/static/components/controls/strict-match-filter-input.jsx
@@ -1,5 +1,5 @@
import React from 'react';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import proxyquire from 'proxyquire';
import userEvent from '@testing-library/user-event';
diff --git a/test/unit/lib/static/components/group-tests/item.jsx b/test/unit/lib/static/components/group-tests/item.jsx
index 257c2e44a..699808c48 100644
--- a/test/unit/lib/static/components/group-tests/item.jsx
+++ b/test/unit/lib/static/components/group-tests/item.jsx
@@ -3,7 +3,7 @@ import React from 'react';
import proxyquire from 'proxyquire';
import {defaultsDeep, set} from 'lodash';
import {CHECKED, UNCHECKED} from 'lib/constants/checked-statuses';
-import {mkConnectedComponent} from 'test/unit/lib/static/components/utils';
+import {mkConnectedComponent} from '../../utils';
import {mkStateTree} from 'test/unit/lib/static/state-utils';
import userEvent from '@testing-library/user-event';
diff --git a/test/unit/lib/static/components/modals/screenshot-accepter/body.jsx b/test/unit/lib/static/components/modals/screenshot-accepter/body.jsx
index 1904800cb..5e005d321 100644
--- a/test/unit/lib/static/components/modals/screenshot-accepter/body.jsx
+++ b/test/unit/lib/static/components/modals/screenshot-accepter/body.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import {defaults} from 'lodash';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/modals/screenshot-accepter/header.jsx b/test/unit/lib/static/components/modals/screenshot-accepter/header.jsx
index b0ec107a7..ddbc9cbda 100644
--- a/test/unit/lib/static/components/modals/screenshot-accepter/header.jsx
+++ b/test/unit/lib/static/components/modals/screenshot-accepter/header.jsx
@@ -11,7 +11,7 @@ import {
mkEmptyTree,
mkRealStore,
renderWithStore
-} from '../../utils';
+} from '../../../utils';
describe('', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/modals/screenshot-accepter/index.jsx b/test/unit/lib/static/components/modals/screenshot-accepter/index.jsx
index 1ffaf6736..fc5d68dc1 100644
--- a/test/unit/lib/static/components/modals/screenshot-accepter/index.jsx
+++ b/test/unit/lib/static/components/modals/screenshot-accepter/index.jsx
@@ -11,7 +11,7 @@ import {
addResultToTree,
addSuiteToTree, generateImageId,
mkEmptyTree, mkRealStore, renderWithStore
-} from '../../utils';
+} from '../../../utils';
const handlers = [
http.post('/reference-data-to-update', () => {
diff --git a/test/unit/lib/static/components/modals/screenshot-accepter/meta.jsx b/test/unit/lib/static/components/modals/screenshot-accepter/meta.jsx
index cb6bbe121..0ff33bfcb 100644
--- a/test/unit/lib/static/components/modals/screenshot-accepter/meta.jsx
+++ b/test/unit/lib/static/components/modals/screenshot-accepter/meta.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/retry-switcher/index.jsx b/test/unit/lib/static/components/retry-switcher/index.jsx
index 93fd5d18d..e103c8611 100644
--- a/test/unit/lib/static/components/retry-switcher/index.jsx
+++ b/test/unit/lib/static/components/retry-switcher/index.jsx
@@ -21,7 +21,7 @@ describe('', () => {
RetrySwitcherItem = sinon.stub().returns(null);
RetrySwitcher = proxyquire('lib/static/components/retry-switcher', {
- './item': {default: RetrySwitcherItem}
+ '@/static/new-ui/components/AttemptPickerItem': {AttemptPickerItem: RetrySwitcherItem}
}).default;
});
diff --git a/test/unit/lib/static/components/section/body/description.jsx b/test/unit/lib/static/components/section/body/description.jsx
index cc6f5814f..ada511914 100644
--- a/test/unit/lib/static/components/section/body/description.jsx
+++ b/test/unit/lib/static/components/section/body/description.jsx
@@ -2,7 +2,7 @@ import userEvent from '@testing-library/user-event';
import {expect} from 'chai';
import React from 'react';
import Description from 'lib/static/components/section/body/description';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('', () => {
const mkDescriptionComponent = (content) => {
diff --git a/test/unit/lib/static/components/section/body/history.jsx b/test/unit/lib/static/components/section/body/history.jsx
index bc49f33f5..bf5443527 100644
--- a/test/unit/lib/static/components/section/body/history.jsx
+++ b/test/unit/lib/static/components/section/body/history.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import {defaultsDeep, set} from 'lodash';
import History from 'lib/static/components/section/body/history';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/section/body/index.jsx b/test/unit/lib/static/components/section/body/index.jsx
index 400b7b74d..61afb2f17 100644
--- a/test/unit/lib/static/components/section/body/index.jsx
+++ b/test/unit/lib/static/components/section/body/index.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import proxyquire from 'proxyquire';
import {defaults} from 'lodash';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
import {mkStateTree} from '../../../state-utils';
import userEvent from '@testing-library/user-event';
diff --git a/test/unit/lib/static/components/section/body/meta-info/index.jsx b/test/unit/lib/static/components/section/body/meta-info/index.jsx
index fee48d867..71c3250a0 100644
--- a/test/unit/lib/static/components/section/body/meta-info/index.jsx
+++ b/test/unit/lib/static/components/section/body/meta-info/index.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import {defaultsDeep} from 'lodash';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from '../../../utils';
+import {mkConnectedComponent} from '../../../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
@@ -16,7 +16,7 @@ describe('', () => {
MetaInfoContent = sinon.stub().returns(null);
MetaInfo = proxyquire('lib/static/components/section/body/meta-info', {
- './content': {default: MetaInfoContent},
+ '@/static/new-ui/components/MetaInfo': {MetaInfo: MetaInfoContent},
'../../../../modules/actions': actionsStub
}).default;
});
@@ -40,7 +40,7 @@ describe('', () => {
assert.calledOnceWith(
MetaInfoContent,
- {resultId: 'some-result'}
+ {qa: 'meta-info', resultId: 'some-result'}
);
});
});
diff --git a/test/unit/lib/static/components/section/body/result.jsx b/test/unit/lib/static/components/section/body/result.jsx
index 8269fe92d..f40bcbb1b 100644
--- a/test/unit/lib/static/components/section/body/result.jsx
+++ b/test/unit/lib/static/components/section/body/result.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import proxyquire from 'proxyquire';
import {defaults, set} from 'lodash';
import {FAIL, SUCCESS} from 'lib/constants/test-statuses';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/section/body/tabs.jsx b/test/unit/lib/static/components/section/body/tabs.jsx
index 729d25f1f..2bf52d9a7 100644
--- a/test/unit/lib/static/components/section/body/tabs.jsx
+++ b/test/unit/lib/static/components/section/body/tabs.jsx
@@ -3,7 +3,7 @@ import React from 'react';
import proxyquire from 'proxyquire';
import {defaultsDeep} from 'lodash';
import {ERROR, FAIL, SUCCESS} from 'lib/constants/test-statuses';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/section/section-browser.jsx b/test/unit/lib/static/components/section/section-browser.jsx
index 5de7d95e8..0d3a16959 100644
--- a/test/unit/lib/static/components/section/section-browser.jsx
+++ b/test/unit/lib/static/components/section/section-browser.jsx
@@ -3,7 +3,7 @@ import {defaults, set} from 'lodash';
import proxyquire from 'proxyquire';
import {SUCCESS, SKIPPED, ERROR} from 'lib/constants/test-statuses';
import {UNCHECKED} from 'lib/constants/checked-statuses';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import {mkBrowser, mkResult, mkStateTree} from '../../state-utils';
describe('', () => {
diff --git a/test/unit/lib/static/components/section/section-common.jsx b/test/unit/lib/static/components/section/section-common.jsx
index 7debbba19..6028254af 100644
--- a/test/unit/lib/static/components/section/section-common.jsx
+++ b/test/unit/lib/static/components/section/section-common.jsx
@@ -2,7 +2,7 @@ import React from 'react';
import {defaults} from 'lodash';
import proxyquire from 'proxyquire';
import {FAIL, SUCCESS} from 'lib/constants/test-statuses';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import {mkSuite, mkStateTree} from '../../state-utils';
describe('', () => {
diff --git a/test/unit/lib/static/components/section/title/browser-skipped.jsx b/test/unit/lib/static/components/section/title/browser-skipped.jsx
index 53354940a..895ed07f4 100644
--- a/test/unit/lib/static/components/section/title/browser-skipped.jsx
+++ b/test/unit/lib/static/components/section/title/browser-skipped.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import {defaults} from 'lodash';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from 'test/unit/lib/static/components/utils';
+import {mkConnectedComponent} from '../../../utils';
import {mkBrowser, mkResult, mkStateTree} from 'test/unit/lib/static/state-utils';
import {SKIPPED} from 'lib/constants/test-statuses';
import {CHECKED, UNCHECKED} from 'lib/constants/checked-statuses';
diff --git a/test/unit/lib/static/components/section/title/browser.jsx b/test/unit/lib/static/components/section/title/browser.jsx
index 135b74c40..1a06ceea6 100644
--- a/test/unit/lib/static/components/section/title/browser.jsx
+++ b/test/unit/lib/static/components/section/title/browser.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import {defaults} from 'lodash';
import proxyquire from 'proxyquire';
-import {mkConnectedComponent} from 'test/unit/lib/static/components/utils';
+import {mkConnectedComponent} from '../../../utils';
import {mkBrowser, mkResult, mkStateTree} from 'test/unit/lib/static/state-utils';
import {SUCCESS} from 'lib/constants/test-statuses';
import {ViewMode} from 'lib/constants/view-modes';
diff --git a/test/unit/lib/static/components/section/title/simple.jsx b/test/unit/lib/static/components/section/title/simple.jsx
index 1d4b3de55..f3e90b29d 100644
--- a/test/unit/lib/static/components/section/title/simple.jsx
+++ b/test/unit/lib/static/components/section/title/simple.jsx
@@ -2,7 +2,7 @@ import {expect} from 'chai';
import React from 'react';
import proxyquire from 'proxyquire';
import {defaultsDeep, set} from 'lodash';
-import {mkConnectedComponent} from 'test/unit/lib/static/components/utils';
+import {mkConnectedComponent} from '../../../utils';
import {mkStateTree} from 'test/unit/lib/static/state-utils';
import {CHECKED, UNCHECKED, INDETERMINATE} from 'lib/constants/checked-statuses';
import {TestStatus} from 'lib/constants';
diff --git a/test/unit/lib/static/components/state/index.jsx b/test/unit/lib/static/components/state/index.jsx
index 292824527..c055391b4 100644
--- a/test/unit/lib/static/components/state/index.jsx
+++ b/test/unit/lib/static/components/state/index.jsx
@@ -5,7 +5,7 @@ import {set, defaults, defaultsDeep} from 'lodash';
import {SUCCESS, FAIL, ERROR, UPDATED} from 'lib/constants/test-statuses';
import {EXPAND_ALL} from 'lib/constants/expand-modes';
import {types as modalTypes} from 'lib/static/components/modals';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
import userEvent from '@testing-library/user-event';
describe('', () => {
diff --git a/test/unit/lib/static/components/state/screenshot/diff-circle.jsx b/test/unit/lib/static/components/state/screenshot/diff-circle.jsx
index 9810419b3..f31d8a413 100644
--- a/test/unit/lib/static/components/state/screenshot/diff-circle.jsx
+++ b/test/unit/lib/static/components/state/screenshot/diff-circle.jsx
@@ -1,7 +1,7 @@
import {fireEvent} from '@testing-library/react';
import React from 'react';
import DiffCircle from 'lib/static/components/state/screenshot/diff-circle';
-import {mkConnectedComponent} from '../../utils';
+import {mkConnectedComponent} from '../../../utils';
describe('DiffCircle component', () => {
const sandbox = sinon.createSandbox();
diff --git a/test/unit/lib/static/components/state/state-error.jsx b/test/unit/lib/static/components/state/state-error.jsx
index 784e32408..ccc19952b 100644
--- a/test/unit/lib/static/components/state/state-error.jsx
+++ b/test/unit/lib/static/components/state/state-error.jsx
@@ -4,7 +4,7 @@ import React from 'react';
import {defaults} from 'lodash';
import proxyquire from 'proxyquire';
import {ERROR_TITLE_TEXT_LENGTH} from 'lib/constants/errors';
-import {mkConnectedComponent} from '../utils';
+import {mkConnectedComponent} from '../../utils';
describe(' component', () => {
const sandbox = sinon.sandbox.create();
diff --git a/test/unit/lib/static/components/suites.jsx b/test/unit/lib/static/components/suites.jsx
index 6567997dc..5d9819f35 100644
--- a/test/unit/lib/static/components/suites.jsx
+++ b/test/unit/lib/static/components/suites.jsx
@@ -3,7 +3,7 @@ import React from 'react';
import proxyquire from 'proxyquire';
import {defaultsDeep} from 'lodash';
-import {mkConnectedComponent} from './utils';
+import {mkConnectedComponent} from '../utils';
import {ViewMode} from 'lib/constants/view-modes';
describe('', () => {
diff --git a/test/unit/lib/static/components/utils.jsx b/test/unit/lib/static/components/utils.jsx
deleted file mode 100644
index dd182c7a3..000000000
--- a/test/unit/lib/static/components/utils.jsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import {ThemeProvider} from '@gravity-ui/uikit';
-import {render} from '@testing-library/react';
-import React from 'react';
-import _ from 'lodash';
-import configureStore from 'redux-mock-store';
-import {Provider} from 'react-redux';
-import defaultState from 'lib/static/modules/default-state';
-import {TestStatus} from 'lib/constants';
-import {applyMiddleware, createStore} from 'redux';
-import thunk from 'redux-thunk';
-import reducer from 'lib/static/modules/reducers';
-import localStorage from 'lib/static/modules/middlewares/local-storage';
-
-exports.mkState = ({initialState} = {}) => {
- return _.defaultsDeep(initialState, defaultState);
-};
-
-exports.mkStore = ({initialState, state} = {}) => {
- const readyState = state ? state : exports.mkState({initialState});
- const mockStore = configureStore();
-
- return mockStore(readyState);
-};
-
-exports.mkRealStore = ({initialState, middlewares = []}) => {
- return createStore(reducer, exports.mkState({initialState}), applyMiddleware(thunk, localStorage, ...middlewares));
-};
-
-exports.generateBrowserId = ({suiteName, browserName}) =>
- `${suiteName} ${browserName}`;
-
-exports.generateResultId = ({suiteName, browserName, attempt}) =>
- `${exports.generateBrowserId({suiteName, browserName})} ${attempt}`;
-
-exports.generateImageId = ({suiteName, browserName, attempt, stateName}) =>
- `${exports.generateResultId({suiteName, browserName, attempt})} ${stateName}`;
-
-exports.mkEmptyTree = () => _.cloneDeep(defaultState.tree);
-
-exports.addSuiteToTree = ({tree, suiteName}) => {
- tree.suites.byId[suiteName] = {id: suiteName};
-};
-
-exports.addBrowserToTree = ({tree, suiteName, browserName}) => {
- const fullId = `${suiteName} ${browserName}`;
-
- tree.browsers.byId[fullId] = {
- id: browserName,
- name: browserName,
- parentId: suiteName,
- resultIds: [],
- imageIds: []
- };
- tree.browsers.stateById[fullId] = {shouldBeShown: true};
-};
-
-exports.addResultToTree = ({tree, suiteName, browserName, attempt, metaInfo = {}}) => {
- const browserId = `${suiteName} ${browserName}`;
- const fullId = `${browserId} ${attempt}`;
-
- tree.results.byId[fullId] = {
- id: fullId,
- parentId: browserId,
- attempt,
- imageIds: [],
- metaInfo
- };
- tree.results.stateById[fullId] = {};
-
- tree.browsers.byId[browserId].resultIds.push(fullId);
-};
-
-exports.addImageToTree = ({tree, suiteName, browserName, attempt, stateName, status = TestStatus.FAIL, expectedImgPath, actualImgPath, diffImgPath}) => {
- const browserId = `${suiteName} ${browserName}`;
- const resultId = `${browserId} ${attempt}`;
- const fullId = `${resultId} ${stateName}`;
-
- tree.images.byId[fullId] = {
- id: fullId,
- parentId: resultId,
- stateName,
- status
- };
-
- if (expectedImgPath) {
- tree.images.byId[fullId].expectedImg = {
- path: expectedImgPath,
- size: {height: 1, width: 2}
- };
- }
- if (actualImgPath) {
- tree.images.byId[fullId].actualImg = {
- path: actualImgPath,
- size: {height: 1, width: 2}
- };
- }
- if (diffImgPath) {
- tree.images.byId[fullId].diffImg = {
- path: diffImgPath,
- size: {height: 1, width: 2}
- };
- }
-
- tree.browsers.byId[browserId].imageIds.push(fullId);
- tree.results.byId[resultId].imageIds.push(fullId);
-};
-
-exports.renderWithStore = (component, store) => {
- return render({component});
-};
-
-exports.mkConnectedComponent = (Component, state) => {
- const store = exports.mkStore(state);
- return render({Component});
-};
-
-exports.mkImg_ = (opts = {}) => {
- return _.defaultsDeep(opts, {
- path: 'default/path',
- size: {width: 100500, height: 500100}
- });
-};
-
-exports.mkTestResult_ = (result) => {
- return _.defaults(result, {
- suiteUrl: '',
- metaInfo: {},
- imagesInfo: [],
- expectedImg: exports.mkImg_()
- });
-};
-
-exports.mkSuite_ = (suite) => {
- return _.defaults(suite, {
- name: 'default-suite',
- suitePath: ['default-suite']
- });
-};
diff --git a/test/unit/lib/static/modules/reducers/grouped-tests/helpers.js b/test/unit/lib/static/modules/reducers/grouped-tests/helpers.js
index 6539415d7..e34da04e5 100644
--- a/test/unit/lib/static/modules/reducers/grouped-tests/helpers.js
+++ b/test/unit/lib/static/modules/reducers/grouped-tests/helpers.js
@@ -78,8 +78,8 @@ describe('lib/static/modules/reducers/grouped-tests/helpers', () => {
const resultCb = sinon.spy().named('onResultCb');
isTestNameMatchFilters
- .withArgs('test-1', 'test-1', strictMatchFilter).returns(true)
- .withArgs('test-2', 'test-1', strictMatchFilter).returns(false);
+ .withArgs('test-1', 'default-bro', 'test-1', strictMatchFilter).returns(true)
+ .withArgs('test-2', 'default-bro', 'test-1', strictMatchFilter).returns(false);
module.handleActiveResults({tree, resultCb, testNameFilter: 'test-1', strictMatchFilter});
diff --git a/test/unit/lib/static/modules/utils.js b/test/unit/lib/static/modules/utils.js
index 7657a8511..1fda988f6 100644
--- a/test/unit/lib/static/modules/utils.js
+++ b/test/unit/lib/static/modules/utils.js
@@ -119,21 +119,21 @@ describe('static/modules/utils', () => {
});
it('test name is contains name from filter', () => {
- assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'test'));
+ assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'some-browser', 'test'));
});
it('test name matches on filter strictly', () => {
- assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'some-test-name', true));
+ assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'some-browser', 'some-test-name', true));
});
});
it('should return "false" if', () => {
it('test name does not contain name from filter', () => {
- assert.isFalse(utils.isTestNameMatchFilters('some-test-name', 'another'));
+ assert.isFalse(utils.isTestNameMatchFilters('some-test-name', 'some-browser', 'another'));
});
it('test name does not match on filter strictly', () => {
- assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'some-test-nam', true));
+ assert.isTrue(utils.isTestNameMatchFilters('some-test-name', 'some-browser', 'some-test-nam', true));
});
});
});
diff --git a/test/unit/lib/static/components/retry-switcher/item.jsx b/test/unit/lib/static/new-ui/components/AttemptPickerItem.tsx
similarity index 76%
rename from test/unit/lib/static/components/retry-switcher/item.jsx
rename to test/unit/lib/static/new-ui/components/AttemptPickerItem.tsx
index f70b53011..340da0a63 100644
--- a/test/unit/lib/static/components/retry-switcher/item.jsx
+++ b/test/unit/lib/static/new-ui/components/AttemptPickerItem.tsx
@@ -1,16 +1,20 @@
+import {RenderResult} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import {expect} from 'chai';
-import React from 'react';
import {defaults} from 'lodash';
-import RetrySwitcherItem from 'lib/static/components/retry-switcher/item';
-import {FAIL, ERROR, SUCCESS} from 'lib/constants/test-statuses';
-import {mkConnectedComponent} from '../utils';
+import React from 'react';
+import sinon from 'sinon';
+
+import styles from 'lib/static/new-ui/components/AttemptPickerItem/index.module.css';
+import {FAIL, ERROR, SUCCESS, FAIL_ERROR} from 'lib/constants/test-statuses';
import {ErrorName} from 'lib/errors';
-import userEvent from '@testing-library/user-event';
+import {AttemptPickerItem, AttemptPickerItemProps} from 'lib/static/new-ui/components/AttemptPickerItem';
+import {mkConnectedComponent} from '../../utils';
-describe('', () => {
+describe('', () => {
const sandbox = sinon.sandbox.create();
- const mkRetrySwitcherItem = (props = {}, initialState = {}) => {
+ const mkAttemptPickerItem = (props: AttemptPickerItemProps, initialState = {}): RenderResult => {
props = defaults(props, {
resultId: 'default-id',
isActive: true,
@@ -26,7 +30,7 @@ describe('', () => {
}
});
- return mkConnectedComponent(, {initialState});
+ return mkConnectedComponent(, {initialState});
};
afterEach(() => sandbox.restore());
@@ -58,9 +62,9 @@ describe('', () => {
}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1', isActive: true}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1', isActive: true}, initialState);
- expect(component.container.querySelector(`button[data-qa="retry-switcher"].tab-switcher__button_status_${FAIL}`)).to.exist;
+ expect(component.container.querySelector(`button[data-qa="retry-switcher"].${styles[`attempt-picker-item--${FAIL}`]}`)).to.exist;
});
});
@@ -76,9 +80,9 @@ describe('', () => {
}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1', isActive: true}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1', isActive: true}, initialState);
- expect(component.container.querySelector(`button[data-qa="retry-switcher"].tab-switcher__button_status_${FAIL}_${ERROR}`)).to.exist;
+ expect(component.container.querySelector(`button[data-qa="retry-switcher"].${styles[`attempt-picker-item--${FAIL_ERROR}`]}`)).to.exist;
});
it('without non matched class if group is not selected', () => {
@@ -92,7 +96,7 @@ describe('', () => {
view: {keyToGroupTestsBy: ''}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1'}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1'}, initialState);
expect(component.container.querySelector('button[data-qa="retry-switcher"]')).to.exist;
expect(component.container.querySelector('button[data-qa="retry-switcher"].tab-switcher__button_non-matched')).to.not.exist;
@@ -109,7 +113,7 @@ describe('', () => {
view: {keyToGroupTestsBy: 'some-key'}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1'}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1'}, initialState);
expect(component.container.querySelector('button[data-qa="retry-switcher"]')).to.exist;
expect(component.container.querySelector('button[data-qa="retry-switcher"].tab-switcher__button_non-matched')).to.not.exist;
@@ -126,10 +130,10 @@ describe('', () => {
view: {keyToGroupTestsBy: 'some-key'}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1'}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1'}, initialState);
expect(component.container.querySelector('button[data-qa="retry-switcher"]')).to.exist;
- expect(component.container.querySelector('button[data-qa="retry-switcher"].tab-switcher__button_non-matched')).to.exist;
+ expect(component.container.querySelector(`button[data-qa="retry-switcher"].${styles['attempt-picker-item--non-matched']}`)).to.exist;
});
});
@@ -145,22 +149,22 @@ describe('', () => {
}
};
- const component = mkRetrySwitcherItem({resultId: 'result-1'}, initialState);
+ const component = mkAttemptPickerItem({resultId: 'result-1'}, initialState);
expect(component.getByText('100500', {selector: 'button[data-qa="retry-switcher"] > *'})).to.exist;
});
it('should render button with correct active class name', () => {
- const component = mkRetrySwitcherItem({isActive: true});
+ const component = mkAttemptPickerItem({resultId: 'default-id', isActive: true});
- expect(component.container.querySelector('button[data-qa="retry-switcher"].tab-switcher__button_active')).to.exist;
+ expect(component.container.querySelector(`button[data-qa="retry-switcher"].${styles['attempt-picker-item--active']}`)).to.exist;
});
it('should call "onClick" handler on click in button', async () => {
const user = userEvent.setup();
const onClick = sinon.stub();
- const component = mkRetrySwitcherItem({onClick});
+ const component = mkAttemptPickerItem({resultId: 'default-id', onClick});
await user.click(component.getByTestId('retry-switcher'));
assert.calledOnceWith(onClick);
diff --git a/test/unit/lib/static/components/section/body/meta-info/content.jsx b/test/unit/lib/static/new-ui/components/MetaInfo.tsx
similarity index 88%
rename from test/unit/lib/static/components/section/body/meta-info/content.jsx
rename to test/unit/lib/static/new-ui/components/MetaInfo.tsx
index 0be3aef20..d2e0018fd 100644
--- a/test/unit/lib/static/components/section/body/meta-info/content.jsx
+++ b/test/unit/lib/static/new-ui/components/MetaInfo.tsx
@@ -1,11 +1,13 @@
+import {RenderResult} from '@testing-library/react';
import {expect} from 'chai';
-import React from 'react';
import {defaultsDeep} from 'lodash';
-import MetaInfoContent from 'lib/static/components/section/body/meta-info/content';
-import {mkConnectedComponent} from '../../../utils';
+import React from 'react';
+
+import {MetaInfo, MetaInfoProps} from 'lib/static/new-ui/components/MetaInfo';
+import {mkConnectedComponent} from '../../utils';
-describe('', () => {
- const mkMetaInfoContentComponent = (props = {}, initialState = {}) => {
+describe('', () => {
+ const mkMetaInfoComponent = (props: MetaInfoProps, initialState = {}): RenderResult => {
props = defaultsDeep(props, {
resultId: 'default-result-id',
testName: 'default suite test'
@@ -31,7 +33,7 @@ describe('', () => {
}
});
- return mkConnectedComponent(, {initialState});
+ return mkConnectedComponent(, {initialState});
};
it('should render meta info from result, extra meta and link to url', () => {
@@ -56,7 +58,7 @@ describe('', () => {
const apiValues = {
extraItems: {baz: 'qux'},
metaInfoExtenders: {
- baz: ((data, extraItems) => `${data.testName}_${extraItems.baz}`).toString()
+ baz: ((data: {testName: string}, extraItems: Record): string => `${data.testName}_${extraItems.baz}`).toString()
}
};
const expectedMetaInfo = [
@@ -65,7 +67,7 @@ describe('', () => {
['url', 'some-url']
];
- const component = mkMetaInfoContentComponent({resultId: 'some-result'}, {tree, apiValues});
+ const component = mkMetaInfoComponent({resultId: 'some-result'}, {tree, apiValues});
expectedMetaInfo.forEach((node) => {
expect(component.getByText(node[0])).to.exist;
@@ -101,7 +103,7 @@ describe('', () => {
['url', 'some-url']
];
- const component = mkMetaInfoContentComponent({resultId: 'some-result'}, {tree});
+ const component = mkMetaInfoComponent({resultId: 'some-result'}, {tree});
expectedMetaInfo.forEach((node) => {
expect(component.getByText(node[0])).to.exist;
@@ -137,7 +139,7 @@ describe('', () => {
['url', 'some-url']
];
- const component = mkMetaInfoContentComponent({resultId: 'some-result'}, {tree});
+ const component = mkMetaInfoComponent({resultId: 'some-result'}, {tree});
expectedMetaInfo.forEach((node) => {
expect(component.getByText(node[0])).to.exist;
@@ -247,12 +249,12 @@ describe('', () => {
};
const config = {metaInfoBaseUrls: stub.metaInfoBaseUrls};
const view = {baseHost: stub.baseHost};
- const component = mkMetaInfoContentComponent({resultId: 'some-result'}, {tree, config, view});
+ const component = mkMetaInfoComponent({resultId: 'some-result'}, {tree, config, view});
const label = stub.expectedFileLabel ?? stub.metaInfo.file;
expect(component.getByText('file')).to.exist;
expect(component.getByText(label)).to.exist;
- expect(component.getByText(label).href).to.equal(stub.expectedFileUrl);
+ expect((component.getByText(label) as HTMLLinkElement).href).to.equal(stub.expectedFileUrl);
});
});
});
diff --git a/test/unit/lib/static/utils.tsx b/test/unit/lib/static/utils.tsx
new file mode 100644
index 000000000..721599ccd
--- /dev/null
+++ b/test/unit/lib/static/utils.tsx
@@ -0,0 +1,174 @@
+import {ThemeProvider} from '@gravity-ui/uikit';
+import {render, RenderResult} from '@testing-library/react';
+import React, {ReactNode} from 'react';
+import _ from 'lodash';
+import configureStore, {MockStore} from 'redux-mock-store';
+import {Provider} from 'react-redux';
+import {applyMiddleware, createStore, Middleware, Store} from 'redux';
+import thunk from 'redux-thunk';
+
+import defaultState from '@/static/modules/default-state';
+import {TestStatus} from '@/constants';
+import reducer from '@/static/modules/reducers';
+import localStorage from '@/static/modules/middlewares/local-storage';
+import {BrowserEntity, ImageEntityFail, State, SuiteEntity, TreeEntity} from '@/static/new-ui/types/store';
+
+export const mkState = ({initialState}: { initialState: Partial }): State => {
+ return _.defaultsDeep(initialState ?? {}, defaultState);
+};
+
+export const mkRealStore = ({initialState, middlewares = []}: {initialState: State, middlewares: Middleware[]}): Store => {
+ return createStore(reducer, exports.mkState({initialState}), applyMiddleware(thunk, localStorage, ...middlewares));
+};
+
+interface GenerateBrowserIdData {
+ suiteName: string;
+ browserName: string;
+}
+
+export const generateBrowserId = ({suiteName, browserName}: GenerateBrowserIdData): string =>
+ `${suiteName} ${browserName}`;
+
+interface GenerateResultIdData {
+ suiteName: string;
+ browserName: string;
+ attempt: 0;
+}
+
+export const generateResultId = ({suiteName, browserName, attempt}: GenerateResultIdData): string =>
+ `${generateBrowserId({suiteName, browserName})} ${attempt}`;
+
+interface GenerateImageIdData {
+ suiteName: string;
+ browserName: string;
+ attempt: 0;
+ stateName: string;
+}
+
+export const generateImageId = ({suiteName, browserName, attempt, stateName}: GenerateImageIdData): string =>
+ `${generateResultId({suiteName, browserName, attempt})} ${stateName}`;
+
+export const mkEmptyTree = (): TreeEntity => _.cloneDeep(defaultState.tree);
+
+interface AddSuiteToTreeData {
+ tree: TreeEntity;
+ suiteName: string;
+}
+
+export const addSuiteToTree = ({tree, suiteName}: AddSuiteToTreeData): void => {
+ tree.suites.byId[suiteName] = {id: suiteName} as SuiteEntity;
+};
+
+interface AddBrowserToTreeData {
+ tree: TreeEntity;
+ suiteName: string;
+ browserName: string;
+}
+
+export const addBrowserToTree = ({tree, suiteName, browserName}: AddBrowserToTreeData): void => {
+ const fullId = `${suiteName} ${browserName}`;
+
+ tree.browsers.byId[fullId] = {
+ id: browserName,
+ name: browserName,
+ parentId: suiteName,
+ resultIds: [],
+ imageIds: []
+ } as BrowserEntity;
+ tree.browsers.stateById[fullId] = {
+ shouldBeShown: true,
+ retryIndex: 0,
+ isHiddenBecauseOfStatus: false
+ };
+};
+
+interface AddResultToTreeData {
+ tree: TreeEntity;
+ suiteName: string;
+ browserName: string;
+ attempt: number;
+ metaInfo: Record;
+}
+
+export const addResultToTree = ({tree, suiteName, browserName, attempt, metaInfo = {}}: AddResultToTreeData): void => {
+ const browserId = `${suiteName} ${browserName}`;
+ const fullId = `${browserId} ${attempt}`;
+
+ tree.results.byId[fullId] = {
+ id: fullId,
+ parentId: browserId,
+ attempt,
+ imageIds: [],
+ metaInfo,
+ status: TestStatus.IDLE,
+ timestamp: 0
+ };
+ tree.results.stateById[fullId] = {
+ matchedSelectedGroup: false
+ };
+
+ tree.browsers.byId[browserId].resultIds.push(fullId);
+};
+
+interface AddImageToTreeData {
+ tree: TreeEntity;
+ suiteName: string;
+ browserName: string;
+ attempt: number;
+ stateName: string;
+ status: TestStatus;
+ expectedImgPath: string;
+ actualImgPath: string;
+ diffImgPath: string;
+}
+
+export const addImageToTree = ({tree, suiteName, browserName, attempt, stateName, status = TestStatus.FAIL, expectedImgPath, actualImgPath, diffImgPath}: AddImageToTreeData): void => {
+ const browserId = `${suiteName} ${browserName}`;
+ const resultId = `${browserId} ${attempt}`;
+ const fullId = `${resultId} ${stateName}`;
+
+ tree.images.byId[fullId] = {
+ id: fullId,
+ parentId: resultId,
+ stateName,
+ status: status as TestStatus.FAIL
+ } as ImageEntityFail;
+
+ if (expectedImgPath) {
+ (tree.images.byId[fullId] as ImageEntityFail).expectedImg = {
+ path: expectedImgPath,
+ size: {height: 1, width: 2}
+ };
+ }
+ if (actualImgPath) {
+ (tree.images.byId[fullId] as ImageEntityFail).actualImg = {
+ path: actualImgPath,
+ size: {height: 1, width: 2}
+ };
+ }
+ if (diffImgPath) {
+ (tree.images.byId[fullId] as ImageEntityFail).diffImg = {
+ path: diffImgPath,
+ size: {height: 1, width: 2}
+ };
+ }
+
+ (tree.browsers.byId[browserId] as any).imageIds.push(fullId); // TODO: why is this needed?
+ tree.results.byId[resultId].imageIds.push(fullId);
+};
+
+export const renderWithStore = (component: ReactNode, store: Store): RenderResult => {
+ return render({component});
+};
+
+const mkStore = ({initialState, state}: {initialState?: Partial, state?: State} = {}): MockStore => {
+ const readyState = state ? state : exports.mkState({initialState});
+ const mockStore = configureStore();
+
+ return mockStore(readyState);
+};
+
+export const mkConnectedComponent = (component: ReactNode, state: {initialState: Partial, state?: State}): RenderResult => {
+ const store = mkStore(state);
+ return render({component});
+};