From dfda0a824c2bc46ba430199605a2542d00e7ac0a Mon Sep 17 00:00:00 2001 From: minnakt <47064971+minnakt@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:28:43 -0500 Subject: [PATCH 1/3] DEVPROD-789: Fix spacing bug in ArrayFieldTemplate (#2170) --- .../ArrayFieldTemplates/index.tsx | 29 +++++++++---------- src/gql/generated/types.ts | 4 +++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/components/SpruceForm/FieldTemplates/ArrayFieldTemplates/index.tsx b/src/components/SpruceForm/FieldTemplates/ArrayFieldTemplates/index.tsx index b131a91381..c1fcdb31d5 100644 --- a/src/components/SpruceForm/FieldTemplates/ArrayFieldTemplates/index.tsx +++ b/src/components/SpruceForm/FieldTemplates/ArrayFieldTemplates/index.tsx @@ -199,23 +199,22 @@ export const ArrayFieldTemplate: React.FC = ({ hasChildren={!!items?.length} data-cy={arrayDataCy} > - {items.length === 0 ? ( + {items.length === 0 && placeholder && ( {placeholder} - ) : ( - items.map((p, i) => ( - - )) )} + {items.map((p, i) => ( + + ))} {buttonAtEnd && ( {addButton} diff --git a/src/gql/generated/types.ts b/src/gql/generated/types.ts index a832e8a1bc..a884f8bd7f 100644 --- a/src/gql/generated/types.ts +++ b/src/gql/generated/types.ts @@ -1692,6 +1692,7 @@ export type Project = { repotrackerError?: Maybe; restricted?: Maybe; spawnHostScriptPath: Scalars["String"]["output"]; + stepbackBisect?: Maybe; stepbackDisabled?: Maybe; taskAnnotationSettings: TaskAnnotationSettings; taskSync: TaskSyncOptions; @@ -1824,6 +1825,7 @@ export type ProjectInput = { repotrackerDisabled?: InputMaybe; restricted?: InputMaybe; spawnHostScriptPath?: InputMaybe; + stepbackBisect?: InputMaybe; stepbackDisabled?: InputMaybe; taskAnnotationSettings?: InputMaybe; taskSync?: InputMaybe; @@ -2144,6 +2146,7 @@ export type RepoRef = { repotrackerDisabled: Scalars["Boolean"]["output"]; restricted: Scalars["Boolean"]["output"]; spawnHostScriptPath: Scalars["String"]["output"]; + stepbackBisect?: Maybe; stepbackDisabled: Scalars["Boolean"]["output"]; taskAnnotationSettings: TaskAnnotationSettings; taskSync: RepoTaskSyncOptions; @@ -2186,6 +2189,7 @@ export type RepoRefInput = { repotrackerDisabled?: InputMaybe; restricted?: InputMaybe; spawnHostScriptPath?: InputMaybe; + stepbackBisect?: InputMaybe; stepbackDisabled?: InputMaybe; taskAnnotationSettings?: InputMaybe; taskSync?: InputMaybe; From a1209396cf2a07685f61b64859c088bf5221200c Mon Sep 17 00:00:00 2001 From: SupaJoon Date: Tue, 28 Nov 2023 15:24:21 -0500 Subject: [PATCH 2/3] EVG-19395: Enable test isolation (#2084) --- .evergreen.yml | 58 +- README.md | 28 +- cypress.config.ts | 18 + cypress/integration/announcements.ts | 9 +- cypress/integration/commit_queue.ts | 22 +- .../distroSettings/provider_section.ts | 11 +- .../distroSettings/task_section.ts | 131 ++- .../myPatches/patchCard/dropdown_menu.ts | 4 +- .../integration/preferences/notifications.ts | 8 +- .../preferences/public_key_management.ts | 41 +- cypress/integration/projectSettings/access.ts | 63 ++ .../projectSettings/admin_actions.ts | 18 +- .../projectSettings/attaching_to_repo.ts | 40 + .../integration/projectSettings/constants.ts | 3 + .../integration/projectSettings/containers.ts | 7 +- .../projectSettings/defaulting_to_repo.ts | 338 ++++++ .../projectSettings/not_defaulting_to_repo.ts | 176 ++++ .../projectSettings/notifications.ts | 60 +- .../integration/projectSettings/plugins.ts | 10 +- .../projectSettings/project_select.ts | 20 + .../projectSettings/project_settings.ts | 995 +----------------- .../projectSettings/repo_settings.ts | 280 +++++ cypress/integration/spawn/volume.ts | 70 +- cypress/integration/task/task_actions.ts | 10 +- cypress/integration/task/task_route.ts | 2 +- cypress/integration/task/test_table.ts | 43 +- cypress/integration/version/action_buttons.ts | 2 +- .../version/patch_with_aborted_task.ts | 18 +- cypress/integration/version/restart_modal.ts | 35 +- cypress/integration/version/routes.ts | 24 +- cypress/integration/version/task_duration.ts | 19 +- .../unscheduled_patch/configure_patch.ts | 51 +- cypress/support/commands.ts | 21 +- cypress/support/e2e.ts | 43 +- cypress/utils/graphql-test-utils.ts | 21 +- package.json | 3 +- scripts/evg-db-ops.sh | 92 ++ scripts/prepare-shell.sh | 1 + src/constants/cookies.ts | 1 - src/pages/projectSettings/index.tsx | 8 +- .../spawn/spawnVolume/SpawnVolumeTable.tsx | 7 - .../spawnVolumeTableActions/MigrateButton.tsx | 21 - src/pages/task/taskTabs/TestsTable.tsx | 2 +- src/types/spawn.ts | 4 +- 44 files changed, 1458 insertions(+), 1380 deletions(-) create mode 100644 cypress/integration/projectSettings/access.ts create mode 100644 cypress/integration/projectSettings/attaching_to_repo.ts create mode 100644 cypress/integration/projectSettings/defaulting_to_repo.ts create mode 100644 cypress/integration/projectSettings/not_defaulting_to_repo.ts create mode 100644 cypress/integration/projectSettings/project_select.ts create mode 100644 cypress/integration/projectSettings/repo_settings.ts create mode 100755 scripts/evg-db-ops.sh diff --git a/.evergreen.yml b/.evergreen.yml index ee1f648cd6..d297fef0d5 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: @@ -229,8 +239,11 @@ functions: script: | ${PREPARE_SHELL} # Only record to cypress cloud if this is a pr, mainline commit or an intentional patch. - if [[ "${requester}" == "github_pr" || "${requester}" == "commit" || "${requester}" == "patch" ]]; then + # And only allow spec filtering for an intentional patch. + if [[ "${requester}" == "github_pr" || "${requester}" == "commit" ]]; then yarn cy:run --record --key "${cypress_record_key}" --reporter junit + elif [[ "${requester}" == "patch" ]]; then + yarn cy:run --record --key "${cypress_record_key}" --reporter junit --spec ${cypress_spec} else yarn cy:run --reporter junit fi @@ -247,8 +260,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 +271,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 +301,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 +313,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 +327,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 +338,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 +349,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 +368,7 @@ functions: content_type: font/woff2 permissions: public-read preserve_path: true - + attach-codegen-diff: command: s3.put type: system @@ -371,7 +381,7 @@ functions: bucket: mciuploads content_type: text/plain permissions: public-read - + attach-email: command: s3.put type: system @@ -411,7 +421,7 @@ functions: EOF echo "Done populating" - + prod-deploy: command: shell.exec params: @@ -427,8 +437,6 @@ functions: AUTHOR_EMAIL=${author_email} \ yarn deploy:prod - - ####################################### # Tasks # ####################################### @@ -474,7 +482,7 @@ tasks: - func: yarn-serve - func: wait-for-evergreen - func: run-cypress-tests - + - name: check_codegen commands: - func: sym-link @@ -493,6 +501,7 @@ buildvariants: - ubuntu2204-large expansions: goroot: /opt/golang/go1.20 + mongodb_tools_url: https://fastdl.mongodb.org/tools/db/mongodb-database-tools-ubuntu2204-x86_64-100.8.0.tgz mongodb_url_2204: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.2.tgz mongosh_url_2204: https://downloads.mongodb.com/compass/mongosh-2.0.2-linux-x64.tgz node_version: 16.17.0 @@ -512,3 +521,8 @@ buildvariants: git_tag_only: true patchable: false priority: 100 + +parameters: + - key: cypress_spec + value: "cypress/integration/**/*" + description: Specify the Cypress spec files to run for user submitted patches running the e2e_test task. 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..4e9c84e8d9 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,7 +1,9 @@ import { defineConfig } from "cypress"; +import { execSync } from "child_process"; export default defineConfig({ e2e: { + retries: 3, baseUrl: "http://localhost:3000", projectId: "yshv48", reporterOptions: { @@ -11,5 +13,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/distroSettings/provider_section.ts b/cypress/integration/distroSettings/provider_section.ts index 21900c5915..b4b026f6ed 100644 --- a/cypress/integration/distroSettings/provider_section.ts +++ b/cypress/integration/distroSettings/provider_section.ts @@ -111,11 +111,12 @@ describe("provider section", () => { }); it("successfully updates ec2 fleet provider fields", () => { + cy.openExpandableCard("us-east-1"); cy.dataCy("provider-select").contains("EC2 Fleet"); // Correct section is displayed. - cy.dataCy("ec2-fleet-provider-settings").should("exist"); - cy.dataCy("region-select").contains("us-east-1"); + cy.dataCy("ec2-fleet-provider-settings").should("be.visible"); + cy.dataCy("region-select").contains("us-east-1").should("be.visible"); // Change field values. cy.selectLGOption("Region", "us-west-1"); @@ -142,11 +143,13 @@ describe("provider section", () => { }); it("can add and delete region settings", () => { + cy.openExpandableCard("us-east-1"); cy.dataCy("ec2-fleet-provider-settings").should("exist"); // Add item for new region. cy.contains("button", "Add region settings").click(); cy.contains("button", "Add region settings").should("not.exist"); + cy.openExpandableCard("New AWS Region"); // Save new region. cy.selectLGOption("Region", "us-west-1"); @@ -181,10 +184,9 @@ describe("provider section", () => { cy.contains("Default VPC Subnet ID").should("not.exist"); cy.contains("VPC Subnet Prefix").should("not.exist"); }); - it("successfully updates ec2 on-demand provider fields", () => { + cy.openExpandableCard("us-east-1"); cy.dataCy("provider-select").contains("EC2 On-Demand"); - // Correct section is displayed. cy.dataCy("ec2-on-demand-provider-settings").should("exist"); cy.dataCy("region-select").contains("us-east-1"); @@ -224,6 +226,7 @@ describe("provider section", () => { // Add item for new region. cy.contains("button", "Add region settings").click(); cy.contains("button", "Add region settings").should("not.exist"); + cy.openExpandableCard("New AWS Region"); // Save new region. cy.selectLGOption("Region", "us-west-1"); diff --git a/cypress/integration/distroSettings/task_section.ts b/cypress/integration/distroSettings/task_section.ts index 5d77790ff9..980a786e73 100644 --- a/cypress/integration/distroSettings/task_section.ts +++ b/cypress/integration/distroSettings/task_section.ts @@ -1,85 +1,76 @@ import { save } from "./utils"; describe("task section", () => { - beforeEach(() => { - cy.visit("/distro/ubuntu1804-workstation/settings/task"); + describe("static provider", () => { + it("should not show tunable options", () => { + cy.visit("/distro/localhost/settings/task"); + cy.selectLGOption("Task Planner Version", "Tunable"); + cy.dataCy("tunable-options").should("not.exist"); + }); }); - describe("providers", () => { - describe("static provider", () => { - it("should not show tunable options", () => { - cy.visit("/distro/localhost/settings/task"); - cy.selectLGOption("Task Planner Version", "Tunable"); - cy.dataCy("tunable-options").should("not.exist"); - }); + describe("docker provider", () => { + it("should not show tunable options", () => { + cy.visit("/distro/ubuntu1604-container-test/settings/task"); + cy.selectLGOption("Task Planner Version", "Tunable"); + cy.dataCy("tunable-options").should("not.exist"); }); + }); - describe("docker provider", () => { - it("should not show tunable options", () => { - cy.visit("/distro/ubuntu1604-container-test/settings/task"); - cy.selectLGOption("Task Planner Version", "Tunable"); - cy.dataCy("tunable-options").should("not.exist"); - }); + describe("ec2 provider", () => { + beforeEach(() => { + cy.visit("/distro/ubuntu1804-workstation/settings/task"); }); - - describe("ec2 provider", () => { - it("should only show tunable options if planner version is tunable", () => { - cy.getInputByLabel("Task Planner Version").should( - "contain.text", - "Legacy" - ); - cy.dataCy("tunable-options").should("not.exist"); - cy.selectLGOption("Task Planner Version", "Tunable"); - cy.dataCy("tunable-options").should("be.visible"); - }); + it("should only show tunable options if planner version is tunable", () => { + cy.selectLGOption("Task Planner Version", "Tunable"); + cy.dataCy("tunable-options").should("be.visible"); + }); + it("should surface warnings for invalid number inputs", () => { + const inputLabel = "Patch Factor"; + cy.selectLGOption("Task Planner Version", "Tunable"); + cy.getInputByLabel(inputLabel).clear(); + cy.getInputByLabel(inputLabel).type("500"); + cy.contains("Value should be <= 100.").should("be.visible"); + cy.getInputByLabel(inputLabel).clear(); + cy.getInputByLabel(inputLabel).type("-500"); + cy.contains("Value should be >= 0.").should("be.visible"); }); - }); - - it("should surface warnings for invalid number inputs", () => { - const inputLabel = "Patch Factor"; - cy.selectLGOption("Task Planner Version", "Tunable"); - cy.getInputByLabel(inputLabel).clear(); - cy.getInputByLabel(inputLabel).type("500"); - cy.contains("Value should be <= 100.").should("be.visible"); - cy.getInputByLabel(inputLabel).clear(); - cy.getInputByLabel(inputLabel).type("-500"); - cy.contains("Value should be >= 0.").should("be.visible"); - }); - it("can update fields and those changes will persist", () => { - cy.dataCy("save-settings-button").should( - "have.attr", - "aria-disabled", - "true" - ); + it("can update fields and those changes will persist", () => { + cy.dataCy("save-settings-button").should( + "have.attr", + "aria-disabled", + "true" + ); - // Update fields. - cy.selectLGOption("Task Finder Version", "Parallel"); - cy.selectLGOption("Task Planner Version", "Tunable"); - cy.selectLGOption("Task Dispatcher Version", "Revised with dependencies"); - save(); - cy.validateToast("success"); + // Update fields. + cy.selectLGOption("Task Finder Version", "Parallel"); + cy.selectLGOption("Task Planner Version", "Tunable"); + cy.selectLGOption("Task Dispatcher Version", "Revised with dependencies"); + save(); + cy.validateToast("success"); - // Changes should persist. - cy.reload(); - cy.getInputByLabel("Task Finder Version").should( - "contain.text", - "Parallel" - ); - cy.getInputByLabel("Task Planner Version").should( - "contain.text", - "Tunable" - ); - cy.getInputByLabel("Task Dispatcher Version").should( - "contain.text", - "Revised with dependencies" - ); + // Changes should persist. + cy.reload(); + cy.getInputByLabel("Task Finder Version").should( + "contain.text", + "Parallel" + ); + cy.getInputByLabel("Task Planner Version").should( + "contain.text", + "Tunable" + ); + cy.getInputByLabel("Task Dispatcher Version").should( + "contain.text", + "Revised with dependencies" + ); - // Undo changes. - cy.selectLGOption("Task Finder Version", "Legacy"); - cy.selectLGOption("Task Planner Version", "Legacy"); - cy.selectLGOption("Task Dispatcher Version", /^Revised$/); - save(); - cy.validateToast("success"); + // Undo changes. + cy.selectLGOption("Task Finder Version", "Legacy"); + cy.selectLGOption("Task Planner Version", "Legacy"); + cy.selectLGOption("Task Dispatcher Version", /^Revised$/); + save(); + cy.validateToast("success"); + }); }); }); diff --git a/cypress/integration/myPatches/patchCard/dropdown_menu.ts b/cypress/integration/myPatches/patchCard/dropdown_menu.ts index 46c5f65660..baaa811733 100644 --- a/cypress/integration/myPatches/patchCard/dropdown_menu.ts +++ b/cypress/integration/myPatches/patchCard/dropdown_menu.ts @@ -8,9 +8,10 @@ const patchWithVersionOnCommitQueue = const getPatchCardByDescription = (description: string) => cy.dataCy("patch-card").filter(`:contains(${description})`); -describe("Dropdown Menu of Patch Actions", { testIsolation: false }, () => { +describe("Dropdown Menu of Patch Actions", () => { beforeEach(() => { cy.visit("/"); + cy.getInputByLabel("Include Commit Queue").check({ force: true }); }); it("'Reconfigure' link takes user to patch configure page", () => { @@ -105,7 +106,6 @@ describe("Dropdown Menu of Patch Actions", { testIsolation: false }, () => { it("Toggle patch visibility", () => { // "Include hidden" checkbox is not checked and patch is visible - cy.getCookie(INCLUDE_HIDDEN_PATCHES).should("not.exist"); cy.getInputByLabel("Include hidden").should("not.be.checked"); cy.location("search").should("not.contain", "hidden=true"); getPatchCardByDescription("testtest") 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/access.ts b/cypress/integration/projectSettings/access.ts new file mode 100644 index 0000000000..df7df77452 --- /dev/null +++ b/cypress/integration/projectSettings/access.ts @@ -0,0 +1,63 @@ +import { + getAccessRoute, + project, + projectUseRepoEnabled, + saveButtonEnabled, +} from "./constants"; +import { clickSave } from "../../utils"; + +xdescribe("Access page", () => { + const origin = getAccessRoute(projectUseRepoEnabled); + beforeEach(() => { + cy.visit(origin); + saveButtonEnabled(false); + 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.contains("label", "Unrestricted").click(); + cy.getInputByLabel("Unrestricted").should("be.checked"); + // Input and save username + cy.contains("Add Username").click(); + 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"); + // 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("@usernameInput").should("not.exist"); + clickSave(); + cy.validateToast("success", "Successfully updated project"); + // Assert persistence + cy.reload(); + 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("be.checked"); + }); + + it("Submitting an invalid admin username produces an error toast", () => { + cy.visit(getAccessRoute(project)); + cy.contains("Add Username").click(); + cy.get("[aria-label='Username'") + .should("have.length", 4) + .first() + .type("mongodb_user"); + clickSave(); + cy.validateToast( + "error", + "There was an error saving the project: error updating project admin roles: no admin role for project 'spruce' found" + ); + }); +}); 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/attaching_to_repo.ts b/cypress/integration/projectSettings/attaching_to_repo.ts new file mode 100644 index 0000000000..dd8d45f67d --- /dev/null +++ b/cypress/integration/projectSettings/attaching_to_repo.ts @@ -0,0 +1,40 @@ +import { getGeneralRoute, project } from "./constants"; +import { clickSave } from "../../utils"; + +describe("Attaching Spruce to a repo", () => { + const origin = getGeneralRoute(project); + + beforeEach(() => { + cy.visit(origin); + }); + + 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"); + cy.dataCy("attach-repo-button").click(); + cy.dataCy("attach-repo-modal").contains("button", "Attach").click(); + cy.validateToast("success", "Successfully attached to repo"); + 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"); + }); +}); 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/defaulting_to_repo.ts b/cypress/integration/projectSettings/defaulting_to_repo.ts new file mode 100644 index 0000000000..0583636ed6 --- /dev/null +++ b/cypress/integration/projectSettings/defaulting_to_repo.ts @@ -0,0 +1,338 @@ +import { + getGeneralRoute, + getVirtualWorkstationRoute, + projectUseRepoEnabled, + repo, + saveButtonEnabled, +} from "./constants"; +import { clickSave } from "../../utils"; + +describe("Project Settings when defaulting to repo", () => { + const origin = getGeneralRoute(projectUseRepoEnabled); + + beforeEach(() => { + cy.visit(origin); + }); + + 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("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("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.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}"); + }); + + 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", () => { + cy.dataCy("spawn-host-input").type("/test"); + 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); + }); + + 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("c"); + cy.dataCy("var-value-input").first().type("3"); + + 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", () => { + beforeEach(() => { + cy.dataCy("navitem-github-commitqueue").click(); + }); + + it("Should not have the save button enabled on load", () => { + saveButtonEnabled(false); + }); + + 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("exist"); + cy.contains("button", "Add Patch Definition").click(); + + 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"); + }); + + 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("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("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", () => { + // 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" + ); + }); + + 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"); + }); + }); + + describe("Patch Aliases page", () => { + beforeEach(() => { + cy.dataCy("navitem-patch-aliases").click(); + saveButtonEnabled(false); + }); + + it("Defaults to repo patch aliases", () => { + cy.getInputByLabel("Default to Repo Patch Aliases").should( + "have.attr", + "checked" + ); + }); + + 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); + }); + }); + + describe("Virtual Workstation page", () => { + beforeEach(() => { + cy.dataCy("navitem-virtual-workstation").click(); + }); + + 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"); + }); + + 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"); + }); + }); +}); diff --git a/cypress/integration/projectSettings/not_defaulting_to_repo.ts b/cypress/integration/projectSettings/not_defaulting_to_repo.ts new file mode 100644 index 0000000000..b2e465ecb9 --- /dev/null +++ b/cypress/integration/projectSettings/not_defaulting_to_repo.ts @@ -0,0 +1,176 @@ +import { getGeneralRoute, project, saveButtonEnabled } from "./constants"; +import { clickSave } from "../../utils"; + +describe("Project Settings when not defaulting to repo", () => { + const origin = getGeneralRoute(project); + + beforeEach(() => { + cy.visit(origin); + saveButtonEnabled(false); + }); + + it("Does not show a 'Default to Repo' button on page", () => { + cy.dataCy("default-to-repo-button").should("not.exist"); + }); + + 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"); + }); + + describe("Variables page", () => { + beforeEach(() => { + cy.dataCy("navitem-variables").click(); + }); + + it("Should not have the save button enabled on load", () => { + saveButtonEnabled(false); + }); + + it("Should not show the move variables button", () => { + cy.dataCy("promote-vars-button").should("not.exist"); + }); + + 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("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"); + }); + }); + + describe("GitHub/Commit Queue page", () => { + beforeEach(() => { + cy.dataCy("navitem-github-commitqueue").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"); + + clickSave(); + cy.validateToast("success", "Successfully updated project"); + cy.dataCy("remote-path-input").should("have.value", "./evergreen.yml"); + }); + }); + + describe("Periodic Builds page", () => { + beforeEach(() => { + cy.dataCy("navitem-periodic-builds").click(); + }); + + 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"); + }); + }); + + describe("Project Triggers page", () => { + beforeEach(() => { + cy.dataCy("navitem-project-triggers").click(); + }); + + 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"); + }); + }); +}); diff --git a/cypress/integration/projectSettings/notifications.ts b/cypress/integration/projectSettings/notifications.ts index 11177b9634..4a37adef07 100644 --- a/cypress/integration/projectSettings/notifications.ts +++ b/cypress/integration/projectSettings/notifications.ts @@ -1,24 +1,20 @@ import { getNotificationsRoute, saveButtonEnabled } from "./constants"; import { clickSave } from "../../utils"; -describe("Notifications", { testIsolation: false }, () => { - const destination = getNotificationsRoute("evergreen"); - before(() => { - cy.visit(destination); +describe("Notifications", () => { + const origin = getNotificationsRoute("evergreen"); + beforeEach(() => { + cy.visit(origin); }); - it("Does not show a 'Default to Repo' button on page", () => { + it("shows correct intitial state", () => { cy.dataCy("default-to-repo-button").should("not.exist"); - }); - it("shouldn't have any subscriptions defined", () => { - cy.contains("No subscriptions are defined.").should("exist"); - }); - it("shouldn't be able to save anything if no changes were made", () => { + cy.contains("No subscriptions are defined.").should("be.visible"); saveButtonEnabled(false); }); - it("should be able to add a subscription and save it", () => { + it("should be able to add a subscription, save it and delete it", () => { cy.dataCy("expandable-card").should("not.exist"); cy.dataCy("add-button").contains("Add Subscription").should("be.visible"); - cy.dataCy("add-button").contains("Add Subscription").click({ force: true }); + cy.dataCy("add-button").click(); cy.dataCy("expandable-card").should("contain.text", "New Subscription"); cy.selectLGOption("Event", "Any Version Finishes"); cy.selectLGOption("Notification Method", "Email"); @@ -28,51 +24,53 @@ describe("Notifications", { testIsolation: false }, () => { cy.validateToast("success", "Successfully updated project"); saveButtonEnabled(false); - cy.dataCy("expandable-card").should("exist"); - cy.dataCy("expandable-card").scrollIntoView(); - cy.dataCy("expandable-card").should( - "contain.text", - "Version outcome - mohamed.khelif@mongodb.com" - ); - }); - it("should be able to delete a subscription", () => { - cy.dataCy("expandable-card").should("exist"); - cy.dataCy("expandable-card").scrollIntoView(); - cy.dataCy("delete-item-button").click({ force: true }); - cy.dataCy("expandable-card").should("not.exist"); + cy.dataCy("expandable-card").as("subscriptionItem").scrollIntoView(); + cy.get("@subscriptionItem") + .should("be.visible") + .should("contain.text", "Version outcome - mohamed.khelif@mongodb.com"); + cy.dataCy("delete-item-button").should("not.be.disabled").click(); + cy.get("@subscriptionItem").should("not.exist"); cy.dataCy("save-settings-button").scrollIntoView(); clickSave(); cy.validateToast("success", "Successfully updated project"); }); + it("should not be able to combine a jira comment subscription with a task event", () => { cy.dataCy("expandable-card").should("not.exist"); cy.dataCy("add-button").contains("Add Subscription").should("be.visible"); - cy.dataCy("add-button").contains("Add Subscription").click({ force: true }); + cy.dataCy("add-button").click(); cy.dataCy("expandable-card").should("exist").scrollIntoView(); - cy.dataCy("expandable-card").should("contain.text", "New Subscription"); + cy.dataCy("expandable-card") + .should("be.visible") + .should("contain.text", "New Subscription"); cy.selectLGOption("Event", "Any Task Finishes"); cy.selectLGOption("Notification Method", "Comment on a JIRA issue"); cy.getInputByLabel("JIRA Issue").type("JIRA-123"); cy.contains("Subscription type not allowed for tasks in a project.").should( - "exist" + "be.visible" ); cy.dataCy("save-settings-button").scrollIntoView(); saveButtonEnabled(false); }); it("should not be able to save a subscription if an input is invalid", () => { + cy.dataCy("add-button").click(); + cy.dataCy("expandable-card").scrollIntoView(); + cy.dataCy("expandable-card") + .should("be.visible") + .should("contain.text", "New Subscription"); cy.selectLGOption("Event", "Any Version Finishes"); cy.selectLGOption("Notification Method", "Email"); cy.getInputByLabel("Email").type("Not a real email"); - cy.contains("Value should be a valid email.").should("exist"); + cy.contains("Value should be a valid email.").should("be.visible"); cy.dataCy("save-settings-button").scrollIntoView(); saveButtonEnabled(false); }); it("Setting a project banner displays the banner on the correct pages and unsetting is removes it", () => { - cy.visit(destination); const bannerText = "This is a project banner!"; // set banner - cy.dataCy("banner-text").clear().type(bannerText); + cy.dataCy("banner-text").clear(); + cy.dataCy("banner-text").type(bannerText); clickSave(); cy.validateToast("success", "Successfully updated project"); @@ -105,7 +103,7 @@ describe("Notifications", { testIsolation: false }, () => { cy.contains(bannerText).should("be.visible"); // clear banner - cy.visit(destination); + cy.visit(origin); cy.dataCy("banner-text").clear(); clickSave(); 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_select.ts b/cypress/integration/projectSettings/project_select.ts new file mode 100644 index 0000000000..e711853b99 --- /dev/null +++ b/cypress/integration/projectSettings/project_select.ts @@ -0,0 +1,20 @@ +import { getGeneralRoute, project } from "./constants"; + +describe("Clicking on The Project Select Dropdown", () => { + const origin = getGeneralRoute(project); + + beforeEach(() => { + cy.visit(origin); + }); + + it("Headers are clickable", () => { + cy.dataCy("project-select").should("be.visible"); + cy.dataCy("project-select").click(); + cy.dataCy("project-select-options").should("be.visible"); + cy.dataCy("project-select-options") + .find("div") + .contains("evergreen-ci/evergreen") + .click(); + cy.location().should((loc) => expect(loc.pathname).to.not.eq(origin)); + }); +}); diff --git a/cypress/integration/projectSettings/project_settings.ts b/cypress/integration/projectSettings/project_settings.ts index a6a4e52cc6..b745c4e84f 100644 --- a/cypress/integration/projectSettings/project_settings.ts +++ b/cypress/integration/projectSettings/project_settings.ts @@ -1,1001 +1,42 @@ import { - getAccessRoute, getGeneralRoute, getGithubCommitQueueRoute, project, - projectUseRepoEnabled, - repo, - saveButtonEnabled, } 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", () => { - saveButtonEnabled(false); - }); - - it("Shows a 'Default to Repo on Page' button on page", () => { - cy.dataCy("default-to-repo-button").should("exist").should("be.enabled"); - }); - - 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("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"); - 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); - cy.dataCy("delete-item-button").should("be.visible").click(); - cy.get("[aria-label='Username']").should("have.length", 0); - clickSave(); - cy.validateToast("success", "Successfully updated project"); - - cy.reload(); - cy.get("[aria-label='Username']").should("have.length", 0); - }); - - 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" - ); - }); - - it("Submitting an invalid admin username produces an error toast", () => { - cy.visit(getAccessRoute(project)); - cy.contains("Add Username").click(); - cy.get("[aria-label='Username'") - .should("have.length", 4) - .first() - .type("mongodb_user"); - clickSave(); - cy.validateToast( - "error", - "There was an error saving the project: error updating project admin roles: no admin role for project 'spruce' found" - ); - }); -}); - -describe("Clicking on The Project Select Dropdown", () => { - const destination = getGeneralRoute(project); +describe("Renaming the identifier", () => { + const origin = getGeneralRoute(project); beforeEach(() => { - cy.visit(destination); - }); - - it("Headers are clickable", () => { - cy.dataCy("project-select").should("be.visible"); - cy.dataCy("project-select").click(); - cy.dataCy("project-select-options").should("be.visible"); - cy.dataCy("project-select-options") - .find("div") - .contains("evergreen-ci/evergreen") - .click(); - cy.location().should((loc) => expect(loc.pathname).to.not.eq(destination)); - }); -}); - -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"); - }); - - it("Clicking on save button should show a success toast", () => { - clickSave(); - cy.validateToast("success", "Successfully updated repo"); + 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", () => { - 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("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("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 override buttons for commit queue patch definitions", () => { - cy.dataCy("cq-override-radio-box").should("not.exist"); - }); - - it("Updates the commit queue message", () => { - cy.dataCy("cq-message-input").type("Repo message"); - }); - - it("Disables save button because Commit Queue definition is missing", () => { - saveButtonEnabled(false); - }); - - 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("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("Patch Aliases page", () => { - before(() => { - 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", () => { - 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"); - }); - - it("Saves a Patch Trigger Alias", () => { - cy.dataCy("add-button") - .contains("Add Patch Trigger Alias") - .parent() - .click(); - cy.dataCy("pta-alias-input").type("my-alias"); - cy.dataCy("project-input").type("spruce"); - cy.dataCy("module-input").type("module_name"); - 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 }); - - clickSave(); - cy.validateToast("success", "Successfully updated repo"); - saveButtonEnabled(false); - }); - - it("Should be possible to return to a deselected state for Wait On", () => { - cy.selectLGOption("Wait on", "Success"); - cy.getInputByLabel("Wait on").should( - "have.attr", - "aria-invalid", - "false" - ); - saveButtonEnabled(); - cy.selectLGOption("Wait on", "Select event…"); - cy.getInputByLabel("Wait on").should( - "have.attr", - "aria-invalid", - "false" - ); - saveButtonEnabled(false); - }); - }); - - describe("Virtual Workstation page", { testIsolation: false }, () => { - before(() => { - cy.dataCy("navitem-virtual-workstation").click(); - }); - - it("Adds two commands", () => { - saveButtonEnabled(false); - - cy.dataCy("add-button").click({ force: true }); - cy.dataCy("command-input").type("command 1"); - cy.dataCy("directory-input").type("mongodb.user.directory"); - - cy.dataCy("add-button").click({ force: true }); - 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(); - }); - - 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"); - }); - - 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"); - }); - }); -}); - -describe( - "Project Settings when not defaulting to repo", - { testIsolation: false }, - () => { - const destination = getGeneralRoute(project); - - 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("Shows two radio boxes", () => { - cy.dataCy("enabled-radio-box").children().should("have.length", 2); - }); - - 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("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("Variables page", () => { - before(() => { - cy.dataCy("navitem-variables").click(); - }); - - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); - - it("Should not show the move variables button", () => { - cy.dataCy("promote-vars-button").should("not.exist"); - }); - - 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("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"); - }); - - 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("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); - }); - - 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" - ); - }); - - 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"); - }); - - 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("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("Should show no variables after deleting", () => { - cy.dataCy("var-name-input").should("not.exist"); - }); - }); - - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").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"); - - clickSave(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Shows the saved Git Tag", () => { - cy.dataCy("remote-path-input").should("have.value", "./evergreen.yml"); - }); - }); - - 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"); - }); - }); - - 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"); - }); - }); - } -); - -describe( - "Project Settings when defaulting to repo", - { testIsolation: false }, - () => { - const destination = getGeneralRoute(projectUseRepoEnabled); - - before(() => { - cy.visit(destination); - }); - - 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", () => { - 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 }); - - 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"); - - 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"); - }); - }); - - describe("GitHub/Commit Queue page", () => { - before(() => { - cy.dataCy("navitem-github-commitqueue").click(); - }); - - it("Should not have the save button enabled on load", () => { - saveButtonEnabled(false); - }); - - 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("Does not show an error banner when a patch definition is defined in the repo", () => { - cy.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"); - }); - - it("Again shows the repo's disabled patch definition", () => { - cy.dataCy("accordion-toggle").should("exist"); - cy.dataCy("accordion-toggle").contains("Patch Definition 1"); - }); - }); - - 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); - - 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"); - }); - - it("Allows defaulting to repo patch aliases", () => { - cy.getInputByLabel("Default to Repo Patch Aliases").click({ - force: true, - }); - - clickSave(); - cy.validateToast("success", "Successfully updated project"); - - saveButtonEnabled(false); - cy.dataCy("expandable-card-title").contains("my alias name"); - }); - - 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("Virtual Workstation page", () => { - before(() => { - cy.dataCy("navitem-virtual-workstation").click(); - }); - - 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("Allows overriding without adding a command", () => { - cy.getInputByLabel("Override Repo Commands").click({ force: true }); - - 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); - - before(() => { - cy.visit(destination); - }); - - it("Saves a new repo", () => { - cy.dataCy("repo-input").clear().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"); - }); - }); -}); - -describe("Renaming the identifier", { testIsolation: false }, () => { - const destination = getGeneralRoute(project); - - before(() => { - cy.visit(destination); - }); - - 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/projectSettings/repo_settings.ts b/cypress/integration/projectSettings/repo_settings.ts new file mode 100644 index 0000000000..7bb2e5b958 --- /dev/null +++ b/cypress/integration/projectSettings/repo_settings.ts @@ -0,0 +1,280 @@ +import { + getAccessRoute, + getGeneralRoute, + projectUseRepoEnabled, + repo, + saveButtonEnabled, +} from "./constants"; +import { clickSave } from "../../utils"; + +describe("Repo Settings", () => { + const origin = getGeneralRoute(repo); + + beforeEach(() => { + cy.visit(origin); + }); + + describe("General settings page", () => { + it("Should have the save button disabled 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("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"); + }); + }); + + 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("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"); + }); + }); + + 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", () => { + beforeEach(() => { + cy.dataCy("navitem-patch-aliases").click(); + saveButtonEnabled(false); + cy.dataCy("patch-aliases-override-radio-box").should("not.exist"); + }); + + 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); + 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"); + 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("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() + .click(); + cy.dataCy("pta-alias-input").type("my-alias"); + cy.dataCy("project-input").type("spruce"); + cy.dataCy("module-input").type("module_name"); + cy.contains("button", "Variant/Task").click(); + cy.dataCy("variant-regex-input").type(".*"); + cy.dataCy("task-regex-input").type(".*"); + 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); + // Demonstrate Wait on field is optional + cy.selectLGOption("Wait on", "Success"); + cy.getInputByLabel("Wait on").should( + "have.attr", + "aria-invalid", + "false" + ); + saveButtonEnabled(true); + cy.selectLGOption("Wait on", "Select event…"); + cy.getInputByLabel("Wait on").should( + "have.attr", + "aria-invalid", + "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", () => { + beforeEach(() => { + cy.dataCy("navitem-virtual-workstation").click(); + }); + + it("Adds two commands and then reorders them", () => { + saveButtonEnabled(false); + 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(); + cy.dataCy("command-input").eq(1).type("command 2"); + clickSave(); + cy.validateToast("success", "Successfully updated repo"); + 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"); + }); + }); +}); diff --git a/cypress/integration/spawn/volume.ts b/cypress/integration/spawn/volume.ts index 72fc3a52e4..098c509b09 100644 --- a/cypress/integration/spawn/volume.ts +++ b/cypress/integration/spawn/volume.ts @@ -1,6 +1,8 @@ -describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { - it("Visiting the spawn volume page should display the number of free and mounted volumes.", () => { +describe("Spawn volume page", () => { + beforeEach(() => { cy.visit("/spawn/volume"); + }); + it("Visiting the spawn volume page should display the number of free and mounted volumes.", () => { cy.dataCy("mounted-badge").contains("9 Mounted"); cy.dataCy("free-badge").contains("4 Free"); }); @@ -32,6 +34,7 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Clicking on the row should toggle the volume card open and closed", () => { + cy.visit("/spawn/volume?volume=vol-0ea662ac92f611ed4"); cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should("be.visible"); cy.dataRowKey("vol-0ea662ac92f611ed4").click(); cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should( @@ -42,7 +45,6 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Click the trash can should remove the volume from the table and update free/mounted volumes badges.", () => { - cy.visit("/spawn/volume"); cy.dataRowKey("vol-0c66e16459646704d").should("exist"); cy.dataCy("trash-vol-0c66e16459646704d").click(); cy.dataCy("delete-volume-popconfirm").should("be.visible"); @@ -59,7 +61,6 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Click the trash can for a mounted volume should show an additional confirmation checkbox which enables the submit button when checked.", () => { - cy.visit("/spawn/volume"); cy.dataRowKey( "1de2728dd9de82efc02dc21f6ca046eaa559462414d28e0b6bba6436436ac873" ).should("exist"); @@ -89,11 +90,10 @@ 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.", () => { - cy.visit("/spawn/volume"); cy.dataCy( "detach-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b857" ).click(); @@ -102,7 +102,6 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Clicking on 'Spawn Volume' should open the Spawn Volume Modal", () => { - cy.visit("/spawn/volume"); cy.dataCy("spawn-volume-btn").should( "not.have.attr", "aria-disabled", @@ -113,6 +112,8 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Reopening the Spawn Volume modal clears previous input changes.", () => { + cy.dataCy("spawn-volume-btn").click(); + cy.dataCy("spawn-volume-modal").should("be.visible"); cy.selectLGOption("Type", "sc1"); cy.dataCy("spawn-volume-modal").within(() => { cy.contains("button", "Cancel").should( @@ -129,8 +130,10 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); describe("Edit volume modal", () => { - it("Clicking on 'Edit' should open the Edit Volume Modal", () => { + beforeEach(() => { cy.visit("/spawn/volume"); + }); + it("Clicking on 'Edit' should open the Edit Volume Modal", () => { cy.dataCy( "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" ).click(); @@ -138,6 +141,10 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Volume name & expiration inputs should be populated with the volume display name & expiration on initial render", () => { + cy.dataCy( + "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" + ).click(); + cy.dataCy("update-volume-modal").should("be.visible"); cy.dataCy("volume-name-input").should( "have.value", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" @@ -147,6 +154,10 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Reopening the edit volume modal should reset form input fields.", () => { + cy.dataCy( + "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" + ).click(); + cy.dataCy("update-volume-modal").should("be.visible"); cy.dataCy("volume-name-input").type("Hello, World"); cy.dataCy("update-volume-modal").within(() => { cy.contains("button", "Cancel").click(); @@ -161,6 +172,10 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Submit button should be enabled when the volume details input value differs from what already exists.", () => { + cy.dataCy( + "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" + ).click(); + cy.dataCy("update-volume-modal").should("be.visible"); cy.contains("button", "Save").should( "have.attr", "aria-disabled", @@ -175,11 +190,10 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { ); // type original name - cy.dataCy("volume-name-input") - .clear() - .type( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" - ); + cy.dataCy("volume-name-input").clear(); + cy.dataCy("volume-name-input").type( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" + ); cy.contains("button", "Save").should( "have.attr", "aria-disabled", @@ -195,6 +209,16 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { }); it("Clicking on save button should close the modal and show a success toast", () => { + cy.dataCy( + "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" + ).click(); + cy.dataCy("update-volume-modal").should("be.visible"); + cy.dataCy("volume-name-input").type("Hello, World"); + cy.contains("button", "Save").should( + "not.have.attr", + "aria-disabled", + "true" + ); cy.contains("button", "Save").click(); cy.validateToast("success", "Successfully updated volume"); cy.dataCy("update-volume-modal").should("not.exist"); @@ -203,7 +227,6 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { describe("Migrate Modal", () => { beforeEach(() => { - cy.setCookie("seen-migrate-guide-cue", "false"); cy.visit("/spawn/volume"); }); it("migrate button is disabled for volumes with the migrating status", () => { @@ -216,25 +239,6 @@ describe("Navigating to Spawn Volume page", { testIsolation: false }, () => { "true" ); }); - it("will persistently not show the guide cue after the Migrate button has been clicked", () => { - cy.dataCy("migrate-cue").should("be.visible"); - cy.dataCy( - "migrate-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" - ).click(); - cy.dataCy("migrate-cue").should("not.exist"); - cy.reload(); - cy.dataCy("migrate-cue").should("not.exist"); - }); - it("will persistently not show the guide cue after the guide cue 'Got it' button has been clicked", () => { - cy.dataCy("migrate-cue").should("be.visible"); - cy.get("[role=dialog]") - .find("button") - .contains("Got it") - .click({ force: true }); - cy.dataCy("migrate-cue").should("not.exist"); - cy.reload(); - cy.dataCy("migrate-cue").should("not.exist"); - }); it("clicking cancel during confirmation renders the Migrate modal form", () => { cy.dataCy( "migrate-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858" 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/action_buttons.ts b/cypress/integration/version/action_buttons.ts index 2d3c19ed19..7b04dcd84e 100644 --- a/cypress/integration/version/action_buttons.ts +++ b/cypress/integration/version/action_buttons.ts @@ -26,7 +26,7 @@ describe("Action Buttons", () => { }); }); - describe("Version dropdown options", { testIsolation: true }, () => { + describe("Version dropdown options", () => { beforeEach(() => { cy.visit(versionPath(patch)); cy.dataCy("ellipsis-btn").click(); 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..88d164b7b6 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"); @@ -41,8 +36,6 @@ describe("Restarting a patch", { testIsolation: false }, () => { cy.getInputByLabel("All").check({ force: true }); cy.dataCy("task-status-filter").click(); - // ideally this would target the text field itself but leafygreen Body tags dont - // support cy-data elements currently cy.dataCy("version-restart-modal").should( "contain.text", "Are you sure you want to restart the 1 selected tasks?" @@ -73,9 +66,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 +74,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..2bd88b5b1d 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,12 +95,12 @@ 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" ); // Check that filter values have updated. cy.toggleTableFilter(2); - cy.getInputByLabel("Unscheduled") + cy.getInputByLabel("Succeeded") .should("have.attr", "aria-checked") .and("equal", "true"); @@ -116,11 +116,9 @@ describe("Version route", () => { // Apply name filter cy.toggleTableFilter(1); - cy.dataCy("taskname-input-wrapper") - .find("input") - .focus() - .type("a-task-name") - .type("{enter}"); + cy.dataCy("taskname-input-wrapper").find("input").as("taskNameInput"); + cy.get("@taskNameInput").focus(); + cy.get("@taskNameInput").type("a-task-name{enter}"); // name filter shouldn't be applied after clicking task status badge cy.dataCy("build-variants").within(() => { @@ -128,7 +126,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" ); }); }); @@ -167,11 +165,9 @@ describe("Version route", () => { // Apply name filter cy.toggleTableFilter(1); - cy.dataCy("taskname-input-wrapper") - .find("input") - .focus() - .type("a-task-name") - .type("{enter}"); + cy.dataCy("taskname-input-wrapper").find("input").as("taskNameInput"); + cy.get("@taskNameInput").focus(); + cy.get("@taskNameInput").type("a-task-name{enter}"); // name filter shouldn't be applied after clicking build variant name cy.dataCy("build-variant-display-name").first().click(); diff --git a/cypress/integration/version/task_duration.ts b/cypress/integration/version/task_duration.ts index 3ec4761342..70ca0e77f8 100644 --- a/cypress/integration/version/task_duration.ts +++ b/cypress/integration/version/task_duration.ts @@ -1,10 +1,9 @@ describe("Task Duration Tab", () => { - const patch = "5e4ff3abe3c3317e352062e4"; - const TASK_DURATION_ROUTE = `/version/${patch}/task-duration`; - + beforeEach(() => { + cy.visit("/version/5e4ff3abe3c3317e352062e4/task-duration"); + }); describe("when interacting with the filters on the page", () => { it("updates URL appropriately when task name filter is applied", () => { - cy.visit(TASK_DURATION_ROUTE); const filterText = "test-annotation"; // Apply text filter. cy.dataCy("task-name-filter-popover").click(); @@ -22,17 +21,15 @@ describe("Task Duration Tab", () => { }); it("updates URL appropriately when status filter is applied", () => { - cy.visit(TASK_DURATION_ROUTE); - // Apply status filter. cy.dataCy("status-filter-popover").click(); cy.dataCy("tree-select-options").within(() => cy.contains("Running").click({ force: true }) ); - cy.dataCy("task-duration-table-row").should("have.length", 2); + cy.dataCy("task-duration-table-row").should("have.length", 3); cy.location("search").should( "include", - `duration=DESC&page=0&statuses=started` + "duration=DESC&page=0&statuses=running-umbrella,started,dispatched" ); // Clear status filter. cy.dataCy("status-filter-popover").click(); @@ -43,7 +40,6 @@ describe("Task Duration Tab", () => { }); it("updates URL appropriately when build variant filter is applied", () => { - cy.visit(TASK_DURATION_ROUTE); const filterText = "Lint"; // Apply text filter. cy.dataCy("build-variant-filter-popover").click(); @@ -61,7 +57,6 @@ describe("Task Duration Tab", () => { }); it("updates URL appropriately when sort is changing", () => { - cy.visit(TASK_DURATION_ROUTE); // The default sort (DURATION DESC) should be applied cy.location("search").should("include", "duration=DESC"); const longestTask = "test-thirdparty"; @@ -71,7 +66,7 @@ describe("Task Duration Tab", () => { .should("contain", longestTask); cy.dataCy("duration-sort-icon").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() @@ -79,7 +74,6 @@ describe("Task Duration Tab", () => { }); it("clearing all filters resets to the default sort", () => { - cy.visit(TASK_DURATION_ROUTE); cy.dataCy("duration-sort-icon").click(); cy.location("search").should("include", "duration=ASC"); cy.contains("Clear all filters").click(); @@ -87,7 +81,6 @@ describe("Task Duration Tab", () => { }); it("shows message when no test results are found", () => { - cy.visit(TASK_DURATION_ROUTE); const filterText = "this_does_not_exist"; cy.dataCy("task-name-filter-popover").click(); diff --git a/cypress/integration/version/unscheduled_patch/configure_patch.ts b/cypress/integration/version/unscheduled_patch/configure_patch.ts index 1b0f5c5964..7bb4097458 100644 --- a/cypress/integration/version/unscheduled_patch/configure_patch.ts +++ b/cypress/integration/version/unscheduled_patch/configure_patch.ts @@ -120,8 +120,8 @@ describe("Configure Patch Page", () => { }); }); - describe("Configuring a patch", { testIsolation: false }, () => { - before(() => { + describe("Configuring a patch", () => { + beforeEach(() => { cy.visit(`patch/${unactivatedPatchId}/configure/tasks`); }); it("Can update patch description by typing into `Patch Name` input field", () => { @@ -166,22 +166,18 @@ describe("Configure Patch Page", () => { }); }); it("Clicking on checked tasks unchecks them and updates task counts", () => { + cy.dataCy("task-checkbox").check({ force: true }); cy.dataCy("build-variant-list-item") .find('[data-cy="task-count-badge"]') .should("exist"); - let count = 7; + cy.dataCy("build-variant-list-item") + .find('[data-cy="task-count-badge"]') + .should("contain.text", "1"); cy.dataCy("selected-task-disclaimer").contains( - `${count} tasks across 1 build variant` + "1 task across 1 build variant" ); - cy.dataCy("task-checkbox").each(($el) => { - cy.wrap($el).should("be.checked"); - cy.wrap($el).uncheck({ - force: true, - }); - count -= 1; - cy.wrap($el).should("not.be.checked"); - }); + cy.dataCy("task-checkbox").uncheck({ force: true }); cy.dataCy("build-variant-list-item") .find('[data-cy="task-count-badge"]') @@ -257,6 +253,9 @@ describe("Configure Patch Page", () => { cy.dataCy("select-all-checkbox").should("be.checked"); }); it("Unchecking all task checkboxes should uncheck the Select All checkbox", () => { + cy.dataCy("task-checkbox").each(($el) => { + cy.wrap($el).check({ force: true }); + }); cy.dataCy("select-all-checkbox").should("be.checked"); cy.dataCy("task-checkbox").each(($el) => { cy.wrap($el).uncheck({ force: true }); @@ -341,7 +340,7 @@ describe("Configure Patch Page", () => { cy.dataCy("build-variant-list-item") .contains("RHEL 7.2 zLinux") .click(); - cy.dataCy("task-checkbox").should("have.length", 6); + cy.dataCy("task-checkbox").should("have.length", 8); }); it("Checking a deduplicated task between multiple build variants updates the task within each selected build variant", () => { @@ -440,7 +439,7 @@ describe("Configure Patch Page", () => { cy.dataCy("build-variant-list-item") .contains("RHEL 7.2 zLinux") .click(); - + cy.dataCy("select-all-checkbox").check({ force: true }); cy.dataCy("task-checkbox").each(($el) => { cy.wrap($el).should("be.checked"); }); @@ -448,6 +447,7 @@ describe("Configure Patch Page", () => { cy.dataCy("build-variant-list-item") .contains("RHEL 7.1 POWER8") .click(); + cy.dataCy("select-all-checkbox").check({ force: true }); cy.dataCy("task-checkbox").each(($el) => { cy.wrap($el).should("be.checked"); @@ -475,13 +475,12 @@ describe("Configure Patch Page", () => { }); it("Shift+click will select the clicked build variant along with all build variants between the clicked build variant and the first selected build variant in the list", () => { - cy.get("body").type("{shift}", { - release: false, - }); // hold shift cy.dataCy("build-variant-list-item") .contains("RHEL 7.2 zLinux") .click(); - + cy.get("body").type("{shift}", { + release: false, + }); // hold shift cy.dataCy("build-variant-list-item").contains("Windows").click(); cy.get("[data-selected=true]").its("length").should("eq", 6); @@ -489,7 +488,8 @@ describe("Configure Patch Page", () => { }); describe("Selecting a trigger alias", () => { - before(() => { + beforeEach(() => { + cy.visit(`/version/${unactivatedPatchId}`); cy.dataCy("trigger-alias-list-item") .contains("logkeeper-alias") .click(); @@ -545,7 +545,14 @@ describe("Configure Patch Page", () => { }); it("Updates the badge count when the trigger alias is deselected", () => { - cy.dataCy("alias-checkbox").uncheck({ + cy.dataCy("select-all-checkbox").check({ + force: true, + }); + + cy.dataCy("trigger-alias-list-item") + .find('[data-cy="task-count-badge"]') + .should("exist"); + cy.dataCy("select-all-checkbox").uncheck({ force: true, }); @@ -562,7 +569,9 @@ describe("Configure Patch Page", () => { }); it("Clicking 'Schedule' button schedules patch and redirects to patch page", () => { const val = "hello world"; - cy.dataCy(`patch-name-input`).as("patchNameInput").clear().type(val); + cy.dataCy(`patch-name-input`).as("patchNameInput"); + cy.get("@patchNameInput").clear(); + cy.get("@patchNameInput").type(val); cy.dataCy("task-checkbox").first().check({ force: true }); cy.intercept("POST", GQL_URL, (req) => { if (hasOperationName(req, "SchedulePatch")) { diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index e18f4e160b..1efa19d08b 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" ); @@ -109,6 +108,7 @@ Cypress.Commands.add( Cypress.Commands.add( "selectLGOption", (label: string, option: string | RegExp) => { + cy.getInputByLabel(label).should("not.have.attr", "aria-disabled", "true"); cy.getInputByLabel(label).click({ force: true }); // open select cy.get('[role="listbox"]').should("have.length", 1); cy.get('[role="listbox"]').within(() => { @@ -126,3 +126,16 @@ Cypress.Commands.add("overwriteGQL", (operationName: string, body: any) => { } }); }); + +// TODO: Usage of openExpandableCard introduced in DEVPROD-2415 can be deleted after DEVPROD-2608 +Cypress.Commands.add("openExpandableCard", (cardTitle: string) => { + cy.dataCy("expandable-card-title") + .contains(cardTitle) + .closest("[role='button']") + .as("card-btn"); + cy.get("@card-btn").then(($btn) => { + if ($btn.attr("aria-expanded") !== "true") { + cy.get("@card-btn").click(); + } + }); +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index e77d5b9f66..1f8c09d788 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') @@ -120,16 +121,46 @@ declare global { * @param body - The replacement response body */ overwriteGQL(operationName: string, body: any); + /** + * Command to open expandable card + * @param cardTitle - The title of the card to expand + */ + openExpandableCard(cardTitle: string); } } } -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 cb0bfaf2a8..bee526c046 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/constants/cookies.ts b/src/constants/cookies.ts index 1d997b1ac3..1a7379954b 100644 --- a/src/constants/cookies.ts +++ b/src/constants/cookies.ts @@ -16,6 +16,5 @@ export const INCLUDE_COMMIT_QUEUE_USER_PATCHES = "include-commit-queue-user-patches"; export const INCLUDE_HIDDEN_PATCHES = "include-hidden-patches"; export const SEEN_HONEYCOMB_GUIDE_CUE = "seen-honeycomb-guide-cue"; -export const SEEN_MIGRATE_GUIDE_CUE = "seen-migrate-guide-cue"; export const SLACK_NOTIFICATION_BANNER = "has-closed-slack-banner"; export const SUBSCRIPTION_METHOD = "subscription-method"; diff --git a/src/pages/projectSettings/index.tsx b/src/pages/projectSettings/index.tsx index 7bb5203245..bbbe36792f 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; @@ -183,7 +187,7 @@ const ProjectSettings: React.FC = () => { - {project ? ( + {hasLoaded ? ( = ({ const dataSource: TableVolume[] = useMemo(() => { const volumesCopy = [...volumes]; volumesCopy.sort(sortByHost); - const firstMigrateableVolumeId = - volumesCopy.find((v) => v.homeVolume)?.id ?? ""; return volumes.map((v) => ({ ...v, - showMigrateBtnCue: - v.id === firstMigrateableVolumeId && - Cookies.get(SEEN_MIGRATE_GUIDE_CUE) !== "true", })); }, [volumes]); return ( diff --git a/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateButton.tsx b/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateButton.tsx index 36378b6449..a1c49bbcba 100644 --- a/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateButton.tsx +++ b/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateButton.tsx @@ -1,8 +1,5 @@ import { useRef, useState } from "react"; import Button, { Size } from "@leafygreen-ui/button"; -import { GuideCue } from "@leafygreen-ui/guide-cue"; -import Cookies from "js-cookie"; -import { SEEN_MIGRATE_GUIDE_CUE } from "constants/cookies"; import { TableVolume } from "types/spawn"; import { MigrateVolumeModal } from "./MigrateVolumeModal"; @@ -12,26 +9,9 @@ interface Props { export const MigrateButton: React.FC = ({ volume }) => { const [openModal, setOpenModal] = useState(false); - const [openGuideCue, setOpenGuideCue] = useState(volume.showMigrateBtnCue); const triggerRef = useRef(null); - const onHideCue = () => { - Cookies.set(SEEN_MIGRATE_GUIDE_CUE, "true"); - setOpenGuideCue(false); - }; return ( <> - - You can now migrate your home volume to a new spawn host! -