diff --git a/frontend-react/.eslintrc.cjs b/frontend-react/.eslintrc.cjs index 4a4debd4a41..ab82b5316c1 100644 --- a/frontend-react/.eslintrc.cjs +++ b/frontend-react/.eslintrc.cjs @@ -82,6 +82,7 @@ module.exports = { // TODO: investigate these for reconsideration or per-module ignoring "playwright/no-conditional-in-test": ["off"], "playwright/no-force-option": ["off"], + "playwright/expect-expect": ["off"], }, }, ], diff --git a/frontend-react/e2e/helpers/internal-links.ts b/frontend-react/e2e/helpers/internal-links.ts index f3dc9e4af23..3ad68bb073a 100644 --- a/frontend-react/e2e/helpers/internal-links.ts +++ b/frontend-react/e2e/helpers/internal-links.ts @@ -1,17 +1,58 @@ import { Page } from "@playwright/test"; -export const ELC = - "https://www.cdc.gov/epidemiology-laboratory-capacity/php/about/"; +export const ELC = "https://www.cdc.gov/epidemiology-laboratory-capacity/php/about/"; -export async function clickOnInternalLink( - locator: string, - dataTestId: string, - linkName: string, - page: Page, -) { - await page - .locator(locator) - .getByTestId(dataTestId) - .getByRole("link", { name: linkName }) - .click(); +export async function clickOnInternalLink(locator: string, dataTestId: string, linkName: string, page: Page) { + await page.locator(locator).getByTestId(dataTestId).getByRole("link", { name: linkName }).click(); } + +export interface SideNavItem { + name: string; + path: string; +} + +export const aboutSideNav = [ + { + name: "About", + path: "/about", + }, + { + name: "Our network", + path: "/about/our-network", + }, + { + name: "Product roadmap", + path: "/about/roadmap", + }, + { + name: "News", + path: "/about/news", + }, + { + name: "Case studies", + path: "/about/case-studies", + }, + { + name: "Security", + path: "/about/security", + }, + { + name: "Release notes", + path: "/about/release-notes", + }, +]; + +export const gettingStartedSideNav = [ + { + name: "Getting started", + path: "/getting-started", + }, + { + name: "Sending data", + path: "/getting-started/sending-data", + }, + { + name: "Receiving data", + path: "/getting-started/receiving-data", + }, +]; diff --git a/frontend-react/e2e/pages/BasePage.ts b/frontend-react/e2e/pages/BasePage.ts index 69a5441c8e6..ea831731a04 100644 --- a/frontend-react/e2e/pages/BasePage.ts +++ b/frontend-react/e2e/pages/BasePage.ts @@ -1,6 +1,7 @@ +import { SideNavItem } from "../helpers/internal-links"; import { selectTestOrg } from "../helpers/utils"; import appInsightsConfig from "../mocks/appInsightsConfig.json" assert { type: "json" }; -import { Locator, Page, Request, Response, Route, TestArgs } from "../test"; +import { expect, Locator, Page, Request, Response, Route, TestArgs } from "../test"; export type RouteHandlers = Record[1]>; export type MockRouteCache = Record; @@ -12,25 +13,12 @@ export interface BasePageProps { heading?: Locator; } -export type RouteFulfillOptions = Exclude< - Parameters[0], - undefined -> & { isMock?: boolean }; -export type RouteFulfillOptionsFn = ( - request: Request, -) => Promise | RouteFulfillOptions; +export type RouteFulfillOptions = Exclude[0], undefined> & { isMock?: boolean }; +export type RouteFulfillOptionsFn = (request: Request) => Promise | RouteFulfillOptions; export type RouteHandlerFn = (route: Route, request: Request) => Promise; -export type RouteHandlerFulfillOptions = - | RouteFulfillOptions - | RouteFulfillOptionsFn; -export type RouteHandlerFulfillEntry = [ - url: string, - fulfillOptions: RouteHandlerFulfillOptions, -]; -export type ResponseHandlerEntry = [ - url: string, - handler: (response: Response) => Promise | void, -]; +export type RouteHandlerFulfillOptions = RouteFulfillOptions | RouteFulfillOptionsFn; +export type RouteHandlerFulfillEntry = [url: string, fulfillOptions: RouteHandlerFulfillOptions]; +export type ResponseHandlerEntry = [url: string, handler: (response: Response) => Promise | void]; export type RouteHandlerEntry = [url: string, handler: RouteHandlerFn]; export interface GotoRouteHandlerOptions { @@ -70,10 +58,7 @@ export abstract class BasePage { readonly heading: Locator; readonly footer: Locator; - constructor( - { url, title, heading }: BasePageProps, - testArgs: BasePageTestArgs, - ) { + constructor({ url, title, heading }: BasePageProps, testArgs: BasePageTestArgs) { this.page = testArgs.page; this.url = url; this.title = title; @@ -94,9 +79,7 @@ export abstract class BasePage { return this._mockError; } - set mockError( - err: boolean | number | RouteHandlerFulfillOptions | undefined, - ) { + set mockError(err: boolean | number | RouteHandlerFulfillOptions | undefined) { if (err == null || err === false) { this._mockError = undefined; return; @@ -151,6 +134,39 @@ export abstract class BasePage { ); } + async testHeader() { + await expect(this.page).toHaveTitle(this.title); + await expect(this.heading).toBeVisible(); + } + + async testCard(card: { name: string }) { + const cardHeader = this.page.locator(".usa-card__header", { + hasText: card.name, + }); + + await expect(cardHeader).toBeVisible(); + } + + async testSidenav(navItems: SideNavItem[]) { + const sideNav = this.page.getByTestId("sidenav"); + + for (const navItem of navItems) { + const link = sideNav.locator(`a`, { hasText: navItem.name }); + + await expect(link).toBeVisible(); + await expect(link).toHaveAttribute("href", navItem.path); + } + } + + async testFooter() { + await expect(this.page.locator("footer")).toBeAttached(); + await this.page.locator("footer").scrollIntoViewIfNeeded(); + await expect(this.page.locator("footer")).toBeInViewport(); + await expect(this.page.getByTestId("govBanner")).not.toBeInViewport(); + await this.page.evaluate(() => window.scrollTo(0, 0)); + await expect(this.page.getByTestId("govBanner")).toBeInViewport(); + } + /** * Used to select the test org if logged-in user is Admin and the isTestOrg prop is set to true. * This is needed for smoke tests since they use live data. @@ -210,19 +226,11 @@ export abstract class BasePage { const wrapped = items.map(([url, _fulfillOptions]) => { const fn = async (request: Request) => { const fulfillOptions = - typeof _fulfillOptions === "function" - ? await _fulfillOptions(request) - : _fulfillOptions; + typeof _fulfillOptions === "function" ? await _fulfillOptions(request) : _fulfillOptions; const mockErrorFulfillOptions = - typeof this.mockError === "function" - ? await this.mockError(request) - : this.mockError; - const mockCacheFulfillOptions = this.getMockCacheFulfillOptions( - url, - fulfillOptions, - ); - const mockOverrideFulfillOptions = - mockErrorFulfillOptions ?? mockCacheFulfillOptions; + typeof this.mockError === "function" ? await this.mockError(request) : this.mockError; + const mockCacheFulfillOptions = this.getMockCacheFulfillOptions(url, fulfillOptions); + const mockOverrideFulfillOptions = mockErrorFulfillOptions ?? mockCacheFulfillOptions; return { isMock: true, @@ -233,9 +241,7 @@ export abstract class BasePage { }); wrapped.forEach(([url, fn]) => - this.mockRouteHandlers.set(url, async (route, req) => - route.fulfill(await fn(req)), - ), + this.mockRouteHandlers.set(url, async (route, req) => route.fulfill(await fn(req))), ); return wrapped; @@ -244,15 +250,10 @@ export abstract class BasePage { /** * Helper function to convert RouteHandlerFulfillEntries to RouteHandlerEntries. */ - createRouteHandlers( - items: RouteHandlerFulfillEntry[], - ): RouteHandlerEntry[] { + createRouteHandlers(items: RouteHandlerFulfillEntry[]): RouteHandlerEntry[] { return items.map(([url, _fulfill]) => { const handler = async (route: Route, request: Request) => { - const fulfill = - typeof _fulfill === "function" - ? await _fulfill(request) - : _fulfill; + const fulfill = typeof _fulfill === "function" ? await _fulfill(request) : _fulfill; return route.fulfill(fulfill); }; @@ -302,10 +303,7 @@ export abstract class BasePage { * Get or warm the cache for a particular mock URL's fulfillOptions. This * allows for dynamic options to persist across page reloads for consistency. */ - getMockCacheFulfillOptions( - url: string, - fulfillOptions: RouteFulfillOptions, - ) { + getMockCacheFulfillOptions(url: string, fulfillOptions: RouteFulfillOptions) { const cache = this._mockRouteCache[url]; if (!cache) { this._mockRouteCache[url] = fulfillOptions; diff --git a/frontend-react/e2e/pages/about-side-navigation.ts b/frontend-react/e2e/pages/about-side-navigation.ts deleted file mode 100644 index cd685dbad0e..00000000000 --- a/frontend-react/e2e/pages/about-side-navigation.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function clickNetwork(page: Page) { - await page - .getByTestId("sidenav") - .getByRole("link", { name: /Our network/ }) - .click(); -} - -export async function clickRoadmap(page: Page) { - await page - .getByTestId("sidenav") - .getByRole("link", { name: /Product roadmap/ }) - .click(); -} - -export async function clickNews(page: Page) { - await page.getByTestId("sidenav").getByRole("link", { name: /News/ }).click(); -} - -export async function clickCaseStudies(page: Page) { - await page - .getByTestId("sidenav") - .getByRole("link", { name: /Case studies/ }) - .click(); -} - -export async function clickSecurity(page: Page) { - await page - .getByTestId("sidenav") - .getByRole("link", { name: /Security/ }) - .click(); -} - -export async function clickReleaseNotes(page: Page) { - await page - .getByTestId("sidenav") - .getByRole("link", { name: /Release notes/ }) - .click(); -} diff --git a/frontend-react/e2e/pages/admin/receiver-status.ts b/frontend-react/e2e/pages/authenticated/admin/receiver-status.ts similarity index 75% rename from frontend-react/e2e/pages/admin/receiver-status.ts rename to frontend-react/e2e/pages/authenticated/admin/receiver-status.ts index 0f69f4bb691..a937092350c 100644 --- a/frontend-react/e2e/pages/admin/receiver-status.ts +++ b/frontend-react/e2e/pages/authenticated/admin/receiver-status.ts @@ -1,18 +1,13 @@ import { expect, Locator } from "@playwright/test"; import { endOfDay, format, startOfDay, subDays } from "date-fns"; -import { RSReceiverStatus } from "../../../src/hooks/api/UseReceiversConnectionStatus/UseReceiversConnectionStatus"; +import { RSReceiverStatus } from "../../../../src/hooks/api/UseReceiversConnectionStatus/UseReceiversConnectionStatus"; import { createStatusTimePeriodData, SUCCESS_RATE_CLASSNAME_MAP, -} from "../../../src/pages/admin/receiver-dashboard/utils"; -import { DatePair, dateShortFormat } from "../../../src/utils/DateTimeUtils"; -import { createMockGetReceiverStatus } from "../../mocks/receiverStatus"; -import { - BasePage, - BasePageTestArgs, - type ResponseHandlerEntry, - type RouteHandlerFulfillEntry, -} from "../BasePage"; +} from "../../../../src/pages/admin/receiver-dashboard/utils"; +import { DatePair, dateShortFormat } from "../../../../src/utils/DateTimeUtils"; +import { createMockGetReceiverStatus } from "../../../mocks/receiverStatus"; +import { BasePage, BasePageTestArgs, type ResponseHandlerEntry, type RouteHandlerFulfillEntry } from "../../BasePage"; export interface AdminReceiverStatusPageUpdateFiltersProps { dateRange?: { @@ -90,9 +85,7 @@ export class AdminReceiverStatusPage extends BasePage { ); this.addMockRouteHandlers([this.createMockReceiverStatusHandler()]); - this.addResponseHandlers([ - this.createMockReceiverStatusResponseHandler(), - ]); + this.addResponseHandlers([this.createMockReceiverStatusResponseHandler()]); const now = new Date(); @@ -100,13 +93,8 @@ export class AdminReceiverStatusPage extends BasePage { this._timePeriodData = []; this.filterForm = this.page.getByRole("form", { name: "filter" }); - const dateRangeOverlay = this.page - .getByRole("dialog") - .locator(".usa-modal-overlay"); - const dateRangeDefaultValue = [ - startOfDay(subDays(now, 2)), - endOfDay(now), - ] as DatePair; + const dateRangeOverlay = this.page.getByRole("dialog").locator(".usa-modal-overlay"); + const dateRangeDefaultValue = [startOfDay(subDays(now, 2)), endOfDay(now)] as DatePair; this.filterFormInputs = { dateRange: { label: this.page.locator("label", { @@ -116,8 +104,7 @@ export class AdminReceiverStatusPage extends BasePage { name: "Change...", }), modalOverlay: dateRangeOverlay, - expectedModalOverlayText: - "Select date range to show. (Max 10 days span)FromToUpdate", + expectedModalOverlayText: "Select date range to show. (Max 10 days span)FromToUpdate", modalOverlayCalendar: dateRangeOverlay.getByRole("application"), modalPrimaryButton: dateRangeOverlay.getByRole("button", { name: "Update", @@ -144,22 +131,15 @@ export class AdminReceiverStatusPage extends BasePage { name: "Receiver Name:", }), expectedDefaultValue: "", - tooltip: this.page - .getByTestId("tooltipWrapper") - .nth(0) - .getByRole("tooltip"), + tooltip: this.page.getByTestId("tooltipWrapper").nth(0).getByRole("tooltip"), value: "", }, resultMessage: { label: this.page.locator("label", { hasText: "Results Message:", }), - expectedTooltipText: - "Filter rows on the Result Message details. This value is found in the details.", - tooltip: this.page - .getByTestId("tooltipWrapper") - .nth(1) - .getByRole("tooltip"), + expectedTooltipText: "Filter rows on the Result Message details. This value is found in the details.", + tooltip: this.page.getByTestId("tooltipWrapper").nth(1).getByRole("tooltip"), input: this.page.getByRole("textbox", { name: "Results Message:", }), @@ -171,10 +151,7 @@ export class AdminReceiverStatusPage extends BasePage { hasText: "Success Type:", }), expectedTooltipText: "Show only rows in one of these states.", - tooltip: this.page - .getByTestId("tooltipWrapper") - .nth(2) - .getByRole("tooltip"), + tooltip: this.page.getByTestId("tooltipWrapper").nth(2).getByRole("tooltip"), input: this.page.getByRole("combobox", { name: "Success Type:", }), @@ -192,10 +169,7 @@ export class AdminReceiverStatusPage extends BasePage { * Error expected additionally if user context isn't admin */ get isPageLoadExpected() { - return ( - super.isPageLoadExpected && - this.testArgs.storageState === this.testArgs.adminLogin.path - ); + return super.isPageLoadExpected && this.testArgs.storageState === this.testArgs.adminLogin.path; } get receiverStatus() { @@ -213,10 +187,7 @@ export class AdminReceiverStatusPage extends BasePage { const url = new URL(request.url()); const startDate = url.searchParams.get("start_date"); const endDate = url.searchParams.get("end_date"); - const range = - startDate && endDate - ? ([new Date(startDate), new Date(endDate)] as DatePair) - : undefined; + const range = startDate && endDate ? ([new Date(startDate), new Date(endDate)] as DatePair) : undefined; return { json: this.createMockReceiverStatuses(range), @@ -233,27 +204,18 @@ export class AdminReceiverStatusPage extends BasePage { const url = new URL(apiRes.url()); const startDate = url.searchParams.get("start_date"); const endDate = url.searchParams.get("end_date"); - const range = - startDate && endDate - ? ([new Date(startDate), new Date(endDate)] as DatePair) - : undefined; + const range = startDate && endDate ? ([new Date(startDate), new Date(endDate)] as DatePair) : undefined; this._receiverStatus = data; - this._timePeriodData = range - ? this.createTimePeriodData({ data, range }) - : []; + this._timePeriodData = range ? this.createTimePeriodData({ data, range }) : []; }, ]; } - createMockReceiverStatuses( - ...args: Parameters - ) { + createMockReceiverStatuses(...args: Parameters) { return createMockGetReceiverStatus(...args); } - createTimePeriodData( - ...args: Parameters - ) { + createTimePeriodData(...args: Parameters) { return createStatusTimePeriodData(...args); } @@ -281,11 +243,7 @@ export class AdminReceiverStatusPage extends BasePage { }); } - getExpectedReceiverStatusRowTitle( - organizationName: string, - receiverName: string, - successRate: number | string, - ) { + getExpectedReceiverStatusRowTitle(organizationName: string, receiverName: string, successRate: number | string) { return [organizationName, receiverName, successRate, "%"].join(""); } @@ -303,16 +261,14 @@ export class AdminReceiverStatusPage extends BasePage { allCustom: async () => { return (await days.all()).map((d) => Object.assign(d, { - timePeriods: - this.getReceiverStatusRowDisplayDayTimePeriods(d), + timePeriods: this.getReceiverStatusRowDisplayDayTimePeriods(d), }), ); }, nthCustom: (nth: number) => { const day = days.nth(nth); return Object.assign(day, { - timePeriods: - this.getReceiverStatusRowDisplayDayTimePeriods(day), + timePeriods: this.getReceiverStatusRowDisplayDayTimePeriods(day), }); }, }); @@ -334,39 +290,21 @@ export class AdminReceiverStatusPage extends BasePage { }: AdminReceiverStatusPageUpdateFiltersProps) { // API request will only fire if date ranges are different const isDateRangeDifferent = - dateRange == null || - this.getIsDateRangesDifferent( - this.filterFormInputs.dateRange.value, - dateRange.value, - ); - const isRequestAwaitedBool = - dateRange != null && - isDateRangeDifferent && - dateRange.isRequestAwaited !== false; + dateRange == null || this.getIsDateRangesDifferent(this.filterFormInputs.dateRange.value, dateRange.value); + const isRequestAwaitedBool = dateRange != null && isDateRangeDifferent && dateRange.isRequestAwaited !== false; const p = isRequestAwaitedBool - ? this.page.waitForRequest( - AdminReceiverStatusPage.API_RECEIVER_STATUS, - ) + ? this.page.waitForRequest(AdminReceiverStatusPage.API_RECEIVER_STATUS) : Promise.resolve(); if (dateRange && isDateRangeDifferent) { const { value, inputMethod } = dateRange; await this.updateFilterDateRange(...value, inputMethod); } - if ( - receiverName != null && - receiverName !== this.filterFormInputs.receiverName.value - ) + if (receiverName != null && receiverName !== this.filterFormInputs.receiverName.value) await this.updateFilterReceiverName(receiverName); - if ( - resultMessage != null && - resultMessage !== this.filterFormInputs.resultMessage.value - ) + if (resultMessage != null && resultMessage !== this.filterFormInputs.resultMessage.value) await this.updateFilterResultMessage(resultMessage); - if ( - successType != null && - successType !== this.filterFormInputs.successType.value - ) + if (successType != null && successType !== this.filterFormInputs.successType.value) await this.updateFilterSuccessType(successType); if (!isRequestAwaitedBool) return undefined as void; @@ -376,11 +314,7 @@ export class AdminReceiverStatusPage extends BasePage { return reqUrl; } - async updateFilterDateRange( - start: Date, - end: Date, - inputMethod: "textbox" | "calendar" = "textbox", - ) { + async updateFilterDateRange(start: Date, end: Date, inputMethod: "textbox" | "calendar" = "textbox") { const { button, modalStartInput, @@ -438,10 +372,8 @@ export class AdminReceiverStatusPage extends BasePage { dateRange: { value: this.filterFormInputs.dateRange.expectedDefaultValue, }, - receiverName: - this.filterFormInputs.receiverName.expectedDefaultValue, - resultMessage: - this.filterFormInputs.resultMessage.expectedDefaultValue, + receiverName: this.filterFormInputs.receiverName.expectedDefaultValue, + resultMessage: this.filterFormInputs.resultMessage.expectedDefaultValue, successType: this.filterFormInputs.successType.expectedDefaultValue, }; return await this.updateFilters(resetValues); @@ -471,9 +403,7 @@ export class AdminReceiverStatusPage extends BasePage { async testReceiverStatusDisplay() { const [startDate, endDate] = this.filterFormInputs.dateRange.value; const statusRows = this.receiverStatusRowsLocator; - await expect(statusRows).toHaveCount( - new Set(this.receiverStatus?.map((r) => r.receiverId)).size, - ); + await expect(statusRows).toHaveCount(new Set(this.receiverStatus?.map((r) => r.receiverId)).size); const expectedDaysText = [ dateShortFormat(startDate), @@ -482,13 +412,7 @@ export class AdminReceiverStatusPage extends BasePage { ].join(" "); for (const [ i, - { - days, - successRate, - organizationName, - receiverName, - successRateType, - }, + { days, successRate, organizationName, receiverName, successRateType }, ] of this.timePeriodData.entries()) { const { title, display, days: daysLoc } = statusRows.nthCustom(i); @@ -497,9 +421,7 @@ export class AdminReceiverStatusPage extends BasePage { receiverName, successRate, ); - const expectedClass = new RegExp( - SUCCESS_RATE_CLASSNAME_MAP[successRateType], - ); + const expectedClass = new RegExp(SUCCESS_RATE_CLASSNAME_MAP[successRateType]); await expect(title).toBeVisible(); await expect(title).toHaveText(expectedTitleText); @@ -516,9 +438,7 @@ export class AdminReceiverStatusPage extends BasePage { for (const [i, { successRateType }] of timePeriods.entries()) { const sliceEle = daySlices.nth(i); - const expectedClass = new RegExp( - SUCCESS_RATE_CLASSNAME_MAP[successRateType], - ); + const expectedClass = new RegExp(SUCCESS_RATE_CLASSNAME_MAP[successRateType]); await expect(sliceEle).toBeVisible(); await expect(sliceEle).toHaveClass(expectedClass); diff --git a/frontend-react/e2e/pages/daily-data-details.ts b/frontend-react/e2e/pages/authenticated/daily-data-details.ts similarity index 90% rename from frontend-react/e2e/pages/daily-data-details.ts rename to frontend-react/e2e/pages/authenticated/daily-data-details.ts index 06d382632b9..ed98347e902 100644 --- a/frontend-react/e2e/pages/daily-data-details.ts +++ b/frontend-react/e2e/pages/authenticated/daily-data-details.ts @@ -1,8 +1,8 @@ -import { BasePage, BasePageTestArgs, type RouteHandlerFulfillEntry } from "./BasePage"; import { API_WATERS_REPORT, URL_REPORT_DETAILS } from "./report-details"; -import { RSDelivery, RSFacility } from "../../src/config/endpoints/deliveries"; -import { MOCK_GET_DELIVERY } from "../mocks/delivery"; -import { MOCK_GET_FACILITIES } from "../mocks/facilities"; +import { RSDelivery, RSFacility } from "../../../src/config/endpoints/deliveries"; +import { MOCK_GET_DELIVERY } from "../../mocks/delivery"; +import { MOCK_GET_FACILITIES } from "../../mocks/facilities"; +import { BasePage, BasePageTestArgs, type RouteHandlerFulfillEntry } from "../BasePage"; const id = "73e3cbc8-9920-4ab7-871f-843a1db4c074"; diff --git a/frontend-react/e2e/pages/daily-data.ts b/frontend-react/e2e/pages/authenticated/daily-data.ts similarity index 96% rename from frontend-react/e2e/pages/daily-data.ts rename to frontend-react/e2e/pages/authenticated/daily-data.ts index 647a20ede97..540b2eb6396 100644 --- a/frontend-react/e2e/pages/daily-data.ts +++ b/frontend-react/e2e/pages/authenticated/daily-data.ts @@ -1,10 +1,9 @@ import { expect, Page } from "@playwright/test"; import { format } from "date-fns"; -import { BasePage, BasePageTestArgs, type RouteHandlerFulfillEntry } from "./BasePage"; import { DailyDataDetailsPage } from "./daily-data-details"; import { API_WATERS_ORG } from "./report-details"; -import { RSReceiver } from "../../src/config/endpoints/settings"; -import { TEST_ORG_AK, TEST_ORG_AK_RECEIVER, TEST_ORG_IGNORE, TEST_ORG_UP_RECEIVER_UP } from "../helpers/utils"; +import { RSReceiver } from "../../../src/config/endpoints/settings"; +import { TEST_ORG_AK, TEST_ORG_AK_RECEIVER, TEST_ORG_IGNORE, TEST_ORG_UP_RECEIVER_UP } from "../../helpers/utils"; import { MOCK_GET_DELIVERIES_AK, MOCK_GET_DELIVERIES_AK_FILENAME, @@ -14,9 +13,10 @@ import { MOCK_GET_DELIVERIES_IGNORE_FILENAME, MOCK_GET_DELIVERIES_IGNORE_FULL_ELR, MOCK_GET_DELIVERIES_IGNORE_REPORT_ID, -} from "../mocks/deliveries"; -import { MOCK_GET_DELIVERY } from "../mocks/delivery"; -import { MOCK_GET_FACILITIES } from "../mocks/facilities"; +} from "../../mocks/deliveries"; +import { MOCK_GET_DELIVERY } from "../../mocks/delivery"; +import { MOCK_GET_FACILITIES } from "../../mocks/facilities"; +import { BasePage, BasePageTestArgs, type RouteHandlerFulfillEntry } from "../BasePage"; export class DailyDataPage extends BasePage { static readonly URL_DAILY_DATA = "/daily-data"; diff --git a/frontend-react/e2e/pages/authenticated/last-mile-failures.ts b/frontend-react/e2e/pages/authenticated/last-mile-failures.ts new file mode 100644 index 00000000000..ab8e38e5f92 --- /dev/null +++ b/frontend-react/e2e/pages/authenticated/last-mile-failures.ts @@ -0,0 +1,45 @@ +import { MOCK_GET_RESEND, MOCK_GET_SEND_FAILURES } from "../../mocks/lastMilefailures"; +import { BasePage, BasePageTestArgs, RouteHandlerFulfillEntry } from "../BasePage"; + +export class LastMileFailuresPage extends BasePage { + static readonly URL_LAST_MILE = "/admin/lastmile"; + static readonly API_GET_SEND_FAILURES = "/api/adm/getsendfailures?days_to_show=15"; + static readonly API_GET_RESEND = "/api/adm/getresend?days_to_show=15"; + + constructor(testArgs: BasePageTestArgs) { + super( + { + url: LastMileFailuresPage.URL_LAST_MILE, + title: "Last Mile Failures", + heading: testArgs.page.getByRole("heading", { + name: "Last Mile Failures", + }), + }, + testArgs, + ); + + this.addMockRouteHandlers([this.createMockGetSendFailuresHandler(), this.createMockGetResendHandler()]); + } + + createMockGetSendFailuresHandler(): RouteHandlerFulfillEntry { + return [ + LastMileFailuresPage.API_GET_SEND_FAILURES, + () => { + return { + json: MOCK_GET_SEND_FAILURES, + }; + }, + ]; + } + + createMockGetResendHandler(): RouteHandlerFulfillEntry { + return [ + LastMileFailuresPage.API_GET_RESEND, + () => { + return { + json: MOCK_GET_RESEND, + }; + }, + ]; + } +} diff --git a/frontend-react/e2e/pages/authenticated/message-details.ts b/frontend-react/e2e/pages/authenticated/message-details.ts new file mode 100644 index 00000000000..124831a1829 --- /dev/null +++ b/frontend-react/e2e/pages/authenticated/message-details.ts @@ -0,0 +1,34 @@ +import { MessageIDSearchPage } from "./message-id-search"; +import { MOCK_GET_MESSAGE } from "../../mocks/messages"; + +import { BasePage, BasePageTestArgs, RouteHandlerFulfillEntry } from "../BasePage"; + +export class MessageDetailsPage extends BasePage { + static readonly URL_MESSAGE_DETAILS = `/message-details/${MessageIDSearchPage.MESSAGE_ID}`; + + constructor(testArgs: BasePageTestArgs) { + super( + { + url: MessageDetailsPage.URL_MESSAGE_DETAILS, + title: "ReportStream - CDC's free, interoperable data transfer platform", + heading: testArgs.page.getByRole("heading", { + name: "Message ID Search", + }), + }, + testArgs, + ); + + this.addMockRouteHandlers([this.createMessageIDSearchAPIHandler()]); + } + + createMessageIDSearchAPIHandler(): RouteHandlerFulfillEntry { + return [ + MessageIDSearchPage.API_MESSAGE, + () => { + return { + json: MOCK_GET_MESSAGE, + }; + }, + ]; + } +} diff --git a/frontend-react/e2e/pages/authenticated/message-id-search.ts b/frontend-react/e2e/pages/authenticated/message-id-search.ts new file mode 100644 index 00000000000..d708f3f10c3 --- /dev/null +++ b/frontend-react/e2e/pages/authenticated/message-id-search.ts @@ -0,0 +1,46 @@ +import { MOCK_GET_MESSAGE, MOCK_GET_MESSAGES } from "../../mocks/messages"; +import { BasePage, BasePageTestArgs, RouteHandlerFulfillEntry } from "../BasePage"; + +export class MessageIDSearchPage extends BasePage { + static readonly URL_MESSAGE_ID_SEARCH = "/admin/message-tracker"; + static readonly API_MESSAGES = "**/api/messages?messageId=*"; + static readonly API_MESSAGE = "**/api/message/*"; + static readonly MESSAGE_ID = "582098"; + + constructor(testArgs: BasePageTestArgs) { + super( + { + url: MessageIDSearchPage.URL_MESSAGE_ID_SEARCH, + title: "Message ID search - Admin", + heading: testArgs.page.getByRole("heading", { + name: "Message ID Search", + }), + }, + testArgs, + ); + + this.addMockRouteHandlers([this.createMessageIDSearchAPIHandler(), this.createMessagesIDSearchAPIHandler()]); + } + + createMessageIDSearchAPIHandler(): RouteHandlerFulfillEntry { + return [ + MessageIDSearchPage.API_MESSAGE, + () => { + return { + json: MOCK_GET_MESSAGE, + }; + }, + ]; + } + + createMessagesIDSearchAPIHandler(): RouteHandlerFulfillEntry { + return [ + MessageIDSearchPage.API_MESSAGES, + () => { + return { + json: MOCK_GET_MESSAGES, + }; + }, + ]; + } +} diff --git a/frontend-react/e2e/pages/organization.ts b/frontend-react/e2e/pages/authenticated/organization.ts similarity index 61% rename from frontend-react/e2e/pages/organization.ts rename to frontend-react/e2e/pages/authenticated/organization.ts index bbac970dd64..6d24305989d 100644 --- a/frontend-react/e2e/pages/organization.ts +++ b/frontend-react/e2e/pages/authenticated/organization.ts @@ -1,10 +1,6 @@ -import { - BasePage, - BasePageTestArgs, - type RouteHandlerFulfillEntry, -} from "./BasePage"; -import { RSOrganizationSettings } from "../../src/config/endpoints/settings"; -import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../mocks/organizations"; +import { RSOrganizationSettings } from "../../../src/config/endpoints/settings"; +import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../mocks/organizations"; +import { BasePage, BasePageTestArgs, type RouteHandlerFulfillEntry } from "../BasePage"; export class OrganizationPage extends BasePage { static readonly API_ORGANIZATIONS = "/api/settings/organizations"; @@ -13,7 +9,7 @@ export class OrganizationPage extends BasePage { super( { url: "/admin/settings", - title: "Organizations", + title: "Admin-Organizations", heading: testArgs.page.getByRole("heading", { name: "Organizations", }), @@ -23,19 +19,13 @@ export class OrganizationPage extends BasePage { this._organizationSettings = []; this.addResponseHandlers([ - [ - OrganizationPage.API_ORGANIZATIONS, - async (res) => (this._organizationSettings = await res.json()), - ], + [OrganizationPage.API_ORGANIZATIONS, async (res) => (this._organizationSettings = await res.json())], ]); this.addMockRouteHandlers([this.createMockOrganizationHandler()]); } get isPageLoadExpected() { - return ( - super.isPageLoadExpected && - this.testArgs.storageState === this.testArgs.adminLogin.path - ); + return super.isPageLoadExpected && this.testArgs.storageState === this.testArgs.adminLogin.path; } createMockOrganizationHandler(): RouteHandlerFulfillEntry { diff --git a/frontend-react/e2e/pages/report-details.ts b/frontend-react/e2e/pages/authenticated/report-details.ts similarity index 90% rename from frontend-react/e2e/pages/report-details.ts rename to frontend-react/e2e/pages/authenticated/report-details.ts index 01f342b9383..757335b0c7d 100644 --- a/frontend-react/e2e/pages/report-details.ts +++ b/frontend-react/e2e/pages/authenticated/report-details.ts @@ -1,8 +1,8 @@ import { expect, Page } from "@playwright/test"; import fs from "node:fs"; -import { MOCK_GET_DELIVERY } from "../mocks/delivery"; -import { MOCK_GET_HISTORY_REPORT } from "../mocks/historyReport"; -import { MOCK_GET_SUBMISSION_HISTORY } from "../mocks/submissionHistory"; +import { MOCK_GET_DELIVERY } from "../../mocks/delivery"; +import { MOCK_GET_HISTORY_REPORT } from "../../mocks/historyReport"; +import { MOCK_GET_SUBMISSION_HISTORY } from "../../mocks/submissionHistory"; export const URL_REPORT_DETAILS = "/report-details"; export const API_WATERS_REPORT = "**/api/waters/report"; diff --git a/frontend-react/e2e/pages/submission-history.ts b/frontend-react/e2e/pages/authenticated/submission-history.ts similarity index 71% rename from frontend-react/e2e/pages/submission-history.ts rename to frontend-react/e2e/pages/authenticated/submission-history.ts index e65b4792397..f09dad1bf5a 100644 --- a/frontend-react/e2e/pages/submission-history.ts +++ b/frontend-react/e2e/pages/authenticated/submission-history.ts @@ -1,6 +1,6 @@ import { expect, Page } from "@playwright/test"; -import { MOCK_GET_SUBMISSION_HISTORY } from "../mocks/submissionHistory"; -import { MOCK_GET_SUBMISSIONS } from "../mocks/submissions"; +import { MOCK_GET_SUBMISSION_HISTORY } from "../../mocks/submissionHistory"; +import { MOCK_GET_SUBMISSIONS } from "../../mocks/submissions"; export const URL_SUBMISSION_HISTORY = "/submissions"; export const API_GET_REPORT_HISTORY = `**/api/waters/report/**`; @@ -21,11 +21,7 @@ export function getOrgSubmissionsAPI(org: string) { return `**/api/waters/org/${org}/submissions?*`; } -export async function mockGetSubmissionsResponse( - page: Page, - org: string, - responseStatus = 200, -) { +export async function mockGetSubmissionsResponse(page: Page, org: string, responseStatus = 200) { const submissionsApi = getOrgSubmissionsAPI(org); await page.route(submissionsApi, async (route) => { const json = MOCK_GET_SUBMISSIONS; @@ -33,10 +29,7 @@ export async function mockGetSubmissionsResponse( }); } -export async function mockGetReportHistoryResponse( - page: Page, - responseStatus = 200, -) { +export async function mockGetReportHistoryResponse(page: Page, responseStatus = 200) { await page.route(API_GET_REPORT_HISTORY, async (route) => { const json = MOCK_GET_SUBMISSION_HISTORY; await route.fulfill({ json, status: responseStatus }); @@ -49,33 +42,21 @@ export async function openReportIdDetailPage(page: Page, id: string) { } export async function title(page: Page) { - await expect(page).toHaveTitle( - /ReportStream - CDC's free, interoperable data transfer platform/, - ); + await expect(page).toHaveTitle(/ReportStream - CDC's free, interoperable data transfer platform/); } export async function tableHeaders(page: Page) { await expect(page.locator(".usa-table th").nth(0)).toHaveText(/Report ID/); - await expect(page.locator(".usa-table th").nth(1)).toHaveText( - "Date/time submitted", - ); + await expect(page.locator(".usa-table th").nth(1)).toHaveText("Date/time submitted"); await expect(page.locator(".usa-table th").nth(2)).toHaveText(/File/); await expect(page.locator(".usa-table th").nth(3)).toHaveText(/Records/); await expect(page.locator(".usa-table th").nth(4)).toHaveText(/Status/); } -export async function breadcrumbLink( - page: Page, - index: number, - linkName: string, - expectedUrl: string, -) { +export async function breadcrumbLink(page: Page, index: number, linkName: string, expectedUrl: string) { const breadcrumbLinks = page.locator(".usa-breadcrumb ol li"); await expect(breadcrumbLinks.nth(index)).toHaveText(linkName); - await breadcrumbLinks - .nth(index) - .getByRole("link", { name: linkName }) - .click(); + await breadcrumbLinks.nth(index).getByRole("link", { name: linkName }).click(); await expect(page.locator("h1")).toBeAttached(); await expect(page).toHaveURL(expectedUrl); } diff --git a/frontend-react/e2e/pages/homepage.ts b/frontend-react/e2e/pages/homepage.ts deleted file mode 100644 index 5a22616b8ba..00000000000 --- a/frontend-react/e2e/pages/homepage.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function goto(page: Page) { - await page.goto("/", { - waitUntil: "domcontentloaded", - }); -} diff --git a/frontend-react/e2e/pages/last-mile-failures.ts b/frontend-react/e2e/pages/last-mile-failures.ts deleted file mode 100644 index 86baae6ac4f..00000000000 --- a/frontend-react/e2e/pages/last-mile-failures.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Page } from "@playwright/test"; -import { - MOCK_GET_RESEND, - MOCK_GET_SEND_FAILURES, -} from "../mocks/lastMilefailures"; - -const URL_LAST_MILE = "/admin/lastmile"; -const API_GET_RESEND = "/api/adm/getresend?days_to_show=15"; -export const API_GET_SEND_FAILURES = "/api/adm/getsendfailures?days_to_show=15"; - -export async function goto(page: Page) { - await page.goto(URL_LAST_MILE, { - waitUntil: "domcontentloaded", - }); -} - -export async function mockGetSendFailuresResponse( - page: Page, - responseStatus = 200, -) { - await page.route(API_GET_SEND_FAILURES, async (route) => { - const json = MOCK_GET_SEND_FAILURES; - await route.fulfill({ json, status: responseStatus }); - }); -} - -export async function mockGetResendResponse(page: Page) { - await page.route(API_GET_RESEND, async (route) => { - const json = MOCK_GET_RESEND; - await route.fulfill({ json }); - }); -} diff --git a/frontend-react/e2e/pages/message-details.ts b/frontend-react/e2e/pages/message-details.ts deleted file mode 100644 index f989d00d1f7..00000000000 --- a/frontend-react/e2e/pages/message-details.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Page } from "@playwright/test"; - -import { MESSAGE_ID } from "../pages/message-id-search"; - -export const URL_MESSAGE_DETAILS = `/message-details/${MESSAGE_ID}`; - -export async function goto(page: Page) { - await page.goto(URL_MESSAGE_DETAILS, { - waitUntil: "domcontentloaded", - }); -} diff --git a/frontend-react/e2e/pages/message-id-search.ts b/frontend-react/e2e/pages/message-id-search.ts deleted file mode 100644 index a71a877b395..00000000000 --- a/frontend-react/e2e/pages/message-id-search.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Page } from "@playwright/test"; - -export const URL_MESSAGE_ID_SEARCH = "/admin/message-tracker"; -export const API_MESSAGES = "**/api/messages?messageId=*"; -export const API_MESSAGE = "**/api/message/*"; - -export const MESSAGE_ID = "582098"; - -export async function goto(page: Page) { - await page.goto(URL_MESSAGE_ID_SEARCH, { - waitUntil: "domcontentloaded", - }); -} diff --git a/frontend-react/e2e/pages/our-network.ts b/frontend-react/e2e/pages/our-network.ts deleted file mode 100644 index 7a8f3d7dbc7..00000000000 --- a/frontend-react/e2e/pages/our-network.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect, Page } from "@playwright/test"; - -const URL_OUR_NETWORK = "/about/our-network"; -export async function goto(page: Page) { - await page.goto(URL_OUR_NETWORK, { - waitUntil: "domcontentloaded", - }); -} -export async function onLoad(page: Page) { - await expect(page).toHaveURL(/our-network/); - await expect(page).toHaveTitle(/Our network/); -} - -export async function clickOnLiveMap(page: Page) { - await page.getByTestId("map").click(); - await expect(page).toHaveURL(URL_OUR_NETWORK); -} diff --git a/frontend-react/e2e/pages/public-pages-link-check.ts b/frontend-react/e2e/pages/public-pages-link-check.ts deleted file mode 100644 index 64224aad02d..00000000000 --- a/frontend-react/e2e/pages/public-pages-link-check.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function publicPageGoto(page: Page, path: string) { - await page.goto(path, { - waitUntil: "networkidle", - }); -} diff --git a/frontend-react/e2e/pages/about.ts b/frontend-react/e2e/pages/public/about/about.ts similarity index 85% rename from frontend-react/e2e/pages/about.ts rename to frontend-react/e2e/pages/public/about/about.ts index 2189c703f39..c1c0d3b6082 100644 --- a/frontend-react/e2e/pages/about.ts +++ b/frontend-react/e2e/pages/public/about/about.ts @@ -1,4 +1,4 @@ -import { BasePage, BasePageTestArgs } from "./BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export class AboutPage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/public/about/our-network.ts b/frontend-react/e2e/pages/public/about/our-network.ts new file mode 100644 index 00000000000..c36c21b8b87 --- /dev/null +++ b/frontend-react/e2e/pages/public/about/our-network.ts @@ -0,0 +1,16 @@ +import { BasePage, BasePageTestArgs } from "../../BasePage"; + +export class OurNetworkPage extends BasePage { + constructor(testArgs: BasePageTestArgs) { + super( + { + url: "/about/our-network", + title: "Our network - ReportStream", + heading: testArgs.page.getByRole("heading", { + name: "Our network", + }), + }, + testArgs, + ); + } +} diff --git a/frontend-react/e2e/pages/public/about/roadmap.ts b/frontend-react/e2e/pages/public/about/roadmap.ts new file mode 100644 index 00000000000..80c6227940f --- /dev/null +++ b/frontend-react/e2e/pages/public/about/roadmap.ts @@ -0,0 +1,16 @@ +import { BasePage, BasePageTestArgs } from "../../BasePage"; + +export class RoadmapPage extends BasePage { + constructor(testArgs: BasePageTestArgs) { + super( + { + url: "/about/roadmap", + title: "Product roadmap", + heading: testArgs.page.getByRole("heading", { + name: "Product roadmap", + }), + }, + testArgs, + ); + } +} diff --git a/frontend-react/e2e/pages/security.ts b/frontend-react/e2e/pages/public/about/security.ts similarity index 91% rename from frontend-react/e2e/pages/security.ts rename to frontend-react/e2e/pages/public/about/security.ts index 8294a43283e..5d494e97795 100644 --- a/frontend-react/e2e/pages/security.ts +++ b/frontend-react/e2e/pages/public/about/security.ts @@ -1,5 +1,5 @@ import { expect, Page } from "@playwright/test"; -import { BasePage, BasePageTestArgs } from "./BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export class SecurityPage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/getting-started/receiving-data.ts b/frontend-react/e2e/pages/public/getting-started/receiving-data.ts similarity index 88% rename from frontend-react/e2e/pages/getting-started/receiving-data.ts rename to frontend-react/e2e/pages/public/getting-started/receiving-data.ts index d87fb8acdb7..3ccd227e24f 100644 --- a/frontend-react/e2e/pages/getting-started/receiving-data.ts +++ b/frontend-react/e2e/pages/public/getting-started/receiving-data.ts @@ -1,4 +1,4 @@ -import { BasePage, BasePageTestArgs } from "../BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export class ReceivingDataPage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/getting-started/sending-data.ts b/frontend-react/e2e/pages/public/getting-started/sending-data.ts similarity index 88% rename from frontend-react/e2e/pages/getting-started/sending-data.ts rename to frontend-react/e2e/pages/public/getting-started/sending-data.ts index 9bb57acf9a5..47aa28823d5 100644 --- a/frontend-react/e2e/pages/getting-started/sending-data.ts +++ b/frontend-react/e2e/pages/public/getting-started/sending-data.ts @@ -1,4 +1,4 @@ -import { BasePage, BasePageTestArgs } from "../BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export class SendingDataPage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/header.ts b/frontend-react/e2e/pages/public/header.ts similarity index 63% rename from frontend-react/e2e/pages/header.ts rename to frontend-react/e2e/pages/public/header.ts index b4929379008..1ed78a50c6e 100644 --- a/frontend-react/e2e/pages/header.ts +++ b/frontend-react/e2e/pages/public/header.ts @@ -6,11 +6,7 @@ export async function clickOnHome(page: Page) { } export async function clickOnAbout(page: Page) { - await page - .getByTestId("header") - .getByTestId("navDropDownButton") - .getByText("About") - .click(); + await page.getByTestId("header").getByTestId("navDropDownButton").getByText("About").click(); expect(page.getByText("About ReportStream")).toBeTruthy(); expect(page.getByText("Our network")).toBeTruthy(); @@ -21,34 +17,22 @@ export async function clickOnAbout(page: Page) { } export async function clickOnGettingStarted(page: Page) { - await page - .getByTestId("header") - .getByRole("link", { name: "Getting started" }) - .click(); + await page.getByTestId("header").getByRole("link", { name: "Getting started" }).click(); await expect(page).toHaveURL(/getting-started/); } export async function clickOnDevelopers(page: Page) { - await page - .getByTestId("header") - .getByRole("link", { name: "Developers" }) - .click(); + await page.getByTestId("header").getByRole("link", { name: "Developers" }).click(); await expect(page).toHaveURL(/.*developer-resources/); } export async function clickOnYourConnection(page: Page) { - await page - .getByTestId("header") - .getByRole("link", { name: "Your Connection" }) - .click(); + await page.getByTestId("header").getByRole("link", { name: "Your Connection" }).click(); await expect(page).toHaveURL(/.*managing-your-connection/); } export async function clickOnSupport(page: Page) { - await page - .getByTestId("header") - .getByRole("link", { name: "Support" }) - .click(); + await page.getByTestId("header").getByRole("link", { name: "Support" }).click(); await expect(page).toHaveURL(/.*support/); } diff --git a/frontend-react/e2e/pages/public/homepage.ts b/frontend-react/e2e/pages/public/homepage.ts new file mode 100644 index 00000000000..994f14f5116 --- /dev/null +++ b/frontend-react/e2e/pages/public/homepage.ts @@ -0,0 +1,17 @@ +import { BasePage, BasePageTestArgs } from "../BasePage"; + +export class HomePage extends BasePage { + constructor(testArgs: BasePageTestArgs) { + super( + { + url: "/", + title: "ReportStream - CDC's free, interoperable data transfer platform", + heading: testArgs.page.getByRole("heading", { + name: "CDC’s free, single connection to streamline your data transfer and improve public health", + exact: true, + }), + }, + testArgs, + ); + } +} diff --git a/frontend-react/e2e/pages/managing-your-connection.ts b/frontend-react/e2e/pages/public/managing-your-connection/managing-your-connection.ts similarity index 91% rename from frontend-react/e2e/pages/managing-your-connection.ts rename to frontend-react/e2e/pages/public/managing-your-connection/managing-your-connection.ts index 6098bec9e63..31427cb506b 100644 --- a/frontend-react/e2e/pages/managing-your-connection.ts +++ b/frontend-react/e2e/pages/public/managing-your-connection/managing-your-connection.ts @@ -1,5 +1,5 @@ import { expect, Page } from "@playwright/test"; -import { BasePage, BasePageTestArgs } from "./BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export async function onLoad(page: Page) { await expect(page).toHaveURL(/managing-your-connection/); diff --git a/frontend-react/e2e/pages/refer-healthcare.ts b/frontend-react/e2e/pages/public/managing-your-connection/refer-healthcare.ts similarity index 88% rename from frontend-react/e2e/pages/refer-healthcare.ts rename to frontend-react/e2e/pages/public/managing-your-connection/refer-healthcare.ts index b86152b914f..a5805d5a9c0 100644 --- a/frontend-react/e2e/pages/refer-healthcare.ts +++ b/frontend-react/e2e/pages/public/managing-your-connection/refer-healthcare.ts @@ -1,4 +1,4 @@ -import { BasePage, BasePageTestArgs } from "./BasePage"; +import { BasePage, BasePageTestArgs } from "../../BasePage"; export class ReferHealthcarePage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/public/resources.ts b/frontend-react/e2e/pages/public/resources.ts new file mode 100644 index 00000000000..5accb844500 --- /dev/null +++ b/frontend-react/e2e/pages/public/resources.ts @@ -0,0 +1,17 @@ +import { BasePage, BasePageTestArgs } from "../BasePage"; + +export class DeveloperResourcesPage extends BasePage { + constructor(testArgs: BasePageTestArgs) { + super( + { + url: "/developer-resources", + title: "ReportStream developer resources", + heading: testArgs.page.getByRole("heading", { + name: "Developer resources", + exact: true, + }), + }, + testArgs, + ); + } +} diff --git a/frontend-react/e2e/pages/support.ts b/frontend-react/e2e/pages/public/support.ts similarity index 87% rename from frontend-react/e2e/pages/support.ts rename to frontend-react/e2e/pages/public/support.ts index ae5bc223708..119815da6b2 100644 --- a/frontend-react/e2e/pages/support.ts +++ b/frontend-react/e2e/pages/public/support.ts @@ -1,4 +1,4 @@ -import { BasePage, BasePageTestArgs } from "./BasePage"; +import { BasePage, BasePageTestArgs } from "../BasePage"; export class SupportPage extends BasePage { constructor(testArgs: BasePageTestArgs) { diff --git a/frontend-react/e2e/pages/resources.ts b/frontend-react/e2e/pages/resources.ts deleted file mode 100644 index c96baa6d17f..00000000000 --- a/frontend-react/e2e/pages/resources.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Page } from "@playwright/test"; - -export async function goto(page: Page) { - await page.goto("/developer-resources", { - waitUntil: "domcontentloaded", - }); -} diff --git a/frontend-react/e2e/pages/roadmap.ts b/frontend-react/e2e/pages/roadmap.ts deleted file mode 100644 index af49e5aefa5..00000000000 --- a/frontend-react/e2e/pages/roadmap.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Page } from "@playwright/test"; - -export const URL_ROADMAP = "/about/roadmap"; -export async function goto(page: Page) { - await page.goto(URL_ROADMAP, { - waitUntil: "domcontentloaded", - }); -} diff --git a/frontend-react/e2e/spec/all/admin/receiver-status-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts similarity index 95% rename from frontend-react/e2e/spec/all/admin/receiver-status-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts index 33fcf977608..f8f92d2615b 100644 --- a/frontend-react/e2e/spec/all/admin/receiver-status-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/admin/receiver-status-page.spec.ts @@ -1,10 +1,10 @@ import { addDays, endOfDay, startOfDay, subDays } from "date-fns"; -import type { RSOrganizationSettings } from "../../../../src/config/endpoints/settings"; -import { SuccessRate } from "../../../../src/pages/admin/receiver-dashboard/utils"; -import { durationFormatShort } from "../../../../src/utils/DateTimeUtils"; -import { formatDate } from "../../../../src/utils/misc"; -import { AdminReceiverStatusPage } from "../../../pages/admin/receiver-status"; -import { test as baseTest, expect, logins } from "../../../test"; +import type { RSOrganizationSettings } from "../../../../../src/config/endpoints/settings"; +import { SuccessRate } from "../../../../../src/pages/admin/receiver-dashboard/utils"; +import { durationFormatShort } from "../../../../../src/utils/DateTimeUtils"; +import { formatDate } from "../../../../../src/utils/misc"; +import { AdminReceiverStatusPage } from "../../../../pages/authenticated/admin/receiver-status"; +import { test as baseTest, expect, logins } from "../../../../test"; export interface AdminReceiverStatusPageFixtures { adminReceiverStatusPage: AdminReceiverStatusPage; @@ -71,23 +71,20 @@ test.describe("Admin Receiver Status Page", () => { await expect(adminReceiverStatusPage.page.getByText("there was an error")).toBeVisible(); }); - test( - "Has correct title", - { - tag: "@smoke", - }, - async ({ adminReceiverStatusPage }) => { - await expect(adminReceiverStatusPage.page).toHaveURL(adminReceiverStatusPage.url); - await expect(adminReceiverStatusPage.page).toHaveTitle(adminReceiverStatusPage.title); - }, - ); + test.describe("Header", () => { + test( + "has correct title + heading", + { + tag: "@smoke", + }, + async ({ adminReceiverStatusPage }) => { + await adminReceiverStatusPage.testHeader(); + }, + ); + }); test.describe("When there is no error", () => { test.describe("Displays correctly", () => { - test("header", async ({ adminReceiverStatusPage }) => { - await expect(adminReceiverStatusPage.heading).toBeVisible(); - }); - test.describe( "filters", { @@ -153,6 +150,14 @@ test.describe("Admin Receiver Status Page", () => { }); }); + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ + adminReceiverStatusPage, + }) => { + await adminReceiverStatusPage.testFooter(); + }); + }); + test.describe("Functions correctly", () => { test.describe("filters", () => { test.describe( diff --git a/frontend-react/e2e/spec/all/daily-data-details-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/daily-data-details-page.spec.ts similarity index 96% rename from frontend-react/e2e/spec/all/daily-data-details-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/daily-data-details-page.spec.ts index 0d8930c39a7..bf2dc36b129 100644 --- a/frontend-react/e2e/spec/all/daily-data-details-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/daily-data-details-page.spec.ts @@ -1,9 +1,9 @@ import { expect } from "@playwright/test"; -import { tableDataCellValue } from "../../helpers/utils"; -import { detailsTableHeaders } from "../../pages/daily-data"; -import { DailyDataDetailsPage } from "../../pages/daily-data-details"; -import * as reportDetails from "../../pages/report-details"; -import { test as baseTest } from "../../test"; +import { tableDataCellValue } from "../../../helpers/utils"; +import { detailsTableHeaders } from "../../../pages/authenticated/daily-data"; +import { DailyDataDetailsPage } from "../../../pages/authenticated/daily-data-details"; +import * as reportDetails from "../../../pages/authenticated/report-details"; +import { test as baseTest } from "../../../test"; export interface DailyDataDetailsPageFixtures { dailyDataDetailsPage: DailyDataDetailsPage; diff --git a/frontend-react/e2e/spec/all/daily-data-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/daily-data-page.spec.ts similarity index 99% rename from frontend-react/e2e/spec/all/daily-data-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/daily-data-page.spec.ts index 71a59e3f0d0..f6b4e05803d 100644 --- a/frontend-react/e2e/spec/all/daily-data-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/daily-data-page.spec.ts @@ -8,8 +8,8 @@ import { TEST_ORG_AK_RECEIVER, TEST_ORG_IGNORE, TEST_ORG_UP_RECEIVER_UP, -} from "../../helpers/utils"; -import * as dailyData from "../../pages/daily-data"; +} from "../../../helpers/utils"; +import * as dailyData from "../../../pages/authenticated/daily-data.js"; import { applyButton, DailyDataPage, @@ -28,8 +28,8 @@ import { startTime, startTimeClear, tableHeaders, -} from "../../pages/daily-data"; -import { test as baseTest } from "../../test"; +} from "../../../pages/authenticated/daily-data.js"; +import { test as baseTest } from "../../../test"; const defaultStartTime = "9:00am"; const defaultEndTime = "11:00pm"; diff --git a/frontend-react/e2e/spec/all/authenticated/last-mile-failures-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/last-mile-failures-page.spec.ts new file mode 100644 index 00000000000..df8503376bb --- /dev/null +++ b/frontend-react/e2e/spec/all/authenticated/last-mile-failures-page.spec.ts @@ -0,0 +1,115 @@ +import { tableRows } from "../../../helpers/utils"; +import { LastMileFailuresPage } from "../../../pages/authenticated/last-mile-failures"; +import { test as baseTest, expect } from "../../../test"; + +export interface LastMileFailuresPageFixtures { + lastMileFailuresPage: LastMileFailuresPage; +} + +const test = baseTest.extend({ + lastMileFailuresPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }, + use, + ) => { + const page = new LastMileFailuresPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe("Last Mile Failure page", () => { + test.describe("admin user - happy path", () => { + test.use({ storageState: "e2e/.auth/admin.json" }); + + test.describe("Header", () => { + test("has correct title + heading", async ({ lastMileFailuresPage }) => { + await lastMileFailuresPage.testHeader(); + }); + }); + + test("table has correct headers", async ({ lastMileFailuresPage }) => { + await expect(lastMileFailuresPage.page.locator(".column-header-text").nth(0)).toHaveText(/Failed At/); + await expect(lastMileFailuresPage.page.locator(".column-header-text").nth(1)).toHaveText(/ReportId/); + await expect(lastMileFailuresPage.page.locator(".column-header-text").nth(2)).toHaveText(/Receiver/); + }); + + test("table column 'Failed At' has expected data", async ({ lastMileFailuresPage }) => { + await expect(tableRows(lastMileFailuresPage.page).nth(0).locator("td").nth(0)).toHaveText( + "Tue, 2/20/2024, 9:35 PM", + ); + }); + + test("table column 'ReportId' will open a modal with report details", async ({ lastMileFailuresPage }) => { + const reportId = tableRows(lastMileFailuresPage.page).nth(0).locator("td").nth(1); + await expect(reportId).toContainText(/e5ce49c0-b230-4364-8230-964273249fa1/); + await reportId.click(); + + const modal = lastMileFailuresPage.page.getByTestId("modalWindow").nth(0); + await expect(modal).toContainText(/Report ID:e5ce49c0-b230-4364-8230-964273249fa1/); + }); + + test("table column 'Receiver' will open receiver edit page", async ({ lastMileFailuresPage }) => { + const receiver = tableRows(lastMileFailuresPage.page).nth(0).locator("td").nth(2); + await expect(receiver).toContainText(/flexion.etor-service-receiver-results/); + await receiver.click(); + + await expect(lastMileFailuresPage.page).toHaveURL( + "/admin/orgreceiversettings/org/flexion/receiver/etor-service-receiver-results/action/edit", + ); + }); + }); + + test.describe("admin user - server error", () => { + test.use({ storageState: "e2e/.auth/admin.json" }); + + test("has alert", async ({ lastMileFailuresPage }) => { + lastMileFailuresPage.mockError = true; + await lastMileFailuresPage.reload(); + + await expect(lastMileFailuresPage.page.getByTestId("alert")).toBeAttached(); + await expect( + lastMileFailuresPage.page.getByText(/Our apologies, there was an error loading this content./), + ).toBeAttached(); + }); + }); + + test.describe("receiver user", () => { + test.use({ storageState: "e2e/.auth/receiver.json" }); + + test("returns Page Not Found", async ({ lastMileFailuresPage }) => { + await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + }); + }); + + test.describe("sender user", () => { + test.use({ storageState: "e2e/.auth/sender.json" }); + + test("returns Page Not Found", async ({ lastMileFailuresPage }) => { + await expect(lastMileFailuresPage.page).toHaveTitle(/Page Not Found/); + }); + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ lastMileFailuresPage }) => { + await lastMileFailuresPage.testFooter(); + }); + }); +}); diff --git a/frontend-react/e2e/spec/all/authenticated/message-details-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/message-details-page.spec.ts new file mode 100644 index 00000000000..916b14b1096 --- /dev/null +++ b/frontend-react/e2e/spec/all/authenticated/message-details-page.spec.ts @@ -0,0 +1,187 @@ +import fs from "node:fs"; +import { parseFileLocation } from "../../../../src/utils/misc"; +import { tableRows } from "../../../helpers/utils"; +import { MOCK_GET_MESSAGE } from "../../../mocks/messages"; +import { MessageDetailsPage } from "../../../pages/authenticated/message-details"; +import { MessageIDSearchPage } from "../../../pages/authenticated/message-id-search"; +import { mockGetHistoryReportResponse } from "../../../pages/authenticated/report-details"; + +import { test as baseTest, expect } from "../../../test"; + +export interface MessageDetailsPageFixtures { + messageDetailsPage: MessageDetailsPage; +} + +const test = baseTest.extend({ + messageDetailsPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }, + use, + ) => { + const page = new MessageDetailsPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe("Message Details Page", () => { + test.describe("not authenticated", () => { + test("redirects to login", async ({ messageDetailsPage }) => { + await expect(messageDetailsPage.page).toHaveURL("/login"); + }); + }); + + test.describe("authenticated admin", () => { + test.use({ storageState: "e2e/.auth/admin.json" }); + + test.describe("Header", () => { + test("has correct title + heading", async ({ messageDetailsPage }) => { + await messageDetailsPage.testHeader(); + }); + }); + + test("has message id section", async ({ messageDetailsPage }) => { + await expect(messageDetailsPage.page.getByText("Message ID", { exact: true })).toBeVisible(); + await expect(messageDetailsPage.page.getByText(MessageIDSearchPage.MESSAGE_ID)).toBeVisible(); + }); + + test("has sender section", async ({ messageDetailsPage }) => { + const { sender, reportId, submittedDate } = MOCK_GET_MESSAGE; + + await expect(messageDetailsPage.page.getByText("Sender:")).toBeVisible(); + await expect(messageDetailsPage.page.getByText(sender)).toBeVisible(); + await expect(messageDetailsPage.page.getByText("Incoming Report ID")).toBeVisible(); + await expect(messageDetailsPage.page.getByText(reportId, { exact: true })).toBeVisible(); + await expect(messageDetailsPage.page.getByText("Date/Time Submitted")).toBeVisible(); + await expect(messageDetailsPage.page.getByText(new Date(submittedDate).toLocaleString())).toBeVisible(); + await expect(messageDetailsPage.page.getByText("File Location")).toBeVisible(); + await expect(messageDetailsPage.page.getByText("RECEIVE", { exact: true })).toBeVisible(); + await expect(messageDetailsPage.page.getByText("ignore.ignore-simple-report")).toBeVisible(); + await expect(messageDetailsPage.page.getByText("Incoming File Name")).toBeVisible(); + await expect( + messageDetailsPage.page.getByText( + "pdi-covid-19-d9a57df0-2702-4e28-9d80-ff8c9ec51816-20240514142655.csv", + ), + ).toBeVisible(); + }); + + test.describe("authenticated admin", () => { + test("displays expected table headers and data", async ({ messageDetailsPage }) => { + // include header row + const rowCount = MOCK_GET_MESSAGE.receiverData.length + 1; + const table = messageDetailsPage.page.getByRole("table"); + await expect(table).toBeVisible(); + const rows = await table.getByRole("row").all(); + expect(rows).toHaveLength(rowCount); + + const colHeaders = [ + "Name", + "Service", + "Date", + "Report Id", + "Main", + "Sub", + "File Name", + "Transport Results", + ]; + for (const [i, row] of rows.entries()) { + const cols = await row.getByRole("cell").allTextContents(); + expect(cols).toHaveLength(colHeaders.length); + + const { receivingOrg, receivingOrgSvc, createdAt, reportId, fileUrl, transportResult } = + i === 0 + ? MOCK_GET_MESSAGE.receiverData[0] + : (MOCK_GET_MESSAGE.receiverData.find((i) => i.reportId === cols[3]) ?? { + reportId: "INVALID", + }); + + // if first row, we expect column headers. else, the data row matching the report id + const expectedColContents = + i === 0 + ? colHeaders + : [ + receivingOrg ?? "", + receivingOrgSvc ?? "", + createdAt ? new Date(createdAt).toLocaleString() : "", + reportId, + parseFileLocation(fileUrl ?? "N/A").folderLocation.toLocaleUpperCase(), + parseFileLocation(fileUrl ?? "N/A").sendingOrg, + parseFileLocation(fileUrl ?? "N/A").fileName, + transportResult ?? "", + ]; + + for (const [i, col] of cols.entries()) { + expect(col).toBe(expectedColContents[i]); + } + } + }); + + test("table column 'FileName' will download file", async ({ messageDetailsPage }) => { + const downloadProm = messageDetailsPage.page.waitForEvent("download"); + await mockGetHistoryReportResponse(messageDetailsPage.page, "*"); + + await tableRows(messageDetailsPage.page).nth(0).locator("td").nth(6).getByRole("button").click(); + + const download = await downloadProm; + + // assert filename + expect(download.suggestedFilename()).toBe( + "hhsprotect-covid-19-73e3cbc8-9920-4ab7-871f-843a1db4c074.csv", + ); + // get and assert stats + expect((await fs.promises.stat(await download.path())).size).toBeGreaterThan(200); + }); + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ messageDetailsPage }) => { + await messageDetailsPage.testFooter(); + }); + }); + }); + + test.describe("receiver user", () => { + test.use({ storageState: "e2e/.auth/receiver.json" }); + + test("has alert", async ({ messageDetailsPage }) => { + messageDetailsPage.mockError = true; + await messageDetailsPage.reload(); + + await expect(messageDetailsPage.page.getByTestId("alert")).toBeAttached(); + await expect( + messageDetailsPage.page.getByText(/Our apologies, there was an error loading this content./), + ).toBeAttached(); + }); + }); + + test.describe("sender user", () => { + test.use({ storageState: "e2e/.auth/sender.json" }); + + test("has alert", async ({ messageDetailsPage }) => { + messageDetailsPage.mockError = true; + await messageDetailsPage.reload(); + + await expect(messageDetailsPage.page.getByTestId("alert")).toBeAttached(); + await expect( + messageDetailsPage.page.getByText(/Our apologies, there was an error loading this content./), + ).toBeAttached(); + }); + }); +}); diff --git a/frontend-react/e2e/spec/all/authenticated/message-id-search-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/message-id-search-page.spec.ts new file mode 100644 index 00000000000..9de8e807739 --- /dev/null +++ b/frontend-react/e2e/spec/all/authenticated/message-id-search-page.spec.ts @@ -0,0 +1,177 @@ +import { noData, tableRows } from "../../../helpers/utils"; +import { MOCK_GET_MESSAGES } from "../../../mocks/messages"; +import { MessageIDSearchPage } from "../../../pages/authenticated/message-id-search"; +import { openReportIdDetailPage } from "../../../pages/authenticated/submission-history"; + +import { test as baseTest, expect } from "../../../test"; + +export interface MessageIDSearchPageFixtures { + messageIDSearchPage: MessageIDSearchPage; +} + +const test = baseTest.extend({ + messageIDSearchPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }, + use, + ) => { + const page = new MessageIDSearchPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + frontendWarningsLogPath, + isFrontendWarningsLog, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe("Message ID Search Page", () => { + test.describe("not authenticated", () => { + test("redirects to login", async ({ messageIDSearchPage }) => { + await expect(messageIDSearchPage.page).toHaveURL("/login"); + }); + }); + + test.describe("authenticated admin", () => { + test.use({ storageState: "e2e/.auth/admin.json" }); + test.beforeEach(async ({ messageIDSearchPage }) => { + await messageIDSearchPage.page.locator("#search-field").fill(MessageIDSearchPage.MESSAGE_ID); + await messageIDSearchPage.page + .getByRole("button", { + name: "Search", + }) + .click(); + }); + + test.describe("on search with results", () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ messageIDSearchPage }) => { + await messageIDSearchPage.testHeader(); + }); + }); + + test("displays expected table headers and data", async ({ messageIDSearchPage }) => { + // include header row + const rowCount = MOCK_GET_MESSAGES.length + 1; + const table = messageIDSearchPage.page.getByRole("table"); + await expect(table).toBeVisible(); + const rows = await table.getByRole("row").all(); + expect(rows).toHaveLength(rowCount); + + const colHeaders = ["Message ID", "Sender", "Date/time submitted", "Incoming Report Id"]; + for (const [i, row] of rows.entries()) { + const cols = await row.getByRole("cell").allTextContents(); + expect(cols).toHaveLength(colHeaders.length); + + const { messageId, sender, submittedDate, reportId } = + i === 0 + ? MOCK_GET_MESSAGES[0] + : (MOCK_GET_MESSAGES.find((i) => i.reportId === cols[3]) ?? { reportId: "INVALID" }); + // if first row, we expect column headers. else, the data row matching the report id + const expectedColContents = + i === 0 + ? colHeaders + : [ + messageId, + sender ?? "", + submittedDate ? new Date(submittedDate).toLocaleString() : "", + reportId ?? "", + ]; + + for (const [i, col] of cols.entries()) { + expect(col).toBe(expectedColContents[i]); + } + } + }); + + test("table column 'Message ID' will open message id details", async ({ messageIDSearchPage }) => { + const messageIdCell = tableRows(messageIDSearchPage.page) + .nth(0) + .locator("td") + .nth(0) + .getByRole("link", { name: MessageIDSearchPage.MESSAGE_ID }); + await messageIdCell.click(); + await expect(messageIDSearchPage.page).toHaveURL("/message-details/0"); + expect(messageIDSearchPage.page.locator("h1").getByText(MessageIDSearchPage.MESSAGE_ID)).toBeTruthy(); + }); + + test("table column 'Incoming Report Id' will open report id details", async ({ messageIDSearchPage }) => { + const reportId = "73e3cbc8-9920-4ab7-871f-843a1db4c074"; + const reportIdCell = tableRows(messageIDSearchPage.page).nth(0).locator("td").nth(3).getByRole("link", { + name: reportId, + }); + await reportIdCell.click(); + await openReportIdDetailPage(messageIDSearchPage.page, reportId); + }); + }); + + test.describe("on search without results", () => { + test.beforeEach(async ({ messageIDSearchPage }) => { + await messageIDSearchPage.page.route(MessageIDSearchPage.API_MESSAGES, (route) => + route.fulfill({ + status: 200, + json: [], + }), + ); + await messageIDSearchPage.page.goto(MessageIDSearchPage.URL_MESSAGE_ID_SEARCH); + + await messageIDSearchPage.page.locator("#search-field").fill(MessageIDSearchPage.MESSAGE_ID); + await messageIDSearchPage.page + .getByRole("button", { + name: "Search", + }) + .click(); + }); + + test("has correct title", async ({ page }) => { + await expect(page).toHaveURL(MessageIDSearchPage.URL_MESSAGE_ID_SEARCH); + await expect(page).toHaveTitle(/Message ID search - Admin/); + }); + + test("shows no data", async ({ page }) => { + await expect(noData(page)).toBeAttached(); + }); + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ messageIDSearchPage }) => { + await messageIDSearchPage.testFooter(); + }); + }); + }); + + test.describe("receiver user", () => { + test.use({ storageState: "e2e/.auth/receiver.json" }); + + test("has alert", async ({ messageIDSearchPage }) => { + messageIDSearchPage.mockError = true; + await messageIDSearchPage.reload(); + + await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + }); + }); + + test.describe("sender user", () => { + test.use({ storageState: "e2e/.auth/sender.json" }); + + test("has alert", async ({ messageIDSearchPage }) => { + messageIDSearchPage.mockError = true; + await messageIDSearchPage.reload(); + + await expect(messageIDSearchPage.page).toHaveTitle(/Page Not Found/); + }); + }); +}); diff --git a/frontend-react/e2e/spec/all/organization-settings-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/organization-settings-page.spec.ts similarity index 93% rename from frontend-react/e2e/spec/all/organization-settings-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/organization-settings-page.spec.ts index e8906c66fe2..83ce97b9e2a 100644 --- a/frontend-react/e2e/spec/all/organization-settings-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/organization-settings-page.spec.ts @@ -2,9 +2,9 @@ import { expect } from "@playwright/test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../mocks/organizations"; -import { OrganizationPage } from "../../pages/organization"; -import { test as baseTest } from "../../test"; +import { MOCK_GET_ORGANIZATION_SETTINGS_LIST } from "../../../mocks/organizations"; +import { OrganizationPage } from "../../../pages/authenticated/organization"; +import { test as baseTest } from "../../../test"; const __dirname = fileURLToPath(import.meta.url); @@ -65,6 +65,12 @@ test.describe("Admin Organization Settings Page", () => { test.describe("authenticated admin", () => { test.use({ storageState: "e2e/.auth/admin.json" }); + test.describe("Header", () => { + test("has correct title + heading", async ({ organizationPage }) => { + await organizationPage.testHeader(); + }); + }); + test("If there is an error, the error is shown on the page", async ({ organizationPage }) => { organizationPage.mockError = true; await organizationPage.reload(); @@ -159,7 +165,7 @@ test.describe("Admin Organization Settings Page", () => { await saveButton.click(); const download = await downloadProm; - const expectedFile = readFileSync(join(__dirname, "../../../mocks/prime-orgs.csv"), { + const expectedFile = readFileSync(join(__dirname, "../../../../mocks/prime-orgs.csv"), { encoding: "utf-8", }); const stream = await download.createReadStream(); @@ -229,4 +235,10 @@ test.describe("Admin Organization Settings Page", () => { }); }); }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ organizationPage }) => { + await organizationPage.testFooter(); + }); + }); }); diff --git a/frontend-react/e2e/spec/all/submission-history-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/submission-history-page.spec.ts similarity index 74% rename from frontend-react/e2e/spec/all/submission-history-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/submission-history-page.spec.ts index 25b3043a6d1..98f6a4f684c 100644 --- a/frontend-react/e2e/spec/all/submission-history-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/submission-history-page.spec.ts @@ -1,14 +1,8 @@ import { expect, test } from "@playwright/test"; -import { - noData, - selectTestOrg, - tableDataCellValue, - tableRows, - TEST_ORG_IGNORE, -} from "../../helpers/utils"; -import * as submissionHistory from "../../pages/submission-history"; -import { openReportIdDetailPage } from "../../pages/submission-history"; +import { noData, selectTestOrg, tableDataCellValue, tableRows, TEST_ORG_IGNORE } from "../../../helpers/utils"; +import * as submissionHistory from "../../../pages/authenticated/submission-history"; +import { openReportIdDetailPage } from "../../../pages/authenticated/submission-history"; const id = "73e3cbc8-9920-4ab7-871f-843a1db4c074"; test.describe("Submission history page", () => { @@ -28,9 +22,7 @@ test.describe("Submission history page", () => { }); test("will not load page", async ({ page }) => { - await expect( - page.getByText("Cannot fetch Organization data as admin"), - ).toBeVisible(); + await expect(page.getByText("Cannot fetch Organization data as admin")).toBeVisible(); }); test("has footer", async ({ page }) => { @@ -41,19 +33,14 @@ test.describe("Submission history page", () => { test.describe("with org selected", () => { test.beforeEach(async ({ page }) => { await selectTestOrg(page); - await submissionHistory.mockGetSubmissionsResponse( - page, - TEST_ORG_IGNORE, - ); + await submissionHistory.mockGetSubmissionsResponse(page, TEST_ORG_IGNORE); await submissionHistory.mockGetReportHistoryResponse(page); // abort all app insight calls await page.route("**/v2/track", (route) => route.abort()); await submissionHistory.goto(page); }); - test("nav contains the 'Submission History' option", async ({ - page, - }) => { + test("nav contains the 'Submission History' option", async ({ page }) => { const navItems = page.locator(".usa-nav li"); await expect(navItems).toContainText(["Submission History"]); }); @@ -63,9 +50,7 @@ test.describe("Submission history page", () => { }); test("has filter", async ({ page }) => { - await expect( - page.getByTestId("filter-container"), - ).toBeAttached(); + await expect(page.getByTestId("filter-container")).toBeAttached(); }); test.describe("table", () => { @@ -73,56 +58,35 @@ test.describe("Submission history page", () => { await submissionHistory.tableHeaders(page); }); - test("table column 'ReportId' will open the report details", async ({ - page, - }) => { - const reportId = tableRows(page) - .nth(0) - .locator("td") - .nth(0); + test("table column 'ReportId' will open the report details", async ({ page }) => { + const reportId = tableRows(page).nth(0).locator("td").nth(0); await expect(reportId).toContainText(id); await reportId.getByRole("link", { name: id }).click(); await openReportIdDetailPage(page, id); }); - test("table column 'Date/time submitted' has expected data", async ({ - page, - }) => { - expect(await tableDataCellValue(page, 0, 1)).toEqual( - "3/7/2024, 6:00:22 PM", - ); + test("table column 'Date/time submitted' has expected data", async ({ page }) => { + expect(await tableDataCellValue(page, 0, 1)).toEqual("3/7/2024, 6:00:22 PM"); }); - test("table column 'File' has expected data", async ({ - page, - }) => { - expect(await tableDataCellValue(page, 0, 2)).toEqual( - "myfile.hl7", - ); + test("table column 'File' has expected data", async ({ page }) => { + expect(await tableDataCellValue(page, 0, 2)).toEqual("myfile.hl7"); expect(await tableDataCellValue(page, 1, 2)).toEqual( "None-03c3b7ab-7c65-4174-bea7-9195cbb7ed01-20240314174050.hl7", ); }); - test("table column 'Records' has expected data", async ({ - page, - }) => { + test("table column 'Records' has expected data", async ({ page }) => { expect(await tableDataCellValue(page, 0, 3)).toEqual("1"); }); - test("table column 'Status' has expected data", async ({ - page, - }) => { - expect(await tableDataCellValue(page, 0, 4)).toEqual( - "Success", - ); + test("table column 'Status' has expected data", async ({ page }) => { + expect(await tableDataCellValue(page, 0, 4)).toEqual("Success"); }); test("table has pagination", async ({ page }) => { - await expect( - page.getByTestId("Submissions pagination"), - ).toBeAttached(); + await expect(page.getByTestId("Submissions pagination")).toBeAttached(); }); }); @@ -139,9 +103,7 @@ test.describe("Submission history page", () => { await submissionHistory.goto(page); }); - test("nav does not contain the Submissions option", async ({ - page, - }) => { + test("nav does not contain the Submissions option", async ({ page }) => { const navItems = page.locator(".usa-nav li"); await expect(navItems).not.toContainText(["Submissions"]); }); @@ -163,10 +125,7 @@ test.describe("Submission history page", () => { test.use({ storageState: "e2e/.auth/sender.json" }); test.beforeEach(async ({ page }) => { - await submissionHistory.mockGetSubmissionsResponse( - page, - TEST_ORG_IGNORE, - ); + await submissionHistory.mockGetSubmissionsResponse(page, TEST_ORG_IGNORE); await submissionHistory.mockGetReportHistoryResponse(page); await submissionHistory.goto(page); }); @@ -189,9 +148,7 @@ test.describe("Submission history page", () => { await submissionHistory.tableHeaders(page); }); - test("table column 'ReportId' will open the report details", async ({ - page, - }) => { + test("table column 'ReportId' will open the report details", async ({ page }) => { const reportId = tableRows(page).nth(0).locator("td").nth(0); await expect(reportId).toContainText(id); await reportId.getByRole("link", { name: id }).click(); @@ -199,30 +156,20 @@ test.describe("Submission history page", () => { await openReportIdDetailPage(page, id); }); - test("table column 'Date/time submitted' has expected data", async ({ - page, - }) => { - expect(await tableDataCellValue(page, 0, 1)).toEqual( - "3/7/2024, 6:00:22 PM", - ); + test("table column 'Date/time submitted' has expected data", async ({ page }) => { + expect(await tableDataCellValue(page, 0, 1)).toEqual("3/7/2024, 6:00:22 PM"); }); - test("table column 'Records' has expected data", async ({ - page, - }) => { + test("table column 'Records' has expected data", async ({ page }) => { expect(await tableDataCellValue(page, 0, 3)).toEqual("1"); }); - test("table column 'Status' has expected data", async ({ - page, - }) => { + test("table column 'Status' has expected data", async ({ page }) => { expect(await tableDataCellValue(page, 0, 4)).toEqual("Success"); }); test("table has pagination", async ({ page }) => { - await expect( - page.getByTestId("Submissions pagination"), - ).toBeAttached(); + await expect(page.getByTestId("Submissions pagination")).toBeAttached(); }); }); diff --git a/frontend-react/e2e/spec/all/submissions-details-page.spec.ts b/frontend-react/e2e/spec/all/authenticated/submissions-details-page.spec.ts similarity index 74% rename from frontend-react/e2e/spec/all/submissions-details-page.spec.ts rename to frontend-react/e2e/spec/all/authenticated/submissions-details-page.spec.ts index 7f4d76b3464..a25780ae0b8 100644 --- a/frontend-react/e2e/spec/all/submissions-details-page.spec.ts +++ b/frontend-react/e2e/spec/all/authenticated/submissions-details-page.spec.ts @@ -1,8 +1,8 @@ import { expect, test } from "@playwright/test"; -import { selectTestOrg } from "../../helpers/utils"; -import * as reportDetails from "../../pages/report-details"; -import * as submissionDetails from "../../pages/submission-history"; -import { URL_SUBMISSION_HISTORY } from "../../pages/submission-history"; +import { selectTestOrg } from "../../../helpers/utils"; +import * as reportDetails from "../../../pages/authenticated/report-details"; +import * as submissionDetails from "../../../pages/authenticated/submission-history"; +import { URL_SUBMISSION_HISTORY } from "../../../pages/authenticated/submission-history"; const id = "73e3cbc8-9920-4ab7-871f-843a1db4c074"; test.describe("Submissions Details page", () => { @@ -27,9 +27,7 @@ test.describe("Submissions Details page", () => { }); test("has reportId in breadcrumb", async ({ page }) => { - await expect( - page.locator(".usa-breadcrumb ol li").nth(1), - ).toHaveText(`Details: ${id}`); + await expect(page.locator(".usa-breadcrumb ol li").nth(1)).toHaveText(`Details: ${id}`); }); test("has footer", async ({ page }) => { @@ -48,15 +46,8 @@ test.describe("Submissions Details page", () => { await submissionDetails.title(page); }); - test("breadcrumb navigates to Submission History page", async ({ - page, - }) => { - await submissionDetails.breadcrumbLink( - page, - 0, - "Submissions", - URL_SUBMISSION_HISTORY, - ); + test("breadcrumb navigates to Submission History page", async ({ page }) => { + await submissionDetails.breadcrumbLink(page, 0, "Submissions", URL_SUBMISSION_HISTORY); }); test("has footer", async ({ page }) => { @@ -74,9 +65,7 @@ test.describe("Submissions Details page", () => { }); test("has error message", async ({ page }) => { - await expect( - page.getByText(/An error has occurred./), - ).toBeAttached(); + await expect(page.getByText(/An error has occurred./)).toBeAttached(); }); test("has footer", async ({ page }) => { @@ -97,20 +86,11 @@ test.describe("Submissions Details page", () => { }); test("has reportId in breadcrumb", async ({ page }) => { - await expect( - page.locator(".usa-breadcrumb ol li").nth(1), - ).toHaveText(`Details: ${id}`); + await expect(page.locator(".usa-breadcrumb ol li").nth(1)).toHaveText(`Details: ${id}`); }); - test("breadcrumb navigates to Submission History page", async ({ - page, - }) => { - await submissionDetails.breadcrumbLink( - page, - 0, - "Submissions", - URL_SUBMISSION_HISTORY, - ); + test("breadcrumb navigates to Submission History page", async ({ page }) => { + await submissionDetails.breadcrumbLink(page, 0, "Submissions", URL_SUBMISSION_HISTORY); }); test("has footer", async ({ page }) => { @@ -127,9 +107,7 @@ test.describe("Submissions Details page", () => { }); test("has error message", async ({ page }) => { - await expect( - page.getByText(/An error has occurred./), - ).toBeAttached(); + await expect(page.getByText(/An error has occurred./)).toBeAttached(); }); test("has footer", async ({ page }) => { @@ -145,9 +123,7 @@ test.describe("Submissions Details page", () => { }); test("has error message", async ({ page }) => { - await expect( - page.getByText(/An error has occurred./), - ).toBeAttached(); + await expect(page.getByText(/An error has occurred./)).toBeAttached(); }); test("has footer", async ({ page }) => { diff --git a/frontend-react/e2e/spec/all/daily-data-page-user-flow.spec.ts b/frontend-react/e2e/spec/all/daily-data-page-user-flow.spec.ts index 03156ebc07f..71aaa857b75 100644 --- a/frontend-react/e2e/spec/all/daily-data-page-user-flow.spec.ts +++ b/frontend-react/e2e/spec/all/daily-data-page-user-flow.spec.ts @@ -28,8 +28,8 @@ import { setTime, startDate, startTime, -} from "../../pages/daily-data"; -import { URL_REPORT_DETAILS } from "../../pages/report-details"; +} from "../../pages/authenticated/daily-data.js"; +import { URL_REPORT_DETAILS } from "../../pages/authenticated/report-details.js"; import { test as baseTest } from "../../test"; const defaultStartTime = "9:00am"; diff --git a/frontend-react/e2e/spec/all/homepage.spec.ts b/frontend-react/e2e/spec/all/homepage.spec.ts deleted file mode 100644 index 6d1b4bb9d08..00000000000 --- a/frontend-react/e2e/spec/all/homepage.spec.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { expect, test } from "@playwright/test"; - -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import * as header from "../../pages/header"; -import * as homepage from "../../pages/homepage"; -import * as managingYourConnection from "../../pages/managing-your-connection"; -import * as ourNetwork from "../../pages/our-network"; -import * as security from "../../pages/security"; - -test.describe( - "Homepage", - { - tag: "@smoke", - }, - () => { - test.beforeEach(async ({ page }) => { - await homepage.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveTitle( - /ReportStream - CDC's free, interoperable data transfer platform/, - ); - }); - - test("opens the Security page on 'security of your data' click", async ({ - page, - }) => { - await page - .getByRole("link", { name: "security of your data" }) - .click(); - await security.onLoad(page); - // Go back to the homepage - await header.clickOnHome(page); - - expect(true).toBe(true); - }); - - test("opens the managing-your-connection page on 'our tools' click", async ({ - page, - }) => { - await page.getByRole("link", { name: "our tools" }).click(); - await managingYourConnection.onLoad(page); - // Go back to the homepage - await header.clickOnHome(page); - - expect(true).toBe(true); - }); - - test("opens Our Network page on 'See our full network' click", async ({ - page, - }) => { - await page - .getByRole("link", { name: "See our full network" }) - .click(); - await ourNetwork.onLoad(page); - // Go back to the homepage - await header.clickOnHome(page); - - expect(true).toBe(true); - }); - - test("is clickable Where were live map", async ({ page }) => { - // Trigger map click and go to our network page - await ourNetwork.clickOnLiveMap(page); - // Go back to the homepage - await header.clickOnHome(page); - - expect(true).toBe(true); - }); - - test("explicit scroll to footer and then scroll to top", async ({ - page, - }) => { - await expect(page.locator("footer")).not.toBeInViewport(); - await scrollToFooter(page); - await expect(page.locator("footer")).toBeInViewport(); - await expect(page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(page); - await expect(page.getByTestId("govBanner")).toBeInViewport(); - }); - }, -); diff --git a/frontend-react/e2e/spec/all/idletimeout.spec.ts b/frontend-react/e2e/spec/all/idletimeout.spec.ts index 3d2ae35eb96..0b35f0fd34d 100644 --- a/frontend-react/e2e/spec/all/idletimeout.spec.ts +++ b/frontend-react/e2e/spec/all/idletimeout.spec.ts @@ -1,7 +1,7 @@ import { expect } from "@playwright/test"; import process from "node:process"; -import { OrganizationPage } from "../../pages/organization"; +import { OrganizationPage } from "../../pages/authenticated/organization"; import { test as baseTest } from "../../test"; const timeout = parseInt(process.env.VITE_IDLE_TIMEOUT ?? "20000"); @@ -35,7 +35,7 @@ const test = baseTest.extend({ receiverLogin, storageState, frontendWarningsLogPath, - isFrontendWarningsLog + isFrontendWarningsLog, }); await page.goto(); await use(page); @@ -45,19 +45,14 @@ const test = baseTest.extend({ test.use({ storageState: "e2e/.auth/admin.json" }); test.skip("Does not trigger early", async ({ organizationPage }) => { - await expect( - organizationPage.page.getByRole("banner").first(), - ).toBeVisible(); + await expect(organizationPage.page.getByRole("banner").first()).toBeVisible(); await organizationPage.page.keyboard.down("Tab"); const start = new Date(); - await organizationPage.page.waitForRequest( - /\/oauth2\/default\/v1\/revoke/, - { - timeout: timeoutHigh, - }, - ); + await organizationPage.page.waitForRequest(/\/oauth2\/default\/v1\/revoke/, { + timeout: timeoutHigh, + }); const end = new Date(); diff --git a/frontend-react/e2e/spec/all/last-mile-failures-page.spec.ts b/frontend-react/e2e/spec/all/last-mile-failures-page.spec.ts deleted file mode 100644 index 8af5e5b64af..00000000000 --- a/frontend-react/e2e/spec/all/last-mile-failures-page.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { expect, test } from "@playwright/test"; - -import { tableRows } from "../../helpers/utils"; -import * as lastMileFailures from "../../pages/last-mile-failures"; - -test.describe("Last Mile Failure page", () => { - test.describe("not authenticated", () => { - test("redirects to login", async ({ page }) => { - await lastMileFailures.goto(page); - await expect(page).toHaveURL("/login"); - }); - }); - - test.describe("admin user - happy path", () => { - test.use({ storageState: "e2e/.auth/admin.json" }); - - test.beforeEach(async ({ page }) => { - // Mock the api call before navigating - await lastMileFailures.mockGetSendFailuresResponse(page); - await lastMileFailures.mockGetResendResponse(page); - await lastMileFailures.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveTitle(/Last Mile Failures/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - - test("table has correct headers", async ({ page }) => { - await expect(page.locator(".column-header-text").nth(0)).toHaveText(/Failed At/); - await expect(page.locator(".column-header-text").nth(1)).toHaveText(/ReportId/); - await expect(page.locator(".column-header-text").nth(2)).toHaveText(/Receiver/); - }); - - test("table column 'Failed At' has expected data", async ({ page }) => { - await expect(tableRows(page).nth(0).locator("td").nth(0)).toHaveText("Tue, 2/20/2024, 9:35 PM"); - }); - - test("table column 'ReportId' will open a modal with report details", async ({ page }) => { - const reportId = tableRows(page).nth(0).locator("td").nth(1); - await expect(reportId).toContainText(/e5ce49c0-b230-4364-8230-964273249fa1/); - await reportId.click(); - - const modal = page.getByTestId("modalWindow").nth(0); - await expect(modal).toContainText(/Report ID:e5ce49c0-b230-4364-8230-964273249fa1/); - }); - - test("table column 'Receiver' will open receiver edit page", async ({ page }) => { - const receiver = tableRows(page).nth(0).locator("td").nth(2); - await expect(receiver).toContainText(/flexion.etor-service-receiver-results/); - await receiver.click(); - - await expect(page).toHaveURL( - "/admin/orgreceiversettings/org/flexion/receiver/etor-service-receiver-results/action/edit", - ); - }); - }); - - test.describe("admin user - server error", () => { - test.use({ storageState: "e2e/.auth/admin.json" }); - - test.beforeEach(async ({ page }) => { - await lastMileFailures.mockGetSendFailuresResponse(page, 500); - await lastMileFailures.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveTitle(/Last Mile Failures/); - }); - - test("has alert", async ({ page }) => { - await expect(page.getByTestId("alert")).toBeAttached(); - await expect(page.getByText(/Our apologies, there was an error loading this content./)).toBeAttached(); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); - - test.describe("receiver user", () => { - test.use({ storageState: "e2e/.auth/receiver.json" }); - - test.beforeEach(async ({ page }) => { - await lastMileFailures.goto(page); - }); - - test("returns Page Not Found", async ({ page }) => { - await expect(page).toHaveTitle(/Page Not Found/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); - - test.describe("sender user", () => { - test.use({ storageState: "e2e/.auth/sender.json" }); - - test.beforeEach(async ({ page }) => { - await lastMileFailures.goto(page); - }); - - test("returns Page Not Found", async ({ page }) => { - await expect(page).toHaveTitle(/Page Not Found/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); -}); diff --git a/frontend-react/e2e/spec/all/managing-your-connection-page.spec.ts b/frontend-react/e2e/spec/all/managing-your-connection-page.spec.ts deleted file mode 100644 index 9e3d7ceabc0..00000000000 --- a/frontend-react/e2e/spec/all/managing-your-connection-page.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import { ManagingYourConnectionPage } from "../../pages/managing-your-connection"; -import { test as baseTest, expect } from "../../test"; - -const cards = [ - { - name: "For healthcare organizations", - links: [ - "Manage your public key", - "View your submission history", - "Login", - "contact us", - ], - }, - { - name: "For public health agencies", - links: [ - "Refer healthcare organizations", - "View your dashboard", - "Login", - "contact us", - ], - }, -]; - -export interface ManagingYourConnectionPageFixtures { - managingYourConnectionPage: ManagingYourConnectionPage; -} - -const test = baseTest.extend({ - managingYourConnectionPage: async ( - { - page: _page, - isMockDisabled, - adminLogin, - senderLogin, - receiverLogin, - storageState, - isFrontendWarningsLog, - frontendWarningsLogPath, - }, - use, - ) => { - const page = new ManagingYourConnectionPage({ - page: _page, - isMockDisabled, - adminLogin, - senderLogin, - receiverLogin, - storageState, - isFrontendWarningsLog, - frontendWarningsLogPath, - }); - await page.goto(); - await use(page); - }, -}); - -test.describe( - "Managing Your Connection page", - { - tag: "@smoke", - }, - () => { - test("has correct title", async ({ managingYourConnectionPage }) => { - await expect(managingYourConnectionPage.page).toHaveTitle( - managingYourConnectionPage.title, - ); - await expect(managingYourConnectionPage.heading).toBeVisible(); - }); - - test.describe("Quick links", () => { - for (const card of cards) { - test(`should have ${card.name} links`, async ({ - managingYourConnectionPage, - }) => { - const cardHeader = managingYourConnectionPage.page.locator( - ".usa-card__header", - { - hasText: card.name, - }, - ); - - await expect(cardHeader).toBeVisible(); - - const cardContainer = cardHeader.locator(".."); - - for (const link of card.links) { - await expect( - cardContainer.getByRole("link", { - name: `${link}`, - }), - ).toBeVisible(); - } - }); - } - }); - - test.describe("Footer", () => { - test("has footer", async ({ managingYourConnectionPage }) => { - await expect(managingYourConnectionPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ - managingYourConnectionPage, - }) => { - await expect( - managingYourConnectionPage.footer, - ).not.toBeInViewport(); - await scrollToFooter(managingYourConnectionPage.page); - await expect( - managingYourConnectionPage.footer, - ).toBeInViewport(); - await expect( - managingYourConnectionPage.page.getByTestId("govBanner"), - ).not.toBeInViewport(); - await scrollToTop(managingYourConnectionPage.page); - await expect( - managingYourConnectionPage.page.getByTestId("govBanner"), - ).toBeInViewport(); - }); - }); - }, -); diff --git a/frontend-react/e2e/spec/all/message-details-page.spec.ts b/frontend-react/e2e/spec/all/message-details-page.spec.ts deleted file mode 100644 index 77291601b05..00000000000 --- a/frontend-react/e2e/spec/all/message-details-page.spec.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { expect, test } from "@playwright/test"; -import fs from "node:fs"; -import { parseFileLocation } from "../../../src/utils/misc"; -import { tableRows } from "../../helpers/utils"; -import { MOCK_GET_MESSAGE } from "../../mocks/messages"; -import * as messageDetails from "../../pages/message-details"; -import { URL_MESSAGE_DETAILS } from "../../pages/message-details"; -import * as messageIdSearch from "../../pages/message-id-search"; -import { MESSAGE_ID } from "../../pages/message-id-search"; -import { mockGetHistoryReportResponse } from "../../pages/report-details"; -test.describe("Message Details Page", () => { - test.describe("not authenticated", () => { - test("redirects to login", async ({ page }) => { - await messageDetails.goto(page); - await expect(page).toHaveURL("/login"); - }); - }); - - test.describe("authenticated admin", () => { - test.use({ storageState: "e2e/.auth/admin.json" }); - - test.beforeEach(async ({ page }) => { - await page.route(messageIdSearch.API_MESSAGE, (route) => - route.fulfill({ - status: 200, - json: MOCK_GET_MESSAGE, - }), - ); - await messageDetails.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveURL(URL_MESSAGE_DETAILS); - await expect(page).toHaveTitle( - /ReportStream - CDC's free, interoperable data transfer platform/, - ); - }); - - test("has message id section", async ({ page }) => { - await expect( - page.getByText("Message ID", { exact: true }), - ).toBeVisible(); - await expect(page.getByText(MESSAGE_ID)).toBeVisible(); - }); - - test("has sender section", async ({ page }) => { - const { sender, reportId, submittedDate } = MOCK_GET_MESSAGE; - - await expect(page.getByText("Sender:")).toBeVisible(); - await expect(page.getByText(sender)).toBeVisible(); - await expect(page.getByText("Incoming Report ID")).toBeVisible(); - await expect( - page.getByText(reportId, { exact: true }), - ).toBeVisible(); - await expect(page.getByText("Date/Time Submitted")).toBeVisible(); - await expect( - page.getByText(new Date(submittedDate).toLocaleString()), - ).toBeVisible(); - await expect(page.getByText("File Location")).toBeVisible(); - await expect( - page.getByText("RECEIVE", { exact: true }), - ).toBeVisible(); - await expect( - page.getByText("ignore.ignore-simple-report"), - ).toBeVisible(); - await expect(page.getByText("Incoming File Name")).toBeVisible(); - await expect( - page.getByText( - "pdi-covid-19-d9a57df0-2702-4e28-9d80-ff8c9ec51816-20240514142655.csv", - ), - ).toBeVisible(); - }); - - test.describe("authenticated admin", () => { - test("has receiver title", async ({ page }) => { - await expect(page.getByText("Receivers:")).toBeVisible(); - }); - - test("displays expected table headers and data", async ({ - page, - }) => { - // include header row - const rowCount = MOCK_GET_MESSAGE.receiverData.length + 1; - const table = page.getByRole("table"); - await expect(table).toBeVisible(); - const rows = await table.getByRole("row").all(); - expect(rows).toHaveLength(rowCount); - - const colHeaders = [ - "Name", - "Service", - "Date", - "Report Id", - "Main", - "Sub", - "File Name", - "Transport Results", - ]; - for (const [i, row] of rows.entries()) { - const cols = await row.getByRole("cell").allTextContents(); - expect(cols).toHaveLength(colHeaders.length); - - const { - receivingOrg, - receivingOrgSvc, - createdAt, - reportId, - fileUrl, - transportResult, - } = - i === 0 - ? MOCK_GET_MESSAGE.receiverData[0] - : MOCK_GET_MESSAGE.receiverData.find( - (i) => i.reportId === cols[3], - ) ?? { reportId: "INVALID" }; - - // if first row, we expect column headers. else, the data row matching the report id - const expectedColContents = - i === 0 - ? colHeaders - : [ - receivingOrg ?? "", - receivingOrgSvc ?? "", - createdAt - ? new Date(createdAt).toLocaleString() - : "", - reportId, - parseFileLocation( - fileUrl ?? "N/A", - ).folderLocation.toLocaleUpperCase(), - parseFileLocation(fileUrl ?? "N/A") - .sendingOrg, - parseFileLocation(fileUrl ?? "N/A").fileName, - transportResult ?? "", - ]; - - for (const [i, col] of cols.entries()) { - expect(col).toBe(expectedColContents[i]); - } - } - }); - - test("table column 'FileName' will download file", async ({ - page, - }) => { - const downloadProm = page.waitForEvent("download"); - await mockGetHistoryReportResponse(page, "*"); - - await tableRows(page) - .nth(0) - .locator("td") - .nth(6) - .getByRole("button") - .click(); - - const download = await downloadProm; - - // assert filename - expect(download.suggestedFilename()).toBe( - "hhsprotect-covid-19-73e3cbc8-9920-4ab7-871f-843a1db4c074.csv", - ); - // get and assert stats - expect( - (await fs.promises.stat(await download.path())).size, - ).toBeGreaterThan(200); - }); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); - - test.describe("receiver user", () => { - test.use({ storageState: "e2e/.auth/receiver.json" }); - - test.beforeEach(async ({ page }) => { - await messageDetails.goto(page); - }); - - test("has alert", async ({ page }) => { - await expect(page.getByTestId("alert")).toBeAttached(); - await expect( - page.getByText( - /Our apologies, there was an error loading this content./, - ), - ).toBeAttached(); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); - - test.describe("sender user", () => { - test.use({ storageState: "e2e/.auth/sender.json" }); - - test.beforeEach(async ({ page }) => { - await messageDetails.goto(page); - }); - - test("has alert", async ({ page }) => { - await expect(page.getByTestId("alert")).toBeAttached(); - await expect( - page.getByText( - /Our apologies, there was an error loading this content./, - ), - ).toBeAttached(); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); -}); diff --git a/frontend-react/e2e/spec/all/message-id-search-page.spec.ts b/frontend-react/e2e/spec/all/message-id-search-page.spec.ts deleted file mode 100644 index c9057d80d2a..00000000000 --- a/frontend-react/e2e/spec/all/message-id-search-page.spec.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { expect, test } from "@playwright/test"; -import { noData, tableRows } from "../../helpers/utils"; -import { MOCK_GET_MESSAGE, MOCK_GET_MESSAGES } from "../../mocks/messages"; -import * as messageIdSearch from "../../pages/message-id-search"; -import { - MESSAGE_ID, - URL_MESSAGE_ID_SEARCH, -} from "../../pages/message-id-search"; -import { openReportIdDetailPage } from "../../pages/submission-history"; -import * as submissionHistory from "../../pages/submission-history"; - -test.describe("Message ID Search Page", () => { - test.describe("not authenticated", () => { - test("redirects to login", async ({ page }) => { - await messageIdSearch.goto(page); - await expect(page).toHaveURL("/login"); - }); - }); - - test.describe("authenticated admin", () => { - test.use({ storageState: "e2e/.auth/admin.json" }); - - test.describe("on search with results", () => { - test.beforeEach(async ({ page }) => { - await page.route(messageIdSearch.API_MESSAGES, (route) => - route.fulfill({ - status: 200, - json: MOCK_GET_MESSAGES, - }), - ); - await page.route(messageIdSearch.API_MESSAGE, (route) => - route.fulfill({ - status: 200, - json: MOCK_GET_MESSAGE, - }), - ); - await submissionHistory.mockGetReportHistoryResponse(page); - await messageIdSearch.goto(page); - - await page.locator("#search-field").fill(MESSAGE_ID); - await page - .getByRole("button", { - name: "Search", - }) - .click(); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveURL(URL_MESSAGE_ID_SEARCH); - await expect(page).toHaveTitle(/Message ID search - Admin/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - - test("displays expected table headers and data", async ({ - page, - }) => { - // include header row - const rowCount = MOCK_GET_MESSAGES.length + 1; - const table = page.getByRole("table"); - await expect(table).toBeVisible(); - const rows = await table.getByRole("row").all(); - expect(rows).toHaveLength(rowCount); - - const colHeaders = [ - "Message ID", - "Sender", - "Date/time submitted", - "Incoming Report Id", - ]; - for (const [i, row] of rows.entries()) { - const cols = await row.getByRole("cell").allTextContents(); - expect(cols).toHaveLength(colHeaders.length); - - const { messageId, sender, submittedDate, reportId } = - i === 0 - ? MOCK_GET_MESSAGES[0] - : MOCK_GET_MESSAGES.find( - (i) => i.reportId === cols[3], - ) ?? { reportId: "INVALID" }; - // if first row, we expect column headers. else, the data row matching the report id - const expectedColContents = - i === 0 - ? colHeaders - : [ - messageId, - sender ?? "", - submittedDate - ? new Date(submittedDate).toLocaleString() - : "", - reportId ?? "", - ]; - - for (const [i, col] of cols.entries()) { - expect(col).toBe(expectedColContents[i]); - } - } - }); - - test("table column 'Message ID' will open message id details", async ({ - page, - }) => { - const messageIdCell = tableRows(page) - .nth(0) - .locator("td") - .nth(0) - .getByRole("link", { name: MESSAGE_ID }); - await messageIdCell.click(); - await expect(page).toHaveURL("/message-details/0"); - expect(page.locator("h1").getByText(MESSAGE_ID)).toBeTruthy(); - }); - - test("table column 'Incoming Report Id' will open report id details", async ({ - page, - }) => { - const reportId = "73e3cbc8-9920-4ab7-871f-843a1db4c074"; - const reportIdCell = tableRows(page) - .nth(0) - .locator("td") - .nth(3) - .getByRole("link", { - name: reportId, - }); - await reportIdCell.click(); - await openReportIdDetailPage(page, reportId); - }); - }); - - test.describe("on search without results", () => { - test.beforeEach(async ({ page }) => { - await page.route(messageIdSearch.API_MESSAGES, (route) => - route.fulfill({ - status: 200, - json: [], - }), - ); - await messageIdSearch.goto(page); - - await page.locator("#search-field").fill(MESSAGE_ID); - await page - .getByRole("button", { - name: "Search", - }) - .click(); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveURL(URL_MESSAGE_ID_SEARCH); - await expect(page).toHaveTitle(/Message ID search - Admin/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - - test("shows no data", async ({ page }) => { - await expect(noData(page)).toBeAttached(); - }); - }); - }); - - test.describe("receiver user", () => { - test.use({ storageState: "e2e/.auth/receiver.json" }); - - test.beforeEach(async ({ page }) => { - await messageIdSearch.goto(page); - }); - - test("returns Page Not Found", async ({ page }) => { - await expect(page).toHaveTitle(/Page Not Found/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); - - test.describe("sender user", () => { - test.use({ storageState: "e2e/.auth/sender.json" }); - - test.beforeEach(async ({ page }) => { - await messageIdSearch.goto(page); - }); - - test("returns Page Not Found", async ({ page }) => { - await expect(page).toHaveTitle(/Page Not Found/); - }); - - test("has footer", async ({ page }) => { - await expect(page.locator("footer")).toBeAttached(); - }); - }); -}); diff --git a/frontend-react/e2e/spec/all/our-network-page.spec.ts b/frontend-react/e2e/spec/all/our-network-page.spec.ts deleted file mode 100644 index d8345618291..00000000000 --- a/frontend-react/e2e/spec/all/our-network-page.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { expect, test } from "@playwright/test"; - -import * as sideNav from "../../pages/about-side-navigation"; -import * as ourNetwork from "../../pages/our-network"; -test.describe( - "Our network page", - { - tag: "@smoke", - }, - () => { - test.beforeEach(async ({ page }) => { - await ourNetwork.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveTitle(/Our network/); - }); - - test.describe("Side navigation", () => { - test("has Our network link", async ({ page }) => { - await sideNav.clickNetwork(page); - await expect(page).toHaveURL(/.*about\/our-network/); - }); - - test("has Product roadmap link", async ({ page }) => { - await sideNav.clickRoadmap(page); - await expect(page).toHaveURL(/.*about\/roadmap/); - }); - - test("has News link", async ({ page }) => { - await sideNav.clickNews(page); - await expect(page).toHaveURL(/.*about\/news/); - }); - - test("has Case studies link", async ({ page }) => { - await sideNav.clickCaseStudies(page); - await expect(page).toHaveURL(/.*about\/case-studies/); - }); - - test("has Security link", async ({ page }) => { - await sideNav.clickSecurity(page); - await expect(page).toHaveURL(/.*about\/security/); - }); - - test("has Release notes link", async ({ page }) => { - await sideNav.clickReleaseNotes(page); - await expect(page).toHaveURL(/.*about\/release-notes/); - }); - }); - }, -); diff --git a/frontend-react/e2e/spec/all/about-page.spec.ts b/frontend-react/e2e/spec/all/public/about/about-page.spec.ts similarity index 84% rename from frontend-react/e2e/spec/all/about-page.spec.ts rename to frontend-react/e2e/spec/all/public/about/about-page.spec.ts index bf32d2b158b..f62b5f1db19 100644 --- a/frontend-react/e2e/spec/all/about-page.spec.ts +++ b/frontend-react/e2e/spec/all/public/about/about-page.spec.ts @@ -1,6 +1,5 @@ -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import { AboutPage } from "../../pages/about"; -import { test as baseTest, expect } from "../../test"; +import { AboutPage } from "../../../../pages/public/about/about"; +import { test as baseTest, expect } from "../../../../test"; const URL_ABOUT = "/about"; @@ -38,6 +37,12 @@ const test = baseTest.extend({ }); test.describe("About page", () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ aboutPage }) => { + await aboutPage.testHeader(); + }); + }); + test("nav contains the 'About' dropdown with 'About Reportstream' option", async ({ aboutPage }) => { const navItems = aboutPage.page.locator(".usa-nav li"); await expect(navItems).toContainText(["About"]); @@ -48,11 +53,6 @@ test.describe("About page", () => { await expect(aboutPage.page).toHaveURL(URL_ABOUT); }); - test("has correct title", async ({ aboutPage }) => { - await expect(aboutPage.page).toHaveTitle(aboutPage.title); - await expect(aboutPage.heading).toBeVisible(); - }); - test.describe("In this section", () => { test("has 'Our network' link", async ({ aboutPage }) => { await aboutPage.page.getByRole("link", { name: /Our network/ }).click(); @@ -156,17 +156,8 @@ test.describe("About page", () => { }); test.describe("Footer", () => { - test("has footer", async ({ aboutPage }) => { - await expect(aboutPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ aboutPage }) => { - await expect(aboutPage.footer).not.toBeInViewport(); - await scrollToFooter(aboutPage.page); - await expect(aboutPage.footer).toBeInViewport(); - await expect(aboutPage.page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(aboutPage.page); - await expect(aboutPage.page.getByTestId("govBanner")).toBeInViewport(); + test("has footer and explicit scroll to footer and scroll to top", async ({ aboutPage }) => { + await aboutPage.testFooter(); }); }); }); diff --git a/frontend-react/e2e/spec/all/public/about/our-network-page.spec.ts b/frontend-react/e2e/spec/all/public/about/our-network-page.spec.ts new file mode 100644 index 00000000000..d826f09b006 --- /dev/null +++ b/frontend-react/e2e/spec/all/public/about/our-network-page.spec.ts @@ -0,0 +1,62 @@ +import { aboutSideNav } from "../../../../helpers/internal-links"; +import { OurNetworkPage } from "../../../../pages/public/about/our-network"; +import { test as baseTest } from "../../../../test"; + +export interface OurNetworkPageFixtures { + ourNetworkPage: OurNetworkPage; +} + +const test = baseTest.extend({ + ourNetworkPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }, + use, + ) => { + const page = new OurNetworkPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe( + "Our network page", + { + tag: "@smoke", + }, + () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ ourNetworkPage }) => { + await ourNetworkPage.testHeader(); + }); + }); + + test.describe("Side navigation", () => { + test("has correct About sidenav items", async ({ ourNetworkPage }) => { + await ourNetworkPage.testSidenav(aboutSideNav); + }); + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ ourNetworkPage }) => { + await ourNetworkPage.testFooter(); + }); + }); + }, +); diff --git a/frontend-react/e2e/spec/all/public/about/roadmap.spec.ts b/frontend-react/e2e/spec/all/public/about/roadmap.spec.ts new file mode 100644 index 00000000000..d3f71cbf108 --- /dev/null +++ b/frontend-react/e2e/spec/all/public/about/roadmap.spec.ts @@ -0,0 +1,82 @@ +import { aboutSideNav } from "../../../../helpers/internal-links"; +import { RoadmapPage } from "../../../../pages/public/about/roadmap"; +import { test as baseTest } from "../../../../test"; + +export interface RoadmapPageFixtures { + roadmapPage: RoadmapPage; +} + +const test = baseTest.extend({ + roadmapPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }, + use, + ) => { + const page = new RoadmapPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }); + await page.goto(); + await use(page); + }, +}); + +const cards = [ + { + name: "News", + }, + { + name: "Release notes", + }, + { + name: "Developer resources", + }, +]; + +test.describe( + "Product roadmap page", + { + tag: "@smoke", + }, + () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ roadmapPage }) => { + await roadmapPage.testHeader(); + }); + }); + + test.describe("Side navigation", () => { + test("has correct About sidenav items", async ({ roadmapPage }) => { + await roadmapPage.testSidenav(aboutSideNav); + }); + }); + + test.describe("CTA", () => { + for (const card of cards) { + test(`should have ${card.name}`, async ({ roadmapPage }) => { + await roadmapPage.testCard(card); + }); + } + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ roadmapPage }) => { + await roadmapPage.testFooter(); + }); + }); + }, +); diff --git a/frontend-react/e2e/spec/all/security.spec.ts b/frontend-react/e2e/spec/all/public/about/security.spec.ts similarity index 73% rename from frontend-react/e2e/spec/all/security.spec.ts rename to frontend-react/e2e/spec/all/public/about/security.spec.ts index a22b9b5c737..08976df88e4 100644 --- a/frontend-react/e2e/spec/all/security.spec.ts +++ b/frontend-react/e2e/spec/all/public/about/security.spec.ts @@ -1,6 +1,5 @@ -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import { SecurityPage } from "../../pages/security"; -import { test as baseTest, expect } from "../../test"; +import { SecurityPage } from "../../../../pages/public/about/security"; +import { test as baseTest, expect } from "../../../../test"; const URL_SECURITY = "/about/security"; @@ -43,6 +42,12 @@ test.describe( tag: "@smoke", }, () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ securityPage }) => { + await securityPage.testHeader(); + }); + }); + test("nav contains the 'About' dropdown with 'Security Reportstream' option", async ({ securityPage }) => { const navItems = securityPage.page.locator(".usa-nav li"); await expect(navItems).toContainText(["About"]); @@ -53,11 +58,6 @@ test.describe( await expect(securityPage.page).toHaveURL(URL_SECURITY); }); - test("has correct title", async ({ securityPage }) => { - await expect(securityPage.page).toHaveTitle(securityPage.title); - await expect(securityPage.heading).toBeVisible(); - }); - test.describe("Security section", () => { test("Accordion sections expand", async ({ securityPage }) => { // Not necessary to test all expansions. @@ -90,17 +90,8 @@ test.describe( }); test.describe("Footer", () => { - test("has footer", async ({ securityPage }) => { - await expect(securityPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ securityPage }) => { - await expect(securityPage.footer).not.toBeInViewport(); - await scrollToFooter(securityPage.page); - await expect(securityPage.footer).toBeInViewport(); - await expect(securityPage.page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(securityPage.page); - await expect(securityPage.page.getByTestId("govBanner")).toBeInViewport(); + test("has footer + test bottom-to-top page scroll", async ({ securityPage }) => { + await securityPage.testFooter(); }); }); }, diff --git a/frontend-react/e2e/spec/all/getting-started/receiving-data.spec.ts b/frontend-react/e2e/spec/all/public/getting-started/receiving-data.spec.ts similarity index 63% rename from frontend-react/e2e/spec/all/getting-started/receiving-data.spec.ts rename to frontend-react/e2e/spec/all/public/getting-started/receiving-data.spec.ts index 3e1eb2c7660..0a7755d0c4f 100644 --- a/frontend-react/e2e/spec/all/getting-started/receiving-data.spec.ts +++ b/frontend-react/e2e/spec/all/public/getting-started/receiving-data.spec.ts @@ -1,7 +1,6 @@ -import site from "../../../../src/content/site.json" assert { type: "json" }; -import { scrollToFooter, scrollToTop } from "../../../helpers/utils"; -import { ReceivingDataPage } from "../../../pages/getting-started/receiving-data"; -import { test as baseTest, expect } from "../../../test"; +import site from "../../../../../src/content/site.json" assert { type: "json" }; +import { ReceivingDataPage } from "../../../../pages/public/getting-started/receiving-data"; +import { test as baseTest, expect } from "../../../../test"; export interface ReceivingDataPageFixtures { receivingDataPage: ReceivingDataPage; @@ -37,9 +36,10 @@ const test = baseTest.extend({ }); test.describe("Receiving data page", () => { - test("has correct title", async ({ receivingDataPage }) => { - await expect(receivingDataPage.page).toHaveTitle(receivingDataPage.title); - await expect(receivingDataPage.heading).toBeVisible(); + test.describe("Header", () => { + test("has correct title + heading", async ({ receivingDataPage }) => { + await receivingDataPage.testHeader(); + }); }); test("has link to onboarding form", async ({ receivingDataPage }) => { @@ -73,17 +73,8 @@ test.describe("Receiving data page", () => { }); test.describe("Footer", () => { - test("has footer", async ({ receivingDataPage }) => { - await expect(receivingDataPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ receivingDataPage }) => { - await expect(receivingDataPage.footer).not.toBeInViewport(); - await scrollToFooter(receivingDataPage.page); - await expect(receivingDataPage.footer).toBeInViewport(); - await expect(receivingDataPage.page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(receivingDataPage.page); - await expect(receivingDataPage.page.getByTestId("govBanner")).toBeInViewport(); + test("has footer and explicit scroll to footer and scroll to top", async ({ receivingDataPage }) => { + await receivingDataPage.testFooter(); }); }); }); diff --git a/frontend-react/e2e/spec/all/getting-started/sending-data.spec.ts b/frontend-react/e2e/spec/all/public/getting-started/sending-data.spec.ts similarity index 64% rename from frontend-react/e2e/spec/all/getting-started/sending-data.spec.ts rename to frontend-react/e2e/spec/all/public/getting-started/sending-data.spec.ts index 3e1b9f5544d..fc915c2c7cd 100644 --- a/frontend-react/e2e/spec/all/getting-started/sending-data.spec.ts +++ b/frontend-react/e2e/spec/all/public/getting-started/sending-data.spec.ts @@ -1,7 +1,6 @@ -import site from "../../../../src/content/site.json" assert { type: "json" }; -import { scrollToFooter, scrollToTop } from "../../../helpers/utils"; -import { SendingDataPage } from "../../../pages/getting-started/sending-data.js"; -import { test as baseTest, expect } from "../../../test"; +import site from "../../../../../src/content/site.json" assert { type: "json" }; +import { SendingDataPage } from "../../../../pages/public/getting-started/sending-data.js"; +import { test as baseTest, expect } from "../../../../test"; export interface SendingDataPageFixtures { sendingDataPage: SendingDataPage; @@ -37,9 +36,10 @@ const test = baseTest.extend({ }); test.describe("Sending data page", () => { - test("has correct title", async ({ sendingDataPage }) => { - await expect(sendingDataPage.page).toHaveTitle(sendingDataPage.title); - await expect(sendingDataPage.heading).toBeVisible(); + test.describe("Header", () => { + test("has correct title + heading", async ({ sendingDataPage }) => { + await sendingDataPage.testHeader(); + }); }); test("has link to get started with ReportStream", async ({ sendingDataPage }) => { @@ -73,17 +73,8 @@ test.describe("Sending data page", () => { }); test.describe("Footer", () => { - test("has footer", async ({ sendingDataPage }) => { - await expect(sendingDataPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ sendingDataPage }) => { - await expect(sendingDataPage.footer).not.toBeInViewport(); - await scrollToFooter(sendingDataPage.page); - await expect(sendingDataPage.footer).toBeInViewport(); - await expect(sendingDataPage.page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(sendingDataPage.page); - await expect(sendingDataPage.page.getByTestId("govBanner")).toBeInViewport(); + test("has footer and explicit scroll to footer and scroll to top", async ({ sendingDataPage }) => { + await sendingDataPage.testFooter(); }); }); }); diff --git a/frontend-react/e2e/spec/all/public/homepage.spec.ts b/frontend-react/e2e/spec/all/public/homepage.spec.ts new file mode 100644 index 00000000000..6efc9037c11 --- /dev/null +++ b/frontend-react/e2e/spec/all/public/homepage.spec.ts @@ -0,0 +1,66 @@ +import site from "../../../../src/content/site.json" assert { type: "json" }; +import { HomePage } from "../../../pages/public/homepage"; +import { test as baseTest, expect } from "../../../test"; + +export interface HomePageFixtures { + homePage: HomePage; +} + +const test = baseTest.extend({ + homePage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }, + use, + ) => { + const page = new HomePage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe( + "Homepage", + { + tag: "@smoke", + }, + () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ homePage }) => { + await homePage.testHeader(); + }); + }); + + test.describe("CTA", () => { + test("has 'Contact us' button", async ({ homePage }) => { + const heroLocator = homePage.page.locator('[class*="hero-wrapper"]'); + const ctaURL = site.forms.connectWithRS.url; + const ctaLink = heroLocator.locator(`a[href="${ctaURL}"]`).first(); + + await expect(ctaLink).toBeVisible(); + }); + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ homePage }) => { + await homePage.testFooter(); + }); + }); + }, +); diff --git a/frontend-react/e2e/spec/all/public/managing-your-connection/managing-your-connection-page.spec.ts b/frontend-react/e2e/spec/all/public/managing-your-connection/managing-your-connection-page.spec.ts new file mode 100644 index 00000000000..e10692aa357 --- /dev/null +++ b/frontend-react/e2e/spec/all/public/managing-your-connection/managing-your-connection-page.spec.ts @@ -0,0 +1,90 @@ +import { ManagingYourConnectionPage } from "../../../../pages/public/managing-your-connection/managing-your-connection"; +import { test as baseTest, expect } from "../../../../test"; + +const cards = [ + { + name: "For healthcare organizations", + links: ["Manage your public key", "View your submission history", "Login", "contact us"], + }, + { + name: "For public health agencies", + links: ["Refer healthcare organizations", "View your dashboard", "Login", "contact us"], + }, +]; + +export interface ManagingYourConnectionPageFixtures { + managingYourConnectionPage: ManagingYourConnectionPage; +} + +const test = baseTest.extend({ + managingYourConnectionPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }, + use, + ) => { + const page = new ManagingYourConnectionPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }); + await page.goto(); + await use(page); + }, +}); + +test.describe( + "Managing Your Connection page", + { + tag: "@smoke", + }, + () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ managingYourConnectionPage }) => { + await managingYourConnectionPage.testHeader(); + }); + }); + + test.describe("Quick links", () => { + for (const card of cards) { + test(`should have ${card.name} links`, async ({ managingYourConnectionPage }) => { + const cardHeader = managingYourConnectionPage.page.locator(".usa-card__header", { + hasText: card.name, + }); + + await expect(cardHeader).toBeVisible(); + + const cardContainer = cardHeader.locator(".."); + + for (const link of card.links) { + await expect( + cardContainer.getByRole("link", { + name: `${link}`, + }), + ).toBeVisible(); + } + }); + } + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ + managingYourConnectionPage, + }) => { + await managingYourConnectionPage.testFooter(); + }); + }); + }, +); diff --git a/frontend-react/e2e/spec/all/refer-healthcare-page.spec.ts b/frontend-react/e2e/spec/all/public/managing-your-connection/refer-healthcare-page.spec.ts similarity index 63% rename from frontend-react/e2e/spec/all/refer-healthcare-page.spec.ts rename to frontend-react/e2e/spec/all/public/managing-your-connection/refer-healthcare-page.spec.ts index 37421d676e4..27c3c74899c 100644 --- a/frontend-react/e2e/spec/all/refer-healthcare-page.spec.ts +++ b/frontend-react/e2e/spec/all/public/managing-your-connection/refer-healthcare-page.spec.ts @@ -1,6 +1,5 @@ -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import { ReferHealthcarePage } from "../../pages/refer-healthcare"; -import { test as baseTest, expect } from "../../test"; +import { ReferHealthcarePage } from "../../../../pages/public/managing-your-connection/refer-healthcare"; +import { test as baseTest, expect } from "../../../../test"; export interface ReferHealthcarePageFixtures { referHealthcarePage: ReferHealthcarePage; @@ -41,9 +40,10 @@ test.describe( tag: "@smoke", }, () => { - test("has correct title", async ({ referHealthcarePage }) => { - await expect(referHealthcarePage.page).toHaveTitle(referHealthcarePage.title); - await expect(referHealthcarePage.heading).toBeVisible(); + test.describe("Header", () => { + test("has correct title + heading", async ({ referHealthcarePage }) => { + await referHealthcarePage.testHeader(); + }); }); test("has correct sidenav items", async ({ referHealthcarePage }) => { @@ -68,17 +68,8 @@ test.describe( }); test.describe("Footer", () => { - test("has footer", async ({ referHealthcarePage }) => { - await expect(referHealthcarePage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ referHealthcarePage }) => { - await expect(referHealthcarePage.footer).not.toBeInViewport(); - await scrollToFooter(referHealthcarePage.page); - await expect(referHealthcarePage.footer).toBeInViewport(); - await expect(referHealthcarePage.page.getByTestId("govBanner")).not.toBeInViewport(); - await scrollToTop(referHealthcarePage.page); - await expect(referHealthcarePage.page.getByTestId("govBanner")).toBeInViewport(); + test("has footer and explicit scroll to footer and scroll to top", async ({ referHealthcarePage }) => { + await referHealthcarePage.testFooter(); }); }); }, diff --git a/frontend-react/e2e/spec/all/public/resources-page.spec.ts b/frontend-react/e2e/spec/all/public/resources-page.spec.ts new file mode 100644 index 00000000000..1acd4e1883f --- /dev/null +++ b/frontend-react/e2e/spec/all/public/resources-page.spec.ts @@ -0,0 +1,69 @@ +import { DeveloperResourcesPage } from "../../../pages/public/resources"; +import { test as baseTest } from "../../../test"; + +export interface DeveloperResourcesPageFixtures { + developerResourcesPage: DeveloperResourcesPage; +} + +const test = baseTest.extend({ + developerResourcesPage: async ( + { + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }, + use, + ) => { + const page = new DeveloperResourcesPage({ + page: _page, + isMockDisabled, + adminLogin, + senderLogin, + receiverLogin, + storageState, + isFrontendWarningsLog, + frontendWarningsLogPath, + }); + await page.goto(); + await use(page); + }, +}); + +const cards = [ + { + name: "API guide", + }, + { + name: "GitHub", + }, + { + name: "Release notes", + }, +]; + +test.describe("Developer Resources page", () => { + test.describe("Header", () => { + test("has correct title + heading", async ({ developerResourcesPage }) => { + await developerResourcesPage.testHeader(); + }); + }); + + test.describe("CTA", () => { + for (const card of cards) { + test(`should have ${card.name}`, async ({ developerResourcesPage }) => { + await developerResourcesPage.testCard(card); + }); + } + }); + + test.describe("Footer", () => { + test("has footer and explicit scroll to footer and scroll to top", async ({ developerResourcesPage }) => { + await developerResourcesPage.testFooter(); + }); + }); +}); diff --git a/frontend-react/e2e/spec/all/support-page.spec.ts b/frontend-react/e2e/spec/all/public/support-page.spec.ts similarity index 57% rename from frontend-react/e2e/spec/all/support-page.spec.ts rename to frontend-react/e2e/spec/all/public/support-page.spec.ts index 0948a85b187..fc18518233f 100644 --- a/frontend-react/e2e/spec/all/support-page.spec.ts +++ b/frontend-react/e2e/spec/all/public/support-page.spec.ts @@ -1,7 +1,6 @@ -import site from "../../../src/content/site.json" assert { type: "json" }; -import { scrollToFooter, scrollToTop } from "../../helpers/utils"; -import { SupportPage } from "../../pages/support.js"; -import { test as baseTest, expect } from "../../test"; +import site from "../../../../src/content/site.json" assert { type: "json" }; +import { SupportPage } from "../../../pages/public/support.js"; +import { test as baseTest, expect } from "../../../test"; const cards = [ { @@ -52,15 +51,14 @@ const test = baseTest.extend({ }); test.describe("Support page", () => { - test("has correct title", async ({ supportPage }) => { - await expect(supportPage.page).toHaveTitle(supportPage.title); - await expect(supportPage.heading).toBeVisible(); + test.describe("Header", () => { + test("has correct title + heading", async ({ supportPage }) => { + await supportPage.testHeader(); + }); }); test("Should have a way of contacting support", async ({ supportPage }) => { - const contactLink = supportPage.page - .locator(`a[href="${site.forms.contactUs.url}"]`) - .first(); + const contactLink = supportPage.page.locator(`a[href="${site.forms.contactUs.url}"]`).first(); await contactLink.scrollIntoViewIfNeeded(); await expect(contactLink).toBeVisible(); @@ -78,30 +76,13 @@ test.describe("Support page", () => { const viewAllLink = cardContainer.locator("a").last(); await viewAllLink.click(); - await expect( - supportPage.page.locator(`#${card.anchorID}`), - ).toBeVisible(); + await expect(supportPage.page.locator(`#${card.anchorID}`)).toBeVisible(); }); } test.describe("Footer", () => { - test("has footer", async ({ supportPage }) => { - await expect(supportPage.footer).toBeAttached(); - }); - - test("explicit scroll to footer and then scroll to top", async ({ - supportPage, - }) => { - await expect(supportPage.footer).not.toBeInViewport(); - await scrollToFooter(supportPage.page); - await expect(supportPage.footer).toBeInViewport(); - await expect( - supportPage.page.getByTestId("govBanner"), - ).not.toBeInViewport(); - await scrollToTop(supportPage.page); - await expect( - supportPage.page.getByTestId("govBanner"), - ).toBeInViewport(); + test("has footer and explicit scroll to footer and scroll to top", async ({ supportPage }) => { + await supportPage.testFooter(); }); }); }); diff --git a/frontend-react/e2e/spec/all/resources-page.spec.ts b/frontend-react/e2e/spec/all/resources-page.spec.ts deleted file mode 100644 index ab99383c089..00000000000 --- a/frontend-react/e2e/spec/all/resources-page.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { expect, test } from "@playwright/test"; -import * as resources from "../../pages/resources"; - -// eslint-disable-next-line playwright/no-skipped-test -test.describe.skip("Developer Resources page", () => { - test.beforeEach(async ({ page }) => { - await resources.goto(page); - }); - - test("should have correct title", async ({ page }) => { - await expect(page).toHaveURL(/developer-resources/); - await expect(page).toHaveTitle(/Developer resources/); - }); - - // TODO: Fix - test.describe("Card navigation", () => { - const cardLinks = [ - { - name: "Security practices", - url: "/resources/security-practices", - }, - { - name: "System and settings", - url: "/resources/system-and-settings", - }, - { - name: "ReportStream API", - url: "/resources/api", - }, - { - name: "Guide to submitting data to ReportStream", - url: "/resources/getting-started-submitting-data", - }, - { - name: "Account Registration Guide", - url: "/resources/account-registration-guide", - }, - { - name: "ELR Onboarding Checklist", - url: "/resources/elr-checklist", - }, - { - name: "Guide to receiving ReportStream data", - url: "/resources/getting-started-public-health-departments", - }, - { - name: "Manual data download guide", - url: "/resources/data-download-guide", - }, - { - name: "ReportStream Referral Guide", - url: "/resources/referral-guide", - }, - ]; - - for (const cardLink of cardLinks) { - test(`should have ${cardLink.name} link`, async ({ page }) => { - await page.getByRole("link", { name: cardLink.name }).click(); - - await expect(page).toHaveURL(cardLink.url); - }); - } - - test("should redirect unauthenticated users to login page on managing public key", async ({ - page, - }) => { - await page - .getByRole("link", { - name: "Manage your public key", - }) - .click(); - - await expect(page).toHaveURL("/login"); - }); - }); -}); diff --git a/frontend-react/e2e/spec/all/roadmap.spec.ts b/frontend-react/e2e/spec/all/roadmap.spec.ts deleted file mode 100644 index a2905db3ce5..00000000000 --- a/frontend-react/e2e/spec/all/roadmap.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { expect, test } from "@playwright/test"; - -import * as internalLinks from "../../helpers/internal-links"; -import * as sideNav from "../../pages/about-side-navigation"; -import * as roadmap from "../../pages/roadmap"; -import { URL_ROADMAP } from "../../pages/roadmap"; - -test.describe( - "Product roadmap page", - { - tag: "@smoke", - }, - () => { - test.beforeEach(async ({ page }) => { - await roadmap.goto(page); - }); - - test("has correct title", async ({ page }) => { - await expect(page).toHaveURL(URL_ROADMAP); - await expect(page).toHaveTitle(/Product roadmap/); - }); - - test.describe("Side navigation", () => { - test("has Our network link", async ({ page }) => { - await sideNav.clickNetwork(page); - await expect(page).toHaveURL(/.*about\/our-network/); - }); - - test("has Product roadmap link", async ({ page }) => { - await sideNav.clickRoadmap(page); - await expect(page).toHaveURL(/.*about\/roadmap/); - }); - - test("has News link", async ({ page }) => { - await sideNav.clickNews(page); - await expect(page).toHaveURL(/.*about\/news/); - }); - - test("has Case studies link", async ({ page }) => { - await sideNav.clickCaseStudies(page); - await expect(page).toHaveURL(/.*about\/case-studies/); - }); - - test("has Security link", async ({ page }) => { - await sideNav.clickSecurity(page); - await expect(page).toHaveURL(/.*about\/security/); - }); - - test("has Release notes link", async ({ page }) => { - await sideNav.clickReleaseNotes(page); - await expect(page).toHaveURL(/.*about\/release-notes/); - }); - }); - - test.describe("Additional resources Links", () => { - test("has News", async ({ page }) => { - await internalLinks.clickOnInternalLink("div", "CardGroup", "News", page); - await expect(page).toHaveURL(/.*about\/news/); - }); - - test("has Release notes", async ({ page }) => { - await internalLinks.clickOnInternalLink("div", "CardGroup", "Release notes", page); - await expect(page).toHaveURL(/.*about\/release-notes/); - }); - - test("has Developer resources", async ({ page }) => { - await internalLinks.clickOnInternalLink("div", "CardGroup", "Developer resources", page); - await expect(page).toHaveURL(/.*developer-resources/); - }); - }); - }, -); diff --git a/frontend-react/e2e/spec/all/timezone.spec.ts b/frontend-react/e2e/spec/all/timezone.spec.ts index 8c7c8cd3597..273227412c4 100644 --- a/frontend-react/e2e/spec/all/timezone.spec.ts +++ b/frontend-react/e2e/spec/all/timezone.spec.ts @@ -1,5 +1,7 @@ -import { expect, test } from "@playwright/test"; import { endOfDay, startOfDay } from "date-fns"; +import { test as baseTest, expect } from "../../test"; + +const test = baseTest.extend({}); test("playwright/browser timezone parity", async ({ page }) => { const now = new Date(); @@ -7,9 +9,7 @@ test("playwright/browser timezone parity", async ({ page }) => { const browserNow = new Date(browserNowIso); const timezoneId = Intl.DateTimeFormat().resolvedOptions().timeZoneName; - const browserTimezoneId = await page.evaluate( - () => Intl.DateTimeFormat().resolvedOptions().timeZoneName, - ); + const browserTimezoneId = await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZoneName); const nowStart = startOfDay(now); const browserStartIso = await page.evaluate(() => { diff --git a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts index 9c5cd9d9865..8c615cfa158 100644 --- a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts @@ -1,8 +1,9 @@ /* eslint-disable playwright/no-conditional-in-test */ import axios, { AxiosError } from "axios"; import * as fs from "fs"; -import * as publicPagesLinkCheck from "../../pages/public-pages-link-check"; -import { expect, test } from "../../test"; +import { test as baseTest, expect } from "../../test"; + +const test = baseTest.extend({}); // To save bandwidth, this test is within the /spec/chromium-only/ folder // Since we're just checking link validity. This is specified within our @@ -18,6 +19,8 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = const normalizeUrl = (href: string, baseUrl: string) => new URL(href, baseUrl).toString(); // Using our sitemap.xml, we'll create a pathnames array + // We cannot use our POM, we must + // create context manually with browser.newContext() test.beforeAll(async ({ browser }) => { const page = await browser.newPage(); const response = await page.goto("/sitemap.xml"); @@ -52,7 +55,7 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = // Set test timeout to be 1 minute instead of 30 seconds test.setTimeout(60000); for (const path of urlPaths) { - await publicPagesLinkCheck.publicPageGoto(page, path); + await page.goto(path); const baseUrl = new URL(page.url()).origin; const allATags = await page.getByRole("link", { includeHidden: true }).elementHandles();