From 6d764062cbb19d059a5ad4f976a51408c1f5da94 Mon Sep 17 00:00:00 2001 From: Arjun Patel Date: Wed, 4 Oct 2023 13:13:14 -0400 Subject: [PATCH] wip --- .evergreen.yml | 48 +- README.md | 28 +- cypress.config.ts | 17 + cypress/integration/announcements.ts | 9 +- cypress/integration/commit_queue.ts | 22 +- .../integration/preferences/notifications.ts | 8 +- .../preferences/public_key_management.ts | 41 +- .../projectSettings/admin_actions.ts | 18 +- .../integration/projectSettings/constants.ts | 3 + .../integration/projectSettings/containers.ts | 7 +- .../integration/projectSettings/plugins.ts | 10 +- .../projectSettings/project_settings.ts | 1431 ++++++++--------- cypress/integration/spawn/volume.ts | 2 +- cypress/integration/task/task_actions.ts | 10 +- cypress/integration/task/task_route.ts | 2 +- cypress/integration/task/test_table.ts | 43 +- .../version/patch_with_aborted_task.ts | 18 +- cypress/integration/version/restart_modal.ts | 39 +- cypress/integration/version/routes.ts | 15 +- cypress/integration/version/task_duration.ts | 5 +- cypress/support/commands.ts | 7 +- cypress/support/e2e.ts | 38 +- cypress/utils/graphql-test-utils.ts | 21 +- package.json | 3 +- scripts/evg-db-ops.sh | 92 ++ scripts/prepare-shell.sh | 1 + src/pages/projectSettings/index.tsx | 8 +- src/pages/task/taskTabs/TestsTable.tsx | 2 +- 28 files changed, 1009 insertions(+), 939 deletions(-) create mode 100755 scripts/evg-db-ops.sh diff --git a/.evergreen.yml b/.evergreen.yml index 020c49467f..72e7054c00 100644 --- a/.evergreen.yml +++ b/.evergreen.yml @@ -67,7 +67,17 @@ functions: params: working_dir: spruce/evergreen command: make configure-mongod - + - command: shell.exec + type: setup + params: + shell: bash + script: | + ${PREPARE_SHELL} + cd $PROJECT_DIRECTORY + mkdir mongodb-tools && cd mongodb-tools + curl ${mongodb_tools_url} -o mongodb-tools.tgz + ${decompress} mongodb-tools.tgz + mv ./mongodb-*/bin/* . setup-node: - command: subprocess.exec params: @@ -202,7 +212,7 @@ functions: script: | ${PREPARE_SHELL} yarn build-storybook - + yarn-snapshot: command: shell.exec params: @@ -247,8 +257,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/cypress/screenshots/*"] + local_files_include_filter: ["spruce/cypress/screenshots/*"] remote_file: spruce/${task_id}/ bucket: mciuploads content_type: image/png @@ -259,8 +268,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/cypress/videos/*"] + local_files_include_filter: ["spruce/cypress/videos/*"] remote_file: spruce/${task_id}/ bucket: mciuploads content_type: video/mp4 @@ -290,8 +298,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/storybook-static/*.html"] + local_files_include_filter: ["spruce/storybook-static/*.html"] remote_file: spruce/${task_id}/storybook/ bucket: mciuploads content_type: text/html @@ -303,7 +310,10 @@ functions: aws_key: ${aws_key} aws_secret: ${aws_secret} local_files_include_filter: - ["spruce/storybook-static/**/*.js$", "spruce/storybook-static/**/*.mjs$"] + [ + "spruce/storybook-static/**/*.js$", + "spruce/storybook-static/**/*.mjs$", + ] remote_file: spruce/${task_id}/storybook/ bucket: mciuploads content_type: application/javascript @@ -314,8 +324,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/storybook-static/**/*.js.map"] + local_files_include_filter: ["spruce/storybook-static/**/*.js.map"] remote_file: spruce/${task_id}/storybook/ bucket: mciuploads content_type: application/json @@ -326,8 +335,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/storybook-static/**/*.css"] + local_files_include_filter: ["spruce/storybook-static/**/*.css"] remote_file: spruce/${task_id}/storybook/ bucket: mciuploads content_type: text/css @@ -338,8 +346,7 @@ functions: params: aws_key: ${aws_key} aws_secret: ${aws_secret} - local_files_include_filter: - ["spruce/storybook-static/**/*.json"] + local_files_include_filter: ["spruce/storybook-static/**/*.json"] remote_file: spruce/${task_id}/storybook/ bucket: mciuploads content_type: application/json @@ -358,7 +365,7 @@ functions: content_type: font/woff2 permissions: public-read preserve_path: true - + attach-codegen-diff: command: s3.put type: system @@ -371,7 +378,7 @@ functions: bucket: mciuploads content_type: text/plain permissions: public-read - + attach-email: command: s3.put type: system @@ -412,7 +419,7 @@ functions: EOF echo "Done populating" - + prod-deploy: command: shell.exec params: @@ -429,8 +436,6 @@ functions: AUTHOR_EMAIL=${author_email} \ yarn deploy:prod - - ####################################### # Tasks # ####################################### @@ -476,7 +481,7 @@ tasks: - func: yarn-serve - func: wait-for-evergreen - func: run-cypress-tests - + - name: check_codegen commands: - func: sym-link @@ -497,6 +502,7 @@ buildvariants: goroot: /opt/golang/go1.20 mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-6.0.6.tgz mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-1.9.0-linux-x64.tgz + mongodb_tools_url: https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.8.0.tgz node_version: 16.17.0 modules: - evergreen diff --git a/README.md b/README.md index bf48d11cb1..c49280f869 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Spruce is the React UI for MongoDB's continuous integration software. ### Running Locally 1. Clone the Spruce Github repository -2. Ensure you have Node.js 16+ installed +2. Ensure you have Node.js v16+ and MongoDB Command Line Database Tools + v100.8.0+ installed 3. Ask a colleague for their .cmdrc.json file and follow the instructions [here](#environment-variables) 4. Run `yarn` @@ -159,10 +160,13 @@ on localhost:9090 for the front-end to work. In order to run the Cypress tests, do the following, assuming you have this repo checked out and all the dependencies installed by yarn: -1. Start the evergreen back-end with the sample local test data. You can do this +1. Increase the limit on open files by running `ulimit -n 64000` before running + mongod in the same shell. +2. Start the evergreen back-end with the sample local test data. You can do this by typing `make local-evergreen` in your evergreen folder. -2. Start the Spruce local server by typing `yarn build:local && yarn serve` in this repo. -3. Run Cypress by typing one of the following: +3. Start the Spruce local server by typing `yarn build:local && yarn serve` in + this repo. +4. Run Cypress by typing one of the following: - `yarn cy:open` - opens the Cypress app in interactive mode. You can select tests to run from here in the Cypress browser. - `yarn cy:run` - runs all the Cypress tests at the command-line and reports @@ -200,22 +204,26 @@ production environments. switched to db mci mci:SECONDARY> db.distro.find({_id: "archlinux-small"}) // the full query ``` + 5. Exit from the mongo shell and prepare to run `mongoexport` + ``` - mongoexport --db=mci --collection=distro --out=distro.json --query='{_id: "archlinux-small"}' + mongoexport --db=mci --collection=distro --out=distro.json --query='{_id: "archlinux-small"}' 2020-07-29T17:41:50.266+0000 connected to: localhost 2020-07-29T17:41:50.269+0000 exported 1 record ``` + After running this command a file will be saved to your home directory with the results of the `mongoexport` _Note you may need to provide the full path to mongoexport on the staging db_ ``` - /var/lib/mongodb-mms-automation/mongodb-linux-x86_64-4.0.5/bin/mongoexport --db=mci --collection=distro --out=distro.json --query='{_id: "archlinux-small"}' + /var/lib/mongodb-mms-automation/mongodb-linux-x86_64-4.0.5/bin/mongoexport --db=mci --collection=distro --out=distro.json --query='{_id: "archlinux-small"}' 2020-07-29T17:41:50.266+0000 connected to: localhost 2020-07-29T17:41:50.269+0000 exported 1 record ``` + 6. Exit the ssh session using `exit` or `Ctrl + D` 7. You can now transfer this json file to your local system by running the following command. `scp :~/distro.json .` This will save a @@ -225,9 +233,8 @@ production environments. from within the evergreen folder 9. Once you have this file you can copy the contents of it to the relevant `testdata/local/.json` file with in the evergreen folder -10. You can then delete `/bin/.load-local-data` within the evergreen folder and - run `make local-evergreen` to repopulate the local database with your new - data. +10. You can then run `yarn evg-db-ops --reseed` to repopulate the local database + with your new data. **Notes** @@ -266,4 +273,5 @@ Run one of the following commands to deploy to the appropriate environment 3. `yarn deploy:beta` = deploy to https://spruce-beta.corp.mongodb.com (Beta connects to the production backend) -In case of emergency (i.e. Evergreen, GitHub, or other systems are down), a production build can be pushed directly to S3 with `yarn deploy:prod --local`. +In case of emergency (i.e. Evergreen, GitHub, or other systems are down), a +production build can be pushed directly to S3 with `yarn deploy:prod --local`. diff --git a/cypress.config.ts b/cypress.config.ts index 39fc5f2d62..6b4e7bf1a9 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,4 +1,5 @@ import { defineConfig } from "cypress"; +import { execSync } from "child_process"; export default defineConfig({ e2e: { @@ -11,5 +12,21 @@ export default defineConfig({ specPattern: "cypress/integration/**/*.ts", viewportWidth: 1920, viewportHeight: 1080, + setupNodeEvents(on) { + on("before:run", () => { + try { + execSync("yarn evg-db-ops --dump"); + } catch (e) { + console.error(e); + } + }); + on("after:run", () => { + try { + execSync("yarn evg-db-ops --clean-up"); + } catch (e) { + console.error(e); + } + }); + }, }, }); diff --git a/cypress/integration/announcements.ts b/cypress/integration/announcements.ts index 9c148f81df..d9055c2e2d 100644 --- a/cypress/integration/announcements.ts +++ b/cypress/integration/announcements.ts @@ -4,13 +4,8 @@ import { } from "constants/cookies"; describe("Announcement overlays", () => { - beforeEach(() => { - cy.clearCookie(CY_DISABLE_NEW_USER_WELCOME_MODAL); - cy.clearCookie(CY_DISABLE_COMMITS_WELCOME_MODAL); - cy.clearCookie("This is an important notification"); - }); - it("Displays a welcome modal only when you first visit spruce", () => { + cy.clearCookie(CY_DISABLE_NEW_USER_WELCOME_MODAL); cy.visit("/"); cy.dataCy("welcome-modal").should("exist"); cy.dataCy("close-welcome-modal").click(); @@ -19,6 +14,7 @@ describe("Announcement overlays", () => { }); it("Should not show a Sitewide banner after it has been dismissed", () => { + cy.clearCookie("This is an important notification"); cy.visit("/"); cy.dataCy("sitewide-banner-success").should("exist"); cy.closeBanner("sitewide-banner-success"); @@ -41,6 +37,7 @@ describe("Announcement overlays", () => { }); it("visiting the commits page for the first time should show a welcome modal", () => { + cy.clearCookie(CY_DISABLE_COMMITS_WELCOME_MODAL); cy.visit("/commits/spruce"); cy.dataCy("welcome-modal").should("be.visible"); cy.dataCy("close-welcome-modal").click(); diff --git a/cypress/integration/commit_queue.ts b/cypress/integration/commit_queue.ts index e05f0d5221..81d637f06b 100644 --- a/cypress/integration/commit_queue.ts +++ b/cypress/integration/commit_queue.ts @@ -9,28 +9,29 @@ const COMMIT_QUEUE_ROUTE_2 = `/commit-queue/${commitQueue.id2}`; const INVALID_COMMIT_QUEUE_ROUTE = `/commit-queue/${commitQueue.id3}`; const COMMIT_QUEUE_ROUTE_4 = `/commit-queue/${commitQueue.id4}`; -describe("commit queue page", { testIsolation: false }, () => { +describe("commit queue page", () => { describe(COMMIT_QUEUE_ROUTE_1, () => { - before(() => { + beforeEach(() => { cy.visit(COMMIT_QUEUE_ROUTE_1); }); + it("Should render the commit queue page with one card", () => { cy.dataCy("commit-queue-card").should("have.length", 1); }); - it("Clicking on Total Code changes should toggle a drop down table", () => { + it("Clicking on 'Total code changes' should show the code changes table", () => { cy.dataCy("code-changes-table").should("not.be.visible"); cy.dataCy("accordion-toggle").click(); cy.dataCy("code-changes-table").should("be.visible"); }); - it("Clicking on remove a patch from the commit queue should work", () => { + it("Should be able to remove a patch from the commit queue", () => { cy.dataCy("commit-queue-card").should("exist"); cy.dataCy("commit-queue-patch-button").should("exist"); cy.dataCy("commit-queue-patch-button").click(); cy.dataCy("commit-queue-confirmation-modal").should("be.visible"); cy.dataCy("commit-queue-confirmation-modal").within(() => { - cy.contains("Remove").click(); + cy.contains("button", "Remove").click(); }); cy.dataCy("commit-queue-confirmation-modal").should("not.exist"); cy.dataCy("commit-queue-card").should("not.exist"); @@ -38,18 +39,17 @@ describe("commit queue page", { testIsolation: false }, () => { }); describe(COMMIT_QUEUE_ROUTE_2, () => { - before(() => { - cy.visit(COMMIT_QUEUE_ROUTE_2); - }); it("visiting a page with multiple sets of code changes should have multiple tables", () => { + cy.visit(COMMIT_QUEUE_ROUTE_2); cy.dataCy("accordion-toggle").should("have.length", 4); }); }); describe(COMMIT_QUEUE_ROUTE_4, () => { - before(() => { + beforeEach(() => { cy.visit(COMMIT_QUEUE_ROUTE_4); }); + it("should display the commit queue message if there is one", () => { cy.dataCy("commit-queue-message").should("exist"); cy.dataCy("commit-queue-message").should( @@ -75,10 +75,8 @@ describe("commit queue page", { testIsolation: false }, () => { }); describe(INVALID_COMMIT_QUEUE_ROUTE, () => { - before(() => { + it("visiting a nonexistent commit queue page should display an error", () => { cy.visit(INVALID_COMMIT_QUEUE_ROUTE); - }); - it("visiting a non existent commit queue page should display an error", () => { cy.validateToast("error", "There was an error loading the commit queue"); }); }); diff --git a/cypress/integration/preferences/notifications.ts b/cypress/integration/preferences/notifications.ts index 26215be3cc..edffa6859d 100644 --- a/cypress/integration/preferences/notifications.ts +++ b/cypress/integration/preferences/notifications.ts @@ -8,7 +8,8 @@ describe("global subscription settings", () => { "aria-disabled", "true" ); - cy.dataCy("slack-member-id-field").clear().type("12345"); + cy.dataCy("slack-member-id-field").clear(); + cy.dataCy("slack-member-id-field").type("12345"); cy.dataCy("save-profile-changes-button").should( "not.have.attr", "aria-disabled", @@ -17,7 +18,8 @@ describe("global subscription settings", () => { }); it("saving changes to a field should work", () => { cy.visit(pageRoute); - cy.dataCy("slack-username-field").clear().type("slack.user"); + cy.dataCy("slack-username-field").clear(); + cy.dataCy("slack-username-field").type("slack.user"); cy.dataCy("save-profile-changes-button").click(); cy.validateToast("success", "Your changes have successfully been saved."); }); @@ -73,7 +75,7 @@ describe("user subscriptions table", () => { ); }); - describe("Deleting subscriptions", { testIsolation: false }, () => { + describe("Deleting subscriptions", () => { it("Deletes a single subscription", () => { cy.dataCy("subscription-row") .eq(0) diff --git a/cypress/integration/preferences/public_key_management.ts b/cypress/integration/preferences/public_key_management.ts index eef90ca89b..45848547f4 100644 --- a/cypress/integration/preferences/public_key_management.ts +++ b/cypress/integration/preferences/public_key_management.ts @@ -1,10 +1,9 @@ -describe("Public Key Management Page", { testIsolation: false }, () => { +describe("Public Key Management Page", () => { const route = "/preferences/publickeys"; - + beforeEach(() => { + cy.visit(route); + }); describe("Public keys list", () => { - before(() => { - cy.visit(route); - }); it("Displays the user's public keys", () => { cy.dataCy("table-key-name").each(($el, index) => cy.wrap($el).contains([keyName1, keyName2][index]) @@ -18,6 +17,8 @@ describe("Public Key Management Page", { testIsolation: false }, () => { cy.dataCy("table-key-name").first().contains(keyName2); }); it('Displays "No keys saved. Add a new key to populate the list." when no keys are available', () => { + cy.dataCy("delete-btn").first().click(); + cy.contains("button", "Yes").click(); cy.dataCy("delete-btn").first().click(); cy.contains("button", "Yes").click(); cy.contains("No keys saved. Add a new key to populate the list."); @@ -25,11 +26,10 @@ describe("Public Key Management Page", { testIsolation: false }, () => { }); describe("Add New Key Modal", () => { - before(() => { - cy.visit(route); + beforeEach(() => { + cy.dataCy("add-key-button").click(); }); it("Displays errors when the modal opens", () => { - cy.dataCy("add-key-button").click(); cy.dataCy("error-message").each(($el, index) => cy.wrap($el).contains([err1, err2][index]) ); @@ -42,15 +42,16 @@ describe("Public Key Management Page", { testIsolation: false }, () => { }); it("Should include the public in the public key list after adding", () => { + cy.dataCy("key-name-input").clear(); + cy.dataCy("key-name-input").type(keyName3); cy.dataCy("key-value-input").clear(); cy.dataCy("key-value-input").type(pubKey, { delay: 0 }); cy.contains("button", "Save").click(); - cy.dataCy("table-key-name").first().contains(keyName3); + cy.dataCy("table-key-name").eq(1).contains(keyName3); }); it("Should show an error if the key name already exists", () => { - cy.dataCy("add-key-button").click(); - cy.dataCy("key-name-input").type(keyName3, { delay: 0 }); + cy.dataCy("key-name-input").type(keyName2, { delay: 0 }); cy.dataCy("error-message").first().contains(err3); }); @@ -60,40 +61,40 @@ describe("Public Key Management Page", { testIsolation: false }, () => { }); describe("Edit Key Modal", () => { - before(() => { + beforeEach(() => { cy.visit(route); + cy.dataCy("edit-btn").first().click(); }); it("Should not have any errors when the modal opens", () => { - cy.dataCy("edit-btn").first().click(); cy.dataCy("error-message").should("have.length", 0); }); it("After submitting, the key name and key value are updated", () => { cy.dataCy("key-name-input").clear(); cy.dataCy("key-name-input").type(keyName4); - cy.dataCy("key-value-input").clear(); + cy.dataCy("key-value-input").clear(); cy.dataCy("key-value-input").type(pubKey2, { delay: 0 }); cy.contains("button", "Save").click(); cy.dataCy("key-edit-modal").should("not.exist"); - cy.dataCy("table-key-name").first().contains(keyName4); - cy.dataCy("edit-btn").first().click(); + cy.dataCy("table-key-name").eq(1).contains(keyName4); + cy.dataCy("edit-btn").eq(1).click(); cy.dataCy("key-name-input").should("have.value", keyName4); cy.dataCy("key-value-input").should("have.value", pubKey2); cy.dataCy("key-value-input").clear(); cy.dataCy("key-value-input").type(pubKey3, { delay: 0 }); cy.contains("button", "Save").click(); cy.dataCy("key-edit-modal").should("not.exist"); - cy.dataCy("table-key-name").first().contains(keyName4); - cy.dataCy("edit-btn").first().click(); + cy.dataCy("table-key-name").eq(1).contains(keyName4); + cy.dataCy("edit-btn").eq(1).click(); cy.dataCy("key-name-input").should("have.value", keyName4); cy.dataCy("key-value-input").should("have.value", pubKey3); cy.dataCy("key-value-input").clear(); cy.dataCy("key-value-input").type(pubKey4, { delay: 0 }); cy.contains("button", "Save").click(); cy.dataCy("key-edit-modal").should("not.exist"); - cy.dataCy("table-key-name").first().contains(keyName4); - cy.dataCy("edit-btn").first().click(); + cy.dataCy("table-key-name").eq(1).contains(keyName4); + cy.dataCy("edit-btn").eq(1).click(); cy.dataCy("key-name-input").should("have.value", keyName4); cy.dataCy("key-value-input").should("have.value", pubKey4); }); diff --git a/cypress/integration/projectSettings/admin_actions.ts b/cypress/integration/projectSettings/admin_actions.ts index 3686908075..229cdb049b 100644 --- a/cypress/integration/projectSettings/admin_actions.ts +++ b/cypress/integration/projectSettings/admin_actions.ts @@ -19,11 +19,10 @@ describe("Duplicating a project", () => { }); }); -describe("Creating a new project", () => { - const destination = getGeneralRoute(project); - - it("Successfully creates a new project", () => { - cy.visit(destination); +describe("Creating a new project and deleting it", () => { + it("Successfully creates a new project and then deletes it", () => { + // Create project + cy.visit(getGeneralRoute(project)); cy.dataCy("new-project-button").click(); cy.dataCy("new-project-menu").should("be.visible"); cy.dataCy("create-project-button").click(); @@ -38,14 +37,9 @@ describe("Creating a new project", () => { cy.validateToast("success"); cy.url().should("include", "my-new-project"); - }); -}); - -describe("Deleting a project", () => { - const destination = getGeneralRoute("my-new-project"); - it("Successfully deletes a project", () => { - cy.visit(destination); + // Delete project + cy.visit(getGeneralRoute("my-new-project")); cy.dataCy("attach-repo-button").click(); cy.dataCy("attach-repo-modal") .find("button") diff --git a/cypress/integration/projectSettings/constants.ts b/cypress/integration/projectSettings/constants.ts index 1756ff0562..4d65f0e812 100644 --- a/cypress/integration/projectSettings/constants.ts +++ b/cypress/integration/projectSettings/constants.ts @@ -22,6 +22,9 @@ export const getContainersRoute = (identifier: string) => export const getViewsAndFiltersRoute = (identifier: string) => `${getSettingsRoute(identifier)}/views-and-filters`; +export const getVirtualWorkstationRoute = (identifier: string) => + `${getSettingsRoute(identifier)}/virtual-workstation`; + export const project = "spruce"; export const projectUseRepoEnabled = "evergreen"; export const repo = "602d70a2b2373672ee493184"; diff --git a/cypress/integration/projectSettings/containers.ts b/cypress/integration/projectSettings/containers.ts index 9debaebd05..decf6265d4 100644 --- a/cypress/integration/projectSettings/containers.ts +++ b/cypress/integration/projectSettings/containers.ts @@ -14,7 +14,8 @@ describe("Containers", () => { it("shouldn't be able to save anything if no changes were made", () => { saveButtonEnabled(false); }); - it("should be able to add a container configuration and save it", () => { + it("should be able to add and save container configuration and then delete it", () => { + // Add configuration cy.dataCy("add-button").should("be.visible"); cy.dataCy("add-button").trigger("mouseover").click(); cy.dataCy("container-size-row").should("exist"); @@ -31,8 +32,8 @@ describe("Containers", () => { cy.dataCy("save-settings-button").scrollIntoView(); clickSave(); cy.validateToast("success", "Successfully updated project"); - }); - it("should be able to delete a container configuration", () => { + + // Delete configuration cy.dataCy("container-size-row").should("exist"); cy.dataCy("delete-item-button").should("be.visible"); cy.dataCy("delete-item-button").should("not.be.disabled"); diff --git a/cypress/integration/projectSettings/plugins.ts b/cypress/integration/projectSettings/plugins.ts index ece6df511c..ba5ae344a8 100644 --- a/cypress/integration/projectSettings/plugins.ts +++ b/cypress/integration/projectSettings/plugins.ts @@ -3,10 +3,9 @@ import { clickSave } from "../../utils"; describe("Plugins", () => { const patchPage = "version/5ecedafb562343215a7ff297"; - beforeEach(() => { + it("Should set an external link to render on patch metadata panel and then unset it to revert the changes", () => { + // Set the external link cy.visit(getPluginsRoute(projectUseRepoEnabled)); - }); - it("Should set an external link to render on patch metadata panel", () => { cy.dataCy("requesters-input").click(); cy.getInputByLabel("Commits").check({ force: true }); cy.getInputByLabel("Patches").check({ force: true }); @@ -24,8 +23,9 @@ describe("Plugins", () => { "href", "https://example.com/5ecedafb562343215a7ff297" ); - }); - it("Unsetting the external link should remove it from the patch metadata panel", () => { + + // Unset the external link + cy.visit(getPluginsRoute(projectUseRepoEnabled)); cy.dataCy("requesters-input").click(); cy.getInputByLabel("Commits").uncheck({ force: true }); cy.getInputByLabel("Patches").uncheck({ force: true }); diff --git a/cypress/integration/projectSettings/project_settings.ts b/cypress/integration/projectSettings/project_settings.ts index a6a4e52cc6..1e304cdaa3 100644 --- a/cypress/integration/projectSettings/project_settings.ts +++ b/cypress/integration/projectSettings/project_settings.ts @@ -2,6 +2,7 @@ import { getAccessRoute, getGeneralRoute, getGithubCommitQueueRoute, + getVirtualWorkstationRoute, project, projectUseRepoEnabled, repo, @@ -9,60 +10,45 @@ import { } from "./constants"; import { clickSave } from "../../utils"; -describe("Access page", { testIsolation: false }, () => { - const destination = getAccessRoute(projectUseRepoEnabled); - before(() => { - cy.visit(destination); - }); - - it("Save button should be disabled on initial load", () => { +describe("Access page", () => { + const origin = getAccessRoute(projectUseRepoEnabled); + beforeEach(() => { + cy.visit(origin); saveButtonEnabled(false); - }); - - it("Shows a 'Default to Repo on Page' button on page", () => { - cy.dataCy("default-to-repo-button").should("exist").should("be.enabled"); + cy.dataCy("default-to-repo-button") + .should("be.visible") + .should("be.enabled") + .should("not.have.attr", "aria-disabled", "true"); }); it("Changing settings and clicking the save button produces a success toast and the changes are persisted", () => { - cy.getInputByLabel("Unrestricted").parent().click(); - cy.getInputByLabel("Unrestricted").should( - "have.attr", - "aria-checked", - "true" - ); - + cy.contains("label", "Unrestricted").click(); + cy.getInputByLabel("Unrestricted").should("be.checked"); + // Input and save username cy.contains("Add Username").click(); - cy.get("[aria-label='Username'") - .should("have.length", 1) - .first() - .type("admin"); - cy.get("[aria-label='Username']") - .should("have.value", "admin") - .should("exist"); + cy.getInputByLabel("Username").as("usernameInput"); + cy.get("@usernameInput").type("admin"); + cy.get("@usernameInput").should("have.value", "admin").should("be.visible"); clickSave(); cy.validateToast("success", "Successfully updated project"); - }); - - it("Deleting a username results in a success toast and the changes are persisted", () => { - cy.get("[aria-label='Username']").should("have.length", 1); + // Assert persistence + cy.reload(); + cy.get("@usernameInput").should("have.value", "admin").should("be.visible"); + // Delete a username cy.dataCy("delete-item-button").should("be.visible").click(); - cy.get("[aria-label='Username']").should("have.length", 0); + cy.get("@usernameInput").should("not.exist"); clickSave(); cy.validateToast("success", "Successfully updated project"); - + // Assert persistence cy.reload(); - cy.get("[aria-label='Username']").should("have.length", 0); + cy.get("@usernameInput").should("not.exist"); }); it("Clicking on 'Default to Repo on Page' selects the 'Default to repo (unrestricted)' radio box and produces a success banner", () => { cy.dataCy("default-to-repo-button").click(); cy.dataCy("default-to-repo-modal").contains("Confirm").click(); cy.validateToast("success", "Successfully defaulted page to repo"); - cy.getInputByLabel("Default to repo (unrestricted)").should( - "have.attr", - "aria-checked", - "true" - ); + cy.getInputByLabel("Default to repo (unrestricted)").should("be.checked"); }); it("Submitting an invalid admin username produces an error toast", () => { @@ -81,10 +67,10 @@ describe("Access page", { testIsolation: false }, () => { }); describe("Clicking on The Project Select Dropdown", () => { - const destination = getGeneralRoute(project); + const origin = getGeneralRoute(project); beforeEach(() => { - cy.visit(destination); + cy.visit(origin); }); it("Headers are clickable", () => { @@ -95,191 +81,210 @@ describe("Clicking on The Project Select Dropdown", () => { .find("div") .contains("evergreen-ci/evergreen") .click(); - cy.location().should((loc) => expect(loc.pathname).to.not.eq(destination)); + cy.location().should((loc) => expect(loc.pathname).to.not.eq(origin)); }); }); -describe("Repo Settings", { testIsolation: false }, () => { - const destination = getGeneralRoute(repo); - - before(() => { - cy.visit(destination); - }); - - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); - - it("Does not show a 'Default to Repo' button on page", () => { - cy.dataCy("default-to-repo-button").should("not.exist"); - }); - - it("Does not show a 'Move to New Repo' button on page", () => { - cy.dataCy("move-repo-button").should("not.exist"); - }); - - it("Does not show an Attach/Detach to Repo button on page", () => { - cy.dataCy("attach-repo-button").should("not.exist"); - }); - - it("Does not show a 'Go to repo settings' link on page", () => { - cy.dataCy("attached-repo-link").should("not.exist"); - }); - - it("Sets a display name", () => { - cy.dataCy("display-name-input").type("evg"); - }); +describe("Repo Settings", () => { + const origin = getGeneralRoute(repo); - it("Clicking on save button should show a success toast", () => { - clickSave(); - cy.validateToast("success", "Successfully updated repo"); + beforeEach(() => { + cy.visit(origin); }); - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - }); - - it("Should not have the save button enabled on load", () => { + describe("General settings page", () => { + it("Should have the save button disabled on load", () => { saveButtonEnabled(false); }); - it("Shows an error banner when a patch definition does not exist", () => { - cy.dataCy("error-banner") - .contains( - "A GitHub Patch Definition must be specified for this feature to run." - ) - .should("exist"); - }); - - it("Shows an error banner when Commit Checks are enabled", () => { - cy.dataCy("github-checks-enabled-radio-box").within(($el) => { - cy.wrap($el).getInputByLabel("Enabled").parent().click(); - }); - cy.dataCy("error-banner") - .contains( - "A Commit Check Definition must be specified for this feature to run." - ) - .should("exist"); - cy.dataCy("github-checks-enabled-radio-box").within(($el) => { - cy.wrap($el).getInputByLabel("Disabled").parent().click(); - }); - cy.dataCy("error-banner") - .contains( - "A Commit Check Definition must be specified for this feature to run." - ) - .should("not.exist"); - }); - - it("Allows enabling manual PR testing", () => { - cy.dataCy("manual-pr-testing-enabled-radio-box") - .children() - .first() - .click(); - }); - - it("Updates a patch definition", () => { - cy.contains("button", "Add Patch Definition").click(); - cy.dataCy("variant-tags-input").first().type("vtag"); - cy.dataCy("task-tags-input").first().type("ttag"); - }); - - it("Does not show an error banner when a patch definition is added", () => { - cy.contains( - "A GitHub Patch Definition must be specified for this feature to run." - ).should("not.exist"); + it("Does not show a 'Default to Repo' button on page", () => { + cy.dataCy("default-to-repo-button").should("not.exist"); }); - it("Enabling commit queue shows hidden inputs and error banner", () => { - const countCQFields = (count: number) => { - cy.dataCy("cq-card").children().should("have.length", count); - }; - - countCQFields(2); - cy.dataCy("cq-enabled-radio-box").children().first().click(); - countCQFields(4); - - cy.dataCy("error-banner") - .contains( - "A Commit Queue Patch Definition must be specified for this feature to run." - ) - .should("exist"); + it("Does not show a 'Move to New Repo' button on page", () => { + cy.dataCy("move-repo-button").should("not.exist"); }); - it("Shows merge method only if merge queue is Evergreen", () => { - const selectId = "merge-method-select"; - - // Hides merge method for GitHub. - cy.getInputByLabel("GitHub").check({ force: true }); - cy.dataCy(selectId).should("not.exist"); - - // Shows merge method for Evergreen. - cy.getInputByLabel("Evergreen").check({ force: true }); - cy.dataCy(selectId).should("exist"); - cy.get(`button[name=${selectId}]`).click(); - cy.get(`#${selectId}-menu`).children().should("have.length", 3); - cy.get(`#${selectId}-menu`).children().first().click(); + it("Does not show an Attach/Detach to Repo button on page", () => { + cy.dataCy("attach-repo-button").should("not.exist"); }); - it("Does not show override buttons for commit queue patch definitions", () => { - cy.dataCy("cq-override-radio-box").should("not.exist"); + it("Does not show a 'Go to repo settings' link on page", () => { + cy.dataCy("attached-repo-link").should("not.exist"); }); - - it("Updates the commit queue message", () => { - cy.dataCy("cq-message-input").type("Repo message"); + it("Inputting a display name then clicking save shows a success toast", () => { + cy.dataCy("display-name-input").type("evg"); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); }); + }); - it("Disables save button because Commit Queue definition is missing", () => { + describe("GitHub/Commit Queue page", () => { + beforeEach(() => { + cy.dataCy("navitem-github-commitqueue").click(); saveButtonEnabled(false); }); + describe("GitHub section", () => { + it("Shows an error banner when Commit Checks are enabled and hides it when Commit Checks are disabled", () => { + cy.dataCy("github-checks-enabled-radio-box") + .contains("label", "Enabled") + .click(); + cy.dataCy("error-banner") + .contains( + "A Commit Check Definition must be specified for this feature to run." + ) + .as("errorBanner"); + cy.get("@errorBanner").should("be.visible"); + cy.dataCy("github-checks-enabled-radio-box") + .contains("label", "Disabled") + .click(); + cy.get("@errorBanner").should("not.exist"); + }); - it("Adds a commit queue definition", () => { - cy.contains("button", "Add Commit Queue Patch Definition").click(); - cy.dataCy("variant-tags-input").last().type("cqvtag"); - cy.dataCy("task-tags-input").last().type("cqttag"); + it("Allows enabling manual PR testing", () => { + cy.dataCy("manual-pr-testing-enabled-radio-box") + .children() + .first() + .click(); + }); + it("Saving a patch defintion should hide the error banner, success toast and displays disable patch definitions for the repo", () => { + cy.contains( + "A GitHub Patch Definition must be specified for this feature to run." + ).as("errorBanner"); + cy.get("@errorBanner").should("be.visible"); + cy.contains("button", "Add Patch Definition").click(); + cy.get("@errorBanner").should("not.exist"); + saveButtonEnabled(false); + cy.dataCy("variant-tags-input").first().type("vtag"); + cy.dataCy("task-tags-input").first().type("ttag"); + saveButtonEnabled(true); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); + cy.visit(getGeneralRoute(projectUseRepoEnabled)); + cy.dataCy("navitem-github-commitqueue").click(); + cy.contains("Repo Patch Definition 1") + .as("patchDefAccordion") + .scrollIntoView(); + cy.get("@patchDefAccordion").click(); + cy.dataCy("variant-tags-input").should("have.value", "vtag"); + cy.dataCy("variant-tags-input").should("be.disabled"); + cy.dataCy("task-tags-input").should("have.value", "ttag"); + cy.dataCy("task-tags-input").should("be.disabled"); + cy.contains( + "A GitHub Patch Definition must be specified for this feature to run." + ).should("not.exist"); + }); }); - it("Successfully saves the page", () => { - cy.dataCy("warning-banner").should("not.exist"); - cy.dataCy("error-banner").should("not.exist"); - clickSave(); - cy.validateToast("success", "Successfully updated repo"); + describe("Commit Queue section", () => { + beforeEach(() => { + cy.dataCy("cq-enabled-radio-box") + .contains("label", "Enabled") + .as("enableCQButton") + .scrollIntoView(); + }); + it("Enabling commit queue shows hidden inputs and error banner", () => { + cy.dataCy("cq-card") + .children() + .as("cqCardFields") + .should("have.length", 2); + + cy.get("@enableCQButton").click(); + cy.get("@cqCardFields").should("have.length", 4); + cy.contains("Commit Queue Patch Definitions").scrollIntoView(); + cy.dataCy("error-banner") + .contains( + "A Commit Queue Patch Definition must be specified for this feature to run." + ) + .should("be.visible"); + }); + + it("Shows merge method only if merge queue is Evergreen", () => { + cy.get("@enableCQButton").click(); + // Evergreen is the default value + cy.getInputByLabel("Evergreen").should("be.checked"); + const selectId = "merge-method-select"; + cy.dataCy(selectId).as("mergeMethodDropdown").scrollIntoView(); + cy.dataCy(selectId).should("be.visible"); + // Click GitHub + cy.contains("label", "GitHub").click(); + cy.getInputByLabel("GitHub").should("be.checked"); + + // Hides merge method for GitHub. + cy.get("mergeMethodDropdown").should("not.exist"); + // Shows merge method for Evergreen. + cy.contains("label", "Evergreen").click(); + cy.getInputByLabel("Evergreen").should("be.checked"); + cy.get("@mergeMethodDropdown").should("be.visible"); + }); + + it("Does not show override buttons for commit queue patch definitions", () => { + cy.get("@enableCQButton").click(); + cy.getInputByLabel("Evergreen").should("be.checked"); + cy.dataCy("cq-override-radio-box").should("not.exist"); + }); + + it("Saves a commit queue definition and uses the repo message as placeholder for a project setting ", () => { + cy.get("@enableCQButton").click(); + cy.dataCy("cq-message-input").type("Repo message wohoo!"); + cy.contains("button", "Add Patch Definition").click(); + cy.dataCy("variant-tags-input").first().type("vtag"); + cy.dataCy("task-tags-input").first().type("ttag"); + saveButtonEnabled(false); + cy.contains("button", "Add Commit Queue Patch Definition").click(); + cy.dataCy("variant-tags-input").last().type("cqvtag"); + cy.dataCy("task-tags-input").last().type("cqttag"); + cy.dataCy("warning-banner").should("not.exist"); + cy.dataCy("error-banner").should("not.exist"); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); + cy.visit(getGeneralRoute(projectUseRepoEnabled)); + cy.dataCy("navitem-github-commitqueue").click(); + cy.dataCy("cq-message-input").should( + "have.attr", + "placeholder", + "Repo message wohoo! (Default from repo)" + ); + }); }); }); describe("Patch Aliases page", () => { - before(() => { + beforeEach(() => { cy.dataCy("navitem-patch-aliases").click(); - }); - - it("Should not have the save button enabled on load", () => { saveButtonEnabled(false); - }); - - it("Does not show override buttons for patch aliases", () => { cy.dataCy("patch-aliases-override-radio-box").should("not.exist"); }); - it("Prevents saving an incomplete patch alias", () => { + it("Saving a patch alias shows a success toast, the alias name in the card title and in the repo defaulted project", () => { cy.dataCy("add-button").contains("Add Patch Alias").parent().click(); cy.dataCy("expandable-card-title").contains("New Patch Alias"); - cy.dataCy("alias-input").type("my alias name"); saveButtonEnabled(false); - }); - - it("Successfully saves a complete alias", () => { cy.dataCy("variant-tags-input").first().type("alias variant tag"); cy.dataCy("task-tags-input").first().type("alias task tag"); clickSave(); cy.validateToast("success", "Successfully updated repo"); - }); - - it("Shows the alias name in the card title upon save", () => { cy.dataCy("expandable-card-title").contains("my alias name"); + // Verify persistence + cy.reload(); + cy.dataCy("expandable-card-title").contains("my alias name"); + cy.visit(getAccessRoute(projectUseRepoEnabled)); + cy.dataCy("default-to-repo-button").click(); + cy.dataCy("default-to-repo-modal").should("be.visible"); + cy.dataCy("default-to-repo-modal").contains("button", "Confirm").click(); + cy.validateToast("success", "Successfully defaulted page to repo"); + cy.dataCy("navitem-patch-aliases").click(); + cy.dataCy("expandable-card-title").contains("my alias name"); + cy.dataCy("expandable-card-title") + .parentsUntil("div") + .first() + .click({ force: true }); + cy.dataCy("expandable-card").find("input").should("be.disabled"); + cy.dataCy("expandable-card").find("button").should("be.disabled"); }); - it("Saves a Patch Trigger Alias", () => { + it("Saving a Patch Trigger Alias shows a success toast and updates the Github/Commit Queue page", () => { cy.dataCy("add-button") .contains("Add Patch Trigger Alias") .parent() @@ -290,21 +295,23 @@ describe("Repo Settings", { testIsolation: false }, () => { cy.contains("button", "Variant/Task").click(); cy.dataCy("variant-regex-input").type(".*"); cy.dataCy("task-regex-input").type(".*"); - cy.dataCy("github-trigger-alias-checkbox").check({ force: true }); - + cy.getInputByLabel("Add to GitHub Trigger Alias").as( + "triggerAliasCheckbox" + ); + cy.get("@triggerAliasCheckbox").should("not.be.checked"); + cy.get("@triggerAliasCheckbox").check({ force: true }); + cy.get("@triggerAliasCheckbox").should("be.checked"); clickSave(); cy.validateToast("success", "Successfully updated repo"); saveButtonEnabled(false); - }); - - it("Should be possible to return to a deselected state for Wait On", () => { + // Demonstrate Wait on field is optional cy.selectLGOption("Wait on", "Success"); cy.getInputByLabel("Wait on").should( "have.attr", "aria-invalid", "false" ); - saveButtonEnabled(); + saveButtonEnabled(true); cy.selectLGOption("Wait on", "Select event…"); cy.getInputByLabel("Wait on").should( "have.attr", @@ -312,690 +319,618 @@ describe("Repo Settings", { testIsolation: false }, () => { "false" ); saveButtonEnabled(false); + // Verify information on Github/Commit Queue page + cy.dataCy("navitem-github-commitqueue").click(); + cy.contains("GitHub Trigger Aliases").scrollIntoView(); + cy.dataCy("pta-item").should("have.length", 1); + cy.contains("my-alias").should("be.visible"); + cy.dataCy("pta-item").trigger("mouseover"); + cy.dataCy("pta-tooltip").should("be.visible"); + cy.dataCy("pta-tooltip").contains("spruce"); + cy.dataCy("pta-tooltip").contains("module_name"); + cy.dataCy("pta-tooltip").contains("Variant/Task Regex Pairs"); }); }); - describe("Virtual Workstation page", { testIsolation: false }, () => { - before(() => { + describe("Virtual Workstation page", () => { + beforeEach(() => { cy.dataCy("navitem-virtual-workstation").click(); }); - it("Adds two commands", () => { + it("Adds two commands and then reorders them", () => { saveButtonEnabled(false); - - cy.dataCy("add-button").click({ force: true }); + cy.dataCy("add-button").click(); cy.dataCy("command-input").type("command 1"); cy.dataCy("directory-input").type("mongodb.user.directory"); - cy.dataCy("add-button").click({ force: true }); + cy.dataCy("add-button").click(); cy.dataCy("command-input").eq(1).type("command 2"); - clickSave(); cy.validateToast("success", "Successfully updated repo"); - }); - - it("Reorders the commands", () => { cy.dataCy("array-down-button").click(); - cy.dataCy("save-settings-button").scrollIntoView(); clickSave(); cy.validateToast("success", "Successfully updated repo"); - cy.dataCy("command-input").first().should("have.value", "command 2"); cy.dataCy("command-input").eq(1).should("have.value", "command 1"); }); }); +}); - describe("GitHub/Commit Queue page after adding patch trigger alias", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - cy.reload(); - }); +describe("Project Settings when not defaulting to repo", () => { + const origin = getGeneralRoute(project); - it("Shows the patch trigger alias", () => { - cy.contains("GitHub Trigger Aliases").scrollIntoView(); - cy.dataCy("pta-item").should("have.length", 1); - cy.contains("my-alias").should("be.visible"); - }); + beforeEach(() => { + cy.visit(origin); + saveButtonEnabled(false); + }); - it("Hovering over the alias name shows its details", () => { - cy.dataCy("pta-item").trigger("mouseover"); - cy.dataCy("pta-tooltip").should("be.visible"); - cy.dataCy("pta-tooltip").contains("spruce"); - cy.dataCy("pta-tooltip").contains("module_name"); - cy.dataCy("pta-tooltip").contains("Variant/Task Regex Pairs"); - }); + it("Does not show a 'Default to Repo' button on page", () => { + cy.dataCy("default-to-repo-button").should("not.exist"); }); -}); -describe( - "Project Settings when not defaulting to repo", - { testIsolation: false }, - () => { - const destination = getGeneralRoute(project); + it("Shows two radio boxes", () => { + cy.dataCy("enabled-radio-box").children().should("have.length", 2); + }); + + it("Successfully attaches to and detaches from a repo that does not yet exist and shows 'Default to Repo' options", () => { + cy.dataCy("attach-repo-button").click(); + cy.dataCy("attach-repo-modal") + .find("button") + .contains("Attach") + .parent() + .click(); + cy.validateToast("success", "Successfully attached to repo"); + cy.dataCy("attach-repo-button").click(); + cy.dataCy("attach-repo-modal") + .find("button") + .contains("Detach") + .parent() + .click(); + cy.validateToast("success", "Successfully detached from repo"); + }); - before(() => { - cy.visit(destination); + describe("Variables page", () => { + beforeEach(() => { + cy.dataCy("navitem-variables").click(); }); it("Should not have the save button enabled on load", () => { saveButtonEnabled(false); }); - it("Does not show a 'Default to Repo' button on page", () => { - cy.dataCy("default-to-repo-button").should("not.exist"); + it("Should not show the move variables button", () => { + cy.dataCy("promote-vars-button").should("not.exist"); }); - it("Shows two radio boxes", () => { - cy.dataCy("enabled-radio-box").children().should("have.length", 2); + it("Should redact and disable private variables on saving", () => { + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").type("sample_name"); + saveButtonEnabled(false); + cy.dataCy("var-value-input").type("sample_value"); + cy.contains("label", "Private").click(); + cy.dataCy("var-private-input").should("be.checked"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("var-value-input").should("have.value", "{REDACTED}"); + cy.dataCy("var-name-input").should("be.disabled"); + cy.dataCy("var-value-input").should("be.disabled"); + cy.dataCy("var-private-input").should("be.disabled"); + cy.dataCy("var-admin-input").should("be.disabled"); + }); + + it("Typing a duplicate variable name will disable saving and show an error message", () => { + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").type("sample_name"); + cy.dataCy("var-value-input").type("sample_value"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("sample_name"); + cy.dataCy("var-value-input").first().type("sample_value_2"); + cy.contains("Value already appears in project variables.").as( + "errorMessage" + ); + saveButtonEnabled(false); + // Undo variable duplication + cy.dataCy("var-name-input").first().type("_2"); + saveButtonEnabled(); + cy.get("@errorMessage").should("not.exist"); }); - it("Successfully attaches to a repo that does not yet exist and shows 'Default to Repo' options", () => { - cy.dataCy("attach-repo-button").click(); - cy.dataCy("attach-repo-modal") - .find("button") - .contains("Attach") - .parent() - .click(); - cy.validateToast("success", "Successfully attached to repo"); + it("Should correctly save an admin only variable", () => { + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("admin_var"); + cy.dataCy("var-value-input").first().type("admin_value"); + cy.contains("label", "Admin Only").click(); + cy.dataCy("var-admin-input").should("be.checked"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + }); + + it("Should persist saved variables and allow deletion", () => { + // Add variables + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").type("sample_name"); + cy.dataCy("var-value-input").type("sample_value"); + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("sample_name_2"); + cy.dataCy("var-value-input").first().type("sample_value"); + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("admin_var"); + cy.dataCy("var-value-input").first().type("admin_value"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + // Verify persistence + cy.reload(); + cy.dataCy("var-name-input").eq(0).should("have.value", "admin_var"); + cy.dataCy("var-name-input").eq(1).should("have.value", "sample_name"); + cy.dataCy("var-name-input").eq(2).should("have.value", "sample_name_2"); + // Verify deletion + cy.dataCy("delete-item-button").first().click(); + cy.dataCy("delete-item-button").first().click(); + cy.dataCy("delete-item-button").first().click(); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("var-name-input").should("not.exist"); + // Verify persistence + cy.reload(); + cy.dataCy("var-name-input").should("not.exist"); }); + }); - it("Successfully detaches from repo", () => { - cy.dataCy("attach-repo-button").click(); - cy.dataCy("attach-repo-modal") - .find("button") - .contains("Detach") - .parent() - .click(); - cy.validateToast("success", "Successfully detached from repo"); + describe("GitHub/Commit Queue page", () => { + beforeEach(() => { + cy.dataCy("navitem-github-commitqueue").click(); }); - describe("Variables page", () => { - before(() => { - cy.dataCy("navitem-variables").click(); - }); + it("Allows adding a git tag alias", () => { + cy.dataCy("git-tag-enabled-radio-box").children().first().click(); + cy.dataCy("add-button").contains("Add Git Tag").parent().click(); + cy.dataCy("git-tag-input").type("myGitTag"); + cy.dataCy("remote-path-input").type("./evergreen.yml"); - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("remote-path-input").should("have.value", "./evergreen.yml"); + }); + }); - it("Should not show the move variables button", () => { - cy.dataCy("promote-vars-button").should("not.exist"); - }); + describe("Periodic Builds page", () => { + beforeEach(() => { + cy.dataCy("navitem-periodic-builds").click(); + }); - it("Should not enable save when the value field is empty", () => { - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").type("sample_name"); - saveButtonEnabled(false); - }); + it("Disables save button when interval is NaN or below minimum and allows saving a number in range", () => { + cy.dataCy("add-button").click(); + cy.dataCy("interval-input").as("intervalInput").type("NaN"); + cy.dataCy("config-file-input").type("config.yml"); + saveButtonEnabled(false); + cy.contains("Value should be a number."); + cy.get("@intervalInput").clear(); + cy.get("@intervalInput").type("0"); + saveButtonEnabled(false); + cy.get("@intervalInput").clear(); + cy.get("@intervalInput").type("12"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + }); + }); - it("Should correctly save a private variable", () => { - cy.dataCy("var-value-input").type("sample_value"); - cy.dataCy("var-private-input").check({ force: true }); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + describe("Project Triggers page", () => { + beforeEach(() => { + cy.dataCy("navitem-project-triggers").click(); + }); - it("Should redact and disable private variables on save", () => { - cy.dataCy("var-value-input").should("have.value", "{REDACTED}"); - cy.dataCy("var-name-input").should("be.disabled"); - cy.dataCy("var-value-input").should("be.disabled"); - cy.dataCy("var-private-input").should("be.disabled"); - cy.dataCy("var-admin-input").should("be.disabled"); - }); + it("Saves a project trigger", () => { + cy.dataCy("add-button").click(); + cy.dataCy("project-input").should("be.visible").should("not.be.disabled"); + cy.dataCy("project-input").type("spruce"); + cy.dataCy("config-file-input").type(".evergreen.yml"); + }); + }); +}); - it("Should error when a duplicate variable name is entered and disable saving", () => { - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").first().type("sample_name"); - cy.dataCy("var-value-input").first().type("sample_value_2"); - cy.contains("Value already appears in project variables."); - saveButtonEnabled(false); - }); +describe("Project Settings when defaulting to repo", () => { + const origin = getGeneralRoute(projectUseRepoEnabled); - it("Should remove the error and enable save when the value changes", () => { - cy.dataCy("var-name-input").first().type("_2"); - saveButtonEnabled(); - cy.contains("Value already appears in project variables.").should( - "not.exist" - ); - }); + beforeEach(() => { + cy.visit(origin); + }); - it("Should correctly save an admin only variable", () => { - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").first().type("admin_var"); - cy.dataCy("var-value-input").first().type("admin_value"); - cy.dataCy("var-admin-input").first().check({ force: true }); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + describe("General Settings page", () => { + it("Save button is disabled on load and shows a link to the repo", () => { + saveButtonEnabled(false); + cy.dataCy("attached-repo-link") + .should("have.attr", "href") + .and("eq", `/${getGeneralRoute(repo)}`); + }); - it("Should show three populated fields when navigating back from another page", () => { - cy.dataCy("navitem-access").click(); - cy.dataCy("navitem-variables").click(); - cy.dataCy("var-name-input").eq(0).should("have.value", "admin_var"); - cy.dataCy("var-name-input").eq(1).should("have.value", "sample_name"); - cy.dataCy("var-name-input").eq(2).should("have.value", "sample_name_2"); - }); + it("Preserves edits to the form when navigating between settings tabs and does not show a warning modal", () => { + cy.dataCy("spawn-host-input").should("have.value", "/path"); + cy.dataCy("spawn-host-input").type("/test"); + saveButtonEnabled(); + cy.dataCy("navitem-access").click(); + cy.dataCy("navigation-warning-modal").should("not.exist"); + cy.dataCy("navitem-general").click(); + cy.dataCy("spawn-host-input").should("have.value", "/path/test"); + saveButtonEnabled(); + }); - it("Should allow deleting all items", () => { - cy.dataCy("delete-item-button").first().click(); - cy.dataCy("delete-item-button").first().click(); - cy.dataCy("delete-item-button").first().click(); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + it("Shows a 'Default to Repo' button on page", () => { + cy.dataCy("default-to-repo-button").should("exist"); + }); - it("Should show no variables after deleting", () => { - cy.dataCy("var-name-input").should("not.exist"); - }); + it("Shows only two radio boxes even when rendering a project that inherits from repo", () => { + cy.dataCy("enabled-radio-box").children().should("have.length", 2); }); - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - }); + it("Does not default to repo value for display name", () => { + cy.dataCy("display-name-input").should("not.have.attr", "placeholder"); + }); - it("Allows adding a git tag alias", () => { - cy.dataCy("git-tag-enabled-radio-box").children().first().click(); - cy.dataCy("add-button").contains("Add Git Tag").parent().click(); - cy.dataCy("git-tag-input").type("myGitTag"); - cy.dataCy("remote-path-input").type("./evergreen.yml"); - - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Shows the saved Git Tag", () => { - cy.dataCy("remote-path-input").should("have.value", "./evergreen.yml"); + it("Shows a navigation warning modal that lists the general page when navigating away from project settings", () => { + cy.dataCy("spawn-host-input").type("/test"); + saveButtonEnabled(); + cy.contains("My Patches").click(); + cy.dataCy("navigation-warning-modal").should("be.visible"); + cy.dataCy("unsaved-pages").within(() => { + cy.get("li").should("have.length", 1); }); + cy.get("body").type("{esc}"); }); - describe("Periodic Builds page", () => { - before(() => { - cy.dataCy("navitem-periodic-builds").click({ force: true }); - }); - - it("Does not allow saving when interval is not a number", () => { - cy.dataCy("add-button").click(); - cy.dataCy("interval-input").type("NaN"); - cy.dataCy("config-file-input").type("config.yml"); - saveButtonEnabled(false); - cy.contains("Value should be a number."); - }); - - it("Does not allow saving when interval is below minimum", () => { - cy.dataCy("interval-input").clear().type("0"); - saveButtonEnabled(false); - }); - - it("Saves when a number is entered", () => { - cy.dataCy("interval-input").clear().type("12"); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + it("Shows the repo value for Batch Time", () => { + cy.dataCy("batch-time-input").should("have.attr", "placeholder"); }); - describe("Project Triggers page", () => { - before(() => { - cy.dataCy("navitem-project-triggers").click({ force: true }); - }); - - it("Saves a project trigger", () => { - cy.dataCy("add-button").click(); - cy.dataCy("project-input") - .should("be.visible") - .should("not.be.disabled"); - cy.dataCy("project-input").type("spruce"); - cy.dataCy("config-file-input").type(".evergreen.yml"); - }); + it("Clicking on save button should show a success toast", () => { + cy.dataCy("spawn-host-input").type("/test"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); }); - } -); -describe( - "Project Settings when defaulting to repo", - { testIsolation: false }, - () => { - const destination = getGeneralRoute(projectUseRepoEnabled); - - before(() => { - cy.visit(destination); + it("Saves when batch time is updated", () => { + cy.dataCy("batch-time-input").type("12"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); }); + }); - describe("General Settings page", () => { - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); - - it("Shows a link to the repo", () => { - cy.dataCy("attached-repo-link") - .should("have.attr", "href") - .and("eq", `/${getGeneralRoute(repo)}`); - }); - - it("Preserves edits to the form when navigating between settings tabs and does not show a warning modal", () => { - cy.dataCy("spawn-host-input").should("have.value", "/path"); - cy.dataCy("spawn-host-input").type("/test"); - saveButtonEnabled(); - cy.dataCy("navitem-access").click(); - cy.dataCy("navigation-warning-modal").should("not.exist"); - cy.dataCy("navitem-general").click(); - cy.dataCy("spawn-host-input").should("have.value", "/path/test"); - }); - - it("Enables the save button", () => { - saveButtonEnabled(); - }); - - it("Shows a 'Default to Repo' button on page", () => { - cy.dataCy("default-to-repo-button").should("exist"); - }); - - it("Shows only two radio boxes even when rendering a project that inherits from repo", () => { - cy.dataCy("enabled-radio-box").children().should("have.length", 2); - }); - - it("Does not default to repo value for display name", () => { - cy.dataCy("display-name-input").should("not.have.attr", "placeholder"); - }); - - it("Shows a navigation warning modal that lists the general page when navigating away from project settings", () => { - cy.contains("My Patches").click(); - cy.dataCy("navigation-warning-modal").should("be.visible"); - cy.dataCy("unsaved-pages").within(() => { - cy.get("li").should("have.length", 1); - }); - cy.get("body").type("{esc}"); - }); - - it("Shows the repo value for Batch Time", () => { - cy.dataCy("batch-time-input").should("have.attr", "placeholder"); - }); - - it("Clicking on save button should show a success toast", () => { - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Saves when batch time is updated", () => { - cy.dataCy("batch-time-input").type("12"); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + describe("Variables page", () => { + beforeEach(() => { + cy.dataCy("navitem-variables").click(); + saveButtonEnabled(false); }); - describe("Variables page", () => { - before(() => { - cy.dataCy("navitem-variables").click(); - }); - - it("Successfully saves variables", () => { - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").type("a"); - cy.dataCy("var-value-input").type("1"); - cy.dataCy("var-private-input").check({ force: true }); + it("Successfully saves variables and then promotes them using the promote variables modal", () => { + // Save variables + cy.dataCy("add-button").should("be.visible").click(); + cy.dataCy("var-name-input").type("a"); + cy.dataCy("var-value-input").type("1"); + cy.contains("label", "Private").click(); - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").first().type("b"); - cy.dataCy("var-value-input").first().type("2"); + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("b"); + cy.dataCy("var-value-input").first().type("2"); - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").first().type("c"); - cy.dataCy("var-value-input").first().type("3"); + cy.dataCy("add-button").click(); + cy.dataCy("var-name-input").first().type("c"); + cy.dataCy("var-value-input").first().type("3"); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Opens the modal and promotes variables", () => { - cy.dataCy("promote-vars-modal").should("not.exist"); - cy.dataCy("promote-vars-button").click(); - cy.dataCy("promote-vars-modal").should("be.visible"); - cy.dataCy("promote-var-checkbox").first().check({ force: true }); - cy.contains("button", "Move 1 variable").click(); - cy.validateToast("success"); - }); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + // Promote variables + cy.dataCy("promote-vars-modal").should("not.exist"); + cy.dataCy("promote-vars-button").click(); + cy.dataCy("promote-vars-modal").should("be.visible"); + cy.dataCy("promote-var-checkbox").first().check({ force: true }); + cy.contains("button", "Move 1 variable").click(); + cy.validateToast("success", "Successfully moved variables to repo"); }); + }); - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - }); - - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); + describe("GitHub/Commit Queue page", () => { + beforeEach(() => { + cy.dataCy("navitem-github-commitqueue").click(); + }); - it("Shows the repo's disabled patch definition", () => { - cy.dataCy("accordion-toggle").should("exist"); - cy.dataCy("accordion-toggle").first().click(); - cy.dataCy("variant-tags-input").should("have.value", "vtag"); - cy.dataCy("variant-tags-input").should("be.disabled"); - cy.dataCy("task-tags-input").should("have.value", "ttag"); - cy.dataCy("task-tags-input").should("be.disabled"); - }); + it("Should not have the save button enabled on load", () => { + saveButtonEnabled(false); + }); - it("Does not show an error banner when a patch definition is defined in the repo", () => { - cy.contains( + it("Allows overriding repo patch definitions", () => { + cy.dataCy("pr-testing-enabled-radio-box") + .find("label") + .should("have.length", 3); + cy.contains("label", "Override Repo Patch Definition").click(); + cy.dataCy("error-banner") + .contains( "A GitHub Patch Definition must be specified for this feature to run." - ).should("not.exist"); - }); - - it("Allows overriding repo patch definitions", () => { - cy.getInputByLabel("Override Repo Patch Definition").first().click({ - force: true, - }); - cy.dataCy("error-banner") - .contains( - "A GitHub Patch Definition must be specified for this feature to run." - ) - .should("exist"); - cy.dataCy("add-button") - .contains("Add Patch Definition") - .parent() - .click(); - cy.dataCy("variant-input-control") - .find("button") - .contains("Regex") - .click(); - cy.dataCy("variant-input").first().type(".*"); - }); - - it("Disables save when the task field is empty", () => { - saveButtonEnabled(false); - }); - - it("Does not clear tag/regex fields when toggling between them", () => { - cy.contains("button", "Tags").first().click(); - cy.contains("button", "Regex").first().click(); - - cy.dataCy("variant-input").should("have.value", ".*"); - }); - - it("Should enable save when the task and variant fields are filled in", () => { - cy.dataCy("task-input-control") - .find("button") - .contains("Regex") - .click(); - cy.dataCy("task-input").first().type(".*"); - saveButtonEnabled(); - }); - - it("Shows a warning banner when a commit check definition does not exist", () => { - cy.dataCy("github-checks-enabled-radio-box").within(($el) => { - cy.wrap($el).getInputByLabel("Enabled").parent().click(); - }); - - cy.dataCy("warning-banner") - .contains( - "This feature will only run if a Commit Check Definition is defined in the project or repo." - ) - .should("exist"); - }); - - it("Disables Authorized Users section based on repo settings", () => { - cy.contains("Authorized Users").should("not.exist"); - cy.contains("Authorized Teams").should("not.exist"); - }); - - it("Displays the repo's merge method as its default", () => { - cy.get("button[name=merge-method-select]").should( - "have.text", - "Default to Repo (squash)" - ); - }); - - it("Shows the repo's commit queue message as a placeholder when the field is cleared", () => { - cy.dataCy("cq-message-input").clear(); - cy.dataCy("cq-message-input").should( - "have.attr", - "placeholder", - "Repo message (Default from repo)" - ); - }); - - it("Defaults to overriding repo since a patch definition is defined", () => { - cy.dataCy("cq-override-radio-box") - .find("input") - .first() - .should("be.checked"); - }); - - it("Shows the existing patch definition", () => { - cy.dataCy("variant-input").last().should("have.value", "^ubuntu1604$"); - cy.dataCy("task-input") - .last() - .should("have.value", "^smoke-test-endpoints$"); - }); - - it("Returns an error on save because no commit check definitions are defined", () => { - clickSave(); - cy.validateToast("error"); - }); - - it("Disabling commit checks saves successfully", () => { - cy.dataCy("github-checks-enabled-radio-box").within(($el) => { - cy.wrap($el).getInputByLabel("Disabled").parent().click(); - }); - - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Defaults to repo", () => { - cy.dataCy("default-to-repo-button").click(); - cy.dataCy("default-to-repo-modal").should("be.visible"); - cy.dataCy("default-to-repo-modal") - .contains("button", "Confirm") - .click(); - cy.validateToast("success", "Successfully defaulted page to repo"); - }); + ) + .should("exist"); + cy.contains("button", "Add Patch Definition").click(); - it("Again shows the repo's disabled patch definition", () => { - cy.dataCy("accordion-toggle").should("exist"); - cy.dataCy("accordion-toggle").contains("Patch Definition 1"); - }); + cy.dataCy("variant-input-control") + .find("button") + .contains("Regex") + .click(); + cy.dataCy("variant-input").first().type(".*"); + saveButtonEnabled(false); + // Persist input value when toggling inputs + cy.contains("button", "Tags").first().click(); + cy.contains("button", "Regex").first().click(); + cy.dataCy("variant-input").should("have.value", ".*"); + cy.dataCy("task-input-control").find("button").contains("Regex").click(); + cy.dataCy("task-input").first().type(".*"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); }); - describe("Patch Aliases page", () => { - before(() => { - cy.dataCy("navitem-patch-aliases").click(); - }); - - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); - - it("Defaults to repo patch aliases", () => { - cy.getInputByLabel("Default to Repo Patch Aliases").should( - "have.attr", - "checked" - ); - }); - - it("Shows the saved repo patch alias", () => { - cy.dataCy("expandable-card-title").contains("my alias name"); - }); - - it("Displays disabled fields when the card is expanded", () => { - cy.dataCy("expandable-card-title") - .parentsUntil("div") - .first() - .click({ force: true }); - cy.dataCy("expandable-card").find("input").should("be.disabled"); - cy.dataCy("expandable-card").find("button").should("be.disabled"); - }); - - it("Allows adding a patch alias", () => { - cy.getInputByLabel("Override Repo Patch Aliases").click({ - force: true, - }); - saveButtonEnabled(false); - - cy.dataCy("add-button") - .contains("Add Patch Alias") - .parent() - .click({ force: true }); - saveButtonEnabled(false); + it("Shows a warning banner when a commit check definition does not exist", () => { + cy.contains("Default to repo (disabled)").should("be.visible"); + cy.dataCy("github-checks-enabled-radio-box").scrollIntoView(); + cy.dataCy("github-checks-enabled-radio-box") + .contains("label", "Enabled") + .click(); - cy.dataCy("alias-input").type("my overriden alias name"); + cy.dataCy("warning-banner") + .contains( + "This feature will only run if a Commit Check Definition is defined in the project or repo." + ) + .should("exist"); + }); - cy.dataCy("variant-tags-input").first().type("alias variant tag 2"); + it("Disables Authorized Users section based on repo settings", () => { + cy.contains("Authorized Users").should("not.exist"); + cy.contains("Authorized Teams").should("not.exist"); + }); - cy.dataCy("task-tags-input").first().type("alias task tag 2"); - cy.dataCy("add-button").contains("Add Task Tag").parent().click(); - cy.dataCy("task-tags-input").first().type("alias task tag 3"); + it("Displays the repo's merge method as its default", () => { + cy.get("button[name=merge-method-select]").should( + "have.text", + "Default to Repo (squash)" + ); + }); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); + it("Defaults to overriding repo since a patch definition is defined", () => { + cy.dataCy("cq-override-radio-box") + .find("input") + .first() + .should("be.checked"); + }); - it("Allows defaulting to repo patch aliases", () => { - cy.getInputByLabel("Default to Repo Patch Aliases").click({ - force: true, - }); + it("Shows the existing patch definition", () => { + cy.dataCy("variant-input").last().should("have.value", "^ubuntu1604$"); + cy.dataCy("task-input") + .last() + .should("have.value", "^smoke-test-endpoints$"); + }); - clickSave(); - cy.validateToast("success", "Successfully updated project"); + it("Returns an error on save because no commit check definitions are defined", () => { + // Ensure page has loaded + cy.dataCy("pr-testing-enabled-radio-box") + .contains("label", "Default to repo (enabled)") + .should("be.visible"); + cy.dataCy("pr-testing-enabled-radio-box") + .contains("label", "Disabled") + .click(); + cy.dataCy("manual-pr-testing-enabled-radio-box") + .contains("label", "Disabled") + .click(); + cy.dataCy("github-checks-enabled-radio-box") + .contains("label", "Enabled") + .click(); + clickSave(); + cy.validateToast( + "error", + "There was an error saving the project: GitHub checks cannot be enabled without aliases" + ); + }); - saveButtonEnabled(false); - cy.dataCy("expandable-card-title").contains("my alias name"); - }); + it("Defaults to repo and shows the repo's disabled patch definition", () => { + cy.dataCy("accordion-toggle") + .contains("Repo Patch Definition 1") + .should("not.exist"); + // Save a repo patch definition + cy.visit(getGeneralRoute(repo)); + cy.dataCy("navitem-github-commitqueue").click(); + cy.contains("button", "Add Patch Definition").click(); + cy.dataCy("variant-tags-input").first().type("vtag"); + cy.dataCy("task-tags-input").first().type("ttag"); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); + cy.visit(origin); + cy.dataCy("navitem-github-commitqueue").click(); + cy.dataCy("default-to-repo-button").click(); + cy.dataCy("default-to-repo-modal").should("be.visible"); + cy.dataCy("default-to-repo-modal").contains("button", "Confirm").click(); + cy.validateToast("success", "Successfully defaulted page to repo"); + cy.dataCy("accordion-toggle").scrollIntoView(); + cy.dataCy("accordion-toggle") + .should("be.visible") + .contains("Repo Patch Definition 1"); + }); + }); - it("Has cleared previously saved alias definitions", () => { - cy.getInputByLabel("Override Repo Patch Aliases").click({ - force: true, - }); - cy.dataCy("alias-row").should("have.length", 0); - }); + describe("Patch Aliases page", () => { + beforeEach(() => { + cy.dataCy("navitem-patch-aliases").click(); + saveButtonEnabled(false); }); - describe("Virtual Workstation page", () => { - before(() => { - cy.dataCy("navitem-virtual-workstation").click(); - }); + it("Defaults to repo patch aliases", () => { + cy.getInputByLabel("Default to Repo Patch Aliases").should( + "have.attr", + "checked" + ); + }); - it("Shows repo commands", () => { - cy.dataCy("add-button").should("not.exist"); - cy.dataCy("command-row").should("have.length", 2); - cy.dataCy("command-row").each(() => { - cy.get("input").should("be.disabled"); - cy.get("textarea").should("be.disabled"); - }); - }); + it("Patch aliases added before defaulting to repo patch aliases are cleared", () => { + // Override repo patch alias and add a patch alias. + cy.contains("label", "Override Repo Patch Aliases") + .should("be.visible") + .click(); + cy.getInputByLabel("Override Repo Patch Aliases").should( + "have.attr", + "aria-checked", + "true" + ); + saveButtonEnabled(false); + cy.dataCy("add-button") + .contains("Add Patch Alias") + .parent() + .click({ force: true }); + saveButtonEnabled(false); + cy.dataCy("alias-input").type("my overriden alias name"); + cy.dataCy("variant-tags-input").first().type("alias variant tag 2"); + cy.dataCy("task-tags-input").first().type("alias task tag 2"); + cy.dataCy("add-button").contains("Add Task Tag").parent().click(); + cy.dataCy("task-tags-input").first().type("alias task tag 3"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + // Default to repo patch alias + cy.contains("label", "Default to Repo Patch Aliases").click(); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + saveButtonEnabled(false); + // Aliases are cleared + cy.contains("label", "Override Repo Patch Aliases").click(); + cy.dataCy("alias-row").should("have.length", 0); + }); + }); - it("Allows overriding without adding a command", () => { - cy.getInputByLabel("Override Repo Commands").click({ force: true }); + describe("Virtual Workstation page", () => { + beforeEach(() => { + cy.dataCy("navitem-virtual-workstation").click(); + }); - clickSave(); - cy.validateToast("success", "Successfully updated project"); + it("Enable git clone", () => { + cy.contains("label", "Enabled").click(); + cy.getInputByLabel("Enabled").should("be.checked"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + }); + it("Add commands", () => { + // Repo commands should be visible on project page based on button selection + cy.getInputByLabel("Default to repo (disabled)").should("be.checked"); + cy.dataCy("command-row").should("not.exist"); + cy.dataCy("attached-repo-link").click(); + cy.location("pathname").should( + "equal", + `/${getVirtualWorkstationRoute(repo)}` + ); + cy.contains("button", "Add Command").click(); + cy.dataCy("command-input").type("a repo command"); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); + // Go to project page + cy.visit(origin); + cy.dataCy("navitem-virtual-workstation").click(); + cy.dataCy("command-row") + .contains("textarea", "a repo command") + .should("be.disabled"); + // Override commands, add a command, default to repo then show override commands are cleared + cy.contains("label", "Override Repo Commands") + .as("overrideRepoCommandsButton") + .click(); + cy.dataCy("command-row").should("not.exist"); + cy.contains("button", "Add Command").click(); + cy.dataCy("command-input").type("a project command"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("command-row") + .contains("textarea", "a project command") + .should("be.enabled"); + cy.contains("label", "Default to Repo Commands").click(); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("command-row") + .contains("textarea", "a repo command") + .should("be.disabled"); + cy.get("@overrideRepoCommandsButton").click(); + cy.dataCy("command-row").should("not.exist"); + }); - cy.getInputByLabel("Override Repo Commands").should("be.checked"); - }); + it("Allows overriding without adding a command", () => { + cy.contains("label", "Override Repo Commands").click(); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.getInputByLabel("Override Repo Commands").should("be.checked"); }); - } -); + }); +}); -describe("Attaching Spruce to a repo", { testIsolation: false }, () => { - const destination = getGeneralRoute(project); +describe("Attaching Spruce to a repo", () => { + const origin = getGeneralRoute(project); - before(() => { - cy.visit(destination); + beforeEach(() => { + cy.visit(origin); }); - it("Saves a new repo", () => { - cy.dataCy("repo-input").clear().type("evergreen"); - + it("Saves and attaches new repo and shows warnings on the Github/Commit Queue page", () => { + cy.dataCy("repo-input").as("repoInput").clear(); + cy.get("@repoInput").type("evergreen"); cy.dataCy("attach-repo-button").should( "have.attr", "aria-disabled", "true" ); - clickSave(); cy.validateToast("success", "Successfully updated project"); - }); - - it("Attaches to new repo", () => { cy.dataCy("attach-repo-button").click(); cy.dataCy("attach-repo-modal").contains("button", "Attach").click(); cy.validateToast("success", "Successfully attached to repo"); - }); - - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - }); - - it("Shows warnings about enabling PR Testing", () => { - cy.dataCy("pr-testing-enabled-radio-box") - .prev() - .dataCy("warning-banner") - .should("exist"); - cy.dataCy("manual-pr-testing-enabled-radio-box") - .prev() - .dataCy("warning-banner") - .should("exist"); - }); - - it("Doesn't show a warning about enabling commit checks because the feature is disabled", () => { - cy.dataCy("github-checks-enabled-radio-box").prev().should("not.exist"); - }); - - it("Shows a warning about enabling commit queue", () => { - cy.dataCy("cq-card").dataCy("warning-banner").should("exist"); - }); - - it("Shows an error banner about enabling commit queue if the feature is enabled", () => { - cy.dataCy("cq-enabled-radio-box").within(($el) => { - cy.wrap($el).getInputByLabel("Enabled").parent().click(); - }); - cy.dataCy("cq-card").dataCy("error-banner").should("exist"); + cy.dataCy("navitem-github-commitqueue").click(); + cy.dataCy("pr-testing-enabled-radio-box") + .prev() + .dataCy("warning-banner") + .should("exist"); + cy.dataCy("manual-pr-testing-enabled-radio-box") + .prev() + .dataCy("warning-banner") + .should("exist"); + cy.dataCy("github-checks-enabled-radio-box").prev().should("not.exist"); + cy.dataCy("cq-card").dataCy("warning-banner").should("exist"); + cy.dataCy("cq-enabled-radio-box").within(($el) => { + cy.wrap($el).getInputByLabel("Enabled").parent().click(); }); + cy.dataCy("cq-card").dataCy("error-banner").should("exist"); }); }); -describe("Renaming the identifier", { testIsolation: false }, () => { - const destination = getGeneralRoute(project); +describe("Renaming the identifier", () => { + const origin = getGeneralRoute(project); - before(() => { - cy.visit(destination); + beforeEach(() => { + cy.visit(origin); }); - it("Shows warning text when identifier is changed", () => { + it("Update identifier", () => { const warningText = "Updates made to the project identifier will change the identifier used for the CLI, inter-project dependencies, etc. Project users should be made aware of this change, as the old identifier will no longer work."; cy.dataCy("input-warning").should("not.exist"); - cy.dataCy("identifier-input").clear().type("new-identifier"); + cy.dataCy("identifier-input").clear(); + cy.dataCy("identifier-input").type("new-identifier"); cy.dataCy("input-warning").should("contain", warningText); - }); - - it("Successfully saves", () => { clickSave(); cy.validateToast("success", "Successfully updated project"); - }); - - it("Redirects to a new URL", () => { cy.url().should("include", "new-identifier"); }); }); -describe( - "A project that has GitHub webhooks disabled", - { testIsolation: false }, - () => { - const destination = getGithubCommitQueueRoute("logkeeper"); +describe("A project that has GitHub webhooks disabled", () => { + const origin = getGithubCommitQueueRoute("logkeeper"); - before(() => { - cy.visit(destination); - }); + beforeEach(() => { + cy.visit(origin); + }); - it("Disables all interactive elements on the page", () => { - cy.dataCy("project-settings-page") - .find("button") - .should("have.attr", "aria-disabled", "true"); - cy.get("input").should("be.disabled"); - }); - } -); + it("Disables all interactive elements on the page", () => { + cy.dataCy("project-settings-page") + .find("button") + .should("have.attr", "aria-disabled", "true"); + cy.get("input").should("be.disabled"); + }); +}); diff --git a/cypress/integration/spawn/volume.ts b/cypress/integration/spawn/volume.ts index 72fc3a52e4..20515086c8 100644 --- a/cypress/integration/spawn/volume.ts +++ b/cypress/integration/spawn/volume.ts @@ -89,7 +89,7 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { "1de2728dd9de82efc02dc21f6ca046eaa559462414d28e0b6bba6436436ac873" ).should("not.exist"); cy.dataCy("mounted-badge").contains("8 Mounted"); - cy.dataCy("free-badge").contains("3 Free"); + cy.dataCy("free-badge").contains("4 Free"); }); it("Clicking on unmount should result in a success toast appearing.", () => { diff --git a/cypress/integration/task/task_actions.ts b/cypress/integration/task/task_actions.ts index f6558fd043..9f89a6f3d2 100644 --- a/cypress/integration/task/task_actions.ts +++ b/cypress/integration/task/task_actions.ts @@ -1,4 +1,4 @@ -describe("Task Action Buttons", { testIsolation: false }, () => { +describe("Task Action Buttons", () => { describe("Based on the state of the task, some buttons should be disabled and others should be clickable. Clicking on buttons produces banners messaging if the action succeeded or failed.", () => { it("Schedule button should be disabled on a completed task", () => { cy.visit(tasks[1]); @@ -12,6 +12,7 @@ describe("Task Action Buttons", { testIsolation: false }, () => { }); it("Clicking Unschedule button should unschedule a task and display a success toast", () => { + cy.visit(tasks[5]); cy.dataCy("ellipsis-btn").click(); cy.dataCy("card-dropdown").should("be.visible"); cy.dataCy("unschedule-task").click(); @@ -19,13 +20,14 @@ describe("Task Action Buttons", { testIsolation: false }, () => { }); it("Abort button should be disabled on completed tasks", () => { + cy.visit(tasks[3]); cy.dataCy("ellipsis-btn").click(); cy.dataCy("card-dropdown").should("be.visible"); cy.dataCy("abort-task").should("have.attr", "disabled"); }); it("Clicking on set priority, entering a priority value and submitting should result in a success toast.", () => { - cy.visit(tasks[3]); + cy.visit(tasks[5]); cy.dataCy("ellipsis-btn").click(); cy.dataCy("card-dropdown").should("be.visible"); cy.dataCy("prioritize-task").click(); @@ -42,7 +44,7 @@ describe("Task Action Buttons", { testIsolation: false }, () => { }); it("Should correctly disable/enable the task when clicked", () => { - cy.visit(tasks[1]); + cy.visit(tasks[5]); cy.dataCy("ellipsis-btn").click(); cy.dataCy("card-dropdown").should("be.visible"); cy.dataCy("disable-enable").click(); @@ -67,10 +69,10 @@ describe("Task Action Buttons", { testIsolation: false }, () => { const prioritySuccessBannerText = "Priority for task updated to 99"; const restartSuccessBannerText = "Task scheduled to restart"; const unscheduleSuccessBannerText = "Task marked as unscheduled"; - const tasks = { 1: "/task/evergreen_ubuntu1604_test_model_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48", 2: "/task/evergreen_lint_lint_service_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48", 3: "/task/evergreen_ubuntu1604_dist_patch_33016573166a36bd5f46b4111151899d5c4e95b1_5ecedafb562343215a7ff297_20_05_27_21_39_46", 4: "/task/mci_ubuntu1604_display_asdf_patch_a1d2c8f70bf5c543de8b9641ac1ec08def1ddb26_5f74d99ab2373627c047c5e5_20_09_30_19_16_47/execution-tasks?execution=0&sorts=STATUS%3AASC", + 5: "/task/evergreen_test_model_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48", }; diff --git a/cypress/integration/task/task_route.ts b/cypress/integration/task/task_route.ts index 9bff02eff1..4121e310b7 100644 --- a/cypress/integration/task/task_route.ts +++ b/cypress/integration/task/task_route.ts @@ -13,7 +13,7 @@ describe("Task Page Route", () => { it("should display an appropriate status badge when visiting task pages", () => { cy.visit(`/task/${tasks[1]}`); - cy.dataCy("task-status-badge").contains("Aborted"); + cy.dataCy("task-status-badge").contains("Dispatched"); cy.visit(`/task/${tasks[2]}`); cy.dataCy("task-status-badge").contains("Running"); cy.visit(`/task/${tasks[3]}`); diff --git a/cypress/integration/task/test_table.ts b/cypress/integration/task/test_table.ts index 582a899c20..743bb6818d 100644 --- a/cypress/integration/task/test_table.ts +++ b/cypress/integration/task/test_table.ts @@ -5,9 +5,16 @@ import { } from "../../utils"; describe("Tests Table", () => { + const visitAndWait = (url: string) => { + cy.visit(url); + cy.dataCy("tests-table") + .should("be.visible") + .should("not.have.attr", "data-loading", "true"); + }; + beforeEach(() => { + visitAndWait(TESTS_ROUTE); + }); it("Test count should update to reflect filtered values", () => { - cy.visit(TESTS_ROUTE); - cy.contains(TABLE_SORT_SELECTOR, "Name").click(); cy.dataCy("filtered-count").contains(20); @@ -22,16 +29,15 @@ describe("Tests Table", () => { cy.toggleTableFilter(1); cy.dataCy("testname-input-wrapper") .find("input") - .focus() - .type("hello") - .type("{enter}"); + .as("testnameInputWrapper") + .focus(); + cy.get("@testnameInputWrapper").type("hello{enter}"); cy.dataCy("filtered-count").contains(0); cy.dataCy("total-count").contains(20); }); it("Adjusts query params when table headers are clicked", () => { - cy.visit(TESTS_ROUTE); cy.contains(TABLE_SORT_SELECTOR, "Name").click(); cy.location().should((loc) => { expect(loc.pathname).to.equal(TESTS_ROUTE); @@ -70,7 +76,7 @@ describe("Tests Table", () => { describe("Test Status Selector", () => { beforeEach(() => { - cy.visit(TESTS_ROUTE); + visitAndWait(TESTS_ROUTE); }); it("Clicking on 'All' checkbox adds all statuses to URL", () => { @@ -103,17 +109,16 @@ describe("Tests Table", () => { describe("Test Name Filter", () => { const testNameInputValue = "group"; - beforeEach(() => { - cy.visit(TESTS_ROUTE); - }); it("Typing in test name filter updates testname query param", () => { + visitAndWait(TESTS_ROUTE); cy.toggleTableFilter(1); cy.dataCy("testname-input-wrapper") .find("input") - .focus() - .type(testNameInputValue) - .type("{enter}"); + .as("testnameInputWrapper") + .focus(); + cy.get("@testnameInputWrapper").type(testNameInputValue); + cy.get("@testnameInputWrapper").type("{enter}"); cy.location().should((loc) => { expect(loc.search).to.include(`testname=${testNameInputValue}`); }); @@ -122,7 +127,7 @@ describe("Tests Table", () => { describe("Changing page number", () => { it("Displays the next page of results and updates URL when right arrow is clicked and next page exists", () => { - cy.visit(`${TESTS_ROUTE}?limit=10`); + visitAndWait(`${TESTS_ROUTE}?limit=10`); cy.dataCy("pagination").first().should("contain.text", "1 / 2"); clickOnPageBtnAndAssertURLandTableResults( dataCyNextPage, @@ -132,7 +137,7 @@ describe("Tests Table", () => { }); it("Does not update results or URL when right arrow is clicked and next page does not exist", () => { - cy.visit(`${TESTS_ROUTE}?limit=10&page=1`); + visitAndWait(`${TESTS_ROUTE}?limit=10&page=1`); cy.dataCy("pagination").first().should("contain.text", "2 / 2"); clickOnPageBtnAndAssertURLandTableResults( dataCyNextPage, @@ -142,7 +147,7 @@ describe("Tests Table", () => { }); it("Displays the previous page of results and updates URL when the left arrow is clicked and previous page exists", () => { - cy.visit(`${TESTS_ROUTE}?limit=10&page=1`); + visitAndWait(`${TESTS_ROUTE}?limit=10&page=1`); cy.dataCy("pagination").first().should("contain.text", "2 / 2"); clickOnPageBtnAndAssertURLandTableResults( dataCyPrevPage, @@ -152,7 +157,7 @@ describe("Tests Table", () => { }); it("Does not update results or URL when left arrow is clicked and previous page does not exist", () => { - cy.visit(`${TESTS_ROUTE}?limit=10&page=0`); + visitAndWait(`${TESTS_ROUTE}?limit=10&page=0`); cy.dataCy("pagination").first().should("contain.text", "1 / 2"); clickOnPageBtnAndAssertURLandTableResults( dataCyPrevPage, @@ -166,7 +171,7 @@ describe("Tests Table", () => { it("Changing page size updates URL and renders less than or equal to that many rows", () => { [20, 50, 100].forEach((pageSize) => { it(`when the page size is set to ${pageSize}`, () => { - cy.visit(`${TESTS_ROUTE}`); + visitAndWait(TESTS_ROUTE); clickOnPageSizeBtnAndAssertURLandTableSize(pageSize, dataCyTableRows); }); }); @@ -179,7 +184,7 @@ const DESCEND_PARAM = "sortDir=DESC"; const ASCEND_PARAM = "sortDir=ASC"; const TESTS_ROUTE = "/task/evergreen_ubuntu1604_test_model_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48/tests"; -const dataCyTableRows = "[data-test-id=tests-table] tr td:first-child"; +const dataCyTableRows = "[data-cy=tests-table] tr td:first-child"; const longTestName = "suuuuuupppppaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnggggggggggggggggggggggggg name"; diff --git a/cypress/integration/version/patch_with_aborted_task.ts b/cypress/integration/version/patch_with_aborted_task.ts index 092693d384..c84c1a5709 100644 --- a/cypress/integration/version/patch_with_aborted_task.ts +++ b/cypress/integration/version/patch_with_aborted_task.ts @@ -1,14 +1,12 @@ describe("Patch with aborted task", () => { it("Shows status `aborted` in task table for tasks that were aborted", () => { - cy.visit("/version/5ee1efb3d1fe073e194e8b5c"); - cy.get( - "[data-row-key=mongodb_mongo_main_enterprise_rhel_62_64_bit_dbtest_patch_0af9c85d7e2ba60f592f2d7a9a35217e254e59fb_5ee1efb3d1fe073e194e8b5c_20_06_11_08_48_06]" - ).within(() => { - cy.get(".cy-task-table-col-STATUS").should("have.text", "Aborted"); - cy.get(".cy-task-table-col-BASE_STATUS").should( - "not.have.text", - "Aborted" - ); - }); + cy.visit("version/5e94c2dfe3c3312519b59480"); + const taskId = + "mongodb_mongo_master_merge_patch_977e984bf4ed5a406da11af800c12356d0975502_5e94c2dfe3c3312519b59480_20_04_13_19_52_11"; + cy.get(`[data-row-key=${taskId}] > .cy-task-table-col-STATUS`).within( + () => { + cy.dataCy("task-status-badge").should("have.text", "Aborted"); + } + ); }); }); diff --git a/cypress/integration/version/restart_modal.ts b/cypress/integration/version/restart_modal.ts index 7a578182f4..694bb7dad4 100644 --- a/cypress/integration/version/restart_modal.ts +++ b/cypress/integration/version/restart_modal.ts @@ -1,25 +1,20 @@ -describe( - "Restarting a patch with Downstream Tasks", - { testIsolation: false }, - () => { - it("Clicking on the Select Downstream Tasks should show the downstream projects", () => { - const versionWithDownstream = `/version/5f74d99ab2373627c047c5e5`; - cy.visit(versionWithDownstream); - cy.dataCy("restart-version").click(); - cy.dataCy("select-downstream").first().click(); - cy.dataCy("select-downstream").first().contains("evergreen").click(); - }); - } -); +describe("Restarting a patch with Downstream Tasks", () => { + it("Clicking on the Select Downstream Tasks should show the downstream projects", () => { + const versionWithDownstream = `/version/5f74d99ab2373627c047c5e5`; + cy.visit(versionWithDownstream); + cy.dataCy("restart-version").click(); + cy.dataCy("select-downstream").first().click(); + cy.dataCy("select-downstream").first().contains("evergreen").click(); + }); +}); -describe("Restarting a patch", { testIsolation: false }, () => { - it("Clicking on the Restart button opens a patch restart modal", () => { +describe("Restarting a patch", () => { + beforeEach(() => { cy.visit(path); cy.dataCy("version-restart-modal").should("not.exist"); cy.dataCy("restart-version").click(); cy.dataCy("version-restart-modal").should("be.visible"); }); - it("Clicking on a variant should toggle an accordion drop down of tasks", () => { cy.dataCy("variant-accordion").first().click(); cy.dataCy("task-status-checkbox").should("exist"); @@ -36,7 +31,8 @@ describe("Restarting a patch", { testIsolation: false }, () => { cy.dataCy("task-status-badge").should("contain.text", "1 of 1 Selected"); }); - it("Selecting on the task status filter should toggle the tasks that have matching statuses to it", () => { + // TODO: Drop skip in https://jira.mongodb.org/browse/EVG-20762 + it.skip("Selecting on the task status filter should toggle the tasks that have matching statuses to it", () => { cy.dataCy("task-status-filter").click(); cy.getInputByLabel("All").check({ force: true }); cy.dataCy("task-status-filter").click(); @@ -52,7 +48,8 @@ describe("Restarting a patch", { testIsolation: false }, () => { cy.dataCy("task-status-filter").click(); }); - it("Selecting on the base status filter should toggle the tasks that have matching statuses to it", () => { + // TODO: Drop skip in https://jira.mongodb.org/browse/EVG-20762 + it.skip("Selecting on the base status filter should toggle the tasks that have matching statuses to it", () => { cy.dataCy("version-restart-modal").within(() => { cy.dataCy("base-task-status-filter").click(); cy.getInputByLabel("Succeeded").check({ force: true }); @@ -73,9 +70,7 @@ describe("Restarting a patch", { testIsolation: false }, () => { it("Restarting a task should close the modal and display a success message if it occurs successfully.", () => { cy.dataCy("version-restart-modal").within(() => { - cy.dataCy("task-status-filter").click(); - cy.getInputByLabel("Unscheduled").check({ force: true }); - cy.dataCy("task-status-filter").click(); + cy.dataCy("task-status-checkbox").first().click({ force: true }); cy.contains("button", "Restart").click(); }); cy.dataCy("version-restart-modal").should("not.exist"); @@ -83,7 +78,7 @@ describe("Restarting a patch", { testIsolation: false }, () => { }); }); -describe("Restarting mainline commits", { testIsolation: false }, () => { +describe("Restarting mainline commits", () => { it("should be able to restart scheduled mainline commit tasks", () => { cy.visit("/version/spruce_ab494436448fbb1d244833046ea6f6af1544e86d"); cy.dataCy("restart-version").should( diff --git a/cypress/integration/version/routes.ts b/cypress/integration/version/routes.ts index 313f20a73d..20c8dcb87d 100644 --- a/cypress/integration/version/routes.ts +++ b/cypress/integration/version/routes.ts @@ -74,7 +74,7 @@ describe("Version route", () => { .trigger("mouseover") .within(($el) => { // @ts-expect-error - expect($el.text()).to.contain("1Undispatched"); + expect($el.text()).to.contain("1Succeeded"); }); }); }); @@ -95,14 +95,15 @@ describe("Version route", () => { .and("equal", "true"); cy.location("search").should( "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=undispatched-umbrella,unscheduled,aborted,blocked&variant=%5Eubuntu1604%24" + "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24" ); + // TODO: Drop skip in https://jira.mongodb.org/browse/EVG-20762 // Check that filter values have updated. - cy.toggleTableFilter(2); - cy.getInputByLabel("Unscheduled") - .should("have.attr", "aria-checked") - .and("equal", "true"); + // cy.toggleTableFilter(2); + // cy.getInputByLabel("success") + // .should("have.attr", "aria-checked") + // .and("equal", "true"); cy.toggleTableFilter(4); cy.dataCy("variant-input-wrapper") @@ -128,7 +129,7 @@ describe("Version route", () => { }); cy.location("search").should( "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=undispatched-umbrella,unscheduled,aborted,blocked&variant=%5Eubuntu1604%24" + "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24" ); }); }); diff --git a/cypress/integration/version/task_duration.ts b/cypress/integration/version/task_duration.ts index 99f8c92c1c..a0402f5aca 100644 --- a/cypress/integration/version/task_duration.ts +++ b/cypress/integration/version/task_duration.ts @@ -20,7 +20,8 @@ describe("Task Duration Tab", () => { cy.location("search").should("include", `page=0`); }); - it("updates URL appropriately when status filter is applied", () => { + // TODO: Drop skip in https://jira.mongodb.org/browse/EVG-20762 + it.skip("updates URL appropriately when status filter is applied", () => { cy.visit(TASK_DURATION_ROUTE); // Apply status filter. @@ -74,7 +75,7 @@ describe("Task Duration Tab", () => { cy.location("search").should("include", "duration=DESC"); cy.get(`[aria-label="sort"]`).click(); cy.location("search").should("include", "duration=ASC"); - const shortestTask = "generate-lint"; + const shortestTask = "test-auth"; cy.contains(shortestTask).should("be.visible"); cy.dataCy("task-duration-table-row") .first() diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index e18f4e160b..ca5437d08e 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -78,10 +78,9 @@ Cypress.Commands.add("logout", () => { /* toggleTableFilter */ Cypress.Commands.add("toggleTableFilter", (colNum: number) => { - cy.get(`.ant-table-thead > tr > :nth-child(${colNum})`) - .find("[role=button]") - .first() - .click(); + const selector = `.ant-table-thead > tr > :nth-child(${colNum})`; + cy.get(selector).should("be.visible"); + cy.get(selector).find("[role=button]").first().click(); cy.get(":not(.ant-dropdown-hidden) > .ant-table-filter-dropdown").should( "be.visible" ); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index e77d5b9f66..5101d743ed 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -20,6 +20,7 @@ import { CY_DISABLE_NEW_USER_WELCOME_MODAL, SLACK_NOTIFICATION_BANNER, } from "constants/cookies"; +import { isMutation } from "../utils/graphql-test-utils"; // Alternatively you can use CommonJS syntax: // require('./commands') @@ -124,12 +125,37 @@ declare global { } } -beforeEach(() => { - cy.login(); - cy.setCookie(bannerCookie, "true"); - cy.setCookie(CY_DISABLE_COMMITS_WELCOME_MODAL, "true"); - cy.setCookie(CY_DISABLE_NEW_USER_WELCOME_MODAL, "true"); - cy.setCookie(SLACK_NOTIFICATION_BANNER, "true"); +before(() => { + cy.exec("yarn evg-db-ops --restore").then((result) => { + if (result.code !== 0) { + throw new Error("EVG DB restoration failed during setup."); + } + }); }); +// Close over beforeEach and afterEach to encapsulate mutationDispatched +(() => { + let mutationDispatched: boolean; + beforeEach(() => { + cy.login(); + cy.setCookie(bannerCookie, "true"); + cy.setCookie(CY_DISABLE_COMMITS_WELCOME_MODAL, "true"); + cy.setCookie(CY_DISABLE_NEW_USER_WELCOME_MODAL, "true"); + cy.setCookie(SLACK_NOTIFICATION_BANNER, "true"); + mutationDispatched = false; + cy.intercept("POST", "/graphql/query", (req) => { + if (isMutation(req)) { + mutationDispatched = true; + } + }); + }); + + afterEach(() => { + if (mutationDispatched) { + cy.log("A mutation was detected. Restoring EVG."); + cy.exec("yarn evg-db-ops --restore"); + } + }); +})(); + const bannerCookie = "This is an important notification"; diff --git a/cypress/utils/graphql-test-utils.ts b/cypress/utils/graphql-test-utils.ts index 8c7f60ae20..572953dbf9 100644 --- a/cypress/utils/graphql-test-utils.ts +++ b/cypress/utils/graphql-test-utils.ts @@ -11,22 +11,5 @@ export const hasOperationName = ( body.operationName === operationName ); }; - -// Alias query if operationName matches -export const aliasQuery = ( - req: CyHttpMessages.IncomingHttpRequest, - operationName: string -): void => { - if (hasOperationName(req, operationName)) { - req.alias = `gql${operationName}Query`; - } -}; - -export const aliasMutation = ( - req: CyHttpMessages.IncomingHttpRequest, - operationName: string -): void => { - if (hasOperationName(req, operationName)) { - req.alias = `gql${operationName}Mutation`; - } -}; +export const isMutation = (req: CyHttpMessages.IncomingHttpRequest) => + req.body.query?.startsWith("mutation"); diff --git a/package.json b/package.json index b20b644b4a..5232561f9a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "check-types": "tsc -p tsconfig.json --noEmit", "clean": "rm -rf node_modules && rm -rf build && yarn install", "codegen": "graphql-codegen --config codegen.ts", - "cy:open": "cypress open", + "cy:open": "yarn evg-db-ops --reseed-and-dump; cypress open", "cy:run": "cypress run", "cy:test": "cypress run --spec", "deploy:beta": "yarn build:beta && env-cmd -e beta yarn deploy:do-not-use", @@ -25,6 +25,7 @@ "eslint:fix": "yarn eslint:strict --fix", "eslint:staged": "STRICT=1 eslint", "eslint:strict": "STRICT=1 eslint '*.{js,ts,tsx}' 'src/**/*.ts?(x)' 'scripts/**/*.js' 'cypress/**/*.ts' 'src/gql/**/*.graphql'", + "evg-db-ops": "scripts/evg-db-ops.sh", "prepare": "husky install", "prettier": "prettier --write", "prod": "env-cmd -e devProduction -r .env-cmdrc.local.json yarn start", diff --git a/scripts/evg-db-ops.sh b/scripts/evg-db-ops.sh new file mode 100755 index 0000000000..6e4e81452d --- /dev/null +++ b/scripts/evg-db-ops.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +DB_NAME="evergreen_local" +URI="mongodb://localhost:27017/$DB_NAME" + +# Define paths for storing database dumps. +DUMP_ROOT=${TMPDIR}evg_dump +DUMP_FOLDER="$DUMP_ROOT/$DB_NAME" + +# Function to clean up temporary dump files. +clean_up() { + rm -rf "$DUMP_ROOT" + echo "Cleaned up $DUMP_ROOT." +} + +# Function to reseed the database with smoke test data. +reseed_database() { + # Change the current directory sdlschema symlink. + if ! cd -- "$(dirname -- "$(readlink -- "sdlschema")")"; then + echo "Unable to find Evergreen directory from the sdlschema symlink" + exit 1 + fi + # Load test data into the database. + ../bin/load-smoke-data -path ../testdata/local -dbName evergreen_local -amboyDBName amboy_local + cd - || exit +} + +# Function to create a dump of the database. +dump_database() { + clean_up + # Use 'mongodump' to create a database dump. + if ! mongodump --uri="$URI" -o "$DUMP_ROOT"; then + echo "Error creating dump from $DB_NAME db." + exit 1 + fi + echo "Dump successfully created in $DUMP_ROOT" +} + +# Function to reseed the database and then create a dump. +reseed_and_dump_database() { + reseed_database + dump_database +} + +# Function to restore the database from a dump. +restore_database() { + # Check if the specified dump folder exists. + if [ ! -d "$DUMP_FOLDER" ]; then + echo "Error: $DUMP_FOLDER does not exist. Ensure you have a valid dump before restoring." + exit 1 + fi + + MAX_RETRIES=2 + + # Use 'mongorestore' to restore the database from the dump. + for ((retry=0; retry<=MAX_RETRIES; retry++)); do + if mongorestore --quiet --drop --uri="$URI" "$DUMP_FOLDER"; then + echo "Successfully restored the database from $DUMP_FOLDER." + exit 0 + else + echo "Error restoring the database from $DUMP_FOLDER. Retry attempt: $retry" + if [ $retry -eq $MAX_RETRIES ]; then + echo "Max retries reached. Exiting." + exit 1 + fi + sleep 3 + fi + done +} + +# Check the command-line argument to determine the action to perform. +case "$1" in + --dump) + dump_database + ;; + --restore) + restore_database + ;; + --clean-up) + clean_up + ;; + --reseed) + reseed_database + ;; + --reseed-and-dump) + reseed_and_dump_database + ;; + *) + echo "Usage: $0 {--dump|--restore|--clean-up|--reseed-and-dump}" + exit 1 + ;; +esac diff --git a/scripts/prepare-shell.sh b/scripts/prepare-shell.sh index 6d503af070..74a7d1bc71 100644 --- a/scripts/prepare-shell.sh +++ b/scripts/prepare-shell.sh @@ -11,6 +11,7 @@ PREPARE_SHELL: | PROJECT_DIRECTORY="$PROJECT_DIRECTORY" NVM_DIR="$NVM_DIR" + export PATH=$PROJECT_DIRECTORY/mongodb-tools:$PATH export PROJECT_DIRECTORY export NVM_DIR diff --git a/src/pages/projectSettings/index.tsx b/src/pages/projectSettings/index.tsx index 1d6121bd21..9cebdeed1f 100644 --- a/src/pages/projectSettings/index.tsx +++ b/src/pages/projectSettings/index.tsx @@ -95,12 +95,16 @@ const ProjectSettings: React.FC = () => { identifier, currentTab: tab, }; - const project = projectType === ProjectType.Repo ? repoData?.repoSettings : projectData?.projectSettings; + const hasLoaded = + (projectData || projectType === ProjectType.Repo) && + (repoData || projectType === ProjectType.Project) && + project; + const owner = project?.projectRef?.owner; const repo = project?.projectRef?.repo; @@ -186,7 +190,7 @@ const ProjectSettings: React.FC = () => { - {project ? ( + {hasLoaded ? ( = ({ task }) => { shouldShowBottomTableControl={filteredTestCount > 10} >