From e88232ede2cf148bc3a1b2c039b92952fd1a699b Mon Sep 17 00:00:00 2001 From: OlegIvaniv Date: Wed, 3 May 2023 14:06:06 +0200 Subject: [PATCH] test: Address flaky setup e2e (no-changelog) (#6085) * test: Add /setup intercept for `skipSetup` command (no-changelog) * Drop all tables for e2e reset, intercept account setup request * Fix linting issues * Allow to skip setup account request intercept and linting fixes * Make sure variables are loaded * Use PATCH for enabling of e2e features * Do not exclude migration table from truncation * Add user sign-up intercept --- cypress.config.js | 11 ++++-- cypress/e2e/23-variables.cy.ts | 4 +++ cypress/e2e/3-default-owner.cy.ts | 3 +- cypress/support/commands.ts | 20 +++++++++-- cypress/support/index.ts | 3 +- packages/cli/src/api/e2e.api.ts | 59 ++++++++++++++++--------------- 6 files changed, 65 insertions(+), 35 deletions(-) diff --git a/cypress.config.js b/cypress.config.js index d1451fcd06..b6cea71083 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -11,7 +11,7 @@ module.exports = defineConfig({ }, defaultCommandTimeout: 10000, requestTimeout: 12000, - numTestsKeptInMemory: 0, + numTestsKeptInMemory: 2, experimentalMemoryManagement: true, e2e: { baseUrl: BASE_URL, @@ -35,8 +35,13 @@ module.exports = defineConfig({ return null } }, - 'enable-feature': (feature) => - fetch(BASE_URL + `/e2e/enable-feature/${feature}`, { method: 'POST' }), + 'set-feature': ({ feature, enabled }) => { + return fetch(BASE_URL + `/e2e/feature/${feature}`, { + method: 'PATCH', + body: JSON.stringify({ enabled }), + headers: { 'Content-Type': 'application/json' } + }) + }, }); }, }, diff --git a/cypress/e2e/23-variables.cy.ts b/cypress/e2e/23-variables.cy.ts index 8dc16bb8e9..ce78f8fbe3 100644 --- a/cypress/e2e/23-variables.cy.ts +++ b/cypress/e2e/23-variables.cy.ts @@ -16,6 +16,7 @@ describe('Variables', () => { }); it('should show the unlicensed action box when the feature is disabled', () => { + cy.disableFeature('feat:variables'); cy.signin({ email, password }); cy.visit(variablesPage.url); @@ -30,7 +31,10 @@ describe('Variables', () => { beforeEach(() => { cy.signin({ email, password }); + cy.intercept('GET', '/rest/variables').as('loadVariables'); + cy.visit(variablesPage.url); + cy.wait(['@loadVariables', '@loadSettings']); }); it('should show the licensed action box when the feature is enabled', () => { diff --git a/cypress/e2e/3-default-owner.cy.ts b/cypress/e2e/3-default-owner.cy.ts index 1871dd9c6c..6aba65180c 100644 --- a/cypress/e2e/3-default-owner.cy.ts +++ b/cypress/e2e/3-default-owner.cy.ts @@ -84,11 +84,12 @@ describe('Default owner', () => { }); it('should be able to setup instance and migrate workflows and credentials', () => { - cy.setup({ email, firstName, lastName, password }); + cy.setup({ email, firstName, lastName, password }, true); messageBox.getters.content().should('contain.text', '1 existing workflow and 1 credential'); messageBox.actions.confirm(); + cy.wait('@setupRequest'); cy.url().should('include', settingsUsersPage.url); settingsSidebar.actions.back(); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 70d8a19882..d0e9ddbaa0 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -105,18 +105,22 @@ Cypress.Commands.add('signup', ({ firstName, lastName, password, url }) => { signupPage.getters.form().within(() => { cy.url().then((url) => { + cy.intercept('/rest/users/*').as('userSignup') signupPage.getters.firstName().type(firstName); signupPage.getters.lastName().type(lastName); signupPage.getters.password().type(password); signupPage.getters.submit().click(); + cy.wait('@userSignup'); }); }); }); -Cypress.Commands.add('setup', ({ email, firstName, lastName, password }) => { +Cypress.Commands.add('setup', ({ email, firstName, lastName, password }, skipIntercept = false) => { const signupPage = new SignupPage(); + cy.intercept('GET', signupPage.url).as('setupPage'); cy.visit(signupPage.url); + cy.wait('@setupPage'); signupPage.getters.form().within(() => { cy.url().then((url) => { @@ -125,7 +129,13 @@ Cypress.Commands.add('setup', ({ email, firstName, lastName, password }) => { signupPage.getters.firstName().type(firstName); signupPage.getters.lastName().type(lastName); signupPage.getters.password().type(password); + + cy.intercept('POST', '/rest/owner/setup').as('setupRequest'); signupPage.getters.submit().click(); + + if(!skipIntercept) { + cy.wait('@setupRequest'); + } } else { cy.log('User already signed up'); } @@ -168,7 +178,9 @@ Cypress.Commands.add('skipSetup', () => { const workflowPage = new WorkflowPage(); const Confirmation = new MessageBox(); + cy.intercept('GET', signupPage.url).as('setupPage'); cy.visit(signupPage.url); + cy.wait('@setupPage'); signupPage.getters.form().within(() => { cy.url().then((url) => { @@ -199,7 +211,11 @@ Cypress.Commands.add('setupOwner', (payload) => { }); Cypress.Commands.add('enableFeature', (feature) => { - cy.task('enable-feature', feature); + cy.task('set-feature', { feature, enabled: true }); +}); + +Cypress.Commands.add('disableFeature', (feature) => { + cy.task('set-feature', { feature, enabled: false }); }); Cypress.Commands.add('grantBrowserPermissions', (...permissions: string[]) => { diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 7665602daf..7b1b15db2b 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -36,13 +36,14 @@ declare global { signin(payload: SigninPayload): void; signout(): void; signup(payload: SignupPayload): void; - setup(payload: SetupPayload): void; + setup(payload: SetupPayload, skipIntercept?: boolean): void; setupOwner(payload: SetupPayload): void; inviteUsers(payload: InviteUsersPayload): void; interceptREST(method: string, url: string): Chainable; skipSetup(): void; resetAll(): void; enableFeature(feature: string): void; + disableFeature(feature: string): void; waitForLoad(waitForIntercepts?: boolean): void; grantBrowserPermissions(...permissions: string[]): void; readClipboard(): Chainable; diff --git a/packages/cli/src/api/e2e.api.ts b/packages/cli/src/api/e2e.api.ts index 73adae880d..2f9bb6ee1f 100644 --- a/packages/cli/src/api/e2e.api.ts +++ b/packages/cli/src/api/e2e.api.ts @@ -36,33 +36,30 @@ type Feature = keyof typeof enabledFeatures; Container.get(License).isFeatureEnabled = (feature: Feature) => enabledFeatures[feature] ?? false; -const tablesToTruncate = [ - 'auth_identity', - 'auth_provider_sync_history', - 'event_destinations', - 'shared_workflow', - 'shared_credentials', - 'webhook_entity', - 'workflows_tags', - 'credentials_entity', - 'tag_entity', - 'workflow_statistics', - 'workflow_entity', - 'execution_entity', - 'settings', - 'installed_packages', - 'installed_nodes', - 'user', - 'role', -]; +const tablesNotToTruncate = ['sqlite_sequence']; const truncateAll = async () => { const connection = Db.getConnection(); - for (const table of tablesToTruncate) { - await connection.query( - `DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`, - ); + const allTables: Array<{ name: string }> = await connection.query( + "SELECT name FROM sqlite_master WHERE type='table';", + ); + + // Disable foreign key constraint checks + await connection.query('PRAGMA foreign_keys = OFF;'); + + for (const { name: table } of allTables) { + try { + if (tablesNotToTruncate.includes(table)) continue; + await connection.query( + `DELETE FROM ${table}; DELETE FROM sqlite_sequence WHERE name=${table};`, + ); + } catch (error) { + console.warn('Dropping Table for E2E Reset error: ', error); + } } + + // Re-enable foreign key constraint checks + await connection.query('PRAGMA foreign_keys = ON;'); }; const setupUserManagement = async () => { @@ -139,8 +136,14 @@ e2eController.post('/db/setup-owner', bodyParser.json(), async (req, res) => { res.writeHead(204).end(); }); -e2eController.post('/enable-feature/:feature', async (req: Request<{ feature: Feature }>, res) => { - const { feature } = req.params; - enabledFeatures[feature] = true; - res.writeHead(204).end(); -}); +e2eController.patch( + '/feature/:feature', + bodyParser.json(), + async (req: Request<{ feature: Feature }>, res) => { + const { feature } = req.params; + const { enabled } = req.body; + + enabledFeatures[feature] = enabled === undefined || enabled === true; + res.writeHead(204).end(); + }, +);