From 210b476465d36fb2db994406d1a39d5ac4c5fb5e Mon Sep 17 00:00:00 2001 From: jdamore-linode <97627410+jdamore-linode@users.noreply.github.com> Date: Thu, 22 Jun 2023 20:43:02 -0400 Subject: [PATCH] fix: Reduce Firewall create and update e2e test flakiness (#9298) * Click back into field to dismiss autocomplete popper * Fix issue with LinodeSelectV2 label/text field association, update test to find input by label * Clean up, click back into select field to dismiss autocomplete popper * Find button within group to avoid selecting drawer title * Offload interactions involving firewall creation to API * Wait for firewall create requests * Add intercepts and wait for Firewall update API requests to resolve * Added changeset: Fix LinodeSelectV2 label association * Fix flake involving Linode selection when capturing image --- .../.changeset/pr-9298-fixed-1687455527847.md | 5 + .../core/firewalls/create-firewall.spec.ts | 32 +- .../core/firewalls/update-firewall.spec.ts | 409 ++++++++++-------- .../core/images/smoke-create-image.spec.ts | 3 +- .../cypress/support/intercepts/firewalls.ts | 30 +- .../Linodes/LinodeSelect/LinodeSelectV2.tsx | 1 + 6 files changed, 274 insertions(+), 206 deletions(-) create mode 100644 packages/manager/.changeset/pr-9298-fixed-1687455527847.md diff --git a/packages/manager/.changeset/pr-9298-fixed-1687455527847.md b/packages/manager/.changeset/pr-9298-fixed-1687455527847.md new file mode 100644 index 00000000000..a334eebbff7 --- /dev/null +++ b/packages/manager/.changeset/pr-9298-fixed-1687455527847.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Fix LinodeSelectV2 label association ([#9298](https://github.com/linode/manager/pull/9298)) diff --git a/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts b/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts index 2a4a750f2b6..886c1050f3c 100644 --- a/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts @@ -33,8 +33,13 @@ describe('create firewall', () => { cy.findByText('Label is required.'); // Fill out and submit firewall create form. containsClick('Label').type(firewall.label); - getClick('[data-testid="create-firewall-submit"]'); + ui.buttonGroup + .findButtonByTitle('Create Firewall') + .should('be.visible') + .should('be.enabled') + .click(); }); + cy.wait('@createFirewall'); // Confirm that firewall is listed on landing page with expected configuration. cy.findByText(firewall.label) @@ -75,24 +80,27 @@ describe('create firewall', () => { .within(() => { // Fill out and submit firewall create form. containsClick('Label').type(firewall.label); - cy.get('[data-testid="textfield-input"]:last') + cy.findByLabelText('Linodes') .should('be.visible') .click() .type(linode.label); - }); - ui.autocompletePopper - .findByTitle(linode.label) - .should('be.visible') - .click(); + ui.autocompletePopper + .findByTitle(linode.label) + .should('be.visible') + .click(); - ui.drawer - .findByTitle('Create Firewall') - .should('be.visible') - .within(() => { - getClick('[data-testid="create-firewall-submit"]'); + cy.findByLabelText('Linodes').should('be.visible').click(); + + ui.buttonGroup + .findButtonByTitle('Create Firewall') + .should('be.visible') + .should('be.enabled') + .click(); }); + cy.wait('@createFirewall'); + // Confirm that firewall is listed on landing page with expected configuration. cy.findByText(firewall.label) .closest('tr') diff --git a/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts b/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts index 8c4228f6643..73fe702a565 100644 --- a/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts @@ -1,10 +1,23 @@ -import { createLinode } from '@linode/api-v4/lib/linodes'; -import { createLinodeRequestFactory } from 'src/factories/linodes'; +import { + createLinode, + createFirewall, + Linode, + Firewall, + FirewallRuleType, +} from '@linode/api-v4'; +import { + createLinodeRequestFactory, + firewallFactory, + firewallRuleFactory, + linodeFactory, +} from 'src/factories'; import { authenticate } from 'support/api/authentication'; import { containsClick, getClick } from 'support/helpers'; -import { Linode, Firewall, FirewallRuleType } from '@linode/api-v4/types'; -import { firewallFactory, firewallRuleFactory } from 'src/factories/firewalls'; -import { interceptCreateFirewall } from 'support/intercepts/firewalls'; +import { + interceptCreateFirewall, + interceptUpdateFirewallLinodes, + interceptUpdateFirewallRules, +} from 'support/intercepts/firewalls'; import { randomItem, randomString, randomLabel } from 'support/util/random'; import { fbtVisible, fbtClick } from 'support/helpers'; import { ui } from 'support/ui'; @@ -123,7 +136,7 @@ const addLinodesToFirewall = (firewall: Firewall, linode: Linode) => { .should('be.visible') .within(() => { // Fill out and submit firewall edit form. - cy.get('[data-testid="textfield-input"]:last') + cy.findByLabelText('Linodes') .should('be.visible') .click() .type(linode.label); @@ -133,10 +146,22 @@ const addLinodesToFirewall = (firewall: Firewall, linode: Linode) => { .should('be.visible') .click(); + cy.findByLabelText('Linodes').should('be.visible').click(); + ui.button.findByTitle('Add').should('be.visible').click(); }); }; +const createLinodeAndFirewall = async ( + linodeRequestPayload, + firewallRequestPayload +) => { + return Promise.all([ + createLinode(linodeRequestPayload), + createFirewall(firewallRequestPayload), + ]); +}; + authenticate(); describe('update firewall', () => { /* @@ -154,207 +179,209 @@ describe('update firewall', () => { root_pass: randomString(16), }); - firewall.label = randomLabel(); + const firewallRequest = firewallFactory.build({ + label: randomLabel(), + region: region.id, + rules: firewallRuleFactory.build({ + inbound: [], + outbound: [], + }), + }); - cy.defer(createLinode(linodeRequest)).then((linode) => { - interceptCreateFirewall().as('createFirewall'); - cy.visitWithLogin('/firewalls/create'); + cy.defer(createLinodeAndFirewall(linodeRequest, firewallRequest)).then( + ([linode, firewall]) => { + cy.visitWithLogin('/firewalls'); - ui.drawer - .findByTitle('Create Firewall') - .should('be.visible') - .within(() => { - // Fill out and submit firewall create form. - containsClick('Label').type(firewall.label); - getClick('[data-testid="create-firewall-submit"]'); - }); - - // Confirm that firewall is listed on landing page with expected configuration. - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText(firewall.label).should('be.visible'); - cy.findByText('Enabled').should('be.visible'); - cy.findByText('No rules').should('be.visible'); - cy.findByText('None assigned').should('be.visible'); - }); - - // Go to the firewalls edit page - cy.findByText(firewall.label).click(); - - // In Rules tab, add inbound rules - addFirewallRules(inboundRule, 'inbound'); - - // Confirm that the inbound rules are listed on edit page with expected configuration - cy.get('[data-rbd-droppable-context-id="0"]') - .should('be.visible') - .within(() => { - cy.findByText(inboundRule.label).should('be.visible'); - cy.findByText(inboundRule.protocol).should('be.visible'); - cy.findByText(inboundRule.ports).should('be.visible'); - cy.findByText(inboundRule.action).should('be.visible'); - }); - - // Add outbound rules - addFirewallRules(outboundRule, 'outbound'); - - // Confirm that the outbound rules are listed on edit page with expected configuration - cy.get('[data-rbd-droppable-context-id="1"]') - .should('be.visible') - .within(() => { - cy.findByText(outboundRule.label).should('be.visible'); - cy.findByText(outboundRule.protocol).should('be.visible'); - cy.findByText(outboundRule.ports).should('be.visible'); - cy.findByText(outboundRule.action).should('be.visible'); - }); - - // Save configuration - ui.button - .findByTitle('Save Changes') - .should('be.visible') - .should('be.enabled') - .click(); + // Confirm that firewall is listed on landing page with expected configuration. + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText(firewall.label).should('be.visible'); + cy.findByText('Enabled').should('be.visible'); + cy.findByText('No rules').should('be.visible'); + cy.findByText('None assigned').should('be.visible'); + }); - // Go back to landing page and check rules are added to the firewall - cy.wait(100); - cy.visitWithLogin('/firewalls'); - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText('1 Inbound / 1 Outbound').should('be.visible'); - }); + // Go to the firewalls edit page + cy.findByText(firewall.label).click(); - // Go to the firewalls edit page - cy.findByText(firewall.label).click(); + // In Rules tab, add inbound rules + addFirewallRules(inboundRule, 'inbound'); - // Remove inbound rules - removeFirewallRules(inboundRule.label); + // Confirm that the inbound rules are listed on edit page with expected configuration + cy.get('[data-rbd-droppable-context-id="0"]') + .should('be.visible') + .within(() => { + cy.findByText(inboundRule.label).should('be.visible'); + cy.findByText(inboundRule.protocol).should('be.visible'); + cy.findByText(inboundRule.ports).should('be.visible'); + cy.findByText(inboundRule.action).should('be.visible'); + }); + + // Add outbound rules + addFirewallRules(outboundRule, 'outbound'); + + // Confirm that the outbound rules are listed on edit page with expected configuration + cy.get('[data-rbd-droppable-context-id="1"]') + .should('be.visible') + .within(() => { + cy.findByText(outboundRule.label).should('be.visible'); + cy.findByText(outboundRule.protocol).should('be.visible'); + cy.findByText(outboundRule.ports).should('be.visible'); + cy.findByText(outboundRule.action).should('be.visible'); + }); + + // Save configuration + interceptUpdateFirewallRules(firewall.id).as('updateFirewallRules'); + ui.button + .findByTitle('Save Changes') + .should('be.visible') + .should('be.enabled') + .click(); - // Remove outbound rules - removeFirewallRules(outboundRule.label); + // Go back to landing page and check rules are added to the firewall + cy.wait('@updateFirewallRules'); + cy.visitWithLogin('/firewalls'); + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText('1 Inbound / 1 Outbound').should('be.visible'); + }); - // Save configuration - ui.button - .findByTitle('Save Changes') - .should('be.visible') - .should('be.enabled') - .click(); + // Go to the firewalls edit page + cy.findByText(firewall.label).click(); - // Go back to landing page and check rules are removed to the firewall - cy.wait(100); - cy.visitWithLogin('/firewalls'); - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText('No rules').should('be.visible'); - }); - - // Go to the firewalls edit page - cy.findByText(firewall.label).click(); - - // Confirm that the firewall can be assigned to the linode - addLinodesToFirewall(firewall, linode); - cy.visitWithLogin('/firewalls'); - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText(linode.label).should('be.visible'); - }); - }); + // Remove inbound rules + removeFirewallRules(inboundRule.label); + + // Remove outbound rules + removeFirewallRules(outboundRule.label); + + // Save configuration + interceptUpdateFirewallRules(firewall.id).as('updateFirewallRules'); + ui.button + .findByTitle('Save Changes') + .should('be.visible') + .should('be.enabled') + .click(); + + // Go back to landing page and check rules are removed to the firewall + cy.wait('@updateFirewallRules'); + cy.visitWithLogin('/firewalls'); + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText('No rules').should('be.visible'); + }); + + // Go to the firewalls edit page + cy.findByText(firewall.label).click(); + + // Confirm that the firewall can be assigned to the linode + interceptUpdateFirewallLinodes(firewall.id).as('updateFirewallLinodes'); + addLinodesToFirewall(firewall, linode); + cy.wait('@updateFirewallLinodes'); + cy.visitWithLogin('/firewalls'); + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText(linode.label).should('be.visible'); + }); + } + ); }); /* * - Confirms that firewall shows the correct status when it is disabled. */ it("updates a firewall's status", () => { - const firewall = { + const region = chooseRegion(); + + const linodeRequest = linodeFactory.build({ + label: randomLabel(), + region: region.id, + root_pass: randomString(16), + }); + + const firewallRequest = firewallFactory.build({ label: randomLabel(), - region: chooseRegion().id, - }; - - interceptCreateFirewall().as('createFirewall'); - cy.visitWithLogin('/firewalls/create'); - - ui.drawer - .findByTitle('Create Firewall') - .should('be.visible') - .within(() => { - // An error message appears when attempting to create a Firewall without a label - getClick('[data-testid="create-firewall-submit"]'); - cy.findByText('Label is required.'); - // Fill out and submit firewall create form. - containsClick('Label').type(firewall.label); - getClick('[data-testid="create-firewall-submit"]'); - }); - - cy.visitWithLogin('/firewalls'); - - // Confirm that firewall is listed on landing page with expected configuration. - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText(firewall.label).should('be.visible'); - cy.findByText('Enabled').should('be.visible'); - cy.findByText('No rules').should('be.visible'); - cy.findByText('None assigned').should('be.visible'); - }); - - // Click 'disable' button and confirms - cy.findByText(firewall.label) - .should('be.visible') - .closest('tr') - .within(() => { - fbtVisible('Disable'); - fbtClick('Disable'); - }); - - // Confirm disabling. - ui.dialog - .findByTitle(`Disable Firewall ${firewall.label}?`) - .should('be.visible') - .within(() => { - ui.buttonGroup - .findButtonByTitle('Disable Firewall') + region: region.id, + rules: { + inbound: [], + outbound: [], + }, + }); + + cy.defer(createLinodeAndFirewall(linodeRequest, firewallRequest)).then( + ([_linode, firewall]) => { + cy.visitWithLogin('/firewalls'); + + // Confirm that firewall is listed on landing page with expected configuration. + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText(firewall.label).should('be.visible'); + cy.findByText('Enabled').should('be.visible'); + cy.findByText('No rules').should('be.visible'); + cy.findByText('None assigned').should('be.visible'); + }); + + // Click 'Disable' button and confirm action. + cy.findByText(firewall.label) .should('be.visible') - .should('be.enabled') - .click(); - }); - - // The firewall status changes to 'Disabled' - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText('Disabled').should('be.visible'); - }); - - cy.visitWithLogin('/firewalls'); - - // Click 'enable' button and confirms - cy.findByText(firewall.label) - .should('be.visible') - .closest('tr') - .within(() => { - fbtVisible('Enable'); - fbtClick('Enable'); - }); - - // Confirm enabing. - ui.dialog - .findByTitle(`Enable Firewall ${firewall.label}?`) - .should('be.visible') - .within(() => { - ui.buttonGroup - .findButtonByTitle('Enable Firewall') + .closest('tr') + .within(() => { + fbtVisible('Disable'); + fbtClick('Disable'); + }); + + ui.dialog + .findByTitle(`Disable Firewall ${firewall.label}?`) .should('be.visible') - .should('be.enabled') - .click(); - }); - - // The firewall status changes to 'Enabled' - cy.findByText(firewall.label) - .closest('tr') - .within(() => { - cy.findByText('Enabled').should('be.visible'); - }); + .within(() => { + ui.buttonGroup + .findButtonByTitle('Disable Firewall') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // Confirm status is updated on landing page. + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText('Disabled').should('be.visible'); + }); + + cy.visitWithLogin('/firewalls'); + + // Click 'Enable' button and confirm action. + cy.findByText(firewall.label) + .should('be.visible') + .closest('tr') + .within(() => { + fbtVisible('Enable'); + fbtClick('Enable'); + }); + + ui.dialog + .findByTitle(`Enable Firewall ${firewall.label}?`) + .should('be.visible') + .within(() => { + ui.buttonGroup + .findButtonByTitle('Enable Firewall') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // Confirm status is updated on landing page. + cy.findByText(firewall.label) + .closest('tr') + .within(() => { + cy.findByText('Enabled').should('be.visible'); + }); + } + ); }); }); diff --git a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts index db70ba8d643..2445fefe61c 100644 --- a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts +++ b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts @@ -70,9 +70,8 @@ describe('create image', () => { cy.wait('@getImages'); cy.findByText('Create Image').click(); - cy.get('input[placeholder="Select a Linode"]').click(); + cy.findByLabelText('Linodes').click(); cy.findByText(linode.label).click(); - cy.get('input[placeholder="Select a Linode"]').click(); cy.wait('@getDisks'); cy.contains('Select a Disk').click().type(`${diskLabel}{enter}`); cy.findAllByLabelText('Label', { exact: false }).type( diff --git a/packages/manager/cypress/support/intercepts/firewalls.ts b/packages/manager/cypress/support/intercepts/firewalls.ts index e46cd6b545d..2ac4d93c69b 100644 --- a/packages/manager/cypress/support/intercepts/firewalls.ts +++ b/packages/manager/cypress/support/intercepts/firewalls.ts @@ -10,5 +10,33 @@ import { apiMatcher } from 'support/util/intercepts'; * @returns Cypress chainable. */ export const interceptCreateFirewall = (): Cypress.Chainable => { - return cy.intercept('POST', apiMatcher('firewalls')); + return cy.intercept('POST', apiMatcher('networking/firewalls')); +}; + +/** + * Intercepts PUT request to update a Firewall's rules. + * + * @returns Cypress chainable. + */ +export const interceptUpdateFirewallRules = ( + firewallId: number +): Cypress.Chainable => { + return cy.intercept( + 'PUT', + apiMatcher(`networking/firewalls/${firewallId}/rules`) + ); +}; + +/** + * Intercepts POST request to update a Firewall's Linodes. + * + * @returns Cypress chainable. + */ +export const interceptUpdateFirewallLinodes = ( + firewallId: number +): Cypress.Chainable => { + return cy.intercept( + 'POST', + apiMatcher(`networking/firewalls/${firewallId}/devices`) + ); }; diff --git a/packages/manager/src/features/Linodes/LinodeSelect/LinodeSelectV2.tsx b/packages/manager/src/features/Linodes/LinodeSelect/LinodeSelectV2.tsx index 127b5423fe8..2c0ef9275fc 100644 --- a/packages/manager/src/features/Linodes/LinodeSelect/LinodeSelectV2.tsx +++ b/packages/manager/src/features/Linodes/LinodeSelect/LinodeSelectV2.tsx @@ -118,6 +118,7 @@ export const LinodeSelectV2 = ( renderInput={(params) => (