From b4ac3f4f9625d4797eeaefd2e1167975a0b51e1f Mon Sep 17 00:00:00 2001 From: Benjamin Paige Date: Tue, 23 Jan 2024 09:03:22 -0700 Subject: [PATCH] feat(508-e2e): add auth setup and init 508 axe tests (#332) * add auth setup step to e2e tests and axe package * fix homepage axe issues * add loginpage and reviewer auth to e2e setup * fix dashboard a11y issues * add webform a11y tests and resolve issues * fix typo --- .gitignore | 3 +- src/services/ui/e2e/pages/index.ts | 1 + src/services/ui/e2e/pages/loginPage.ts | 21 +++++++++ src/services/ui/e2e/tests/a11y/index.spec.ts | 46 +++++++++++++++++++ src/services/ui/e2e/tests/home/index.spec.ts | 27 ----------- src/services/ui/e2e/utils/auth.setup.ts | 35 ++++++++++++++ src/services/ui/e2e/utils/users.ts | 6 +-- src/services/ui/package.json | 1 + src/services/ui/playwright.config.ts | 21 +++++---- .../ui/src/components/HowItWorks/index.tsx | 2 +- .../ui/src/components/Layout/index.tsx | 10 ++-- .../Opensearch/main/Filtering/index.tsx | 2 +- .../ui/src/components/Pagination/index.tsx | 6 ++- .../ui/src/components/RHF/Document.tsx | 4 +- src/services/ui/src/components/RHF/Slot.tsx | 17 +++++-- .../ui/src/components/UsaBanner/index.tsx | 7 +-- .../dashboard/Lists/renderCells/index.tsx | 8 +++- src/services/ui/src/pages/profile/index.tsx | 3 +- src/services/ui/src/pages/welcome/index.tsx | 24 +++++----- yarn.lock | 12 +++++ 20 files changed, 179 insertions(+), 77 deletions(-) create mode 100644 src/services/ui/e2e/pages/index.ts create mode 100644 src/services/ui/e2e/pages/loginPage.ts create mode 100644 src/services/ui/e2e/tests/a11y/index.spec.ts create mode 100644 src/services/ui/e2e/utils/auth.setup.ts diff --git a/.gitignore b/.gitignore index b6925ba786..0be21c7dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ build_run .build .turbo coverage -lambda_layer.zip \ No newline at end of file +lambda_layer.zip +**/.auth/*.json \ No newline at end of file diff --git a/src/services/ui/e2e/pages/index.ts b/src/services/ui/e2e/pages/index.ts new file mode 100644 index 0000000000..b311de19c0 --- /dev/null +++ b/src/services/ui/e2e/pages/index.ts @@ -0,0 +1 @@ +export * from "./loginPage"; diff --git a/src/services/ui/e2e/pages/loginPage.ts b/src/services/ui/e2e/pages/loginPage.ts new file mode 100644 index 0000000000..13c740bb5d --- /dev/null +++ b/src/services/ui/e2e/pages/loginPage.ts @@ -0,0 +1,21 @@ +import { Page, expect } from "@playwright/test"; + +export class LoginPage { + constructor(private readonly page: Page) {} + + async goto() { + await this.page.goto("/"); + await this.page.getByRole("button", { name: "Sign In" }).click(); + } + + async login(email: string, password: string) { + await this.goto(); + await this.page.getByRole("textbox", { name: "name@host.com" }).fill(email); + await this.page.getByRole("textbox", { name: "Password" }).fill(password); + await this.page.getByRole("button", { name: "submit" }).click(); + await this.page.waitForLoadState("networkidle"); + expect( + await this.page.getByRole("link", { name: "Dashboard" }) + ).toBeVisible(); + } +} diff --git a/src/services/ui/e2e/tests/a11y/index.spec.ts b/src/services/ui/e2e/tests/a11y/index.spec.ts new file mode 100644 index 0000000000..1b81f1b4d4 --- /dev/null +++ b/src/services/ui/e2e/tests/a11y/index.spec.ts @@ -0,0 +1,46 @@ +import { test, expect } from "@playwright/test"; +import AxeBuilder from "@axe-core/playwright"; + +const staticRoutes = ["/", "/dashboard", "/faq", "/profile"]; + +test.describe("test a11y on static routes", () => { + for (const route of staticRoutes) { + test(`${route} should not have any automatically detectable accessibility issues`, async ({ + page, + }) => { + await page.goto(route); + await page.waitForLoadState("networkidle"); // playwright is so fast this is sometimes helpful to slow it down to view results + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); + console.log( + `${route} violations: `, + accessibilityScanResults.violations.length + ); + expect(accessibilityScanResults.violations).toEqual([]); + }); + } +}); + +const webformRoutes = [ + "/guides/abp", + "/webform/abp10/1", + "/webform/abp3_1/1", + "/webform/abp3/1", + "/webform/abp1/1", +]; + +test.describe("test a11y on webform routes", () => { + for (const route of webformRoutes) { + test(`${route} should not have any automatically detectable accessibility issues`, async ({ + page, + }) => { + await page.goto(route); + await page.waitForLoadState("networkidle"); + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); + console.log( + `${route} violations: `, + accessibilityScanResults.violations.length + ); + expect(accessibilityScanResults.violations).toEqual([]); + }); + } +}); diff --git a/src/services/ui/e2e/tests/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts index e4e7616ff1..4757a72aa5 100644 --- a/src/services/ui/e2e/tests/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -1,16 +1,4 @@ -import * as Libs from "../../../../../libs/secrets-manager-lib"; import { test, expect } from "@playwright/test"; -import { testUsers } from "e2e/utils/users"; -const stage = - process.env.STAGE_NAME === "production" || process.env.STAGE_NAME === "val" - ? process.env.STAGE_NAME - : "default"; -const secretId = `${process.env.PROJECT}/${stage}/bootstrapUsersPassword`; - -const password = (await Libs.getSecretsValue( - process.env.REGION_A as string, - secretId -)) as string; test("has title", async ({ page }) => { await page.goto("/"); @@ -32,12 +20,6 @@ test("see frequently asked questions header when in faq page", async ({ test("see dashboard link when log in", async ({ page }) => { await page.goto("/"); - await page.getByRole("button", { name: "Sign In" }).click(); - await page - .getByRole("textbox", { name: "name@host.com" }) - .fill(testUsers.state); - await page.getByRole("textbox", { name: "Password" }).fill(password); - await page.getByRole("button", { name: "submit" }).click(); await page.getByRole("link", { name: "Dashboard" }).click(); const dashboardLinkVisible = await page @@ -45,12 +27,3 @@ test("see dashboard link when log in", async ({ page }) => { .isVisible(); expect(dashboardLinkVisible).toBeTruthy(); }); - -test("failed incorrect login username", async ({ page }) => { - await page.goto("/"); - await page.getByRole("button", { name: "Sign In" }).click(); - await page.getByRole("textbox", { name: "name@host.com" }).fill("."); - await page.getByRole("textbox", { name: "Password" }).fill(password); - await page.getByRole("button", { name: "submit" }).click(); - await page.locator("#loginErrorMessage").first().isVisible(); -}); diff --git a/src/services/ui/e2e/utils/auth.setup.ts b/src/services/ui/e2e/utils/auth.setup.ts new file mode 100644 index 0000000000..cfcf5be90e --- /dev/null +++ b/src/services/ui/e2e/utils/auth.setup.ts @@ -0,0 +1,35 @@ +import { test as setup } from "@playwright/test"; +import * as Libs from "../../../../libs/secrets-manager-lib"; +import { testUsers } from "./users"; +import { LoginPage } from "../pages"; + +const stage = + process.env.STAGE_NAME === "production" || process.env.STAGE_NAME === "val" + ? process.env.STAGE_NAME + : "default"; +const secretId = `${process.env.PROJECT}/${stage}/bootstrapUsersPassword`; + +const password = (await Libs.getSecretsValue( + process.env.REGION_A as string, + secretId +)) as string; + +const stateSubmitterAuthFile = "playwright/.auth/state-user.json"; + +setup("authenticate state submitter", async ({ page, context }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + + await loginPage.login(testUsers.state, password); + await context.storageState({ path: stateSubmitterAuthFile }); +}); + +const reviewerAuthFile = "playwright/.auth/reviewer-user.json"; + +setup("authenticate cms reviewer", async ({ page, context }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + + await loginPage.login(testUsers.reviewer, password); + await context.storageState({ path: reviewerAuthFile }); +}); diff --git a/src/services/ui/e2e/utils/users.ts b/src/services/ui/e2e/utils/users.ts index d80170240d..1076ba94d1 100644 --- a/src/services/ui/e2e/utils/users.ts +++ b/src/services/ui/e2e/utils/users.ts @@ -1,4 +1,4 @@ export const testUsers = { - state: "george@example.com", - cmsAdmin: "cmsadmin@example.com" -}; \ No newline at end of file + state: "george@example.com", + reviewer: "reviewer@example.com", +}; diff --git a/src/services/ui/package.json b/src/services/ui/package.json index e5936db5f4..e736f46680 100644 --- a/src/services/ui/package.json +++ b/src/services/ui/package.json @@ -72,6 +72,7 @@ "zod": "^3.22.3" }, "devDependencies": { + "@axe-core/playwright": "^4.8.3", "@playwright/test": "^1.38.0", "@tailwindcss/typography": "^0.5.10", "@testing-library/jest-dom": "^5.16.5", diff --git a/src/services/ui/playwright.config.ts b/src/services/ui/playwright.config.ts index 61159b6c7d..3ffb614799 100644 --- a/src/services/ui/playwright.config.ts +++ b/src/services/ui/playwright.config.ts @@ -33,18 +33,19 @@ export default defineConfig({ /* Configure projects for major browsers */ // Note: we can test on multiple browsers and resolutions defined here projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - { - name: "firefox", - use: { ...devices["Desktop Firefox"] }, - }, + // Setup project + { name: "setup", testMatch: /.*\.setup\.ts/, fullyParallel: true }, { - name: "webkit", - use: { ...devices["Desktop Safari"] }, + // we can have different projects for different users/use cases + name: "logged in state user", + use: { + ...devices["Desktop Chrome"], + // Use prepared auth state for state submitter. + storageState: "playwright/.auth/state-user.json", + }, + // Tests start already authenticated because we specified storageState in the config. + dependencies: ["setup"], }, ], }); diff --git a/src/services/ui/src/components/HowItWorks/index.tsx b/src/services/ui/src/components/HowItWorks/index.tsx index bec8e16992..e323cc4eef 100644 --- a/src/services/ui/src/components/HowItWorks/index.tsx +++ b/src/services/ui/src/components/HowItWorks/index.tsx @@ -1,7 +1,7 @@ export const HowItWorks = ({ children }: React.PropsWithChildren) => { return (
-

How it works

+

How it works

{children}
); diff --git a/src/services/ui/src/components/Layout/index.tsx b/src/services/ui/src/components/Layout/index.tsx index ea86f87fe8..4779de9389 100644 --- a/src/services/ui/src/components/Layout/index.tsx +++ b/src/services/ui/src/components/Layout/index.tsx @@ -57,7 +57,7 @@ const UserDropdownMenu = () => { asChild className="hover:text-white/70 p-4 data-[state=open]:bg-white data-[state=open]:text-primary" > -
+
+ { return (
-
+
+
diff --git a/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx b/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx index 6d6c71d7d8..3d6509a2e3 100644 --- a/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx +++ b/src/services/ui/src/components/Opensearch/main/Filtering/index.tsx @@ -18,7 +18,7 @@ export const OsFiltering: FC<{ return (
-

+

{"Search by Package ID, CPOC Name, or Submitter Name"}

diff --git a/src/services/ui/src/components/Pagination/index.tsx b/src/services/ui/src/components/Pagination/index.tsx index f326bf4dd8..8307b572f5 100644 --- a/src/services/ui/src/components/Pagination/index.tsx +++ b/src/services/ui/src/components/Pagination/index.tsx @@ -63,6 +63,7 @@ export const Pagination: FC = (props) => { viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" + display="none" > = (props) => { {state.pageRange.map((PAGE) => { if (Array.isArray(PAGE)) return ( - +
); const isActive = props.pageNumber === PAGE - 1; @@ -132,6 +133,7 @@ export const Pagination: FC = (props) => { viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" + display="none" > (props: {
- +

{props.document.header} - +

{props.document.sections.map((SEC, index) => ( { const hops = props as RHFComponentMap["Input"]; - return ; + return ; })()} {/* ----------------------------------------------------------------------------- */} {rhf === "Textarea" && (() => { const hops = props as RHFComponentMap["Textarea"]; - return