diff --git a/.github/workflows/e2e.frontend.yaml b/.github/workflows/e2e.frontend.yaml
index c7eda419f3..968e52e8e0 100644
--- a/.github/workflows/e2e.frontend.yaml
+++ b/.github/workflows/e2e.frontend.yaml
@@ -11,6 +11,7 @@ jobs:
- groups/groups.spec.ts
- projects/access.spec.ts
- segments/segments.spec.ts
+ - import/import.spec.ts
steps:
- name: Dump GitHub context
env:
diff --git a/frontend/cypress/integration/import/import.spec.ts b/frontend/cypress/integration/import/import.spec.ts
new file mode 100644
index 0000000000..a74ccd2965
--- /dev/null
+++ b/frontend/cypress/integration/import/import.spec.ts
@@ -0,0 +1,125 @@
+///
+
+const baseUrl = Cypress.config().baseUrl;
+const randomSeed = String(Math.random()).split('.')[1];
+const randomFeatureName = `cypress-features${randomSeed}`;
+const userIds: any[] = [];
+
+// Disable all active splash pages by visiting them.
+const disableActiveSplashScreens = () => {
+ cy.visit(`/splash/operators`);
+};
+
+describe('imports', () => {
+ before(() => {
+ disableActiveSplashScreens();
+ cy.login();
+ for (let i = 1; i <= 2; i++) {
+ cy.request('POST', `${baseUrl}/api/admin/user-admin`, {
+ name: `unleash-e2e-user${i}-${randomFeatureName}`,
+ email: `unleash-e2e-user${i}-${randomFeatureName}@test.com`,
+ sendEmail: false,
+ rootRole: 3,
+ }).then(response => userIds.push(response.body.id));
+ }
+ });
+
+ after(() => {
+ userIds.forEach(id =>
+ cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`)
+ );
+ });
+
+ beforeEach(() => {
+ cy.login();
+ if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
+ cy.get("[data-testid='CLOSE_SPLASH']").click();
+ }
+ });
+
+ it('can import data', () => {
+ cy.visit('/projects/default');
+ cy.get("[data-testid='IMPORT_BUTTON']").click();
+
+ const exportText = {
+ features: [
+ {
+ name: randomFeatureName,
+ description: '',
+ type: 'release',
+ project: 'default',
+ stale: false,
+ impressionData: false,
+ archived: false,
+ },
+ ],
+ featureStrategies: [
+ {
+ name: 'flexibleRollout',
+ id: '14a0d9dd-2b5d-4a21-98fd-ede72bda0328',
+ featureName: randomFeatureName,
+ parameters: {
+ groupId: randomFeatureName,
+ rollout: '50',
+ stickiness: 'default',
+ },
+ constraints: [],
+ segments: [],
+ },
+ ],
+ featureEnvironments: [
+ {
+ enabled: true,
+ featureName: randomFeatureName,
+ environment: 'test',
+ variants: [],
+ name: randomFeatureName,
+ },
+ ],
+ contextFields: [],
+ featureTags: [
+ {
+ featureName: randomFeatureName,
+ tagType: 'simple',
+ tagValue: 'best-tag',
+ },
+ {
+ featureName: randomFeatureName,
+ tagType: 'simple',
+ tagValue: 'rserw',
+ },
+ {
+ featureName: randomFeatureName,
+ tagType: 'simple',
+ tagValue: 'FARO',
+ },
+ ],
+ segments: [],
+ tagTypes: [
+ {
+ name: 'simple',
+ description: 'Used to simplify filtering of features',
+ icon: '#',
+ },
+ ],
+ };
+
+ cy.get("[data-testid='VALIDATE_BUTTON']").should('be.disabled');
+
+ // cypress can only work with input@file that is visible
+ cy.get('input[type=file]')
+ .invoke('attr', 'style', 'display: block')
+ .selectFile({
+ contents: Cypress.Buffer.from(JSON.stringify(exportText)),
+ fileName: 'upload.json',
+ lastModified: Date.now(),
+ });
+ cy.get("[data-testid='VALIDATE_BUTTON']").click();
+ cy.get("[data-testid='IMPORT_CONFIGURATION_BUTTON']").click();
+ // cy.contains('Import completed');
+
+ cy.visit(`/projects/default/features/${randomFeatureName}`);
+ cy.contains('enabled in development');
+ cy.contains('50%');
+ });
+});
diff --git a/frontend/src/component/project/Project/Import/ImportModal.tsx b/frontend/src/component/project/Project/Import/ImportModal.tsx
index cef316970b..582c1d9959 100644
--- a/frontend/src/component/project/Project/Import/ImportModal.tsx
+++ b/frontend/src/component/project/Project/Import/ImportModal.tsx
@@ -14,6 +14,11 @@ import {
import { ValidationStage } from './validate/ValidationStage';
import { ImportStage } from './import/ImportStage';
import { ImportOptions } from './configure/ImportOptions';
+import {
+ IMPORT_BUTTON,
+ IMPORT_CONFIGURATION_BUTTON,
+ VALIDATE_BUTTON,
+} from '../../../../utils/testIds';
const ModalContentContainer = styled('div')(({ theme }) => ({
minHeight: '100vh',
diff --git a/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx b/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx
index f8638a6ec0..f0f2cff1c6 100644
--- a/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx
+++ b/frontend/src/component/project/Project/Import/configure/ConfigurationStage.tsx
@@ -16,6 +16,11 @@ import React, { FC, ReactNode, useState } from 'react';
import useToast from 'hooks/useToast';
import { ImportLayoutContainer } from '../ImportLayoutContainer';
import { ActionsContainer } from '../ActionsContainer';
+import {
+ CODE_EDITOR_TAB,
+ CODE_TEXT_FIELD,
+ VALIDATE_BUTTON,
+} from 'utils/testIds';
const StyledTextField = styled(TextField)(({ theme }) => ({
width: '100%',
@@ -58,6 +63,7 @@ export const ConfigurationTabs: FC<{
setActiveTab('code')}
/>
@@ -119,6 +125,7 @@ export const ImportArea: FC<{
variant="outlined"
onChange={event => setImportPayload(event.target.value)}
value={importPayload}
+ data-testid={CODE_TEXT_FIELD}
multiline
minRows={13}
maxRows={13}
@@ -139,6 +146,7 @@ export const Actions: FC<{
variant="contained"
type="submit"
onClick={onSubmit}
+ data-testid={VALIDATE_BUTTON}
disabled={disabled}
>
Validate
diff --git a/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx b/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx
index afe7952213..0a73810a4f 100644
--- a/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx
+++ b/frontend/src/component/project/Project/Import/configure/ImportOptions.tsx
@@ -3,6 +3,7 @@ import { KeyboardArrowDownOutlined } from '@mui/icons-material';
import React, { FC, useEffect } from 'react';
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
import { Box, styled, Typography } from '@mui/material';
+import { IMPORT_ENVIRONMENT } from 'utils/testIds';
const ImportOptionsContainer = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.secondaryContainer,
@@ -57,6 +58,7 @@ export const ImportOptions: FC = ({
onChange={onChange}
label={'Environment'}
value={environment}
+ data-testid={IMPORT_ENVIRONMENT}
IconComponent={KeyboardArrowDownOutlined}
fullWidth
/>
diff --git a/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx b/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx
index 68468efc02..355f1a314c 100644
--- a/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx
+++ b/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx
@@ -9,6 +9,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
import { ActionsContainer } from '../ActionsContainer';
+import { IMPORT_CONFIGURATION_BUTTON } from 'utils/testIds';
const ImportInfoContainer = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.secondaryContainer,
@@ -191,6 +192,7 @@ export const ValidationStage: FC<{
variant="contained"
type="submit"
onClick={onSubmit}
+ data-testid={IMPORT_CONFIGURATION_BUTTON}
disabled={validationResult.errors.length > 0 || !validJSON}
>
Import configuration
diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx
index 027a449ec5..c8e4439480 100644
--- a/frontend/src/component/project/Project/Project.tsx
+++ b/frontend/src/component/project/Project/Project.tsx
@@ -41,6 +41,7 @@ import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests
import { ProjectSettings } from './ProjectSettings/ProjectSettings';
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
import { ImportModal } from './Import/ImportModal';
+import { IMPORT_BUTTON } from 'utils/testIds';
export const Project = () => {
const projectId = useRequiredPathParam('projectId');
@@ -150,6 +151,7 @@ export const Project = () => {
}}
onClick={() => setModalOpen(true)}
tooltipProps={{ title: 'Import' }}
+ data-testid={IMPORT_BUTTON}
data-loading
>
diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts
index 39bfd2467d..c1f3133683 100644
--- a/frontend/src/utils/testIds.ts
+++ b/frontend/src/utils/testIds.ts
@@ -75,3 +75,12 @@ export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID';
export const INSTANCE_STATUS_BAR_ID = 'INSTANCE_STATUS_BAR_ID';
export const TOAST_TEXT = 'TOAST_TEXT';
export const LARGE_NUMBER_PRETTIFIED = 'LARGE_NUMBER_PRETTIFIED';
+
+/* EXPORT/IMPORT FEATURES */
+
+export const IMPORT_BUTTON = 'IMPORT_BUTTON';
+export const CODE_EDITOR_TAB = 'CODE_EDITOR_TAB';
+export const IMPORT_ENVIRONMENT = 'IMPORT_ENVIRONMENT';
+export const CODE_TEXT_FIELD = 'CODE_TEXT_FIELD';
+export const VALIDATE_BUTTON = 'VALIDATE_BUTTON';
+export const IMPORT_CONFIGURATION_BUTTON = 'IMPORT_CONFIGURATION_BUTTON';