diff --git a/.evergreen/scripts/constants.js b/.evergreen/scripts/constants.js index 5f2aa23190..a6a9e2487a 100644 --- a/.evergreen/scripts/constants.js +++ b/.evergreen/scripts/constants.js @@ -54,8 +54,8 @@ const MARKDOWN_EXT = ".md"; const IGNORED_FILE_EXTENSIONS = new Set([MARKDOWN_EXT]); -const CYPRESS_PARALLEL_COUNT = 2; -const PLAYWRIGHT_PARALLEL_COUNT = 2; +const CYPRESS_PARALLEL_COUNT = 1; +const PLAYWRIGHT_PARALLEL_COUNT = 4; export { Tasks, diff --git a/apps/spruce/cypress/integration/dummy_test.ts b/apps/spruce/cypress/integration/dummy_test.ts new file mode 100644 index 0000000000..a9c3a7c8d5 --- /dev/null +++ b/apps/spruce/cypress/integration/dummy_test.ts @@ -0,0 +1,12 @@ +describe("Downstream Projects Tab", () => { + const DOWNSTREAM_ROUTE = `/version/5f74d99ab2373627c047c5e5/downstream-projects`; + + beforeEach(() => { + cy.visit(DOWNSTREAM_ROUTE); + }); + + it("shows number of failed patches in the Downstream tab label", () => { + cy.dataCy("downstream-tab-badge").should("exist"); + cy.dataCy("downstream-tab-badge").should("contain.text", "1"); + }); +}); diff --git a/apps/spruce/cypress/integration/projectSettings/access.ts b/apps/spruce/cypress/integration/projectSettings/access.ts deleted file mode 100644 index edb32df24a..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/access.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - getProjectSettingsRoute, - project, - ProjectSettingsTabRoutes, - projectUseRepoEnabled, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Access page", () => { - const origin = getProjectSettingsRoute( - projectUseRepoEnabled, - ProjectSettingsTabRoutes.Access, - ); - 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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - // Assert persistence - cy.reload(); - cy.getInputByLabel("Username").as("usernameInput"); - 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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - // Assert persistence - cy.reload(); - saveButtonEnabled(false); - 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").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("default-to-repo-button").click(); - cy.getInputByLabel('Type "confirm" to confirm your action').type("confirm"); - 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(getProjectSettingsRoute(project, ProjectSettingsTabRoutes.Access)); - cy.contains("Add Username").click(); - cy.getInputByLabel("Username").type("mongodb_user"); - clickSaveAndConfirmDiff(); - cy.validateToast("error", "There was an error saving the project"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/admin_actions.ts b/apps/spruce/cypress/integration/projectSettings/admin_actions.ts deleted file mode 100644 index 03389ff8c9..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/admin_actions.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { getProjectSettingsRoute, project } from "./constants"; - -describe("projectSettings/admin_actions", () => { - describe("Duplicating a project", () => { - const destination = getProjectSettingsRoute(project); - - it("Successfully duplicates a project with warnings", () => { - cy.visit(destination); - cy.dataCy("new-project-button").click(); - cy.dataCy("new-project-menu").should("be.visible"); - cy.dataCy("copy-project-button").click(); - cy.dataCy("copy-project-modal").should("be.visible"); - cy.dataCy("performance-tooling-banner").should("be.visible"); - - cy.dataCy("project-name-input").type("copied-project"); - - cy.contains("button", "Duplicate").click(); - cy.validateToast( - "warning", - "The project was duplicated but may not be fully enabled", - ); - - cy.url().should("include", "copied-project"); - }); - }); - - describe("Creating a new project and deleting it", () => { - it("Successfully creates a new project and then deletes it", () => { - // Create project - cy.visit(getProjectSettingsRoute(project)); - cy.dataCy("new-project-button").click(); - cy.dataCy("new-project-menu").should("be.visible"); - cy.dataCy("create-project-button").click(); - cy.dataCy("create-project-modal").should("be.visible"); - cy.dataCy("performance-tooling-banner").should("be.visible"); - - cy.dataCy("project-name-input").type("my-new-project"); - cy.dataCy("new-owner-select").contains("evergreen-ci"); - cy.dataCy("new-repo-input").should("have.value", "spruce"); - cy.dataCy("new-repo-input").clear(); - cy.dataCy("new-repo-input").type("new-repo"); - - cy.contains("button", "Create project").click(); - cy.validateToast( - "success", - "Successfully created the project “my-new-project”", - ); - - cy.url().should("include", "my-new-project"); - - // Delete project - cy.visit(getProjectSettingsRoute("my-new-project")); - 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("delete-project-button").scrollIntoView(); - cy.dataCy("delete-project-button").click(); - cy.dataCy("delete-project-modal") - .find("button") - .contains("Delete") - .parent() - .click(); - cy.validateToast("success", "The project “my-new-project” was deleted."); - - cy.reload(); - cy.validateToast( - "error", - "There was an error loading the project my-new-project", - ); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/attaching_to_repo.ts b/apps/spruce/cypress/integration/projectSettings/attaching_to_repo.ts deleted file mode 100644 index 8722cb5aaa..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/attaching_to_repo.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { getProjectSettingsRoute, project } from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Attaching Spruce to a repo", () => { - const origin = getProjectSettingsRoute(project); - - beforeEach(() => { - cy.visit(origin); - }); - - it("Saves and attaches new repo and shows warnings on the Github page", () => { - cy.dataCy("repo-input").as("repoInput").clear(); - cy.get("@repoInput").type("evergreen"); - cy.dataCy("attach-repo-button").should( - "have.attr", - "aria-disabled", - "true", - ); - clickSaveAndConfirmDiff(); - 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/apps/spruce/cypress/integration/projectSettings/constants.ts b/apps/spruce/cypress/integration/projectSettings/constants.ts deleted file mode 100644 index c8a98e9efc..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/constants.ts +++ /dev/null @@ -1,46 +0,0 @@ -export enum ProjectSettingsTabRoutes { - General = "general", - Access = "access", - Variables = "variables", - GithubCommitQueue = "github-commitqueue", - Notifications = "notifications", - PatchAliases = "patch-aliases", - VirtualWorkstation = "virtual-workstation", - ProjectTriggers = "project-triggers", - PeriodicBuilds = "periodic-builds", - Plugins = "plugins", - EventLog = "event-log", - ViewsAndFilters = "views-and-filters", - GithubAppSettings = "github-app-settings", - GithubPermissionGroups = "github-permission-groups", - MergeQueue = "merge-queue", - PullRequests = "pull-requests", - CommitChecks = "commit-checks", - GitTags = "git-tags", -} - -export const getProjectSettingsRoute = ( - identifier: string, - tab: ProjectSettingsTabRoutes = ProjectSettingsTabRoutes.General, -) => `project/${identifier}/settings/${tab}`; - -export const getRepoSettingsRoute = ( - repoId: string, - tab: ProjectSettingsTabRoutes = ProjectSettingsTabRoutes.General, -) => `repo/${repoId}/settings/${tab}`; - -export const project = "spruce"; -export const projectUseRepoEnabled = "evergreen"; -export const repo = "602d70a2b2373672ee493184"; - -/** - * `saveButtonEnabled` checks if the save button is enabled or disabled. - * @param isEnabled - if true, the save button should be enabled. If false, the save button should be disabled. - */ -export const saveButtonEnabled = (isEnabled: boolean = true) => { - cy.dataCy("save-settings-button").should( - isEnabled ? "not.have.attr" : "have.attr", - "aria-disabled", - "true", - ); -}; diff --git a/apps/spruce/cypress/integration/projectSettings/defaulting_to_repo.ts b/apps/spruce/cypress/integration/projectSettings/defaulting_to_repo.ts deleted file mode 100644 index 932112c9e7..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/defaulting_to_repo.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { - getProjectSettingsRoute, - getRepoSettingsRoute, - project, - ProjectSettingsTabRoutes, - projectUseRepoEnabled, - repo, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Project Settings when defaulting to repo", () => { - const origin = getProjectSettingsRoute(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", `/${getRepoSettingsRoute(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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - }); - - it("Saves when batch time is updated", () => { - cy.dataCy("batch-time-input").clear(); - cy.dataCy("batch-time-input").type("12"); - clickSaveAndConfirmDiff(); - cy.dataCy("batch-time-input").should("have.value", 12); - cy.validateToast("success", "Successfully updated project"); - // Check if clearing attached project defaults batchtime to repo value - cy.dataCy("batch-time-input").clear(); - clickSaveAndConfirmDiff(); - cy.dataCy("batch-time-input") - .invoke("attr", "placeholder") - .should("equal", "60 (Default from repo)"); - cy.validateToast("success", "Successfully updated project"); - // Update repo batch time and check if project batch time placeholder is updated - cy.dataCy("attached-repo-link").click(); - cy.dataCy("batch-time-input").should("have.value", 60); - cy.dataCy("batch-time-input").clear(); - clickSaveAndConfirmDiff(); - cy.dataCy("batch-time-input").should("have.value", 0); - cy.validateToast("success", "Successfully updated repo"); - cy.visit(origin); - cy.dataCy("batch-time-input") - .invoke("attr", "placeholder") - .should("equal", "0 (Default from repo)"); - // Check if clearing project batch time saves as 0 instead of null - cy.visit(getProjectSettingsRoute(project)); - cy.dataCy("batch-time-input").should("have.value", 60); - cy.dataCy("batch-time-input").clear(); - clickSaveAndConfirmDiff(); - cy.dataCy("batch-time-input").should("have.value", 0); - 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.dataCy("var-description-input").type("Description for variable a"); - 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("var-description-input") - .first() - .type("Description for variable b"); - - cy.dataCy("add-button").click(); - cy.dataCy("var-name-input").first().type("c"); - cy.dataCy("var-value-input").first().type("3"); - cy.dataCy("var-description-input") - .first() - .type("Description for variable c"); - - clickSaveAndConfirmDiff(); - 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 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(".*"); - clickSaveAndConfirmDiff(); - 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("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(); - clickSaveAndConfirmDiff(); - cy.validateToast("error", "There was an error saving the project"); - }); - - 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(getRepoSettingsRoute(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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated repo"); - cy.visit(origin); - cy.dataCy("navitem-github-commitqueue").click(); - cy.dataCy("default-to-repo-button").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("default-to-repo-button").click(); - cy.dataCy("default-to-repo-modal").should("be.visible"); - cy.getInputByLabel('Type "confirm" to confirm your action').type( - "confirm", - ); - cy.dataCy("default-to-repo-modal").contains("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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - // Default to repo patch alias - cy.contains("label", "Default to Repo Patch Aliases").click(); - clickSaveAndConfirmDiff(); - 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"); - clickSaveAndConfirmDiff(); - 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", - `/${getRepoSettingsRoute(repo, ProjectSettingsTabRoutes.VirtualWorkstation)}`, - ); - cy.contains("button", "Add Command").click(); - cy.dataCy("command-input").type("a repo command"); - clickSaveAndConfirmDiff(); - 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("have.attr", "aria-disabled", "true"); - // 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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("command-row") - .contains("textarea", "a project command") - .should("have.attr", "aria-disabled", "false"); - cy.contains("label", "Default to Repo Commands").click(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("command-row") - .contains("textarea", "a repo command") - .should("have.attr", "aria-disabled", "true"); - 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(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.getInputByLabel("Override Repo Commands").should("be.checked"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/github_app_settings.ts b/apps/spruce/cypress/integration/projectSettings/github_app_settings.ts deleted file mode 100644 index 7abf3b80dc..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/github_app_settings.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - getProjectSettingsRoute, - ProjectSettingsTabRoutes, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("GitHub app settings", () => { - const destination = getProjectSettingsRoute( - "spruce", - ProjectSettingsTabRoutes.GithubAppSettings, - ); - const selectMenu = "[role='listbox']"; - const permissionGroups = { - all: "All app permissions", - readPRs: "Read Pull Requests", - writeIssues: "Write Issues", - }; - - beforeEach(() => { - cy.visit(destination); - // Wait for page content to finish loading. - cy.contains("Token Permission Restrictions"); - }); - - it("save button should be disabled by default", () => { - saveButtonEnabled(false); - }); - - it("should be able to replace app credentials", () => { - // Replace button should be visible when app is defined. - cy.dataCy("replace-app-credentials-button").should("be.visible"); - cy.dataCy("replace-app-credentials-button").click(); - cy.dataCy("replace-github-credentials-modal").should("be.visible"); - - // Replace button in modal should be disabled without input. - cy.dataCy("replace-github-credentials-modal") - .find("button") - .contains("Replace") - .parent() - .should("have.attr", "aria-disabled", "true"); - - // Fill in new credentials. - cy.dataCy("replace-app-id-input").type("99999"); - cy.dataCy("replace-private-key-input").type("new-private-key"); - - // Replace button should now be enabled. - cy.dataCy("replace-github-credentials-modal") - .find("button") - .contains("Replace") - .parent() - .should("not.have.attr", "aria-disabled", "true"); - - cy.dataCy("replace-github-credentials-modal") - .find("button") - .contains("Replace") - .parent() - .click(); - cy.validateToast( - "success", - "GitHub app credentials were successfully replaced.", - ); - }); - - it("should be able to save different permission groups for requesters, then return to defaults", () => { - cy.dataCy("permission-group-input").should("have.length", 8); - cy.dataCy("permission-group-input").eq(0).as("permission-group-input-0"); - cy.dataCy("permission-group-input").eq(4).as("permission-group-input-4"); - - // Save different permission groups. - cy.get("@permission-group-input-0").click(); - cy.get(selectMenu) - .first() - .within(() => { - cy.contains(permissionGroups.readPRs).click(); - }); - cy.get("@permission-group-input-4").click(); - cy.get(selectMenu) - .first() - .within(() => { - cy.contains(permissionGroups.writeIssues).click(); - }); - cy.dataCy("save-settings-button").scrollIntoView(); - saveButtonEnabled(true); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - // Changes should persist on the page. - cy.reload(); - cy.get("@permission-group-input-0").contains(permissionGroups.readPRs); - cy.get("@permission-group-input-4").contains(permissionGroups.writeIssues); - - // Return to and save defaults. - cy.get("@permission-group-input-0").click(); - cy.get(selectMenu) - .first() - .within(() => { - cy.contains(permissionGroups.all).click(); - }); - cy.get("@permission-group-input-4").click(); - cy.get(selectMenu) - .first() - .within(() => { - cy.contains(permissionGroups.all).click(); - }); - cy.dataCy("save-settings-button").scrollIntoView(); - saveButtonEnabled(true); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/github_permission_groups.ts b/apps/spruce/cypress/integration/projectSettings/github_permission_groups.ts deleted file mode 100644 index af9ca254d1..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/github_permission_groups.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - getProjectSettingsRoute, - ProjectSettingsTabRoutes, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("GitHub permission groups", () => { - const destination = getProjectSettingsRoute( - "logkeeper", - ProjectSettingsTabRoutes.GithubPermissionGroups, - ); - beforeEach(() => { - cy.visit(destination); - // Wait for page content to finish loading. - cy.contains("Token Permission Groups"); - }); - - it("should not have any permission groups defined", () => { - cy.dataCy("permission-group-list").children().should("have.length", 0); - saveButtonEnabled(false); - }); - - it("should throw an error if permission group definitions are invalid", () => { - cy.contains("button", /^Add permission group$/).should("be.visible"); - cy.contains("button", /^Add permission group$/).click(); - cy.dataCy("permission-group-list").children().should("have.length", 1); - - const invalidGithubPermission = "invalid_github_permission"; - cy.dataCy("permission-group-title-input").type("test permission group"); - cy.dataCy("add-permission-button").should("be.visible"); - cy.dataCy("add-permission-button").click(); - cy.dataCy("permission-type-input").type(invalidGithubPermission); - cy.dataCy("permission-value-input").click(); - cy.contains("Write").click({ force: true }); - saveButtonEnabled(true); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - cy.validateToast("error", "There was an error saving the project"); - }); - - it("should be able to save permission group, then delete it", () => { - // Add permission group. - cy.contains("button", /^Add permission group$/).should("be.visible"); - cy.contains("button", /^Add permission group$/).click(); - cy.dataCy("permission-group-list").children().should("have.length", 1); - - cy.dataCy("permission-group-title-input").type("test permission group"); - cy.dataCy("add-permission-button").should("be.visible"); - cy.dataCy("add-permission-button").click(); - cy.dataCy("permission-type-input").type("actions"); - cy.dataCy("permission-value-input").click(); - cy.contains("Read").click(); - saveButtonEnabled(true); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - // Delete permission group. - cy.reload(); - cy.dataCy("permission-group-list").children().should("have.length", 1); - cy.dataCy("delete-item-button").click(); - cy.dataCy("permission-group-list").children().should("have.length", 0); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/not_defaulting_to_repo.ts b/apps/spruce/cypress/integration/projectSettings/not_defaulting_to_repo.ts deleted file mode 100644 index e4023d08cc..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/not_defaulting_to_repo.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - getProjectSettingsRoute, - project, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Project Settings when not defaulting to repo", () => { - const origin = getProjectSettingsRoute(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"); - }); - - it("Allows enabling Run Every Mainline Commit", () => { - cy.dataCy("navitem-general").click(); - cy.dataCy("run-every-mainline-commit-radio-box").children().first().click(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("run-every-mainline-commit-radio-box") - .children() - .first() - .children() - .first() - .should("have.attr", "aria-checked", "true"); - }); - - 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.dataCy("var-description-input").type("Sample description"); - cy.dataCy("var-private-input").should("be.checked"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("var-value-input").should("have.value", "{REDACTED}"); - cy.dataCy("var-name-input").should("have.attr", "aria-disabled", "true"); - cy.dataCy("var-value-input").should("have.attr", "aria-disabled", "true"); - cy.dataCy("var-private-input").should( - "have.attr", - "aria-disabled", - "true", - ); - // Admin checkbox should not be disabled. - cy.dataCy("var-admin-input").should( - "have.attr", - "aria-disabled", - "false", - ); - // Description input should not be disabled. - cy.dataCy("var-description-input").should( - "have.attr", - "aria-disabled", - "false", - ); - }); - - 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"); - clickSaveAndConfirmDiff(); - 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"); - clickSaveAndConfirmDiff(); - 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("var-description-input").type("Description for sample_name"); - 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("var-description-input") - .first() - .type("Description for sample_name_2"); - 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-description-input") - .first() - .type("Description for admin_var"); - clickSaveAndConfirmDiff(); - 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-description-input") - .eq(0) - .should("have.value", "Description for admin_var"); - cy.dataCy("var-name-input").eq(1).should("have.value", "sample_name"); - cy.dataCy("var-description-input") - .eq(1) - .should("have.value", "Description for sample_name"); - cy.dataCy("var-name-input").eq(2).should("have.value", "sample_name_2"); - cy.dataCy("var-description-input") - .eq(2) - .should("have.value", "Description for 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(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("var-name-input").should("not.exist"); - // Verify persistence - cy.reload(); - saveButtonEnabled(false); - cy.dataCy("var-name-input").should("not.exist"); - }); - }); - - describe("GitHub 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"); - - clickSaveAndConfirmDiff(); - 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("allows a user to schedule the next build on the current day", () => { - const now = new Date(2025, 8, 16); // month is 0-indexed - - cy.clock(now, ["Date"]); - // Reload to apply clock changes - cy.reload(); - cy.dataCy("add-button").click(); - cy.validateDatePickerDate("date-picker", { - year: "2025", - month: "09", - day: "16", - }); - - cy.get("input[id='year']").as("startOfDateInput"); - - cy.clearDatePickerInput(); - - cy.get("@startOfDateInput").type("20250101"); - // Check for error text - cy.contains("Date must be after").should("exist"); - cy.validateDatePickerDate("date-picker", { - year: "2025", - month: "01", - day: "01", - }); - - cy.clearDatePickerInput(); - - cy.get("@startOfDateInput").type("20250920"); - // No error text - cy.contains("Date must be after").should("not.exist"); - cy.validateDatePickerDate("date-picker", { - year: "2025", - month: "09", - day: "20", - }); - - cy.clearDatePickerInput(); - - cy.get("@startOfDateInput").type("20250916"); - // No error text - cy.contains("Date must be after").should("not.exist"); - cy.validateDatePickerDate("date-picker", { - year: "2025", - month: "09", - day: "16", - }); - }); - - 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"); - clickSaveAndConfirmDiff(); - 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/apps/spruce/cypress/integration/projectSettings/notifications.ts b/apps/spruce/cypress/integration/projectSettings/notifications.ts deleted file mode 100644 index 883162b94d..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/notifications.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { - getProjectSettingsRoute, - ProjectSettingsTabRoutes, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Notifications", () => { - const origin = getProjectSettingsRoute( - "evergreen", - ProjectSettingsTabRoutes.Notifications, - ); - beforeEach(() => { - cy.visit(origin); - }); - it("shows correct intitial state", () => { - cy.dataCy("default-to-repo-button").should("not.exist"); - cy.contains("No subscriptions are defined.").should("be.visible"); - saveButtonEnabled(false); - }); - 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").click(); - cy.dataCy("expandable-card").should("contain.text", "New Subscription"); - cy.selectLGOption("Event", "Any version finishes"); - cy.selectLGOption("Notification Method", "Email"); - cy.getInputByLabel("Email").type("mohamed.khelif@mongodb.com"); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - saveButtonEnabled(false); - 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(); - clickSaveAndConfirmDiff(); - 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").click(); - cy.dataCy("expandable-card").should("exist").scrollIntoView(); - 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( - "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("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", () => { - const bannerText = "This is a project banner!"; - - // set banner - cy.dataCy("banner-text").clear(); - cy.dataCy("banner-text").type(bannerText); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - // ensure banner is displayed - cy.contains(bannerText).should("be.visible"); - - const taskRoute = - "task/evergreen_ubuntu1604_test_model_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48"; - cy.visit(taskRoute); - cy.contains(bannerText).should("be.visible"); - - const configureRoute = "patch/5e6bb9e23066155a993e0f1b/configure/tasks"; - cy.visit(configureRoute); - cy.contains(bannerText).should("be.visible"); - - const versionRoute = "version/5e4ff3abe3c3317e352062e4"; - cy.visit(versionRoute); - cy.contains(bannerText).should("be.visible"); - - const waterfallRoute = "project/evergreen/waterfall"; - cy.visit(waterfallRoute); - cy.contains(bannerText).should("be.visible"); - - const variantHistoryRoute = "/variant-history/evergreen/ubuntu1604"; - cy.visit(variantHistoryRoute); - cy.contains(bannerText).should("be.visible"); - - // clear banner - cy.visit(origin); - cy.dataCy("banner-text").clear(); - clickSaveAndConfirmDiff(); - - // ensure banner is not displayed - cy.contains(bannerText).should("not.exist"); - - cy.visit(taskRoute); - cy.contains(bannerText).should("not.exist"); - - cy.visit(configureRoute); - cy.contains(bannerText).should("not.exist"); - - cy.visit(versionRoute); - cy.contains(bannerText).should("not.exist"); - - cy.visit(waterfallRoute); - cy.contains(bannerText).should("not.exist"); - - cy.visit(variantHistoryRoute); - cy.contains(bannerText).should("not.exist"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/permissions.ts b/apps/spruce/cypress/integration/projectSettings/permissions.ts deleted file mode 100644 index 9c19dcd410..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/permissions.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { users } from "../../constants"; -import { - projectUseRepoEnabled, - getRepoSettingsRoute, - getProjectSettingsRoute, - repo, -} from "./constants"; - -describe("project/repo permissions", () => { - beforeEach(() => { - cy.logout(); - }); - - describe("projects", () => { - it("disables fields when user lacks edit permissions", () => { - cy.login(users.privileged); - cy.visit(getProjectSettingsRoute(projectUseRepoEnabled)); - cy.dataCy("project-settings-page").within(() => { - cy.get('input[type="radio"]').should( - "have.attr", - "aria-disabled", - "true", - ); - }); - }); - - it("enables fields if user has edit permissions", () => { - cy.login(users.admin); - cy.visit(getProjectSettingsRoute(projectUseRepoEnabled)); - cy.dataCy("project-settings-page").within(() => { - cy.get('input[type="radio"]').should( - "have.attr", - "aria-disabled", - "false", - ); - }); - }); - }); - - describe("repos", () => { - it("disables fields when user lacks edit permissions", () => { - cy.login(users.privileged); - cy.visit(getRepoSettingsRoute(repo)); - cy.dataCy("repo-settings-page").within(() => { - cy.get('input[type="radio"]').should( - "have.attr", - "aria-disabled", - "true", - ); - }); - }); - - it("enables fields if user has edit permissions", () => { - cy.login(users.admin); - cy.visit(getRepoSettingsRoute(repo)); - cy.dataCy("repo-settings-page").within(() => { - cy.get('input[type="radio"]').should( - "have.attr", - "aria-disabled", - "false", - ); - }); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/plugins.ts b/apps/spruce/cypress/integration/projectSettings/plugins.ts deleted file mode 100644 index df0833dc51..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/plugins.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { - getProjectSettingsRoute, - ProjectSettingsTabRoutes, - projectUseRepoEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Plugins", () => { - const patchPage = "version/5ecedafb562343215a7ff297"; - - const addMetadataLink = (metadataLink: { - displayName: string; - url: string; - }) => { - cy.contains("button", "Add metadata link").scrollIntoView(); - cy.contains("button", "Add metadata link").click(); - cy.dataCy("requesters-input").first().click(); - cy.getInputByLabel("Patches").check({ force: true }); - cy.dataCy("requesters-input").first().click(); - cy.dataCy("display-name-input").first().type(metadataLink.displayName); - cy.dataCy("url-template-input").first().type(metadataLink.url, { - parseSpecialCharSequences: false, - }); - }; - - it("Should be able to set external links to render on patch metadata panel", () => { - // Add external links. - cy.visit( - getProjectSettingsRoute( - projectUseRepoEnabled, - ProjectSettingsTabRoutes.Plugins, - ), - ); - addMetadataLink({ - displayName: "An external link 1", - url: "https://example-1.com/{version_id}", - }); - addMetadataLink({ - displayName: "An external link 2", - url: "https://example-2.com/{version_id}", - }); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - - cy.visit(patchPage); - cy.dataCy("external-link").should("have.length", 2); - cy.dataCy("external-link").last().contains("An external link 1"); - cy.dataCy("external-link") - .last() - .should( - "have.attr", - "href", - "https://example-1.com/5ecedafb562343215a7ff297", - ); - cy.dataCy("external-link").first().contains("An external link 2"); - cy.dataCy("external-link") - .first() - .should( - "have.attr", - "href", - "https://example-2.com/5ecedafb562343215a7ff297", - ); - - // Remove external links. - cy.visit( - getProjectSettingsRoute( - projectUseRepoEnabled, - ProjectSettingsTabRoutes.Plugins, - ), - ); - cy.dataCy("delete-item-button").first().click(); - cy.dataCy("delete-item-button").first().click(); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - - cy.visit(patchPage); - cy.dataCy("external-link").should("not.exist"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/project_select.ts b/apps/spruce/cypress/integration/projectSettings/project_select.ts deleted file mode 100644 index b80066f4c5..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/project_select.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getProjectSettingsRoute, project } from "./constants"; - -describe("Clicking on The Project Select Dropdown", () => { - const origin = getProjectSettingsRoute(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/apps/spruce/cypress/integration/projectSettings/project_settings.ts b/apps/spruce/cypress/integration/projectSettings/project_settings.ts deleted file mode 100644 index 19ac03f3bd..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/project_settings.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - getProjectSettingsRoute, - project, - ProjectSettingsTabRoutes, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("projectSettings/project_settings", () => { - describe("Renaming the identifier", () => { - const origin = getProjectSettingsRoute(project); - - beforeEach(() => { - cy.visit(origin); - }); - - 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(); - cy.dataCy("identifier-input").type("new-identifier"); - cy.dataCy("input-warning").should("contain", warningText); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.url().should("include", "new-identifier"); - }); - }); - - describe("A project that has GitHub webhooks disabled", () => { - const origin = getProjectSettingsRoute( - "logkeeper", - ProjectSettingsTabRoutes.GithubCommitQueue, - ); - - 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("have.attr", "aria-disabled", "true"); - }); - }); - - describe("A project id should redirect to the project identifier", () => { - const projectId = "602d70a2b2373672ee493189"; - const origin = getProjectSettingsRoute(projectId); - - beforeEach(() => { - cy.visit(origin); - }); - - it("Redirects to the project identifier", () => { - cy.url().should("include", getProjectSettingsRoute("parsley")); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/repo_settings.ts b/apps/spruce/cypress/integration/projectSettings/repo_settings.ts deleted file mode 100644 index c0b59458d7..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/repo_settings.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { - getProjectSettingsRoute, - getRepoSettingsRoute, - ProjectSettingsTabRoutes, - projectUseRepoEnabled, - repo, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Repo Settings", () => { - const origin = getRepoSettingsRoute(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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated repo"); - }); - }); - - describe("GitHub 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 definition 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); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated repo"); - cy.visit(getProjectSettingsRoute(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( - "have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("task-tags-input").should("have.value", "ttag"); - cy.dataCy("task-tags-input").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.contains( - "A GitHub Patch Definition must be specified for this feature to run.", - ).should("not.exist"); - }); - }); - - describe("Merge Queue section", () => { - beforeEach(() => { - cy.dataCy("cq-enabled-radio-box") - .contains("label", "Enabled") - .as("enableCQButton") - .scrollIntoView(); - }); - it("Enabling merge 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", 3); - cy.contains("Merge Queue Patch Definitions").scrollIntoView(); - cy.dataCy("error-banner") - .contains( - "A Merge Queue Patch Definition must be specified for this feature to run.", - ) - .should("be.visible"); - }); - - it("Does not show override buttons for merge queue patch definitions", () => { - cy.get("@enableCQButton").click(); - cy.dataCy("cq-override-radio-box").should("not.exist"); - }); - - it("Saves a merge queue definition", () => { - cy.get("@enableCQButton").click(); - 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 merge 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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated 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"); - clickSaveAndConfirmDiff(); - 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( - getProjectSettingsRoute( - projectUseRepoEnabled, - ProjectSettingsTabRoutes.Access, - ), - ); - cy.dataCy("default-to-repo-button").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("default-to-repo-button").click(); - cy.dataCy("default-to-repo-modal").should("be.visible"); - cy.getInputByLabel('Type "confirm" to confirm your action').type( - "confirm", - ); - cy.dataCy("default-to-repo-modal").contains("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("have.attr", "aria-disabled", "true"); - cy.dataCy("expandable-card").find("button").should("be.disabled"); - }); - - it("Saving a Patch Trigger Alias shows a success toast and updates the Github 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("Schedule in GitHub Pull Requests").as( - "pullRequestCheckbox", - ); - cy.get("@pullRequestCheckbox").should("not.be.checked"); - cy.get("@pullRequestCheckbox").check({ force: true }); - cy.get("@pullRequestCheckbox").should("be.checked"); - cy.getInputByLabel("Schedule in GitHub Merge Queue").as( - "mergeQueueCheckbox", - ); - cy.get("@mergeQueueCheckbox").should("not.be.checked"); - cy.get("@mergeQueueCheckbox").check({ force: true }); - cy.get("@mergeQueueCheckbox").should("be.checked"); - clickSaveAndConfirmDiff(); - 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 page - cy.dataCy("navitem-github-commitqueue").click(); - - cy.contains("Pull Request Trigger Aliases").scrollIntoView(); - cy.dataCy("github-pr-trigger-aliases").within(() => { - cy.dataCy("pta-item").should("have.length", 1); - cy.contains("my-alias").should("be.visible"); - cy.dataCy("pta-item").trigger("mouseover"); - }); - // The tooltip is rendered in a different part of the DOM so we can't chain the 'within' command. - 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"); - cy.dataCy("github-pr-trigger-aliases").within(() => { - cy.dataCy("pta-item").trigger("mouseout"); - }); - - cy.contains("Merge Queue Trigger Aliases").scrollIntoView(); - cy.dataCy("github-mq-trigger-aliases").within(() => { - 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"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated repo"); - cy.dataCy("array-down-button").click(); - cy.dataCy("save-settings-button").scrollIntoView(); - clickSaveAndConfirmDiff(); - 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/apps/spruce/cypress/integration/projectSettings/stepback_bisect.ts b/apps/spruce/cypress/integration/projectSettings/stepback_bisect.ts deleted file mode 100644 index eeab7187d7..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/stepback_bisect.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - getProjectSettingsRoute, - project, - projectUseRepoEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Stepback bisect setting", () => { - describe("Repo project present", () => { - const destination = getProjectSettingsRoute(projectUseRepoEnabled); - - beforeEach(() => { - cy.visit(destination); - }); - - it("Starts as default to repo", () => { - cy.dataCy("stepback-bisect-group") - .contains("Default to repo") - .find("input") - .should("have.attr", "aria-checked", "true"); - }); - - it("Clicking on enabled and then save shows a success toast", () => { - cy.dataCy("stepback-bisect-group").contains("Enable").click(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - cy.reload(); - - cy.dataCy("stepback-bisect-group") - .contains("Enable") - .find("input") - .should("have.attr", "aria-checked", "true"); - }); - }); - - describe("Repo project not present", () => { - const destination = getProjectSettingsRoute(project); - - beforeEach(() => { - cy.visit(destination); - }); - - it("Starts as disabled", () => { - cy.dataCy("stepback-bisect-group") - .contains("Disable") - .find("input") - .should("have.attr", "aria-checked", "true"); - }); - - it("Clicking on enabled and then save shows a success toast", () => { - cy.dataCy("stepback-bisect-group").contains("Enable").click(); - - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - - cy.reload(); - - cy.dataCy("stepback-bisect-group") - .contains("Enable") - .find("input") - .should("have.attr", "aria-checked", "true"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/utils.ts b/apps/spruce/cypress/integration/projectSettings/utils.ts deleted file mode 100644 index bfe7857f98..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { clickSave } from "../../utils"; - -/** - * Save a project or repo settings page. Clicking the save button opens a diff - * confirmation modal which must be accepted to persist the change. - */ -export const clickSaveAndConfirmDiff = () => { - clickSave(); - cy.dataCy("save-changes-modal").should("be.visible"); - cy.dataCy("save-changes-modal").contains("button", "Save changes").click(); - cy.dataCy("save-changes-modal").should("not.exist"); -}; diff --git a/apps/spruce/cypress/integration/projectSettings/views_and_filters.ts b/apps/spruce/cypress/integration/projectSettings/views_and_filters.ts deleted file mode 100644 index 7342464c9f..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/views_and_filters.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - getProjectSettingsRoute, - ProjectSettingsTabRoutes, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("Views & filters page", () => { - const destination = getProjectSettingsRoute( - "sys-perf", - ProjectSettingsTabRoutes.ViewsAndFilters, - ); - - beforeEach(() => { - cy.visit(destination); - // Wait for page content to finish loading. - cy.dataCy("parsley-filter-list").children().should("have.length", 2); - saveButtonEnabled(false); - }); - - describe("parsley filters", () => { - it("does not allow saving with invalid regular expression or empty expression", () => { - cy.contains("button", "Add filter").should("be.visible").click(); - cy.dataCy("parsley-filter-expression").first().type("*"); - saveButtonEnabled(false); - cy.contains("Value should be a valid regex expression."); - cy.dataCy("parsley-filter-expression").first().clear(); - saveButtonEnabled(false); - }); - - it("does not allow saving with duplicate filter expressions", () => { - cy.contains("button", "Add filter").should("be.visible").click(); - cy.dataCy("parsley-filter-expression").first().type("filter_1"); - saveButtonEnabled(false); - cy.contains("Filter expression already appears in this project."); - }); - - it("can successfully save and delete filter", () => { - cy.contains("button", "Add filter").should("be.visible").click(); - cy.dataCy("parsley-filter-expression").first().type("my_filter"); - saveButtonEnabled(true); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("parsley-filter-list").children().should("have.length", 3); - - cy.dataCy("delete-item-button").first().scrollIntoView(); - cy.dataCy("delete-item-button").first().should("be.visible").click(); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - cy.dataCy("parsley-filter-list").children().should("have.length", 2); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/spawn/host.ts b/apps/spruce/cypress/integration/spawn/host.ts deleted file mode 100644 index 6deca276ef..0000000000 --- a/apps/spruce/cypress/integration/spawn/host.ts +++ /dev/null @@ -1,326 +0,0 @@ -const ascendingSortSpawnHostOrderByHostId = [ - "i-04ade558e1e26b0ad", - "i-07669e7a3cd2c238c", - "i-092593689871a50dc", -]; -const descendingSortSpawnHostOrderByHostId = [ - "i-092593689871a50dc", - "i-07669e7a3cd2c238c", - "i-04ade558e1e26b0ad", -]; - -const descendingSortSpawnHostOrderByExpiration = [ - "i-092593689871a50dc", - "i-07669e7a3cd2c238c", - "i-04ade558e1e26b0ad", -]; -const ascendingSortSpawnHostOrderByExpiration = [ - "i-04ade558e1e26b0ad", - "i-07669e7a3cd2c238c", - "i-092593689871a50dc", -]; - -const hostTaskId = - "evergreen_ubuntu1604_dist_patch_33016573166a36bd5f46b4111151899d5c4e95b1_5ecedafb562343215a7ff297_20_05_27_21_39_46"; -const distroId = "windows-64-vs2015-small"; - -describe("Navigating to Spawn Host page", () => { - beforeEach(() => { - cy.visit("/spawn/host"); - }); - it("Visiting the spawn host page should display all of your spawned hosts", () => { - cy.dataCy("leafygreen-table-row").should("have.length", 3); - }); - it("Visiting the spawn host page should not have any cards expanded by default", () => { - cy.dataCy("spawn-host-card").should("not.exist"); - }); - it("Clicking on a spawn host row should toggle the host card", () => { - cy.dataCy("spawn-host-card").should("not.exist"); - cy.get("button[aria-label='Expand row']").first().click(); - cy.dataCy("spawn-host-card").should("be.visible"); - cy.get("button[aria-label='Collapse row']").first().click(); - cy.dataCy("spawn-host-card").should("not.exist"); - }); - it("Visiting the spawn host page with an id in the url should open the page with the row expanded", () => { - cy.visit("/spawn/host?host=i-092593689871a50dc"); - cy.dataCy("spawn-host-card").first().should("be.visible"); - cy.dataCy("spawn-host-card").eq(1).should("not.exist"); - }); - it("Clicking on the Event Log link should redirect to /host/:hostId", () => { - cy.contains('[data-cy="leafygreen-table-row"]', "i-092593689871a50dc") - .find("button[aria-label='Expand row']") - .click(); - cy.contains("Event Log").click(); - cy.location("pathname").should("eq", "/host/i-092593689871a50dc"); - }); - - describe("Spawn host card sorting", () => { - beforeEach(() => { - cy.visit("/spawn/host"); - }); - - it("Visiting the spawn host page should display all of your spawned hosts not sorted by default", () => { - cy.dataCy("leafygreen-table-row").should("have.length", 3); - }); - - it("Clicking on the host column header should sort spawn hosts by ascending order, then descending, then remove sort", () => { - cy.get("button[aria-label='Sort by Host']").as("hostSortControl").click(); - cy.dataCy("leafygreen-table-row").each(($el, index) => - cy.wrap($el).contains(ascendingSortSpawnHostOrderByHostId[index]), - ); - cy.get("@hostSortControl").click(); - cy.dataCy("leafygreen-table-row").each(($el, index) => - cy.wrap($el).contains(descendingSortSpawnHostOrderByHostId[index]), - ); - cy.get("@hostSortControl").click(); - cy.dataCy("leafygreen-table-row").should("have.length", 3); - }); - - it("Clicking on the expiration column header should sort the hosts by ascending order, then descending, then remove sort", () => { - cy.get("button[aria-label='Sort by Expires In']") - .as("expiresInSortControl") - .click(); - cy.dataCy("leafygreen-table-row").each(($el, index) => - cy.wrap($el).contains(ascendingSortSpawnHostOrderByExpiration[index]), - ); - cy.get("@expiresInSortControl").click(); - cy.dataCy("leafygreen-table-row").each(($el, index) => - cy.wrap($el).contains(descendingSortSpawnHostOrderByExpiration[index]), - ); - cy.get("@expiresInSortControl").click(); - cy.dataCy("leafygreen-table-row").should("have.length", 3); - }); - }); - - describe("Spawn host modal", () => { - it("Should disable 'Unexpirable Host' radio box when max number of unexpirable hosts is met (2)", () => { - cy.visit("/spawn/host"); - cy.contains("Spawn a host").click(); - cy.dataCy("distro-input").click(); - cy.dataCy("distro-option-ubuntu1804-workstation") - .should("be.visible") - .click(); - cy.dataCy("expirable-radio-box").children().should("have.length", 2); - cy.getInputByLabel("Expirable Host").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.getInputByLabel("Unexpirable Host").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.contains( - "You have reached the max number of unexpirable hosts", - ).should("be.visible"); - }); - - it("Clicking on the spawn host button should open a spawn host modal.", () => { - cy.visit("/spawn/host"); - cy.dataCy("spawn-host-modal").should("not.exist"); - cy.dataCy("spawn-host-button").click(); - cy.dataCy("spawn-host-modal").should("be.visible"); - }); - it("Visiting the spawn host page with the proper url param should open the spawn host modal by default", () => { - cy.visit("/spawn/host?spawnHost=True "); - cy.dataCy("spawn-host-modal").should("be.visible"); - }); - it("Closing the spawn host modal removes the 'spawnHost' query param from the url and hides the modal", () => { - cy.visit("/spawn/host?spawnHost=True "); - cy.dataCy("spawn-host-modal").should("be.visible"); - cy.location().should(({ search }) => { - expect(search).to.include("spawnHost=True"); - }); - cy.dataCy("spawn-host-modal").contains("Cancel").click(); - cy.location().should(({ search }) => { - expect(search).to.not.include("spawnHost"); - }); - cy.dataCy("spawn-host-modal").should("not.exist"); - }); - it("Visiting the spawn host page with a taskId url param should render additional options at the bottom of the modal.", () => { - cy.visit( - `spawn/host?spawnHost=True&distroId=rhel71-power8-large&taskId=${hostTaskId}`, - ); - cy.dataCy("spawn-host-modal").should("contain.text", label1); - cy.dataCy("spawn-host-modal").should( - "contain.text", - "Load data for dist on ubuntu1604", - ); - cy.dataCy("spawn-host-modal").should("contain.text", label2); - }); - - it("Unchecking 'Load data for dist' hides nested checkbox selections and checking shows them.", () => { - cy.visit( - `spawn/host?spawnHost=True&distroId=rhel71-power8-large&taskId=${hostTaskId}`, - ); - cy.dataCy("spawn-host-modal").should("be.visible"); - cy.dataCy("load-data-checkbox").should("be.checked"); - cy.contains(label1).should("be.visible"); - cy.contains(label2).should("be.visible"); - - cy.dataCy("load-data-checkbox").click({ force: true }); - cy.dataCy("load-data-checkbox").should("not.be.checked"); - cy.contains(label1).should("not.exist"); - cy.contains(label2).should("not.exist"); - }); - - it("Visiting the spawn host page with a task and distro supplied in the url should populate the distro input", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - cy.dataCy("spawn-host-modal").should("be.visible"); - cy.dataCy("distro-input").dataCy("dropdown-value").contains(distroId); - }); - it("The virtual workstation dropdown should filter any volumes that aren't a home volume", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - cy.dataCy("distro-input").click(); - cy.contains("Admin-only distros").should("not.exist"); - cy.dataCy("distro-option-ubuntu1804-workstation") - .should("be.visible") - .click(); - cy.dataCy("volume-select").should("have.attr", "aria-disabled", "true"); - }); - - it("Clicking 'Add new key' hides the key name dropdown and shows the key value text area", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - cy.dataCy("key-select").should("be.visible"); - cy.dataCy("key-value-text-area").should("not.exist"); - cy.contains("Add new key").click(); - cy.dataCy("key-select").should("not.exist"); - cy.dataCy("key-value-text-area").should("be.visible"); - }); - - it("Checking 'Run Userdata script on start' shows the user data script text area", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - cy.dataCy("run-user-data-script-text-area").should("not.exist"); - cy.contains("Run Userdata script on start").click(); - cy.dataCy("user-data-script-text-area").should("be.visible"); - }); - - it("Checking 'Define setup script...' shows the setup script text area", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - cy.dataCy("setup-script-text-area").should("not.exist"); - cy.dataCy("project-setup-script-checkbox").uncheck({ force: true }); - cy.dataCy("setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("setup-script-checkbox").check({ force: true }); - cy.dataCy("setup-script-text-area").should("be.visible"); - }); - - it("Conditionally disables setup script and project setup script checkboxes based on the other's value", () => { - cy.visit( - `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, - ); - // Project setup script should be checked by default, which should disable setup script. - cy.dataCy("project-setup-script-checkbox").should( - "have.attr", - "aria-checked", - "true", - ); - cy.dataCy("project-setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "true", - ); - - // Unchecking project setup script should reenable setup script. - cy.dataCy("project-setup-script-checkbox").uncheck({ force: true }); - cy.dataCy("project-setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "false", - ); - cy.dataCy("setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "false", - ); - - // Checking setup script should disable project setup script. - cy.dataCy("setup-script-checkbox").check({ force: true }); - cy.dataCy("project-setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("setup-script-checkbox").should( - "have.attr", - "aria-disabled", - "false", - ); - }); - const label1 = "Use project-specific setup script defined at /path"; - const label2 = "Also start any hosts this task started (if applicable)"; - }); - - it("Allows editing a modal with sleep schedule enabled and validates dates", () => { - cy.dataCy("edit-host-button").eq(2).click(); - cy.dataCy("edit-spawn-host-modal").should("be.visible"); - - cy.get("input[id='year']").as("startOfDateInput"); - - cy.get("@startOfDateInput").click(); - cy.get("td[aria-current=true]").as("currentDateCell"); - - // Select a date in the future either this month or next month - // if the current date is the last day of the month select the first day of the next month - // if it is not select the next day - // We can determine if the current date is the last day of the month if the next cell is disabled - cy.get("@currentDateCell").then(($currentDateCell) => { - // Get all sibling elements of the current date cell - cy.wrap($currentDateCell) - .siblings() - .then(($siblings) => { - const currentDay = Number($currentDateCell.text()); - // Get the next date cell based on the next index - const nextIndex = $siblings - .toArray() - .findIndex((el) => Number(el.textContent) === currentDay + 1); - - const $nextCell = $siblings.eq(nextIndex); - // If the next cell exists and is not disabled, click on it - if ($nextCell && $nextCell.attr("aria-disabled") === "false") { - cy.wrap($nextCell).click(); - } else { - cy.log("Current date is the last day of the month"); - cy.get('button[aria-label^="Next month"]').click(); - cy.get('td[data-testid="lg-date_picker-calendar_cell"]') - .first() - .click(); - } - }); - }); - - cy.contains("button", "Save").should("have.attr", "aria-disabled", "false"); - - cy.clearDatePickerInput(); - - // Select a date in the past - cy.get("@startOfDateInput").type("20250101"); - cy.get("body").click(); - cy.contains("button", "Save").should("have.attr", "aria-disabled", "true"); - - cy.clearDatePickerInput(); - - // Select a date too far in the future - cy.get("@startOfDateInput").type("20600115"); - cy.contains("button", "Save").should("have.attr", "aria-disabled", "true"); - }); -}); diff --git a/apps/spruce/cypress/integration/spawn/route.ts b/apps/spruce/cypress/integration/spawn/route.ts deleted file mode 100644 index 83c7c6e7ed..0000000000 --- a/apps/spruce/cypress/integration/spawn/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -describe("Navigating to Spawn Host and Spawn Volume pages", () => { - it("Navigating to /spawn will redirect to /spawn/host", () => { - cy.visit("/spawn"); - cy.location("pathname").should("eq", "/spawn/host"); - }); - - it("Navigating to /spawn/not-a-route will redirect to /spawn/host", () => { - cy.visit("/spawn/not-a-route"); - cy.location("pathname").should("eq", "/spawn/host"); - }); - - it("Clicking on the Volume side nav item will redirect to /spawn/volume", () => { - cy.visit("/spawn/host"); - cy.dataCy("volume-nav-tab").click(); - cy.location("pathname").should("eq", "/spawn/volume"); - }); - - it("Clicking on the Host side nav item will redirect to /spawn/host", () => { - cy.visit("/spawn/volume"); - cy.dataCy("host-nav-tab").click(); - cy.location("pathname").should("eq", "/spawn/host"); - }); -}); diff --git a/apps/spruce/cypress/integration/spawn/volume.ts b/apps/spruce/cypress/integration/spawn/volume.ts deleted file mode 100644 index c65b196fad..0000000000 --- a/apps/spruce/cypress/integration/spawn/volume.ts +++ /dev/null @@ -1,329 +0,0 @@ -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"); - }); - - it("The table initially displays volumes with status ascending.", () => { - cy.dataCy("leafygreen-table-row").each(($el, index) => - cy.contains(expectedVolNames[index]), - ); - }); - - it("Table should have no cards visible by default.", () => { - expectedVolNames.forEach((cardName) => { - cy.dataCy(cardName).should("not.exist"); - }); - }); - - it("Should render migrating volumes with a different badge and disable action buttons", () => { - cy.contains( - '[data-cy="leafygreen-table-row"]', - "vol-0ae8720b445b771b6", - ).within(() => { - cy.dataCy("volume-status-badge").contains("Migrating"); - cy.get("button[aria-label!='Expand row']").should( - "have.attr", - "aria-disabled", - "true", - ); - }); - }); - - it("Should have a volume card visible initially when the 'volume' query param is provided.", () => { - cy.visit("/spawn/volume?volume=vol-0ea662ac92f611ed4"); - cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should("exist"); - cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").scrollIntoView(); - cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should("be.visible"); - }); - - 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.contains('[data-cy="leafygreen-table-row"]', "vol-0ea662ac92f611ed4") - .find("button[aria-label='Collapse row']") - .click(); - cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should("not.exist"); - cy.contains('[data-cy="leafygreen-table-row"]', "vol-0ea662ac92f611ed4") - .find("button[aria-label='Expand row']") - .click(); - cy.dataCy("spawn-volume-card-vol-0ea662ac92f611ed4").should("be.visible"); - }); - - it("Clicking the trash can should remove the volume from the table and update free/mounted volumes badges.", () => { - cy.contains( - '[data-cy="leafygreen-table-row"]', - "vol-0c66e16459646704d", - ).should("exist"); - cy.dataCy("trash-vol-0c66e16459646704d").click(); - cy.dataCy("delete-volume-popconfirm").should("be.visible"); - cy.dataCy("delete-volume-popconfirm").within(($el) => { - cy.wrap($el) - .contains("Yes") - .should("be.visible") - .should("not.have.attr", "aria-disabled", "true"); - cy.wrap($el).contains("Yes").click(); - }); - cy.contains( - '[data-cy="leafygreen-table-row"]', - "vol-0c66e16459646704d", - ).should("not.exist"); - cy.dataCy("mounted-badge").contains("9 Mounted"); - cy.dataCy("free-badge").contains("3 Free"); - }); - - it("Clicking the trash can for a mounted volume should show an additional confirmation checkbox which enables the submit button when checked.", () => { - cy.dataCy( - "trash-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - ).click(); - cy.dataCy("delete-volume-popconfirm").should("be.visible"); - cy.dataCy("delete-volume-popconfirm").within(($el) => { - cy.wrap($el) - .getInputByLabel( - "I understand this volume is currently mounted to a host.", - ) - .should("not.be.checked"); - cy.wrap($el).contains("Yes"); - cy.wrap($el).contains("Yes").should("have.attr", "aria-disabled", "true"); - cy.wrap($el) - .getInputByLabel( - "I understand this volume is currently mounted to a host.", - ) - .check({ force: true }); - cy.wrap($el) - .contains("Yes") - .should("not.have.attr", "aria-disabled", "true") - .click(); - }); - cy.dataCy("mounted-badge").contains("8 Mounted"); - cy.dataCy("free-badge").contains("4 Free"); - }); - - it("Clicking on unmount should result in a success toast appearing.", () => { - cy.dataCy( - "detach-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b857", - ).click(); - cy.contains("button", "Yes").click(); - cy.validateToast("success", "Successfully unmounted the volume."); - }); - - it("Clicking on 'Spawn Volume' should open the Spawn Volume Modal", () => { - cy.dataCy("spawn-volume-btn").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("spawn-volume-btn").click(); - cy.dataCy("spawn-volume-modal").should("be.visible"); - }); - - 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( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("button", "Cancel").click({ force: true }); - }); - - cy.dataCy("spawn-volume-btn").click(); - cy.dataCy("spawn-volume-modal").should("be.visible"); - cy.dataCy("type-select").contains("gp3"); - }); - - describe("Edit volume modal", () => { - beforeEach(() => { - cy.visit("/spawn/volume"); - }); - it("Clicking on 'Edit' should open the Edit Volume Modal", () => { - cy.dataCy( - "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ).click(); - cy.dataCy("update-volume-modal").should("be.visible"); - }); - - it("name, size, expiration inputs should be populated 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", - ); - cy.dataCy("volume-size-input").should("have.value", "100"); - cy.validateDatePickerDate("date-picker", { - year: "2020", - month: "06", - day: "06", - }); - cy.dataCy("hour-input").should("have.value", "15"); // Defaults to UTC - cy.dataCy("minute-input").should("have.value", "48"); - }); - - 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(); - }); - cy.dataCy( - "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ).click(); - cy.dataCy("volume-name-input").should( - "have.value", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ); - }); - - it("size field is validated correctly", () => { - cy.dataCy( - "edit-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ).click(); - cy.dataCy("update-volume-modal").should("be.visible"); - - // Exceeding max volume limit should disable 'Save' button. - cy.dataCy("volume-size-input").clear(); - cy.dataCy("volume-size-input").type("10000"); - cy.contains("button", "Save").should( - "have.attr", - "aria-disabled", - "true", - ); - // Decreasing volume size should disable 'Save' button. - cy.dataCy("volume-size-input").clear(); - cy.dataCy("volume-size-input").type("2"); - cy.contains("button", "Save").should( - "have.attr", - "aria-disabled", - "true", - ); - }); - - 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", - "true", - ); - // type a new name - cy.dataCy("volume-name-input").type("Hello, World"); - cy.contains("button", "Save").should( - "not.have.attr", - "aria-disabled", - "true", - ); - - // type original name - cy.dataCy("volume-name-input").clear(); - cy.dataCy("volume-name-input").type( - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ); - cy.contains("button", "Save").should( - "have.attr", - "aria-disabled", - "true", - ); - - cy.contains("Never").click(); - cy.contains("button", "Save").should( - "not.have.attr", - "aria-disabled", - "true", - ); - }); - - 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"); - }); - }); - - describe("Migrate Modal", () => { - beforeEach(() => { - cy.visit("/spawn/volume"); - }); - it("migrate button is disabled for volumes with the migrating status", () => { - cy.contains('[data-cy="leafygreen-table-row"]', "vol-0ae8720b445b771b6") - .find("[data-cy=volume-status-badge]") - .contains("Migrating"); - cy.dataCy("migrate-btn-vol-0ae8720b445b771b6").should( - "have.attr", - "aria-disabled", - "true", - ); - }); - // TODO the availability zone defined in Evergreen's test data for this volume is invalid, making it impossible to submit the form. Re-enable these tests when the data is fixed. - it.skip("clicking cancel during confirmation renders the Migrate modal form", () => { - cy.dataCy( - "migrate-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ).click(); - cy.dataCy("distro-input").should("be.visible").click(); - cy.dataCy("distro-option-ubuntu1804-workstation").click(); - cy.dataCy("migrate-modal").contains("Next").click({ force: true }); - cy.dataCy("migrate-modal").contains( - "Are you sure you want to migrate this home volume?", - ); - cy.dataCy("distro-input").should("not.exist"); - cy.dataCy("migrate-modal").contains("Cancel").click({ force: true }); - cy.dataCy("distro-input").should("be.visible"); - }); - it.skip("open the Migrate modal and spawn a host", () => { - cy.dataCy( - "migrate-btn-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - ).click(); - cy.dataCy("distro-input").click(); - cy.dataCy("distro-option-ubuntu1804-workstation").click(); - cy.dataCy("region-select").should("have.attr", "aria-disabled", "true"); - cy.dataCy("migrate-modal").contains("Next").click({ force: true }); - cy.dataCy("migrate-modal") - .contains("Migrate Volume") - .click({ force: true }); - cy.validateToast( - "success", - "Volume migration has been scheduled. A new host will be spawned and accessible on your Hosts page.", - ); - }); - }); - - const expectedVolNames = [ - "1da0e996608e6871b60a92f6564bbc9cdf66ce90be1178dfb653920542a0d0f0", - "vol-0c66e16459646704d", - "vol-0583d66433a69f136", - "vol-0ea662ac92f611ed4", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b815", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b859", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b825", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b857", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b856", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b835", - "vol-0ae8720b445b771b6", - ]; -}); diff --git a/apps/spruce/cypress/integration/version/action_buttons.ts b/apps/spruce/cypress/integration/version/action_buttons.ts deleted file mode 100644 index 93c91a19b7..0000000000 --- a/apps/spruce/cypress/integration/version/action_buttons.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { INCLUDE_NEVER_ACTIVATED_TASKS } from "constants/cookies"; -import { mockErrorResponse } from "../../utils/mockErrorResponse"; - -describe("Action Buttons", () => { - const patch = "5ecedafb562343215a7ff297"; - const mainlineCommit = "5e4ff3abe3c3317e352062e4"; - const versionPath = (id: string) => `/version/${id}`; - - describe("When viewing a patch build", () => { - beforeEach(() => { - cy.visit(versionPath(patch)); - }); - it("Clicking 'Schedule' button shows modal and clicking on 'Cancel' closes it", () => { - cy.dataCy("schedule-patch").click(); - cy.dataCy("schedule-tasks-modal").should("be.visible"); - cy.contains("Cancel").click(); - cy.dataCy("schedule-tasks-modal").should("not.be.visible"); - }); - - it("Clicking ellipses dropdown shows ellipses options", () => { - cy.dataCy("ellipses-btn").should("not.exist"); - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("be.visible"); - - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("not.exist"); - }); - }); - - describe("Version dropdown options", () => { - beforeEach(() => { - cy.visit(versionPath(patch)); - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("be.visible"); - }); - - it("Error unscheduling a version shows error toast", () => { - cy.dataCy("unschedule-patch").click(); - mockErrorResponse({ - errorMessage: "There was an error unscheduling tasks", - }); - cy.contains("button", "Yes").click({ force: true }); - cy.validateToast("error", "Error unscheduling tasks"); - }); - - it("Clicking 'Unschedule' button show popconfirm with abort checkbox and a toast on success", () => { - cy.dataCy("unschedule-patch").click(); - cy.contains("button", "Yes").click({ force: true }); - cy.validateToast( - "success", - "All tasks were unscheduled and tasks that already started were aborted", - ); - }); - - it("Clicking 'Set Priority' button shows popconfirm with input and toast on success", () => { - const priority = "99"; - cy.dataCy("set-priority-menu-item").click(); - cy.dataCy("patch-priority-input").type(`${priority}{enter}`, { - force: true, - }); - cy.validateToast("success", priority); - }); - - it("Error setting priority shows error toast", () => { - cy.dataCy("set-priority-menu-item").click(); - cy.dataCy("patch-priority-input").type("88", { force: true }); - mockErrorResponse({ - errorMessage: "There was an error setting priority", - }); - cy.dataCy("patch-priority-input").type("{enter}", { force: true }); - cy.validateToast("error", "Error updating priority for patch"); - }); - - it("Sets priority for multiple tasks when version page table is filtered", () => { - const priority = 10; - cy.visit( - `${versionPath(mainlineCommit)}/tasks?statuses=failed-umbrella,failed,known-issue`, - ); - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("be.visible"); - cy.dataCy("set-priority-menu-item").should( - "contain.text", - "Set task priority (2)", - ); - cy.dataCy("set-priority-menu-item").click(); - cy.dataCy("task-priority-input").type(`${priority}{enter}`, { - force: true, - }); - cy.validateToast("success", "Priority updated for 2 tasks."); - }); - - it("Should be able to reconfigure the patch", () => { - cy.dataCy("reconfigure-link").should("not.be.disabled"); - cy.dataCy("reconfigure-link").click(); - cy.location("pathname").should("include", "configure"); - cy.visit(versionPath(patch)); - }); - }); - - describe("When viewing a mainline commit", () => { - describe("Version dropdown options", () => { - beforeEach(() => { - cy.visit(versionPath(mainlineCommit)); - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("be.visible"); - }); - it("Reconfigure link is disabled for mainline commits", () => { - cy.dataCy("reconfigure-link").should("be.visible"); - cy.dataCy("reconfigure-link").should( - "have.attr", - "aria-disabled", - "true", - ); - }); - }); - }); - - describe("Include Never-activated Tasks toggle", () => { - it("sets URL and cookie when toggled on", () => { - cy.visit(versionPath(patch)); - cy.dataCy("ellipsis-btn").click(); - cy.dataCy("card-dropdown").should("be.visible"); - cy.dataCy("card-dropdown") - .contains("Include never-activated tasks") - .click(); - cy.location("search").should( - "contain", - "includeNeverActivatedTasks=true", - ); - cy.getCookie(INCLUDE_NEVER_ACTIVATED_TASKS).should( - "have.property", - "value", - "true", - ); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/banners.ts b/apps/spruce/cypress/integration/version/banners.ts deleted file mode 100644 index 43058e4cf9..0000000000 --- a/apps/spruce/cypress/integration/version/banners.ts +++ /dev/null @@ -1,43 +0,0 @@ -describe("banners", () => { - const versionWithBanners = - "/version/logkeeper_e864cf934194c161aa044e4599c8c81cee7b6237/tasks?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC"; - - describe("errors", () => { - beforeEach(() => { - cy.visit(versionWithBanners); - }); - - it("should display the number of configuration errors", () => { - cy.dataCy("configuration-errors-banner").should("be.visible"); - cy.contains("4 errors in configuration file").should("be.visible"); - }); - it("should be able to open the modal and see all errors", () => { - cy.dataCy("configuration-errors-modal-trigger").click(); - cy.dataCy("configuration-errors-modal").should("be.visible"); - cy.get("ol").find("li").should("have.length", 4); - }); - }); - - describe("warnings", () => { - beforeEach(() => { - cy.visit(versionWithBanners); - }); - - it("should display the number of configuration warnings", () => { - cy.dataCy("configuration-warnings-banner").should("be.visible"); - cy.contains("3 warnings in configuration file").should("be.visible"); - }); - it("should be able to open the modal and see all warnings", () => { - cy.dataCy("configuration-warnings-modal-trigger").click(); - cy.dataCy("configuration-warnings-modal").should("be.visible"); - cy.get("ol").find("li").should("have.length", 3); - }); - }); - - describe("ignored", () => { - it("should display a banner", () => { - cy.visit("/version/spruce_e695f654c8b4b959d3e12e71696c3e318bcd4c33"); - cy.dataCy("ignored-banner").should("be.visible"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/downstream_projects.ts b/apps/spruce/cypress/integration/version/downstream_projects.ts deleted file mode 100644 index 5bc1631bc4..0000000000 --- a/apps/spruce/cypress/integration/version/downstream_projects.ts +++ /dev/null @@ -1,46 +0,0 @@ -describe("Downstream Projects Tab", () => { - const DOWNSTREAM_ROUTE = `/version/5f74d99ab2373627c047c5e5/downstream-projects`; - - beforeEach(() => { - cy.visit(DOWNSTREAM_ROUTE); - }); - - it("shows number of failed patches in the Downstream tab label", () => { - cy.dataCy("downstream-tab-badge").should("exist"); - cy.dataCy("downstream-tab-badge").should("contain.text", "1"); - }); - - it("renders child patches", () => { - cy.dataCy("project-accordion").should("have.length", 3); - cy.dataCy("project-title").should("have.length", 3); - cy.dataCy("downstream-tasks-table").should("have.length", 3); - }); - - it("links to base commit", () => { - cy.dataCy("accordion-toggle").first().click(); - cy.dataCy("downstream-base-commit") - .should("have.attr", "href") - .and( - "includes", - "/version/logkeeper_e3579537e848d14f0c3e5c25ef745fd0f10702d4", - ); - }); - - it("filters by test name", () => { - cy.dataCy("task-name-filter").eq(1).click(); - cy.dataCy("task-name-filter-wrapper") - .find("input") - .as("testnameInputWrapper"); - cy.get("@testnameInputWrapper").focus(); - cy.get("@testnameInputWrapper").type("generate-lint"); - cy.get("@testnameInputWrapper").type("{enter}"); - cy.location("search").should("not.contain", "generate-lint"); // Should not update the URL. - cy.contains("generate-lint").should("be.visible"); - }); - - it("does not push query params to the URL", () => { - cy.location().should((loc) => { - expect(loc.pathname).to.equal(DOWNSTREAM_ROUTE); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/name_change_modal.ts b/apps/spruce/cypress/integration/version/name_change_modal.ts deleted file mode 100644 index ecb6c0b2b4..0000000000 --- a/apps/spruce/cypress/integration/version/name_change_modal.ts +++ /dev/null @@ -1,51 +0,0 @@ -describe("Name change modal", () => { - beforeEach(() => { - cy.visit("version/5f74d99ab2373627c047c5e5"); - }); - - it("Use the name change modal to change the name of a patch", () => { - const originalName = "main: EVG-7823 add a commit queue message (#4048)"; - cy.contains(originalName); - cy.dataCy("name-change-modal-trigger").click(); - const newName = "a different name"; - cy.get("textarea").clear(); - cy.get("textarea").type(newName); - cy.contains("Confirm").click(); - cy.get("textarea").should("not.exist"); - cy.contains(newName); - cy.validateToast("success", "Patch name was successfully updated.", true); - // revert name change - cy.dataCy("name-change-modal-trigger").click(); - cy.get("textarea").clear(); - cy.get("textarea").type(originalName); - cy.contains("Confirm").click(); - cy.get("textarea").should("not.exist"); - cy.validateToast("success", "Patch name was successfully updated.", true); - cy.contains(originalName); - }); - - it("The confirm button is disabled when the text area value is empty or greater than 300 characters", () => { - cy.dataCy("name-change-modal-trigger").click(); - cy.get("textarea").clear(); - cy.contains("button", "Confirm").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.get("textarea").type("lol"); - cy.contains("button", "Confirm").should( - "have.attr", - "aria-disabled", - "false", - ); - const over300Chars = - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - cy.get("textarea").type(over300Chars); - cy.contains("button", "Confirm").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.contains("Value cannot exceed 300 characters"); - }); -}); diff --git a/apps/spruce/cypress/integration/version/patch_with_aborted_task.ts b/apps/spruce/cypress/integration/version/patch_with_aborted_task.ts deleted file mode 100644 index 105f223858..0000000000 --- a/apps/spruce/cypress/integration/version/patch_with_aborted_task.ts +++ /dev/null @@ -1,10 +0,0 @@ -describe("Patch with aborted task", () => { - it("Shows status `aborted` in task table for tasks that were aborted", () => { - cy.visit("version/5e94c2dfe3c3312519b59480"); - cy.dataCy("tasks-table-row") - .eq(1) - .within(() => { - cy.dataCy("task-status-badge").should("have.text", "Aborted"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/restart_modal.ts b/apps/spruce/cypress/integration/version/restart_modal.ts deleted file mode 100644 index 8f882d062b..0000000000 --- a/apps/spruce/cypress/integration/version/restart_modal.ts +++ /dev/null @@ -1,106 +0,0 @@ -describe("version/restart_modal", () => { - 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", () => { - beforeEach(() => { - cy.visit(path); - cy.dataCy("version-restart-modal").should("not.be.visible"); - 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"); - }); - it("Clicking on a variant checkbox should toggle its textbox and all the associated tasks", () => { - cy.dataCy("variant-accordion").first().click(); - cy.dataCy("task-status-badge").should("contain.text", "0 of 1 Selected"); - cy.dataCy("variant-checkbox-select-all").first().click({ force: true }); - cy.dataCy("task-status-badge").should("contain.text", "1 of 1 Selected"); - cy.dataCy("variant-checkbox-select-all").first().click({ force: true }); - cy.dataCy("task-status-badge").should("contain.text", "0 of 1 Selected"); - }); - it("Clicking on a task should toggle its check box and select the task", () => { - cy.dataCy("variant-accordion").first().click(); - cy.dataCy("task-status-checkbox").first().click({ force: true }); - cy.dataCy("task-status-badge").should("contain.text", "1 of 1 Selected"); - }); - - it("Selecting on the task status filter should toggle the tasks that have matching statuses to it", () => { - cy.dataCy("task-status-filter").click(); - cy.getInputByLabel("All").check({ force: true }); - cy.dataCy("task-status-filter").click(); - - cy.dataCy("version-restart-modal").should( - "contain.text", - "Are you sure you want to restart the 1 selected tasks?", - ); - cy.dataCy("task-status-filter").click(); - cy.getInputByLabel("All").check({ force: true }); - cy.dataCy("task-status-filter").click(); - }); - - it("Selecting on the base status filter should toggle the tasks that have matching statuses to it", () => { - cy.dataCy("version-restart-modal").within(() => { - cy.dataCy("base-task-status-filter").click(); - cy.getInputByLabel("Succeeded").check({ force: true }); - cy.dataCy("base-task-status-filter").click(); - - // ideally this would target the text field itself but leafygreen Body tags dont - // support cy-data elements currently - cy.dataCy("confirmation-message").should( - "contain.text", - "Are you sure you want to restart the 1 selected tasks?", - ); - cy.dataCy("base-task-status-filter").click(); - - cy.getInputByLabel("Succeeded").check({ force: true }); - cy.dataCy("base-task-status-filter").click(); - }); - }); - - 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("variant-accordion").first().click(); - cy.dataCy("task-status-checkbox").first().click({ force: true }); - cy.contains("button", "Restart").click(); - }); - cy.dataCy("version-restart-modal").should("not.be.visible"); - cy.validateToast("success", "Successfully restarted tasks!"); - }); - }); - - describe("Restarting mainline commits", () => { - it("should be able to restart scheduled mainline commit tasks", () => { - cy.visit("/version/spruce_ab494436448fbb1d244833046ea6f6af1544e86d"); - cy.dataCy("restart-version").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("restart-version").click(); - cy.dataCy("version-restart-modal").should("be.visible"); - cy.dataCy("version-restart-modal").within(() => { - cy.dataCy("accordion-toggle").click(); - cy.getInputByLabel("check_codegen").should("exist"); - cy.getInputByLabel("check_codegen").check({ force: true }); - cy.contains("button", "Restart").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("button", "Restart").click(); - }); - cy.validateToast("success", "Successfully restarted tasks!"); - }); - }); - const path = `/version/5ecedafb562343215a7ff297`; -}); diff --git a/apps/spruce/cypress/integration/version/routes.ts b/apps/spruce/cypress/integration/version/routes.ts deleted file mode 100644 index f4c51c0aeb..0000000000 --- a/apps/spruce/cypress/integration/version/routes.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { waitForTaskTable } from "../../utils"; - -const versions = { - 0: "5ecedafb562343215a7ff297", // normal patch - 1: "i-dont-exist", // non existent patch - 2: "52a630633ff1227909000021", // patch 2 - 3: "5e6bb9e23066155a993e0f1a", // unconfigured patch - 4: "evergreen_33016573166a36bd5f46b4111151899d5c4e95b1", // basecommit for versions[0] - 5: "5e4ff3abe3c3317e352062e4", -}; - -const versionRoute = (id: string) => `/version/${id}`; - -describe("Version route", () => { - describe("Metadata", () => { - it("Shows patch parameters if they exist", () => { - cy.visit(versionRoute(versions[0])); - cy.dataCy("parameters-modal").should("not.exist"); - cy.dataCy("parameters-link").click(); - cy.dataCy("parameters-modal").should("be.visible"); - cy.get('button[aria-label="Close modal"]').click(); - cy.dataCy("parameters-modal").should("not.be.visible"); - }); - it("'Base commit' link in metadata links to version page", () => { - cy.visit(versionRoute(versions[0])); - cy.dataCy("patch-base-commit") - .should("have.attr", "href") - .and("include", `/version/${versions[4]}`); - }); - it("Doesn't show patch parameters if they don't exist", () => { - cy.visit(versionRoute(versions[2])); - cy.dataCy("parameters-link").should("not.exist"); - cy.dataCy("parameters-modal").should("not.exist"); - }); - }); - - describe("Build Variants", () => { - beforeEach(() => { - cy.visit(versionRoute(versions[0])); - waitForTaskTable(); - }); - - it("Lists the patch's build variants", () => { - cy.dataCy("build-variants").within(() => { - cy.dataCy("patch-build-variant").within( - ($variants) => Array.from($variants).length > 0, - ); - }); - }); - - describe("Grouped Task Status Badge", () => { - it("Shows tooltip with task's name on hover", () => { - cy.dataCy("build-variants").within(() => { - cy.dataCy("grouped-task-status-badge").first().as("statusBadge"); - cy.get("@statusBadge").trigger("mouseover"); - cy.get("@statusBadge").within(($el) => { - expect($el.text()).to.contain("1Succeeded"); - }); - }); - }); - - it("Navigates to task tab and applies filters when clicking on grouped task status badge", () => { - // click on a different tab first, so that we aren't on the task tab initially - cy.dataCy("changes-tab").first().click(); - cy.dataCy("task-tab") - .should("have.attr", "aria-selected") - .and("equal", "false"); - - // clicking on task status badge should move to the task tab - cy.dataCy("build-variants").within(() => { - cy.dataCy("grouped-task-status-badge").first().click(); - }); - cy.dataCy("task-tab") - .should("have.attr", "aria-selected") - .and("equal", "true"); - cy.location("search").should( - "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24", - ); - - // Check that filter values have updated. - cy.dataCy("status-filter").click(); - cy.dataCy("status-filter-wrapper").should("be.visible"); - cy.getInputByLabel("Succeeded") - .should("have.attr", "aria-checked") - .and("equal", "true"); - - cy.dataCy("variant-filter").click(); - cy.dataCy("variant-filter-wrapper").find("input").as("variantInput"); - cy.get("@variantInput").should("have.value", "^ubuntu1604$"); - }); - - it("Keeps sorts but not other filters when clicking on grouped task status badge", () => { - // Clear filters persisting from last test - cy.dataCy("clear-all-filters").click(); - - // Apply name filter - cy.dataCy("task-name-filter").click(); - cy.dataCy("task-name-filter-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(() => { - cy.dataCy("grouped-task-status-badge").first().click(); - }); - cy.location("search").should( - "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24", - ); - }); - }); - - describe("Build Variant Name", () => { - it("Navigates to task tab and applies filters when clicking on build variant name", () => { - // Clear filters persisting from last test - cy.dataCy("clear-all-filters").click(); - - // click on a different tab first, so that we aren't on the task tab initially - cy.dataCy("changes-tab").first().click(); - cy.dataCy("task-tab") - .should("have.attr", "aria-selected") - .and("equal", "false"); - - // clicking on build variant name should move to the task tab - cy.dataCy("build-variant-display-name").first().click(); - cy.dataCy("task-tab") - .should("have.attr", "aria-selected") - .and("equal", "true"); - cy.location("search").should( - "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&variant=%5Eubuntu1604%24", - ); - - // Check that filter values have updated. - cy.dataCy("variant-filter").click(); - cy.dataCy("variant-filter-wrapper").find("input").as("variantInput"); - cy.get("@variantInput").should("have.value", "^ubuntu1604$"); - }); - - it("Keeps sorts but not other filters when clicking on build variant name", () => { - // Clear filters persisting from last test - cy.dataCy("clear-all-filters").click(); - - // Apply name filter - cy.dataCy("task-name-filter").click(); - cy.dataCy("task-name-filter-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(); - cy.location("search").should( - "include", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&variant=%5Eubuntu1604%24", - ); - }); - }); - }); - - describe("Page title", () => { - beforeEach(() => { - cy.visit(versionRoute(versions[5])); - }); - it("Should include a link to Jira", () => { - cy.dataCy("page-title") - .contains("a", "EVG-7425") - .should( - "have.attr", - "href", - "https://jira.example.com/browse/EVG-7425", - ); - }); - - it("Should include a link to GitHub", () => { - cy.dataCy("page-title") - .contains("a", "https://github.com/evergreen-ci/evergreen/pull/3186") - .should( - "have.attr", - "href", - "https://github.com/evergreen-ci/evergreen/pull/3186", - ); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/schedule_modal.ts b/apps/spruce/cypress/integration/version/schedule_modal.ts deleted file mode 100644 index 431682096c..0000000000 --- a/apps/spruce/cypress/integration/version/schedule_modal.ts +++ /dev/null @@ -1,25 +0,0 @@ -describe("Restarting and scheduling mainline commits", () => { - it("should be able to schedule inactive mainline commit tasks", () => { - cy.visit("/version/spruce_e695f654c8b4b959d3e12e71696c3e318bcd4c33"); - cy.dataCy("schedule-patch").should("exist"); - cy.dataCy("schedule-patch").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("schedule-patch").click(); - cy.dataCy("schedule-tasks-modal").should("be.visible"); - cy.dataCy("schedule-tasks-modal").within(() => { - cy.dataCy("accordion-toggle").click(); - cy.getInputByLabel("check_codegen").should("exist"); - cy.getInputByLabel("check_codegen").click({ force: true }); - cy.contains("button", "Schedule").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("button", "Schedule").click({ force: true }); - }); - cy.validateToast("success", "Successfully scheduled tasks!"); - }); -}); diff --git a/apps/spruce/cypress/integration/version/subscription_modal.ts b/apps/spruce/cypress/integration/version/subscription_modal.ts deleted file mode 100644 index 7133d88f30..0000000000 --- a/apps/spruce/cypress/integration/version/subscription_modal.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { openSubscriptionModal } from "../../utils/subscriptionModal"; - -describe("Version Subscription Modal", () => { - const dataCyToggleModalButton = "notify-patch"; - const route = "/version/5e4ff3abe3c3317e352062e4/tasks"; - const regexSelectorRow = "regex-selector-row"; - - describe("Regex selector inputs", () => { - it("Can add and remove regex selectors", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.dataCy(regexSelectorRow).should("have.length", 0); - cy.contains("Add Additional Criteria").click(); - cy.dataCy(regexSelectorRow).should("have.length", 1); - cy.dataCy("delete-item-button").first().click(); - cy.dataCy(regexSelectorRow).should("have.length", 0); - }); - - // Skip because of complications with SpruceForm - it.skip("'Regex' input should be disabled when the 'Field name' is empty and enabled otherwise", () => { - cy.contains("Add Additional Criteria").click(); - cy.dataCy(regexSelectorRow).should("be.disabled"); - cy.selectLGOption("regex-select", "Build Variant Name"); - cy.dataCy(regexSelectorRow).should("not.be.disabled"); - }); - - it("Selecting a regex selector type will disable that option in other regex selector type dropdowns", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.contains("Add Additional Criteria").click(); - cy.contains("Build Variant ID").should("be.visible"); - cy.contains("Add Additional Criteria").click(); - cy.contains("Build Variant Name").should("be.visible"); - cy.dataCy("regex-select").last().click(); - cy.get("#regex-select-menu").within(() => { - cy.contains("Build Variant ID").should( - "have.css", - "cursor", - "not-allowed", - ); - }); - }); - - it("Regex selectors are optional for triggers that offer them", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.dataCy("jira-comment-input").type("EVG-2000"); - cy.contains("button", "Save").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("button", "Save").click(); - cy.validateToast("success", "Your subscription has been added"); - }); - - // Skip because of complications with SpruceForm - it.skip("Switching between Event types should either hide or reset regex selector inputs", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.contains("Add Additional Criteria").click(); - cy.dataCy("regex-select").click(); - cy.contains("Build Variant Name").click(); - cy.dataCy("regex-input").type("stuff"); - cy.dataCy("regex-input").should("have.value", "stuff"); - cy.selectLGOption("Event", "A build-variant in this version fails"); - cy.dataCy("regex-input").should("have.value", ""); - }); - - // Skip because of complications with SpruceForm - it.skip("Changing the regex selector dropdown should reset the regex selector input", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.contains("Add Additional Criteria").click(); - cy.selectLGOption("Field name", "Build Variant Name"); - - cy.dataCy("regex-input").type("stuff"); - cy.dataCy("regex-input").should("have.value", "stuff"); - cy.dataCy("regex-select").click(); - cy.contains("Build Variant ID").click(); - cy.dataCy("regex-input").should("have.value", ""); - }); - - it("Display success toast after submitting a valid form with regex selectors inputs and request succeeds", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.contains("Add Additional Criteria").click(); - cy.selectLGOption("Field name", "Build Variant Name"); - cy.dataCy("regex-input").type("stuff"); - cy.dataCy("jira-comment-input").type("EVG-2000"); - cy.contains("button", "Save").click(); - cy.validateToast("success", "Your subscription has been added"); - }); - - it("'Add Additional Criteria' button should not appear when there are enough 'Field name' dropdowns to represent all possible regex selector types for a trigger", () => { - openSubscriptionModal(route, dataCyToggleModalButton); - cy.selectLGOption("Event", "A build-variant in this version finishes"); - cy.contains("Add Additional Criteria").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("Add Additional Criteria").click(); - cy.contains("Add Additional Criteria").should( - "not.have.attr", - "aria-disabled", - "true", - ); - cy.contains("Add Additional Criteria").click(); - cy.contains("Add Additional Criteria").should("not.exist"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/task_duration.ts b/apps/spruce/cypress/integration/version/task_duration.ts deleted file mode 100644 index 0aea7347b0..0000000000 --- a/apps/spruce/cypress/integration/version/task_duration.ts +++ /dev/null @@ -1,124 +0,0 @@ -describe("Task Duration Tab", () => { - 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", () => { - const filterText = "test-annotation"; - // Apply text filter. - cy.dataCy("task-name-filter-popover").click(); - cy.dataCy("task-name-filter-popover-input-filter").type( - `${filterText}{enter}`, - ); - cy.dataCy("task-duration-table-row").should("have.length", 1); - cy.location("search").should( - "include", - `page=0&sorts=DURATION%3ADESC&taskName=${filterText}`, - ); - // Clear text filter. - cy.dataCy("task-name-filter-popover").click(); - cy.dataCy("task-name-filter-popover-input-filter").clear(); - cy.dataCy("task-name-filter-popover-input-filter").type("{enter}"); - cy.location("search").should("include", `page=0`); - }); - - it("updates URL appropriately when status filter is applied", () => { - // 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", 3); - cy.location("search").should( - "include", - "page=0&sorts=DURATION%3ADESC&statuses=running-umbrella,started,dispatched", - ); - // Clear status filter. - cy.dataCy("status-filter-popover").click(); - cy.dataCy("tree-select-options").within(() => - cy.contains("Succeeded").click({ force: true }), - ); - cy.location("search").should("include", "page=0&sorts=DURATION%3ADESC"); - }); - - it("updates URL appropriately when build variant filter is applied", () => { - const filterText = "Lint"; - // Apply text filter. - cy.dataCy("build-variant-filter-popover").click(); - cy.dataCy("build-variant-filter-popover-input-filter").type( - `${filterText}{enter}`, - ); - cy.dataCy("task-duration-table-row").should("have.length", 2); - cy.location("search").should( - "include", - `page=0&sorts=DURATION%3ADESC&variant=${filterText}`, - ); - // Clear text filter. - cy.dataCy("build-variant-filter-popover").click(); - cy.dataCy("build-variant-filter-popover-input-filter").clear(); - cy.dataCy("build-variant-filter-popover-input-filter").type("{enter}"); - cy.location("search").should("include", `page=0`); - }); - - it("updates URL appropriately when sort is changing", () => { - const durationSortControl = "button[aria-label='Sort by Task Duration']"; - const statusSortControl = "button[aria-label='Sort by Status']"; - const variantSortControl = "button[aria-label='Sort by Build Variant']"; - // The default sort (DURATION DESC) should be applied - cy.location("search").should("include", "sorts=DURATION%3ADESC"); - const longestTask = "test-thirdparty"; - cy.contains(longestTask).should("be.visible"); - cy.dataCy("task-duration-table-row") - .first() - .should("contain", longestTask); - cy.get(durationSortControl).click(); - cy.location("search").should("not.include", "duration"); - cy.get(durationSortControl).click(); - cy.location("search").should("include", "sorts=DURATION%3AASC"); - const shortestTask = "clone_test-model"; - cy.contains(shortestTask).should("be.visible"); - cy.dataCy("task-duration-table-row") - .first() - .should("contain", shortestTask); - cy.get(statusSortControl).click(); - cy.location("search").should( - "include", - "page=0&sorts=DURATION%3AASC%3BSTATUS%3AASC", - ); - cy.get(statusSortControl).click(); - cy.location("search").should( - "include", - "page=0&sorts=DURATION%3AASC%3BSTATUS%3ADESC", - ); - cy.get(variantSortControl).click(); - cy.location("search").should( - "include", - "page=0&sorts=DURATION%3AASC%3BSTATUS%3ADESC", - ); - }); - - it("clearing all filters resets to the default sort", () => { - const durationSortControl = "button[aria-label='Sort by Task Duration']"; - cy.get(durationSortControl).click(); - cy.location("search").should("not.include", "duration"); - cy.get(durationSortControl).click(); - cy.location("search").should("include", "sorts=DURATION%3AASC"); - cy.contains("Clear all filters").click(); - cy.location("search").should("include", "sorts=DURATION%3ADESC"); - }); - - it("shows message when no test results are found", () => { - const filterText = "this_does_not_exist"; - - cy.dataCy("task-name-filter-popover").click(); - cy.dataCy("task-name-filter-popover-input-filter").type( - `${filterText}{enter}`, - ); - cy.dataCy("task-name-filter-popover-task-duration-table-row").should( - "have.length", - 0, - ); - cy.contains("No tasks found.").should("exist"); - }); - }); -}); diff --git a/apps/spruce/cypress/integration/version/task_filters.ts b/apps/spruce/cypress/integration/version/task_filters.ts deleted file mode 100644 index 6a82f7c3a3..0000000000 --- a/apps/spruce/cypress/integration/version/task_filters.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { urlSearchParamsAreUpdated, waitForTaskTable } from "../../utils"; - -const patch = { - id: "5e4ff3abe3c3317e352062e4", -}; -const pathTasks = `/version/${patch.id}/tasks`; -const pathURLWithFilters = `${pathTasks}?page=0&sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=failed,success,running-umbrella,dispatched,started&taskName=test-thirdparty&variant=ubuntu`; -const defaultPath = `${pathTasks}?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC`; - -describe("Tasks filters", () => { - it("Should clear any filters with the Clear All Filters button and reset the table to its default state", () => { - cy.visit(pathURLWithFilters); - waitForTaskTable(); - cy.dataCy("tasks-table-row").should("be.visible"); - - cy.dataCy("clear-all-filters").click(); - cy.location().should((loc) => { - expect(loc.href).to.equal(loc.origin + defaultPath); - }); - cy.dataCy("task-name-filter").click(); - cy.dataCy("task-name-filter-wrapper") - .find("input") - .invoke("val") - .should("be.empty"); - cy.dataCy("status-filter").click(); - cy.dataCy("status-filter-wrapper") - .get('input[type="checkbox"]') - .should("not.be.checked"); - cy.dataCy("base-status-filter").click(); - cy.dataCy("base-status-filter-wrapper") - .get('input[type="checkbox"]') - .should("not.be.checked"); - cy.dataCy("variant-filter").click(); - cy.dataCy("variant-filter-wrapper") - .find("input") - .invoke("val") - .should("be.empty"); - }); - - describe("Variant input field", () => { - const variantInputValue = "lint"; - const urlParam = "variant"; - it("Updates url with input value and fetches tasks filtered by variant", () => { - cy.visit(defaultPath); - waitForTaskTable(); - cy.dataCy("variant-filter").click(); - - cy.dataCy("variant-filter-wrapper").find("input").as("variant-input"); - cy.get("@variant-input").focus(); - cy.get("@variant-input").type(variantInputValue); - cy.get("@variant-input").type("{enter}", { scrollBehavior: false }); - cy.dataCy("variant-filter-wrapper").should("not.exist"); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: variantInputValue, - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("contain.text", 2); - - cy.dataCy("variant-filter").click(); - cy.get("@variant-input").focus(); - cy.get("@variant-input").clear(); - cy.get("@variant-input").type("{enter}", { scrollBehavior: false }); - cy.dataCy("variant-filter-wrapper").should("not.exist"); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: null, - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("contain.text", 47); - }); - }); - - describe("Task name input field", () => { - const taskNameInputValue = "test-cloud"; - const urlParam = "taskName"; - it("Updates url with input value and fetches tasks filtered by task name", () => { - cy.visit(defaultPath); - waitForTaskTable(); - cy.dataCy("task-name-filter").click(); - - cy.dataCy("task-name-filter-wrapper").find("input").as("taskname-input"); - cy.get("@taskname-input").focus(); - cy.get("@taskname-input").type(taskNameInputValue); - cy.get("@taskname-input").type("{enter}", { scrollBehavior: false }); - cy.dataCy("task-name-filter-wrapper").should("not.exist"); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: taskNameInputValue, - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("contain.text", 1); - - cy.dataCy("task-name-filter").click(); - cy.get("@taskname-input").focus(); - cy.get("@taskname-input").clear(); - cy.get("@taskname-input").type("{enter}", { scrollBehavior: false }); - cy.dataCy("task-name-filter-wrapper").should("not.exist"); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: null, - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("contain.text", 47); - }); - }); - - describe("Task Statuses select", () => { - const dataCyStatusDropdown = "status-filter-wrapper"; - const urlParam = "statuses"; - - beforeEach(() => { - cy.visit(defaultPath); - waitForTaskTable(); - cy.dataCy("status-filter").click(); - cy.dataCy("status-filter-wrapper").should("be.visible"); - }); - - it("Clicking on a status filter filters the tasks to only those statuses", () => { - cy.dataCy("filtered-count") - .invoke("text") - .then((preFilterCount) => { - selectCheckboxOption(dataCyStatusDropdown, "Failed", true); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: "failed", - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("have.text", 2); - - cy.dataCy("filtered-count") - .invoke("text") - .then((postFilterCount) => { - cy.dataCy("filtered-count").should( - "not.have.text", - preFilterCount, - ); - selectCheckboxOption(dataCyStatusDropdown, "Succeeded", true); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: "failed-umbrella,failed,known-issue,success", - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should( - "not.have.text", - postFilterCount, - ); - }); - }); - }); - - it("Clicking on 'All' checkbox adds all the statuses and clicking again removes them", () => { - const taskStatuses = [ - "All", - "Failed / Timed Out", - "Failed", - "Known Issue", - "Succeeded", - "Running", - "Will Run", - "Dispatched", - "Undispatched", - "Aborted", - "Blocked", - ]; - selectCheckboxOption(dataCyStatusDropdown, "All", true); - assertChecked(dataCyStatusDropdown, taskStatuses, true); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: "all", - }); - waitForTaskTable(); - - selectCheckboxOption(dataCyStatusDropdown, "All", false); - assertChecked(dataCyStatusDropdown, taskStatuses, false); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: null, - }); - }); - }); - - describe("Task Base Statuses select", () => { - const dataCyBaseStatusDropdown = "base-status-filter-wrapper"; - const urlParam = "baseStatuses"; - - beforeEach(() => { - cy.visit(defaultPath); - waitForTaskTable(); - cy.dataCy("base-status-filter").click(); - cy.dataCy("base-status-filter-wrapper").should("be.visible"); - }); - - it("Clicking on a base status filter filters the tasks to only those base statuses", () => { - // All tasks have a base status of succeeded for this version. - cy.dataCy("filtered-count") - .invoke("text") - .then((preFilterCount) => { - selectCheckboxOption(dataCyBaseStatusDropdown, "Succeeded", true); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: "success", - }); - waitForTaskTable(); - - cy.dataCy("filtered-count").should("have.text", 44); - selectCheckboxOption(dataCyBaseStatusDropdown, "Succeeded", false); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: null, - }); - waitForTaskTable(); - cy.dataCy("filtered-count").should("have.text", preFilterCount); - }); - }); - - it("Clicking on 'All' checkbox adds all the base statuses and clicking again removes them", () => { - const taskStatuses = ["All", "Succeeded", "Running"]; - selectCheckboxOption(dataCyBaseStatusDropdown, "All", true); - assertChecked(dataCyBaseStatusDropdown, taskStatuses, true); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: "all", - }); - waitForTaskTable(); - - selectCheckboxOption(dataCyBaseStatusDropdown, "All", false); - assertChecked(dataCyBaseStatusDropdown, taskStatuses, false); - urlSearchParamsAreUpdated({ - pathname: pathTasks, - paramName: urlParam, - search: null, - }); - }); - }); -}); - -/** - * Function used to assert if checkboxes with certain labels are checked or unchecked. - * @param dataCy - data-cy of the container - * @param statuses list of labels to assert on - * @param checked true if should be checked, false if should be unchecked - */ -const assertChecked = ( - dataCy: string, - statuses: string[], - checked: boolean, -) => { - cy.dataCy(dataCy) - .find(".cy-checkbox") - .each((el) => { - expect(statuses).to.include(el.text()); - if (checked) { - cy.wrap(el).find('input[type="checkbox"]').should("be.checked"); - } else { - cy.wrap(el).find('input[type="checkbox"]').should("not.be.checked"); - } - }); -}; - -/** - * Function used to select a checkbox option from the table filter dropdown. - * Only the first checkbox whose label is a match (i.e. the umbrella group name) will be checked. - * @param dataCy - data-cy of the container - * @param label label of the checkbox option to click on - * @param checked true if should be checked, false if should be unchecked - */ -const selectCheckboxOption = ( - dataCy: string, - label: string, - checked: boolean, -) => { - cy.dataCy(dataCy) - .find(".cy-checkbox") - .should("not.be.disabled") - .each((el) => { - if (el.text() === label) { - if (checked) { - cy.wrap(el) - .find('input[type="checkbox"]') - .check({ force: true, scrollBehavior: false }); - } else { - cy.wrap(el) - .find('input[type="checkbox"]') - .uncheck({ force: true, scrollBehavior: false }); - } - return false; - } - }); -}; diff --git a/apps/spruce/cypress/integration/version/task_table.ts b/apps/spruce/cypress/integration/version/task_table.ts deleted file mode 100644 index 14f7666305..0000000000 --- a/apps/spruce/cypress/integration/version/task_table.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { SEEN_TASK_REVIEW_TOOLTIP } from "constants/cookies"; -import { - clickOnPageSizeBtnAndAssertURLandTableSize, - waitForTaskTable, -} from "../../utils"; - -const pathTasks = "/version/5e4ff3abe3c3317e352062e4/tasks"; -const patchDescriptionTasksExist = "dist"; - -describe("Task table", () => { - it("Loading skeleton does not persist when you navigate to Patch page from My Patches and adjust a filter", () => { - cy.visit("user/patches"); - cy.dataCy("patch-card-patch-link") - .filter(`:contains(${patchDescriptionTasksExist})`) - .click(); - cy.dataCy("tasks-table").should("exist"); - }); - - it("Updates sorting in the url when column headers are clicked", () => { - cy.visit(pathTasks); - waitForTaskTable(); - cy.dataCy("tasks-table-row").should("be.visible"); - - cy.location("search").should( - "contain", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC", - ); - - const nameSortControl = "button[aria-label='Sort by Name']"; - const statusSortControl = "button[aria-label='Sort by Task Status']"; - const baseStatusSortControl = - "button[aria-label='Sort by Previous Status']"; - const variantSortControl = "button[aria-label='Sort by Variant']"; - - cy.get(nameSortControl).click(); - cy.location("search").should("contain", "BASE_STATUS%3ADESC%3BNAME%3AASC"); - - cy.get(variantSortControl).click(); - cy.location("search").should("contain", "sorts=NAME%3AASC%3BVARIANT%3AASC"); - - cy.get(statusSortControl).click(); - cy.location("search").should( - "contain", - "sorts=VARIANT%3AASC%3BSTATUS%3AASC", - ); - - cy.get(baseStatusSortControl).click(); - cy.location("search").should( - "contain", - "sorts=STATUS%3AASC%3BBASE_STATUS%3AASC", - ); - - cy.get(baseStatusSortControl).click(); - cy.location("search").should( - "contain", - "sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC", - ); - - cy.get(baseStatusSortControl).click(); - cy.location("search").should("contain", "sorts=STATUS%3AASC"); - }); - - it("Clicking task name goes to task page for that task", () => { - cy.visit(pathTasks); - cy.dataCy("tasks-table-row") - .eq(0) - .within(() => { - cy.get("a").should("have.attr", "href").and("include", "/task"); - }); - }); - - it("Task count displays total tasks", () => { - cy.visit(pathTasks); - cy.dataCy("total-count").first().contains("49"); - }); - - describe("Changing page number", () => { - // Instead of checking the entire table rows lets just check if the elements on the table have changed - it("Displays the next page of results and updates URL when right arrow is clicked and next page exists", () => { - cy.visit(`${pathTasks}?page=0`); - cy.dataCy(dataCyTableRows).should("be.visible"); - - const firstPageRows = tableRowToText(dataCyTableRows); - cy.dataCy(dataCyNextPage).click(); - cy.dataCy(dataCyTableRows).should("be.visible"); - - const secondPageRows = tableRowToText(dataCyTableRows); - - expect(firstPageRows).to.not.eq(secondPageRows); - }); - - it("Displays the previous page of results and updates URL when the left arrow is clicked and previous page exists", () => { - cy.visit(`${pathTasks}?page=1`); - cy.dataCy(dataCyTableRows).should("be.visible"); - const secondPageRows = tableRowToText(dataCyTableRows); - cy.dataCy(dataCyPrevPage).click(); - cy.dataCy(dataCyTableRows).should("be.visible"); - const firstPageRows = tableRowToText(dataCyTableRows); - expect(firstPageRows).to.not.eq(secondPageRows); - }); - - it("Does not update results or URL when left arrow is clicked and previous page does not exist", () => { - cy.visit(`${pathTasks}?page=0`); - cy.dataCy(dataCyTableRows).should("be.visible"); - cy.dataCy(dataCyPrevPage).should("have.attr", "aria-disabled", "true"); - }); - - it("Does not update results or URL when right arrow is clicked and next page does not exist", () => { - cy.visit(`${pathTasks}?page=4`); - cy.dataCy(dataCyTableRows).should("be.visible"); - cy.dataCy(dataCyNextPage).should("have.attr", "aria-disabled", "true"); - }); - }); - - describe("Changing page limit", () => { - 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(pathTasks); - cy.dataCy("tasks-table").should("exist"); - cy.dataCy(dataCyTableRows).should("be.visible"); - clickOnPageSizeBtnAndAssertURLandTableSize( - pageSize, - dataCyTableDataRows, - ); - }); - }); - }); - }); - - describe("blocked tasks", () => { - beforeEach(() => { - cy.visit(`${pathTasks}?limit=100`); - waitForTaskTable(); - }); - - it("shows the blocking tasks when hovering over status badge", () => { - cy.dataCy("depends-on-tooltip").should("not.exist"); - cy.dataCy("task-status-badge").contains("Blocked").trigger("mouseover"); - cy.dataCy("depends-on-tooltip").should("be.visible"); - cy.dataCy("depends-on-tooltip").contains( - "Depends on tasks: “test-migrations”, “test-graphql”", - ); - }); - }); - - describe("task review", () => { - it("marks tasks as viewed and preserves their state on reload", () => { - cy.visit(pathTasks); - cy.dataCy(`reviewed-${firstTaskId}`).check({ force: true }); - cy.dataCy(`reviewed-${firstTaskId}`).should("be.checked"); - - cy.get(`button[aria-label='Expand row']`).click(); - cy.dataCy(`reviewed-${executionTaskId1}`).should( - "have.attr", - "aria-disabled", - "true", - ); - cy.dataCy(`reviewed-${executionTaskId2}`).check({ force: true }); - cy.dataCy(`reviewed-${displayTaskId}`).should("be.checked"); - cy.dataCy(`reviewed-${displayTaskId}`).uncheck({ force: true }); - cy.dataCy(`reviewed-${displayTaskId}`).should("not.be.checked"); - cy.dataCy(`reviewed-${executionTaskId2}`).should("not.be.checked"); - cy.dataCy(`reviewed-${displayTaskId}`).check({ force: true }); - cy.dataCy(`reviewed-${displayTaskId}`).should("be.checked"); - cy.dataCy(`reviewed-${executionTaskId2}`).should("be.checked"); - - cy.reload(); - - cy.dataCy(`reviewed-${firstTaskId}`).should("be.checked"); - cy.dataCy(`reviewed-${displayTaskId}`).should("be.checked"); - cy.get(`button[aria-label='Expand row']`).click(); - cy.dataCy(`reviewed-${executionTaskId2}`).should("be.checked"); - }); - - describe("announcement tooltip", () => { - const resetDate = (d: Date) => { - cy.clock().then((clock) => { - // Need to restore to set a new system date - clock.restore(); - }); - cy.clock(d, ["Date"]); - // Reload to apply clock changes - cy.reload(); - }; - - beforeEach(() => { - cy.clearCookie(SEEN_TASK_REVIEW_TOOLTIP); - cy.visit(pathTasks); - waitForTaskTable(); - }); - - it("shows the announcement tooltip open on the first viewing", () => { - cy.contains("New feature: Task Review").should("be.visible"); - }); - - it("shows the info icon one day after the initial close and hides it after one week", () => { - const now = new Date(2025, 1, 1); // month is 0-indexed - resetDate(now); - cy.contains("New feature: Task Review").should("be.visible"); - cy.contains("button", "Got it").click(); - - const tomorrow = new Date(2025, 1, 2); - resetDate(tomorrow); - cy.contains("Reviewed").should("be.visible"); - cy.contains("New feature: Task Review").should("not.exist"); - cy.dataCy("announcement-tooltip-trigger").click({ force: true }); - - const nextWeek = new Date(2025, 1, 9); - resetDate(nextWeek); - cy.contains("Reviewed").should("be.visible"); - cy.contains("New feature: Task Review").should("not.exist"); - cy.dataCy("announcement-tooltip-trigger").should("not.exist"); - }); - }); - }); -}); - -const firstTaskId = - "evergreen_ubuntu1604_test_service_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48"; -const displayTaskId = "evergreen_ubuntu1604_89"; -const executionTaskId1 = "exec1"; -const executionTaskId2 = "exec2"; - -const dataCyTableDataRows = "[data-cy=tasks-table-row]"; -const dataCyTableRows = "tasks-table-row"; - -const dataCyNextPage = "next-page-button"; -const dataCyPrevPage = "prev-page-button"; - -const tableRowToText = (selector: string) => - new Cypress.Promise((resolve) => { - cy.dataCy(selector) - .invoke("text") - .then((txt) => resolve(txt.toString())); - }); diff --git a/apps/spruce/cypress/integration/version/test_analysis.ts b/apps/spruce/cypress/integration/version/test_analysis.ts deleted file mode 100644 index b87245f541..0000000000 --- a/apps/spruce/cypress/integration/version/test_analysis.ts +++ /dev/null @@ -1,53 +0,0 @@ -describe("Test Analysis", () => { - beforeEach(() => { - cy.visit("/version/5e4ff3abe3c3317e352062e4/test-analysis"); - }); - - it("should group together all matching failing tests in a version and present a stat", () => { - cy.contains("1 test failed across more than one task").should("be.visible"); - cy.contains("JustAFakeTestInALonelyWorld").should("be.visible"); - cy.contains("JustAnotherFakeFailingTestInALonelyWorld").should( - "be.visible", - ); - }); - it("clicking on a test should show the test details", () => { - cy.contains("JustAFakeTestInALonelyWorld").click(); - cy.dataCy("failed-test-grouped-table").should("be.visible"); - }); - it("filtering by test name should only show matching tests", () => { - cy.getInputByLabel("Search Test Failures").type( - "JustAFakeTestInALonelyWorld{enter}", - ); - cy.contains("1 test failed across more than one task").should("be.visible"); - cy.contains("JustAFakeTestInALonelyWorld").should("be.visible"); - cy.contains("JustAnotherFakeFailingTestInALonelyWorld").should("not.exist"); - }); - it("filtering by task status should only show matching tests", () => { - cy.getInputByLabel("Failure Type").click(); - cy.dataCy("task-status-known-issue-option").should("be.visible"); - cy.dataCy("task-status-known-issue-option").click(); - - cy.contains("0 tests failed across more than one task").should( - "be.visible", - ); - cy.contains("JustAFakeTestInALonelyWorld").should("be.visible"); - cy.contains("JustAnotherFakeFailingTestInALonelyWorld").should("not.exist"); - }); - it("clearing the filters should reset the view", () => { - cy.getInputByLabel("Search Test Failures").type( - "JustAFakeTestInALonelyWorld{enter}", - ); - cy.getInputByLabel("Failure Type").click(); - cy.dataCy("task-status-known-issue-option").should("be.visible"); - cy.dataCy("task-status-known-issue-option").click(); - cy.get("body").type("{esc}"); - - cy.contains("0 tests failed across more than one task").should( - "be.visible", - ); - cy.dataCy("clear-filter-button").should("not.be.disabled"); - cy.dataCy("clear-filter-button").click({ force: true }); - cy.getInputByLabel("Search Test Failures").should("have.value", ""); - cy.contains("1 test failed across more than one task").should("be.visible"); - }); -}); diff --git a/apps/spruce/cypress/integration/version/version_timing.ts b/apps/spruce/cypress/integration/version/version_timing.ts deleted file mode 100644 index c9b766f5dc..0000000000 --- a/apps/spruce/cypress/integration/version/version_timing.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { clickOnPageSizeBtnAndAssertURLandTableSize } from "../../utils"; - -describe("Version Timing Tab without a variant selected", () => { - beforeEach(() => { - cy.visit("/version/5e4ff3abe3c3317e352062e4/version-timing"); - }); - it("shows a chart of all variants in the version", () => { - const expectedVariants = ["Ubuntu 16.04", "Lint", "Race Detector"]; - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - expectedVariants.forEach((variant) => { - expect(textFound).to.include(variant); - }); - }); - }); - it("allows the user to select a variant and navigate to the variant timing view", () => { - cy.get("[id^=reactgooglegraph]").within(() => { - cy.contains("Ubuntu 16.04").click(); - }); - cy.url().should( - "equal", - "http://localhost:3000/version/5e4ff3abe3c3317e352062e4/version-timing?variant=%5Eubuntu1604%24", - ); - }); - - it("has disabled pagination functionality", () => { - cy.get("button[aria-labelledby='page-size-select']").should( - "have.attr", - "aria-disabled", - "true", - ); - cy.dataCy("next-page-button").should("have.attr", "aria-disabled", "true"); - cy.dataCy("prev-page-button").should("have.attr", "aria-disabled", "true"); - cy.dataCy("clear-all-filters").should("have.attr", "aria-disabled", "true"); - }); -}); - -describe("Version Timing Tab with a variant selected", () => { - beforeEach(() => { - cy.visit( - "/version/5e4ff3abe3c3317e352062e4/version-timing?variant=^ubuntu1604%24", - ); - }); - - const expectedTasks = [ - [ - "test-agent", - "test-cloud", - "test-operations", - "test-scheduler", - "js-test", - "test-units", - "test-command", - "test-model", - "test-model-2", - "test-model-host", - ], - [ - "test-validator", - "test-thirdparty", - "test-service", - "test-rest-model", - "test-rest-client", - "test-graphql", - "test-repotracker", - "test-trigger", - "test-rest-data", - "test-rest-route", - ], - [ - "test-model-grid", - "test-model-user", - "test-model-testresult", - "test-model-manifest", - "test-model-notification", - "test-model-commitqueue", - "test-model-patch", - "test-model-event", - "test-monitor", - "test-model-task", - ], - [ - "test-migrations", - "test-model-alertrecord", - "test-thirdparty-docker", - "test-model-stats", - "test-evergreen", - "test-model-distro", - "test-util", - "test-model-artifact", - "test-model-build", - "test-plugin", - ], - ["test-db", "test-auth"], - ]; - - it("shows a paginated chart of all tasks in the variant", () => { - // Iterate through each page of results and check the expected tasks are present - expectedTasks.forEach((page) => { - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - page.forEach((task) => { - expect(textFound).to.include(task); - }); - cy.dataCy("next-page-button").click(); - }); - }); - - // Reverse and iterate through each page of results and check the expected tasks are present - expectedTasks.reverse().forEach((page) => { - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - page.forEach((task) => { - expect(textFound).to.include(task); - }); - cy.dataCy("prev-page-button").click(); - }); - }); - }); - - it("respects the task filter", () => { - cy.visit( - "/version/5e4ff3abe3c3317e352062e4/version-timing?taskName=agent&variant=^ubuntu1604%24", - ); - cy.dataCy("next-page-button").should("have.attr", "aria-disabled", "true"); - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - expectedTasks.flat().forEach((task) => { - if (task.includes("agent")) { - expect(textFound).to.include(task); - } else { - expect(textFound).to.not.include(task); - } - }); - }); - }); - - it("allows the user to clear all filters", () => { - cy.dataCy("clear-all-filters").click(); - - cy.url().should( - "equal", - "http://localhost:3000/version/5e4ff3abe3c3317e352062e4/version-timing?sorts=DURATION%3ADESC", - ); - - const expectedVariants = ["Ubuntu 16.04", "Lint", "Race Detector"]; - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - expectedVariants.forEach((variant) => { - expect(textFound).to.include(variant); - }); - }); - }); - - it("allows the user to change the page size", () => { - clickOnPageSizeBtnAndAssertURLandTableSize(50, ""); - cy.get("svg > g > text").then(($items) => { - const textFound = Array.from($items, (item) => item.innerHTML); - expectedTasks.flat().forEach((task) => { - expect(textFound).to.include(task); - }); - }); - }); - - it("allows the user to select a task and navigate to it", () => { - cy.get("[id^=reactgooglegraph]").within(() => { - cy.contains("test-agent").click(); - }); - cy.location("pathname").should( - "equal", - "/task/evergreen_ubuntu1604_test_agent_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48/logs", - ); - }); -}); diff --git a/apps/spruce/playwright/helpers/index.ts b/apps/spruce/playwright/helpers/index.ts index 69e9b7860e..1db35d9c6e 100644 --- a/apps/spruce/playwright/helpers/index.ts +++ b/apps/spruce/playwright/helpers/index.ts @@ -15,6 +15,7 @@ export const selectOption = async ( options?: { exact: boolean }, ): Promise => { const button = page.getByRole("button", { name: label, exact: true }); + await expect(button).toHaveCount(1); await expect(button).toBeEnabled(); await button.click(); const listbox = page.locator('[role="listbox"]'); @@ -123,6 +124,7 @@ export { login, logout, clickCheckbox, + clickRadio, mockGraphQLResponse, hasOperationName, } from "@evg-ui/playwright-config/helpers"; diff --git a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts b/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts deleted file mode 100644 index 6240bdadbd..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { SEEN_GITHUB_NAV_GUIDE_CUE } from "../../../src/constants/cookies"; -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; - -test.describe("Commit Checks project settings when GitHub webhooks are disabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/logkeeper/settings/commit-checks"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("Commit Checks page shows a disabled webhooks banner when webhooks are disabled", async ({ - authenticatedPage: page, - }) => { - const banner = page.getByTestId("disabled-webhook-banner"); - await expect(banner).toBeVisible(); - await expect(banner).toContainText( - "GitHub features are disabled because the Evergreen GitHub App is not", - ); - }); - - test("Disables all interactive elements on the page", async ({ - authenticatedPage: page, - }) => { - const settingsPage = page.getByTestId("project-settings-page"); - await expect( - settingsPage.locator('button:not([aria-disabled="true"])'), - ).toHaveCount(0); - await expect(page.locator('input:not([aria-disabled="true"])')).toHaveCount( - 0, - ); - }); -}); - -test.describe("Commit Checks project settings when GitHub webhooks are enabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/spruce/settings/commit-checks"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("Shows an error banner when Commit Checks are enabled and hides it when Commit Checks are disabled", async ({ - authenticatedPage: page, - }) => { - await page - .getByTestId("github-checks-enabled-radio-box") - .locator("label") - .first() - .click(); - - const errorBanner = page.getByTestId("error-banner"); - await expect(errorBanner).toBeVisible(); - await expect(errorBanner).toContainText( - "A Commit Check Definition must be specified for this feature to run.", - ); - await page - .getByTestId("github-checks-enabled-radio-box") - .locator("label") - .last() - .click(); - await expect(page.getByTestId("error-banner")).toHaveCount(0); - }); - - test("Saves successfully when Commit Checks are enabled and a Commit Check Definition is provided", async ({ - authenticatedPage: page, - }) => { - await page - .getByTestId("github-checks-enabled-radio-box") - .locator("label") - .first() - .click(); - await page.getByRole("button", { name: "Add definition" }).click(); - await page.getByTestId("variant-tags-input").first().fill("vtag"); - await page.getByTestId("task-tags-input").first().fill("ttag"); - await expect(page.getByTestId("error-banner")).toHaveCount(0); - - const saveButton = page.getByTestId("save-settings-button"); - await expect(saveButton).toBeEnabled(); - await saveButton.click(); - const modal = page.getByTestId("save-changes-modal"); - await expect(modal).toBeVisible(); - await modal.getByRole("button", { name: "Save changes" }).click(); - await expect(modal).toBeHidden(); - await validateToast(page, "success", "Successfully updated project"); - }); -}); diff --git a/apps/spruce/playwright/tests/projectSettings/git_tags.spec.ts b/apps/spruce/playwright/tests/projectSettings/git_tags.spec.ts deleted file mode 100644 index 30d949030b..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/git_tags.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { clickRadio } from "@evg-ui/playwright-config/helpers"; -import { SEEN_GITHUB_NAV_GUIDE_CUE } from "../../../src/constants/cookies"; -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; - -test.describe("Git Tags project settings when GitHub webhooks are disabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/logkeeper/settings/git-tags"); - await expect(page.getByTestId("save-settings-button")).toBeDisabled(); - }); - - test("Git tags page shows a disabled webhooks banner when webhooks are disabled", async ({ - authenticatedPage: page, - }) => { - const banner = page.getByTestId("disabled-webhook-banner"); - await expect(banner).toBeVisible(); - await expect(banner).toContainText( - "GitHub features are disabled because the Evergreen GitHub App is not", - ); - }); - - test("Disables all interactive elements on the page", async ({ - authenticatedPage: page, - }) => { - const settingsPage = page.getByTestId("project-settings-page"); - await expect( - settingsPage.locator('button:not([aria-disabled="true"])'), - ).toHaveCount(0); - await expect(page.locator('input:not([aria-disabled="true"])')).toHaveCount( - 0, - ); - }); -}); - -test.describe("Git Tags project settings when GitHub webhooks are enabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/repo/602d70a2b2373672ee493184/settings/git-tags"); - await expect(page.getByTestId("save-settings-button")).toBeDisabled(); - }); - - test("Saves successfully when Git Tags are enabled and a Git Tag Definition is provided", async ({ - authenticatedPage: page, - }) => { - const gitTagRadioBox = page.getByTestId("git-tag-enabled-radio-box"); - const enabledRadio = gitTagRadioBox.getByRole("radio", { name: "Enabled" }); - await clickRadio(enabledRadio); - const errorText = - "A Git Tag Version Definition must be specified for this feature to run."; - const errorBanner = page.getByTestId("error-banner"); - await expect(errorBanner).toBeVisible(); - await expect(errorBanner).toContainText(errorText); - - await page - .getByTestId("add-button") - .filter({ hasText: "Add git tag" }) - .click(); - await page.getByTestId("git-tag-input").fill("v*"); - await page.getByTestId("remote-path-input").fill("./evergreen.yml"); - await expect(page.getByTestId("error-banner")).toHaveCount(0); - - const saveButton = page.getByTestId("save-settings-button"); - await expect(saveButton).toBeEnabled(); - await saveButton.click(); - const modal = page.getByTestId("save-changes-modal"); - await expect(modal).toBeVisible(); - await modal.getByRole("button", { name: "Save changes" }).click(); - await expect(modal).toBeHidden(); - await validateToast(page, "success", "Successfully updated repo"); - }); -}); diff --git a/apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts b/apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts deleted file mode 100644 index 9dbe93942d..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { SEEN_GITHUB_NAV_GUIDE_CUE } from "../../../src/constants/cookies"; -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; - -test.describe("Merge Queue project settings when GitHub webhooks are disabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/logkeeper/settings/merge-queue"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("Merge Queue page shows a disabled webhooks banner when webhooks are disabled", async ({ - authenticatedPage: page, - }) => { - const banner = page.getByTestId("disabled-webhook-banner"); - await expect(banner).toBeVisible(); - await expect(banner).toContainText( - "GitHub features are disabled because the Evergreen GitHub App is not", - ); - }); - - test("Disables all interactive elements on the page", async ({ - authenticatedPage: page, - }) => { - const settingsPage = page.getByTestId("project-settings-page"); - await expect( - settingsPage.locator('button:not([aria-disabled="true"])'), - ).toHaveCount(0); - await expect(page.locator('input:not([aria-disabled="true"])')).toHaveCount( - 0, - ); - }); -}); - -test.describe("Merge Queue project settings when GitHub webhooks are enabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/spruce/settings/merge-queue"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("Enabling merge queue shows hidden inputs and error banner", async ({ - authenticatedPage: page, - }) => { - await page - .getByTestId("mq-enabled-radio-box") - .locator("label", { hasText: "Enabled" }) - .click(); - await expect(page.getByText("Merge Queue Patch Definitions")).toBeVisible(); - - const errorBanner = page.getByTestId("error-banner"); - await expect(errorBanner).toBeVisible(); - await expect(errorBanner).toContainText( - "A Merge Queue Patch Definition must be specified for this feature to run.", - ); - }); - - test("Saves a merge queue definition", async ({ - authenticatedPage: page, - }) => { - await page - .getByTestId("mq-enabled-radio-box") - .locator("label", { hasText: "Enabled" }) - .click(); - await page - .getByRole("button", { name: "Add merge queue patch definition" }) - .click(); - await page.getByTestId("variant-tags-input").first().fill("vtag"); - await page.getByTestId("task-tags-input").first().fill("ttag"); - - const saveButton = page.getByTestId("save-settings-button"); - await expect(saveButton).toBeEnabled(); - await saveButton.click(); - const modal = page.getByTestId("save-changes-modal"); - await expect(modal).toBeVisible(); - await modal.getByRole("button", { name: "Save changes" }).click(); - await expect(modal).toBeHidden(); - await validateToast(page, "success", "Successfully updated project"); - }); -}); diff --git a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts b/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts deleted file mode 100644 index 2b2dfbfeb5..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { SEEN_GITHUB_NAV_GUIDE_CUE } from "../../../src/constants/cookies"; -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; - -test.describe("Pull Requests project settings when GitHub webhooks are disabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/project/logkeeper/settings/pull-requests"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("shows a disabled webhooks banner when webhooks are disabled", async ({ - authenticatedPage: page, - }) => { - const banner = page.getByTestId("disabled-webhook-banner"); - await expect(banner).toBeVisible(); - await expect(banner).toContainText( - "GitHub features are disabled because the Evergreen GitHub App is not", - ); - }); - - test("disables all interactive elements on the page", async ({ - authenticatedPage: page, - }) => { - const settingsPage = page.getByTestId("project-settings-page"); - await expect( - settingsPage.locator('button:not([aria-disabled="true"])'), - ).toHaveCount(0); - await expect(page.locator('input:not([aria-disabled="true"])')).toHaveCount( - 0, - ); - }); -}); - -test.describe("Pull Requests project settings when GitHub webhooks are enabled", () => { - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.context().addCookies([ - { - name: SEEN_GITHUB_NAV_GUIDE_CUE, - value: "true", - domain: "localhost", - path: "/", - }, - ]); - await page.goto("/repo/602d70a2b2373672ee493184/settings/pull-requests"); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); - }); - - test("allows enabling manual PR testing", async ({ - authenticatedPage: page, - }) => { - const manualEnabledLabel = page - .getByTestId("manual-pr-testing-enabled-radio-box") - .locator("label", { hasText: "Enabled" }); - await manualEnabledLabel.scrollIntoViewIfNeeded(); - await manualEnabledLabel.click(); - const manualEnabledRadio = page - .getByTestId("manual-pr-testing-enabled-radio-box") - .getByRole("radio", { name: "Enabled" }); - await expect(manualEnabledRadio).toBeChecked(); - }); - - test("saving a patch definition hides the error banner, shows success toast, and disables repo patch definitions", async ({ - authenticatedPage: page, - }) => { - const errorText = - "A GitHub Patch Definition must be specified for this feature to run."; - const errorBanner = page.getByText(errorText); - await expect(errorBanner).toBeVisible(); - - await page.getByRole("button", { name: "Add patch definition" }).click(); - await expect(page.getByText(errorText)).toHaveCount(0); - await page.getByTestId("variant-tags-input").first().fill("vtag"); - await page.getByTestId("task-tags-input").first().fill("ttag"); - const saveButton = page.getByTestId("save-settings-button"); - await expect(saveButton).toBeEnabled(); - await saveButton.click(); - - const modal = page.getByTestId("save-changes-modal"); - await expect(modal).toBeVisible(); - await modal.getByRole("button", { name: "Save changes" }).click(); - await expect(modal).toBeHidden(); - await validateToast(page, "success", "Successfully updated repo"); - - await page.goto("/project/evergreen/settings/pull-requests"); - const patchDefAccordion = page.getByText("Repo Patch Definition 1"); - await patchDefAccordion.scrollIntoViewIfNeeded(); - await patchDefAccordion.click(); - const variantInput = page.getByTestId("variant-tags-input").first(); - const taskInput = page.getByTestId("task-tags-input").first(); - - await expect(variantInput).toHaveValue("vtag"); - await expect(variantInput).toHaveAttribute("aria-disabled", "true"); - await expect(taskInput).toHaveValue("ttag"); - await expect(taskInput).toHaveAttribute("aria-disabled", "true"); - await expect(page.getByText(errorText)).toHaveCount(0); - }); -}); diff --git a/apps/spruce/playwright/tests/spawn/host.spec.ts b/apps/spruce/playwright/tests/spawn/host.spec.ts new file mode 100644 index 0000000000..33cac66d47 --- /dev/null +++ b/apps/spruce/playwright/tests/spawn/host.spec.ts @@ -0,0 +1,381 @@ +import { test, expect } from "../../fixtures"; +import { + clearDatePickerInput, + clickCheckbox, + clickRadio, + typeDatePickerDate, +} from "../../helpers"; + +const ascendingSortSpawnHostOrderByHostId = [ + "i-04ade558e1e26b0ad", + "i-07669e7a3cd2c238c", + "i-092593689871a50dc", +]; +const descendingSortSpawnHostOrderByHostId = [ + "i-092593689871a50dc", + "i-07669e7a3cd2c238c", + "i-04ade558e1e26b0ad", +]; +const descendingSortSpawnHostOrderByExpiration = [ + "i-092593689871a50dc", + "i-07669e7a3cd2c238c", + "i-04ade558e1e26b0ad", +]; +const ascendingSortSpawnHostOrderByExpiration = [ + "i-04ade558e1e26b0ad", + "i-07669e7a3cd2c238c", + "i-092593689871a50dc", +]; + +const hostTaskId = + "evergreen_ubuntu1604_dist_patch_33016573166a36bd5f46b4111151899d5c4e95b1_5ecedafb562343215a7ff297_20_05_27_21_39_46"; +const distroId = "windows-64-vs2015-small"; +const projectSetupCheckboxLabel = + "Use project-specific setup script defined at /path"; +const setupScriptCheckboxLabel = "Define setup script to run after host"; +const startHostsCheckboxLabel = + "Also start any hosts this task started (if applicable)"; +const loadDataCheckboxLabel = "Load data for dist"; + +test.describe("Spawn Host page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/spawn/host"); + }); + + test("Visiting the spawn host page should display all of your spawned hosts", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("leafygreen-table-row")).toHaveCount(3); + }); + + test("Visiting the spawn host page should not have any cards expanded by default", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("spawn-host-card")).toHaveCount(0); + }); + + test("Clicking on a spawn host row should toggle the host card", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("spawn-host-card")).toHaveCount(0); + + const firstExpandButton = page + .getByRole("button", { name: "Expand row" }) + .first(); + await firstExpandButton.click(); + await expect(page.getByTestId("spawn-host-card")).toBeVisible(); + + const firstCollapseButton = page + .getByRole("button", { name: "Collapse row" }) + .first(); + await firstCollapseButton.click(); + await expect(page.getByTestId("spawn-host-card")).toHaveCount(0); + }); + + test("Visiting the spawn host page with an id in the url should open the page with the row expanded", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/host?host=i-092593689871a50dc"); + await expect(page.getByTestId("spawn-host-card")).toHaveCount(1); + await expect(page.getByTestId("spawn-host-card")).toBeVisible(); + }); + + test("Clicking on the Event Log link should redirect to /host/:hostId", async ({ + authenticatedPage: page, + }) => { + await page + .getByTestId("leafygreen-table-row") + .filter({ hasText: "i-092593689871a50dc" }) + .getByRole("button", { name: "Expand row" }) + .click(); + await page.getByText("Event Log").click(); + await expect(page).toHaveURL("/host/i-092593689871a50dc"); + }); + + test.describe("Spawn host card sorting", () => { + test("Clicking on the host column header should sort spawn hosts by ascending order, then descending, then remove sort", async ({ + authenticatedPage: page, + }) => { + const hostSortControl = page.getByRole("button", { + name: "Sort by Host", + }); + await hostSortControl.click(); + const rows = page.getByTestId("leafygreen-table-row"); + for (let i = 0; i < ascendingSortSpawnHostOrderByHostId.length; i++) { + await expect(rows.nth(i)).toContainText( + ascendingSortSpawnHostOrderByHostId[i], + ); + } + await hostSortControl.click(); + for (let i = 0; i < descendingSortSpawnHostOrderByHostId.length; i++) { + await expect(rows.nth(i)).toContainText( + descendingSortSpawnHostOrderByHostId[i], + ); + } + await hostSortControl.click(); + await expect(rows).toHaveCount(3); + }); + + test("Clicking on the expiration column header should sort the hosts by ascending order, then descending, then remove sort", async ({ + authenticatedPage: page, + }) => { + const expiresInSortControl = page.getByRole("button", { + name: "Sort by Expires In", + }); + await expiresInSortControl.click(); + const rows = page.getByTestId("leafygreen-table-row"); + for (let i = 0; i < ascendingSortSpawnHostOrderByExpiration.length; i++) { + await expect(rows.nth(i)).toContainText( + ascendingSortSpawnHostOrderByExpiration[i], + ); + } + await expiresInSortControl.click(); + for ( + let i = 0; + i < descendingSortSpawnHostOrderByExpiration.length; + i++ + ) { + await expect(rows.nth(i)).toContainText( + descendingSortSpawnHostOrderByExpiration[i], + ); + } + await expiresInSortControl.click(); + await expect(rows).toHaveCount(3); + }); + }); + + test.describe("Spawn host modal", () => { + test("Should disable 'Unexpirable Host' radio box when max number of unexpirable hosts is met (2)", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Spawn a host" }).click(); + await page.getByTestId("distro-input").click(); + await page.getByTestId("distro-option-ubuntu1804-workstation").click(); + + const expirableHostButton = page.getByRole("radio", { + name: "Expirable Host", + exact: true, + }); + await expect(expirableHostButton).toBeEnabled(); + + const unexpirableHostButton = page.getByRole("radio", { + name: "Unexpirable Host", + exact: true, + }); + await expect(unexpirableHostButton).toBeDisabled(); + + await expect( + page.getByText("You have reached the max number of unexpirable hosts"), + ).toBeVisible(); + }); + + test("Clicking on the spawn host button should open a spawn host modal", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("spawn-host-modal")).toHaveCount(0); + await page.getByRole("button", { name: "Spawn a host" }).click(); + await expect(page.getByTestId("spawn-host-modal")).toBeVisible(); + }); + + test("Visiting the spawn host page with the proper url param should open the spawn host modal by default", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/host?spawnHost=True"); + await expect(page.getByTestId("spawn-host-modal")).toBeVisible(); + }); + + test("Closing the spawn host modal removes the 'spawnHost' query param from the url and hides the modal", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/host?spawnHost=True"); + await expect(page.getByTestId("spawn-host-modal")).toBeVisible(); + await expect(page).toHaveURL("/spawn/host?spawnHost=True"); + await page + .getByTestId("spawn-host-modal") + .getByRole("button", { name: "Cancel" }) + .click(); + await expect(page).not.toHaveURL("/spawn/host?spawnHost=True"); + await expect(page.getByTestId("spawn-host-modal")).toHaveCount(0); + }); + + test("Visiting the spawn host page with a taskId url param should render additional options at the bottom of the modal", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=rhel71-power8-large&taskId=${hostTaskId}`, + ); + const projectSetupCheckbox = page.getByRole("checkbox", { + name: projectSetupCheckboxLabel, + }); + const loadDataCheckbox = page.getByRole("checkbox", { + name: loadDataCheckboxLabel, + }); + const startHostsCheckbox = page.getByRole("checkbox", { + name: startHostsCheckboxLabel, + }); + + await expect(projectSetupCheckbox).toHaveCount(1); + await expect(loadDataCheckbox).toHaveCount(1); + await expect(startHostsCheckbox).toHaveCount(1); + }); + + test("Unchecking 'Load data for dist' hides nested checkbox selections and checking shows them", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=rhel71-power8-large&taskId=${hostTaskId}`, + ); + await expect(page.getByTestId("spawn-host-modal")).toBeVisible(); + const loadDataCheckbox = page.getByRole("checkbox", { + name: loadDataCheckboxLabel, + }); + const projectSetupCheckbox = page.getByRole("checkbox", { + name: projectSetupCheckboxLabel, + }); + const startHostsCheckbox = page.getByRole("checkbox", { + name: startHostsCheckboxLabel, + }); + + await expect(loadDataCheckbox).toBeChecked(); + await expect(projectSetupCheckbox).toHaveCount(1); + await expect(startHostsCheckbox).toHaveCount(1); + + await clickCheckbox(loadDataCheckbox); + await expect(loadDataCheckbox).not.toBeChecked(); + await expect(projectSetupCheckbox).toHaveCount(0); + await expect(startHostsCheckbox).toHaveCount(0); + }); + + test("Visiting the spawn host page with a task and distro supplied in the url should populate the distro input", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + await expect(page.getByTestId("spawn-host-modal")).toBeVisible(); + await expect( + page.getByTestId("distro-input").getByTestId("dropdown-value"), + ).toContainText(distroId); + }); + + test("The virtual workstation dropdown should filter any volumes that aren't a home volume", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + await page.getByTestId("distro-input").click(); + await expect(page.getByText("Admin-only distros")).toHaveCount(0); + await page.getByTestId("distro-option-ubuntu1804-workstation").click(); + await expect(page.getByTestId("volume-select")).toBeDisabled(); + }); + + test("Clicking 'Add new key' hides the key name dropdown and shows the key value text area", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + await expect(page.getByTestId("key-select")).toBeVisible(); + await expect(page.getByTestId("key-value-text-area")).toHaveCount(0); + const addNewKeyRadio = page.getByRole("radio", { name: "Add new key" }); + await clickRadio(addNewKeyRadio); + await expect(page.getByTestId("key-select")).toHaveCount(0); + await expect(page.getByTestId("key-value-text-area")).toBeVisible(); + }); + + test("Checking 'Run Userdata script on start' shows the user data script text area", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + const userDataScriptInput = page.getByRole("textbox", { + name: "Userdata Script", + }); + await expect(userDataScriptInput).toHaveCount(0); + const userDataScriptCheckbox = page.getByRole("checkbox", { + name: "Run Userdata script on start", + }); + await clickCheckbox(userDataScriptCheckbox); + await expect(userDataScriptInput).toBeVisible(); + }); + + test("Checking 'Define setup script...' shows the setup script text area", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + const setupScriptInput = page.getByRole("textbox", { + name: "Setup Script", + }); + await expect(setupScriptInput).toHaveCount(0); + + const projectSetupScriptCheckbox = page.getByRole("checkbox", { + name: projectSetupCheckboxLabel, + }); + await clickCheckbox(projectSetupScriptCheckbox); + + const setupScriptCheckbox = page.getByRole("checkbox", { + name: setupScriptCheckboxLabel, + }); + await expect(setupScriptCheckbox).toBeEnabled(); + await clickCheckbox(setupScriptCheckbox); + await expect(setupScriptInput).toHaveCount(1); + }); + + test("Conditionally disables setup script and project setup script checkboxes based on the other's value", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `/spawn/host?spawnHost=True&distroId=${distroId}&taskId=${hostTaskId}`, + ); + const projectCheckbox = page.getByRole("checkbox", { + name: projectSetupCheckboxLabel, + }); + const setupCheckbox = page.getByRole("checkbox", { + name: setupScriptCheckboxLabel, + }); + + await expect(projectCheckbox).toBeChecked(); + await expect(projectCheckbox).toBeEnabled(); + await expect(setupCheckbox).toBeDisabled(); + + await clickCheckbox(projectCheckbox); + await expect(projectCheckbox).not.toBeChecked(); + await expect(projectCheckbox).toBeEnabled(); + await expect(setupCheckbox).toBeEnabled(); + + await clickCheckbox(setupCheckbox); + await expect(projectCheckbox).toBeDisabled(); + await expect(setupCheckbox).toBeEnabled(); + }); + }); + + test("Allows editing a modal with sleep schedule enabled and validates dates", async ({ + authenticatedPage: page, + }) => { + await page.clock.setFixedTime("2026-05-26T00:00:00Z"); + await page.getByTestId("edit-host-button").nth(2).click(); + + const modal = page.getByTestId("edit-spawn-host-modal").nth(2); + await expect(modal).toBeVisible(); + + const saveButton = page.getByRole("button", { name: "Save" }); + + // Set a valid near-future date + await typeDatePickerDate(page, { year: "2026", month: "06", day: "01" }); + await expect(saveButton).toHaveAttribute("aria-disabled", "false"); + + // Set a date in the past + await clearDatePickerInput(page); + await typeDatePickerDate(page, { year: "2025", month: "01", day: "01" }); + await expect(saveButton).toHaveAttribute("aria-disabled", "true"); + + // Set a date too far in the future + await clearDatePickerInput(page); + await typeDatePickerDate(page, { year: "2060", month: "01", day: "15" }); + await expect(saveButton).toHaveAttribute("aria-disabled", "true"); + }); +}); diff --git a/apps/spruce/playwright/tests/spawn/route.spec.ts b/apps/spruce/playwright/tests/spawn/route.spec.ts new file mode 100644 index 0000000000..ae3d3f6ba6 --- /dev/null +++ b/apps/spruce/playwright/tests/spawn/route.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from "../../fixtures"; + +test.describe("Navigating to Spawn Host and Spawn Volume pages", () => { + test("Navigating to /spawn will redirect to /spawn/host", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn"); + await expect(page).toHaveURL("/spawn/host"); + }); + + test("Navigating to /spawn/not-a-route will redirect to /spawn/host", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/not-a-route"); + await expect(page).toHaveURL("/spawn/host"); + }); + + test("Clicking on the Volume side nav item will redirect to /spawn/volume", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/host"); + await page.getByTestId("volume-nav-tab").click(); + await expect(page).toHaveURL("/spawn/volume"); + }); + + test("Clicking on the Host side nav item will redirect to /spawn/host", async ({ + authenticatedPage: page, + }) => { + await page.goto("/spawn/volume"); + await page.getByTestId("host-nav-tab").click(); + await expect(page).toHaveURL("/spawn/host"); + }); +}); diff --git a/apps/spruce/playwright/tests/spawn/volume.spec.ts b/apps/spruce/playwright/tests/spawn/volume.spec.ts new file mode 100644 index 0000000000..1f90fe2dae --- /dev/null +++ b/apps/spruce/playwright/tests/spawn/volume.spec.ts @@ -0,0 +1,382 @@ +import { test, expect } from "../../fixtures"; +import { + clickCheckbox, + mockGraphQLResponse, + selectOption, + validateDatePickerDate, + validateToast, +} from "../../helpers"; + +const expectedVolNames = [ + "vol-0ae8720b445b771b6", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b856", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b857", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b859", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b815", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b825", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b835", + "1da0e996608e6871b60a92f6564bbc9cdf66ce90be1178dfb653920542a0d0f0", + "vol-0c66e16459646704d", + "vol-0583d66433a69f136", + "vol-0ea662ac92f611ed4", +]; + +test.describe("Spawn volume page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/spawn/volume"); + }); + + test("Visiting the spawn volume page should display the number of free and mounted volumes", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("mounted-badge")).toContainText("9 Mounted"); + await expect(page.getByTestId("free-badge")).toContainText("4 Free"); + }); + + test("The table initially displays volumes with status ascending", async ({ + authenticatedPage: page, + }) => { + const rows = page.getByTestId("leafygreen-table-row"); + for (let i = 0; i < expectedVolNames.length; i++) { + await expect(rows.nth(i)).toContainText(expectedVolNames[i]); + } + }); + + test("Table should have no cards visible by default", async ({ + authenticatedPage: page, + }) => { + for (const name of expectedVolNames) { + await expect(page.getByTestId(name)).toHaveCount(0); + } + }); + + test("Should render migrating volumes with a different badge and disable action buttons", async ({ + authenticatedPage: page, + }) => { + const targetVolume = "vol-0ae8720b445b771b6"; + const migratingRow = page + .getByTestId("leafygreen-table-row") + .filter({ hasText: targetVolume }); + await expect(migratingRow.getByTestId("volume-status-badge")).toContainText( + "Migrating", + ); + const trashButton = page.getByTestId(`trash-${targetVolume}`); + await expect(trashButton).toBeDisabled(); + const migrateButton = page.getByTestId(`migrate-btn-${targetVolume}`); + await expect(migrateButton).toBeDisabled(); + const editButton = page.getByTestId(`edit-btn-${targetVolume}`); + await expect(editButton).toBeDisabled(); + }); + + test("Should have a volume card visible initially when the 'volume' query param is provided", async ({ + authenticatedPage: page, + }) => { + const targetVolume = "vol-0ea662ac92f611ed4"; + await page.goto(`/spawn/volume?volume=${targetVolume}`); + const card = page.getByTestId(`spawn-volume-card-${targetVolume}`); + await expect(card).toBeVisible(); + }); + + test("Clicking on the row should toggle the volume card open and closed", async ({ + authenticatedPage: page, + }) => { + const targetVolume = "vol-0ea662ac92f611ed4"; + await page.goto(`/spawn/volume?volume=${targetVolume}`); + const card = page.getByTestId(`spawn-volume-card-${targetVolume}`); + await expect(card).toBeVisible(); + + const row = page + .getByTestId("leafygreen-table-row") + .filter({ hasText: targetVolume }); + await row.getByRole("button", { name: "Collapse row" }).click(); + await expect(card).toHaveCount(0); + + await row.getByRole("button", { name: "Expand row" }).click(); + await expect(card).toBeVisible(); + }); + + test("Clicking the trash can should remove the volume from the table and update free/mounted volumes badges", async ({ + authenticatedPage: page, + }) => { + await mockGraphQLResponse(page, "RemoveVolume", { + data: { removeVolume: true }, + errors: null, + }); + const targetVolume = "vol-0c66e16459646704d"; + await expect( + page + .getByTestId("leafygreen-table-row") + .filter({ hasText: targetVolume }), + ).toBeVisible(); + + await page.getByTestId(`trash-${targetVolume}`).click(); + const popconfirm = page.getByTestId("delete-volume-popconfirm"); + await expect(popconfirm).toBeVisible(); + const yesButton = popconfirm.getByRole("button", { name: "Yes" }); + await expect(yesButton).toBeVisible(); + await expect(yesButton).toBeEnabled(); + await yesButton.click(); + await validateToast(page, "success", "Successfully deleted the volume."); + }); + + test("Clicking the trash can for a mounted volume should show an additional confirmation checkbox which enables the submit button when checked", async ({ + authenticatedPage: page, + }) => { + await mockGraphQLResponse(page, "RemoveVolume", { + data: { removeVolume: true }, + errors: null, + }); + await page + .getByTestId( + "trash-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + ) + .click(); + const popconfirm = page.getByTestId("delete-volume-popconfirm"); + await expect(popconfirm).toBeVisible(); + + const confirmCheckbox = popconfirm.getByRole("checkbox", { + name: "I understand this volume is currently mounted to a host.", + }); + const yesButton = popconfirm.getByRole("button", { name: "Yes" }); + await expect(confirmCheckbox).not.toBeChecked(); + await expect(yesButton).toBeDisabled(); + await clickCheckbox(confirmCheckbox); + await expect(yesButton).toBeEnabled(); + await yesButton.click(); + + await validateToast(page, "success", "Successfully deleted the volume."); + }); + + test("Clicking on unmount should result in a success toast appearing", async ({ + authenticatedPage: page, + }) => { + await mockGraphQLResponse(page, "DetachVolumeFromHost", { + data: { detachVolumeFromHost: true }, + errors: null, + }); + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b857"; + await page.getByTestId(`detach-btn-${targetVolume}`).click(); + const popconfirm = page.getByTestId("unmount-volume-popconfirm"); + const yesButton = popconfirm.getByRole("button", { name: "Yes" }); + await yesButton.click(); + await validateToast(page, "success", "Successfully unmounted the volume."); + }); + + test("Clicking on 'Spawn Volume' should open the Spawn Volume Modal", async ({ + authenticatedPage: page, + }) => { + const spawnButton = page.getByTestId("spawn-volume-btn"); + await expect(spawnButton).toBeEnabled(); + await spawnButton.click(); + await expect(page.getByTestId("spawn-volume-modal")).toBeVisible(); + }); + + test("Reopening the Spawn Volume modal clears previous input changes", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("spawn-volume-btn").click(); + await expect(page.getByTestId("spawn-volume-modal")).toBeVisible(); + await selectOption(page, "Type", "sc1"); + + const modal = page.getByTestId("spawn-volume-modal"); + const cancelButton = modal.getByRole("button", { name: "Cancel" }); + await expect(cancelButton).toBeEnabled(); + await cancelButton.click(); + + await page.getByTestId("spawn-volume-btn").click(); + await expect(page.getByTestId("spawn-volume-modal")).toBeVisible(); + await expect(page.getByTestId("type-select")).toContainText("gp3"); + }); + + test.describe("Edit volume modal", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/spawn/volume"); + }); + + test("Clicking on 'Edit' should open the Edit Volume Modal", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + }); + + test("name, size, expiration inputs should be populated on initial render", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + await expect(page.getByTestId("volume-name-input")).toHaveValue( + targetVolume, + ); + await expect(page.getByTestId("volume-size-input")).toHaveValue("100"); + await validateDatePickerDate(page, "date-picker", { + year: "2020", + month: "06", + day: "06", + }); + await expect(page.getByTestId("hour-input")).toHaveValue("15"); + await expect(page.getByTestId("minute-input")).toHaveValue("48"); + }); + + test("Reopening the edit volume modal should reset form input fields", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + await page.getByTestId("volume-name-input").fill("Hello, World"); + + await page + .getByTestId("update-volume-modal") + .getByRole("button", { name: "Cancel" }) + .click(); + + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("volume-name-input")).toHaveValue( + targetVolume, + ); + }); + + test("size field is validated correctly", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + + await page.getByTestId("volume-size-input").clear(); + await page.getByTestId("volume-size-input").fill("10000"); + await expect(page.getByRole("button", { name: "Save" })).toBeDisabled(); + + await page.getByTestId("volume-size-input").clear(); + await page.getByTestId("volume-size-input").fill("2"); + await expect(page.getByRole("button", { name: "Save" })).toBeDisabled(); + }); + + test("Submit button should be enabled when the volume details input value differs from what already exists", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + + const saveButton = page.getByRole("button", { name: "Save" }); + const volumeInput = page.getByTestId("volume-name-input"); + + await expect(saveButton).toBeDisabled(); + await volumeInput.fill("Hello, World"); + await expect(saveButton).toBeEnabled(); + await volumeInput.clear(); + await volumeInput.fill(targetVolume); + await expect(saveButton).toBeDisabled(); + + const neverExpireCheckbox = page.getByRole("checkbox", { + name: "Never expire", + }); + await clickCheckbox(neverExpireCheckbox); + await expect(saveButton).toBeEnabled(); + }); + + test("Clicking on save button should close the modal and show a success toast", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + mockGraphQLResponse(page, "UpdateVolume", { + data: { + updateVolume: { + id: targetVolume, + name: "Hello, World", + }, + }, + errors: null, + }); + await page.getByTestId(`edit-btn-${targetVolume}`).click(); + await expect(page.getByTestId("update-volume-modal")).toBeVisible(); + await page.getByTestId("volume-name-input").fill("Hello, World"); + const saveButton = page.getByRole("button", { name: "Save" }); + await expect(saveButton).toBeEnabled(); + await saveButton.click(); + await validateToast(page, "success", "Successfully updated volume"); + await expect(page.getByTestId("update-volume-modal")).toHaveCount(0); + }); + }); + + test.describe("Migrate Modal", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/spawn/volume"); + }); + + test("migrate button is disabled for volumes with the migrating status", async ({ + authenticatedPage: page, + }) => { + const targetVolume = "vol-0ae8720b445b771b6"; + const migratingRow = page + .getByTestId("leafygreen-table-row") + .filter({ hasText: targetVolume }); + await expect( + migratingRow.getByTestId("volume-status-badge"), + ).toContainText("Migrating"); + await expect( + page.getByTestId(`migrate-btn-${targetVolume}`), + ).toBeDisabled(); + }); + + test("clicking cancel during confirmation renders the Migrate modal form", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`migrate-btn-${targetVolume}`).click(); + await page.getByTestId("distro-input").click(); + await page.getByTestId("distro-option-ubuntu1804-workstation").click(); + await page + .getByTestId("migrate-modal") + .getByRole("button", { name: "Next" }) + .click(); + await expect(page.getByTestId("migrate-modal")).toContainText( + "Are you sure you want to migrate this home volume?", + ); + await expect(page.getByTestId("distro-input")).toHaveCount(0); + await page + .getByTestId("migrate-modal") + .getByRole("button", { name: "Cancel" }) + .click(); + await expect(page.getByTestId("distro-input")).toBeVisible(); + }); + + test("open the Migrate modal and spawn a host", async ({ + authenticatedPage: page, + }) => { + const targetVolume = + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b858"; + await page.getByTestId(`migrate-btn-${targetVolume}`).click(); + await page.getByTestId("distro-input").click(); + await page.getByTestId("distro-option-ubuntu1804-workstation").click(); + await expect(page.getByTestId("region-select")).toBeDisabled(); + await page + .getByTestId("migrate-modal") + .getByRole("button", { name: "Next" }) + .click(); + await page + .getByTestId("migrate-modal") + .getByRole("button", { name: "Migrate volume" }) + .click(); + await validateToast( + page, + "success", + "Volume migration has been scheduled. A new host will be spawned and accessible on your Hosts page.", + ); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/action_buttons.spec.ts b/apps/spruce/playwright/tests/version/action_buttons.spec.ts new file mode 100644 index 0000000000..057544a012 --- /dev/null +++ b/apps/spruce/playwright/tests/version/action_buttons.spec.ts @@ -0,0 +1,184 @@ +import { test, expect } from "../../fixtures"; +import { validateToast, mockGraphQLResponse } from "../../helpers"; + +const patch = "5ecedafb562343215a7ff297"; +const mainlineCommit = "5e4ff3abe3c3317e352062e4"; +const versionPath = (id: string) => `/version/${id}`; + +test.describe("Action Buttons", () => { + test.describe("When viewing a patch build", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionPath(patch)); + }); + + test("Clicking 'Schedule' button shows modal and clicking on 'Cancel' closes it", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Schedule" }).click(); + await expect(page.getByTestId("schedule-tasks-modal")).toBeVisible(); + await page.getByRole("button", { name: "Cancel" }).click(); + await expect(page.getByTestId("schedule-tasks-modal")).toBeHidden(); + }); + + test("Clicking ellipses dropdown shows ellipses options", async ({ + authenticatedPage: page, + }) => { + const ellipsisButton = page.getByTestId("ellipsis-btn"); + await expect(ellipsisButton).toHaveCount(0); + await ellipsisButton.click(); + await expect(page.getByTestId("card-dropdown")).toBeVisible(); + await ellipsisButton.click(); + await expect(page.getByTestId("card-dropdown")).toHaveCount(0); + }); + }); + + test.describe("Version dropdown options", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionPath(patch)); + await page.getByTestId("ellipsis-btn").click(); + await expect(page.getByTestId("card-dropdown")).toBeVisible(); + }); + + test("Error unscheduling a version shows error toast", async ({ + authenticatedPage: page, + }) => { + await mockGraphQLResponse(page, "UnscheduleVersionTasks", { + data: null, + errors: [ + { + message: "There was an error unscheduling tasks", + path: ["UnscheduleVersionTasks"], + extensions: { code: "INTERNAL_SERVER_ERROR" }, + }, + ], + }); + await page + .getByRole("menuitem", { name: "Unschedule all tasks" }) + .click(); + await page.getByRole("button", { name: "Yes" }).click(); + await validateToast( + page, + "error", + "There was an error unscheduling tasks", + ); + }); + + test("Clicking 'Unschedule' button show popconfirm with abort checkbox and a toast on success", async ({ + authenticatedPage: page, + }) => { + await page + .getByRole("menuitem", { name: "Unschedule all tasks" }) + .click(); + await page.getByRole("button", { name: "Yes" }).click(); + await validateToast( + page, + "success", + "All tasks were unscheduled and tasks that already started were aborted", + ); + }); + + test("Clicking 'Set Priority' button shows popconfirm with input and toast on success", async ({ + authenticatedPage: page, + }) => { + const priority = "99"; + await page.getByRole("menuitem", { name: "Set patch priority" }).click(); + const patchPriorityInput = page.getByTestId("patch-priority-input"); + await patchPriorityInput.fill(priority); + await patchPriorityInput.press("Enter"); + await validateToast(page, "success", priority); + }); + + test("Error setting priority shows error toast", async ({ + authenticatedPage: page, + }) => { + await mockGraphQLResponse(page, "SetVersionPriority", { + data: null, + errors: [ + { + message: "There was an error setting priority", + path: ["SetVersionPriority"], + extensions: { code: "INTERNAL_SERVER_ERROR" }, + }, + ], + }); + await page.getByRole("menuitem", { name: "Set patch priority" }).click(); + const patchPriorityInput = page.getByTestId("patch-priority-input"); + await patchPriorityInput.fill("80"); + await patchPriorityInput.press("Enter"); + await validateToast(page, "error", "Error updating priority for patch"); + }); + + test("Sets priority for multiple tasks when version page table is filtered", async ({ + authenticatedPage: page, + }) => { + await page.goto( + `${versionPath(mainlineCommit)}/tasks?statuses=failed-umbrella,failed,known-issue`, + ); + await page.getByTestId("ellipsis-btn").click(); + await expect(page.getByTestId("card-dropdown")).toBeVisible(); + + const setTaskPriorityButton = page.getByRole("menuitem", { + name: "Set task priority", + }); + await expect(setTaskPriorityButton).toContainText( + "Set task priority (2)", + ); + await setTaskPriorityButton.click(); + const taskPriorityInput = page.getByTestId("task-priority-input"); + await taskPriorityInput.fill("10"); + await taskPriorityInput.press("Enter"); + await validateToast(page, "success", "Priority updated for 2 tasks."); + }); + + test("Should be able to reconfigure the patch", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("reconfigure-link")).not.toHaveAttribute( + "disabled", + ); + await page.getByTestId("reconfigure-link").click(); + await expect(page).toHaveURL(/configure/); + }); + }); + + test.describe("When viewing a mainline commit", () => { + test.describe("Version dropdown options", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionPath(mainlineCommit)); + await page.getByTestId("ellipsis-btn").click(); + await expect(page.getByTestId("card-dropdown")).toBeVisible(); + }); + + test("Reconfigure link is disabled for mainline commits", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("reconfigure-link")).toBeVisible(); + await expect(page.getByTestId("reconfigure-link")).toHaveAttribute( + "aria-disabled", + "true", + ); + }); + }); + }); + + test.describe("Include Never-activated Tasks toggle", () => { + test("sets URL and cookie when toggled on", async ({ + authenticatedPage: page, + }) => { + await page.goto(versionPath(patch)); + await page.getByTestId("ellipsis-btn").click(); + await expect(page.getByTestId("card-dropdown")).toBeVisible(); + await page + .getByTestId("card-dropdown") + .getByText("Include never-activated tasks") + .click(); + await expect(page).toHaveURL(/includeNeverActivatedTasks=true/); + + const cookies = await page.context().cookies(); + const cookie = cookies.find( + (c) => c.name === "include-never-activated-tasks", + ) ?? { value: "false" }; + expect(cookie.value).toBe("true"); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/banners.spec.ts b/apps/spruce/playwright/tests/version/banners.spec.ts new file mode 100644 index 0000000000..135c54f156 --- /dev/null +++ b/apps/spruce/playwright/tests/version/banners.spec.ts @@ -0,0 +1,67 @@ +import { test, expect } from "../../fixtures"; + +const versionWithBanners = + "/version/logkeeper_e864cf934194c161aa044e4599c8c81cee7b6237/tasks?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC"; + +test.describe("banners", () => { + test.describe("errors", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionWithBanners); + }); + + test("should display the number of configuration errors", async ({ + authenticatedPage: page, + }) => { + const errorBanner = page.getByTestId("configuration-errors-banner"); + await expect(errorBanner).toBeVisible(); + await expect( + errorBanner.getByText("4 errors in configuration file"), + ).toBeVisible(); + }); + + test("should be able to open the modal and see all errors", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("configuration-errors-modal-trigger").click(); + await expect( + page.getByTestId("configuration-errors-modal"), + ).toBeVisible(); + await expect(page.locator("li")).toHaveCount(4); + }); + }); + + test.describe("warnings", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionWithBanners); + }); + + test("should display the number of configuration warnings", async ({ + authenticatedPage: page, + }) => { + const warningBanner = page.getByTestId("configuration-warnings-banner"); + await expect(warningBanner).toBeVisible(); + await expect( + warningBanner.getByText("3 warnings in configuration file"), + ).toBeVisible(); + }); + + test("should be able to open the modal and see all warnings", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("configuration-warnings-modal-trigger").click(); + await expect( + page.getByTestId("configuration-warnings-modal"), + ).toBeVisible(); + await expect(page.locator("li")).toHaveCount(3); + }); + }); + + test.describe("ignored", () => { + test("should display a banner", async ({ authenticatedPage: page }) => { + await page.goto( + "/version/spruce_e695f654c8b4b959d3e12e71696c3e318bcd4c33", + ); + await expect(page.getByTestId("ignored-banner")).toBeVisible(); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/breadcrumbs.spec.ts b/apps/spruce/playwright/tests/version/breadcrumbs.spec.ts index 7782937b02..b7bb53dae1 100644 --- a/apps/spruce/playwright/tests/version/breadcrumbs.spec.ts +++ b/apps/spruce/playwright/tests/version/breadcrumbs.spec.ts @@ -14,8 +14,9 @@ test.describe("Viewing a patch requester", () => { test("Clicking on the display task breadcrumb should take you to that task", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("bc-display-task")).toContainText("asdf"); - await page.getByTestId("bc-display-task").click(); + const breadcrumb = page.getByTestId("bc-display-task"); + await expect(breadcrumb).toContainText("asdf"); + await breadcrumb.click(); await expect(page).toHaveURL(new RegExp(`/task/${displayTaskId}`)); }); @@ -37,10 +38,9 @@ test.describe("Viewing a patch requester", () => { test("Clicking on the patch name breadcrumb from a task should take you to that version", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("bc-message")).toContainText( - "Patch 1251 - dist", - ); - await page.getByTestId("bc-message").click(); + const breadcrumb = page.getByTestId("bc-message"); + await expect(breadcrumb).toContainText("Patch 1251 - dist"); + await breadcrumb.click(); await expect(page).toHaveURL( "/version/5ecedafb562343215a7ff297/tasks?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC", ); @@ -65,10 +65,9 @@ test.describe("Viewing a mainline commit requester", () => { test("Clicking the commit message breadcrumb from a task should take you to that version", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("bc-message")).toContainText( - "5e823e1 - 'ever…reen/pull/3186)", - ); - await page.getByTestId("bc-message").click(); + const breadcrumb = page.getByTestId("bc-message"); + await expect(breadcrumb).toContainText("5e823e1 - 'ever…reen/pull/3186)"); + await breadcrumb.click(); await expect(page).toHaveURL( "/version/5e4ff3abe3c3317e352062e4/tasks?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC", ); @@ -77,8 +76,9 @@ test.describe("Viewing a mainline commit requester", () => { test("Clicking on the commits link should take you to that versions waterfall", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("bc-waterfall")).toContainText("evergreen"); - await page.getByTestId("bc-waterfall").click(); + const breadcrumb = page.getByTestId("bc-waterfall"); + await expect(breadcrumb).toContainText("evergreen"); + await breadcrumb.click(); await expect(page).toHaveURL("/project/evergreen/waterfall"); }); }); diff --git a/apps/spruce/playwright/tests/version/downstream_projects.spec.ts b/apps/spruce/playwright/tests/version/downstream_projects.spec.ts new file mode 100644 index 0000000000..b1dea3467e --- /dev/null +++ b/apps/spruce/playwright/tests/version/downstream_projects.spec.ts @@ -0,0 +1,53 @@ +import { test, expect } from "../../fixtures"; + +const DOWNSTREAM_ROUTE = + "/version/5f74d99ab2373627c047c5e5/downstream-projects"; + +test.describe("Downstream Projects Tab", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(DOWNSTREAM_ROUTE); + }); + + test("shows number of failed patches in the Downstream tab label", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("downstream-tab-badge")).toBeVisible(); + await expect(page.getByTestId("downstream-tab-badge")).toContainText("1"); + }); + + test("renders child patches", async ({ authenticatedPage: page }) => { + await expect(page.getByTestId("project-accordion")).toHaveCount(3); + await expect(page.getByTestId("project-title")).toHaveCount(3); + await expect(page.getByTestId("downstream-tasks-table")).toHaveCount(3); + }); + + test("links to base commit", async ({ authenticatedPage: page }) => { + await page.getByTestId("accordion-toggle").first().click(); + await expect( + page.getByTestId("downstream-base-commit").first(), + ).toHaveAttribute( + "href", + /\/version\/logkeeper_e3579537e848d14f0c3e5c25ef745fd0f10702d4/, + ); + }); + + test("filters by task name but doesn't update URL", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("task-name-filter").nth(1).click(); + const taskInput = page + .getByTestId("task-name-filter-wrapper") + .locator("input"); + await taskInput.focus(); + await taskInput.fill("generate-lint"); + await taskInput.press("Enter"); + await expect(page).not.toHaveURL(/generate-lint/); + await expect(page.getByText("generate-lint")).toBeVisible(); + }); + + test("does not push query params to the URL", async ({ + authenticatedPage: page, + }) => { + await expect(page).toHaveURL(new RegExp(DOWNSTREAM_ROUTE)); + }); +}); diff --git a/apps/spruce/playwright/tests/version/name_change_modal.spec.ts b/apps/spruce/playwright/tests/version/name_change_modal.spec.ts new file mode 100644 index 0000000000..44c7b163bb --- /dev/null +++ b/apps/spruce/playwright/tests/version/name_change_modal.spec.ts @@ -0,0 +1,51 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; + +test.describe("Name change modal", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/version/5f74d99ab2373627c047c5e5"); + }); + + test("Use the name change modal to change the name of a patch", async ({ + authenticatedPage: page, + }) => { + const originalName = "main: EVG-7823 add a commit queue message (#4048)"; + await expect(page.getByText(originalName)).toBeVisible(); + + await page.getByTestId("name-change-modal-trigger").click(); + const newName = "a different name"; + const nameInput = page.locator("textarea"); + await nameInput.clear(); + await nameInput.fill(newName); + await page.getByRole("button", { name: "Confirm" }).click(); + await expect(nameInput).toBeHidden(); + await expect(page.getByText(newName)).toBeVisible(); + await validateToast( + page, + "success", + "Patch name was successfully updated.", + ); + }); + + test("The confirm button is disabled when the text area value is empty or greater than 300 characters", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("name-change-modal-trigger").click(); + const nameInput = page.locator("textarea"); + const confirmButton = page.getByRole("button", { name: "Confirm" }); + + await nameInput.clear(); + await expect(confirmButton).toBeDisabled(); + + await nameInput.fill("lol"); + await expect(confirmButton).toBeEnabled(); + + const over300Chars = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + await nameInput.fill(over300Chars); + await expect(confirmButton).toBeDisabled(); + await expect( + page.getByText("Value cannot exceed 300 characters"), + ).toBeVisible(); + }); +}); diff --git a/apps/spruce/playwright/tests/version/page_tabs.spec.ts b/apps/spruce/playwright/tests/version/page_tabs.spec.ts index f63d582fae..6736030f0b 100644 --- a/apps/spruce/playwright/tests/version/page_tabs.spec.ts +++ b/apps/spruce/playwright/tests/version/page_tabs.spec.ts @@ -3,9 +3,9 @@ import { test, expect } from "../../fixtures"; const versionId = "5ecedafb562343215a7ff297"; const versionRoute = `/version/${versionId}`; const versions = { - changes: { route: `${versionRoute}/changes`, btn: "changes-tab" }, - tasks: { route: `${versionRoute}/tasks`, btn: "task-tab" }, - duration: { route: `${versionRoute}/task-duration`, btn: "duration-tab" }, + changes: { route: `${versionRoute}/changes`, name: "Changes" }, + tasks: { route: `${versionRoute}/tasks`, name: "Tasks" }, + duration: { route: `${versionRoute}/task-duration`, name: "Duration" }, }; test.describe("page tabs", () => { @@ -13,10 +13,9 @@ test.describe("page tabs", () => { authenticatedPage: page, }) => { await page.goto(versionRoute); - await expect(page.getByTestId(versions.tasks.btn)).toHaveAttribute( - "aria-selected", - "true", - ); + await expect( + page.getByRole("tab", { name: versions.tasks.name }), + ).toHaveAttribute("aria-selected", "true"); await expect(page).toHaveURL( `${versions.tasks.route}?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC`, ); @@ -26,10 +25,9 @@ test.describe("page tabs", () => { authenticatedPage: page, }) => { await page.goto(`${versionRoute}/task-duration`); - await expect(page.getByTestId(versions.duration.btn)).toHaveAttribute( - "aria-selected", - "true", - ); + await expect( + page.getByRole("tab", { name: versions.duration.name }), + ).toHaveAttribute("aria-selected", "true"); await expect(page).toHaveURL( `${versions.duration.route}?sorts=DURATION%3ADESC`, ); @@ -39,13 +37,11 @@ test.describe("page tabs", () => { authenticatedPage: page, }) => { await page.goto(`${versionRoute}/changes`); - await expect(page.getByTestId(versions.changes.btn)).toHaveAttribute( - "aria-selected", - "true", - ); + await expect( + page.getByRole("tab", { name: versions.changes.name }), + ).toHaveAttribute("aria-selected", "true"); await expect(page).toHaveURL(versions.changes.route); - - await page.getByTestId("task-tab").first().click(); + await page.getByRole("tab", { name: versions.tasks.name }).first().click(); await expect(page).toHaveURL( `${versions.tasks.route}?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC`, ); @@ -56,8 +52,7 @@ test.describe("page tabs", () => { }) => { await page.goto(versionRoute); await expect(page).toHaveURL(/sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC/); - - await page.getByTestId(versions.changes.btn).click(); + await page.getByRole("tab", { name: versions.changes.name }).click(); await expect(page).toHaveURL( `${versions.changes.route}?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC`, ); @@ -76,9 +71,9 @@ test.describe("page tabs", () => { authenticatedPage: page, }) => { await page.goto(versionRoute); - await page.getByTestId(versions.changes.btn).click(); + await page.getByRole("tab", { name: versions.changes.name }).click(); await expect(page.getByTestId("code-changes")).toBeVisible(); - await page.getByTestId(versions.tasks.btn).click(); + await page.getByRole("tab", { name: versions.tasks.name }).click(); await expect(page.getByTestId("total-count")).toBeVisible(); }); }); diff --git a/apps/spruce/playwright/tests/version/restart_modal.spec.ts b/apps/spruce/playwright/tests/version/restart_modal.spec.ts new file mode 100644 index 0000000000..bced8ec902 --- /dev/null +++ b/apps/spruce/playwright/tests/version/restart_modal.spec.ts @@ -0,0 +1,150 @@ +import { test, expect } from "../../fixtures"; +import { clickCheckbox, validateToast } from "../../helpers"; + +const path = "/version/5ecedafb562343215a7ff297"; + +test.describe("version/restart_modal", () => { + test.describe("Restarting a patch with Downstream Tasks", () => { + test("Clicking on the Select Downstream Tasks should show the downstream projects", async ({ + authenticatedPage: page, + }) => { + await page.goto("/version/5f74d99ab2373627c047c5e5"); + await page.getByTestId("restart-version").click(); + await page + .getByTestId("select-downstream") + .getByText("evergreen") + .first() + .click(); + }); + }); + + test.describe("Restarting a patch", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(path); + await expect(page.getByTestId("version-restart-modal")).toBeHidden(); + await page.getByTestId("restart-version").click(); + await expect(page.getByTestId("version-restart-modal")).toBeVisible(); + }); + + test("Clicking on a variant should toggle an accordion dropdown of tasks", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("variant-accordion").first().click(); + await expect( + page.getByTestId("version-restart-modal").getByText("dist"), + ).toBeVisible(); + }); + + test("Clicking on a variant checkbox should toggle its textbox and all the associated tasks", async ({ + authenticatedPage: page, + }) => { + const taskStatusBadge = page + .getByTestId("version-restart-modal") + .getByTestId("task-status-badge"); + await page.getByTestId("variant-accordion").first().click(); + await expect(taskStatusBadge).toContainText("0 of 1 Selected"); + const selectUbuntuCheckbox = page + .getByTestId("version-restart-modal") + .getByRole("checkbox", { name: "Ubuntu 16.04" }); + await clickCheckbox(selectUbuntuCheckbox); + await expect(taskStatusBadge).toContainText("1 of 1 Selected"); + await clickCheckbox(selectUbuntuCheckbox); + await expect(taskStatusBadge).toContainText("0 of 1 Selected"); + }); + + test("Clicking on a task should toggle its check box and select the task", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("variant-accordion").first().click(); + const taskCheckbox = page + .getByTestId("version-restart-modal") + .getByRole("checkbox", { name: "dist" }); + await clickCheckbox(taskCheckbox); + await expect( + page + .getByTestId("version-restart-modal") + .getByTestId("task-status-badge"), + ).toContainText("1 of 1 Selected"); + }); + + test("Selecting on the task status filter should toggle the tasks that have matching statuses to it", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("task-status-filter").click(); + const options = page.getByTestId("tree-select-options"); + await expect(options).toBeVisible(); + + const allCheckbox = options.getByRole("checkbox", { name: "All" }); + await clickCheckbox(allCheckbox); + await page.getByTestId("task-status-filter").click(); + + await expect(page.getByTestId("version-restart-modal")).toContainText( + "Are you sure you want to restart the 1 selected tasks?", + ); + await page.getByTestId("task-status-filter").click(); + await expect(options).toBeVisible(); + await clickCheckbox(allCheckbox); + await page.getByTestId("task-status-filter").click(); + }); + + test("Selecting on the base status filter should toggle the tasks that have matching statuses to it", async ({ + authenticatedPage: page, + }) => { + const modal = page.getByTestId("version-restart-modal"); + await modal.getByTestId("base-task-status-filter").click(); + const options = page.getByTestId("tree-select-options"); + await expect(options).toBeVisible(); + const succeededCheckbox = options.getByRole("checkbox", { + name: "Succeeded", + }); + await clickCheckbox(succeededCheckbox); + await modal.getByTestId("base-task-status-filter").click(); + + await expect(modal.getByTestId("confirmation-message")).toContainText( + "Are you sure you want to restart the 1 selected tasks?", + ); + await modal.getByTestId("base-task-status-filter").click(); + await expect(options).toBeVisible(); + await clickCheckbox(succeededCheckbox); + await modal.getByTestId("base-task-status-filter").click(); + }); + + test("Restarting a task should close the modal and display a success message if it occurs successfully.", async ({ + authenticatedPage: page, + }) => { + const modal = page.getByTestId("version-restart-modal"); + await modal.getByTestId("variant-accordion").first().click(); + const taskCheckbox = page + .getByTestId("version-restart-modal") + .getByRole("checkbox", { name: "dist" }); + await clickCheckbox(taskCheckbox); + await modal.getByRole("button", { name: "Restart" }).click(); + await expect(page.getByTestId("version-restart-modal")).toBeHidden(); + await validateToast(page, "success", "Successfully restarted tasks!"); + }); + }); + + test.describe("Restarting mainline commits", () => { + test("should be able to restart scheduled mainline commit tasks", async ({ + authenticatedPage: page, + }) => { + await page.goto( + "/version/spruce_ab494436448fbb1d244833046ea6f6af1544e86d", + ); + const restartButton = page.getByRole("button", { name: "Restart" }); + await expect(restartButton).toBeEnabled(); + await restartButton.click(); + await expect(page.getByTestId("version-restart-modal")).toBeVisible(); + const modal = page.getByTestId("version-restart-modal"); + await modal.getByTestId("accordion-toggle").click(); + const taskCheckbox = modal.getByRole("checkbox", { + name: "check_codegen", + }); + await clickCheckbox(taskCheckbox); + const confirmButton = modal.getByRole("button", { name: "Restart" }); + await expect(confirmButton).toHaveAttribute("aria-disabled", "false"); + await confirmButton.click(); + await validateToast(page, "success", "Successfully restarted tasks!"); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/routes.spec.ts b/apps/spruce/playwright/tests/version/routes.spec.ts new file mode 100644 index 0000000000..4f7cca30a8 --- /dev/null +++ b/apps/spruce/playwright/tests/version/routes.spec.ts @@ -0,0 +1,201 @@ +import { test, expect } from "../../fixtures"; + +const versions = { + 0: "5ecedafb562343215a7ff297", + 2: "52a630633ff1227909000021", + 4: "evergreen_33016573166a36bd5f46b4111151899d5c4e95b1", + 5: "5e4ff3abe3c3317e352062e4", +}; + +const versionRoute = (id: string) => `/version/${id}`; + +test.describe("Version route", () => { + test.describe("Metadata", () => { + test("Shows patch parameters if they exist", async ({ + authenticatedPage: page, + }) => { + await page.goto(versionRoute(versions[0])); + await expect(page.getByTestId("parameters-modal")).toHaveCount(0); + await page.getByTestId("parameters-link").click(); + await expect(page.getByTestId("parameters-modal")).toBeVisible(); + await page.getByRole("button", { name: "Close modal" }).click(); + await expect(page.getByTestId("parameters-modal")).toBeHidden(); + }); + + test("'Base commit' link in metadata links to version page", async ({ + authenticatedPage: page, + }) => { + await page.goto(versionRoute(versions[0])); + await expect(page.getByTestId("patch-base-commit")).toHaveAttribute( + "href", + new RegExp(`/version/${versions[4]}`), + ); + }); + + test("Doesn't show patch parameters if they don't exist", async ({ + authenticatedPage: page, + }) => { + await page.goto(versionRoute(versions[2])); + await expect(page.getByTestId("parameters-link")).toHaveCount(0); + await expect(page.getByTestId("parameters-modal")).toHaveCount(0); + }); + }); + + test.describe("Build Variants", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionRoute(versions[0])); + const table = page.getByTestId("tasks-table"); + await expect(table).toBeVisible(); + await expect(table).toHaveAttribute("data-loading", "false"); + }); + + test("Lists the patch's build variants", async ({ + authenticatedPage: page, + }) => { + await expect( + page + .getByTestId("build-variants") + .getByTestId("patch-build-variant") + .first(), + ).toBeVisible(); + }); + + test.describe("Grouped Task Status Badge", () => { + test("Shows tooltip with task's name on hover", async ({ + authenticatedPage: page, + }) => { + const statusBadge = page + .getByTestId("build-variants") + .getByTestId("grouped-task-status-badge") + .first(); + await statusBadge.hover(); + await expect(statusBadge).toContainText("1Succeeded"); + }); + + test("Navigates to task tab and applies filters when clicking on grouped task status badge", async ({ + authenticatedPage: page, + }) => { + const changesTab = page.getByRole("tab", { name: "Changes" }); + const tasksTab = page.getByRole("tab", { name: "Tasks" }); + + await changesTab.first().click(); + await expect(tasksTab).toHaveAttribute("aria-selected", "false"); + + await page + .getByTestId("build-variants") + .getByTestId("grouped-task-status-badge") + .first() + .click(); + await expect(tasksTab).toHaveAttribute("aria-selected", "true"); + await expect(page).toHaveURL( + /sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24/, + ); + + await page.getByTestId("status-filter").click(); + await expect(page.getByTestId("status-filter-wrapper")).toBeVisible(); + await expect(page.getByLabel("Succeeded")).toHaveAttribute( + "aria-checked", + "true", + ); + + await page.getByTestId("variant-filter").click(); + await expect( + page.getByTestId("variant-filter-wrapper").locator("input"), + ).toHaveValue("^ubuntu1604$"); + }); + + test("Keeps sorts but not other filters when clicking on grouped task status badge", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("clear-all-filters").click(); + + await page.getByTestId("task-name-filter").click(); + const taskNameInput = page + .getByTestId("task-name-filter-wrapper") + .locator("input"); + await taskNameInput.focus(); + await taskNameInput.fill("a-task-name"); + await taskNameInput.press("Enter"); + + await page + .getByTestId("build-variants") + .getByTestId("grouped-task-status-badge") + .first() + .click(); + await expect(page).toHaveURL( + /sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=success&variant=%5Eubuntu1604%24/, + ); + }); + }); + + test.describe("Build Variant Name", () => { + test("Navigates to task tab and applies filters when clicking on build variant name", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("clear-all-filters").click(); + + const changesTab = page.getByRole("tab", { name: "Changes" }); + const tasksTab = page.getByRole("tab", { name: "Tasks" }); + + await changesTab.first().click(); + await expect(tasksTab).toHaveAttribute("aria-selected", "false"); + + await page.getByTestId("build-variant-display-name").first().click(); + await expect(tasksTab).toHaveAttribute("aria-selected", "true"); + await expect(page).toHaveURL( + /sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&variant=%5Eubuntu1604%24/, + ); + + await page.getByTestId("variant-filter").click(); + await expect( + page.getByTestId("variant-filter-wrapper").locator("input"), + ).toHaveValue("^ubuntu1604$"); + }); + + test("Keeps sorts but not other filters when clicking on build variant name", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("clear-all-filters").click(); + await page.getByTestId("task-name-filter").click(); + const taskNameInput = page + .getByTestId("task-name-filter-wrapper") + .locator("input"); + await taskNameInput.focus(); + await taskNameInput.fill("a-task-name"); + await taskNameInput.press("Enter"); + + await page.getByTestId("build-variant-display-name").first().click(); + await expect(page).toHaveURL( + /sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&variant=%5Eubuntu1604%24/, + ); + }); + }); + }); + + test.describe("Page title", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(versionRoute(versions[5])); + }); + + test("Should include a link to Jira", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByTestId("page-title").getByRole("link", { name: "EVG-7425" }), + ).toHaveAttribute("href", "https://jira.example.com/browse/EVG-7425"); + }); + + test("Should include a link to GitHub", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByTestId("page-title").getByRole("link", { + name: "https://github.com/evergreen-ci/evergreen/pull/3186", + }), + ).toHaveAttribute( + "href", + "https://github.com/evergreen-ci/evergreen/pull/3186", + ); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/schedule_modal.spec.ts b/apps/spruce/playwright/tests/version/schedule_modal.spec.ts new file mode 100644 index 0000000000..129baeb6cb --- /dev/null +++ b/apps/spruce/playwright/tests/version/schedule_modal.spec.ts @@ -0,0 +1,26 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; + +test.describe("Restarting and scheduling mainline commits", () => { + test("should be able to schedule inactive mainline commit tasks", async ({ + authenticatedPage: page, + }) => { + await page.goto("/version/spruce_e695f654c8b4b959d3e12e71696c3e318bcd4c33"); + + const schedulePatchButton = page.getByRole("button", { name: "Schedule" }); + await expect(schedulePatchButton).toBeVisible(); + await expect(schedulePatchButton).toBeEnabled(); + await schedulePatchButton.click(); + + const modal = page.getByTestId("schedule-tasks-modal"); + await expect(modal).toBeVisible(); + await modal.getByTestId("accordion-toggle").click(); + const taskCheckbox = modal.getByText("check_codegen"); + await taskCheckbox.click(); + + const confirmButton = modal.getByRole("button", { name: "Schedule" }); + await expect(confirmButton).toHaveAttribute("aria-disabled", "false"); + await confirmButton.click(); + await validateToast(page, "success", "Successfully scheduled tasks!"); + }); +}); diff --git a/apps/spruce/playwright/tests/version/subscription_modal.spec.ts b/apps/spruce/playwright/tests/version/subscription_modal.spec.ts index 649d847235..991185c5fe 100644 --- a/apps/spruce/playwright/tests/version/subscription_modal.spec.ts +++ b/apps/spruce/playwright/tests/version/subscription_modal.spec.ts @@ -171,4 +171,104 @@ test.describe("Version Subscription Modal", () => { await context.clearCookies(); }); + + test.describe("Regex selector inputs", () => { + test("Can add and remove regex selectors", async ({ + authenticatedPage: page, + }) => { + await openSubscriptionModal(page); + await selectOption( + page, + "Event", + "A build-variant in this version finishes", + ); + await expect(page.getByTestId("regex-selector-row")).toHaveCount(0); + const addCriteriaButton = page.getByRole("button", { + name: "Add additional criteria", + }); + await addCriteriaButton.click(); + await expect(page.getByTestId("regex-selector-row")).toHaveCount(1); + await page.getByTestId("delete-item-button").first().click(); + await expect(page.getByTestId("regex-selector-row")).toHaveCount(0); + }); + + test("Selecting a regex selector type will disable that option in other regex selector type dropdowns", async ({ + authenticatedPage: page, + }) => { + await openSubscriptionModal(page); + await selectOption( + page, + "Event", + "A build-variant in this version finishes", + ); + const addCriteriaButton = page.getByRole("button", { + name: "Add additional criteria", + }); + await addCriteriaButton.click(); + await expect(page.getByText("Build Variant ID")).toBeVisible(); + await addCriteriaButton.click(); + await expect(page.getByText("Build Variant Name")).toBeVisible(); + await page.getByTestId("regex-select").last().click(); + await expect( + page.locator("#regex-select-menu").getByText("Build Variant ID"), + ).toHaveCSS("cursor", "not-allowed"); + }); + + test("Regex selectors are optional for triggers that offer them", async ({ + authenticatedPage: page, + }) => { + await openSubscriptionModal(page); + await selectOption( + page, + "Event", + "A build-variant in this version finishes", + ); + await page.getByTestId("jira-comment-input").fill("EVG-2000"); + await expect(page.getByRole("button", { name: "Save" })).toHaveAttribute( + "aria-disabled", + "false", + ); + await page.getByRole("button", { name: "Save" }).click(); + await validateToast(page, "success", "Your subscription has been added"); + }); + + test("Display success toast after submitting a valid form with regex selectors inputs and request succeeds", async ({ + authenticatedPage: page, + }) => { + await openSubscriptionModal(page); + await selectOption( + page, + "Event", + "A build-variant in this version finishes", + ); + const addCriteriaButton = page.getByRole("button", { + name: "Add additional criteria", + }); + await addCriteriaButton.click(); + await selectOption(page, "Field name", "Build Variant Name"); + await page.getByTestId("regex-input").fill("stuff"); + await page.getByTestId("jira-comment-input").fill("EVG-2000"); + await page.getByRole("button", { name: "Save" }).click(); + await validateToast(page, "success", "Your subscription has been added"); + }); + + test("'Add Additional Criteria' button should not appear when there are enough 'Field name' dropdowns to represent all possible regex selector types for a trigger", async ({ + authenticatedPage: page, + }) => { + await openSubscriptionModal(page); + await selectOption( + page, + "Event", + "A build-variant in this version finishes", + ); + const addCriteriaButton = page.getByRole("button", { + name: "Add additional criteria", + }); + await expect(addCriteriaButton).toHaveAttribute("aria-disabled", "false"); + await addCriteriaButton.click(); + await expect(addCriteriaButton).toHaveAttribute("aria-disabled", "false"); + await addCriteriaButton.click(); + await expect(addCriteriaButton).toBeHidden(); + }); + }); }); diff --git a/apps/spruce/playwright/tests/version/task_duration.spec.ts b/apps/spruce/playwright/tests/version/task_duration.spec.ts new file mode 100644 index 0000000000..51caae8762 --- /dev/null +++ b/apps/spruce/playwright/tests/version/task_duration.spec.ts @@ -0,0 +1,154 @@ +import { clickCheckbox } from "@evg-ui/playwright-config/helpers"; +import { test, expect } from "../../fixtures"; + +test.describe("Task Duration Tab", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/version/5e4ff3abe3c3317e352062e4/task-duration"); + }); + + test.describe("when interacting with the filters on the page", () => { + test("updates URL appropriately when task name filter is applied", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("task-name-filter-popover").click(); + const filterInput = page.getByPlaceholder("Task name regex"); + await filterInput.fill("test-annotation"); + await filterInput.press("Enter"); + await expect(page.getByTestId("task-duration-table-row")).toHaveCount(1); + await expect(page).toHaveURL( + /page=0&sorts=DURATION%3ADESC&taskName=test-annotation/, + ); + + await page.getByTestId("task-name-filter-popover").click(); + await filterInput.clear(); + await filterInput.press("Enter"); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/task-duration?page=0&sorts=DURATION%3ADESC", + ); + }); + + test("updates URL appropriately when status filter is applied", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("status-filter-popover").click(); + const options = page.getByTestId("tree-select-options"); + await expect(options).toBeVisible(); + + const runningCheckbox = options + .getByRole("checkbox", { + name: "Running", + }) + .first(); + await clickCheckbox(runningCheckbox); + + await expect(page.getByTestId("task-duration-table-row")).toHaveCount(3); + await expect(page).toHaveURL( + /statuses=running-umbrella,started,dispatched/, + ); + await expect(options).toBeVisible(); + + const succeededCheckbox = options.getByRole("checkbox", { + name: "Succeeded", + }); + await clickCheckbox(succeededCheckbox); + await expect(page).toHaveURL( + /statuses=running-umbrella,started,dispatched,success/, + ); + }); + + test("updates URL appropriately when build variant filter is applied", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("build-variant-filter-popover").click(); + const filterInput = page.getByPlaceholder("Build variant regex"); + await filterInput.fill("Lint"); + await filterInput.press("Enter"); + await expect(page.getByTestId("task-duration-table-row")).toHaveCount(2); + await expect(page).toHaveURL(/page=0&sorts=DURATION%3ADESC&variant=Lint/); + + await page.getByTestId("build-variant-filter-popover").click(); + await filterInput.clear(); + await filterInput.press("Enter"); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/task-duration?page=0&sorts=DURATION%3ADESC", + ); + }); + + test("updates URL appropriately when sort is changing", async ({ + authenticatedPage: page, + }) => { + const durationSortControl = page.getByRole("button", { + name: "Sort by Task Duration", + }); + const statusSortControl = page.getByRole("button", { + name: "Sort by Status", + }); + const variantSortControl = page.getByRole("button", { + name: "Sort by Build Variant", + }); + + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/task-duration?sorts=DURATION%3ADESC", + ); + const longestTask = "test-thirdparty"; + await expect(page.getByText(longestTask)).toBeVisible(); + await expect( + page.getByTestId("task-duration-table-row").first(), + ).toContainText(longestTask); + + await durationSortControl.click(); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/task-duration?page=0", + ); + await durationSortControl.click(); + await expect(page).toHaveURL(/sorts=DURATION%3AASC/); + const shortestTask = "clone_test-model"; + await expect(page.getByText(shortestTask)).toBeVisible(); + await expect( + page.getByTestId("task-duration-table-row").first(), + ).toContainText(shortestTask); + + await statusSortControl.click(); + await expect(page).toHaveURL( + /page=0&sorts=DURATION%3AASC%3BSTATUS%3AASC/, + ); + await statusSortControl.click(); + await expect(page).toHaveURL( + /page=0&sorts=DURATION%3AASC%3BSTATUS%3ADESC/, + ); + await variantSortControl.click(); + await expect(page).toHaveURL( + /page=0&sorts=DURATION%3AASC%3BSTATUS%3ADESC/, + ); + }); + + test("clearing all filters resets to the default sort", async ({ + authenticatedPage: page, + }) => { + const durationSortControl = page.getByRole("button", { + name: "Sort by Task Duration", + }); + await durationSortControl.click(); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/task-duration?page=0", + ); + await durationSortControl.click(); + await expect(page).toHaveURL(/sorts=DURATION%3AASC/); + await page.getByText("Clear all filters").click(); + await expect(page).toHaveURL(/sorts=DURATION%3ADESC/); + }); + + test("shows message when no test results are found", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("task-name-filter-popover").click(); + const filterInput = page.getByPlaceholder("Task name regex"); + await filterInput.fill("this_does_not_exist"); + await filterInput.press("Enter"); + await expect( + page.getByTestId("task-name-filter-popover-task-duration-table-row"), + ).toHaveCount(0); + await expect(page.getByText("No tasks found.")).toBeVisible(); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/task_filters.spec.ts b/apps/spruce/playwright/tests/version/task_filters.spec.ts new file mode 100644 index 0000000000..e46e9ca1fb --- /dev/null +++ b/apps/spruce/playwright/tests/version/task_filters.spec.ts @@ -0,0 +1,229 @@ +import { Page } from "@playwright/test"; +import { test, expect } from "../../fixtures"; +import { clickCheckbox } from "../../helpers"; + +const patch = { id: "5e4ff3abe3c3317e352062e4" }; +const pathTasks = `/version/${patch.id}/tasks`; +const pathURLWithFilters = `${pathTasks}?page=0&sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC&statuses=failed,success,running-umbrella,dispatched,started&taskName=test-thirdparty&variant=ubuntu`; +const defaultPath = `${pathTasks}?sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC`; + +const waitForTaskTable = async (page: Page) => { + const table = page.getByTestId("tasks-table"); + await expect(table).toBeVisible(); + await expect(table).toHaveAttribute("data-loading", "false"); +}; + +test.describe("Tasks filters", () => { + test("Should clear any filters with the Clear All Filters button and reset the table to its default state", async ({ + authenticatedPage: page, + }) => { + await page.goto(pathURLWithFilters); + await waitForTaskTable(page); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + + await page.getByTestId("clear-all-filters").click(); + await expect(page).toHaveURL(`${defaultPath}`); + + await page.getByTestId("task-name-filter").click(); + await expect( + page.getByTestId("task-name-filter-wrapper").locator("input"), + ).toHaveValue(""); + + await page.getByTestId("status-filter").click(); + const statusCheckboxes = page + .getByTestId("status-filter-wrapper") + .locator('input[type="checkbox"]'); + + for (const checkbox of await statusCheckboxes.all()) { + await expect(checkbox).not.toBeChecked(); + } + + await page.getByTestId("base-status-filter").click(); + const baseStatusCheckboxes = page + .getByTestId("base-status-filter-wrapper") + .locator('input[type="checkbox"]'); + + for (const checkbox of await baseStatusCheckboxes.all()) { + await expect(checkbox).not.toBeChecked(); + } + + await page.getByTestId("variant-filter").click(); + await expect( + page.getByTestId("variant-filter-wrapper").locator("input"), + ).toHaveValue(""); + }); + + test.describe("Variant input field", () => { + const variantInputValue = "lint"; + + test("Updates url with input value and fetches tasks filtered by variant", async ({ + authenticatedPage: page, + }) => { + await page.goto(defaultPath); + await waitForTaskTable(page); + await page.getByTestId("variant-filter").click(); + + const variantInput = page + .getByTestId("variant-filter-wrapper") + .locator("input"); + await variantInput.focus(); + await variantInput.fill(variantInputValue); + await variantInput.press("Enter"); + await expect(page.getByTestId("variant-filter-wrapper")).toHaveCount(0); + await expect(page).toHaveURL(new RegExp(variantInputValue)); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toContainText("2"); + + await page.getByTestId("variant-filter").click(); + await variantInput.focus(); + await variantInput.clear(); + await variantInput.press("Enter"); + await expect(page.getByTestId("variant-filter-wrapper")).toHaveCount(0); + await expect(page).not.toHaveURL(/variant=/); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toContainText("47"); + }); + }); + + test.describe("Task name input field", () => { + const taskNameInputValue = "test-cloud"; + + test("Updates url with input value and fetches tasks filtered by task name", async ({ + authenticatedPage: page, + }) => { + await page.goto(defaultPath); + await waitForTaskTable(page); + await page.getByTestId("task-name-filter").click(); + + const taskNameInput = page + .getByTestId("task-name-filter-wrapper") + .locator("input"); + await taskNameInput.focus(); + await taskNameInput.fill(taskNameInputValue); + await taskNameInput.press("Enter"); + await expect(page.getByTestId("task-name-filter-wrapper")).toHaveCount(0); + await expect(page).toHaveURL(new RegExp(taskNameInputValue)); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toContainText("1"); + + await page.getByTestId("task-name-filter").click(); + await taskNameInput.focus(); + await taskNameInput.clear(); + await taskNameInput.press("Enter"); + await expect(page.getByTestId("task-name-filter-wrapper")).toHaveCount(0); + await expect(page).not.toHaveURL(/taskName=/); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toContainText("47"); + }); + }); + + test.describe("Task Statuses select", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(defaultPath); + await waitForTaskTable(page); + await page.getByTestId("status-filter").click(); + await expect(page.getByTestId("tree-select-options")).toBeVisible(); + }); + + test("Clicking on a status filter filters the tasks to only those statuses", async ({ + authenticatedPage: page, + }) => { + const options = page.getByTestId("tree-select-options"); + const failedCheckbox = options + .getByRole("checkbox", { name: "Failed" }) + .first(); + await clickCheckbox(failedCheckbox); + await expect(page).toHaveURL(/statuses=failed/); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toHaveText("2"); + + const succeededCheckbox = options.getByRole("checkbox", { + name: "Succeeded", + }); + await clickCheckbox(succeededCheckbox); + await expect(page).toHaveURL( + /statuses=failed-umbrella,failed,known-issue,success/, + ); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).not.toHaveText("2"); + }); + + test("Clicking on 'All' checkbox adds all the statuses and clicking again removes them", async ({ + authenticatedPage: page, + }) => { + const taskStatuses = [ + "All", + "Failed", + "Known Issue", + "Succeeded", + "Running", + "Dispatched", + "Blocked", + ]; + const allCheckbox = page.getByRole("checkbox", { name: "All" }); + await clickCheckbox(allCheckbox); + for (const label of taskStatuses) { + const checkbox = page.getByRole("checkbox", { name: label }).first(); + await expect(checkbox).toBeChecked(); + } + await expect(page).toHaveURL(/statuses=all/); + await waitForTaskTable(page); + + await clickCheckbox(allCheckbox); + for (const label of taskStatuses) { + const checkbox = page.getByRole("checkbox", { name: label }).first(); + await expect(checkbox).not.toBeChecked(); + } + await expect(page).not.toHaveURL(/statuses/); + }); + }); + + test.describe("Task Base Statuses select", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(defaultPath); + await waitForTaskTable(page); + await page.getByTestId("base-status-filter").click(); + await expect( + page.getByTestId("base-status-filter-wrapper"), + ).toBeVisible(); + }); + + test("Clicking on a base status filter filters the tasks to only those base statuses", async ({ + authenticatedPage: page, + }) => { + const succeededCheckbox = page.getByRole("checkbox", { + name: "Succeeded", + }); + await clickCheckbox(succeededCheckbox); + await expect(page).toHaveURL(/baseStatuses=success/); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toHaveText("44"); + + await clickCheckbox(succeededCheckbox); + await expect(page).not.toHaveURL(/baseStatuses/); + await waitForTaskTable(page); + await expect(page.getByTestId("filtered-count")).toHaveText("47"); + }); + + test("Clicking on 'All' checkbox adds all the base statuses and clicking again removes them", async ({ + authenticatedPage: page, + }) => { + const taskStatuses = ["All", "Succeeded", "Running"]; + const allCheckbox = page.getByRole("checkbox", { name: "All" }); + await clickCheckbox(allCheckbox); + for (const label of taskStatuses) { + await expect(page.getByRole("checkbox", { name: label })).toBeChecked(); + } + await expect(page).toHaveURL(/baseStatuses=all/); + await waitForTaskTable(page); + + await clickCheckbox(allCheckbox); + for (const label of taskStatuses) { + await expect( + page.getByRole("checkbox", { name: label }), + ).not.toBeChecked(); + } + await expect(page).not.toHaveURL(/baseStatuses/); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/task_table.spec.ts b/apps/spruce/playwright/tests/version/task_table.spec.ts new file mode 100644 index 0000000000..0f1ed059f6 --- /dev/null +++ b/apps/spruce/playwright/tests/version/task_table.spec.ts @@ -0,0 +1,239 @@ +import { Page } from "@playwright/test"; +import { SEEN_TASK_REVIEW_TOOLTIP } from "constants/cookies"; +import { test, expect } from "../../fixtures"; +import { clickCheckbox } from "../../helpers"; + +const pathTasks = "/version/5e4ff3abe3c3317e352062e4/tasks"; +const patchDescriptionTasksExist = "dist"; + +const waitForTaskTable = async (page: Page) => { + const table = page.getByTestId("tasks-table"); + await expect(table).toBeVisible(); + await expect(table).toHaveAttribute("data-loading", "false"); +}; + +test.describe("Task table", () => { + test("Loading skeleton does not persist when you navigate to Patch page from My Patches and adjust a filter", async ({ + authenticatedPage: page, + }) => { + await page.goto("/user/patches"); + await page + .getByTestId("patch-card-patch-link") + .filter({ hasText: patchDescriptionTasksExist }) + .click(); + await expect(page.getByTestId("tasks-table")).toBeVisible(); + }); + + test("Updates sorting in the url when column headers are clicked", async ({ + authenticatedPage: page, + }) => { + await page.goto(pathTasks); + await waitForTaskTable(page); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + await expect(page).toHaveURL(/sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC/); + + const nameSortControl = page.getByRole("button", { name: "Sort by Name" }); + const statusSortControl = page.getByRole("button", { + name: "Sort by Task Status", + }); + const baseStatusSortControl = page.getByRole("button", { + name: "Sort by Previous Status", + }); + const variantSortControl = page.getByRole("button", { + name: "Sort by Variant", + }); + + await nameSortControl.click(); + await expect(page).toHaveURL(/BASE_STATUS%3ADESC%3BNAME%3AASC/); + + await variantSortControl.click(); + await expect(page).toHaveURL(/sorts=NAME%3AASC%3BVARIANT%3AASC/); + + await statusSortControl.click(); + await expect(page).toHaveURL(/sorts=VARIANT%3AASC%3BSTATUS%3AASC/); + + await baseStatusSortControl.click(); + await expect(page).toHaveURL(/sorts=STATUS%3AASC%3BBASE_STATUS%3AASC/); + + await baseStatusSortControl.click(); + await expect(page).toHaveURL(/sorts=STATUS%3AASC%3BBASE_STATUS%3ADESC/); + + await baseStatusSortControl.click(); + await expect(page).toHaveURL(/sorts=STATUS%3AASC/); + }); + + test("Clicking task name goes to task page for that task", async ({ + authenticatedPage: page, + }) => { + await page.goto(pathTasks); + await expect( + page.getByTestId("tasks-table-row").first().locator("a").first(), + ).toHaveAttribute("href", /\/task/); + }); + + test("Task count displays total tasks", async ({ + authenticatedPage: page, + }) => { + await page.goto(pathTasks); + await expect(page.getByTestId("total-count").first()).toContainText("49"); + }); + + test.describe("Changing page number", () => { + test("Displays the next page of results and updates URL when right arrow is clicked and next page exists", async ({ + authenticatedPage: page, + }) => { + await page.goto(`${pathTasks}?page=0`); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + + const firstPageText = await page + .getByTestId("tasks-table-row") + .first() + .textContent(); + await page.getByTestId("next-page-button").click(); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + const secondPageText = page.getByTestId("tasks-table-row").first(); + await expect(secondPageText).not.toHaveText(firstPageText!); + }); + + test("Displays the previous page of results and updates URL when the left arrow is clicked and previous page exists", async ({ + authenticatedPage: page, + }) => { + await page.goto(`${pathTasks}?page=1`); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + const secondPageText = await page + .getByTestId("tasks-table-row") + .first() + .textContent(); + await page.getByTestId("prev-page-button").click(); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + const firstPageText = page.getByTestId("tasks-table-row").first(); + await expect(firstPageText).not.toHaveText(secondPageText!); + }); + + test("Does not update results or URL when left arrow is clicked and previous page does not exist", async ({ + authenticatedPage: page, + }) => { + await page.goto(`${pathTasks}?page=0`); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + await expect(page.getByTestId("prev-page-button")).toBeDisabled(); + }); + + test("Does not update results or URL when right arrow is clicked and next page does not exist", async ({ + authenticatedPage: page, + }) => { + await page.goto(`${pathTasks}?page=4`); + await expect(page.getByTestId("tasks-table-row").first()).toBeVisible(); + await expect(page.getByTestId("next-page-button")).toBeDisabled(); + }); + }); + + test.describe("blocked tasks", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(`${pathTasks}?limit=100`); + await waitForTaskTable(page); + }); + + test("shows the blocking tasks when hovering over status badge", async ({ + authenticatedPage: page, + }) => { + const dependsOnTooltip = page.getByTestId("depends-on-tooltip"); + await expect(dependsOnTooltip).toHaveCount(0); + await page.getByTestId("task-status-badge").getByText("Blocked").hover(); + await expect(dependsOnTooltip).toBeVisible(); + await expect(dependsOnTooltip).toContainText( + `Depends on tasks: “test-migrations”, “test-graphql”`, + ); + }); + }); + + test.describe("task review", () => { + const firstTask = + "reviewed-evergreen_ubuntu1604_test_service_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48"; + const displayTask = "reviewed-evergreen_ubuntu1604_89"; + const executionTask1 = "reviewed-exec1"; + const executionTask2 = "reviewed-exec2"; + + test("marks tasks as viewed and preserves their state on reload", async ({ + authenticatedPage: page, + }) => { + await page.goto(pathTasks); + const firstTaskCheckbox = page.getByTestId(firstTask); + await clickCheckbox(firstTaskCheckbox); + await expect(firstTaskCheckbox).toBeChecked(); + + await page.getByRole("button", { name: "Expand row" }).click(); + await expect(page.getByTestId(executionTask1)).toHaveAttribute( + "aria-disabled", + "true", + ); + + const executionTask2Checkbox = page.getByTestId(executionTask2); + await clickCheckbox(executionTask2Checkbox); + await expect(executionTask2Checkbox).toBeChecked(); + + const displayTaskCheckbox = page.getByTestId(displayTask); + await clickCheckbox(displayTaskCheckbox); + await expect(displayTaskCheckbox).not.toBeChecked(); + await expect(executionTask2Checkbox).not.toBeChecked(); + + await clickCheckbox(displayTaskCheckbox); + await expect(displayTaskCheckbox).toBeChecked(); + await expect(executionTask2Checkbox).toBeChecked(); + + await page.reload(); + + await expect(firstTaskCheckbox).toBeChecked(); + await expect(displayTaskCheckbox).toBeChecked(); + await page.getByRole("button", { name: "Expand row" }).click(); + await expect(executionTask2Checkbox).toBeChecked(); + }); + + test.describe("announcement tooltip", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.context().clearCookies({ name: SEEN_TASK_REVIEW_TOOLTIP }); + await page.goto(pathTasks); + await waitForTaskTable(page); + }); + + test("shows the announcement tooltip open on the first viewing", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByText("New feature: Task Review")).toBeVisible(); + }); + + test("shows the info icon one day after the initial close and hides it after one week", async ({ + authenticatedPage: page, + }) => { + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); + await page.context().addCookies([ + { + name: SEEN_TASK_REVIEW_TOOLTIP, + value: oneDayAgo.toString(), + domain: "localhost", + path: "/", + }, + ]); + await page.goto(pathTasks); + await expect(page.getByText("Reviewed")).toBeVisible(); + await expect(page.getByText("New feature: Task Review")).toBeHidden(); + await page.getByTestId("announcement-tooltip-trigger").click(); + + const eightDaysAgo = new Date(Date.now() - 8 * 24 * 60 * 60 * 1000); + await page.context().addCookies([ + { + name: SEEN_TASK_REVIEW_TOOLTIP, + value: eightDaysAgo.toString(), + domain: "localhost", + path: "/", + }, + ]); + await page.goto(pathTasks); + await expect(page.getByText("Reviewed")).toBeVisible(); + await expect(page.getByText("New feature: Task Review")).toBeHidden(); + await expect( + page.getByTestId("announcement-tooltip-trigger"), + ).toBeHidden(); + }); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/version/test_analysis.spec.ts b/apps/spruce/playwright/tests/version/test_analysis.spec.ts new file mode 100644 index 0000000000..d380e4c525 --- /dev/null +++ b/apps/spruce/playwright/tests/version/test_analysis.spec.ts @@ -0,0 +1,89 @@ +import { test, expect } from "../../fixtures"; + +test.describe("Test Analysis", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/version/5e4ff3abe3c3317e352062e4/test-analysis"); + }); + + test("should group together all matching failing tests in a version and present a stat", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByText("1 test failed across more than one task"), + ).toBeVisible(); + await expect(page.getByText("JustAFakeTestInALonelyWorld")).toBeVisible(); + await expect( + page.getByText("JustAnotherFakeFailingTestInALonelyWorld"), + ).toBeVisible(); + }); + + test("clicking on a test should show the test details", async ({ + authenticatedPage: page, + }) => { + await page.getByText("JustAFakeTestInALonelyWorld").click(); + await expect( + page.getByTestId("failed-test-grouped-table").first(), + ).toBeVisible(); + }); + + test("filtering by test name should only show matching tests", async ({ + authenticatedPage: page, + }) => { + await page + .getByLabel("Search Test Failures") + .fill("JustAFakeTestInALonelyWorld"); + await page.getByLabel("Search Test Failures").press("Enter"); + await expect( + page.getByText("1 test failed across more than one task"), + ).toBeVisible(); + await expect(page.getByText("JustAFakeTestInALonelyWorld")).toBeVisible(); + await expect( + page.getByText("JustAnotherFakeFailingTestInALonelyWorld"), + ).toBeHidden(); + }); + + test("filtering by task status should only show matching tests", async ({ + authenticatedPage: page, + }) => { + await page.getByLabel("Failure Type").click(); + await expect( + page.getByTestId("task-status-known-issue-option"), + ).toBeVisible(); + await page.getByTestId("task-status-known-issue-option").click(); + + await expect( + page.getByText("0 tests failed across more than one task"), + ).toBeVisible(); + await expect(page.getByText("JustAFakeTestInALonelyWorld")).toBeVisible(); + await expect( + page.getByText("JustAnotherFakeFailingTestInALonelyWorld"), + ).toBeHidden(); + }); + + test("clearing the filters should reset the view", async ({ + authenticatedPage: page, + }) => { + await page + .getByLabel("Search Test Failures") + .fill("JustAFakeTestInALonelyWorld"); + await page.getByLabel("Search Test Failures").press("Enter"); + await page.getByLabel("Failure Type").click(); + await expect( + page.getByTestId("task-status-known-issue-option"), + ).toBeVisible(); + await page.getByTestId("task-status-known-issue-option").click(); + await page.keyboard.press("Escape"); + + await expect( + page.getByText("0 tests failed across more than one task"), + ).toBeVisible(); + await expect(page.getByTestId("clear-filter-button")).not.toHaveAttribute( + "disabled", + ); + await page.getByTestId("clear-filter-button").click(); + await expect(page.getByLabel("Search Test Failures")).toHaveValue(""); + await expect( + page.getByText("1 test failed across more than one task"), + ).toBeVisible(); + }); +}); diff --git a/apps/spruce/playwright/tests/version/version_timing.spec.ts b/apps/spruce/playwright/tests/version/version_timing.spec.ts new file mode 100644 index 0000000000..352bb020d6 --- /dev/null +++ b/apps/spruce/playwright/tests/version/version_timing.spec.ts @@ -0,0 +1,180 @@ +import { test, expect } from "../../fixtures"; + +const chart = "[id^=reactgooglegraph]"; + +test.describe("Version Timing Tab without a variant selected", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto("/version/5e4ff3abe3c3317e352062e4/version-timing"); + }); + + test("shows a chart of all variants in the version", async ({ + authenticatedPage: page, + }) => { + await expect(page.locator(chart).getByText("Ubuntu 16.04")).toBeVisible(); + await expect(page.locator(chart).getByText("Race Detector")).toBeVisible(); + await expect(page.locator(chart).getByText("Lint")).toBeVisible(); + }); + + test("allows the user to select a variant and navigate to the variant timing view", async ({ + authenticatedPage: page, + }) => { + await page.locator(chart).getByText("Ubuntu 16.04").click(); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/version-timing?variant=%5Eubuntu1604%24", + ); + }); + + test("has disabled pagination functionality", async ({ + authenticatedPage: page, + }) => { + await expect( + page.locator("button[aria-labelledby='page-size-select']"), + ).toHaveAttribute("aria-disabled", "true"); + await expect(page.getByTestId("next-page-button")).toBeDisabled(); + await expect(page.getByTestId("prev-page-button")).toBeDisabled(); + await expect(page.getByTestId("clear-all-filters")).toBeDisabled(); + }); +}); + +test.describe("Version Timing Tab with a variant selected", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto( + "/version/5e4ff3abe3c3317e352062e4/version-timing?variant=^ubuntu1604%24", + ); + }); + + const expectedTasks = [ + [ + "test-agent", + "test-cloud", + "test-operations", + "test-scheduler", + "js-test", + "test-units", + "test-command", + "test-model", + "test-model-2", + "test-model-host", + ], + [ + "test-validator", + "test-thirdparty", + "test-service", + "test-rest-model", + "test-rest-client", + "test-graphql", + "test-repotracker", + "test-trigger", + "test-rest-data", + "test-rest-route", + ], + [ + "test-model-grid", + "test-model-user", + "test-model-testresult", + "test-model-manifest", + "test-model-notification", + "test-model-commitqueue", + "test-model-patch", + "test-model-event", + "test-monitor", + "test-model-task", + ], + [ + "test-migrations", + "test-model-alertrecord", + "test-thirdparty-docker", + "test-model-stats", + "test-evergreen", + "test-model-distro", + "test-util", + "test-model-artifact", + "test-model-build", + "test-plugin", + ], + ["test-db", "test-auth"], + ]; + + test("shows a paginated chart of all tasks in the variant", async ({ + authenticatedPage: page, + }) => { + for (const pageTasks of expectedTasks.slice(0, -1)) { + for (const task of pageTasks) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeVisible(); + } + await page.getByTestId("next-page-button").click(); + } + for (const task of expectedTasks.at(-1)!) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeVisible(); + } + + const reversedTasks = [...expectedTasks].reverse(); + for (const pageTasks of reversedTasks.slice(0, -1)) { + for (const task of pageTasks) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeVisible(); + } + await page.getByTestId("prev-page-button").click(); + } + for (const task of reversedTasks.at(-1)!) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeVisible(); + } + }); + + test("respects the task filter", async ({ authenticatedPage: page }) => { + await page.goto( + "/version/5e4ff3abe3c3317e352062e4/version-timing?taskName=agent&variant=^ubuntu1604%24", + ); + await expect(page.getByTestId("next-page-button")).toBeDisabled(); + await expect( + page.locator(chart).getByText("test-agent", { exact: true }), + ).toBeVisible(); + const otherTasks = expectedTasks.flat().filter((t) => t !== "test-agent"); + for (const task of otherTasks) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeHidden(); + } + }); + + test("allows the user to clear all filters", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("clear-all-filters").click(); + await expect(page).toHaveURL( + "/version/5e4ff3abe3c3317e352062e4/version-timing?sorts=DURATION%3ADESC", + ); + await expect(page.locator(chart).getByText("Ubuntu 16.04")).toBeVisible(); + await expect(page.locator(chart).getByText("Race Detector")).toBeVisible(); + await expect(page.locator(chart).getByText("Lint")).toBeVisible(); + }); + + test("allows the user to change the page size", async ({ + authenticatedPage: page, + }) => { + await page.locator("button[aria-labelledby='page-size-select']").click(); + await page.getByText("50 / page").first().click(); + await expect(page).toHaveURL(/limit=50/); + for (const task of expectedTasks.flat()) { + await expect( + page.locator(chart).getByText(task, { exact: true }), + ).toBeVisible(); + } + }); + + test("allows the user to select a task and navigate to it", async ({ + authenticatedPage: page, + }) => { + await page.locator(chart).getByText("test-agent").click(); + await expect(page).toHaveURL( + "/task/evergreen_ubuntu1604_test_agent_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48/logs?execution=0", + ); + }); +}); diff --git a/apps/spruce/src/components/Notifications/form/event.ts b/apps/spruce/src/components/Notifications/form/event.ts index 900ebaf313..d9cfad57f1 100644 --- a/apps/spruce/src/components/Notifications/form/event.ts +++ b/apps/spruce/src/components/Notifications/form/event.ts @@ -464,7 +464,7 @@ export const getEventSchema = ( "Regex can be specified for at most one name and one ID.", "ui:orderable": false, "ui:addToEnd": true, - "ui:addButtonText": "Add Additional Criteria", + "ui:addButtonText": "Add additional criteria", items: { "ui:ObjectFieldTemplate": RegexSelectorRow, "ui:label": false, diff --git a/apps/spruce/src/pages/distroSettings/tabs/ProjectTab/getFormSchema.ts b/apps/spruce/src/pages/distroSettings/tabs/ProjectTab/getFormSchema.ts index 131423fbe7..3ec279d141 100644 --- a/apps/spruce/src/pages/distroSettings/tabs/ProjectTab/getFormSchema.ts +++ b/apps/spruce/src/pages/distroSettings/tabs/ProjectTab/getFormSchema.ts @@ -49,8 +49,8 @@ export const getFormSchema = (): ReturnType => ({ "ui:orderable": false, items: { "ui:ObjectFieldTemplate": FieldRow, - "ui:label": false, "ui:data-cy": "expansion-item", + "ui:label": false, }, }, validProjects: { diff --git a/apps/spruce/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts b/apps/spruce/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts index d0b7472176..fcf2f66405 100644 --- a/apps/spruce/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts +++ b/apps/spruce/src/pages/distroSettings/tabs/ProviderTab/schemaFields.ts @@ -8,6 +8,7 @@ const userData = { title: "User Data", }, uiSchema: { + "ui:data-cy": "user-data-input", "ui:widget": "textarea", "ui:elementWrapperCSS": textAreaCSS, "ui:rows": 6, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx index 1b68a8e699..9859fbf8db 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx @@ -242,6 +242,7 @@ export const getFormSchema = ( }, uiSchema: { github: { + "ui:data-cy": "github-card", "ui:ObjectFieldTemplate": CardFieldTemplate, prTestingEnabledTitle: { "ui:sectionTitle": true, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx index c76a3fcd0e..ddbe30189a 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx @@ -169,6 +169,7 @@ const permissionCss = css` const itemsUISchema = { "ui:displayTitle": "New Permission Group", + "ui:data-cy": "permission-group-item", name: { "ui:ariaLabelledBy": "Permission Group Name", "ui:data-cy": "permission-group-title-input", diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx index 25238d8caf..5ad01746b0 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx @@ -264,6 +264,7 @@ export const getFormSchema = ( "ui:useExpandableCard": true, items: { "ui:displayTitle": "New Metadata Link", + "ui:data-cy": "metadata-link-item", requesters: { "ui:widget": widgets.MultiSelectWidget, "ui:data-cy": "requesters-input", diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts index 75ee912954..a7cc13539a 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts @@ -96,6 +96,7 @@ export const getFormSchema = ( "ui:data-cy": "parsley-filter-list", items: { "ui:displayTitle": "New Parsley Filter", + "ui:data-cy": "parsley-filter-item", "ui:label": false, expression: { "ui:widget": "textarea", diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts index 1ae0cc83b9..b388513e9d 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts @@ -96,6 +96,7 @@ export const getFormSchema = ( items: { "ui:ObjectFieldTemplate": CommandRow, command: { + "ui:data-cy": "repo-command-input", "ui:widget": widgets.TextareaWidget, }, }, diff --git a/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateVolumeModal.tsx b/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateVolumeModal.tsx index e6a9bb8533..75d7d5d8bb 100644 --- a/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateVolumeModal.tsx +++ b/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/MigrateVolumeModal.tsx @@ -154,7 +154,7 @@ export const MigrateVolumeModal: React.FC = ({ ? "Migrate Volume" : "Are you sure you want to migrate this home volume?"; - let buttonText = "Migrate Volume"; + let buttonText = "Migrate volume"; if (loadingMigration) { buttonText = "Migrating"; } else if (onPageOne) { diff --git a/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/UnmountButton.tsx b/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/UnmountButton.tsx index a4a1e27e71..15055587ff 100644 --- a/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/UnmountButton.tsx +++ b/apps/spruce/src/pages/spawn/spawnVolume/spawnVolumeTableActions/UnmountButton.tsx @@ -68,6 +68,7 @@ export const UnmountButton: React.FC = ({ volume }) => { ) : ( { spawnAnalytics.sendEvent({ name: "Changed unmounted volume on host",