Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experience/15301/clean up e2e test suite #15634

Merged
merged 18 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend-react/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
},
],
Expand Down
67 changes: 54 additions & 13 deletions frontend-react/e2e/helpers/internal-links.ts
Original file line number Diff line number Diff line change
@@ -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",
},
];
102 changes: 50 additions & 52 deletions frontend-react/e2e/pages/BasePage.ts
Original file line number Diff line number Diff line change
@@ -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<string, Parameters<Page["route"]>[1]>;
export type MockRouteCache = Record<string, RouteFulfillOptions>;
Expand All @@ -12,25 +13,12 @@ export interface BasePageProps {
heading?: Locator;
}

export type RouteFulfillOptions = Exclude<
Parameters<Route["fulfill"]>[0],
undefined
> & { isMock?: boolean };
export type RouteFulfillOptionsFn = (
request: Request,
) => Promise<RouteFulfillOptions> | RouteFulfillOptions;
export type RouteFulfillOptions = Exclude<Parameters<Route["fulfill"]>[0], undefined> & { isMock?: boolean };
export type RouteFulfillOptionsFn = (request: Request) => Promise<RouteFulfillOptions> | RouteFulfillOptions;
export type RouteHandlerFn = (route: Route, request: Request) => Promise<void>;
export type RouteHandlerFulfillOptions =
| RouteFulfillOptions
| RouteFulfillOptionsFn;
export type RouteHandlerFulfillEntry = [
url: string,
fulfillOptions: RouteHandlerFulfillOptions,
];
export type ResponseHandlerEntry = [
url: string,
handler: (response: Response) => Promise<void> | void,
];
export type RouteHandlerFulfillOptions = RouteFulfillOptions | RouteFulfillOptionsFn;
export type RouteHandlerFulfillEntry = [url: string, fulfillOptions: RouteHandlerFulfillOptions];
export type ResponseHandlerEntry = [url: string, handler: (response: Response) => Promise<void> | void];
export type RouteHandlerEntry = [url: string, handler: RouteHandlerFn];

export interface GotoRouteHandlerOptions {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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);
};
Expand Down Expand Up @@ -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;
Expand Down
40 changes: 0 additions & 40 deletions frontend-react/e2e/pages/about-side-navigation.ts

This file was deleted.

Loading
Loading