mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
E2E test - batch updates (#3392)
This commit is contained in:
parent
5c27153e50
commit
f124997485
3
.github/workflows/e2e.frontend.yaml
vendored
3
.github/workflows/e2e.frontend.yaml
vendored
@ -10,6 +10,9 @@ jobs:
|
|||||||
- feature/feature.spec.ts
|
- feature/feature.spec.ts
|
||||||
- groups/groups.spec.ts
|
- groups/groups.spec.ts
|
||||||
- projects/access.spec.ts
|
- projects/access.spec.ts
|
||||||
|
- projects/overview.spec.ts
|
||||||
|
# - projects/settings.spec.ts
|
||||||
|
- projects/notifications.spec.ts
|
||||||
- segments/segments.spec.ts
|
- segments/segments.spec.ts
|
||||||
- import/import.spec.ts
|
- import/import.spec.ts
|
||||||
steps:
|
steps:
|
||||||
|
@ -113,7 +113,6 @@ describe('notifications', () => {
|
|||||||
|
|
||||||
//then
|
//then
|
||||||
cy.get("[data-testid='NOTIFICATIONS_MODAL']").should('exist');
|
cy.get("[data-testid='NOTIFICATIONS_MODAL']").should('exist');
|
||||||
cy.get("[data-testid='UNREAD_NOTIFICATIONS']").should('not.exist');
|
|
||||||
|
|
||||||
const credentials = userCredentials[0];
|
const credentials = userCredentials[0];
|
||||||
|
|
||||||
|
171
frontend/cypress/integration/projects/overview.spec.ts
Normal file
171
frontend/cypress/integration/projects/overview.spec.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
import {
|
||||||
|
BATCH_ACTIONS_BAR,
|
||||||
|
BATCH_SELECT,
|
||||||
|
BATCH_SELECTED_COUNT,
|
||||||
|
MORE_BATCH_ACTIONS,
|
||||||
|
SEARCH_INPUT,
|
||||||
|
} from '../../../src/utils/testIds';
|
||||||
|
|
||||||
|
const randomId = String(Math.random()).split('.')[1];
|
||||||
|
const featureTogglePrefix = 'unleash-e2e-project-overview';
|
||||||
|
const featureToggleName = `${featureTogglePrefix}-${randomId}`;
|
||||||
|
const baseUrl = Cypress.config().baseUrl;
|
||||||
|
const selectAll = '[title="Toggle All Rows Selected"] input[type="checkbox"]';
|
||||||
|
|
||||||
|
// Disable the prod guard modal by marking it as seen.
|
||||||
|
const disableFeatureStrategiesProdGuard = () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'useFeatureStrategyProdGuardSettings:v2',
|
||||||
|
JSON.stringify({ hide: true })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Disable all active splash pages by visiting them.
|
||||||
|
const disableActiveSplashScreens = () => {
|
||||||
|
cy.visit(`/splash/operators`);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('project overview', () => {
|
||||||
|
before(() => {
|
||||||
|
disableFeatureStrategiesProdGuard();
|
||||||
|
disableActiveSplashScreens();
|
||||||
|
cy.login();
|
||||||
|
cy.request({
|
||||||
|
url: '/api/admin/projects/default/features',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
name: `${featureToggleName}-A`,
|
||||||
|
description: 'hello-world',
|
||||||
|
type: 'release',
|
||||||
|
impressionData: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
cy.request({
|
||||||
|
url: '/api/admin/projects/default/features',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
name: `${featureToggleName}-B`,
|
||||||
|
description: 'hello-world',
|
||||||
|
type: 'release',
|
||||||
|
impressionData: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login();
|
||||||
|
if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
|
||||||
|
cy.get("[data-testid='CLOSE_SPLASH']").click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${baseUrl}/api/admin/features/${featureToggleName}-A`,
|
||||||
|
failOnStatusCode: false,
|
||||||
|
});
|
||||||
|
cy.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${baseUrl}/api/admin/features/${featureToggleName}-B`,
|
||||||
|
failOnStatusCode: false,
|
||||||
|
});
|
||||||
|
cy.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${baseUrl}/api/admin/archive/${featureToggleName}-A`,
|
||||||
|
});
|
||||||
|
cy.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${baseUrl}/api/admin/archive/${featureToggleName}-B`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads the table', () => {
|
||||||
|
cy.visit('/projects/default');
|
||||||
|
|
||||||
|
// Use search to filter feature toggles and check that the feature toggle is listed in the table.
|
||||||
|
cy.get("[data-testid='SEARCH_INPUT']").click().type(featureToggleName);
|
||||||
|
cy.get('table').contains('td', `${featureToggleName}-A`);
|
||||||
|
cy.get('table tbody tr').should('have.length', 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can select and deselect feature toggles', () => {
|
||||||
|
cy.visit('/projects/default');
|
||||||
|
cy.viewport(1920, 1080);
|
||||||
|
cy.get("[data-testid='SEARCH_INPUT']").click().type(featureToggleName);
|
||||||
|
cy.get('table tbody tr').should('have.length', 2);
|
||||||
|
const counter = `[data-testid="${BATCH_SELECTED_COUNT}"]`;
|
||||||
|
|
||||||
|
cy.get(counter).should('not.exist');
|
||||||
|
cy.get(selectAll).click();
|
||||||
|
cy.get(counter).contains('2');
|
||||||
|
cy.get(selectAll).click();
|
||||||
|
cy.get(counter).should('not.exist');
|
||||||
|
|
||||||
|
cy.get('table td')
|
||||||
|
.contains(`${featureToggleName}-A`)
|
||||||
|
.closest('tr')
|
||||||
|
.find(`[data-testid="${BATCH_SELECT}"] input[type="checkbox"]`)
|
||||||
|
.click();
|
||||||
|
cy.get(counter).contains('1');
|
||||||
|
|
||||||
|
cy.get('table td')
|
||||||
|
.contains(`${featureToggleName}-A`)
|
||||||
|
.closest('tr')
|
||||||
|
.find(`[data-testid="${BATCH_SELECT}"] input[type="checkbox"]`)
|
||||||
|
.click();
|
||||||
|
cy.get(counter).should('not.exist');
|
||||||
|
cy.get('table td')
|
||||||
|
.contains(`${featureToggleName}-B`)
|
||||||
|
.closest('tr')
|
||||||
|
.find(`[data-testid="${BATCH_SELECT}"] input[type="checkbox"]`)
|
||||||
|
.click();
|
||||||
|
cy.get(counter).contains('1');
|
||||||
|
|
||||||
|
cy.get('table td')
|
||||||
|
.contains(`${featureToggleName}-A`)
|
||||||
|
.closest('tr')
|
||||||
|
.find(`[data-testid="${BATCH_SELECT}"] input[type="checkbox"]`)
|
||||||
|
.click();
|
||||||
|
cy.get(counter).contains('2');
|
||||||
|
cy.get('table td')
|
||||||
|
.contains(`${featureToggleName}-B`)
|
||||||
|
.closest('tr')
|
||||||
|
.find(`[data-testid="${BATCH_SELECT}"] input[type="checkbox"]`)
|
||||||
|
.click();
|
||||||
|
cy.get(counter).contains('1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can mark selected togggles as stale', () => {
|
||||||
|
cy.visit('/projects/default');
|
||||||
|
cy.viewport(1920, 1080);
|
||||||
|
cy.get(`[data-testid='${SEARCH_INPUT}']`).click().type(featureToggleName);
|
||||||
|
cy.get('table tbody tr').should('have.length', 2);
|
||||||
|
cy.get(selectAll).click();
|
||||||
|
|
||||||
|
cy.get(`[data-testid="${MORE_BATCH_ACTIONS}"]`).click();
|
||||||
|
|
||||||
|
cy.get('[role="menuitem"]').contains('Mark as stale').click();
|
||||||
|
|
||||||
|
cy.visit(`/projects/default/features/${featureToggleName}-A`);
|
||||||
|
cy.get('[title="Feature toggle is deprecated."]').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can archive selected togggles', () => {
|
||||||
|
cy.visit('/projects/default');
|
||||||
|
cy.viewport(1920, 1080);
|
||||||
|
cy.get(`[data-testid='${SEARCH_INPUT}']`).click().type(featureToggleName);
|
||||||
|
cy.get('table tbody tr').should('have.length', 2);
|
||||||
|
cy.get(selectAll).click();
|
||||||
|
|
||||||
|
cy.get(`[data-testid=${BATCH_ACTIONS_BAR}] button`)
|
||||||
|
.contains('Archive')
|
||||||
|
.click();
|
||||||
|
cy.get('p')
|
||||||
|
.contains('Are you sure you want to archive 2 feature toggles?')
|
||||||
|
.should('exist');
|
||||||
|
cy.get('button').contains('Archive toggles').click();
|
||||||
|
cy.get('table tbody tr').should('have.length', 0);
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Box, Paper, styled, Typography } from '@mui/material';
|
import { Box, Paper, styled, Typography } from '@mui/material';
|
||||||
|
import { BATCH_ACTIONS_BAR, BATCH_SELECTED_COUNT } from 'utils/testIds';
|
||||||
|
|
||||||
interface IBatchSelectionActionsBarProps {
|
interface IBatchSelectionActionsBarProps {
|
||||||
count: number;
|
count: number;
|
||||||
@ -56,11 +57,13 @@ export const BatchSelectionActionsBar: FC<IBatchSelectionActionsBarProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledStickyContainer>
|
<StyledStickyContainer data-testid={BATCH_ACTIONS_BAR}>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledBar elevation={4}>
|
<StyledBar elevation={4}>
|
||||||
<StyledText>
|
<StyledText>
|
||||||
<StyledCount>{count}</StyledCount>
|
<StyledCount data-testid={BATCH_SELECTED_COUNT}>
|
||||||
|
{count}
|
||||||
|
</StyledCount>
|
||||||
 selected
|
 selected
|
||||||
</StyledText>
|
</StyledText>
|
||||||
{children}
|
{children}
|
||||||
|
@ -6,6 +6,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
|
|||||||
import { SearchSuggestions } from './SearchSuggestions/SearchSuggestions';
|
import { SearchSuggestions } from './SearchSuggestions/SearchSuggestions';
|
||||||
import { IGetSearchContextOutput } from 'hooks/useSearch';
|
import { IGetSearchContextOutput } from 'hooks/useSearch';
|
||||||
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
|
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
|
||||||
|
import { SEARCH_INPUT } from 'utils/testIds';
|
||||||
|
|
||||||
interface ISearchProps {
|
interface ISearchProps {
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
@ -108,7 +109,10 @@ export const Search = ({
|
|||||||
<StyledInputBase
|
<StyledInputBase
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
inputProps={{ 'aria-label': placeholder }}
|
inputProps={{
|
||||||
|
'aria-label': placeholder,
|
||||||
|
'data-testid': SEARCH_INPUT,
|
||||||
|
}}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={e => onSearchChange(e.target.value)}
|
onChange={e => onSearchChange(e.target.value)}
|
||||||
onFocus={() => setShowSuggestions(true)}
|
onFocus={() => setShowSuggestions(true)}
|
||||||
|
@ -19,6 +19,7 @@ import useProject from 'hooks/api/getters/useProject/useProject';
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
|
||||||
|
import { MORE_BATCH_ACTIONS } from 'utils/testIds';
|
||||||
|
|
||||||
interface IMoreActionsProps {
|
interface IMoreActionsProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -89,7 +90,7 @@ export const MoreActions: VFC<IMoreActionsProps> = ({ projectId, data }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="Feature toggle actions" arrow describeChild>
|
<Tooltip title="More bulk actions" arrow describeChild>
|
||||||
<IconButton
|
<IconButton
|
||||||
id={menuId}
|
id={menuId}
|
||||||
aria-controls={open ? menuId : undefined}
|
aria-controls={open ? menuId : undefined}
|
||||||
@ -97,6 +98,7 @@ export const MoreActions: VFC<IMoreActionsProps> = ({ projectId, data }) => {
|
|||||||
aria-expanded={open ? 'true' : undefined}
|
aria-expanded={open ? 'true' : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid={MORE_BATCH_ACTIONS}
|
||||||
>
|
>
|
||||||
<MoreVert />
|
<MoreVert />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Box, Checkbox, styled } from '@mui/material';
|
import { Box, Checkbox, styled } from '@mui/material';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { BATCH_SELECT } from 'utils/testIds';
|
||||||
|
|
||||||
interface IRowSelectCellProps {
|
interface IRowSelectCellProps {
|
||||||
onChange: () => void;
|
onChange: () => void;
|
||||||
@ -18,7 +19,7 @@ export const RowSelectCell: FC<IRowSelectCellProps> = ({
|
|||||||
checked,
|
checked,
|
||||||
title,
|
title,
|
||||||
}) => (
|
}) => (
|
||||||
<StyledBoxCell>
|
<StyledBoxCell data-testid={BATCH_SELECT}>
|
||||||
<Checkbox onChange={onChange} title={title} checked={checked} />
|
<Checkbox onChange={onChange} title={title} checked={checked} />
|
||||||
</StyledBoxCell>
|
</StyledBoxCell>
|
||||||
);
|
);
|
||||||
|
@ -55,6 +55,7 @@ exports[`renders an empty list correctly 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-label="Search (Ctrl+K)"
|
aria-label="Search (Ctrl+K)"
|
||||||
className="MuiInputBase-input css-mfsqjb-MuiInputBase-input"
|
className="MuiInputBase-input css-mfsqjb-MuiInputBase-input"
|
||||||
|
data-testid="SEARCH_INPUT"
|
||||||
onAnimationStart={[Function]}
|
onAnimationStart={[Function]}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
|
@ -77,7 +77,6 @@ export const TOAST_TEXT = 'TOAST_TEXT';
|
|||||||
export const LARGE_NUMBER_PRETTIFIED = 'LARGE_NUMBER_PRETTIFIED';
|
export const LARGE_NUMBER_PRETTIFIED = 'LARGE_NUMBER_PRETTIFIED';
|
||||||
|
|
||||||
/* EXPORT/IMPORT FEATURES */
|
/* EXPORT/IMPORT FEATURES */
|
||||||
|
|
||||||
export const IMPORT_BUTTON = 'IMPORT_BUTTON';
|
export const IMPORT_BUTTON = 'IMPORT_BUTTON';
|
||||||
export const CODE_EDITOR_TAB = 'CODE_EDITOR_TAB';
|
export const CODE_EDITOR_TAB = 'CODE_EDITOR_TAB';
|
||||||
export const IMPORT_ENVIRONMENT = 'IMPORT_ENVIRONMENT';
|
export const IMPORT_ENVIRONMENT = 'IMPORT_ENVIRONMENT';
|
||||||
@ -86,5 +85,13 @@ export const VALIDATE_BUTTON = 'VALIDATE_BUTTON';
|
|||||||
export const IMPORT_CONFIGURATION_BUTTON = 'IMPORT_CONFIGURATION_BUTTON';
|
export const IMPORT_CONFIGURATION_BUTTON = 'IMPORT_CONFIGURATION_BUTTON';
|
||||||
|
|
||||||
/* NOTIFICATIONS FEATURES */
|
/* NOTIFICATIONS FEATURES */
|
||||||
|
|
||||||
export const NOTIFICATIONS_BUTTON = 'NOTIFICATIONS_BUTTON';
|
export const NOTIFICATIONS_BUTTON = 'NOTIFICATIONS_BUTTON';
|
||||||
|
|
||||||
|
/* TABLE */
|
||||||
|
export const SEARCH_INPUT = 'SEARCH_INPUT';
|
||||||
|
|
||||||
|
/* BATCH UPDATES */
|
||||||
|
export const BATCH_ACTIONS_BAR = 'BATCH_ACTIONS_BAR';
|
||||||
|
export const BATCH_SELECTED_COUNT = 'BATCH_SELECTED_COUNT';
|
||||||
|
export const BATCH_SELECT = 'BATCH_SELECT';
|
||||||
|
export const MORE_BATCH_ACTIONS = 'MORE_BATCH_ACTIONS';
|
||||||
|
Loading…
Reference in New Issue
Block a user