From f8cec375ac112808b07f7e832d6d8b4068747a30 Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Mon, 6 May 2024 16:25:01 -0400 Subject: [PATCH 1/6] WIP Cypress test tags --- packages/manager/cypress.config.ts | 2 + packages/manager/cypress/support/e2e.ts | 2 + packages/manager/cypress/support/index.d.ts | 28 ++- .../support/plugins/test-tagging-info.ts | 36 ++++ .../cypress/support/setup/test-tagging.ts | 50 +++++ .../manager/cypress/support/util/arrays.ts | 11 ++ packages/manager/cypress/support/util/tag.ts | 174 ++++++++++++++++++ 7 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 packages/manager/cypress/support/plugins/test-tagging-info.ts create mode 100644 packages/manager/cypress/support/setup/test-tagging.ts create mode 100644 packages/manager/cypress/support/util/tag.ts diff --git a/packages/manager/cypress.config.ts b/packages/manager/cypress.config.ts index ff201ae504e..1c7efb41af8 100644 --- a/packages/manager/cypress.config.ts +++ b/packages/manager/cypress.config.ts @@ -14,6 +14,7 @@ import { fetchAccount } from './cypress/support/plugins/fetch-account'; import { fetchLinodeRegions } from './cypress/support/plugins/fetch-linode-regions'; import { splitCypressRun } from './cypress/support/plugins/split-run'; import { enableJunitReport } from './cypress/support/plugins/junit-report'; +import { logTestTagInfo } from './cypress/support/plugins/test-tagging-info'; /** * Exports a Cypress configuration object. @@ -66,6 +67,7 @@ export default defineConfig({ fetchAccount, fetchLinodeRegions, regionOverrideCheck, + logTestTagInfo, splitCypressRun, enableJunitReport, ]); diff --git a/packages/manager/cypress/support/e2e.ts b/packages/manager/cypress/support/e2e.ts index dc2dfc94cdf..36169150553 100644 --- a/packages/manager/cypress/support/e2e.ts +++ b/packages/manager/cypress/support/e2e.ts @@ -22,6 +22,8 @@ import 'cypress-real-events/support'; import './setup/defer-command'; import './setup/login-command'; import './setup/page-visit-tracking-commands'; +import './setup/test-tagging'; + chai.use(chaiString); chai.use(function (chai, utils) { diff --git a/packages/manager/cypress/support/index.d.ts b/packages/manager/cypress/support/index.d.ts index 1439322a740..2cae0aef3e6 100644 --- a/packages/manager/cypress/support/index.d.ts +++ b/packages/manager/cypress/support/index.d.ts @@ -1,9 +1,14 @@ import { Labelable } from './commands'; import type { LinodeVisitOptions } from './login.ts'; +import type { TestTag } from 'support/util/tag'; declare global { namespace Cypress { + interface Cypress { + mocha: Mocha; + } + interface Chainable { /** * Custom command to select DOM element by data-cy attribute. @@ -63,12 +68,33 @@ declare global { */ expectNewPageVisit(alias: string): Chainable<>; + /** + * Sets tags for the current runnable. + * + * Alias for `tag()` in `support/util/tag.ts`. + * + * @param tags - Tags to set for test or runnable. + */ + tag(...tags: TestTag[]): void; + + /** + * Adds tags for the given runnable. + * + * If tags have already been set (e.g. using a hook), this method will add + * the given tags in addition the tags that have already been set. + * + * Alias for `addTag()` in `support/util/tag.ts`. + * + * @param tags - Test tags. + */ + addTag(...tags: TestTag[]): void; + /** * Internal Cypress command to retrieve test state. * * @param state - Cypress internal state to retrieve. */ - state(state: string): any; + state(state?: string): any; } } } diff --git a/packages/manager/cypress/support/plugins/test-tagging-info.ts b/packages/manager/cypress/support/plugins/test-tagging-info.ts new file mode 100644 index 00000000000..f30782e035e --- /dev/null +++ b/packages/manager/cypress/support/plugins/test-tagging-info.ts @@ -0,0 +1,36 @@ +import { CypressPlugin } from './plugin'; +import { + validateQuery, + getHumanReadableQueryRules, + getQueryRules, +} from '../util/tag'; + +const envVarName = 'CY_TEST_TAGS'; +export const logTestTagInfo: CypressPlugin = (_on, config) => { + if (config.env[envVarName]) { + const query = config.env[envVarName]; + + if (!validateQuery(query)) { + throw `Failed to validate tag query '${query}'. Please double check the syntax of your query.`; + } + + const rules = getQueryRules(query); + + if (rules.length) { + console.info(`Running tests that satisfy tag query '${query}'.`); + console.info( + 'Running tests that satisfy all of the following tag rules:' + ); + + console.table( + getHumanReadableQueryRules(query).reduce( + (acc: {}, cur: string, index: number) => { + acc[index] = cur; + return acc; + }, + {} + ) + ); + } + } +}; diff --git a/packages/manager/cypress/support/setup/test-tagging.ts b/packages/manager/cypress/support/setup/test-tagging.ts new file mode 100644 index 00000000000..4b562dea301 --- /dev/null +++ b/packages/manager/cypress/support/setup/test-tagging.ts @@ -0,0 +1,50 @@ +/** + * @file Exposes the `tag` util from the `cy` object. + */ + +import { Runnable, Test } from 'mocha'; +import { tag, addTag } from 'support/util/tag'; +import { evaluateQuery, testTagMap } from 'support/util/tag'; + +// Expose tag utils from the `cy` object. +// Similar to `cy.state`, and unlike other functions exposed in `cy`, these do not +// queue Cypress commands. Instead, they modify the test tag map upon execution. +cy.tag = tag; +cy.addTag = addTag; + +const query = Cypress.env('CY_TEST_TAGS') ?? ''; + +Cypress.mocha.getRunner().on('hook end', () => { + console.log('THE HOOK HAS ENDED!'); +}); + +/** + * + */ +Cypress.on('test:before:run', (test: Test, _runnable: Runnable) => { + /* + * Looks for the first command that does not belong in a hook and evalutes tags. + * + * Waiting for the first command to begin executing ensure that test context is + * set up and that tags have been assigned to the test. + */ + const commandHandler = () => { + const context = cy.state('ctx'); + if (context && context.test?.type !== 'hook') { + //debugger; + const tags = context?.tags ?? []; + + if (!evaluateQuery(query, tags)) { + //debugger; + //test.pending = true; + //context.skip(); + context.skip(); + //Cypress.once('command:end', () => cy.state('runnable').ctx.skip()); + } + + Cypress.removeListener('command:start', commandHandler); + } + }; + + Cypress.on('command:start', commandHandler); +}); diff --git a/packages/manager/cypress/support/util/arrays.ts b/packages/manager/cypress/support/util/arrays.ts index 74ce77cdf9f..b713f115d69 100644 --- a/packages/manager/cypress/support/util/arrays.ts +++ b/packages/manager/cypress/support/util/arrays.ts @@ -32,3 +32,14 @@ export const shuffleArray = (unsortedArray: T[]): T[] => { .sort((a, b) => a.sort - b.sort) .map(({ value }) => value); }; + +/** + * Returns a copy of an array with duplicate items removed. + * + * @param array - Array from which to create de-duplicated array. + * + * @returns Copy of `array` with duplicate items removed. + */ +export const removeDuplicates = (array: T[]): T[] => { + return Array.from(new Set(array)); +}; diff --git a/packages/manager/cypress/support/util/tag.ts b/packages/manager/cypress/support/util/tag.ts new file mode 100644 index 00000000000..82b1665dd2e --- /dev/null +++ b/packages/manager/cypress/support/util/tag.ts @@ -0,0 +1,174 @@ +import type { Context } from 'mocha'; +import { removeDuplicates } from './arrays'; + +const queryRegex = /(?:-|\+)?([^\s]+)/g; + +/** + * Allowed test tags. + */ +export type TestTag = + // Feature-related tags. + // Used to identify tests which deal with a certain feature or features. + | 'feat:linodes' + | 'feat:placementGroups' + + // Purpose-related tags. + // Describes additional uses for which a test may serve. + // For example, a test which creates a Linode end-to-end could be useful for + // DC testing purposes even if that is not the primary purpose of the test. + | 'purpose:dcTesting' + | 'purpose:smokeTesting' + + // Method-related tags. + // Describe the way the tests operate -- either end-to-end using real API requests, + // or integration using mocked API requests. + | 'method:e2e' + | 'method:mock'; + +/** + * + */ +export const testTagMap: Map = new Map(); + +/** + * Extended Mocha context that contains a tags property. + * + * `Context` already allows for arbitrary key/value pairs, this type simply + * enforces the `tags` property as an optional array of strings. + */ +export type ExtendedMochaContext = Context & { + tags?: string[]; +}; + +/** + * Sets tags for the current runnable. + * + * @param tags - Test tags. + */ +export const tag = (...tags: TestTag[]) => { + const extendedMochaContext = cy.state('ctx') as ExtendedMochaContext; + + if (extendedMochaContext) { + extendedMochaContext.tags = removeDuplicates(tags); + } +}; + +/** + * Adds tags for the given runnable. + * + * If tags have already been set (e.g. using a hook), this method will add + * the given tags in addition the tags that have already been set. + * + * @param tags - Test tags. + */ +export const addTag = (...tags: TestTag[]) => { + const extendedMochaContext = cy.state('ctx') as ExtendedMochaContext; + + if (extendedMochaContext) { + extendedMochaContext.tags = removeDuplicates([ + ...(extendedMochaContext.tags || []), + ...tags, + ]); + } + // const test = cy.state('test'); + // if (test) { + // testTagMap.set(test.id, removeDuplicates([...testTagMap[test.id] || [], ...tags])); + // console.log(`Set tag for ${test.id} to ${removeDuplicates(tags).join(' ')}`); + // } +}; + +/** + * Returns a boolean indicating whether `query` is a valid test tag query. + * + * @param query - Test tag query string. + * + * @return `true` if `query` is valid, `false` otherwise. + */ +export const validateQuery = (query: string) => { + // An empty string is a special case. + if (query === '') { + return true; + } + const result = queryRegex.test(query); + queryRegex.lastIndex = 0; + return result; +}; + +/** + * Gets an array of individual query rules from a query string. + * + * @param query - Query string from which to get query rules. + * + * @example + * // Query for all Linode or Volume tests, which also test Placement Groups, + * // and which are not end-to-end. + * const query = '+feat:linode,feat:volumes feat:placementGroups -e2e' + * getQueryRules(query); + * // Expected output: ['+feat:linode,feat:volumes', '+feat:placementGroups', '-e2e'] + * + * @returns Array of query rule strings. + */ +export const getQueryRules = (query: string) => { + return (query.match(queryRegex) ?? []).map((rule: string) => { + if (!['-', '+'].includes(rule[0]) || rule.length === 1) { + return `+${rule}`; + } + return rule; + }); +}; + +/** + * Returns an array of human-readable query rules. + * + * This can be useful for presentation or debugging purposes. + */ +export const getHumanReadableQueryRules = (query: string) => { + return getQueryRules(query).map((queryRule: string) => { + const queryOperation = queryRule[0]; + const queryOperands = queryRule.slice(1).split(','); + + const operationName = queryOperation === '+' ? 'HAS' : 'DOES NOT HAVE'; + const tagNames = queryOperands.join(' OR '); + + return `${operationName} ${tagNames}`; + }); +}; + +/** + * Evaluates a query rule against an array of test tags. + * + * @param queryRule - Query rule against which to evaluate test tags. + * @param tags - Tags to evaluate. + * + * @returns `true` if tags satisfy the query rule, `false` otherwise. + */ +export const evaluateQueryRule = ( + queryRule: string, + tags: TestTag[] +): boolean => { + const queryOperation = queryRule[0]; // Either '-' or '+'. + const queryOperands = queryRule.slice(1).split(','); // The tags to check. + + return queryOperation === '+' + ? tags.some((tag) => queryOperands.includes(tag)) + : !tags.some((tag) => queryOperands.includes(tag)); +}; + +/** + * Evaluates a query against an array of test tags. + * + * Tags are considered to satisfy query if every query rule evaluates to `true`. + * + * @param query - Query against which to evaluate test tags. + * @param tags - Tags to evaluate. + * + * @returns `true` if tags satisfy query, `false` otherwise. + */ +export const evaluateQuery = (query: string, tags: TestTag[]): boolean => { + if (!validateQuery(query)) { + throw new Error(`Invalid test tag query '${query}'`); + } + return getQueryRules(query).every((queryRule) => + evaluateQueryRule(queryRule, tags) + ); +}; From 12859a8eb834110b1a1d3111277c28d0b65a81e0 Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Wed, 15 May 2024 14:40:08 -0400 Subject: [PATCH 2/6] Make `cy.defer` accept a Promise generator function instead of a Promise --- .../e2e/core/account/service-transfer.spec.ts | 4 +- .../account/third-party-access-tokens.spec.ts | 2 +- .../billing/smoke-billing-activity.spec.ts | 2 +- .../core/domains/smoke-clone-domain.spec.ts | 2 +- .../core/domains/smoke-delete-domain.spec.ts | 2 +- .../core/firewalls/create-firewall.spec.ts | 72 +++++------ .../core/firewalls/delete-firewall.spec.ts | 2 +- .../migrate-linode-with-firewall.spec.ts | 2 +- .../core/firewalls/update-firewall.spec.ts | 6 +- .../e2e/core/linodes/backup-linode.spec.ts | 4 +- .../e2e/core/linodes/clone-linode.spec.ts | 2 +- .../e2e/core/linodes/linode-config.spec.ts | 60 +++++---- .../e2e/core/linodes/rebuild-linode.spec.ts | 6 +- .../e2e/core/linodes/rescue-linode.spec.ts | 2 +- .../core/linodes/smoke-delete-linode.spec.ts | 8 +- .../core/linodes/switch-linode-state.spec.ts | 116 +++++++++--------- .../e2e/core/longview/longview.spec.ts | 2 +- .../core/objectStorage/access-key.e2e.spec.ts | 2 +- .../objectStorage/object-storage.e2e.spec.ts | 4 +- .../stackscripts/create-stackscripts.spec.ts | 2 +- .../smoke-community-stackscrips.spec.ts | 2 +- .../e2e/core/volumes/attach-volume.spec.ts | 8 +- .../e2e/core/volumes/clone-volume.spec.ts | 2 +- .../e2e/core/volumes/create-volume.spec.ts | 94 +++++++------- .../e2e/core/volumes/delete-volume.spec.ts | 2 +- .../e2e/core/volumes/resize-volume.spec.ts | 2 +- .../e2e/core/volumes/update-volume.spec.ts | 2 +- .../create-machine-image-from-linode.spec.ts | 2 +- .../update-delete-machine-image.spec.ts | 2 +- .../e2e/region/linodes/delete-linode.spec.ts | 2 +- .../e2e/region/linodes/update-linode.spec.ts | 4 +- packages/manager/cypress/support/index.d.ts | 2 +- .../cypress/support/setup/defer-command.ts | 4 +- .../manager/cypress/support/util/cleanup.ts | 4 +- 34 files changed, 223 insertions(+), 211 deletions(-) diff --git a/packages/manager/cypress/e2e/core/account/service-transfer.spec.ts b/packages/manager/cypress/e2e/core/account/service-transfer.spec.ts index 7a71dfe0fb9..659b9a34f95 100644 --- a/packages/manager/cypress/e2e/core/account/service-transfer.spec.ts +++ b/packages/manager/cypress/e2e/core/account/service-transfer.spec.ts @@ -175,7 +175,7 @@ describe('Account service transfers', () => { cy.wait(['@getTransfers', '@getTransfers', '@getTransfers']); // Confirm that pending transfers are displayed in "Pending Service Transfers" panel. - cy.defer(getProfile(), 'getting profile').then((profile: Profile) => { + cy.defer(() => getProfile(), 'getting profile').then((profile: Profile) => { const dateFormatOptions = { timezone: profile.timezone }; cy.get('[data-qa-panel="Pending Service Transfers"]') .should('be.visible') @@ -259,7 +259,7 @@ describe('Account service transfers', () => { return linode; }; - cy.defer(setupLinode(), 'creating and booting Linode').then( + cy.defer(() => setupLinode(), 'creating and booting Linode').then( (linode: Linode) => { interceptInitiateEntityTransfer().as('initiateTransfer'); diff --git a/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts b/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts index 153f8c85458..9e918bf9dcd 100644 --- a/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts +++ b/packages/manager/cypress/e2e/core/account/third-party-access-tokens.spec.ts @@ -38,7 +38,7 @@ describe('Third party access tokens', () => { .closest('tr') .within(() => { cy.findByText(token.label).should('be.visible'); - cy.defer(getProfile()).then((profile: Profile) => { + cy.defer(() => getProfile()).then((profile: Profile) => { const dateFormatOptions = { timezone: profile.timezone }; cy.findByText(formatDate(token.created, dateFormatOptions)).should( 'be.visible' diff --git a/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts b/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts index f130dae258c..1cdcbcb1b6c 100644 --- a/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts +++ b/packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts @@ -162,7 +162,7 @@ describe('Billing Activity Feed', () => { mockGetPayments(paymentMocks6Months).as('getPayments'); mockGetPaymentMethods([]); - cy.defer(getProfile()).then((profile: Profile) => { + cy.defer(() => getProfile()).then((profile: Profile) => { const timezone = profile.timezone; cy.visitWithLogin('/account/billing'); cy.wait(['@getInvoices', '@getPayments']); diff --git a/packages/manager/cypress/e2e/core/domains/smoke-clone-domain.spec.ts b/packages/manager/cypress/e2e/core/domains/smoke-clone-domain.spec.ts index f85218804e7..d45a2bf886d 100644 --- a/packages/manager/cypress/e2e/core/domains/smoke-clone-domain.spec.ts +++ b/packages/manager/cypress/e2e/core/domains/smoke-clone-domain.spec.ts @@ -34,7 +34,7 @@ describe('Clone a Domain', () => { const domainRecords = createDomainRecords(); - cy.defer(createDomain(domainRequest), 'creating domain').then( + cy.defer(() => createDomain(domainRequest), 'creating domain').then( (domain: Domain) => { // Add records to the domain. cy.visitWithLogin(`/domains/${domain.id}`); diff --git a/packages/manager/cypress/e2e/core/domains/smoke-delete-domain.spec.ts b/packages/manager/cypress/e2e/core/domains/smoke-delete-domain.spec.ts index 6a992e26b70..80d9b632aa2 100644 --- a/packages/manager/cypress/e2e/core/domains/smoke-delete-domain.spec.ts +++ b/packages/manager/cypress/e2e/core/domains/smoke-delete-domain.spec.ts @@ -20,7 +20,7 @@ describe('Delete a Domain', () => { group: 'test-group', }); - cy.defer(createDomain(domainRequest), 'creating domain').then( + cy.defer(() => createDomain(domainRequest), 'creating domain').then( (domain: Domain) => { cy.visitWithLogin('/domains'); 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 43a92c8c3ca..7fbf276d4b3 100644 --- a/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/create-firewall.spec.ts @@ -75,46 +75,48 @@ describe('create firewall', () => { label: randomLabel(), }; - cy.defer(createLinode(linodeRequest), 'creating Linode').then((linode) => { - interceptCreateFirewall().as('createFirewall'); - cy.visitWithLogin('/firewalls/create'); + cy.defer(() => createLinode(linodeRequest), 'creating Linode').then( + (linode) => { + interceptCreateFirewall().as('createFirewall'); + cy.visitWithLogin('/firewalls/create'); - ui.drawer - .findByTitle('Create Firewall') - .should('be.visible') - .within(() => { - // Fill out and submit firewall create form. - containsClick('Label').type(firewall.label); - cy.findByLabelText('Linodes') - .should('be.visible') - .click() - .type(linode.label); + ui.drawer + .findByTitle('Create Firewall') + .should('be.visible') + .within(() => { + // Fill out and submit firewall create form. + containsClick('Label').type(firewall.label); + 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(); - cy.findByLabelText('Linodes').should('be.visible').click(); + cy.findByLabelText('Linodes').should('be.visible').click(); - ui.buttonGroup - .findButtonByTitle('Create Firewall') - .should('be.visible') - .should('be.enabled') - .click(); - }); + ui.buttonGroup + .findButtonByTitle('Create Firewall') + .should('be.visible') + .should('be.enabled') + .click(); + }); - cy.wait('@createFirewall'); + cy.wait('@createFirewall'); - // 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(linode.label).should('be.visible'); - }); - }); + // 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(linode.label).should('be.visible'); + }); + } + ); }); }); diff --git a/packages/manager/cypress/e2e/core/firewalls/delete-firewall.spec.ts b/packages/manager/cypress/e2e/core/firewalls/delete-firewall.spec.ts index 323a42cd398..2cbedb29e5f 100644 --- a/packages/manager/cypress/e2e/core/firewalls/delete-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/delete-firewall.spec.ts @@ -23,7 +23,7 @@ describe('delete firewall', () => { label: randomLabel(), }); - cy.defer(createFirewall(firewallRequest), 'creating firewalls').then( + cy.defer(() => createFirewall(firewallRequest), 'creating firewalls').then( (firewall: Firewall) => { cy.visitWithLogin('/firewalls'); diff --git a/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts b/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts index 8763f5bd8fe..0e3b3b5a8d3 100644 --- a/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/migrate-linode-with-firewall.spec.ts @@ -144,7 +144,7 @@ describe('Migrate Linode With Firewall', () => { interceptGetFirewalls().as('getFirewalls'); // Create a Linode, then navigate to the Firewalls landing page. - cy.defer(createLinode(linodePayload)).then((linode: Linode) => { + cy.defer(() => createLinode(linodePayload)).then((linode: Linode) => { interceptMigrateLinode(linode.id).as('migrateLinode'); cy.visitWithLogin('/firewalls'); cy.wait('@getFirewalls'); 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 dbbde69165f..9ca48ad7fe0 100644 --- a/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/firewalls/update-firewall.spec.ts @@ -196,7 +196,7 @@ describe('update firewall', () => { }); cy.defer( - createLinodeAndFirewall(linodeRequest, firewallRequest), + () => createLinodeAndFirewall(linodeRequest, firewallRequest), 'creating Linode and firewall' ).then(([linode, firewall]) => { cy.visitWithLogin('/firewalls'); @@ -324,7 +324,7 @@ describe('update firewall', () => { }); cy.defer( - createLinodeAndFirewall(linodeRequest, firewallRequest), + () => createLinodeAndFirewall(linodeRequest, firewallRequest), 'creating Linode and firewall' ).then(([_linode, firewall]) => { cy.visitWithLogin('/firewalls'); @@ -420,7 +420,7 @@ describe('update firewall', () => { const newFirewallLabel = randomLabel(); cy.defer( - createLinodeAndFirewall(linodeRequest, firewallRequest), + () => createLinodeAndFirewall(linodeRequest, firewallRequest), 'creating Linode and firewall' ).then(([_linode, firewall]) => { cy.visitWithLogin('/firewalls'); diff --git a/packages/manager/cypress/e2e/core/linodes/backup-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/backup-linode.spec.ts index 01694c1fa4a..6e5d35d5d27 100644 --- a/packages/manager/cypress/e2e/core/linodes/backup-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/backup-linode.spec.ts @@ -53,7 +53,7 @@ describe('linode backups', () => { booted: false, }); - cy.defer(createLinode(createLinodeRequest), 'creating Linode').then( + cy.defer(() => createLinode(createLinodeRequest), 'creating Linode').then( (linode: Linode) => { interceptGetLinode(linode.id).as('getLinode'); interceptEnableLinodeBackups(linode.id).as('enableBackups'); @@ -116,7 +116,7 @@ describe('linode backups', () => { const snapshotName = randomLabel(); - cy.defer(createLinode(createLinodeRequest), 'creating Linode').then( + cy.defer(() => createLinode(createLinodeRequest), 'creating Linode').then( (linode: Linode) => { interceptGetLinode(linode.id).as('getLinode'); interceptCreateLinodeSnapshot(linode.id).as('createSnapshot'); diff --git a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts index 3e4901afb86..d8ef82ca77b 100644 --- a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts @@ -54,7 +54,7 @@ describe('clone linode', () => { const newLinodeLabel = `${linodePayload.label}-clone`; - cy.defer(createLinode(linodePayload)).then((linode: Linode) => { + cy.defer(() => createLinode(linodePayload)).then((linode: Linode) => { const linodeRegion = getRegionById(linodePayload.region!); interceptCloneLinode(linode.id).as('cloneLinode'); diff --git a/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts b/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts index 51b32e4ca54..967d9af5f2e 100644 --- a/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts @@ -80,7 +80,7 @@ describe('Linode Config management', () => { // Fetch Linode kernel data from the API. // We'll use this data in the tests to confirm that config labels are rendered correctly. - cy.defer(fetchAllKernels(), 'Fetching Linode kernels...').then( + cy.defer(fetchAllKernels, 'Fetching Linode kernels...').then( (fetchedKernels) => { kernels = fetchedKernels; } @@ -95,7 +95,7 @@ describe('Linode Config management', () => { */ it('Creates a config', () => { // Wait for Linode to be created for kernel data to be retrieved. - cy.defer(createTestLinode(), 'Creating Linode').then((linode: Linode) => { + cy.defer(createTestLinode, 'Creating Linode').then((linode: Linode) => { interceptCreateLinodeConfigs(linode.id).as('postLinodeConfigs'); interceptGetLinodeConfigs(linode.id).as('getLinodeConfigs'); @@ -103,16 +103,18 @@ describe('Linode Config management', () => { // Confirm that initial config is listed in Linode configurations table. cy.wait('@getLinodeConfigs'); - cy.defer(fetchLinodeConfigs(linode.id)).then((configs: Config[]) => { - cy.findByLabelText('List of Configurations').within(() => { - configs.forEach((config) => { - const kernel = findKernelById(kernels, config.kernel); - cy.findByText(`${config.label} – ${kernel.label}`).should( - 'be.visible' - ); + cy.defer(() => fetchLinodeConfigs(linode.id)).then( + (configs: Config[]) => { + cy.findByLabelText('List of Configurations').within(() => { + configs.forEach((config) => { + const kernel = findKernelById(kernels, config.kernel); + cy.findByText(`${config.label} – ${kernel.label}`).should( + 'be.visible' + ); + }); }); - }); - }); + } + ); // Add new configuration. cy.findByText('Add Configuration').click(); @@ -136,19 +138,23 @@ describe('Linode Config management', () => { // Confirm that new config and existing config are both listed. cy.wait('@getLinodeConfigs'); - cy.defer(fetchLinodeConfigs(linode.id)).then((configs: Config[]) => { - cy.findByLabelText('List of Configurations').within(() => { - configs.forEach((config) => { - const kernel = findKernelById(kernels, config.kernel); - cy.findByText(`${config.label} – ${kernel.label}`) - .should('be.visible') - .closest('tr') - .within(() => { - cy.findByText('eth0 – Public Internet').should('be.visible'); - }); + cy.defer(() => fetchLinodeConfigs(linode.id)).then( + (configs: Config[]) => { + cy.findByLabelText('List of Configurations').within(() => { + configs.forEach((config) => { + const kernel = findKernelById(kernels, config.kernel); + cy.findByText(`${config.label} – ${kernel.label}`) + .should('be.visible') + .closest('tr') + .within(() => { + cy.findByText('eth0 – Public Internet').should( + 'be.visible' + ); + }); + }); }); - }); - }); + } + ); }); }); @@ -174,7 +180,7 @@ describe('Linode Config management', () => { // Create a Linode and wait for its Config to be fetched before proceeding. cy.defer( - createLinodeAndGetConfig({ interfaces }, { waitForDisks: true }), + () => createLinodeAndGetConfig({ interfaces }, { waitForDisks: true }), 'creating a linode and getting its config' ).then(([linode, config]: [Linode, Config]) => { // Get kernel info for config. @@ -234,7 +240,7 @@ describe('Linode Config management', () => { */ it('Boots a config', () => { cy.defer( - createLinodeAndGetConfig(null, { waitForBoot: true }), + () => createLinodeAndGetConfig(null, { waitForBoot: true }), 'Creating and booting test Linode' ).then(([linode, config]: [Linode, Config]) => { const kernel = findKernelById(kernels, config.kernel); @@ -288,7 +294,7 @@ describe('Linode Config management', () => { // Create clone and source destination Linodes, then proceed with clone flow. cy.defer( - createCloneTestLinodes(), + createCloneTestLinodes, 'Waiting for 2 Linodes to be created' ).then(([sourceLinode, destLinode]: [Linode, Linode]) => { const kernel = findKernelById(kernels, 'linode/latest-64bit'); @@ -367,7 +373,7 @@ describe('Linode Config management', () => { */ it('Deletes a config', () => { cy.defer( - createLinodeAndGetConfig(), + createLinodeAndGetConfig, 'creating a linode and getting its config' ).then(([linode, config]: [Linode, Config]) => { // Get kernel info for config to be deleted. diff --git a/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts index 55b5e951243..04230444348 100644 --- a/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/rebuild-linode.spec.ts @@ -117,7 +117,7 @@ describe('rebuild linode', () => { region: chooseRegion().id, }); - cy.defer(createLinode(linodeCreatePayload), 'creating Linode').then( + cy.defer(() => createLinode(linodeCreatePayload), 'creating Linode').then( (linode: Linode) => { interceptRebuildLinode(linode.id).as('linodeRebuild'); @@ -171,7 +171,7 @@ describe('rebuild linode', () => { region: chooseRegion().id, }); - cy.defer(createLinode(linodeCreatePayload), 'creating Linode').then( + cy.defer(() => createLinode(linodeCreatePayload), 'creating Linode').then( (linode: Linode) => { interceptRebuildLinode(linode.id).as('linodeRebuild'); interceptGetStackScripts().as('getStackScripts'); @@ -250,7 +250,7 @@ describe('rebuild linode', () => { }; cy.defer( - createStackScriptAndLinode(stackScriptRequest, linodeRequest), + () => createStackScriptAndLinode(stackScriptRequest, linodeRequest), 'creating stackScript and linode' ).then(([stackScript, linode]) => { interceptRebuildLinode(linode.id).as('linodeRebuild'); diff --git a/packages/manager/cypress/e2e/core/linodes/rescue-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/rescue-linode.spec.ts index 2d62584aa8e..3e9c4b00c3b 100644 --- a/packages/manager/cypress/e2e/core/linodes/rescue-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/rescue-linode.spec.ts @@ -43,7 +43,7 @@ describe('Rescue Linodes', () => { region: chooseRegion().id, }); - cy.defer(createLinode(linodePayload), 'creating Linode').then( + cy.defer(() => createLinode(linodePayload), 'creating Linode').then( (linode: Linode) => { interceptGetLinodeDetails(linode.id).as('getLinode'); interceptRebootLinodeIntoRescueMode(linode.id).as( diff --git a/packages/manager/cypress/e2e/core/linodes/smoke-delete-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/smoke-delete-linode.spec.ts index debcc10bfbf..8d10f145cf4 100644 --- a/packages/manager/cypress/e2e/core/linodes/smoke-delete-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/smoke-delete-linode.spec.ts @@ -72,7 +72,7 @@ describe('delete linode', () => { const linodeCreatePayload = createLinodeRequestFactory.build({ label: randomLabel(), }); - cy.defer(createLinode(linodeCreatePayload)).then((linode) => { + cy.defer(() => createLinode(linodeCreatePayload)).then((linode) => { // catch delete request cy.intercept('DELETE', apiMatcher('linode/instances/*')).as( 'deleteLinode' @@ -121,7 +121,7 @@ describe('delete linode', () => { const linodeCreatePayload = createLinodeRequestFactory.build({ label: randomLabel(), }); - cy.defer(createLinode(linodeCreatePayload)).then((linode) => { + cy.defer(() => createLinode(linodeCreatePayload)).then((linode) => { // catch delete request cy.intercept('DELETE', apiMatcher('linode/instances/*')).as( 'deleteLinode' @@ -174,7 +174,7 @@ describe('delete linode', () => { const linodeCreatePayload = createLinodeRequestFactory.build({ label: randomLabel(), }); - cy.defer(createLinode(linodeCreatePayload)).then((linode) => { + cy.defer(() => createLinode(linodeCreatePayload)).then((linode) => { // catch delete request cy.intercept('DELETE', apiMatcher('linode/instances/*')).as( 'deleteLinode' @@ -238,7 +238,7 @@ describe('delete linode', () => { }).as('getAccountSettings'); cy.intercept('DELETE', apiMatcher('linode/instances/*')).as('deleteLinode'); - cy.defer(createTwoLinodes()).then(([linodeA, linodeB]) => { + cy.defer(createTwoLinodes).then(([linodeA, linodeB]) => { cy.visitWithLogin('/linodes', { preferenceOverrides }); cy.wait('@getAccountSettings'); getVisible('[data-qa-header="Linodes"]'); diff --git a/packages/manager/cypress/e2e/core/linodes/switch-linode-state.spec.ts b/packages/manager/cypress/e2e/core/linodes/switch-linode-state.spec.ts index 848aeb2fc87..6621f18780c 100644 --- a/packages/manager/cypress/e2e/core/linodes/switch-linode-state.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/switch-linode-state.spec.ts @@ -17,7 +17,7 @@ describe('switch linode state', () => { * - Does not wait for Linode to finish being shut down before succeeding. */ it('powers off a linode from landing page', () => { - cy.defer(createTestLinode()).then((linode: Linode) => { + cy.defer(createTestLinode).then((linode: Linode) => { cy.visitWithLogin('/linodes'); cy.get(`[data-qa-linode="${linode.label}"]`) .should('be.visible') @@ -58,7 +58,7 @@ describe('switch linode state', () => { * - Waits for Linode to fully shut down before succeeding. */ it('powers off a linode from details page', () => { - cy.defer(createTestLinode()).then((linode: Linode) => { + cy.defer(createTestLinode).then((linode: Linode) => { cy.visitWithLogin(`/linodes/${linode.id}`); cy.contains('RUNNING').should('be.visible'); cy.findByText(linode.label).should('be.visible'); @@ -86,39 +86,41 @@ describe('switch linode state', () => { * - Waits for Linode to finish booting up before succeeding. */ it('powers on a linode from landing page', () => { - cy.defer(createTestLinode({ booted: false })).then((linode: Linode) => { - cy.visitWithLogin('/linodes'); - cy.get(`[data-qa-linode="${linode.label}"]`) - .should('be.visible') - .within(() => { - cy.contains('Offline').should('be.visible'); - }); - - ui.actionMenu - .findByTitle(`Action menu for Linode ${linode.label}`) - .should('be.visible') - .click(); - - ui.actionMenuItem.findByTitle('Power On').should('be.visible').click(); - - ui.dialog - .findByTitle(`Power On Linode ${linode.label}?`) - .should('be.visible') - .within(() => { - ui.button - .findByTitle('Power On Linode') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - cy.get(`[data-qa-linode="${linode.label}"]`) - .should('be.visible') - .within(() => { - cy.contains('Booting').should('be.visible'); - cy.contains('Running', { timeout: 300000 }).should('be.visible'); - }); - }); + cy.defer(() => createTestLinode({ booted: false })).then( + (linode: Linode) => { + cy.visitWithLogin('/linodes'); + cy.get(`[data-qa-linode="${linode.label}"]`) + .should('be.visible') + .within(() => { + cy.contains('Offline').should('be.visible'); + }); + + ui.actionMenu + .findByTitle(`Action menu for Linode ${linode.label}`) + .should('be.visible') + .click(); + + ui.actionMenuItem.findByTitle('Power On').should('be.visible').click(); + + ui.dialog + .findByTitle(`Power On Linode ${linode.label}?`) + .should('be.visible') + .within(() => { + ui.button + .findByTitle('Power On Linode') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.get(`[data-qa-linode="${linode.label}"]`) + .should('be.visible') + .within(() => { + cy.contains('Booting').should('be.visible'); + cy.contains('Running', { timeout: 300000 }).should('be.visible'); + }); + } + ); }); /* @@ -128,25 +130,27 @@ describe('switch linode state', () => { * - Does not wait for Linode to finish booting up before succeeding. */ it('powers on a linode from details page', () => { - cy.defer(createTestLinode({ booted: false })).then((linode: Linode) => { - cy.visitWithLogin(`/linodes/${linode.id}`); - cy.contains('OFFLINE').should('be.visible'); - cy.findByText(linode.label).should('be.visible'); - - cy.findByText('Power On').should('be.visible').click(); - ui.dialog - .findByTitle(`Power On Linode ${linode.label}?`) - .should('be.visible') - .within(() => { - ui.button - .findByTitle('Power On Linode') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - cy.contains('BOOTING').should('be.visible'); - }); + cy.defer(() => createTestLinode({ booted: false })).then( + (linode: Linode) => { + cy.visitWithLogin(`/linodes/${linode.id}`); + cy.contains('OFFLINE').should('be.visible'); + cy.findByText(linode.label).should('be.visible'); + + cy.findByText('Power On').should('be.visible').click(); + ui.dialog + .findByTitle(`Power On Linode ${linode.label}?`) + .should('be.visible') + .within(() => { + ui.button + .findByTitle('Power On Linode') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + cy.contains('BOOTING').should('be.visible'); + } + ); }); /* @@ -156,7 +160,7 @@ describe('switch linode state', () => { * - Does not wait for Linode to finish rebooting before succeeding. */ it('reboots a linode from landing page', () => { - cy.defer(createTestLinode()).then((linode: Linode) => { + cy.defer(createTestLinode).then((linode: Linode) => { cy.visitWithLogin('/linodes'); cy.get(`[data-qa-linode="${linode.label}"]`) .should('be.visible') @@ -197,7 +201,7 @@ describe('switch linode state', () => { * - Waits for Linode to finish rebooting before succeeding. */ it('reboots a linode from details page', () => { - cy.defer(createTestLinode()).then((linode: Linode) => { + cy.defer(createTestLinode).then((linode: Linode) => { cy.visitWithLogin(`/linodes/${linode.id}`); cy.contains('RUNNING').should('be.visible'); cy.findByText(linode.label).should('be.visible'); diff --git a/packages/manager/cypress/e2e/core/longview/longview.spec.ts b/packages/manager/cypress/e2e/core/longview/longview.spec.ts index d45259e8a6a..6cbcb6afc37 100644 --- a/packages/manager/cypress/e2e/core/longview/longview.spec.ts +++ b/packages/manager/cypress/e2e/core/longview/longview.spec.ts @@ -131,7 +131,7 @@ describe('longview', () => { }; // Create Linode and Longview Client before loading Longview landing page. - cy.defer(createLinodeAndClient(), { + cy.defer(createLinodeAndClient, { label: 'Creating Linode and Longview Client...', timeout: linodeCreateTimeout, }).then(([linode, client]: [Linode, LongviewClient]) => { diff --git a/packages/manager/cypress/e2e/core/objectStorage/access-key.e2e.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/access-key.e2e.spec.ts index 3594b8d7eab..9d8ef3a7757 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/access-key.e2e.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/access-key.e2e.spec.ts @@ -126,7 +126,7 @@ describe('object storage access key end-to-end tests', () => { // Create a bucket before creating access key. cy.defer( - createBucket(bucketRequest), + () => createBucket(bucketRequest), 'creating Object Storage bucket' ).then(() => { const keyLabel = randomLabel(); diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts index c53fdf988c3..885e7cca2ff 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts @@ -216,7 +216,7 @@ describe('object storage end-to-end tests', () => { ]; cy.defer( - setUpBucket(bucketLabel, bucketCluster), + () => setUpBucket(bucketLabel, bucketCluster), 'creating Object Storage bucket' ).then(() => { interceptUploadBucketObjectS3( @@ -409,7 +409,7 @@ describe('object storage end-to-end tests', () => { const bucketAccessPage = `/object-storage/buckets/${bucketCluster}/${bucketLabel}/access`; cy.defer( - setUpBucket(bucketLabel, bucketCluster), + () => setUpBucket(bucketLabel, bucketCluster), 'creating Object Storage bucket' ).then(() => { interceptGetBucketAccess(bucketLabel, bucketCluster).as( diff --git a/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts b/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts index 32a32528f09..ae5faddf55a 100644 --- a/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts +++ b/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts @@ -307,7 +307,7 @@ describe('Create stackscripts', () => { interceptGetStackScripts().as('getStackScripts'); interceptCreateLinode().as('createLinode'); - cy.defer(createLinodeAndImage(), { + cy.defer(createLinodeAndImage, { label: 'creating Linode and Image', timeout: 360000, }).then((privateImage) => { diff --git a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscrips.spec.ts b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscrips.spec.ts index 2ebd072b40e..fc907689815 100644 --- a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscrips.spec.ts +++ b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscrips.spec.ts @@ -103,7 +103,7 @@ describe('Community Stackscripts integration tests', () => { cy.get('[data-qa-stackscript-empty-msg="true"]').should('not.exist'); cy.findByText('Automate deployment scripts').should('not.exist'); - cy.defer(getProfile(), 'getting profile').then((profile: Profile) => { + cy.defer(getProfile, 'getting profile').then((profile: Profile) => { const dateFormatOptionsLanding = { timezone: profile.timezone, displayTime: false, diff --git a/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts index 42adfdfd0ec..747438d5cde 100644 --- a/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts @@ -68,12 +68,10 @@ describe('volume attach and detach flows', () => { root_pass: randomString(32), }); - const entityPromise = Promise.all([ - createVolume(volumeRequest), - createLinode(linodeRequest), - ]); + const entityPromiseGenerator = () => + Promise.all([createVolume(volumeRequest), createLinode(linodeRequest)]); - cy.defer(entityPromise, 'creating Volume and Linode').then( + cy.defer(entityPromiseGenerator, 'creating Volume and Linode').then( ([volume, linode]: [Volume, Linode]) => { interceptAttachVolume(volume.id).as('attachVolume'); interceptGetLinodeConfigs(linode.id).as('getLinodeConfigs'); diff --git a/packages/manager/cypress/e2e/core/volumes/clone-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/clone-volume.spec.ts index 80918644c3c..f589c9b979b 100644 --- a/packages/manager/cypress/e2e/core/volumes/clone-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/clone-volume.spec.ts @@ -48,7 +48,7 @@ describe('volume clone flow', () => { const cloneVolumeLabel = randomLabel(); - cy.defer(createActiveVolume(volumeRequest), 'creating volume').then( + cy.defer(() => createActiveVolume(volumeRequest), 'creating volume').then( (volume: Volume) => { interceptCloneVolume(volume.id).as('cloneVolume'); cy.visitWithLogin('/volumes', { diff --git a/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts index 5304208c626..9b296518902 100644 --- a/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/create-volume.spec.ts @@ -85,54 +85,56 @@ describe('volume create flow', () => { regionLabel: region.label, }; - cy.defer(createLinode(linodeRequest), 'creating Linode').then((linode) => { - interceptCreateVolume().as('createVolume'); + cy.defer(() => createLinode(linodeRequest), 'creating Linode').then( + (linode) => { + interceptCreateVolume().as('createVolume'); - cy.visitWithLogin('/volumes/create', { - localStorageOverrides: pageSizeOverride, - }); - - // Fill out and submit volume create form. - containsClick('Label').type(volume.label); - containsClick('Size').type(`{selectall}{backspace}${volume.size}`); - ui.regionSelect.find().click().type(`${volume.region}{enter}`); - - cy.findByLabelText('Linode') - .should('be.visible') - .click() - .type(linode.label); - - ui.autocompletePopper - .findByTitle(linode.label) - .should('be.visible') - .click(); - - fbtClick('Create Volume'); - cy.wait('@createVolume'); - - // Confirm volume configuration drawer opens, then close it. - fbtVisible('Volume scheduled for creation.'); - getClick('[data-qa-close-drawer="true"]'); - - // Confirm that volume is listed on landing page with expected configuration. - cy.findByText(volume.label) - .closest('tr') - .within(() => { - cy.findByText(volume.label).should('be.visible'); - cy.findByText(`${volume.size} GB`).should('be.visible'); - cy.findByText(volume.regionLabel).should('be.visible'); - cy.findByText(linode.label).should('be.visible'); + cy.visitWithLogin('/volumes/create', { + localStorageOverrides: pageSizeOverride, }); - // Confirm that volume is listed on Linode 'Storage' details page. - cy.visitWithLogin(`/linodes/${linode.id}/storage`); - cy.findByText(volume.label) - .closest('tr') - .within(() => { - fbtVisible(volume.label); - fbtVisible(`${volume.size} GB`); - }); - }); + // Fill out and submit volume create form. + containsClick('Label').type(volume.label); + containsClick('Size').type(`{selectall}{backspace}${volume.size}`); + ui.regionSelect.find().click().type(`${volume.region}{enter}`); + + cy.findByLabelText('Linode') + .should('be.visible') + .click() + .type(linode.label); + + ui.autocompletePopper + .findByTitle(linode.label) + .should('be.visible') + .click(); + + fbtClick('Create Volume'); + cy.wait('@createVolume'); + + // Confirm volume configuration drawer opens, then close it. + fbtVisible('Volume scheduled for creation.'); + getClick('[data-qa-close-drawer="true"]'); + + // Confirm that volume is listed on landing page with expected configuration. + cy.findByText(volume.label) + .closest('tr') + .within(() => { + cy.findByText(volume.label).should('be.visible'); + cy.findByText(`${volume.size} GB`).should('be.visible'); + cy.findByText(volume.regionLabel).should('be.visible'); + cy.findByText(linode.label).should('be.visible'); + }); + + // Confirm that volume is listed on Linode 'Storage' details page. + cy.visitWithLogin(`/linodes/${linode.id}/storage`); + cy.findByText(volume.label) + .closest('tr') + .within(() => { + fbtVisible(volume.label); + fbtVisible(`${volume.size} GB`); + }); + } + ); }); /* @@ -147,7 +149,7 @@ describe('volume create flow', () => { region: chooseRegion().id, }); - cy.defer(createLinode(linodeRequest), 'creating Linode').then( + cy.defer(() => createLinode(linodeRequest), 'creating Linode').then( (linode: Linode) => { const volume = { label: randomLabel(), diff --git a/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts index 919eb54e383..fe441dcf865 100644 --- a/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts @@ -34,7 +34,7 @@ describe('volume delete flow', () => { region: chooseRegion().id, }); - cy.defer(createVolume(volumeRequest), 'creating volume').then( + cy.defer(() => createVolume(volumeRequest), 'creating volume').then( (volume: Volume) => { interceptDeleteVolume(volume.id).as('deleteVolume'); cy.visitWithLogin('/volumes', { diff --git a/packages/manager/cypress/e2e/core/volumes/resize-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/resize-volume.spec.ts index c755e7d5d5e..f1ef8a5ddb7 100644 --- a/packages/manager/cypress/e2e/core/volumes/resize-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/resize-volume.spec.ts @@ -51,7 +51,7 @@ describe('volume resize flow', () => { size: oldSize, }); - cy.defer(createActiveVolume(volumeRequest), 'creating Volume').then( + cy.defer(() => createActiveVolume(volumeRequest), 'creating Volume').then( (volume: Volume) => { interceptResizeVolume(volume.id).as('resizeVolume'); cy.visitWithLogin('/volumes', { diff --git a/packages/manager/cypress/e2e/core/volumes/update-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/update-volume.spec.ts index c2cfe7283f3..2980b3e6a22 100644 --- a/packages/manager/cypress/e2e/core/volumes/update-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/update-volume.spec.ts @@ -24,7 +24,7 @@ describe('volume update flow', () => { const newLabel = randomLabel(); const newTags = [randomLabel(5), randomLabel(5), randomLabel(5)]; - cy.defer(createVolume(volumeRequest), 'creating volume').then( + cy.defer(() => createVolume(volumeRequest), 'creating volume').then( (volume: Volume) => { cy.visitWithLogin('/volumes', { // Temporarily force volume table to show up to 100 results per page. diff --git a/packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts b/packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts index e324a416e03..9141de228e2 100644 --- a/packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts +++ b/packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts @@ -57,7 +57,7 @@ describe('Capture Machine Images', () => { }); cy.defer( - createAndBootLinode(linodePayload), + () => createAndBootLinode(linodePayload), 'creating and booting Linode' ).then(([linode, disk]: [Linode, Disk]) => { cy.visitWithLogin('/images/create/disk'); diff --git a/packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts b/packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts index f786773eaa2..e2f6dc2e65f 100644 --- a/packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts +++ b/packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts @@ -73,7 +73,7 @@ describe('Delete Machine Images', () => { // Wait for machine image to become ready, then begin test. cy.fixture('machine-images/test-image.gz', null).then( (imageFileContents) => { - cy.defer(uploadMachineImage(region, imageFileContents), { + cy.defer(() => uploadMachineImage(region, imageFileContents), { label: 'uploading Machine Image', timeout: imageUploadProcessingTimeout, }).then((image: Image) => { diff --git a/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts b/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts index 0c92f09ce01..0a457ade406 100644 --- a/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts +++ b/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts @@ -32,7 +32,7 @@ describeRegions('Delete Linodes', (region: Region) => { // Create a Linode before navigating to its details page to delete it. cy.defer( - createLinode(linodeCreatePayload), + () => createLinode(linodeCreatePayload), `creating Linode in ${region.label}` ).then((linode: Linode) => { interceptGetLinodeDetails(linode.id).as('getLinode'); diff --git a/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts b/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts index a62e9e7bdd5..355e0095da4 100644 --- a/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts +++ b/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts @@ -34,7 +34,7 @@ describeRegions('Can update Linodes', (region) => { */ it('can update a Linode label', () => { cy.defer( - createLinode(makeLinodePayload(region.id, true)), + () => createLinode(makeLinodePayload(region.id, true)), 'creating Linode' ).then((linode: Linode) => { const newLabel = randomLabel(); @@ -101,7 +101,7 @@ describeRegions('Can update Linodes', (region) => { return [linode, disks[0]]; }; - cy.defer(createLinodeAndGetDisk(), 'creating Linode').then( + cy.defer(createLinodeAndGetDisk, 'creating Linode').then( ([linode, disk]: [Linode, Disk]) => { // Navigate to Linode details page. interceptGetLinodeDetails(linode.id).as('getLinode'); diff --git a/packages/manager/cypress/support/index.d.ts b/packages/manager/cypress/support/index.d.ts index 2cae0aef3e6..b6c7a2e19e1 100644 --- a/packages/manager/cypress/support/index.d.ts +++ b/packages/manager/cypress/support/index.d.ts @@ -22,7 +22,7 @@ declare global { * @example cy.defer(new Promise('value')).then((val) => {...}) */ defer( - promise: Promise, + promiseGenerator: () => Promise, labelOrOptions?: | Partial | string diff --git a/packages/manager/cypress/support/setup/defer-command.ts b/packages/manager/cypress/support/setup/defer-command.ts index f5f34068674..a667d505030 100644 --- a/packages/manager/cypress/support/setup/defer-command.ts +++ b/packages/manager/cypress/support/setup/defer-command.ts @@ -162,7 +162,7 @@ Cypress.Commands.add( 'defer', { prevSubject: false }, ( - promise: Promise, + promiseGenerator: () => Promise, labelOrOptions?: | Partial | string @@ -205,7 +205,7 @@ Cypress.Commands.add( const wrapPromise = async (): Promise => { let result: T; try { - result = await promise; + result = await promiseGenerator(); } catch (e: any) { commandLog.error(e); // If we're getting rate limited, timeout for 15 seconds so that diff --git a/packages/manager/cypress/support/util/cleanup.ts b/packages/manager/cypress/support/util/cleanup.ts index b8260b6773d..0621107c4cf 100644 --- a/packages/manager/cypress/support/util/cleanup.ts +++ b/packages/manager/cypress/support/util/cleanup.ts @@ -65,7 +65,7 @@ const cleanUpMap: CleanUpMap = { */ export const cleanUp = (resources: CleanUpResource | CleanUpResource[]) => { const resourcesArray = Array.isArray(resources) ? resources : [resources]; - const promise = async () => { + const promiseGenerator = async () => { for (const resource of resourcesArray) { const cleanFunction = cleanUpMap[resource]; // Perform clean-up sequentially to avoid API rate limiting. @@ -74,7 +74,7 @@ export const cleanUp = (resources: CleanUpResource | CleanUpResource[]) => { } }; return cy.defer( - promise(), + promiseGenerator, `cleaning up test resources: ${resourcesArray.join(', ')}` ); }; From aa4f4741c112a0f424fb532945ee3091fb135c09 Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Wed, 15 May 2024 14:41:43 -0400 Subject: [PATCH 3/6] Slight improvements to test tag info output during Cypress start up --- .../manager/cypress/support/plugins/test-tagging-info.ts | 3 +-- packages/manager/cypress/support/util/tag.ts | 8 ++------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/manager/cypress/support/plugins/test-tagging-info.ts b/packages/manager/cypress/support/plugins/test-tagging-info.ts index f30782e035e..47aa97cd462 100644 --- a/packages/manager/cypress/support/plugins/test-tagging-info.ts +++ b/packages/manager/cypress/support/plugins/test-tagging-info.ts @@ -17,9 +17,8 @@ export const logTestTagInfo: CypressPlugin = (_on, config) => { const rules = getQueryRules(query); if (rules.length) { - console.info(`Running tests that satisfy tag query '${query}'.`); console.info( - 'Running tests that satisfy all of the following tag rules:' + `Running tests that satisfy all of the following tag rules for query '${query}':` ); console.table( diff --git a/packages/manager/cypress/support/util/tag.ts b/packages/manager/cypress/support/util/tag.ts index 82b1665dd2e..c7fa37f0a08 100644 --- a/packages/manager/cypress/support/util/tag.ts +++ b/packages/manager/cypress/support/util/tag.ts @@ -70,11 +70,6 @@ export const addTag = (...tags: TestTag[]) => { ...tags, ]); } - // const test = cy.state('test'); - // if (test) { - // testTagMap.set(test.id, removeDuplicates([...testTagMap[test.id] || [], ...tags])); - // console.log(`Set tag for ${test.id} to ${removeDuplicates(tags).join(' ')}`); - // } }; /** @@ -127,7 +122,8 @@ export const getHumanReadableQueryRules = (query: string) => { const queryOperation = queryRule[0]; const queryOperands = queryRule.slice(1).split(','); - const operationName = queryOperation === '+' ? 'HAS' : 'DOES NOT HAVE'; + const operationName = + queryOperation === '+' ? `HAS TAG` : `DOES NOT HAVE TAG`; const tagNames = queryOperands.join(' OR '); return `${operationName} ${tagNames}`; From a6be57180ffa9c35ab4109444fc5ad6a3d4e022c Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Wed, 15 May 2024 14:43:08 -0400 Subject: [PATCH 4/6] Misc clean up --- .../cypress/support/setup/test-tagging.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/manager/cypress/support/setup/test-tagging.ts b/packages/manager/cypress/support/setup/test-tagging.ts index 4b562dea301..5dbd48c8405 100644 --- a/packages/manager/cypress/support/setup/test-tagging.ts +++ b/packages/manager/cypress/support/setup/test-tagging.ts @@ -4,7 +4,7 @@ import { Runnable, Test } from 'mocha'; import { tag, addTag } from 'support/util/tag'; -import { evaluateQuery, testTagMap } from 'support/util/tag'; +import { evaluateQuery } from 'support/util/tag'; // Expose tag utils from the `cy` object. // Similar to `cy.state`, and unlike other functions exposed in `cy`, these do not @@ -14,32 +14,23 @@ cy.addTag = addTag; const query = Cypress.env('CY_TEST_TAGS') ?? ''; -Cypress.mocha.getRunner().on('hook end', () => { - console.log('THE HOOK HAS ENDED!'); -}); - /** * */ -Cypress.on('test:before:run', (test: Test, _runnable: Runnable) => { +Cypress.on('test:before:run', (_test: Test, _runnable: Runnable) => { /* * Looks for the first command that does not belong in a hook and evalutes tags. * - * Waiting for the first command to begin executing ensure that test context is - * set up and that tags have been assigned to the test. + * Waiting for the first command to begin executing ensures that test context + * is set up and that tags have been assigned to the test. */ const commandHandler = () => { const context = cy.state('ctx'); if (context && context.test?.type !== 'hook') { - //debugger; const tags = context?.tags ?? []; if (!evaluateQuery(query, tags)) { - //debugger; - //test.pending = true; - //context.skip(); context.skip(); - //Cypress.once('command:end', () => cy.state('runnable').ctx.skip()); } Cypress.removeListener('command:start', commandHandler); From 057f0f2473cb5381891d8a27a3814171f6f706ff Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Wed, 15 May 2024 14:49:43 -0400 Subject: [PATCH 5/6] Tag Linode create tests for demonstration purposes --- .../manager/cypress/e2e/core/linodes/create-linode.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts index a7d7a8c2fb9..1153826e752 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts @@ -131,6 +131,7 @@ describe('create linode', () => { }); it('creates a nanode', () => { + cy.tag('method:e2e', 'feat:linodes'); const rootpass = randomString(32); const linodeLabel = randomLabel(); // intercept request @@ -153,6 +154,7 @@ describe('create linode', () => { }); it('creates a linode via CLI', () => { + cy.tag('method:e2e', 'feat:linodes'); const linodeLabel = randomLabel(); const linodePass = randomString(32); const linodeRegion = chooseRegion(); @@ -228,6 +230,7 @@ describe('create linode', () => { * - Confirms that backups pricing is correct when selecting a region with a different price structure. */ it('shows DC-specific pricing information during create flow', () => { + cy.tag('method:mock', 'feat:linodes'); const rootpass = randomString(32); const linodeLabel = randomLabel(); const initialRegion = getRegionById('us-west'); @@ -334,6 +337,7 @@ describe('create linode', () => { }); it("prevents a VPC from being assigned in a region that doesn't support VPCs during the Linode Create flow", () => { + cy.tag('method:mock', 'feat:linodes'); const region: Region = getRegionById('us-southeast'); const mockNoVPCRegion = regionFactory.build({ id: region.id, @@ -376,6 +380,7 @@ describe('create linode', () => { }); it('assigns a VPC to the linode during create flow', () => { + cy.tag('method:mock', 'feat:linodes'); const rootpass = randomString(32); const linodeLabel = randomLabel(); const region: Region = getRegionById('us-southeast'); From c2f3097644d6709c13cb8b082f74f0fe1931af19 Mon Sep 17 00:00:00 2001 From: Joe D'Amore Date: Wed, 15 May 2024 17:00:24 -0400 Subject: [PATCH 6/6] Document `CY_TEST_TAGS` environment variable --- docs/development-guide/08-testing.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/development-guide/08-testing.md b/docs/development-guide/08-testing.md index caf87e86060..febe5b53519 100644 --- a/docs/development-guide/08-testing.md +++ b/docs/development-guide/08-testing.md @@ -186,9 +186,10 @@ These environment variables are specific to Cloud Manager UI tests. They can be ###### General Environment variables related to the general operation of the Cloud Manager Cypress tests. -| Environment Variable | Description | Example | Default | -|----------------------|-------------------------------------------------------------------------------------------------------|----------|---------------------------------| -| `CY_TEST_SUITE` | Name of the Cloud Manager UI test suite to run. Possible values are `core`, `region`, or `synthetic`. | `region` | Unset; defaults to `core` suite | +| Environment Variable | Description | Example | Default | +|----------------------|-------------------------------------------------------------------------------------------------------|--------------|---------------------------------| +| `CY_TEST_SUITE` | Name of the Cloud Manager UI test suite to run. Possible values are `core`, `region`, or `synthetic`. | `region` | Unset; defaults to `core` suite | +| `CY_TEST_TAGS` | Query identifying tests that should run by specifying allowed and disallowed tags. | `method:e2e` | Unset; all tests run by default | ###### Regions These environment variables are used by Cloud Manager's UI tests to override region selection behavior. This can be useful for testing Cloud Manager functionality against a specific region.