From 19dcc19dcbccf270f9baf58907c54ab1928a62a1 Mon Sep 17 00:00:00 2001 From: David Whittaker Date: Tue, 27 May 2025 18:13:48 -0700 Subject: [PATCH 1/2] tests(ui): test security event form submission --- .../static/e2e/fixtures/dispatch-fixtures.ts | 6 ++ tests/static/e2e/pages/report-event-page.ts | 64 +++++++++++++ tests/static/e2e/report-event.spec.ts | 90 +++++++++++++++++++ tests/static/e2e/routes.ts | 1 + 4 files changed, 161 insertions(+) create mode 100644 tests/static/e2e/pages/report-event-page.ts create mode 100644 tests/static/e2e/report-event.spec.ts diff --git a/tests/static/e2e/fixtures/dispatch-fixtures.ts b/tests/static/e2e/fixtures/dispatch-fixtures.ts index ec55b0302f62..98a1b815eadd 100644 --- a/tests/static/e2e/fixtures/dispatch-fixtures.ts +++ b/tests/static/e2e/fixtures/dispatch-fixtures.ts @@ -3,12 +3,14 @@ import { AuthPage } from "../pages/auth-page" import { ReportIncidentPage } from "../pages/report-incident-page" import { ReportCasePage } from "../pages/report-case-page" import { IncidentsPage } from "../pages/incidents-page" +import { ReportEventPage } from "../pages/report-event-page" type DispatchFixtures = { authPage: AuthPage reportIncidentPage: ReportIncidentPage reportCasePage: ReportCasePage incidentsPage: IncidentsPage + reportEventPage: ReportEventPage } export const test = base.extend({ @@ -24,6 +26,10 @@ export const test = base.extend({ await use(new ReportCasePage(page)) }, + reportEventPage: async ({ page }, use) => { + await use(new ReportEventPage(page)) + }, + incidentsPage: async ({ page }, use) => { const incidentsPage = new IncidentsPage(page) await use(incidentsPage) diff --git a/tests/static/e2e/pages/report-event-page.ts b/tests/static/e2e/pages/report-event-page.ts new file mode 100644 index 000000000000..34ac5f77e874 --- /dev/null +++ b/tests/static/e2e/pages/report-event-page.ts @@ -0,0 +1,64 @@ +import { expect, Locator, Page } from "@playwright/test" +import { orgSlug, Routes } from "../routes" + +export class ReportEventPage { + readonly page: Page + readonly route: string + readonly reportHeader: Locator + readonly descriptionTextBox: Locator + readonly urgentCheckbox: Locator + readonly submitButton: Locator + readonly pageBorder: Locator + + constructor(page: Page) { + this.page = page + this.route = orgSlug + Routes.ReportEvent + this.reportHeader = page.getByText("Report a Security Event").first() + this.descriptionTextBox = page.getByLabel("Description", { exact: true }) + this.urgentCheckbox = page.getByLabel( + "URGENT: I need immediate help with this (the oncall will be paged)", + { exact: true } + ) + this.submitButton = page.getByRole("button", { name: "Submit" }) + this.pageBorder = this.page.locator("span").filter({ + hasText: "Security Events are an input", + }) + } + + async goto() { + await Promise.all([ + this.page.goto(this.route), + await this.page.waitForURL(this.route), + await expect(this.reportHeader).toBeVisible(), + ]) + } + + async reportEvent(description: string, urgent: boolean = false) { + await this.goto() + // give time for default project to settle + await this.page.waitForTimeout(3000) + await this.addDescription(description) + if (urgent) { + await this.urgentCheckbox.click() + } + await this.page.waitForTimeout(1500) + await this.resetPageView() + await Promise.all([ + await this.submitButton.click(), + await this.page.waitForLoadState("networkidle"), + ]) + } + + async addDescription(description: string) { + await this.descriptionTextBox.click() + await this.descriptionTextBox.fill(description) + } + + async resetPageView() { + // await this.pageBorder.click() + } + + async pageObjectModel(description: string, urgent: boolean = false) { + await this.reportEvent(description, urgent) + } +} diff --git a/tests/static/e2e/report-event.spec.ts b/tests/static/e2e/report-event.spec.ts new file mode 100644 index 000000000000..45cecee4002b --- /dev/null +++ b/tests/static/e2e/report-event.spec.ts @@ -0,0 +1,90 @@ +import register from "./utils/register" +import { test, expect } from "./fixtures/dispatch-fixtures" + +test.describe("Authenticated Dispatch App", () => { + test.beforeEach(async ({ authPage }) => { + await register(authPage) + }), + test("Should allow me to report an event", async ({ page, reportEventPage }) => { + /* The ability to report a case is one of the most critical + user stories in the Dispatch application. */ + + const description = "Security Event Test Created by Playwright" + const title = "Security Event Triage" + + await reportEventPage.reportEvent(description) + // Soft validate that we get redirected to the case submission form + const expectedURL = encodeURI( + `/default/events/report?project=default&title=${title}&description=${description}` + ) + // replace + with %20 + const pageURL = page.url().replace(/\+/g, "%20") + + await expect.soft(pageURL).toContain(expectedURL) + + // Soft validate that we receive the post-create resources form. + await expect + .soft( + page.getByText( + "This page will be populated with case resources as they are created (if available). If you have any questions, please feel free to review the Frequently Asked Questions (FAQ) document linked below, and/or reach out to the listed assignee." + ), + "'Case Resources' text visible on page after submission of a case." + ) + .toBeVisible() + + // Soft validate that the ticket link is present + const loc = page.getByRole("link", { + name: "Ticket Ticket for tracking purposes. It contains information and links to resources.", + }) + await expect + .soft(await loc.first().getAttribute("href")) + .toContain("default/cases/dispatch-default-default-") + }), + test("Should allow me to report an event with urgent flagged", async ({ + page, + reportEventPage, + }) => { + /* The ability to report a case is one of the most critical + user stories in the Dispatch application. */ + + const description = "Security Event Test Created by Playwright" + const title = "Security Event Triage" + + await reportEventPage.reportEvent(description, true) + // Soft validate that we get redirected to the case submission form + const expectedURL = encodeURI( + `/default/events/report?project=default&title=${title}&description=${description}` + ) + // replace + with %20 + const pageURL = page.url().replace(/\+/g, "%20") + + await expect.soft(pageURL).toContain(expectedURL) + + // Soft validate that we receive the post-create resources form. + await expect + .soft( + page.getByText( + "This page will be populated with case resources as they are created (if available). If you have any questions, please feel free to review the Frequently Asked Questions (FAQ) document linked below, and/or reach out to the listed assignee." + ), + "'Case Resources' text visible on page after submission of an event." + ) + .toBeVisible() + + // Validate that the Priority is set to 'Critical' + const priorityElement = page.getByText("Priority") + + const subtitleElement = priorityElement.locator( + 'xpath=following-sibling::div[contains(@class, "v-list-item-subtitle")]' + ) + await expect(subtitleElement).toBeVisible() + await expect(subtitleElement).toHaveText(/Critical/) + + // Soft validate that the ticket link is present + const loc = page.getByRole("link", { + name: "Ticket Ticket for tracking purposes. It contains information and links to resources.", + }) + await expect + .soft(await loc.first().getAttribute("href")) + .toContain("default/cases/dispatch-default-default-") + }) +}) diff --git a/tests/static/e2e/routes.ts b/tests/static/e2e/routes.ts index 76adf6f9b948..cd3a872fee68 100644 --- a/tests/static/e2e/routes.ts +++ b/tests/static/e2e/routes.ts @@ -7,4 +7,5 @@ export enum Routes { Incidents = "/incidents", ReportIncident = "/incidents/report", ReportCase = "/cases/report", + ReportEvent = "/events/report", } From 830d6a76f573f7ecb6fb5eec664b816d7ddf9904 Mon Sep 17 00:00:00 2001 From: David Whittaker Date: Tue, 27 May 2025 19:59:45 -0700 Subject: [PATCH 2/2] updating database to include a priority that is paged --- data/dispatch-sample-data.dump | 3 ++- tests/static/e2e/report-event.spec.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/data/dispatch-sample-data.dump b/data/dispatch-sample-data.dump index 7be0bb5b5854..4b7129b583b5 100644 --- a/data/dispatch-sample-data.dump +++ b/data/dispatch-sample-data.dump @@ -7891,6 +7891,7 @@ COPY dispatch_organization_default.case_priority (id, name, description, color, 1 Low Low priority #558b2f t t -1 \N 1 f 2 Medium Medium priority \N t f 2 \N 1 f 3 High High priority #b71c1c t f 3 \N 1 f +4 Critical Critical priority \N t f 4 \N 1 t \. @@ -9263,7 +9264,7 @@ SELECT pg_catalog.setval('dispatch_organization_default.case_id_seq', 1, true); -- Name: case_priority_id_seq; Type: SEQUENCE SET; Schema: dispatch_organization_default; Owner: postgres -- -SELECT pg_catalog.setval('dispatch_organization_default.case_priority_id_seq', 3, true); +SELECT pg_catalog.setval('dispatch_organization_default.case_priority_id_seq', 4, true); -- diff --git a/tests/static/e2e/report-event.spec.ts b/tests/static/e2e/report-event.spec.ts index 45cecee4002b..8bed2f52a772 100644 --- a/tests/static/e2e/report-event.spec.ts +++ b/tests/static/e2e/report-event.spec.ts @@ -13,7 +13,7 @@ test.describe("Authenticated Dispatch App", () => { const title = "Security Event Triage" await reportEventPage.reportEvent(description) - // Soft validate that we get redirected to the case submission form + // Soft validate that we get redirected to the event submission form const expectedURL = encodeURI( `/default/events/report?project=default&title=${title}&description=${description}` )