From d3d52b4662f7226ebf276528fde93e90148c30a9 Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 14:22:52 -0400 Subject: [PATCH 01/18] DEVPROD-31460: Migrate project settings tests to Playwright * Reorganize everything to make less confusing --- apps/spruce/playwright/helpers/index.ts | 86 +-- .../projectSettings/access_section.spec.ts | 91 ++++ .../projectSettings/admin_actions.spec.ts | 98 ++++ .../projectSettings/commit_checks.spec.ts | 93 ++++ .../tests/projectSettings/constants.ts | 32 ++ .../projectSettings/general_section.spec.ts | 106 ++++ .../github_app_settings.spec.ts | 93 ++++ .../github_permission_groups.spec.ts | 79 +++ .../projectSettings/github_section.spec.ts | 28 + .../tests/projectSettings/navigation.spec.ts | 39 ++ .../not_defaulting_to_repo.spec.ts | 42 ++ .../projectSettings/notifications.spec.ts | 143 +++++ .../projectSettings/periodic_builds.spec.ts | 77 +++ .../tests/projectSettings/permissions.spec.ts | 34 ++ .../tests/projectSettings/plugins.spec.ts | 89 +++ .../projectSettings/project_triggers.spec.ts | 19 + .../projectSettings/pull_requests.spec.ts | 106 ++++ .../playwright/tests/projectSettings/utils.ts | 25 + .../tests/projectSettings/variables.spec.ts | 145 +++++ .../projectSettings/views_and_filters.spec.ts | 65 +++ .../repoSettings/attaching_to_repo.spec.ts | 61 +++ .../tests/repoSettings/constants.ts | 32 ++ .../repoSettings/defaulting_to_repo.spec.ts | 512 ++++++++++++++++++ .../repoSettings/general_section.spec.ts | 50 ++ .../tests/repoSettings/github_section.spec.ts | 146 +++++ .../tests/repoSettings/patch_aliases.spec.ts | 146 +++++ .../tests/repoSettings/permissions.spec.ts | 32 ++ .../playwright/tests/repoSettings/utils.ts | 25 + .../repoSettings/virtual_workstation.spec.ts | 38 ++ .../components/Notifications/form/event.ts | 2 +- .../GeneralTab/Fields/RepoConfigField.tsx | 6 +- .../GithubCommitQueueTab/getFormSchema.tsx | 2 +- .../tabs/NotificationsTab/getFormSchema.tsx | 2 +- .../tabs/PatchAliasesTab/getFormSchema.tsx | 4 +- .../tabs/PeriodicBuildsTab/getFormSchema.ts | 2 +- .../shared/tabs/PluginsTab/getFormSchema.tsx | 2 +- .../tabs/ProjectTriggersTab/getFormSchema.tsx | 2 +- .../VirtualWorkstationTab/getFormSchema.ts | 2 +- .../shared/tabs/utils/alias.ts | 8 +- 39 files changed, 2484 insertions(+), 80 deletions(-) create mode 100644 apps/spruce/playwright/tests/projectSettings/access_section.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/constants.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/general_section.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/github_section.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/navigation.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/notifications.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/permissions.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/plugins.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/utils.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/variables.spec.ts create mode 100644 apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/constants.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/general_section.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/github_section.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/permissions.spec.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/utils.ts create mode 100644 apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts diff --git a/apps/spruce/playwright/helpers/index.ts b/apps/spruce/playwright/helpers/index.ts index 192402769e..ab0e315f81 100644 --- a/apps/spruce/playwright/helpers/index.ts +++ b/apps/spruce/playwright/helpers/index.ts @@ -1,50 +1,5 @@ import { Page, Locator, expect } from "@playwright/test"; -type LocatorOptions = Parameters[1]; - -/** - * Gets a locator for an element with the specified data-row-key attribute - * @param page - The Playwright page object - * @param value - The data-row-key attribute value - * @param options - Optional locator options - * @returns A Playwright locator - */ -export function dataRowKeyLocator( - page: Page, - value: string, - options?: LocatorOptions, -): Locator { - return page.locator(`[data-row-key=${value}]`, options); -} - -/** - * Validates table sort direction by checking for the appropriate sort icon - * @param page - The Playwright page object - * @param direction - The expected sort direction (asc, desc, or none) - */ -export async function validateTableSort( - page: Page, - direction?: "asc" | "desc" | "none", -): Promise { - switch (direction) { - case "asc": - await expect( - page.locator("svg[aria-label='Sort Ascending Icon']"), - ).toBeVisible(); - return; - case "desc": - await expect( - page.locator("svg[aria-label='Sort Descending Icon']"), - ).toBeVisible(); - return; - case "none": - default: - await expect( - page.locator("svg[aria-label='Unsorted Icon']"), - ).toBeVisible(); - } -} - /** * Selects an option from a LeafyGreen select component * @param page - The Playwright page object @@ -53,33 +8,35 @@ export async function validateTableSort( * @param options - Additional options for selecting the option * @param options.exact - Whether to match the option text exactly (default: false) */ -export async function selectOption( - page: Page, +export const selectOption = async ( + page: Page | Locator, label: string, option: string | RegExp, options?: { exact: boolean }, -): Promise { - const button = page.getByRole("button", { name: label }); +): Promise => { + const button = page.getByRole("button", { name: label, exact: true }); await expect(button).toBeEnabled(); await button.click(); const listbox = page.locator('[role="listbox"]'); await expect(listbox).toHaveCount(1); await listbox.getByText(option, options).click(); -} +}; /** * Clears a date picker input by pressing backspace multiple times * LG Date Picker does not respond well to .clear() * @param page - The Playwright page object */ -export async function clearDatePickerInput(page: Page): Promise { +export const clearDatePickerInput = async ( + page: Page | Locator, +): Promise => { const dayInput = page.locator("input[id='day']"); await dayInput.press("Backspace"); await dayInput.press("Backspace"); await dayInput.press("Backspace"); await dayInput.press("Backspace"); await dayInput.press("Backspace"); -} +}; /** * Validates the values in a date picker component @@ -90,17 +47,17 @@ export async function clearDatePickerInput(page: Page): Promise { * @param opts.month - The expected month value * @param opts.day - The expected day value */ -export async function validateDatePickerDate( - page: Page, +export const validateDatePickerDate = async ( + page: Page | Locator, dataCy: string, { year = "", month = "", day = "" } = {}, -): Promise { +): Promise => { const datePicker = page.getByTestId(dataCy); await expect(datePicker.locator("input[id='year']")).toHaveValue(year); await expect(datePicker.locator("input[id='month']")).toHaveValue(month); await expect(datePicker.locator("input[id='day']")).toHaveValue(day); -} +}; /** * Selects a date in a LeafyGreen date picker by navigating the year/month dropdowns @@ -112,11 +69,11 @@ export async function validateDatePickerDate( * @param opts.isoDate - The ISO date string of the day cell to click (e.g., "2025-02-28") * @param dataCy - The data-cy attribute value of the date picker (default: "date-picker") */ -export async function selectDatePickerDate( - page: Page, +export const selectDatePickerDate = async ( + page: Page | Locator, { year = "", month = "", isoDate = "" } = {}, dataCy = "date-picker", -): Promise { +): Promise => { await page.getByTestId(dataCy).click(); const options = page.getByRole("listbox").getByRole("option"); @@ -134,7 +91,7 @@ export async function selectDatePickerDate( await options.getByText(month).click(); await page.locator(`[data-iso='${isoDate}']`).click(); -} +}; /** * Types a date into a LeafyGreen date picker by filling the year, month, and day inputs. @@ -145,11 +102,11 @@ export async function selectDatePickerDate( * @param opts.day - The day to select (e.g., "28") * @param dataCy - The data-cy attribute value of the date picker (default: "date-picker") */ -export async function typeDatePickerDate( - page: Page, +export const typeDatePickerDate = async ( + page: Page | Locator, { year = "", month = "", day = "" } = {}, dataCy = "date-picker", -): Promise { +): Promise => { const datePicker = page.getByTestId(dataCy); const yearInput = datePicker.locator("input[id='year']"); const monthInput = datePicker.locator("input[id='month']"); @@ -158,7 +115,7 @@ export async function typeDatePickerDate( await yearInput.fill(year); await monthInput.fill(month); await dayInput.fill(day); -} +}; // Re-export shared helpers from the playwright-config package. export { @@ -166,6 +123,7 @@ export { login, logout, clickCheckbox, + clickRadio, mockGraphQLResponse, hasOperationName, } from "@evg-ui/playwright-config/helpers"; diff --git a/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts b/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts new file mode 100644 index 0000000000..4772a25f45 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts @@ -0,0 +1,91 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + project, + ProjectSettingsTabRoutes, + projectUseRepoEnabled, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Access page", () => { + const origin = getProjectSettingsRoute( + projectUseRepoEnabled, + ProjectSettingsTabRoutes.Access, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await expectSaveButtonEnabled(page, false); + const defaultToRepoButton = page.getByRole("button", { + name: "Default to repo on page", + }); + await expect(defaultToRepoButton).toBeVisible(); + await expect(defaultToRepoButton).toBeEnabled(); + }); + + test("Changing settings and clicking the save button produces a success toast and the changes are persisted", async ({ + authenticatedPage: page, + }) => { + const unrestrictedRadio = page.getByRole("radio", { name: "Unrestricted" }); + await clickRadio(unrestrictedRadio); + await expect(unrestrictedRadio).toBeChecked(); + + await page.getByText("Add Username").click(); + const usernameInput = page.getByLabel("Username"); + await usernameInput.fill("admin"); + await expect(usernameInput).toHaveValue("admin"); + await expect(usernameInput).toBeVisible(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await page.reload(); + await expect(page.getByLabel("Username")).toHaveValue("admin"); + await expect(page.getByLabel("Username")).toBeVisible(); + + await page.getByTestId("delete-item-button").click(); + await expect(page.getByLabel("Username")).toHaveCount(0); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await page.reload(); + await expectSaveButtonEnabled(page, false); + await expect(page.getByLabel("Username")).toHaveCount(0); + }); + + test("Clicking on 'Default to Repo on Page' selects the 'Default to repo (unrestricted)' radio box and produces a success banner", async ({ + authenticatedPage: page, + }) => { + const defaultToRepoButton = page.getByRole("button", { + name: "Default to repo on page", + }); + await expect(defaultToRepoButton).toBeEnabled(); + await defaultToRepoButton.click(); + await page + .getByLabel('Type "confirm" to confirm your action') + .fill("confirm"); + await page + .getByTestId("default-to-repo-modal") + .getByRole("button", { name: "Confirm" }) + .click(); + await validateToast(page, "success", "Successfully defaulted page to repo"); + + const defaultToRepoRadio = page.getByRole("radio", { + name: "Default to repo (unrestricted)", + }); + await expect(defaultToRepoRadio).toBeChecked(); + }); + + test("Submitting an invalid admin username produces an error toast", async ({ + authenticatedPage: page, + }) => { + await page.goto( + getProjectSettingsRoute(project, ProjectSettingsTabRoutes.Access), + ); + await page.getByText("Add Username").click(); + const newUsernameInput = page.getByLabel("Username").first(); + await newUsernameInput.fill("mongodb_user"); + await save(page); + await validateToast(page, "error", "There was an error saving the project"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts b/apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts new file mode 100644 index 0000000000..c62ba20cb2 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts @@ -0,0 +1,98 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; + +test.describe("projectSettings/admin_actions", () => { + test.describe("Duplicating a project", () => { + const destination = getProjectSettingsRoute(project); + + test("Successfully duplicates a project with warnings", async ({ + authenticatedPage: page, + }) => { + await page.goto(destination); + await page.getByTestId("new-project-button").click(); + await expect(page.getByTestId("new-project-menu")).toBeVisible(); + await page.getByTestId("copy-project-button").click(); + await expect(page.getByTestId("copy-project-modal")).toBeVisible(); + await expect( + page.getByTestId("performance-tooling-banner"), + ).toBeVisible(); + + await page.getByTestId("project-name-input").fill("copied-project"); + + await page.getByRole("button", { name: "Duplicate" }).click(); + await validateToast( + page, + "warning", + "The project was duplicated but may not be fully enabled", + ); + + await expect(page).toHaveURL(/copied-project/); + }); + }); + + test.describe("Creating a new project and deleting it", () => { + test("Successfully creates a new project and then deletes it", async ({ + authenticatedPage: page, + }) => { + await page.goto(getProjectSettingsRoute(project)); + await page.getByTestId("new-project-button").click(); + await expect(page.getByTestId("new-project-menu")).toBeVisible(); + await page.getByTestId("create-project-button").click(); + await expect(page.getByTestId("create-project-modal")).toBeVisible(); + await expect( + page.getByTestId("performance-tooling-banner"), + ).toBeVisible(); + + await page.getByTestId("project-name-input").fill("my-new-project"); + await expect(page.getByTestId("new-owner-select")).toContainText( + "evergreen-ci", + ); + await expect(page.getByTestId("new-repo-input")).toHaveValue("spruce"); + await page.getByTestId("new-repo-input").clear(); + await page.getByTestId("new-repo-input").fill("new-repo"); + + await page.getByRole("button", { name: "Create project" }).click(); + await validateToast( + page, + "success", + "Successfully created the project “my-new-project”", + true, + ); + + await expect(page).toHaveURL(/my-new-project/); + + await page.goto(getProjectSettingsRoute("my-new-project")); + await page.getByTestId("attach-repo-button").click(); + await page + .getByTestId("attach-repo-modal") + .getByRole("button", { name: "Attach" }) + .click(); + await validateToast( + page, + "success", + "Successfully attached to repo", + true, + ); + + await page.getByRole("button", { name: "Delete project" }).click(); + await page + .getByTestId("delete-project-modal") + .getByRole("button", { name: "Delete" }) + .click(); + await validateToast( + page, + "success", + "The project “my-new-project” was deleted.", + true, + ); + + await page.reload(); + await validateToast( + page, + "error", + "There was an error loading the project my-new-project", + ); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts b/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts new file mode 100644 index 0000000000..97d8166d2a --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts @@ -0,0 +1,93 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + project, + ProjectSettingsTabRoutes, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("A project that has GitHub webhooks disabled", () => { + const destination = getProjectSettingsRoute( + "logkeeper", + ProjectSettingsTabRoutes.CommitChecks, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expectSaveButtonEnabled(page, false); + }); + + test("Commit Checks page shows a disabled webhooks banner when webhooks are disabled", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("disabled-webhook-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"); + const buttons = settingsPage.getByRole("button"); + for (const button of await buttons.all()) { + await expect(button).toBeDisabled(); + } + const inputs = page.locator("input"); + for (const input of await inputs.all()) { + await expect(input).toBeDisabled(); + } + }); +}); + +test.describe("A project that has GitHub webhooks enabled", () => { + const destination = getProjectSettingsRoute( + project, + ProjectSettingsTabRoutes.CommitChecks, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expectSaveButtonEnabled(page, false); + }); + + test("Shows an error banner when Commit Checks are enabled and hides it when Commit Checks are disabled", async ({ + authenticatedPage: page, + }) => { + const radioBox = page.getByTestId("github-checks-enabled-radio-box"); + const githubChecksEnabledRadio = radioBox.getByRole("radio", { + name: "Enabled", + }); + const githubChecksDisabledRadio = radioBox.getByRole("radio", { + name: "Disabled", + }); + + await clickRadio(githubChecksEnabledRadio); + const errorBanner = page.getByTestId("error-banner").filter({ + hasText: + "A Commit Check Definition must be specified for this feature to run.", + }); + await expect(errorBanner).toBeVisible(); + await clickRadio(githubChecksDisabledRadio); + await expect(errorBanner).toBeHidden(); + }); + + test("Saves successfully when Commit Checks are enabled and a Commit Check Definition is provided", async ({ + authenticatedPage: page, + }) => { + const radioBox = page.getByTestId("github-checks-enabled-radio-box"); + const githubChecksEnabledRadio = radioBox.getByRole("radio", { + name: "Enabled", + }); + await clickRadio(githubChecksEnabledRadio); + + 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")).toBeHidden(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/constants.ts b/apps/spruce/playwright/tests/projectSettings/constants.ts new file mode 100644 index 0000000000..63083b4cdb --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/constants.ts @@ -0,0 +1,32 @@ +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", + CommitChecks = "commit-checks", + PullRequests = "pull-requests", +} + +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"; diff --git a/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts b/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts new file mode 100644 index 0000000000..9d6fd87b46 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts @@ -0,0 +1,106 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + project, + projectUseRepoEnabled, +} from "./constants"; +import { save } from "./utils"; + +test.describe("general section", () => { + test.describe("Renaming the identifier", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("Update identifier", async ({ authenticatedPage: page }) => { + 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."; + + await expect(page.getByTestId("input-warning")).toHaveCount(0); + await page.getByTestId("identifier-input").clear(); + await page.getByTestId("identifier-input").fill("new-identifier"); + await expect(page.getByTestId("input-warning")).toContainText( + warningText, + ); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page).toHaveURL(/new-identifier/); + }); + }); + + test("Allows enabling Run Every Mainline Commit", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("navitem-general").click(); + const enableRadio = page + .getByTestId("run-every-mainline-commit-radio-box") + .getByRole("radio", { name: "Enabled" }); + clickRadio(enableRadio); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(enableRadio).toBeChecked(); + }); + + test.describe("Stepback bisect setting", () => { + test.describe("Repo project present", () => { + const destination = getProjectSettingsRoute(projectUseRepoEnabled); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + }); + + test("Starts as default to repo", async ({ authenticatedPage: page }) => { + await expect( + page + .getByTestId("stepback-bisect-group") + .getByRole("radio", { name: "Default to repo" }), + ).toHaveAttribute("aria-checked", "true"); + }); + + test("Clicking on enabled and then save shows a success toast", async ({ + authenticatedPage: page, + }) => { + const enableRadio = page + .getByTestId("stepback-bisect-group") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(enableRadio); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await page.reload(); + await expect(enableRadio).toHaveAttribute("aria-checked", "true"); + }); + }); + + test.describe("Repo project not present", () => { + const destination = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + }); + + test("Starts as disabled", async ({ authenticatedPage: page }) => { + await expect( + page + .getByTestId("stepback-bisect-group") + .getByRole("radio", { name: "Disabled", exact: true }), + ).toHaveAttribute("aria-checked", "true"); + }); + + test("Clicking on enabled and then save shows a success toast", async ({ + authenticatedPage: page, + }) => { + const enableRadio = page + .getByTestId("stepback-bisect-group") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(enableRadio); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await page.reload(); + await expect(enableRadio).toHaveAttribute("aria-checked", "true"); + }); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts b/apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts new file mode 100644 index 0000000000..ed12cdc0f0 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts @@ -0,0 +1,93 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("GitHub app settings", () => { + const destination = getProjectSettingsRoute( + "spruce", + ProjectSettingsTabRoutes.GithubAppSettings, + ); + const permissionGroups = { + all: "All app permissions", + readPRs: "Read Pull Requests", + writeIssues: "Write Issues", + }; + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expect(page.getByText("Token Permission Restrictions")).toBeVisible(); + }); + + test("save button should be disabled by default", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + }); + + test("should be able to replace app credentials", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByTestId("replace-app-credentials-button"), + ).toBeVisible(); + await page.getByTestId("replace-app-credentials-button").click(); + + const modal = page.getByTestId("replace-github-credentials-modal"); + await expect(modal).toBeVisible(); + + const confirmButton = modal.getByRole("button", { name: "Replace" }); + await expect(confirmButton).toBeDisabled(); + + await page.getByTestId("replace-app-id-input").fill("99999"); + await page.getByTestId("replace-private-key-input").fill("new-private-key"); + + await expect(confirmButton).toBeEnabled(); + + await confirmButton.click(); + await validateToast( + page, + "success", + "GitHub app credentials were successfully replaced.", + ); + }); + + test("should be able to save different permission groups for requesters, then return to defaults", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("permission-group-input")).toHaveCount(8); + const permissionGroupInput0 = page + .getByTestId("permission-group-input") + .nth(0); + const permissionGroupInput4 = page + .getByTestId("permission-group-input") + .nth(4); + + await permissionGroupInput0.click(); + const options = page.locator('[role="listbox"]'); + await expect(options).toHaveCount(1); + await options.getByText(permissionGroups.readPRs).click(); + await permissionGroupInput4.click(); + await expect(options).toHaveCount(1); + await options.getByText(permissionGroups.writeIssues).click(); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await page.reload(); + await expect(permissionGroupInput0).toContainText(permissionGroups.readPRs); + await expect(permissionGroupInput4).toContainText( + permissionGroups.writeIssues, + ); + + await permissionGroupInput0.click(); + await expect(options).toHaveCount(1); + await options.getByText(permissionGroups.all).click(); + await permissionGroupInput4.click(); + await expect(options).toHaveCount(1); + await options.getByText(permissionGroups.all).click(); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts b/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts new file mode 100644 index 0000000000..605f81f7b7 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts @@ -0,0 +1,79 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("GitHub permission groups", () => { + const destination = getProjectSettingsRoute( + "logkeeper", + ProjectSettingsTabRoutes.GithubPermissionGroups, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expect(page.getByText("Token Permission Groups")).toBeVisible(); + }); + + test("should not have any permission groups defined", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("permission-group-item")).toHaveCount(0); + await expectSaveButtonEnabled(page, false); + }); + + test("should throw an error if permission group definitions are invalid", async ({ + authenticatedPage: page, + }) => { + const addPermissionGroupButton = page.getByRole("button", { + name: /^Add permission group$/, + }); + await expect(addPermissionGroupButton).toBeVisible(); + await addPermissionGroupButton.click(); + await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + + const invalidGithubPermission = "invalid_github_permission"; + await page + .getByTestId("permission-group-title-input") + .fill("test permission group"); + await expect(page.getByTestId("add-permission-button")).toBeVisible(); + await page.getByTestId("add-permission-button").click(); + await page + .getByTestId("permission-type-input") + .fill(invalidGithubPermission); + await page.getByTestId("permission-value-input").click(); + await page.getByText("Write").click(); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "error", "There was an error saving the project"); + }); + + test("should be able to save permission group, then delete it", async ({ + authenticatedPage: page, + }) => { + const addPermissionGroupButton = page.getByRole("button", { + name: /^Add permission group$/, + }); + await expect(addPermissionGroupButton).toBeVisible(); + await addPermissionGroupButton.click(); + await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + + await page + .getByTestId("permission-group-title-input") + .fill("test permission group"); + await expect(page.getByTestId("add-permission-button")).toBeVisible(); + await page.getByTestId("add-permission-button").click(); + await page.getByTestId("permission-type-input").fill("actions"); + await page.getByTestId("permission-value-input").click(); + await page.getByText("Read").click(); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await page.reload(); + await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + await page.getByTestId("delete-item-button").click(); + await expect(page.getByTestId("permission-group-item")).toHaveCount(0); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/github_section.spec.ts b/apps/spruce/playwright/tests/projectSettings/github_section.spec.ts new file mode 100644 index 0000000000..bcc022ccd8 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/github_section.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from "../../fixtures"; +import { validateToast, clickRadio } from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; +import { save } from "./utils"; + +test.describe("GitHub page", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-github-commitqueue").click(); + }); + + test("Allows adding a git tag alias", async ({ authenticatedPage: page }) => { + const enabledRadio = page + .getByTestId("git-tag-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(enabledRadio); + await page.getByRole("button", { name: "Add Git Tag" }).click(); + await page.getByTestId("git-tag-input").fill("myGitTag"); + await page.getByTestId("remote-path-input").fill("./evergreen.yml"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page.getByTestId("remote-path-input")).toHaveValue( + "./evergreen.yml", + ); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/navigation.spec.ts b/apps/spruce/playwright/tests/projectSettings/navigation.spec.ts new file mode 100644 index 0000000000..b048b00a19 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/navigation.spec.ts @@ -0,0 +1,39 @@ +import { test, expect } from "../../fixtures"; +import { getProjectSettingsRoute, project } from "./constants"; + +test.describe("navigation", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("headers (repos) are clickable in project select dropdown", async ({ + authenticatedPage: page, + }) => { + const projectSelect = page.getByTestId("project-select"); + await expect(projectSelect).toBeVisible(); + await projectSelect.click(); + const projectSelectOptions = page.getByTestId("project-select-options"); + await expect(projectSelectOptions).toBeVisible(); + await projectSelectOptions.getByText("evergreen-ci/evergreen").click(); + await expect(page).not.toHaveURL(new RegExp(origin)); + }); + + test.describe("project ID should redirect to the project identifier", () => { + const projectId = "602d70a2b2373672ee493189"; + const origin = getProjectSettingsRoute(projectId); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("Redirects to the project identifier", async ({ + authenticatedPage: page, + }) => { + await expect(page).toHaveURL( + new RegExp(getProjectSettingsRoute("parsley")), + ); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts new file mode 100644 index 0000000000..3ac2695ee0 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts @@ -0,0 +1,42 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; +import { expectSaveButtonEnabled } from "./utils"; + +test.describe("Project Settings when not defaulting to repo", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await expectSaveButtonEnabled(page, false); + }); + + test("Does not show a 'Default to Repo' button on page", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("default-to-repo-button")).toHaveCount(0); + }); + + test("Shows two radio boxes", async ({ authenticatedPage: page }) => { + await expect( + page.getByTestId("enabled-radio-box").locator("> *"), + ).toHaveCount(2); + }); + + test("Successfully attaches to and detaches from a repo that does not yet exist and shows 'Default to Repo' options", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("attach-repo-button").click(); + await page + .getByTestId("attach-repo-modal") + .getByRole("button", { name: "Attach" }) + .click(); + await validateToast(page, "success", "Successfully attached to repo", true); + await page.getByTestId("attach-repo-button").click(); + await page + .getByTestId("attach-repo-modal") + .getByRole("button", { name: "Detach" }) + .click(); + await validateToast(page, "success", "Successfully detached from repo"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/notifications.spec.ts b/apps/spruce/playwright/tests/projectSettings/notifications.spec.ts new file mode 100644 index 0000000000..bc942ebc63 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/notifications.spec.ts @@ -0,0 +1,143 @@ +import { test, expect } from "../../fixtures"; +import { validateToast, selectOption } from "../../helpers"; +import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Notifications", () => { + const origin = getProjectSettingsRoute( + "evergreen", + ProjectSettingsTabRoutes.Notifications, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("shows correct initial state", async ({ authenticatedPage: page }) => { + await expect(page.getByTestId("default-to-repo-button")).toHaveCount(0); + await expect(page.getByText("No subscriptions are defined.")).toBeVisible(); + await expectSaveButtonEnabled(page, false); + }); + + test("should be able to add a subscription, save it and delete it", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("expandable-card")).toHaveCount(0); + const addSubscriptionButton = page.getByRole("button", { + name: "Add Subscription", + }); + await expect(addSubscriptionButton).toBeVisible(); + await addSubscriptionButton.click(); + await expect(page.getByTestId("expandable-card")).toContainText( + "New Subscription", + ); + await selectOption(page, "Event", "Any version finishes"); + await selectOption(page, "Notification Method", "Email"); + await page.getByTestId("email-input").fill("mohamed.khelif@mongodb.com"); + await save(page); + await validateToast(page, "success", "Successfully updated project", true); + + await expectSaveButtonEnabled(page, false); + const subscriptionItem = page.getByTestId("expandable-card"); + await expect(subscriptionItem).toBeVisible(); + await expect(subscriptionItem).toContainText( + "Version outcome - mohamed.khelif@mongodb.com", + ); + await page.getByTestId("delete-item-button").click(); + await expect(subscriptionItem).toHaveCount(0); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); + + test("should not be able to combine a jira comment subscription with a task event", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("expandable-card")).toHaveCount(0); + const addSubscriptionButton = page.getByRole("button", { + name: "Add Subscription", + }); + await expect(addSubscriptionButton).toBeVisible(); + await addSubscriptionButton.click(); + const expandableCard = page.getByTestId("expandable-card"); + await expect(expandableCard).toBeVisible(); + await expect(expandableCard).toContainText("New Subscription"); + await selectOption(page, "Event", "Any task finishes"); + await selectOption(page, "Notification Method", "Comment on a JIRA issue"); + await page.getByTestId("jira-comment-input").fill("JIRA-123"); + await expect( + page.getByText("Subscription type not allowed for tasks in a project."), + ).toBeVisible(); + await expectSaveButtonEnabled(page, false); + }); + + test("should not be able to save a subscription if an input is invalid", async ({ + authenticatedPage: page, + }) => { + const addSubscriptionButton = page.getByRole("button", { + name: "Add Subscription", + }); + await expect(addSubscriptionButton).toBeVisible(); + await addSubscriptionButton.click(); + const expandableCard = page.getByTestId("expandable-card"); + await expect(expandableCard).toBeVisible(); + await expect(expandableCard).toContainText("New Subscription"); + await selectOption(page, "Event", "Any version finishes"); + await selectOption(page, "Notification Method", "Email"); + await page.getByTestId("email-input").fill("Not a real email"); + await expect( + page.getByText("Value should be a valid email."), + ).toBeVisible(); + await expectSaveButtonEnabled(page, false); + }); + + test("Setting a project banner displays the banner on the correct pages and unsetting it removes it", async ({ + authenticatedPage: page, + }) => { + const bannerText = "This is a project banner!"; + + await page.getByTestId("banner-text").clear(); + await page.getByTestId("banner-text").fill(bannerText); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await expect(page.getByText(bannerText)).toBeVisible(); + + const taskRoute = + "task/evergreen_ubuntu1604_test_model_patch_5e823e1f28baeaa22ae00823d83e03082cd148ab_5e4ff3abe3c3317e352062e4_20_02_21_15_13_48"; + await page.goto(taskRoute); + await expect(page.getByText(bannerText)).toBeVisible(); + + await page.goto("patch/5e6bb9e23066155a993e0f1b/configure/tasks"); + await expect(page.getByText(bannerText)).toBeVisible(); + + await page.goto("version/5e4ff3abe3c3317e352062e4"); + await expect(page.getByText(bannerText)).toBeVisible(); + + await page.goto("project/evergreen/waterfall"); + await expect(page.getByText(bannerText)).toBeVisible(); + + await page.goto("variant-history/evergreen/ubuntu1604"); + await expect(page.getByText(bannerText)).toBeVisible(); + + await page.goto(origin); + await page.getByTestId("banner-text").clear(); + await save(page); + + await expect(page.getByText(bannerText)).toHaveCount(0); + + await page.goto(taskRoute); + await expect(page.getByText(bannerText)).toHaveCount(0); + + await page.goto("patch/5e6bb9e23066155a993e0f1b/configure/tasks"); + await expect(page.getByText(bannerText)).toHaveCount(0); + + await page.goto("version/5e4ff3abe3c3317e352062e4"); + await expect(page.getByText(bannerText)).toHaveCount(0); + + await page.goto("project/evergreen/waterfall"); + await expect(page.getByText(bannerText)).toHaveCount(0); + + await page.goto("variant-history/evergreen/ubuntu1604"); + await expect(page.getByText(bannerText)).toHaveCount(0); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts b/apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts new file mode 100644 index 0000000000..a65170cdaa --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts @@ -0,0 +1,77 @@ +import { test, expect } from "../../fixtures"; +import { + validateToast, + validateDatePickerDate, + clearDatePickerInput, + typeDatePickerDate, +} from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Periodic Builds page", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-periodic-builds").click(); + }); + + test("allows a user to schedule the next build on the current day", async ({ + authenticatedPage: page, + }) => { + await page.clock.setFixedTime(new Date(2025, 8, 16)); + await page.reload(); + await page.getByTestId("navitem-periodic-builds").click(); + await page.getByRole("button", { name: "Add periodic build" }).click(); + await validateDatePickerDate(page, "date-picker", { + year: "2025", + month: "09", + day: "16", + }); + await clearDatePickerInput(page); + + await typeDatePickerDate(page, { year: "2025", month: "01", day: "01" }); + await validateDatePickerDate(page, "date-picker", { + year: "2025", + month: "01", + day: "01", + }); + await expect(page.getByText("Date must be after")).toBeVisible(); + await clearDatePickerInput(page); + + await typeDatePickerDate(page, { year: "2025", month: "09", day: "20" }); + await validateDatePickerDate(page, "date-picker", { + year: "2025", + month: "09", + day: "20", + }); + await expect(page.getByText("Date must be after")).toHaveCount(0); + await clearDatePickerInput(page); + + await typeDatePickerDate(page, { year: "2025", month: "09", day: "16" }); + await validateDatePickerDate(page, "date-picker", { + year: "2025", + month: "09", + day: "16", + }); + await expect(page.getByText("Date must be after")).toHaveCount(0); + }); + + test("Disables save button when interval is NaN or below minimum and allows saving a number in range", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add periodic build" }).click(); + const intervalInput = page.getByTestId("interval-input"); + await intervalInput.fill("NaN"); + await page.getByTestId("config-file-input").fill("config.yml"); + await expectSaveButtonEnabled(page, false); + await expect(page.getByText("Value should be a number.")).toBeVisible(); + await intervalInput.clear(); + await intervalInput.fill("0"); + await expectSaveButtonEnabled(page, false); + await intervalInput.clear(); + await intervalInput.fill("12"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/permissions.spec.ts b/apps/spruce/playwright/tests/projectSettings/permissions.spec.ts new file mode 100644 index 0000000000..570b199df9 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/permissions.spec.ts @@ -0,0 +1,34 @@ +import { users } from "@evg-ui/playwright-config/constants"; +import { test, expect } from "../../fixtures"; +import { login, logout } from "../../helpers"; +import { getProjectSettingsRoute, projectUseRepoEnabled } from "./constants"; + +test.describe("permissions", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await logout(page); + }); + + test.describe("projects", () => { + test("disables fields when user lacks edit permissions", async ({ + authenticatedPage: page, + }) => { + await login(page, users.privileged); + await page.goto(getProjectSettingsRoute(projectUseRepoEnabled)); + const settingsPage = page.getByTestId("project-settings-page"); + await expect( + settingsPage.locator('input[type="radio"]').first(), + ).toBeDisabled(); + }); + + test("enables fields if user has edit permissions", async ({ + authenticatedPage: page, + }) => { + await login(page, users.admin); + await page.goto(getProjectSettingsRoute(projectUseRepoEnabled)); + const settingsPage = page.getByTestId("project-settings-page"); + await expect( + settingsPage.locator('input[type="radio"]').first(), + ).toBeEnabled(); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/plugins.spec.ts b/apps/spruce/playwright/tests/projectSettings/plugins.spec.ts new file mode 100644 index 0000000000..96f40c0106 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/plugins.spec.ts @@ -0,0 +1,89 @@ +import { Page } from "@playwright/test"; +import { test, expect } from "../../fixtures"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, + projectUseRepoEnabled, +} from "./constants"; +import { save } from "./utils"; + +test.describe("Plugins", () => { + const patchPage = "version/5ecedafb562343215a7ff297"; + + const addMetadataLink = async ( + page: Page, + metadataLink: { displayName: string; url: string }, + ) => { + await page.getByRole("button", { name: "Add metadata link" }).click(); + + const mostRecentMetadataLink = page + .getByTestId("metadata-link-item") + .first(); + await mostRecentMetadataLink.getByTestId("requesters-input").click(); + + const options = mostRecentMetadataLink.getByTestId("tree-select-options"); + await expect(options).toBeVisible(); + await options.getByText("Patches").click(); + + await mostRecentMetadataLink.getByTestId("requesters-input").click(); + await mostRecentMetadataLink + .getByTestId("display-name-input") + + .fill(metadataLink.displayName); + await mostRecentMetadataLink + .getByTestId("url-template-input") + .fill(metadataLink.url); + }; + + test("Should be able to set external links to render on patch metadata panel", async ({ + authenticatedPage: page, + }) => { + await page.goto( + getProjectSettingsRoute( + projectUseRepoEnabled, + ProjectSettingsTabRoutes.Plugins, + ), + ); + await addMetadataLink(page, { + displayName: "An external link 1", + url: "https://example-1.com/{version_id}", + }); + await addMetadataLink(page, { + displayName: "An external link 2", + url: "https://example-2.com/{version_id}", + }); + await expect(page.getByTestId("metadata-link-item")).toHaveCount(2); + await save(page); + + await page.goto(patchPage); + await expect(page.getByTestId("user-patches-link")).toBeVisible(); + await expect(page.getByTestId("external-link")).toHaveCount(2); + await expect(page.getByTestId("external-link").last()).toContainText( + "An external link 1", + ); + await expect(page.getByTestId("external-link").last()).toHaveAttribute( + "href", + "https://example-1.com/5ecedafb562343215a7ff297", + ); + await expect(page.getByTestId("external-link").first()).toContainText( + "An external link 2", + ); + await expect(page.getByTestId("external-link").first()).toHaveAttribute( + "href", + "https://example-2.com/5ecedafb562343215a7ff297", + ); + + await page.goto( + getProjectSettingsRoute( + projectUseRepoEnabled, + ProjectSettingsTabRoutes.Plugins, + ), + ); + await page.getByTestId("delete-item-button").first().click(); + await page.getByTestId("delete-item-button").first().click(); + await save(page); + + await page.goto(patchPage); + await expect(page.getByTestId("external-link")).toHaveCount(0); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts b/apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts new file mode 100644 index 0000000000..285b04685b --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from "../../fixtures"; +import { getProjectSettingsRoute, project } from "./constants"; + +test.describe("Project Triggers page", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-project-triggers").click(); + }); + + test("Saves a project trigger", async ({ authenticatedPage: page }) => { + await page.getByRole("button", { name: "Add project trigger" }).click(); + await expect(page.getByTestId("project-input")).toBeVisible(); + await expect(page.getByTestId("project-input")).toBeEnabled(); + await page.getByTestId("project-input").fill("spruce"); + await page.getByTestId("config-file-input").fill(".evergreen.yml"); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts b/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts new file mode 100644 index 0000000000..5c6f99038e --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts @@ -0,0 +1,106 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + getRepoSettingsRoute, + projectUseRepoEnabled, + ProjectSettingsTabRoutes, + repo, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("A project that has GitHub webhooks disabled", () => { + const destination = getProjectSettingsRoute( + "logkeeper", + ProjectSettingsTabRoutes.PullRequests, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expectSaveButtonEnabled(page, false); + }); + + test("Pull Requests page shows a disabled webhooks banner when webhooks are disabled", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("disabled-webhook-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"); + const buttons = settingsPage.getByRole("button"); + for (const button of await buttons.all()) { + await expect(button).toBeDisabled(); + } + const inputs = page.locator("input"); + for (const input of await inputs.all()) { + await expect(input).toBeDisabled(); + } + }); +}); + +test.describe("A project that has GitHub webhooks enabled", () => { + const destination = getRepoSettingsRoute( + repo, + ProjectSettingsTabRoutes.PullRequests, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expectSaveButtonEnabled(page, false); + }); + + test("Allows enabling manual PR testing", async ({ + authenticatedPage: page, + }) => { + const radioBox = page.getByTestId("manual-pr-testing-enabled-radio-box"); + const enabledRadio = radioBox.getByRole("radio", { name: "Enabled" }); + await clickRadio(enabledRadio); + await expect(enabledRadio).toBeChecked(); + }); + + test("Saving a patch definition should hide the error banner, show a success toast and display disabled patch definitions for the repo on the project page", async ({ + authenticatedPage: page, + }) => { + const errorBanner = page.getByTestId("error-banner").filter({ + hasText: + "A GitHub Patch Definition must be specified for this feature to run.", + }); + await expect(errorBanner).toBeVisible(); + + await page.getByRole("button", { name: "Add patch definition" }).click(); + await expect(errorBanner).toBeHidden(); + await expectSaveButtonEnabled(page, false); + + await page.getByTestId("variant-tags-input").first().fill("vtag"); + await page.getByTestId("task-tags-input").first().fill("ttag"); + await expectSaveButtonEnabled(page, true); + + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + + await page.goto( + getProjectSettingsRoute( + projectUseRepoEnabled, + ProjectSettingsTabRoutes.PullRequests, + ), + ); + + const patchDefAccordion = page.getByText("Repo Patch Definition 1"); + await patchDefAccordion.click(); + + const variantTagsInput = page.getByTestId("variant-tags-input"); + await expect(variantTagsInput).toHaveValue("vtag"); + await expect(variantTagsInput).toBeDisabled(); + + const taskTagsInput = page.getByTestId("task-tags-input"); + await expect(taskTagsInput).toHaveValue("ttag"); + await expect(taskTagsInput).toBeDisabled(); + + await expect(errorBanner).toBeHidden(); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/utils.ts b/apps/spruce/playwright/tests/projectSettings/utils.ts new file mode 100644 index 0000000000..3f321fffb0 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/utils.ts @@ -0,0 +1,25 @@ +import { Page } from "@playwright/test"; +import { expect } from "../../fixtures"; + +export const save = async (page: Page) => { + const saveButton = page.getByTestId("save-settings-button"); + await expect(saveButton).toHaveAttribute("aria-disabled", "false"); + await saveButton.click(); + + const saveChangesModal = page.getByTestId("save-changes-modal"); + await expect(saveChangesModal).toBeVisible(); + await saveChangesModal.getByRole("button", { name: "Save changes" }).click(); + await expect(saveChangesModal).toBeHidden(); +}; + +export const expectSaveButtonEnabled = async ( + page: Page, + isEnabled: boolean = true, +) => { + const saveButton = page.getByTestId("save-settings-button"); + if (isEnabled) { + await expect(saveButton).toHaveAttribute("aria-disabled", "false"); + } else { + await expect(saveButton).toHaveAttribute("aria-disabled", "true"); + } +}; diff --git a/apps/spruce/playwright/tests/projectSettings/variables.spec.ts b/apps/spruce/playwright/tests/projectSettings/variables.spec.ts new file mode 100644 index 0000000000..ca8d1f0a12 --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/variables.spec.ts @@ -0,0 +1,145 @@ +import { test, expect } from "../../fixtures"; +import { validateToast, clickCheckbox } from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Variables page", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-variables").click(); + }); + + test("Should not have the save button enabled on load", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + }); + + test("Should not show the move variables button", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("promote-vars-button")).toHaveCount(0); + }); + + test("Should redact and disable private variables on saving", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").fill("sample_name"); + await expectSaveButtonEnabled(page, false); + await page.getByTestId("var-value-input").fill("sample_value"); + await page.getByTestId("var-description-input").fill("Sample description"); + const privateCheckbox = page.getByRole("checkbox", { + name: "Private", + }); + const adminOnlyCheckbox = page.getByRole("checkbox", { + name: "Admin Only", + }); + await expect(privateCheckbox).toBeChecked(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page.getByTestId("var-value-input")).toHaveValue("{REDACTED}"); + await expect(page.getByTestId("var-name-input")).toBeDisabled(); + await expect(page.getByTestId("var-value-input")).toBeDisabled(); + await expect(privateCheckbox).toBeDisabled(); + await expect(adminOnlyCheckbox).toBeEnabled(); + await expect(page.getByTestId("var-description-input")).toBeEnabled(); + }); + + test("Typing a duplicate variable name will disable saving and show an error message", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").fill("sample_name"); + await page.getByTestId("var-value-input").fill("sample_value"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("sample_name"); + await page.getByTestId("var-value-input").first().fill("sample_value_2"); + const errorMessage = page.getByText( + "Value already appears in project variables.", + ); + await expect(errorMessage).toBeVisible(); + await expectSaveButtonEnabled(page, false); + + await page.getByTestId("var-name-input").first().fill("sample_name_2"); + await expectSaveButtonEnabled(page, true); + await expect(errorMessage).toHaveCount(0); + }); + + test("Should correctly save an admin only variable", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("admin_var"); + await page.getByTestId("var-value-input").first().fill("admin_value"); + const adminOnlyCheckbox = page.getByRole("checkbox", { + name: "Admin Only", + }); + await clickCheckbox(adminOnlyCheckbox); + await expect(adminOnlyCheckbox).toBeChecked(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); + + test("Should persist saved variables and allow deletion", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").fill("sample_name"); + await page.getByTestId("var-value-input").fill("sample_value"); + await page + .getByTestId("var-description-input") + .fill("Description for sample_name"); + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("sample_name_2"); + await page.getByTestId("var-value-input").first().fill("sample_value"); + await page + .getByTestId("var-description-input") + .first() + .fill("Description for sample_name_2"); + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("admin_var"); + await page.getByTestId("var-value-input").first().fill("admin_value"); + await page + .getByTestId("var-description-input") + .first() + .fill("Description for admin_var"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await page.reload(); + await expect(page.getByTestId("var-name-input").nth(0)).toHaveValue( + "admin_var", + ); + await expect(page.getByTestId("var-description-input").nth(0)).toHaveValue( + "Description for admin_var", + ); + await expect(page.getByTestId("var-name-input").nth(1)).toHaveValue( + "sample_name", + ); + await expect(page.getByTestId("var-description-input").nth(1)).toHaveValue( + "Description for sample_name", + ); + await expect(page.getByTestId("var-name-input").nth(2)).toHaveValue( + "sample_name_2", + ); + await expect(page.getByTestId("var-description-input").nth(2)).toHaveValue( + "Description for sample_name_2", + ); + + await page.getByTestId("delete-item-button").first().click(); + await page.getByTestId("delete-item-button").first().click(); + await page.getByTestId("delete-item-button").first().click(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page.getByTestId("var-name-input")).toHaveCount(0); + + await page.reload(); + await expectSaveButtonEnabled(page, false); + await expect(page.getByTestId("var-name-input")).toHaveCount(0); + }); +}); diff --git a/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts b/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts new file mode 100644 index 0000000000..745885365d --- /dev/null +++ b/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts @@ -0,0 +1,65 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Views & filters page", () => { + const destination = getProjectSettingsRoute( + "sys-perf", + ProjectSettingsTabRoutes.ViewsAndFilters, + ); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(destination); + await expect(page.getByTestId("parsley-filter-item")).toHaveCount(2); + await expectSaveButtonEnabled(page, false); + }); + + test.describe("parsley filters", () => { + test("does not allow saving with invalid regular expression or empty expression", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add filter" }).click(); + await page.getByTestId("parsley-filter-expression").first().fill("*"); + await expectSaveButtonEnabled(page, false); + await expect( + page.getByText("Value should be a valid regex expression."), + ).toBeVisible(); + await page.getByTestId("parsley-filter-expression").first().clear(); + await expectSaveButtonEnabled(page, false); + }); + + test("does not allow saving with duplicate filter expressions", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add filter" }).click(); + await page + .getByTestId("parsley-filter-expression") + .first() + .fill("filter_1"); + await expectSaveButtonEnabled(page, false); + await expect( + page.getByText("Filter expression already appears in this project."), + ).toBeVisible(); + }); + + test("can successfully save and delete filter", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add filter" }).click(); + await page + .getByTestId("parsley-filter-expression") + .first() + .fill("my_filter"); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page.getByTestId("parsley-filter-item")).toHaveCount(3); + + await page.getByTestId("delete-item-button").first().click(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(page.getByTestId("parsley-filter-item")).toHaveCount(2); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts b/apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts new file mode 100644 index 0000000000..aff520c4fb --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts @@ -0,0 +1,61 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { getProjectSettingsRoute, project } from "./constants"; +import { save } from "./utils"; + +test.describe("Attaching Spruce to a repo", () => { + const origin = getProjectSettingsRoute(project); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("Saves and attaches new repo and shows warnings on the Github page", async ({ + authenticatedPage: page, + }) => { + const repoInput = page.getByTestId("repo-input"); + await repoInput.clear(); + await repoInput.fill("evergreen"); + await expect(page.getByTestId("attach-repo-button")).toBeDisabled(); + await save(page); + await validateToast(page, "success", "Successfully updated project", true); + + await page.getByRole("button", { name: "Attach to current repo" }).click(); + await page + .getByTestId("attach-repo-modal") + .getByRole("button", { name: "Attach" }) + .click(); + await validateToast(page, "success", "Successfully attached to repo"); + + await page.getByTestId("navitem-github-commitqueue").click(); + await expect( + page + .getByTestId("pr-testing-enabled-radio-box") + .locator("..") + .getByTestId("warning-banner"), + ).toBeVisible(); + await expect( + page + .getByTestId("manual-pr-testing-enabled-radio-box") + .locator("..") + .getByTestId("warning-banner"), + ).toBeVisible(); + await expect( + page + .getByTestId("github-checks-enabled-radio-box") + .locator("..") + .getByTestId("warning-banner"), + ).toHaveCount(0); + await expect( + page.getByTestId("cq-card").getByTestId("warning-banner"), + ).toBeVisible(); + + const mergeQueueEnabledRadio = page + .getByTestId("cq-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(mergeQueueEnabledRadio); + await expect( + page.getByTestId("cq-card").getByTestId("error-banner"), + ).toBeVisible(); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/constants.ts b/apps/spruce/playwright/tests/repoSettings/constants.ts new file mode 100644 index 0000000000..63083b4cdb --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/constants.ts @@ -0,0 +1,32 @@ +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", + CommitChecks = "commit-checks", + PullRequests = "pull-requests", +} + +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"; diff --git a/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts new file mode 100644 index 0000000000..e137f6f402 --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts @@ -0,0 +1,512 @@ +import { test, expect } from "../../fixtures"; +import { clickCheckbox, clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + getRepoSettingsRoute, + project, + ProjectSettingsTabRoutes, + projectUseRepoEnabled, + repo, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Project Settings when defaulting to repo", () => { + const origin = getProjectSettingsRoute(projectUseRepoEnabled); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test.describe("General Settings page", () => { + test("Save button is disabled on load and shows a link to the repo", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + await expect(page.getByTestId("attached-repo-link")).toHaveAttribute( + "href", + `${getRepoSettingsRoute(repo)}`, + ); + }); + + test("Preserves edits to the form when navigating between settings tabs and does not show a warning modal", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("spawn-host-input")).toHaveValue("/path"); + await page.getByTestId("spawn-host-input").fill("/path/test"); + await expectSaveButtonEnabled(page, true); + await page.getByTestId("navitem-access").click(); + await expect(page.getByTestId("navigation-warning-modal")).toHaveCount(0); + await page.getByTestId("navitem-general").click(); + await expect(page.getByTestId("spawn-host-input")).toHaveValue( + "/path/test", + ); + await expectSaveButtonEnabled(page, true); + }); + + test("Shows a 'Default to Repo' button on page", async ({ + authenticatedPage: page, + }) => { + const defaultToRepoButton = page.getByRole("button", { + name: "Default to repo on page", + }); + await expect(defaultToRepoButton).toBeVisible(); + }); + + test("Shows only two radio boxes even when rendering a project that inherits from repo", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByTestId("enabled-radio-box").locator("> *"), + ).toHaveCount(2); + }); + + test("Does not default to repo value for display name", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("display-name-input")).not.toHaveAttribute( + "placeholder", + ); + }); + + test("Shows a navigation warning modal that lists the general page when navigating away from project settings", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("spawn-host-input").fill("/path/test"); + await expectSaveButtonEnabled(page, true); + await page.getByText("My Patches").click(); + await expect(page.getByTestId("navigation-warning-modal")).toBeVisible(); + await expect(page.getByTestId("unsaved-pages").locator("li")).toHaveCount( + 1, + ); + await page.keyboard.press("Escape"); + }); + + test("Shows the repo value for Batch Time", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("batch-time-input")).toHaveAttribute( + "placeholder", + /.+/, + ); + }); + + test("Clicking on save button should show a success toast", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("spawn-host-input").fill("/path/test"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); + + test("Saves when batch time is updated", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("batch-time-input").clear(); + await page.getByTestId("batch-time-input").fill("12"); + await save(page); + await expect(page.getByTestId("batch-time-input")).toHaveValue("12"); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); + + await page.getByTestId("batch-time-input").clear(); + await save(page); + await expect(page.getByTestId("batch-time-input")).toHaveAttribute( + "placeholder", + "60 (Default from repo)", + ); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); + + await page.getByTestId("attached-repo-link").click(); + await expect(page.getByTestId("batch-time-input")).toHaveValue("60"); + await page.getByTestId("batch-time-input").clear(); + await save(page); + await expect(page.getByTestId("batch-time-input")).toHaveValue("0"); + await validateToast(page, "success", "Successfully updated repo", true); + await page.goto(origin); + await expect(page.getByTestId("batch-time-input")).toHaveAttribute( + "placeholder", + "0 (Default from repo)", + ); + + await page.goto(getProjectSettingsRoute(project)); + await expect(page.getByTestId("batch-time-input")).toHaveValue("60"); + await page.getByTestId("batch-time-input").clear(); + await save(page); + await expect(page.getByTestId("batch-time-input")).toHaveValue("0"); + await validateToast(page, "success", "Successfully updated project"); + }); + }); + + test.describe("Variables page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.getByTestId("navitem-variables").click(); + await expectSaveButtonEnabled(page, false); + }); + + test("Successfully saves variables and then promotes them using the promote variables modal", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").fill("a"); + await page.getByTestId("var-value-input").fill("1"); + await page + .getByTestId("var-description-input") + .fill("Description for variable a"); + const privateCheckbox = page.getByRole("checkbox", { name: "Private" }); + await clickCheckbox(privateCheckbox); + + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("b"); + await page.getByTestId("var-value-input").first().fill("2"); + await page + .getByTestId("var-description-input") + .first() + .fill("Description for variable b"); + + await page.getByRole("button", { name: "Add variables" }).click(); + await page.getByTestId("var-name-input").first().fill("c"); + await page.getByTestId("var-value-input").first().fill("3"); + await page + .getByTestId("var-description-input") + .first() + .fill("Description for variable c"); + + await save(page); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); + + await page.getByTestId("promote-vars-button").click(); + await expect(page.getByTestId("promote-vars-modal")).toBeVisible(); + + await expect(page.getByTestId("promote-var-checkbox")).toHaveCount(3); + const variableToPromoteCheckbox = page + .getByTestId("promote-var-checkbox") + .first(); + await clickCheckbox(variableToPromoteCheckbox); + + await page.getByRole("button", { name: "Move 1 variable" }).click(); + await validateToast( + page, + "success", + "Successfully moved variables to repo", + ); + }); + }); + + test.describe("GitHub page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.getByTestId("navitem-github-commitqueue").click(); + }); + + test("Should not have the save button enabled on load", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + }); + + test("Allows overriding repo patch definitions", async ({ + authenticatedPage: page, + }) => { + const githubSection = page.getByTestId("github-card"); + const overrideRepoPatchDefinitionRadio = githubSection.getByRole( + "radio", + { name: "Override Repo Patch Definition", exact: true }, + ); + await overrideRepoPatchDefinitionRadio.click(); + await expect(overrideRepoPatchDefinitionRadio).toBeChecked(); + + await expect( + githubSection.getByTestId("error-banner").filter({ + hasText: + "A GitHub Patch Definition must be specified for this feature to run.", + }), + ).toBeVisible(); + + await githubSection + .getByRole("button", { name: "Add patch definition" }) + .click(); + await githubSection.getByText("Variant Regex").click(); + await githubSection.getByTestId("variant-input").fill(".*"); + await expectSaveButtonEnabled(page, false); + + await githubSection.getByText("Variant Tags").click(); + await githubSection.getByText("Variant Regex").click(); + await expect(githubSection.getByTestId("variant-input")).toHaveValue( + ".*", + ); + await githubSection.getByText("Task Regex").click(); + await githubSection.getByTestId("task-input").fill(".*"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); + + test("Shows a warning banner when a commit check definition does not exist", async ({ + authenticatedPage: page, + }) => { + const enabledRadio = page + .getByTestId("github-checks-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(enabledRadio); + await expect( + page.getByTestId("warning-banner").filter({ + hasText: + "This feature will only run if a Commit Check Definition is defined in the project or repo.", + }), + ).toBeVisible(); + }); + + test("Disables Authorized Users section based on repo settings", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByText("Authorized Users")).toHaveCount(0); + await expect(page.getByText("Authorized Teams")).toHaveCount(0); + }); + + test("Defaults to overriding repo since a patch definition is defined", async ({ + authenticatedPage: page, + }) => { + const overrideRepoPatchDefinitionRadio = page.getByRole("radio", { + name: "Override Repo Patch Definition", + exact: true, + }); + await expect(overrideRepoPatchDefinitionRadio).toBeChecked(); + }); + + test("Shows the existing patch definition", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("variant-input").last()).toHaveValue( + "^ubuntu1604$", + ); + await expect(page.getByTestId("task-input").last()).toHaveValue( + "^smoke-test-endpoints$", + ); + }); + + test("Returns an error on save because no commit check definitions are defined", async ({ + authenticatedPage: page, + }) => { + const prDisabledRadio = page + .getByTestId("pr-testing-enabled-radio-box") + .getByRole("radio", { name: "Disabled", exact: true }); + await clickRadio(prDisabledRadio); + const manualDisabledRadio = page + .getByTestId("manual-pr-testing-enabled-radio-box") + .getByRole("radio", { name: "Disabled", exact: true }); + await clickRadio(manualDisabledRadio); + const githubEnabledRadio = page + .getByTestId("github-checks-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(githubEnabledRadio); + await save(page); + await validateToast( + page, + "error", + "There was an error saving the project", + ); + }); + + test("Defaults to repo and shows the repo's disabled patch definition", async ({ + authenticatedPage: page, + }) => { + await expect( + page + .getByTestId("accordion-toggle") + .filter({ hasText: "Repo Patch Definition 1" }), + ).toHaveCount(0); + + await page.goto(getRepoSettingsRoute(repo)); + await page.getByTestId("navitem-github-commitqueue").click(); + await page.getByRole("button", { name: "Add Patch Definition" }).click(); + await page.getByTestId("variant-tags-input").first().fill("vtag"); + await page.getByTestId("task-tags-input").first().fill("ttag"); + await save(page); + await validateToast(page, "success", "Successfully updated repo", true); + + await page.goto(origin); + await page.getByTestId("navitem-github-commitqueue").click(); + await expect(page.getByTestId("default-to-repo-button")).toHaveAttribute( + "aria-disabled", + "false", + ); + await page.getByTestId("default-to-repo-button").click(); + await expect(page.getByTestId("default-to-repo-modal")).toBeVisible(); + await page + .getByLabel('Type "confirm" to confirm your action') + .fill("confirm"); + await page + .getByTestId("default-to-repo-modal") + .getByRole("button", { name: "Confirm" }) + .click(); + await validateToast( + page, + "success", + "Successfully defaulted page to repo", + ); + await expect( + page + .getByTestId("accordion-toggle") + .filter({ hasText: "Repo Patch Definition 1" }), + ).toBeVisible(); + }); + }); + + test.describe("Patch Aliases page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.getByTestId("navitem-patch-aliases").click(); + await expectSaveButtonEnabled(page, false); + }); + + test("Defaults to repo patch aliases", async ({ + authenticatedPage: page, + }) => { + const defaultToRepoRadio = page.getByRole("radio", { + name: "Default to Repo Patch Aliases", + }); + await expect(defaultToRepoRadio).toHaveAttribute("checked"); + }); + + test("Patch aliases added before defaulting to repo patch aliases are cleared", async ({ + authenticatedPage: page, + }) => { + const overrideRepoPatchAliasesRadio = page.getByRole("radio", { + name: "Override Repo Patch Aliases", + }); + await clickRadio(overrideRepoPatchAliasesRadio); + await expect(overrideRepoPatchAliasesRadio).toBeChecked(); + await expectSaveButtonEnabled(page, false); + + await page.getByRole("button", { name: "Add patch alias" }).click(); + await expectSaveButtonEnabled(page, false); + await page.getByTestId("alias-input").fill("my overriden alias name"); + await page + .getByTestId("variant-tags-input") + .first() + .fill("alias variant tag 2"); + await page + .getByTestId("task-tags-input") + .first() + .fill("alias task tag 2"); + await page.getByRole("button", { name: "Add task tag" }).click(); + await page + .getByTestId("task-tags-input") + .first() + .fill("alias task tag 3"); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + const defaultToRepoRadio = page.getByRole("radio", { + name: "Default to Repo Patch Aliases", + }); + await clickRadio(defaultToRepoRadio); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expectSaveButtonEnabled(page, false); + + await clickRadio(overrideRepoPatchAliasesRadio); + await expect(page.getByTestId("alias-row")).toHaveCount(0); + }); + }); + + test.describe("Virtual Workstation page", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.getByTestId("navitem-virtual-workstation").click(); + }); + + test("Enable git clone", async ({ authenticatedPage: page }) => { + const githubEnabledRadio = page.getByRole("radio", { name: "Enabled" }); + await clickRadio(githubEnabledRadio); + await expect(githubEnabledRadio).toBeChecked(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + }); + + test("Add commands", async ({ authenticatedPage: page }) => { + const defaultToRepoRadio = page.getByRole("radio", { + name: "Default to repo (disabled)", + }); + await expect(defaultToRepoRadio).toBeChecked(); + await expect(page.getByTestId("command-row")).toHaveCount(0); + + await page.getByTestId("attached-repo-link").click(); + await expect(page).toHaveURL( + new RegExp( + getRepoSettingsRoute( + repo, + ProjectSettingsTabRoutes.VirtualWorkstation, + ), + ), + ); + await page.getByRole("button", { name: "Add Command" }).click(); + await page.getByTestId("command-input").fill("a repo command"); + await save(page); + await validateToast(page, "success", "Successfully updated repo", true); + + await page.goto(origin); + await page.getByTestId("navitem-virtual-workstation").click(); + + await expect(page.getByTestId("command-row")).toHaveCount(1); + const repoCommandInput = page.getByTestId("repo-command-input"); + await expect(repoCommandInput).toHaveValue("a repo command"); + await expect(repoCommandInput).toHaveAttribute("aria-disabled", "true"); + + const overrideRepoCommandsRadio = page.getByRole("radio", { + name: "Override Repo Commands", + }); + await clickRadio(overrideRepoCommandsRadio); + + await expect(page.getByTestId("command-row")).toHaveCount(0); + await page.getByRole("button", { name: "Add Command" }).click(); + const projectCommandInput = page.getByTestId("command-input"); + await projectCommandInput.fill("a project command"); + await save(page); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); + await expect(projectCommandInput).toHaveValue("a project command"); + await expect(projectCommandInput).toBeEnabled(); + + const defaultToRepoCommandsRadio = page.getByRole("radio", { + name: "Default to Repo Commands", + }); + await clickRadio(defaultToRepoCommandsRadio); + await expect(page.getByTestId("command-row")).toHaveCount(1); + await expect(repoCommandInput).toHaveValue("a repo command"); + await expect(repoCommandInput).toBeDisabled(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + + await clickRadio(overrideRepoCommandsRadio); + await expect(page.getByTestId("command-row")).toHaveCount(0); + }); + + test("Allows overriding without adding a command", async ({ + authenticatedPage: page, + }) => { + const overrideRepoCommandsRadio = page.getByRole("radio", { + name: "Override Repo Commands", + }); + await clickRadio(overrideRepoCommandsRadio); + await expect(overrideRepoCommandsRadio).toBeChecked(); + await save(page); + await validateToast(page, "success", "Successfully updated project"); + await expect(overrideRepoCommandsRadio).toBeChecked(); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/general_section.spec.ts b/apps/spruce/playwright/tests/repoSettings/general_section.spec.ts new file mode 100644 index 0000000000..02e3c5ee36 --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/general_section.spec.ts @@ -0,0 +1,50 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getRepoSettingsRoute, repo } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("General settings page", () => { + const origin = getRepoSettingsRoute(repo); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + }); + + test("Should have the save button disabled on load", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + }); + + test("Does not show a 'Default to Repo' button on page", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("default-to-repo-button")).toHaveCount(0); + }); + + test("Does not show a 'Move to New Repo' button on page", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("move-repo-button")).toHaveCount(0); + }); + + test("Does not show an Attach/Detach to Repo button on page", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("attach-repo-button")).toHaveCount(0); + }); + + test("Does not show a 'Go to repo settings' link on page", async ({ + authenticatedPage: page, + }) => { + await expect(page.getByTestId("attached-repo-link")).toHaveCount(0); + }); + + test("Inputting a display name then clicking save shows a success toast", async ({ + authenticatedPage: page, + }) => { + await page.getByTestId("display-name-input").fill("evg"); + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/github_section.spec.ts b/apps/spruce/playwright/tests/repoSettings/github_section.spec.ts new file mode 100644 index 0000000000..13b1109436 --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/github_section.spec.ts @@ -0,0 +1,146 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + getRepoSettingsRoute, + projectUseRepoEnabled, + repo, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("GitHub page", () => { + const origin = getRepoSettingsRoute(repo); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-github-commitqueue").click(); + await expectSaveButtonEnabled(page, false); + }); + + test.describe("GitHub section", () => { + test("Shows an error banner when Commit Checks are enabled and hides it when Commit Checks are disabled", async ({ + authenticatedPage: page, + }) => { + const githubChecksEnabledRadio = page + .getByTestId("github-checks-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(githubChecksEnabledRadio); + const errorBanner = page.getByTestId("error-banner").filter({ + hasText: + "A Commit Check Definition must be specified for this feature to run.", + }); + await expect(errorBanner).toBeVisible(); + const githubChecksDisabledRadio = page + .getByTestId("github-checks-enabled-radio-box") + .getByRole("radio", { name: "Disabled", exact: true }); + await clickRadio(githubChecksDisabledRadio); + await expect(errorBanner).toHaveCount(0); + }); + + test("Allows enabling manual PR testing", async ({ + authenticatedPage: page, + }) => { + const enabledRadio = page + .getByTestId("manual-pr-testing-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(enabledRadio); + await expect(enabledRadio).toBeChecked(); + }); + + test("Saving a patch definition should hide the error banner, success toast and displays disabled patch definitions for the repo", async ({ + authenticatedPage: page, + }) => { + const errorBanner = page.getByText( + "A GitHub Patch Definition must be specified for this feature to run.", + ); + await expect(errorBanner).toBeVisible(); + await page.getByRole("button", { name: "Add patch definition" }).click(); + await expect(errorBanner).toHaveCount(0); + await expectSaveButtonEnabled(page, false); + await page.getByTestId("variant-tags-input").first().fill("vtag"); + await page.getByTestId("task-tags-input").first().fill("ttag"); + await expectSaveButtonEnabled(page, true); + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + + await page.goto(getProjectSettingsRoute(projectUseRepoEnabled)); + await page.getByTestId("navitem-github-commitqueue").click(); + const patchDefAccordion = page + .getByTestId("accordion-toggle") + .filter({ hasText: "Repo Patch Definition 1" }); + await patchDefAccordion.click(); + await expect(page.getByTestId("variant-tags-input")).toHaveValue("vtag"); + await expect(page.getByTestId("variant-tags-input")).toHaveAttribute( + "aria-disabled", + "true", + ); + await expect(page.getByTestId("task-tags-input")).toHaveValue("ttag"); + await expect(page.getByTestId("task-tags-input")).toHaveAttribute( + "aria-disabled", + "true", + ); + await expect( + page.getByText( + "A GitHub Patch Definition must be specified for this feature to run.", + ), + ).toHaveCount(0); + }); + }); + + test.describe("Merge Queue section", () => { + test("Enabling merge queue shows hidden inputs and error banner", async ({ + authenticatedPage: page, + }) => { + await expect( + page.getByText("Merge Queue Patch Definitions"), + ).toBeHidden(); + + const mergeQueueEnabledRadio = page + .getByTestId("cq-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(mergeQueueEnabledRadio); + + await expect( + page.getByText("Merge Queue Patch Definitions"), + ).toBeVisible(); + await expect( + page.getByTestId("error-banner").filter({ + hasText: + "A Merge Queue Patch Definition must be specified for this feature to run.", + }), + ).toBeVisible(); + }); + + test("Does not show override buttons for merge queue patch definitions", async ({ + authenticatedPage: page, + }) => { + const mergeQueueEnabledRadio = page + .getByTestId("cq-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(mergeQueueEnabledRadio); + await expect(page.getByTestId("cq-override-radio-box")).toHaveCount(0); + }); + + test("Saves a merge queue definition", async ({ + authenticatedPage: page, + }) => { + const mergeQueueEnabledRadio = page + .getByTestId("cq-enabled-radio-box") + .getByRole("radio", { name: "Enabled" }); + await clickRadio(mergeQueueEnabledRadio); + await page.getByRole("button", { name: "Add patch definition" }).click(); + await page.getByTestId("variant-tags-input").first().fill("vtag"); + await page.getByTestId("task-tags-input").first().fill("ttag"); + await expectSaveButtonEnabled(page, false); + await page + .getByRole("button", { name: "Add merge queue patch definition" }) + .click(); + await page.getByTestId("variant-tags-input").last().fill("cqvtag"); + await page.getByTestId("task-tags-input").last().fill("cqttag"); + await expect(page.getByTestId("warning-banner")).toHaveCount(0); + await expect(page.getByTestId("error-banner")).toHaveCount(0); + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + }); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts b/apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts new file mode 100644 index 0000000000..767829a8fa --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts @@ -0,0 +1,146 @@ +import { test, expect } from "../../fixtures"; +import { clickRadio, validateToast } from "../../helpers"; +import { + getProjectSettingsRoute, + getRepoSettingsRoute, + ProjectSettingsTabRoutes, + projectUseRepoEnabled, + repo, +} from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Patch Aliases page", () => { + const origin = getRepoSettingsRoute(repo); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-patch-aliases").click(); + await expectSaveButtonEnabled(page, false); + await expect( + page.getByTestId("patch-aliases-override-radio-box"), + ).toHaveCount(0); + }); + + test("Saving a patch alias shows a success toast, the alias name in the card title and in the repo defaulted project", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add patch alias" }).click(); + await expect( + page + .getByTestId("expandable-card-title") + .filter({ hasText: "New Patch Alias" }), + ).toBeVisible(); + await page.getByTestId("alias-input").fill("my alias name"); + await expectSaveButtonEnabled(page, false); + await page + .getByTestId("variant-tags-input") + .first() + .fill("alias variant tag"); + await page.getByTestId("task-tags-input").first().fill("alias task tag"); + await save(page); + await validateToast(page, "success", "Successfully updated repo", true); + await expect( + page + .getByTestId("expandable-card-title") + .filter({ hasText: "my alias name" }), + ).toBeVisible(); + + await page.reload(); + await expect( + page + .getByTestId("expandable-card-title") + .filter({ hasText: "my alias name" }), + ).toBeVisible(); + + await page.goto( + getProjectSettingsRoute( + projectUseRepoEnabled, + ProjectSettingsTabRoutes.Access, + ), + ); + await expect(page.getByTestId("default-to-repo-button")).toHaveAttribute( + "aria-disabled", + "false", + ); + await page.getByTestId("default-to-repo-button").click(); + await expect(page.getByTestId("default-to-repo-modal")).toBeVisible(); + await page + .getByLabel('Type "confirm" to confirm your action') + .fill("confirm"); + await page + .getByTestId("default-to-repo-modal") + .getByRole("button", { name: "Confirm" }) + .click(); + await validateToast(page, "success", "Successfully defaulted page to repo"); + await page.getByTestId("navitem-patch-aliases").click(); + await expect( + page + .getByTestId("expandable-card-title") + .filter({ hasText: "my alias name" }), + ).toBeVisible(); + + const cardTitle = page + .getByTestId("expandable-card-title") + .filter({ hasText: "my alias name" }); + await cardTitle.click(); + await expect( + page.getByTestId("expandable-card").locator("input").first(), + ).toHaveAttribute("aria-disabled", "true"); + }); + + test("Saving a Patch Trigger Alias shows a success toast and updates the Github page", async ({ + authenticatedPage: page, + }) => { + await page.getByRole("button", { name: "Add patch trigger alias" }).click(); + await page.getByTestId("pta-alias-input").fill("my-alias"); + await page.getByTestId("project-input").fill("spruce"); + await page.getByTestId("module-input").fill("module_name"); + await page.getByText("Variant/Task", { exact: true }).click(); + await page.getByTestId("variant-regex-input").fill(".*"); + await page.getByTestId("task-regex-input").fill(".*"); + + const pullRequestCheckbox = page.getByRole("checkbox", { + name: "Schedule in GitHub Pull Requests", + }); + await expect(pullRequestCheckbox).not.toBeChecked(); + await clickRadio(pullRequestCheckbox); + await expect(pullRequestCheckbox).toBeChecked(); + + const mergeQueueCheckbox = page.getByRole("checkbox", { + name: "Schedule in GitHub Merge Queue", + }); + await expect(mergeQueueCheckbox).not.toBeChecked(); + await clickRadio(mergeQueueCheckbox); + await expect(mergeQueueCheckbox).toBeChecked(); + + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + await expectSaveButtonEnabled(page, false); + + await page.getByTestId("navitem-github-commitqueue").click(); + + const prTriggerAliases = page.getByTestId("github-pr-trigger-aliases"); + await expect(prTriggerAliases.getByTestId("pta-item")).toHaveCount(1); + await expect(prTriggerAliases.getByText("my-alias")).toBeVisible(); + await prTriggerAliases.getByTestId("pta-item").hover(); + await expect(page.getByTestId("pta-tooltip")).toHaveCount(1); + await expect(page.getByTestId("pta-tooltip")).toBeVisible(); + await expect(page.getByTestId("pta-tooltip")).toContainText("spruce"); + await expect(page.getByTestId("pta-tooltip")).toContainText("module_name"); + await expect(page.getByTestId("pta-tooltip")).toContainText( + "Variant/Task Regex Pairs", + ); + + const mqTriggerAliases = page.getByTestId("github-mq-trigger-aliases"); + await expect(mqTriggerAliases.getByTestId("pta-item")).toHaveCount(1); + await expect(mqTriggerAliases.getByText("my-alias")).toBeVisible(); + await mqTriggerAliases.getByTestId("pta-item").hover(); + await expect(page.getByTestId("pta-tooltip")).toHaveCount(1); + await expect(page.getByTestId("pta-tooltip")).toBeVisible(); + await expect(page.getByTestId("pta-tooltip")).toContainText("spruce"); + await expect(page.getByTestId("pta-tooltip")).toContainText("module_name"); + await expect(page.getByTestId("pta-tooltip")).toContainText( + "Variant/Task Regex Pairs", + ); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/permissions.spec.ts b/apps/spruce/playwright/tests/repoSettings/permissions.spec.ts new file mode 100644 index 0000000000..bed1a1105d --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/permissions.spec.ts @@ -0,0 +1,32 @@ +import { users } from "@evg-ui/playwright-config/constants"; +import { test, expect } from "../../fixtures"; +import { login, logout } from "../../helpers"; +import { getRepoSettingsRoute, repo } from "./constants"; + +test.describe("permissions", () => { + test.beforeEach(async ({ authenticatedPage: page }) => { + await logout(page); + }); + + test("disables fields when user lacks edit permissions", async ({ + authenticatedPage: page, + }) => { + await login(page, users.privileged); + await page.goto(getRepoSettingsRoute(repo)); + const settingsPage = page.getByTestId("repo-settings-page"); + await expect( + settingsPage.locator('input[type="radio"]').first(), + ).toBeDisabled(); + }); + + test("enables fields if user has edit permissions", async ({ + authenticatedPage: page, + }) => { + await login(page, users.admin); + await page.goto(getRepoSettingsRoute(repo)); + const settingsPage = page.getByTestId("repo-settings-page"); + await expect( + settingsPage.locator('input[type="radio"]').first(), + ).toBeEnabled(); + }); +}); diff --git a/apps/spruce/playwright/tests/repoSettings/utils.ts b/apps/spruce/playwright/tests/repoSettings/utils.ts new file mode 100644 index 0000000000..3f321fffb0 --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/utils.ts @@ -0,0 +1,25 @@ +import { Page } from "@playwright/test"; +import { expect } from "../../fixtures"; + +export const save = async (page: Page) => { + const saveButton = page.getByTestId("save-settings-button"); + await expect(saveButton).toHaveAttribute("aria-disabled", "false"); + await saveButton.click(); + + const saveChangesModal = page.getByTestId("save-changes-modal"); + await expect(saveChangesModal).toBeVisible(); + await saveChangesModal.getByRole("button", { name: "Save changes" }).click(); + await expect(saveChangesModal).toBeHidden(); +}; + +export const expectSaveButtonEnabled = async ( + page: Page, + isEnabled: boolean = true, +) => { + const saveButton = page.getByTestId("save-settings-button"); + if (isEnabled) { + await expect(saveButton).toHaveAttribute("aria-disabled", "false"); + } else { + await expect(saveButton).toHaveAttribute("aria-disabled", "true"); + } +}; diff --git a/apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts b/apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts new file mode 100644 index 0000000000..8bcab2e801 --- /dev/null +++ b/apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts @@ -0,0 +1,38 @@ +import { test, expect } from "../../fixtures"; +import { validateToast } from "../../helpers"; +import { getRepoSettingsRoute, repo } from "./constants"; +import { expectSaveButtonEnabled, save } from "./utils"; + +test.describe("Virtual Workstation page", () => { + const origin = getRepoSettingsRoute(repo); + + test.beforeEach(async ({ authenticatedPage: page }) => { + await page.goto(origin); + await page.getByTestId("navitem-virtual-workstation").click(); + }); + + test("Adds two commands and then reorders them", async ({ + authenticatedPage: page, + }) => { + await expectSaveButtonEnabled(page, false); + + const addCommandButton = page.getByRole("button", { name: "Add Command" }); + await addCommandButton.click(); + await page.getByTestId("command-input").fill("command 1"); + await page.getByTestId("directory-input").fill("mongodb.user.directory"); + + await addCommandButton.click(); + await page.getByTestId("command-input").nth(1).fill("command 2"); + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + await page.getByTestId("array-down-button").click(); + await save(page); + await validateToast(page, "success", "Successfully updated repo"); + await expect(page.getByTestId("command-input").first()).toHaveValue( + "command 2", + ); + await expect(page.getByTestId("command-input").nth(1)).toHaveValue( + "command 1", + ); + }); +}); 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/projectAndRepoSettings/shared/tabs/GeneralTab/Fields/RepoConfigField.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GeneralTab/Fields/RepoConfigField.tsx index 87dffe77d0..de40261ecc 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GeneralTab/Fields/RepoConfigField.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GeneralTab/Fields/RepoConfigField.tsx @@ -62,7 +62,7 @@ export const RepoConfigField: Field = ({ onClick={() => setMoveModalOpen(true)} size="small" > - Move to New Repo + Move to new repo {isAttachedProject - ? "Detach from Current Repo" - : "Attach to Current Repo"} + ? "Detach from current repo" + : "Attach to current repo"} } triggerEvent="hover" 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..2797f2ffe2 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx @@ -319,7 +319,7 @@ export const getFormSchema = ( "ui:description": PRAliasesDescription, githubPrAliases: { ...aliasRowUiSchema({ - addButtonText: "Add Patch Definition", + addButtonText: "Add patch definition", numberedTitle: "Patch Definition", }), }, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/NotificationsTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/NotificationsTab/getFormSchema.tsx index 21dfa43492..83a31dddb6 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/NotificationsTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/NotificationsTab/getFormSchema.tsx @@ -138,7 +138,7 @@ export const getFormSchema = ( subscriptions: { "ui:placeholder": "No subscriptions are defined.", "ui:descriptionNode": , - "ui:addButtonText": "Add Subscription", + "ui:addButtonText": "Add subscription", "ui:orderable": false, "ui:useExpandableCard": true, items: { diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PatchAliasesTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PatchAliasesTab/getFormSchema.tsx index 966f180bab..35c684120a 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PatchAliasesTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PatchAliasesTab/getFormSchema.tsx @@ -197,7 +197,7 @@ export const getFormSchema = ( }); const aliasesUiSchema = { - "ui:addButtonText": "Add Patch Trigger Alias", + "ui:addButtonText": "Add patch trigger alias", "ui:orderable": false, "ui:showLabel": false, "ui:useExpandableCard": true, @@ -220,7 +220,7 @@ const aliasesUiSchema = { "ui:allowDeselect": false, }, taskSpecifiers: { - "ui:addButtonText": "Add Task Regex Pair", + "ui:addButtonText": "Add task regex pair", "ui:orderable": false, "ui:showLabel": false, "ui:topAlignDelete": true, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PeriodicBuildsTab/getFormSchema.ts b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PeriodicBuildsTab/getFormSchema.ts index 644adaf440..62a73ae72b 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PeriodicBuildsTab/getFormSchema.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PeriodicBuildsTab/getFormSchema.ts @@ -133,7 +133,7 @@ export const getFormSchema = ( "ui:showLabel": false, }, periodicBuilds: { - "ui:addButtonText": "Add Periodic Build", + "ui:addButtonText": "Add periodic build", "ui:orderable": false, "ui:showLabel": false, "ui:useExpandableCard": true, 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..c270af5a3e 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/PluginsTab/getFormSchema.tsx @@ -213,7 +213,7 @@ export const getFormSchema = ( ticketSearchProjects: { "ui:description": "Specify an existing JIRA project to search for tickets related to a failing task.", - "ui:addButtonText": "Add Search Project", + "ui:addButtonText": "Add search project", "ui:orderable": false, items: { "ui:label": false, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ProjectTriggersTab/getFormSchema.tsx b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ProjectTriggersTab/getFormSchema.tsx index f96bb33e9f..53d92196f8 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ProjectTriggersTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ProjectTriggersTab/getFormSchema.tsx @@ -119,7 +119,7 @@ export const getFormSchema = ( "ui:showLabel": false, }, triggers: { - "ui:addButtonText": "Add Project Trigger", + "ui:addButtonText": "Add project trigger", "ui:orderable": false, "ui:showLabel": false, "ui:useExpandableCard": true, 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..b5cf100cd7 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/VirtualWorkstationTab/getFormSchema.ts @@ -68,7 +68,7 @@ export const getFormSchema = ( "ui:showLabel": false, }, setupCommands: { - "ui:addButtonText": "Add Command", + "ui:addButtonText": "Add command", "ui:addToEnd": true, "ui:border": true, "ui:fullWidth": true, diff --git a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/utils/alias.ts b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/utils/alias.ts index 04ea34b3c8..3c1542c97e 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/utils/alias.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/utils/alias.ts @@ -308,7 +308,7 @@ export const baseProps = { }, uiSchema: { "ui:addButtonSize": "xsmall", - "ui:addButtonText": "Add Task Tag", + "ui:addButtonText": "Add task tag", "ui:orderable": false, "ui:sectionId": "task-tags-field", "ui:showLabel": false, @@ -349,7 +349,7 @@ export const baseProps = { }, uiSchema: { "ui:addButtonSize": "xsmall", - "ui:addButtonText": "Add Variant Tag", + "ui:addButtonText": "Add variant tag", "ui:orderable": false, "ui:sectionId": "variant-tags-field", "ui:showLabel": false, @@ -574,7 +574,7 @@ export const gitTagArray = { }, }, uiSchema: { - "ui:addButtonText": "Add Git Tag", + "ui:addButtonText": "Add git tag", "ui:orderable": false, "ui:showLabel": false, "ui:topAlignDelete": true, @@ -664,7 +664,7 @@ export const patchAliasArray = { }, }, uiSchema: aliasRowUiSchema({ - addButtonText: "Add Patch Alias", + addButtonText: "Add patch alias", displayTitle: "New Patch Alias", aliasHidden: false, useExpandableCard: true, From bace9cbc76c7f484acfe9cea28477e9ac8c1626e Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 15:55:58 -0400 Subject: [PATCH 02/18] fix: failing tests --- .../projectSettings/access_section.spec.ts | 5 +- .../projectSettings/general_section.spec.ts | 10 ++-- .../github_permission_groups.spec.ts | 10 ++-- .../projectSettings/views_and_filters.spec.ts | 6 +-- .../repoSettings/defaulting_to_repo.spec.ts | 54 +++++++++++-------- .../GithubCommitQueueTab/getFormSchema.tsx | 1 + .../getFormSchema.tsx | 1 + .../tabs/ViewsAndFiltersTab/getFormSchema.ts | 1 + 8 files changed, 52 insertions(+), 36 deletions(-) diff --git a/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts b/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts index 4772a25f45..72495217ec 100644 --- a/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts +++ b/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts @@ -27,7 +27,10 @@ test.describe("Access page", () => { test("Changing settings and clicking the save button produces a success toast and the changes are persisted", async ({ authenticatedPage: page, }) => { - const unrestrictedRadio = page.getByRole("radio", { name: "Unrestricted" }); + const unrestrictedRadio = page.getByRole("radio", { + name: "Unrestricted", + exact: true, + }); await clickRadio(unrestrictedRadio); await expect(unrestrictedRadio).toBeChecked(); diff --git a/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts b/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts index 9d6fd87b46..3a9dd806b0 100644 --- a/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts +++ b/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts @@ -7,15 +7,12 @@ import { } from "./constants"; import { save } from "./utils"; +const origin = getProjectSettingsRoute(project); + test.describe("general section", () => { test.describe("Renaming the identifier", () => { - const origin = getProjectSettingsRoute(project); - - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.goto(origin); - }); - test("Update identifier", async ({ authenticatedPage: page }) => { + await page.goto(origin); 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."; @@ -34,6 +31,7 @@ test.describe("general section", () => { test("Allows enabling Run Every Mainline Commit", async ({ authenticatedPage: page, }) => { + await page.goto(origin); await page.getByTestId("navitem-general").click(); const enableRadio = page .getByTestId("run-every-mainline-commit-radio-box") diff --git a/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts b/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts index 605f81f7b7..30fff69de4 100644 --- a/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts +++ b/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts @@ -17,7 +17,7 @@ test.describe("GitHub permission groups", () => { test("should not have any permission groups defined", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("permission-group-item")).toHaveCount(0); + await expect(page.getByTestId("permission-group")).toHaveCount(0); await expectSaveButtonEnabled(page, false); }); @@ -29,7 +29,7 @@ test.describe("GitHub permission groups", () => { }); await expect(addPermissionGroupButton).toBeVisible(); await addPermissionGroupButton.click(); - await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + await expect(page.getByTestId("permission-group")).toHaveCount(1); const invalidGithubPermission = "invalid_github_permission"; await page @@ -55,7 +55,7 @@ test.describe("GitHub permission groups", () => { }); await expect(addPermissionGroupButton).toBeVisible(); await addPermissionGroupButton.click(); - await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + await expect(page.getByTestId("permission-group")).toHaveCount(1); await page .getByTestId("permission-group-title-input") @@ -70,9 +70,9 @@ test.describe("GitHub permission groups", () => { await validateToast(page, "success", "Successfully updated project"); await page.reload(); - await expect(page.getByTestId("permission-group-item")).toHaveCount(1); + await expect(page.getByTestId("permission-group")).toHaveCount(1); await page.getByTestId("delete-item-button").click(); - await expect(page.getByTestId("permission-group-item")).toHaveCount(0); + await expect(page.getByTestId("permission-group")).toHaveCount(0); await save(page); await validateToast(page, "success", "Successfully updated project"); }); diff --git a/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts b/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts index 745885365d..a12d9fe641 100644 --- a/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts +++ b/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts @@ -11,7 +11,7 @@ test.describe("Views & filters page", () => { test.beforeEach(async ({ authenticatedPage: page }) => { await page.goto(destination); - await expect(page.getByTestId("parsley-filter-item")).toHaveCount(2); + await expect(page.getByTestId("parsley-filter")).toHaveCount(2); await expectSaveButtonEnabled(page, false); }); @@ -54,12 +54,12 @@ test.describe("Views & filters page", () => { await expectSaveButtonEnabled(page, true); await save(page); await validateToast(page, "success", "Successfully updated project"); - await expect(page.getByTestId("parsley-filter-item")).toHaveCount(3); + await expect(page.getByTestId("parsley-filter")).toHaveCount(3); await page.getByTestId("delete-item-button").first().click(); await save(page); await validateToast(page, "success", "Successfully updated project"); - await expect(page.getByTestId("parsley-filter-item")).toHaveCount(2); + await expect(page.getByTestId("parsley-filter")).toHaveCount(2); }); }); }); diff --git a/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts index e137f6f402..ef79cb4152 100644 --- a/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts +++ b/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts @@ -225,8 +225,11 @@ test.describe("Project Settings when defaulting to repo", () => { "radio", { name: "Override Repo Patch Definition", exact: true }, ); - await overrideRepoPatchDefinitionRadio.click(); - await expect(overrideRepoPatchDefinitionRadio).toBeChecked(); + await clickRadio(overrideRepoPatchDefinitionRadio); + await expect(overrideRepoPatchDefinitionRadio).toHaveAttribute( + "aria-checked", + "true", + ); await expect( githubSection.getByTestId("error-banner").filter({ @@ -278,11 +281,16 @@ test.describe("Project Settings when defaulting to repo", () => { test("Defaults to overriding repo since a patch definition is defined", async ({ authenticatedPage: page, }) => { - const overrideRepoPatchDefinitionRadio = page.getByRole("radio", { - name: "Override Repo Patch Definition", - exact: true, - }); - await expect(overrideRepoPatchDefinitionRadio).toBeChecked(); + const overrideRepoPatchDefinitionRadio = page + .getByTestId("cq-override-radio-box") + .getByRole("radio", { + name: "Override Repo Patch Definition", + exact: true, + }); + await expect(overrideRepoPatchDefinitionRadio).toHaveAttribute( + "aria-checked", + "true", + ); }); test("Shows the existing patch definition", async ({ @@ -386,7 +394,10 @@ test.describe("Project Settings when defaulting to repo", () => { name: "Override Repo Patch Aliases", }); await clickRadio(overrideRepoPatchAliasesRadio); - await expect(overrideRepoPatchAliasesRadio).toBeChecked(); + await expect(overrideRepoPatchAliasesRadio).toHaveAttribute( + "aria-checked", + "true", + ); await expectSaveButtonEnabled(page, false); await page.getByRole("button", { name: "Add patch alias" }).click(); @@ -458,20 +469,21 @@ test.describe("Project Settings when defaulting to repo", () => { await page.goto(origin); await page.getByTestId("navitem-virtual-workstation").click(); - await expect(page.getByTestId("command-row")).toHaveCount(1); - const repoCommandInput = page.getByTestId("repo-command-input"); - await expect(repoCommandInput).toHaveValue("a repo command"); - await expect(repoCommandInput).toHaveAttribute("aria-disabled", "true"); + const commandRow = page.getByTestId("command-row"); + + await expect(commandRow).toHaveCount(1); + const commandInput = commandRow.getByRole("textbox", { name: "Command" }); + await expect(commandInput).toHaveValue("a repo command"); + await expect(commandInput).toBeDisabled(); const overrideRepoCommandsRadio = page.getByRole("radio", { name: "Override Repo Commands", }); await clickRadio(overrideRepoCommandsRadio); - await expect(page.getByTestId("command-row")).toHaveCount(0); + await expect(commandRow).toHaveCount(0); await page.getByRole("button", { name: "Add Command" }).click(); - const projectCommandInput = page.getByTestId("command-input"); - await projectCommandInput.fill("a project command"); + await commandInput.fill("a project command"); await save(page); await validateToast( page, @@ -479,21 +491,21 @@ test.describe("Project Settings when defaulting to repo", () => { "Successfully updated project", true, ); - await expect(projectCommandInput).toHaveValue("a project command"); - await expect(projectCommandInput).toBeEnabled(); + await expect(commandInput).toHaveValue("a project command"); + await expect(commandInput).toBeEnabled(); const defaultToRepoCommandsRadio = page.getByRole("radio", { name: "Default to Repo Commands", }); await clickRadio(defaultToRepoCommandsRadio); - await expect(page.getByTestId("command-row")).toHaveCount(1); - await expect(repoCommandInput).toHaveValue("a repo command"); - await expect(repoCommandInput).toBeDisabled(); + await expect(commandRow).toHaveCount(1); + await expect(commandInput).toHaveValue("a repo command"); + await expect(commandInput).toBeDisabled(); await save(page); await validateToast(page, "success", "Successfully updated project"); await clickRadio(overrideRepoCommandsRadio); - await expect(page.getByTestId("command-row")).toHaveCount(0); + await expect(commandRow).toHaveCount(0); }); test("Allows overriding without adding a command", async ({ 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 2797f2ffe2..910a19465a 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubCommitQueueTab/getFormSchema.tsx @@ -243,6 +243,7 @@ export const getFormSchema = ( uiSchema: { github: { "ui:ObjectFieldTemplate": CardFieldTemplate, + "ui:data-cy": "github-card", 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..5a481e0a8b 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/GithubPermissionGroupsTab/getFormSchema.tsx @@ -168,6 +168,7 @@ const permissionCss = css` `; const itemsUISchema = { + "ui:data-cy": "permission-group", "ui:displayTitle": "New Permission Group", name: { "ui:ariaLabelledBy": "Permission Group Name", 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..d96cc38d39 100644 --- a/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts +++ b/apps/spruce/src/pages/projectAndRepoSettings/shared/tabs/ViewsAndFiltersTab/getFormSchema.ts @@ -95,6 +95,7 @@ export const getFormSchema = ( "ui:useExpandableCard": true, "ui:data-cy": "parsley-filter-list", items: { + "ui:data-cy": "parsley-filter", "ui:displayTitle": "New Parsley Filter", "ui:label": false, expression: { From 8d6712612149fd406b8e1254505361d1757d297c Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 16:09:27 -0400 Subject: [PATCH 03/18] delete the old tests lol --- .../integration/projectSettings/access.ts | 69 ---- .../projectSettings/admin_actions.ts | 77 ---- .../projectSettings/attaching_to_repo.ts | 40 -- .../projectSettings/commit_checks.ts | 65 --- .../integration/projectSettings/constants.ts | 44 --- .../projectSettings/defaulting_to_repo.ts | 372 ------------------ .../projectSettings/github_app_settings.ts | 111 ------ .../github_permission_groups.ts | 68 ---- .../projectSettings/not_defaulting_to_repo.ts | 274 ------------- .../projectSettings/notifications.ts | 131 ------ .../projectSettings/permissions.ts | 65 --- .../integration/projectSettings/plugins.ts | 79 ---- .../projectSettings/project_select.ts | 20 - .../projectSettings/project_settings.ts | 60 --- .../projectSettings/pull_requests.ts | 82 ---- .../projectSettings/repo_settings.ts | 300 -------------- .../projectSettings/stepback_bisect.ts | 65 --- .../integration/projectSettings/utils.ts | 12 - .../projectSettings/views_and_filters.ts | 53 --- 19 files changed, 1987 deletions(-) delete mode 100644 apps/spruce/cypress/integration/projectSettings/access.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/admin_actions.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/attaching_to_repo.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/commit_checks.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/constants.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/defaulting_to_repo.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/github_app_settings.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/github_permission_groups.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/not_defaulting_to_repo.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/notifications.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/permissions.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/plugins.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/project_select.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/project_settings.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/pull_requests.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/repo_settings.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/stepback_bisect.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/utils.ts delete mode 100644 apps/spruce/cypress/integration/projectSettings/views_and_filters.ts 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/commit_checks.ts b/apps/spruce/cypress/integration/projectSettings/commit_checks.ts deleted file mode 100644 index e1d0743ed8..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/commit_checks.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - getProjectSettingsRoute, - project, - ProjectSettingsTabRoutes, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("A project that has GitHub webhooks disabled", () => { - const origin = getProjectSettingsRoute( - "logkeeper", - ProjectSettingsTabRoutes.CommitChecks, - ); - beforeEach(() => { - cy.visit(origin); - saveButtonEnabled(false); - }); - - it("Commit Checks page shows a disabled webhooks banner when webhooks are disabled", () => { - cy.dataCy("disabled-webhook-banner") - .contains( - "GitHub features are disabled because the Evergreen GitHub App is not", - ) - .should("be.visible"); - }); - - 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 that has GitHub webhooks enabled", () => { - const origin = getProjectSettingsRoute( - project, - ProjectSettingsTabRoutes.CommitChecks, - ); - beforeEach(() => { - cy.visit(origin); - saveButtonEnabled(false); - }); - - 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").children().first().click(); - cy.contains( - "A Commit Check Definition must be specified for this feature to run.", - ).as("errorBanner"); - cy.dataCy("error-banner").should("be.visible"); - cy.dataCy("github-checks-enabled-radio-box").children().last().click(); - cy.dataCy("error-banner").should("not.exist"); - }); - - it("Saves successfully when Commit Checks are enabled and a Commit Check Definition is provided", () => { - cy.dataCy("github-checks-enabled-radio-box").children().first().click(); - cy.contains("button", "Add definition").click(); - cy.dataCy("variant-tags-input").first().type("vtag"); - cy.dataCy("task-tags-input").first().type("ttag"); - - cy.dataCy("error-banner").should("not.exist"); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated project"); - }); -}); diff --git a/apps/spruce/cypress/integration/projectSettings/constants.ts b/apps/spruce/cypress/integration/projectSettings/constants.ts deleted file mode 100644 index 8d77252b87..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/constants.ts +++ /dev/null @@ -1,44 +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", - PullRequests = "pull-requests", - CommitChecks = "commit-checks", -} - -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 764c2a2a41..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/pull_requests.ts b/apps/spruce/cypress/integration/projectSettings/pull_requests.ts deleted file mode 100644 index cdffe03207..0000000000 --- a/apps/spruce/cypress/integration/projectSettings/pull_requests.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - getProjectSettingsRoute, - getRepoSettingsRoute, - ProjectSettingsTabRoutes, - projectUseRepoEnabled, - repo, - saveButtonEnabled, -} from "./constants"; -import { clickSaveAndConfirmDiff } from "./utils"; - -describe("A project that has GitHub webhooks disabled", () => { - const origin = getProjectSettingsRoute( - "logkeeper", - ProjectSettingsTabRoutes.PullRequests, - ); - beforeEach(() => { - cy.visit(origin); - saveButtonEnabled(false); - }); - - it("Pull Requests page shows a disabled webhooks banner when webhooks are disabled", () => { - cy.dataCy("disabled-webhook-banner") - .contains( - "GitHub features are disabled because the Evergreen GitHub App is not", - ) - .should("be.visible"); - }); - - 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 that has GitHub webhooks enabled", () => { - beforeEach(() => { - const origin = getRepoSettingsRoute(repo); - cy.visit(origin); - cy.dataCy("navitem-pull-requests").click(); - saveButtonEnabled(false); - }); - - it("Allows enabling manual PR testing", () => { - cy.dataCy("manual-pr-testing-enabled-radio-box").children().first().click(); - }); - - it("Saving a patch defintion should hide the error banner, success toast and displays disable patch definitions for the repo", () => { - cy.contains( - "A GitHub Patch Definition must be specified for this feature to run.", - ).as("errorBanner"); - cy.get("@errorBanner").should("be.visible"); - cy.contains("button", "Add patch definition").click(); - cy.get("@errorBanner").should("not.exist"); - saveButtonEnabled(false); - - cy.dataCy("variant-tags-input").first().type("vtag"); - cy.dataCy("task-tags-input").first().type("ttag"); - saveButtonEnabled(true); - clickSaveAndConfirmDiff(); - cy.validateToast("success", "Successfully updated repo"); - - cy.visit(getProjectSettingsRoute(projectUseRepoEnabled)); - cy.dataCy("navitem-pull-requests").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"); - }); -}); 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); - }); - }); -}); From bae6d2175f073b0b69fa71942bcfbef671dd0226 Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 16:15:39 -0400 Subject: [PATCH 04/18] share `utils` --- .../constants.ts | 0 .../projectSettings/access_section.spec.ts | 8 ++--- .../projectSettings/admin_actions.spec.ts | 6 ++-- .../projectSettings/commit_checks.spec.ts | 8 ++--- .../projectSettings/general_section.spec.ts | 8 ++--- .../github_app_settings.spec.ts | 11 ++++--- .../github_permission_groups.spec.ts | 11 ++++--- .../projectSettings/github_section.spec.ts | 8 ++--- .../projectSettings/navigation.spec.ts | 4 +-- .../not_defaulting_to_repo.spec.ts | 8 ++--- .../projectSettings/notifications.spec.ts | 11 ++++--- .../projectSettings/periodic_builds.spec.ts | 8 ++--- .../projectSettings/permissions.spec.ts | 6 ++-- .../projectSettings/plugins.spec.ts | 6 ++-- .../projectSettings/project_triggers.spec.ts | 4 +-- .../projectSettings/pull_requests.spec.ts | 8 ++--- .../projectSettings/variables.spec.ts | 8 ++--- .../projectSettings/views_and_filters.spec.ts | 11 ++++--- .../repoSettings/attaching_to_repo.spec.ts | 8 ++--- .../repoSettings/defaulting_to_repo.spec.ts | 8 ++--- .../repoSettings/general_section.spec.ts | 8 ++--- .../repoSettings/github_section.spec.ts | 8 ++--- .../repoSettings/patch_aliases.spec.ts | 8 ++--- .../repoSettings/permissions.spec.ts | 6 ++-- .../repoSettings/virtual_workstation.spec.ts | 8 ++--- .../utils.ts | 2 +- .../playwright/tests/projectSettings/utils.ts | 25 --------------- .../tests/repoSettings/constants.ts | 32 ------------------- 28 files changed, 101 insertions(+), 146 deletions(-) rename apps/spruce/playwright/tests/{projectSettings => projectAndRepoSettings}/constants.ts (100%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/access_section.spec.ts (94%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/admin_actions.spec.ts (95%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/commit_checks.spec.ts (94%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/general_section.spec.ts (95%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/github_app_settings.spec.ts (92%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/github_permission_groups.spec.ts (92%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/github_section.spec.ts (81%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/navigation.spec.ts (91%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/not_defaulting_to_repo.spec.ts (85%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/notifications.spec.ts (95%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/periodic_builds.spec.ts (92%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/permissions.spec.ts (90%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/plugins.spec.ts (96%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/project_triggers.spec.ts (85%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/pull_requests.spec.ts (94%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/variables.spec.ts (96%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/views_and_filters.spec.ts (90%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/attaching_to_repo.spec.ts (89%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/defaulting_to_repo.spec.ts (99%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/general_section.spec.ts (86%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/github_section.spec.ts (96%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/patch_aliases.spec.ts (96%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/permissions.spec.ts (85%) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/repoSettings/virtual_workstation.spec.ts (84%) rename apps/spruce/playwright/tests/{repoSettings => projectAndRepoSettings}/utils.ts (94%) delete mode 100644 apps/spruce/playwright/tests/projectSettings/utils.ts delete mode 100644 apps/spruce/playwright/tests/repoSettings/constants.ts diff --git a/apps/spruce/playwright/tests/projectSettings/constants.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts similarity index 100% rename from apps/spruce/playwright/tests/projectSettings/constants.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts diff --git a/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/access_section.spec.ts similarity index 94% rename from apps/spruce/playwright/tests/projectSettings/access_section.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/access_section.spec.ts index 72495217ec..51edda5df7 100644 --- a/apps/spruce/playwright/tests/projectSettings/access_section.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/access_section.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, project, ProjectSettingsTabRoutes, projectUseRepoEnabled, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Access page", () => { const origin = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/admin_actions.spec.ts similarity index 95% rename from apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/admin_actions.spec.ts index c62ba20cb2..e421326484 100644 --- a/apps/spruce/playwright/tests/projectSettings/admin_actions.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/admin_actions.spec.ts @@ -1,6 +1,6 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; test.describe("projectSettings/admin_actions", () => { test.describe("Duplicating a project", () => { diff --git a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts similarity index 94% rename from apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts index 97d8166d2a..7693cf0bbd 100644 --- a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts @@ -1,11 +1,11 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, project, ProjectSettingsTabRoutes, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("A project that has GitHub webhooks disabled", () => { const destination = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/general_section.spec.ts similarity index 95% rename from apps/spruce/playwright/tests/projectSettings/general_section.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/general_section.spec.ts index 3a9dd806b0..47d4cadbf5 100644 --- a/apps/spruce/playwright/tests/projectSettings/general_section.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/general_section.spec.ts @@ -1,11 +1,11 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, project, projectUseRepoEnabled, -} from "./constants"; -import { save } from "./utils"; +} from "../constants"; +import { save } from "../utils"; const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_app_settings.spec.ts similarity index 92% rename from apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_app_settings.spec.ts index ed12cdc0f0..8fa224d3fb 100644 --- a/apps/spruce/playwright/tests/projectSettings/github_app_settings.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_app_settings.spec.ts @@ -1,7 +1,10 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("GitHub app settings", () => { const destination = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_permission_groups.spec.ts similarity index 92% rename from apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_permission_groups.spec.ts index 30fff69de4..42772abf2b 100644 --- a/apps/spruce/playwright/tests/projectSettings/github_permission_groups.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_permission_groups.spec.ts @@ -1,7 +1,10 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("GitHub permission groups", () => { const destination = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/github_section.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_section.spec.ts similarity index 81% rename from apps/spruce/playwright/tests/projectSettings/github_section.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_section.spec.ts index bcc022ccd8..481313b154 100644 --- a/apps/spruce/playwright/tests/projectSettings/github_section.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/github_section.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { validateToast, clickRadio } from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; -import { save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast, clickRadio } from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; +import { save } from "../utils"; test.describe("GitHub page", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/navigation.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/navigation.spec.ts similarity index 91% rename from apps/spruce/playwright/tests/projectSettings/navigation.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/navigation.spec.ts index b048b00a19..d8d4eed398 100644 --- a/apps/spruce/playwright/tests/projectSettings/navigation.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/navigation.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "../../fixtures"; -import { getProjectSettingsRoute, project } from "./constants"; +import { test, expect } from "../../../fixtures"; +import { getProjectSettingsRoute, project } from "../constants"; test.describe("navigation", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/not_defaulting_to_repo.spec.ts similarity index 85% rename from apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/not_defaulting_to_repo.spec.ts index 3ac2695ee0..328d7c9155 100644 --- a/apps/spruce/playwright/tests/projectSettings/not_defaulting_to_repo.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/not_defaulting_to_repo.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; -import { expectSaveButtonEnabled } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; +import { expectSaveButtonEnabled } from "../utils"; test.describe("Project Settings when not defaulting to repo", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/notifications.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/notifications.spec.ts similarity index 95% rename from apps/spruce/playwright/tests/projectSettings/notifications.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/notifications.spec.ts index bc942ebc63..d701932c51 100644 --- a/apps/spruce/playwright/tests/projectSettings/notifications.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/notifications.spec.ts @@ -1,7 +1,10 @@ -import { test, expect } from "../../fixtures"; -import { validateToast, selectOption } from "../../helpers"; -import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast, selectOption } from "../../../helpers"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Notifications", () => { const origin = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/periodic_builds.spec.ts similarity index 92% rename from apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/periodic_builds.spec.ts index a65170cdaa..303dca3fd8 100644 --- a/apps/spruce/playwright/tests/projectSettings/periodic_builds.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/periodic_builds.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from "../../fixtures"; +import { test, expect } from "../../../fixtures"; import { validateToast, validateDatePickerDate, clearDatePickerInput, typeDatePickerDate, -} from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Periodic Builds page", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/permissions.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/permissions.spec.ts similarity index 90% rename from apps/spruce/playwright/tests/projectSettings/permissions.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/permissions.spec.ts index 570b199df9..a566909b05 100644 --- a/apps/spruce/playwright/tests/projectSettings/permissions.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/permissions.spec.ts @@ -1,7 +1,7 @@ import { users } from "@evg-ui/playwright-config/constants"; -import { test, expect } from "../../fixtures"; -import { login, logout } from "../../helpers"; -import { getProjectSettingsRoute, projectUseRepoEnabled } from "./constants"; +import { test, expect } from "../../../fixtures"; +import { login, logout } from "../../../helpers"; +import { getProjectSettingsRoute, projectUseRepoEnabled } from "../constants"; test.describe("permissions", () => { test.beforeEach(async ({ authenticatedPage: page }) => { diff --git a/apps/spruce/playwright/tests/projectSettings/plugins.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts similarity index 96% rename from apps/spruce/playwright/tests/projectSettings/plugins.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts index 96f40c0106..7fa0a395aa 100644 --- a/apps/spruce/playwright/tests/projectSettings/plugins.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts @@ -1,11 +1,11 @@ import { Page } from "@playwright/test"; -import { test, expect } from "../../fixtures"; +import { test, expect } from "../../../fixtures"; import { getProjectSettingsRoute, ProjectSettingsTabRoutes, projectUseRepoEnabled, -} from "./constants"; -import { save } from "./utils"; +} from "../constants"; +import { save } from "../utils"; test.describe("Plugins", () => { const patchPage = "version/5ecedafb562343215a7ff297"; diff --git a/apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/project_triggers.spec.ts similarity index 85% rename from apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/project_triggers.spec.ts index 285b04685b..782b6c8a67 100644 --- a/apps/spruce/playwright/tests/projectSettings/project_triggers.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/project_triggers.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "../../fixtures"; -import { getProjectSettingsRoute, project } from "./constants"; +import { test, expect } from "../../../fixtures"; +import { getProjectSettingsRoute, project } from "../constants"; test.describe("Project Triggers page", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts similarity index 94% rename from apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts index 5c6f99038e..44a4b56709 100644 --- a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts @@ -1,13 +1,13 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, getRepoSettingsRoute, projectUseRepoEnabled, ProjectSettingsTabRoutes, repo, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("A project that has GitHub webhooks disabled", () => { const destination = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/projectSettings/variables.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/variables.spec.ts similarity index 96% rename from apps/spruce/playwright/tests/projectSettings/variables.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/variables.spec.ts index ca8d1f0a12..93730d61ba 100644 --- a/apps/spruce/playwright/tests/projectSettings/variables.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/variables.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { validateToast, clickCheckbox } from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast, clickCheckbox } from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Variables page", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts similarity index 90% rename from apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts index a12d9fe641..986955b6a1 100644 --- a/apps/spruce/playwright/tests/projectSettings/views_and_filters.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts @@ -1,7 +1,10 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getProjectSettingsRoute, ProjectSettingsTabRoutes } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Views & filters page", () => { const destination = getProjectSettingsRoute( diff --git a/apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/attaching_to_repo.spec.ts similarity index 89% rename from apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/attaching_to_repo.spec.ts index aff520c4fb..1d1bce2ba8 100644 --- a/apps/spruce/playwright/tests/repoSettings/attaching_to_repo.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/attaching_to_repo.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; -import { getProjectSettingsRoute, project } from "./constants"; -import { save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; +import { getProjectSettingsRoute, project } from "../constants"; +import { save } from "../utils"; test.describe("Attaching Spruce to a repo", () => { const origin = getProjectSettingsRoute(project); diff --git a/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts similarity index 99% rename from apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts index ef79cb4152..7d741d1f50 100644 --- a/apps/spruce/playwright/tests/repoSettings/defaulting_to_repo.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts @@ -1,5 +1,5 @@ -import { test, expect } from "../../fixtures"; -import { clickCheckbox, clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickCheckbox, clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, getRepoSettingsRoute, @@ -7,8 +7,8 @@ import { ProjectSettingsTabRoutes, projectUseRepoEnabled, repo, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Project Settings when defaulting to repo", () => { const origin = getProjectSettingsRoute(projectUseRepoEnabled); diff --git a/apps/spruce/playwright/tests/repoSettings/general_section.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/general_section.spec.ts similarity index 86% rename from apps/spruce/playwright/tests/repoSettings/general_section.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/general_section.spec.ts index 02e3c5ee36..dad75f613d 100644 --- a/apps/spruce/playwright/tests/repoSettings/general_section.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/general_section.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getRepoSettingsRoute, repo } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { getRepoSettingsRoute, repo } from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("General settings page", () => { const origin = getRepoSettingsRoute(repo); diff --git a/apps/spruce/playwright/tests/repoSettings/github_section.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/github_section.spec.ts similarity index 96% rename from apps/spruce/playwright/tests/repoSettings/github_section.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/github_section.spec.ts index 13b1109436..cdd1725572 100644 --- a/apps/spruce/playwright/tests/repoSettings/github_section.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/github_section.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, getRepoSettingsRoute, projectUseRepoEnabled, repo, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("GitHub page", () => { const origin = getRepoSettingsRoute(repo); diff --git a/apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts similarity index 96% rename from apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts index 767829a8fa..cf1be2afb3 100644 --- a/apps/spruce/playwright/tests/repoSettings/patch_aliases.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts @@ -1,13 +1,13 @@ -import { test, expect } from "../../fixtures"; -import { clickRadio, validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; import { getProjectSettingsRoute, getRepoSettingsRoute, ProjectSettingsTabRoutes, projectUseRepoEnabled, repo, -} from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Patch Aliases page", () => { const origin = getRepoSettingsRoute(repo); diff --git a/apps/spruce/playwright/tests/repoSettings/permissions.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/permissions.spec.ts similarity index 85% rename from apps/spruce/playwright/tests/repoSettings/permissions.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/permissions.spec.ts index bed1a1105d..867b8b49c2 100644 --- a/apps/spruce/playwright/tests/repoSettings/permissions.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/permissions.spec.ts @@ -1,7 +1,7 @@ import { users } from "@evg-ui/playwright-config/constants"; -import { test, expect } from "../../fixtures"; -import { login, logout } from "../../helpers"; -import { getRepoSettingsRoute, repo } from "./constants"; +import { test, expect } from "../../../fixtures"; +import { login, logout } from "../../../helpers"; +import { getRepoSettingsRoute, repo } from "../constants"; test.describe("permissions", () => { test.beforeEach(async ({ authenticatedPage: page }) => { diff --git a/apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts similarity index 84% rename from apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts index 8bcab2e801..eb62cdf2a7 100644 --- a/apps/spruce/playwright/tests/repoSettings/virtual_workstation.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts @@ -1,7 +1,7 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; -import { getRepoSettingsRoute, repo } from "./constants"; -import { expectSaveButtonEnabled, save } from "./utils"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { getRepoSettingsRoute, repo } from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Virtual Workstation page", () => { const origin = getRepoSettingsRoute(repo); diff --git a/apps/spruce/playwright/tests/repoSettings/utils.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts similarity index 94% rename from apps/spruce/playwright/tests/repoSettings/utils.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts index 3f321fffb0..819610d24e 100644 --- a/apps/spruce/playwright/tests/repoSettings/utils.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts @@ -1,5 +1,5 @@ import { Page } from "@playwright/test"; -import { expect } from "../../fixtures"; +import { expect } from "../../../fixtures"; export const save = async (page: Page) => { const saveButton = page.getByTestId("save-settings-button"); diff --git a/apps/spruce/playwright/tests/projectSettings/utils.ts b/apps/spruce/playwright/tests/projectSettings/utils.ts deleted file mode 100644 index 3f321fffb0..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Page } from "@playwright/test"; -import { expect } from "../../fixtures"; - -export const save = async (page: Page) => { - const saveButton = page.getByTestId("save-settings-button"); - await expect(saveButton).toHaveAttribute("aria-disabled", "false"); - await saveButton.click(); - - const saveChangesModal = page.getByTestId("save-changes-modal"); - await expect(saveChangesModal).toBeVisible(); - await saveChangesModal.getByRole("button", { name: "Save changes" }).click(); - await expect(saveChangesModal).toBeHidden(); -}; - -export const expectSaveButtonEnabled = async ( - page: Page, - isEnabled: boolean = true, -) => { - const saveButton = page.getByTestId("save-settings-button"); - if (isEnabled) { - await expect(saveButton).toHaveAttribute("aria-disabled", "false"); - } else { - await expect(saveButton).toHaveAttribute("aria-disabled", "true"); - } -}; diff --git a/apps/spruce/playwright/tests/repoSettings/constants.ts b/apps/spruce/playwright/tests/repoSettings/constants.ts deleted file mode 100644 index 63083b4cdb..0000000000 --- a/apps/spruce/playwright/tests/repoSettings/constants.ts +++ /dev/null @@ -1,32 +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", - CommitChecks = "commit-checks", - PullRequests = "pull-requests", -} - -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"; From 48e070d3e724f98f5544d5f60035ab5e7c61fca9 Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 16:21:18 -0400 Subject: [PATCH 05/18] fix: incorrect path --- apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts index 819610d24e..3f321fffb0 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/utils.ts @@ -1,5 +1,5 @@ import { Page } from "@playwright/test"; -import { expect } from "../../../fixtures"; +import { expect } from "../../fixtures"; export const save = async (page: Page) => { const saveButton = page.getByTestId("save-settings-button"); From 0c87014035542b1d322c519d778e1350f248342e Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 16:24:24 -0400 Subject: [PATCH 06/18] Update generate-parallel-e2e-tasks.js --- .../scripts/generate-parallel-e2e-tasks.js | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.evergreen/scripts/generate-parallel-e2e-tasks.js b/.evergreen/scripts/generate-parallel-e2e-tasks.js index 355a27d397..f6e6d2a4da 100644 --- a/.evergreen/scripts/generate-parallel-e2e-tasks.js +++ b/.evergreen/scripts/generate-parallel-e2e-tasks.js @@ -57,14 +57,23 @@ const getPlaywrightDirSizes = (dirPath) => { const makeRelative = (d) => d.substring(d.indexOf("playwright")); const dirSizeMap = {}; - const dirs = getDirs(dirPath); - dirs.forEach((dir) => { - const size = getDirSize(dir); - dirSizeMap[`${makeRelative(dir)}/*.spec.ts`] = size; - }); - // Specifically add root tests with no wildcard directory regex - dirSizeMap[`${makeRelative(dirPath)}/*.spec.ts`] = getDirSize(dirPath, false); + const collectDirSizes = (currentPath, isRoot) => { + const dirs = getDirs(currentPath); + const directSize = getDirSize(currentPath, false); + + // Add an entry for specs directly in this directory (always for root, only when non-empty for subdirs) + if (isRoot || directSize > 0) { + dirSizeMap[`${makeRelative(currentPath)}/*.spec.ts`] = directSize; + } + + // Recurse into subdirectories + dirs.forEach((dir) => { + collectDirSizes(dir, false); + }); + }; + + collectDirSizes(dirPath, true); return dirSizeMap; }; From 857aec323932d4211557a1fe95fd42ee0f8e02e1 Mon Sep 17 00:00:00 2001 From: minnakt Date: Wed, 29 Apr 2026 16:56:04 -0400 Subject: [PATCH 07/18] fix: claude's bad variable names --- .evergreen/scripts/generate-parallel-e2e-tasks.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.evergreen/scripts/generate-parallel-e2e-tasks.js b/.evergreen/scripts/generate-parallel-e2e-tasks.js index f6e6d2a4da..d7ee162a88 100644 --- a/.evergreen/scripts/generate-parallel-e2e-tasks.js +++ b/.evergreen/scripts/generate-parallel-e2e-tasks.js @@ -60,14 +60,12 @@ const getPlaywrightDirSizes = (dirPath) => { const collectDirSizes = (currentPath, isRoot) => { const dirs = getDirs(currentPath); - const directSize = getDirSize(currentPath, false); - + const dirSize = getDirSize(currentPath, false); // Add an entry for specs directly in this directory (always for root, only when non-empty for subdirs) - if (isRoot || directSize > 0) { - dirSizeMap[`${makeRelative(currentPath)}/*.spec.ts`] = directSize; + if (isRoot || dirSize > 0) { + dirSizeMap[`${makeRelative(currentPath)}/*.spec.ts`] = dirSize; } - - // Recurse into subdirectories + // Recurse into subdirectories. dirs.forEach((dir) => { collectDirSizes(dir, false); }); From 7de7df40e27642385ba0e2c1dfe107feb3a693c7 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 00:38:49 -0400 Subject: [PATCH 08/18] fix: resolve merge conflicts --- .../tests/adminSettings/save_function.spec.ts | 1 - .../tests/adminSettings/web.spec.ts | 3 - .../tests/projectAndRepoSettings/constants.ts | 1 + .../projectSettings/commit_checks.spec.ts | 4 +- .../projectSettings/merge_queue.spec.ts | 71 +++++++------- .../projectSettings/pull_requests.spec.ts | 4 +- .../projectSettings/commit_checks.spec.ts | 91 ------------------ .../projectSettings/pull_requests.spec.ts | 96 ------------------- 8 files changed, 45 insertions(+), 226 deletions(-) rename apps/spruce/playwright/tests/{ => projectAndRepoSettings}/projectSettings/merge_queue.spec.ts (57%) delete mode 100644 apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts delete mode 100644 apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts diff --git a/apps/spruce/playwright/tests/adminSettings/save_function.spec.ts b/apps/spruce/playwright/tests/adminSettings/save_function.spec.ts index ea44f4c650..5bfd2449c5 100644 --- a/apps/spruce/playwright/tests/adminSettings/save_function.spec.ts +++ b/apps/spruce/playwright/tests/adminSettings/save_function.spec.ts @@ -119,7 +119,6 @@ test.describe("admin settings save properly", () => { await validateToast(page, "success", "Settings saved successfully"); await page.reload(); - await page.getByLabel("Total Project Limit").scrollIntoViewIfNeeded(); await expect(page.getByLabel("Total Project Limit")).toHaveValue("200"); await expect(newProjectCreationException.getByLabel("Owner")).toHaveValue( "owner", diff --git a/apps/spruce/playwright/tests/adminSettings/web.spec.ts b/apps/spruce/playwright/tests/adminSettings/web.spec.ts index 4f7608233e..3f996de68f 100644 --- a/apps/spruce/playwright/tests/adminSettings/web.spec.ts +++ b/apps/spruce/playwright/tests/adminSettings/web.spec.ts @@ -24,9 +24,6 @@ test.describe("web", () => { // Disabled GraphQL Queries section. const disabledGQLQueriesSection = page.getByTestId("disabled-gql-queries"); - await disabledGQLQueriesSection - .getByLabel("Disabled GraphQL Queries") - .scrollIntoViewIfNeeded(); await disabledGQLQueriesSection .getByLabel("Disabled GraphQL Queries") .clear(); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts index 63083b4cdb..2e9be6af8b 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts @@ -15,6 +15,7 @@ export enum ProjectSettingsTabRoutes { GithubPermissionGroups = "github-permission-groups", CommitChecks = "commit-checks", PullRequests = "pull-requests", + MergeQueue = "merge-queue", } export const getProjectSettingsRoute = ( diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts index 7693cf0bbd..b14bee8181 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/commit_checks.spec.ts @@ -21,7 +21,9 @@ test.describe("A project that has GitHub webhooks disabled", () => { test("Commit Checks page shows a disabled webhooks banner when webhooks are disabled", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("disabled-webhook-banner")).toContainText( + 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", ); }); diff --git a/apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/merge_queue.spec.ts similarity index 57% rename from apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts rename to apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/merge_queue.spec.ts index a90abdcb0e..316d39967b 100644 --- a/apps/spruce/playwright/tests/projectSettings/merge_queue.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/merge_queue.spec.ts @@ -1,15 +1,20 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { clickRadio, validateToast } from "../../../helpers"; +import { + getProjectSettingsRoute, + ProjectSettingsTabRoutes, +} from "../constants"; +import { expectSaveButtonEnabled, save } from "../utils"; test.describe("Merge Queue project settings when GitHub webhooks are disabled", () => { - const origin = "/project/logkeeper/settings/merge-queue"; + const origin = getProjectSettingsRoute( + "logkeeper", + ProjectSettingsTabRoutes.MergeQueue, + ); test.beforeEach(async ({ authenticatedPage: page }) => { await page.goto(origin); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); + await expectSaveButtonEnabled(page, false); }); test("Merge Queue page shows a disabled webhooks banner when webhooks are disabled", async ({ @@ -26,33 +31,36 @@ test.describe("Merge Queue project settings when GitHub webhooks are disabled", 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, - ); + const buttons = settingsPage.getByRole("button"); + for (const button of await buttons.all()) { + await expect(button).toBeDisabled(); + } + const inputs = page.locator("input"); + for (const input of await inputs.all()) { + await expect(input).toBeDisabled(); + } }); }); test.describe("Merge Queue project settings when GitHub webhooks are enabled", () => { - const origin = "/project/spruce/settings/merge-queue"; + const origin = getProjectSettingsRoute( + "spruce", + ProjectSettingsTabRoutes.MergeQueue, + ); test.beforeEach(async ({ authenticatedPage: page }) => { await page.goto(origin); - await expect(page.getByTestId("save-settings-button")).toHaveAttribute( - "aria-disabled", - "true", - ); + await expectSaveButtonEnabled(page, false); }); 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(); + const radioBox = page.getByTestId("mq-enabled-radio-box"); + const mergeQueueEnabledRadio = radioBox.getByRole("radio", { + name: "Enabled", + }); + await clickRadio(mergeQueueEnabledRadio); await expect(page.getByText("Merge Queue Patch Definitions")).toBeVisible(); const errorBanner = page.getByTestId("error-banner"); @@ -65,23 +73,20 @@ test.describe("Merge Queue project settings when GitHub webhooks are enabled", ( test("Saves a merge queue definition", async ({ authenticatedPage: page, }) => { - await page - .getByTestId("mq-enabled-radio-box") - .locator("label", { hasText: "Enabled" }) - .click(); + const radioBox = page.getByTestId("mq-enabled-radio-box"); + const mergeQueueEnabledRadio = radioBox.getByRole("radio", { + name: "Enabled", + }); + await clickRadio(mergeQueueEnabledRadio); + 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 expect(page.getByTestId("error-banner")).toBeHidden(); + await save(page); await validateToast(page, "success", "Successfully updated project"); }); }); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts index 44a4b56709..2d2fbb395a 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/pull_requests.spec.ts @@ -23,7 +23,9 @@ test.describe("A project that has GitHub webhooks disabled", () => { test("Pull Requests page shows a disabled webhooks banner when webhooks are disabled", async ({ authenticatedPage: page, }) => { - await expect(page.getByTestId("disabled-webhook-banner")).toContainText( + 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", ); }); 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 3401cef622..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/commit_checks.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; - -test.describe("Commit Checks project settings when GitHub webhooks are disabled", () => { - const origin = "/project/logkeeper/settings/commit-checks"; - - test.beforeEach(async ({ authenticatedPage: page }) => { - await page.goto(origin); - 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.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/pull_requests.spec.ts b/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts deleted file mode 100644 index ec2ab0a214..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -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.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.goto("/repo/602d70a2b2373672ee493184/settings/general"); - await page.getByTestId("navitem-pull-requests").click(); - 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/general"); - await page.getByTestId("navitem-pull-requests").click(); - 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); - }); -}); From 5b0c7b7e979a101cbb9ddee86ec6df461b8ce299 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 10:41:26 -0400 Subject: [PATCH 09/18] fix: undo change (redo in another PR) --- apps/spruce/src/components/Notifications/form/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/spruce/src/components/Notifications/form/event.ts b/apps/spruce/src/components/Notifications/form/event.ts index d9cfad57f1..900ebaf313 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, From 6af641fb9b9ac65830ff40406d97a24a6f510277 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 16:30:13 -0400 Subject: [PATCH 10/18] fix: add guards --- .../projectSettings/plugins.spec.ts | 9 +++++---- .../shared/tabs/PluginsTab/getFormSchema.tsx | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts index 7fa0a395aa..2f6b48f521 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/plugins.spec.ts @@ -16,9 +16,7 @@ test.describe("Plugins", () => { ) => { await page.getByRole("button", { name: "Add metadata link" }).click(); - const mostRecentMetadataLink = page - .getByTestId("metadata-link-item") - .first(); + const mostRecentMetadataLink = page.getByTestId("metadata-link").first(); await mostRecentMetadataLink.getByTestId("requesters-input").click(); const options = mostRecentMetadataLink.getByTestId("tree-select-options"); @@ -52,10 +50,11 @@ test.describe("Plugins", () => { displayName: "An external link 2", url: "https://example-2.com/{version_id}", }); - await expect(page.getByTestId("metadata-link-item")).toHaveCount(2); + await expect(page.getByTestId("metadata-link")).toHaveCount(2); await save(page); await page.goto(patchPage); + await expect(page.getByText("Patch Metadata")).toBeVisible(); await expect(page.getByTestId("user-patches-link")).toBeVisible(); await expect(page.getByTestId("external-link")).toHaveCount(2); await expect(page.getByTestId("external-link").last()).toContainText( @@ -81,9 +80,11 @@ test.describe("Plugins", () => { ); await page.getByTestId("delete-item-button").first().click(); await page.getByTestId("delete-item-button").first().click(); + await expect(page.getByTestId("metadata-link")).toHaveCount(0); await save(page); await page.goto(patchPage); + await expect(page.getByText("Patch Metadata")).toBeVisible(); await expect(page.getByTestId("external-link")).toHaveCount(0); }); }); 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 c270af5a3e..e8e0d55872 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", requesters: { "ui:widget": widgets.MultiSelectWidget, "ui:data-cy": "requesters-input", From 1f1db7afcd64d2f5918b0af7fde958d892d21ca8 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 16:56:48 -0400 Subject: [PATCH 11/18] fix: add guards --- .../tests/distroSettings/project_section.spec.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/spruce/playwright/tests/distroSettings/project_section.spec.ts b/apps/spruce/playwright/tests/distroSettings/project_section.spec.ts index 828a634a8a..6b3fd066c6 100644 --- a/apps/spruce/playwright/tests/distroSettings/project_section.spec.ts +++ b/apps/spruce/playwright/tests/distroSettings/project_section.spec.ts @@ -19,8 +19,12 @@ test.describe("project section", () => { await addExpansionButton.click(); const newExpansion = page.getByTestId("expansion-item"); - await newExpansion.getByLabel("Key").fill("key-name"); - await newExpansion.getByLabel("Value").fill("my-value"); + const keyInput = newExpansion.getByLabel("Key"); + await expect(keyInput).toHaveCount(1); + await keyInput.fill("key-name"); + const valueInput = newExpansion.getByLabel("Value"); + await expect(valueInput).toHaveCount(1); + await valueInput.fill("my-value"); await page.getByRole("button", { name: "Add project" }).click(); await page.getByLabel("Project ID").fill("spruce"); @@ -29,8 +33,11 @@ test.describe("project section", () => { await validateToast(page, "success", "Updated distro."); await page.reload(); - await expect(newExpansion.getByLabel("Key")).toHaveValue("key-name"); - await expect(newExpansion.getByLabel("Value")).toHaveValue("my-value"); + await expect(keyInput).toHaveCount(1); + await expect(keyInput).toHaveValue("key-name"); + await expect(valueInput).toHaveCount(1); + await expect(valueInput).toHaveValue("my-value"); + await expect(page.getByLabel("Project ID")).toHaveValue("spruce"); await page.getByTestId("delete-item-button").first().click(); From b67b1ee4065046b0fd85ac9ade56b395601030d4 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 17:36:28 -0400 Subject: [PATCH 12/18] feat: change parallel counts --- .evergreen/scripts/constants.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.evergreen/scripts/constants.js b/.evergreen/scripts/constants.js index 5f2aa23190..5d4c28feb5 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 = 3; export { Tasks, From 10ea60543e333edc58fbc51ced93db20ce56fae6 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 17:41:22 -0400 Subject: [PATCH 13/18] fix settings to retain on failure --- packages/playwright-config/playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-config/playwright.config.ts b/packages/playwright-config/playwright.config.ts index 2e80c65c8c..e1367bfa97 100644 --- a/packages/playwright-config/playwright.config.ts +++ b/packages/playwright-config/playwright.config.ts @@ -23,9 +23,9 @@ export const createPlaywrightConfig = ({ use: { baseURL, viewport, - video: process.env.CI ? "on-first-retry" : "off", + video: process.env.CI ? "retain-on-failure" : "off", screenshot: process.env.CI ? "only-on-failure" : "off", - trace: process.env.CI ? "on-first-retry" : "off", + trace: process.env.CI ? "retain-on-failure-and-retries" : "off", permissions: ["clipboard-read", "clipboard-write"], testIdAttribute: "data-cy", }, From 27608ff63e98816ad6c05d73670caef5c533e2a7 Mon Sep 17 00:00:00 2001 From: minnakt Date: Thu, 30 Apr 2026 18:28:49 -0400 Subject: [PATCH 14/18] fix: flakes --- apps/spruce/playwright/helpers/index.ts | 1 + .../projectSettings/views_and_filters.spec.ts | 7 ++++- .../repoSettings/defaulting_to_repo.spec.ts | 7 ++++- .../repoSettings/patch_aliases.spec.ts | 28 +++++++++---------- .../repoSettings/virtual_workstation.spec.ts | 3 +- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/spruce/playwright/helpers/index.ts b/apps/spruce/playwright/helpers/index.ts index ab0e315f81..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"]'); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts index 986955b6a1..af7c11a8ea 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/views_and_filters.spec.ts @@ -56,7 +56,12 @@ test.describe("Views & filters page", () => { .fill("my_filter"); await expectSaveButtonEnabled(page, true); await save(page); - await validateToast(page, "success", "Successfully updated project"); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); await expect(page.getByTestId("parsley-filter")).toHaveCount(3); await page.getByTestId("delete-item-button").first().click(); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts index 7d741d1f50..791193b4d7 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/defaulting_to_repo.spec.ts @@ -417,7 +417,12 @@ test.describe("Project Settings when defaulting to repo", () => { .first() .fill("alias task tag 3"); await save(page); - await validateToast(page, "success", "Successfully updated project"); + await validateToast( + page, + "success", + "Successfully updated project", + true, + ); const defaultToRepoRadio = page.getByRole("radio", { name: "Default to Repo Patch Aliases", diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts index cf1be2afb3..30a7c1279d 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/patch_aliases.spec.ts @@ -122,25 +122,25 @@ test.describe("Patch Aliases page", () => { const prTriggerAliases = page.getByTestId("github-pr-trigger-aliases"); await expect(prTriggerAliases.getByTestId("pta-item")).toHaveCount(1); await expect(prTriggerAliases.getByText("my-alias")).toBeVisible(); + await prTriggerAliases.getByTestId("pta-item").hover(); - await expect(page.getByTestId("pta-tooltip")).toHaveCount(1); - await expect(page.getByTestId("pta-tooltip")).toBeVisible(); - await expect(page.getByTestId("pta-tooltip")).toContainText("spruce"); - await expect(page.getByTestId("pta-tooltip")).toContainText("module_name"); - await expect(page.getByTestId("pta-tooltip")).toContainText( - "Variant/Task Regex Pairs", - ); + const prTooltip = prTriggerAliases.getByTestId("pta-tooltip"); + await expect(prTooltip).toHaveCount(1); + await expect(prTooltip).toBeVisible(); + await expect(prTooltip).toContainText("spruce"); + await expect(prTooltip).toContainText("module_name"); + await expect(prTooltip).toContainText("Variant/Task Regex Pairs"); const mqTriggerAliases = page.getByTestId("github-mq-trigger-aliases"); await expect(mqTriggerAliases.getByTestId("pta-item")).toHaveCount(1); await expect(mqTriggerAliases.getByText("my-alias")).toBeVisible(); + await mqTriggerAliases.getByTestId("pta-item").hover(); - await expect(page.getByTestId("pta-tooltip")).toHaveCount(1); - await expect(page.getByTestId("pta-tooltip")).toBeVisible(); - await expect(page.getByTestId("pta-tooltip")).toContainText("spruce"); - await expect(page.getByTestId("pta-tooltip")).toContainText("module_name"); - await expect(page.getByTestId("pta-tooltip")).toContainText( - "Variant/Task Regex Pairs", - ); + const mqTooltip = mqTriggerAliases.getByTestId("pta-tooltip"); + await expect(mqTooltip).toHaveCount(1); + await expect(mqTooltip).toBeVisible(); + await expect(mqTooltip).toContainText("spruce"); + await expect(mqTooltip).toContainText("module_name"); + await expect(mqTooltip).toContainText("Variant/Task Regex Pairs"); }); }); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts index eb62cdf2a7..7ab025f083 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/repoSettings/virtual_workstation.spec.ts @@ -24,7 +24,8 @@ test.describe("Virtual Workstation page", () => { await addCommandButton.click(); await page.getByTestId("command-input").nth(1).fill("command 2"); await save(page); - await validateToast(page, "success", "Successfully updated repo"); + await validateToast(page, "success", "Successfully updated repo", true); + await page.getByTestId("array-down-button").click(); await save(page); await validateToast(page, "success", "Successfully updated repo"); From 01c021acde335d672ed22f26427c0d1c3dba548c Mon Sep 17 00:00:00 2001 From: minnakt Date: Fri, 1 May 2026 11:19:36 -0400 Subject: [PATCH 15/18] fix: resolve conflicts --- .../tests/projectAndRepoSettings/constants.ts | 1 + .../projectSettings/git_tags.spec.ts | 43 ++++----- .../projectSettings/pull_requests.spec.ts | 94 ------------------- 3 files changed, 20 insertions(+), 118 deletions(-) delete mode 100644 apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts index 2e9be6af8b..fd03c75505 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts @@ -16,6 +16,7 @@ export enum ProjectSettingsTabRoutes { CommitChecks = "commit-checks", PullRequests = "pull-requests", MergeQueue = "merge-queue", + GitTags = "git-tags", } export const getProjectSettingsRoute = ( diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts index f6939633ee..e93164ff02 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts @@ -1,6 +1,7 @@ import { clickRadio } from "@evg-ui/playwright-config/helpers"; -import { test, expect } from "../../fixtures"; -import { validateToast } from "../../helpers"; +import { test, expect } from "../../../fixtures"; +import { validateToast } from "../../../helpers"; +import { save } from "../utils"; test.describe("Git Tags project settings when GitHub webhooks are disabled", () => { const origin = "/project/logkeeper/settings/git-tags"; @@ -24,12 +25,14 @@ test.describe("Git Tags project settings when GitHub webhooks are disabled", () 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, - ); + const buttons = settingsPage.getByRole("button"); + for (const button of await buttons.all()) { + await expect(button).toBeDisabled(); + } + const inputs = page.locator("input"); + for (const input of await inputs.all()) { + await expect(input).toBeDisabled(); + } }); }); @@ -47,27 +50,19 @@ test.describe("Git Tags project settings when GitHub webhooks are enabled", () = 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"); + + const errorBanner = page.getByTestId("error-banner").filter({ + hasText: + "A Git Tag Version Definition must be specified for this feature to run.", + }); await expect(errorBanner).toBeVisible(); - await expect(errorBanner).toContainText(errorText); - await page - .getByTestId("add-button") - .filter({ hasText: "Add git tag" }) - .click(); + await page.getByRole("button", { name: "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 expect(page.getByTestId("error-banner")).toBeHidden(); + save(page); await validateToast(page, "success", "Successfully updated repo"); }); }); 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 d35b6cfab5..0000000000 --- a/apps/spruce/playwright/tests/projectSettings/pull_requests.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -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.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.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); - }); -}); From 1c32289040920bde3f3827cca928188a13d9ed96 Mon Sep 17 00:00:00 2001 From: minnakt Date: Fri, 1 May 2026 11:22:36 -0400 Subject: [PATCH 16/18] fix: merge conflicts --- .../projectAndRepoSettings/projectSettings/git_tags.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts index e93164ff02..e338b65b9f 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/projectSettings/git_tags.spec.ts @@ -1,14 +1,14 @@ import { clickRadio } from "@evg-ui/playwright-config/helpers"; import { test, expect } from "../../../fixtures"; import { validateToast } from "../../../helpers"; -import { save } from "../utils"; +import { save, expectSaveButtonEnabled } from "../utils"; test.describe("Git Tags project settings when GitHub webhooks are disabled", () => { const origin = "/project/logkeeper/settings/git-tags"; test.beforeEach(async ({ authenticatedPage: page }) => { await page.goto(origin); - await expect(page.getByTestId("save-settings-button")).toBeDisabled(); + await expectSaveButtonEnabled(page, false); }); test("Git tags page shows a disabled webhooks banner when webhooks are disabled", async ({ @@ -41,7 +41,7 @@ test.describe("Git Tags project settings when GitHub webhooks are enabled", () = test.beforeEach(async ({ authenticatedPage: page }) => { await page.goto(origin); - await expect(page.getByTestId("save-settings-button")).toBeDisabled(); + await expectSaveButtonEnabled(page, false); }); test("Saves successfully when Git Tags are enabled and a Git Tag Definition is provided", async ({ From e79f60c5e05e62ea9f09173e7344d47d66de8848 Mon Sep 17 00:00:00 2001 From: minnakt Date: Fri, 1 May 2026 11:28:41 -0400 Subject: [PATCH 17/18] fix: guards --- .../distroSettings/provider_section.spec.ts | 17 ++++++++++++----- .../tests/projectAndRepoSettings/constants.ts | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/apps/spruce/playwright/tests/distroSettings/provider_section.spec.ts b/apps/spruce/playwright/tests/distroSettings/provider_section.spec.ts index c589422132..31f2b0a663 100644 --- a/apps/spruce/playwright/tests/distroSettings/provider_section.spec.ts +++ b/apps/spruce/playwright/tests/distroSettings/provider_section.spec.ts @@ -50,15 +50,22 @@ test.describe("provider section", () => { test("shows pool mapping information based on container pool id", async ({ authenticatedPage: page, }) => { - await expect(page.getByLabel("Container Pool ID")).toContainText( - "test-pool-1", - ); - await expect(page.getByLabel("Pool Mapping Information")).toHaveAttribute( + const containerPoolSelect = page.getByRole("button", { + name: "Container Pool ID", + }); + await expect(containerPoolSelect).toHaveCount(1); + await expect(containerPoolSelect).toContainText("test-pool-1"); + + const containerPoolMapping = page.getByRole("textbox", { + name: "Pool Mapping Information", + }); + await expect(containerPoolMapping).toHaveCount(1); + await expect(containerPoolMapping).toHaveAttribute( "placeholder", /test-pool-1/, ); await selectOption(page, "Container Pool ID", "test-pool-2"); - await expect(page.getByLabel("Pool Mapping Information")).toHaveAttribute( + await expect(containerPoolMapping).toHaveAttribute( "placeholder", /test-pool-2/, ); diff --git a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts index fd03c75505..639b6de88a 100644 --- a/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts +++ b/apps/spruce/playwright/tests/projectAndRepoSettings/constants.ts @@ -1,4 +1,5 @@ export enum ProjectSettingsTabRoutes { + // Evergreen sections General = "general", Access = "access", Variables = "variables", @@ -11,6 +12,8 @@ export enum ProjectSettingsTabRoutes { Plugins = "plugins", EventLog = "event-log", ViewsAndFilters = "views-and-filters", + + // GitHub sections. GithubAppSettings = "github-app-settings", GithubPermissionGroups = "github-permission-groups", CommitChecks = "commit-checks", From b4242fcf0f1c9f6e499f5f0c32c90971fa23533c Mon Sep 17 00:00:00 2001 From: minnakt Date: Fri, 1 May 2026 11:30:57 -0400 Subject: [PATCH 18/18] move cookie location --- apps/spruce/playwright/fixtures.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/spruce/playwright/fixtures.ts b/apps/spruce/playwright/fixtures.ts index a1b7d19688..9f29b0961a 100644 --- a/apps/spruce/playwright/fixtures.ts +++ b/apps/spruce/playwright/fixtures.ts @@ -6,6 +6,7 @@ import { SEEN_TASK_HISTORY_ONBOARDING_TUTORIAL, SEEN_TASK_REVIEW_TOOLTIP, SEEN_TEST_SELECTION_GUIDE_CUE, + SEEN_GITHUB_NAV_GUIDE_CUE, } from "constants/cookies"; import * as helpers from "./helpers"; @@ -84,6 +85,12 @@ export const test = base.extend({ domain: "localhost", path: "/", }, + { + name: SEEN_GITHUB_NAV_GUIDE_CUE, + value: "true", + domain: "localhost", + path: "/", + }, ]); await use(page);