diff --git a/.github/workflows/e2e.frontend.yaml b/.github/workflows/e2e.frontend.yaml
index 968e52e8e0..434b1ac0c2 100644
--- a/.github/workflows/e2e.frontend.yaml
+++ b/.github/workflows/e2e.frontend.yaml
@@ -10,6 +10,9 @@ jobs:
- feature/feature.spec.ts
- groups/groups.spec.ts
- projects/access.spec.ts
+ - projects/overview.spec.ts
+ # - projects/settings.spec.ts
+ - projects/notifications.spec.ts
- segments/segments.spec.ts
- import/import.spec.ts
steps:
diff --git a/frontend/cypress/integration/projects/notifications.spec.ts b/frontend/cypress/integration/projects/notifications.spec.ts
index 64ca745c09..125c9b861b 100644
--- a/frontend/cypress/integration/projects/notifications.spec.ts
+++ b/frontend/cypress/integration/projects/notifications.spec.ts
@@ -113,7 +113,6 @@ describe('notifications', () => {
//then
cy.get("[data-testid='NOTIFICATIONS_MODAL']").should('exist');
- cy.get("[data-testid='UNREAD_NOTIFICATIONS']").should('not.exist');
const credentials = userCredentials[0];
diff --git a/frontend/cypress/integration/projects/overview.spec.ts b/frontend/cypress/integration/projects/overview.spec.ts
new file mode 100644
index 0000000000..4d02dbafa0
--- /dev/null
+++ b/frontend/cypress/integration/projects/overview.spec.ts
@@ -0,0 +1,171 @@
+///
+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);
+ });
+});
diff --git a/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx b/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
index 7fc0c7cca1..2fec1c34c3 100644
--- a/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
+++ b/frontend/src/component/common/BatchSelectionActionsBar/BatchSelectionActionsBar.tsx
@@ -1,5 +1,6 @@
import { FC } from 'react';
import { Box, Paper, styled, Typography } from '@mui/material';
+import { BATCH_ACTIONS_BAR, BATCH_SELECTED_COUNT } from 'utils/testIds';
interface IBatchSelectionActionsBarProps {
count: number;
@@ -56,11 +57,13 @@ export const BatchSelectionActionsBar: FC = ({
}
return (
-
+
- {count}
+
+ {count}
+
selected
{children}
diff --git a/frontend/src/component/common/Search/Search.tsx b/frontend/src/component/common/Search/Search.tsx
index 92bf3823c3..8e8a2fd38d 100644
--- a/frontend/src/component/common/Search/Search.tsx
+++ b/frontend/src/component/common/Search/Search.tsx
@@ -6,6 +6,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { SearchSuggestions } from './SearchSuggestions/SearchSuggestions';
import { IGetSearchContextOutput } from 'hooks/useSearch';
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
+import { SEARCH_INPUT } from 'utils/testIds';
interface ISearchProps {
initialValue?: string;
@@ -108,7 +109,10 @@ export const Search = ({
onSearchChange(e.target.value)}
onFocus={() => setShowSuggestions(true)}
diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeaturesBatchActions/MoreActions.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeaturesBatchActions/MoreActions.tsx
index 8a1fbc445b..117202dc05 100644
--- a/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeaturesBatchActions/MoreActions.tsx
+++ b/frontend/src/component/project/Project/ProjectFeatureToggles/ProjectFeaturesBatchActions/MoreActions.tsx
@@ -19,6 +19,7 @@ import useProject from 'hooks/api/getters/useProject/useProject';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
+import { MORE_BATCH_ACTIONS } from 'utils/testIds';
interface IMoreActionsProps {
projectId: string;
@@ -89,7 +90,7 @@ export const MoreActions: VFC = ({ projectId, data }) => {
return (
<>
-
+
diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell.tsx
index eaac3ba5ff..fa77f40c73 100644
--- a/frontend/src/component/project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell.tsx
+++ b/frontend/src/component/project/Project/ProjectFeatureToggles/RowSelectCell/RowSelectCell.tsx
@@ -1,5 +1,6 @@
import { Box, Checkbox, styled } from '@mui/material';
import { FC } from 'react';
+import { BATCH_SELECT } from 'utils/testIds';
interface IRowSelectCellProps {
onChange: () => void;
@@ -18,7 +19,7 @@ export const RowSelectCell: FC = ({
checked,
title,
}) => (
-
+
);
diff --git a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
index 527faf5644..19c6820f8c 100644
--- a/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
+++ b/frontend/src/component/tags/TagTypeList/__tests__/__snapshots__/TagTypeList.test.tsx.snap
@@ -55,6 +55,7 @@ exports[`renders an empty list correctly 1`] = `