test: move import test from cypress to RTL to make it less flaky (#6982)

pull/6984/head^2
Mateusz Kwasniewski 2 weeks ago committed by GitHub
parent d698eccb4a
commit d01100fbf0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .github/workflows/e2e.frontend.yaml
  2. 136
      frontend/cypress/integration/import/import.spec.ts
  3. 151
      frontend/src/component/project/Project/Import/Import.test.tsx
  4. 2
      frontend/src/component/project/Project/Import/configure/FileDropZone.tsx
  5. 7
      package.json

@ -12,7 +12,6 @@ jobs:
- projects/access.spec.ts
- projects/overview.spec.ts
- segments/segments.spec.ts
- import/import.spec.ts
steps:
- name: Dump GitHub context
env:

@ -1,136 +0,0 @@
///<reference path="../../global.d.ts" />
describe('imports', () => {
const baseUrl = Cypress.config().baseUrl;
const randomSeed = String(Math.random()).split('.')[1];
const randomFeatureName = `cypress-features${randomSeed}`;
const userIds: any[] = [];
before(() => {
cy.runBefore();
cy.login_UI();
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_UI();
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({ force: true });
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');
cy.intercept('POST', '/api/admin/features-batch/import').as(
'featureImported',
);
// 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.wait('@featureImported');
cy.contains('Import completed');
cy.request({
url: `/api/admin/projects/default/features/${randomFeatureName}`,
headers: { 'Content-Type': 'application/json' },
}).then((response) => {
expect(response.body.name).to.equal(randomFeatureName);
const devEnv = response.body.environments.find(
(env: any) => env.name === 'development',
);
expect(devEnv.name).to.equal('development');
expect(devEnv.strategies[0].parameters.rollout).to.equal('50');
expect(devEnv.enabled).to.equal(true);
});
});
});

@ -0,0 +1,151 @@
import { render } from 'utils/testRenderer';
import { screen, waitFor } from '@testing-library/react';
import { ImportModal } from './ImportModal';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import userEvent from '@testing-library/user-event';
import { CREATE_FEATURE } from 'component/providers/AccessProvider/permissions';
const server = testServerSetup();
const setupApi = () => {
testServerRoute(server, '/api/admin/ui-config', {
versionInfo: {
current: { enterprise: 'present' },
},
});
testServerRoute(server, '/api/admin/projects/default', {
environments: [
{ environment: 'development' },
{ environment: 'production' },
],
});
testServerRoute(
server,
'/api/admin/features-batch/validate',
{ errors: [], permissions: [], warnings: [] },
'post',
);
testServerRoute(server, '/api/admin/features-batch/import', {}, 'post');
};
const importFile = async (content: string) => {
const selectFileInput = screen.getByTestId('import-file');
const importFile = new File([content], 'import.json', {
type: 'application/json',
});
userEvent.upload(selectFileInput, importFile);
};
test('Import happy path', async () => {
setupApi();
let closed = false;
const setOpen = (open: boolean) => {
closed = !open;
};
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
permissions: [{ permission: CREATE_FEATURE }],
});
// configure stage
screen.getByText('Import options');
screen.getByText('Drop your file here');
const validateButton = screen.getByText('Validate');
expect(validateButton).toBeDisabled();
await importFile('{}');
await waitFor(() => {
expect(screen.getByText('Validate')).toBeEnabled();
});
const codeEditorLabel = screen.getByText('Code editor');
codeEditorLabel.click();
const editor = screen.getByLabelText('Exported toggles');
expect(editor.textContent).toBe('{}');
screen.getByText('Validate').click();
// validate stage
screen.getByText('You are importing this configuration in:');
screen.getByText('development');
screen.getByText('default');
const importButton = screen.getByText('Import configuration');
expect(importButton).toBeEnabled();
importButton.click();
// import stage
await screen.findByText('Importing...');
await screen.findByText('Import completed');
expect(closed).toBe(false);
const closeButton = screen.getByText('Close');
closeButton.click();
expect(closed).toBe(true);
});
test('Block when importing non json content', async () => {
setupApi();
const setOpen = () => {};
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
permissions: [{ permission: CREATE_FEATURE }],
});
const codeEditorLabel = screen.getByText('Code editor');
codeEditorLabel.click();
const editor = screen.getByLabelText('Exported toggles');
userEvent.type(editor, 'invalid non json');
const validateButton = screen.getByText('Validate');
expect(validateButton).toBeDisabled();
});
test('Show validation errors', async () => {
setupApi();
testServerRoute(
server,
'/api/admin/features-batch/validate',
{
errors: [
{ message: 'error message', affectedItems: ['itemC', 'itemD'] },
],
permissions: [
{
message: 'permission message',
affectedItems: ['itemE', 'itemF'],
},
],
warnings: [
{
message: 'warning message',
affectedItems: ['itemA', 'itemB'],
},
],
},
'post',
);
const setOpen = () => {};
render(<ImportModal open={true} setOpen={setOpen} project='default' />, {
permissions: [{ permission: CREATE_FEATURE }],
});
await importFile('{}');
await waitFor(() => {
expect(screen.getByText('Validate')).toBeEnabled();
});
screen.getByText('Validate').click();
await screen.findByText('warning message');
await screen.findByText('itemA');
await screen.findByText('itemB');
await screen.findByText('error message');
await screen.findByText('itemC');
await screen.findByText('itemD');
await screen.findByText('permission message');
await screen.findByText('itemE');
await screen.findByText('itemF');
const importButton = screen.getByText('Import configuration');
expect(importButton).toBeDisabled();
});

@ -52,7 +52,7 @@ export const FileDropZone: FC<IFileDropZoneProps> = ({
return (
<Box {...getRootProps()} {...props}>
<input {...getInputProps()} />
<input data-testid='import-file' {...getInputProps()} />
{children}
</Box>
);

@ -85,7 +85,12 @@
"^.+\\.tsx?$": ["@swc/jest"]
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testPathIgnorePatterns": ["/dist/", "/node_modules/", "/frontend/"],
"testPathIgnorePatterns": [
"/dist/",
"/node_modules/",
"/frontend/",
"/website/"
],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"],
"coveragePathIgnorePatterns": [
"/node_modules/",

Loading…
Cancel
Save