From 4a0f9491ae6d6803bc4a8df8101227e56d3f3e7e Mon Sep 17 00:00:00 2001 From: dmcintyr-akamai Date: Tue, 4 Mar 2025 10:26:06 -0500 Subject: [PATCH 1/3] M3-8628 delete region test suite --- .../create-machine-image-from-linode.spec.ts | 80 ---------- .../update-delete-machine-image.spec.ts | 149 ------------------ .../images/upload-machine-image.spec.ts | 69 -------- .../e2e/region/linodes/create-linode.spec.ts | 46 ------ .../e2e/region/linodes/delete-linode.spec.ts | 75 --------- .../e2e/region/linodes/update-linode.spec.ts | 148 ----------------- 6 files changed, 567 deletions(-) delete mode 100644 packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts delete mode 100644 packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts delete mode 100644 packages/manager/cypress/e2e/region/images/upload-machine-image.spec.ts delete mode 100644 packages/manager/cypress/e2e/region/linodes/create-linode.spec.ts delete mode 100644 packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts delete mode 100644 packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts 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 deleted file mode 100644 index 5b883defa24..00000000000 --- a/packages/manager/cypress/e2e/region/images/create-machine-image-from-linode.spec.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Disk, Linode } from '@linode/api-v4'; -import { createTestLinode } from 'support/util/linodes'; -import { createLinodeRequestFactory } from '@src/factories'; -import { authenticate } from 'support/api/authentication'; -import { imageCaptureProcessingTimeout } from 'support/constants/images'; -import { ui } from 'support/ui'; -import { cleanUp } from 'support/util/cleanup'; -import { randomLabel, randomPhrase, randomString } from 'support/util/random'; -import { testRegions } from 'support/util/regions'; - -authenticate(); -describe('Capture Machine Images', () => { - before(() => { - cleanUp(['images', 'linodes']); - }); - - /* - * - Captures a machine image from a Linode in the targeted region. - * - Confirms that user is redirected to landing page upon image capture. - * - Confirms that user is shown toast notifications related to the image's status. - * - Confirms that the image finishes processing successfully. - */ - testRegions('can capture a Machine Image from a Linode', (region) => { - const imageLabel = randomLabel(); - const imageDescription = randomPhrase(); - - const linodePayload = createLinodeRequestFactory.build({ - label: randomLabel(), - root_pass: randomString(32), - region: region.id, - booted: true, - }); - - cy.defer( - () => createTestLinode(linodePayload, { waitForBoot: true }), - 'creating and booting Linode' - ).then(([linode, disk]: [Linode, Disk]) => { - cy.visitWithLogin('/images/create/disk'); - - // Select Linode that we just created via the API. - cy.findByLabelText('Linode').should('be.visible').click(); - ui.autocompletePopper.findByTitle(linode.label).click(); - - // Select the Linode's disk. - cy.contains('Select a Disk').click(); - cy.focused().type(disk.label); - ui.autocompletePopper.findByTitle(disk.label).click(); - - // Specify a label and description for the captured image, click submit. - cy.findByLabelText('Label').should('be.visible').click(); - cy.focused().type(imageLabel); - - cy.findByLabelText('Description').should('be.visible').click(); - cy.focused().type(imageDescription); - - ui.button - .findByTitle('Create Image') - .should('be.visible') - .should('be.enabled') - .click(); - - // Confirm redirect back to landing page and that new image is listed. - cy.url().should('endWith', '/images'); - ui.toast.assertMessage('Image scheduled for creation.'); - cy.findByText(imageLabel).should('be.visible'); - - // Confirm that image capture finishes successfully. - ui.toast.assertMessage(`Image ${imageLabel} created successfully.`, { - timeout: imageCaptureProcessingTimeout, - }); - - cy.findByText(imageLabel) - .should('be.visible') - .closest('tr') - .within(() => { - cy.findByText('Ready').should('be.visible'); - }); - }); - }); -}); 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 deleted file mode 100644 index 625c842c0fc..00000000000 --- a/packages/manager/cypress/e2e/region/images/update-delete-machine-image.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @file Image update and deletion region tests. - */ - -import type { Image, Region } from '@linode/api-v4'; -import { uploadImage } from '@linode/api-v4'; -import { authenticate } from 'support/api/authentication'; -import { imageUploadProcessingTimeout } from 'support/constants/images'; -import { ui } from 'support/ui'; -import { SimpleBackoffMethod } from 'support/util/backoff'; -import { cleanUp } from 'support/util/cleanup'; -import { pollImageStatus } from 'support/util/polling'; -import { randomLabel, randomPhrase } from 'support/util/random'; -import { testRegions } from 'support/util/regions'; - -/** - * Uploads a machine image and waits for it to become available. - * - * See Linode API v4 documentation for more information. - * - * @link https://techdocs.akamai.com/linode-api/reference/post-upload-image - * - * @param region - Image upload region. - * @param data - Data to upload. - * - * @returns Promise that resolves to uploaded Image object. - */ -const uploadMachineImage = async (region: Region, data: Blob) => { - const uploadResponse = await uploadImage({ - label: randomLabel(), - region: region.id, - }); - - const [endpoint, image] = [uploadResponse.upload_to, uploadResponse.image]; - await fetch(endpoint, { - method: 'PUT', - body: data, - headers: { - 'Content-type': 'application/octet-stream', - }, - }); - - await pollImageStatus( - image.id, - 'available', - new SimpleBackoffMethod(5000, { - initialDelay: 20000, - maxAttempts: 20, - }) - ); - - return image; -}; - -authenticate(); -describe('Delete Machine Images', () => { - before(() => { - cleanUp('images'); - }); - - /* - * - Updates and deletes a Machine Image for the targeted region. - * - Confirms that Image label and description can be updated. - * - Confirms that landing page content changes to reflect updated Image. - * - Confirms that Image can be deleted. - * - Confirms that deleted Image is removed from the landing page. - */ - testRegions('can update and delete a Machine Image', (region) => { - const newLabel = randomLabel(); - const newDescription = randomPhrase(); - - // Upload a machine image using the `test-image.gz` fixture. - // 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), { - label: 'uploading Machine Image', - timeout: imageUploadProcessingTimeout, - }).then((image: Image) => { - // Navigate to Images landing page, find Image and click its Edit menu item. - cy.visitWithLogin('/images'); - cy.findByText(image.label) - .should('be.visible') - .closest('tr') - .within(() => { - cy.findByText('Ready').should('be.visible'); - ui.actionMenu - .findByTitle(`Action menu for Image ${image.label}`) - .should('be.visible') - .click(); - }); - - ui.actionMenuItem.findByTitle('Edit').should('be.visible').click(); - - // Update Image label and description. - ui.drawer - .findByTitle('Edit Image') - .should('be.visible') - .within(() => { - cy.findByLabelText('Label').should('be.visible').click(); - cy.focused().clear(); - cy.focused().type(newLabel); - - cy.findByLabelText('Description').should('be.visible').click(); - cy.focused().clear(); - cy.focused().type(newDescription); - - ui.button - .findByTitle('Save Changes') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - // Confirm that new label is shown on landing page, initiate delete. - cy.findByText(newLabel) - .should('be.visible') - .closest('tr') - .within(() => { - ui.actionMenu - .findByTitle(`Action menu for Image ${newLabel}`) - .should('be.visible') - .click(); - }); - - ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); - - // Confirm Image delete prompt. - ui.dialog - .findByTitle(`Delete Image ${newLabel}`) - .should('be.visible') - .within(() => { - ui.button - .findByTitle('Delete Image') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - // Confirm that Image is deleted successfully. - ui.toast.assertMessage('Image has been scheduled for deletion.'); - ui.toast.assertMessage(`Image ${newLabel} deleted successfully.`); - cy.findByText(image.label).should('not.exist'); - cy.findByText(newLabel).should('not.exist'); - }); - } - ); - }); -}); diff --git a/packages/manager/cypress/e2e/region/images/upload-machine-image.spec.ts b/packages/manager/cypress/e2e/region/images/upload-machine-image.spec.ts deleted file mode 100644 index 498eabaf80e..00000000000 --- a/packages/manager/cypress/e2e/region/images/upload-machine-image.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { Region } from '@linode/api-v4'; -import 'cypress-file-upload'; -import { authenticate } from 'support/api/authentication'; -import { imageUploadProcessingTimeout } from 'support/constants/images'; -import { interceptUploadImage } from 'support/intercepts/images'; -import { ui } from 'support/ui'; -import { cleanUp } from 'support/util/cleanup'; -import { randomLabel, randomPhrase } from 'support/util/random'; -import { testRegions } from 'support/util/regions'; - -authenticate(); -describe('Upload Machine Images', () => { - before(() => { - cleanUp('images'); - }); - /* - * - Confirms that users can upload Machine Images to the targeted region. - * - Confirms that user is redirected back to landing page. - * - Confirms that uploaded Image is listed on the landing page. - * - Confirms that Image uploads successfully and landing page reflects its status. - */ - testRegions('can upload a Machine Image', (region: Region) => { - const imageLabel = randomLabel(); - const imageDescription = randomPhrase(); - const imageFile = 'machine-images/test-image.gz'; - - interceptUploadImage().as('uploadImage'); - // Navigate to Image upload page, enter label, select region, and upload Image file. - cy.visitWithLogin('/images/create/upload'); - cy.findByText('Label').should('be.visible').click(); - cy.focused().type(imageLabel); - - cy.findByText('Description').should('be.visible').click(); - cy.focused().type(imageDescription); - - ui.regionSelect.find().click(); - ui.regionSelect.findItemByRegionId(region.id).should('be.visible').click(); - - // Pass `null` to `cy.fixture()` to encode file as a Cypress buffer object. - cy.fixture(imageFile, null).then((imageFileContents) => { - ui.fileUpload.find().attachFile({ - fileContent: imageFileContents, - fileName: 'test-image', - mimeType: 'application/x-gzip', - }); - }); - - // Wait for Image upload request and confirm toast notification is shown. - cy.wait('@uploadImage'); - ui.toast.assertMessage( - `Image ${imageLabel} uploaded successfully. It is being processed and will be available shortly.` - ); - - // Confirm redirect back to Images landing, new image is listed, and becomes available. - cy.url().should('endWith', '/images'); - cy.findByText(imageLabel).should('be.visible'); - - ui.toast.assertMessage(`Image ${imageLabel} is now available.`, { - timeout: imageUploadProcessingTimeout, - }); - - cy.findByText(imageLabel) - .should('be.visible') - .closest('tr') - .within(() => { - cy.findByText('Ready').should('be.visible'); - }); - }); -}); diff --git a/packages/manager/cypress/e2e/region/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/region/linodes/create-linode.spec.ts deleted file mode 100644 index 36cbf115fcc..00000000000 --- a/packages/manager/cypress/e2e/region/linodes/create-linode.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { testRegions } from 'support/util/regions'; -import { ui } from 'support/ui'; -import { randomLabel, randomString } from 'support/util/random'; -import { interceptCreateLinode } from 'support/intercepts/linodes'; - -import type { Region } from '@linode/api-v4'; - -describe('Create Linodes', () => { - /* - * - Navigates to Linode create page. - * - Selects a region, plan (Dedicated 4 GB), and enters label and password. - * - Clicks "Create Linode" and confirms that new Linode boots. - */ - testRegions('can create and boot a Linode', (region: Region) => { - const label = randomLabel(); - - interceptCreateLinode().as('createLinode'); - cy.visitWithLogin('linodes/create'); - - // Select region and plan. - ui.regionSelect.find().click(); - ui.regionSelect.findItemByRegionId(region.id).should('be.visible').click(); - - cy.get('[data-qa-plan-row="Dedicated 4 GB"]') - .closest('tr') - .within(() => { - cy.get('[data-qa-radio]').click(); - }); - - // Enter label and password. - cy.findByLabelText('Linode Label').click(); - cy.focused().clear(); - cy.focused().type(label); - cy.findByLabelText('Root Password').click(); - cy.focused().type(randomString(32)); - - // Submit. - ui.button.findByTitle('Create Linode').click(); - - // Confirm Linode boots. - cy.wait('@createLinode'); - cy.findByText(label).should('be.visible'); - cy.findByText(region.label).should('be.visible'); - cy.findByText('RUNNING', { timeout: 180000 }).should('be.visible'); - }); -}); diff --git a/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts b/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts deleted file mode 100644 index 92ec17a6ce8..00000000000 --- a/packages/manager/cypress/e2e/region/linodes/delete-linode.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createLinodeRequestFactory } from '@src/factories'; -import { describeRegions } from 'support/util/regions'; -import { randomLabel, randomString } from 'support/util/random'; -import { Region } from '@linode/api-v4'; -import type { Linode } from '@linode/api-v4'; -import { ui } from 'support/ui'; -import { authenticate } from 'support/api/authentication'; -import { - interceptGetLinodeDetails, - interceptGetLinodes, -} from 'support/intercepts/linodes'; -import { cleanUp } from 'support/util/cleanup'; -import { createTestLinode } from 'support/util/linodes'; - -authenticate(); -describeRegions('Delete Linodes', (region: Region) => { - before(() => { - cleanUp('linodes'); - }); - - /* - * - Navigates to a Linode details page. - * - Deletes the Linode via the "Delete" action menu item. - * - Navigates back to Linode landing page, confirms deleted Linode is not shown. - */ - it('can delete a Linode', () => { - const linodeCreatePayload = createLinodeRequestFactory.build({ - label: randomLabel(), - region: region.id, - root_pass: randomString(32), - booted: false, - }); - - // Create a Linode before navigating to its details page to delete it. - cy.defer( - () => createTestLinode(linodeCreatePayload), - `creating Linode in ${region.label}` - ).then((linode: Linode) => { - interceptGetLinodeDetails(linode.id).as('getLinode'); - cy.visitWithLogin(`/linodes/${linode.id}`); - cy.wait('@getLinode'); - - // Delete Linode via action menu. - ui.actionMenu - .findByTitle(`Action menu for Linode ${linode.label}`) - .should('be.visible') - .click(); - - ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); - - // Confirm deletion via type-to-confirm dialog. - ui.dialog - .findByTitle(`Delete ${linode.label}?`) - .should('be.visible') - .within(() => { - cy.findByLabelText('Linode Label').should('be.visible').click(); - cy.focused().type(linode.label); - - ui.buttonGroup - .findButtonByTitle('Delete') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - // Confirm that Linode is no longer listed on landing page. - // Cloud currently does not navigate back to landing page. - // Remove call to `cy.visitWithLogin()` once redirect is restored. - interceptGetLinodes().as('getLinodes'); - cy.visitWithLogin('/linodes'); - cy.wait('@getLinodes'); - cy.findByText(linode.label).should('not.exist'); - }); - }); -}); diff --git a/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts b/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts deleted file mode 100644 index 23919669b36..00000000000 --- a/packages/manager/cypress/e2e/region/linodes/update-linode.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { Disk, Linode } from '@linode/api-v4'; -import { getLinodeDisks } from '@linode/api-v4'; -import { createLinodeRequestFactory } from '@src/factories'; -import { authenticate } from 'support/api/authentication'; -import { interceptGetLinodeDetails } from 'support/intercepts/linodes'; -import { ui } from 'support/ui'; -import { cleanUp } from 'support/util/cleanup'; -import { depaginate } from 'support/util/paginate'; -import { randomLabel, randomString } from 'support/util/random'; -import { describeRegions } from 'support/util/regions'; -import { createTestLinode } from 'support/util/linodes'; - -/* - * Returns a Linode create payload for the given region. - */ -const makeLinodePayload = (region: string, booted: boolean) => { - return createLinodeRequestFactory.build({ - label: randomLabel(), - root_pass: randomString(32), - region, - booted, - }); -}; - -authenticate(); -describeRegions('Can update Linodes', (region) => { - before(() => { - cleanUp('linodes'); - }); - - /* - * - Navigates to a Linode details page's "Settings" tab. - * - Enters a new label and clicks "Save". - * - Confirms that label is updated and shown on Linode landing page. - */ - it('can update a Linode label', () => { - cy.defer( - () => createTestLinode(makeLinodePayload(region.id, true)), - 'creating Linode' - ).then((linode: Linode) => { - const newLabel = randomLabel(); - const oldLabel = linode.label; - - // Navigate to Linode details page. - interceptGetLinodeDetails(linode.id).as('getLinode'); - cy.visitWithLogin(`/linodes/${linode.id}`); - cy.wait('@getLinode'); - - // Click on 'Settings' tab. - cy.findByText('Settings').should('be.visible').click(); - - // Type in new label, click "Save". - cy.get('[data-qa-panel="Linode Label"]') - .should('be.visible') - .within(() => { - cy.findByLabelText('Label').should('be.visible').click(); - cy.focused().clear(); - cy.focused().type(newLabel); - - ui.button - .findByTitle('Save') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - // Confirm that label has been updated. - ui.toast.assertMessage( - `Successfully updated Linode label to ${newLabel}` - ); - ui.entityHeader.find().within(() => { - cy.findByText(newLabel).should('be.visible'); - cy.findByText('linodes').should('be.visible').click(); - }); - - cy.url().should('endWith', '/linodes'); - cy.findByText(oldLabel).should('not.exist'); - cy.findByText(newLabel).should('be.visible'); - }); - }); - - /* - * - Navigates to a Linode details page's "Settings" tab. - * - Enters a new password and clicks "Save". - * - Confirms that successful toast notification appears. - */ - it('can update a Linode root password', () => { - const newPassword = randomString(32); - - const createLinodeAndGetDisk = async (): Promise<[Linode, Disk]> => { - const linode = await createTestLinode( - makeLinodePayload(region.id, false) - ); - const disks = await depaginate((page) => - getLinodeDisks(linode.id, { page }) - ); - - // Throw if Linode has no disks. Shouldn't happen in practice. - if (!disks[0]) { - throw new Error('Created Linode does not have any disks'); - } - return [linode, disks[0]]; - }; - - cy.defer(() => createLinodeAndGetDisk(), 'creating Linode').then( - ([linode, disk]: [Linode, Disk]) => { - // Navigate to Linode details page. - interceptGetLinodeDetails(linode.id).as('getLinode'); - cy.visitWithLogin(`/linodes/${linode.id}`); - cy.wait('@getLinode'); - - // Wait for Linode to finish provisioning. - cy.findByText('PROVISIONING').should('not.exist'); - cy.findByText('OFFLINE').should('be.visible'); - - // Click on 'Settings' tab. - cy.findByText('Settings').should('be.visible').click(); - - cy.get('[data-qa-panel="Reset Root Password"]') - .should('be.visible') - .within(() => { - cy.findByText('Disk').should('be.visible').clear(); - cy.focused().type(disk.label); - - ui.autocompletePopper - .findByTitle(disk.label) - .should('be.visible') - .click(); - - cy.findByLabelText('New Root Password') - .should('be.visible') - .clear(); - cy.focused().clear(); - cy.focused().type(newPassword); - - ui.button - .findByTitle('Save') - .should('be.visible') - .should('be.enabled') - .click(); - }); - - ui.toast.assertMessage('Sucessfully changed password'); - // TODO Investigate whether e2e solution to test password can be done securely. - } - ); - }); -}); From 240d604e79215acf434165e583e7178ed9dfe5a8 Mon Sep 17 00:00:00 2001 From: dmcintyr-akamai Date: Tue, 4 Mar 2025 10:35:03 -0500 Subject: [PATCH 2/3] M3-8628 cleanup docs --- docs/development-guide/08-testing.md | 3 +-- .../manager/cypress/support/plugins/configure-test-suite.ts | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/development-guide/08-testing.md b/docs/development-guide/08-testing.md index a89da1e88dd..8d51f0c4208 100644 --- a/docs/development-guide/08-testing.md +++ b/docs/development-guide/08-testing.md @@ -180,7 +180,7 @@ Environment variables related to the general operation of the Cloud Manager Cypr | 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_SUITE` | Name of the Cloud Manager UI test suite to run. Possible values are `core` or `synthetic`. | `synthetic` | 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 | ###### Overriding Behavior @@ -189,7 +189,6 @@ These environment variables can be used to override some behaviors of Cloud Mana | Environment Variable | Description | Example | Default | |-------------------------|-------------------------------------------------|-----------|--------------------------------------------| -| `CY_TEST_REGION` | ID of region to test (as used by Linode APIv4). | `us-east` | Unset; regions are selected at random | | `CY_TEST_FEATURE_FLAGS` | JSON string containing feature flag data | `{}` | Unset; feature flag data is not overridden | ###### Run Splitting diff --git a/packages/manager/cypress/support/plugins/configure-test-suite.ts b/packages/manager/cypress/support/plugins/configure-test-suite.ts index 5db9ed6b4b6..0e431ee07a9 100644 --- a/packages/manager/cypress/support/plugins/configure-test-suite.ts +++ b/packages/manager/cypress/support/plugins/configure-test-suite.ts @@ -20,9 +20,6 @@ export const configureTestSuite: CypressPlugin = (_on, config) => { case 'synthetic': return 'synthetic'; - case 'region': - return 'region'; - case 'core': default: if (!!config.env[envVarName] && config.env[envVarName] !== 'core') { From 9078c5225e19cf9e89bc718e81d219db65415dab Mon Sep 17 00:00:00 2001 From: dmcintyr-akamai Date: Tue, 4 Mar 2025 10:40:27 -0500 Subject: [PATCH 3/3] Added changeset: Delete region test suite --- packages/manager/.changeset/pr-11780-tests-1741102827420.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-11780-tests-1741102827420.md diff --git a/packages/manager/.changeset/pr-11780-tests-1741102827420.md b/packages/manager/.changeset/pr-11780-tests-1741102827420.md new file mode 100644 index 00000000000..aae538082f3 --- /dev/null +++ b/packages/manager/.changeset/pr-11780-tests-1741102827420.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tests +--- + +Delete region test suite ([#11780](https://github.com/linode/manager/pull/11780))