Skip to content

Commit

Permalink
test: add interactive demo guide e2e test (#3656)
Browse files Browse the repository at this point in the history
This PR revamps e2e tests, while adding a new one for the interactive
demo guide:
 - Bumps Cypress from `9.7.0` to `12.11.0`;
 - Bumps Cypress GH action from `v2` to `v5`;
 - Makes any adjustments needed;
 - Fixes a lot of issues identified with existing tests;
- Adds new `demo.spec.ts` e2e test that covers the entire demo guide
flow;

**Note:** Currently does not include `demo.spec.ts` in the GH action, as
it
[fails](https://github.com/Unleash/unleash/actions/runs/4896839575/jobs/8744137231?pr=3656)
on step 2.13 (last step of "user-specific" topic). It runs perfectly
fine locally, though.

Might be placebo, but in general tests seem less flaky now and they may
even be faster (especially when not adding the `demo` one, which would
always take a long time).
  • Loading branch information
nunogois committed May 8, 2023
1 parent dc6a158 commit edefa6f
Show file tree
Hide file tree
Showing 17 changed files with 288 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e.frontend.yaml
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Run Cypress
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v5
with:
working-directory: frontend
env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all
Expand Down
28 changes: 28 additions & 0 deletions frontend/cypress.config.ts
@@ -0,0 +1,28 @@
import path from 'path';
import { defineConfig } from 'cypress';
import vitePreprocessor from 'cypress-vite';

export default defineConfig({
projectId: 'tc2qff',
defaultCommandTimeout: 12000,
screenshotOnRunFailure: false,
video: false,
e2e: {
specPattern: '**/*.spec.ts',
setupNodeEvents(on, config) {
on(
'file:preprocessor',
vitePreprocessor({
configFile: path.resolve(__dirname, './vite.config.ts'),
mode: 'development',
})
);
on('task', {
log(message) {
console.log(message);
return null;
},
});
},
},
});
7 changes: 0 additions & 7 deletions frontend/cypress.json

This file was deleted.

28 changes: 20 additions & 8 deletions frontend/cypress/global.d.ts
Expand Up @@ -12,6 +12,12 @@ declare namespace Cypress {
email: string;
password: string;
}

interface IEnvironment {
name: string;
type: 'development' | 'preproduction' | 'test' | 'production';
}

interface Chainable {
runBefore(): Chainable;

Expand Down Expand Up @@ -48,19 +54,14 @@ declare namespace Cypress {
// STRATEGY
addUserIdStrategyToFeature_UI(
featureName: string,
strategyId: string,
projectName?: string
): Chainable;
addFlexibleRolloutStrategyToFeature_UI(
options: AddFlexibleRolloutStrategyOptions
): Chainable;
updateFlexibleRolloutStrategy_UI(
featureToggleName: string,
strategyId: string
);
updateFlexibleRolloutStrategy_UI(featureToggleName: string);
deleteFeatureStrategy_UI(
featureName: string,
strategyId: string,
shouldWait?: boolean,
projectName?: string
): Chainable;
Expand All @@ -73,9 +74,20 @@ declare namespace Cypress {
role: number,
projectName?: string
): Chainable;
createProject_API(name: string): Chainable;
createProject_API(
name: string,
options?: Partial<Cypress.RequestOptions>
): Chainable;
deleteProject_API(name: string): Chainable;
createFeature_API(name: string, projectName?: string): Chainable;
createFeature_API(
name: string,
projectName?: string,
options?: Partial<Cypress.RequestOptions>
): Chainable;
deleteFeature_API(name: string): Chainable;
createEnvironment_API(
environment: IEnvironment,
options?: Partial<Cypress.RequestOptions>
): Chainable;
}
}
130 changes: 130 additions & 0 deletions frontend/cypress/integration/demo/demo.spec.ts
@@ -0,0 +1,130 @@
///<reference path="../../global.d.ts" />
import { TOPICS } from '../../../src/component/demo/demo-topics';

describe('demo', () => {
const baseUrl = Cypress.config().baseUrl;
const randomId = String(Math.random()).split('.')[1];

before(() => {
cy.runBefore();
cy.login_UI();

const optionsIgnore409 = { failOnStatusCode: false };

cy.createEnvironment_API(
{
name: 'dev',
type: 'development',
},
optionsIgnore409
);
cy.createProject_API('demo-app', optionsIgnore409);
cy.createFeature_API('demoApp.step1', 'demo-app', optionsIgnore409);
cy.createFeature_API('demoApp.step2', 'demo-app', optionsIgnore409);
cy.createFeature_API('demoApp.step3', 'demo-app', optionsIgnore409);
cy.createFeature_API('demoApp.step4', 'demo-app', optionsIgnore409);
});

beforeEach(() => {
cy.login_UI();
cy.visit('/projects');
if (document.querySelector("[data-testid='CLOSE_SPLASH']")) {
cy.get("[data-testid='CLOSE_SPLASH']").click();
}

cy.intercept('GET', `${baseUrl}/api/admin/ui-config`, req => {
req.headers['cache-control'] =
'no-cache, no-store, must-revalidate';
req.on('response', res => {
if (res.body) {
res.body.flags = {
...res.body.flags,
demo: true,
};
}
});
});
});

afterEach(() => {
cy.intercept('GET', `${baseUrl}/api/admin/ui-config`).as('uiConfig');
});

after(() => {
cy.request({
method: 'DELETE',
url: `${baseUrl}/api/admin/projects/demo-app/features/demoApp.step1`,
});
cy.request({
method: 'DELETE',
url: `${baseUrl}/api/admin/projects/demo-app/features/demoApp.step2`,
});
cy.request({
method: 'DELETE',
url: `${baseUrl}/api/admin/projects/demo-app/features/demoApp.step3`,
});
cy.request({
method: 'DELETE',
url: `${baseUrl}/api/admin/projects/demo-app/features/demoApp.step4`,
});
cy.request({
method: 'POST',
url: `${baseUrl}/api/admin/projects/demo-app/delete`,
body: {
features: [
'demoApp.step1',
'demoApp.step2',
'demoApp.step3',
'demoApp.step4',
],
},
});
});

it('can complete the demo', () => {
cy.get('[data-testid="DEMO_START_BUTTON"]').click();

for (let topic = 0; topic < TOPICS.length; topic++) {
const currentTopic = TOPICS[topic];
for (let step = 0; step < currentTopic.steps.length; step++) {
const currentStep = currentTopic.steps[step];

cy.task(
'log',
`Testing topic #${topic + 1} "${
currentTopic.title
}", step #${step + 1}...`
);

if (!currentStep.optional) {
cy.wait(2000);

if (currentStep.nextButton) {
if (currentStep.focus) {
if (currentStep.focus === true) {
cy.get(currentStep.target as string)
.first()
.type(randomId, { force: true });
} else {
cy.get(currentStep.target as string)
.first()
.find(currentStep.focus)
.first()
.type(randomId, { force: true });
}
}
cy.get('[data-testid="DEMO_NEXT_BUTTON"]').click({
force: true,
});
} else {
cy.get(currentStep.target as string)
.first()
.click({
force: true,
});
}
}
}
}
});
});
21 changes: 7 additions & 14 deletions frontend/cypress/integration/feature/feature.spec.ts
Expand Up @@ -6,7 +6,6 @@ describe('feature', () => {

const variant1 = 'variant1';
const variant2 = 'variant2';
let strategyId = '';

before(() => {
cy.runBefore();
Expand Down Expand Up @@ -40,26 +39,20 @@ describe('feature', () => {
);
});

it('can add, update and delete a gradual rollout strategy to the development environment', async () => {
it('can add, update and delete a gradual rollout strategy to the development environment', () => {
cy.addFlexibleRolloutStrategyToFeature_UI({
featureToggleName,
}).then(value => {
strategyId = value;
cy.updateFlexibleRolloutStrategy_UI(
featureToggleName,
strategyId
).then(() =>
cy.deleteFeatureStrategy_UI(featureToggleName, strategyId)
}).then(() => {
cy.updateFlexibleRolloutStrategy_UI(featureToggleName).then(() =>
cy.deleteFeatureStrategy_UI(featureToggleName)
);
});
});

it('can add a userId strategy to the development environment', () => {
cy.addUserIdStrategyToFeature_UI(featureToggleName, strategyId).then(
value => {
cy.deleteFeatureStrategy_UI(featureToggleName, value, false);
}
);
cy.addUserIdStrategyToFeature_UI(featureToggleName).then(() => {
cy.deleteFeatureStrategy_UI(featureToggleName, false);
});
});

it('can add variants to the development environment', () => {
Expand Down
5 changes: 4 additions & 1 deletion frontend/cypress/integration/import/import.spec.ts
Expand Up @@ -34,7 +34,7 @@ describe('imports', () => {

it('can import data', () => {
cy.visit('/projects/default');
cy.get("[data-testid='IMPORT_BUTTON']").click();
cy.get("[data-testid='IMPORT_BUTTON']").click({ force: true });

const exportText = {
features: [
Expand Down Expand Up @@ -114,6 +114,9 @@ describe('imports', () => {
// cy.contains('Import completed');

cy.visit(`/projects/default/features/${randomFeatureName}`);

cy.wait(500);

cy.get(
"[data-testid='feature-toggle-status'] input[type='checkbox']:checked"
)
Expand Down
22 changes: 0 additions & 22 deletions frontend/cypress/plugins/index.ts

This file was deleted.

31 changes: 27 additions & 4 deletions frontend/cypress/support/API.ts
Expand Up @@ -6,18 +6,20 @@ const password = Cypress.env(`AUTH_PASSWORD`) + '_A';
const PROJECT_MEMBER = 5;
export const createFeature_API = (
featureName: string,
projectName?: string
projectName?: string,
options?: Partial<Cypress.RequestOptions>
): Chainable<any> => {
const project = projectName || 'default';
return cy.request({
url: `/api/admin/projects/${project}/features`,
url: `${baseUrl}/api/admin/projects/${project}/features`,
method: 'POST',
body: {
name: `${featureName}`,
description: 'hello-world',
type: 'release',
impressionData: false,
},
...options,
});
};

Expand All @@ -32,16 +34,20 @@ export const deleteFeature_API = (name: string): Chainable<any> => {
});
};

export const createProject_API = (project: string): Chainable<any> => {
export const createProject_API = (
project: string,
options?: Partial<Cypress.RequestOptions>
): Chainable<any> => {
return cy.request({
url: `/api/admin/projects`,
url: `${baseUrl}/api/admin/projects`,
method: 'POST',
body: {
id: project,
name: project,
description: project,
impressionData: false,
},
...options,
});
};

Expand Down Expand Up @@ -101,3 +107,20 @@ export const addUserToProject_API = (
}
);
};

interface IEnvironment {
name: string;
type: 'development' | 'preproduction' | 'test' | 'production';
}

export const createEnvironment_API = (
environment: IEnvironment,
options?: Partial<Cypress.RequestOptions>
): Chainable<any> => {
return cy.request({
url: `${baseUrl}/api/admin/environments`,
method: 'POST',
body: environment,
...options,
});
};

0 comments on commit edefa6f

Please sign in to comment.