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

feat(508-e2e): add auth setup and init 508 axe tests #332

Merged
merged 8 commits into from
Jan 23, 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ build_run
.build
.turbo
coverage
lambda_layer.zip
lambda_layer.zip
**/.auth/*.json
1 change: 1 addition & 0 deletions src/services/ui/e2e/pages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./loginPage";
21 changes: 21 additions & 0 deletions src/services/ui/e2e/pages/loginPage.ts
Original file line number Diff line number Diff line change
@@ -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: "[email protected]" }).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();
}
}
46 changes: 46 additions & 0 deletions src/services/ui/e2e/tests/a11y/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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([]);
});
}
});
27 changes: 0 additions & 27 deletions src/services/ui/e2e/tests/home/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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("/");
Expand All @@ -32,25 +20,10 @@ 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: "[email protected]" })
.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
.getByRole("link", { name: "Dashboard" })
.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: "[email protected]" }).fill(".");
await page.getByRole("textbox", { name: "Password" }).fill(password);
await page.getByRole("button", { name: "submit" }).click();
await page.locator("#loginErrorMessage").first().isVisible();
});
35 changes: 35 additions & 0 deletions src/services/ui/e2e/utils/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -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 });
});
6 changes: 3 additions & 3 deletions src/services/ui/e2e/utils/users.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const testUsers = {
state: "[email protected]",
cmsAdmin: "cmsadmin@example.com"
};
state: "[email protected]",
reviewer: "reviewer@example.com",
};
1 change: 1 addition & 0 deletions src/services/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 11 additions & 10 deletions src/services/ui/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
],
});
2 changes: 1 addition & 1 deletion src/services/ui/src/components/HowItWorks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const HowItWorks = ({ children }: React.PropsWithChildren) => {
return (
<div className="py-8 px-6 border border-gray-300 rounded-md border-solid w-full">
<h4 className="text-bold text-xl">How it works</h4>
<h3 className="text-bold text-xl">How it works</h3>
{children}
</div>
);
Expand Down
10 changes: 5 additions & 5 deletions src/services/ui/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const UserDropdownMenu = () => {
asChild
className="hover:text-white/70 p-4 data-[state=open]:bg-white data-[state=open]:text-primary"
>
<div className="flex flex-row gap-4 items-center cursor-pointer">
<button className="flex flex-row gap-4 items-center cursor-pointer">
<p className="flex">My Account</p>
<svg
xmlns="http://www.w3.org/2000/svg"
Expand All @@ -73,7 +73,7 @@ const UserDropdownMenu = () => {
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</div>
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
Expand Down Expand Up @@ -108,20 +108,20 @@ export const Layout = () => {
return (
<div className="min-h-full flex flex-col">
<UsaBanner />
<div className="bg-primary">
<nav className="bg-primary">
<div className="max-w-screen-xl mx-auto px-4 lg:px-8">
<div className="h-[70px] flex gap-12 items-center text-white">
<Link to="/">
<img
className="h-10 w-28 min-w-[112px] resize-none"
src={oneMacLogo}
alt="One Mac Site Logo"
alt="onemac site logo"
/>
</Link>
<ResponsiveNav isDesktop={isDesktop} />
</div>
</div>
</div>
</nav>
<main className="flex-1">
<Outlet />
</main>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const OsFiltering: FC<{

return (
<div>
<p className="mb-1 text-sm text-slate-400">
<p className="mb-1 text-sm">
{"Search by Package ID, CPOC Name, or Submitter Name"}
</p>
<div className="flex flex-row content-between gap-2 mb-4">
Expand Down
6 changes: 4 additions & 2 deletions src/services/ui/src/components/Pagination/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const Pagination: FC<Props> = (props) => {
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
display="none"
>
<path
fillRule="evenodd"
Expand All @@ -75,7 +76,7 @@ export const Pagination: FC<Props> = (props) => {
{state.pageRange.map((PAGE) => {
if (Array.isArray(PAGE))
return (
<button
<div
key={`PAGE-${PAGE}`}
className="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 focus:outline-offset-0 cursor-pointer"
id="morePagesButton"
Expand All @@ -97,7 +98,7 @@ export const Pagination: FC<Props> = (props) => {
</option>
))}
</select>
</button>
</div>
);

const isActive = props.pageNumber === PAGE - 1;
Expand Down Expand Up @@ -132,6 +133,7 @@ export const Pagination: FC<Props> = (props) => {
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
display="none"
>
<path
fillRule="evenodd"
Expand Down
4 changes: 2 additions & 2 deletions src/services/ui/src/components/RHF/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export const RHFDocument = <TFieldValues extends FieldValues>(props: {
<div className="h-[5px] bg-gradient-to-r from-primary from-50% to-[#02bfe7] to-[66%] rounded-t"></div>
<div className="py-4 px-8 border-2 border-t-0 mt-0">
<div className="mb-3 mt-9">
<FormLabel className="font-bold text-4xl px-8 inline-block leading-[48px]">
<h1 className="font-bold text-4xl px-8 inline-block leading-[48px]">
{props.document.header}
</FormLabel>
</h1>
</div>
{props.document.sections.map((SEC, index) => (
<RHFSection
Expand Down
17 changes: 12 additions & 5 deletions src/services/ui/src/components/RHF/Slot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,23 @@ export const RHFSlot = <
{rhf === "Input" &&
(() => {
const hops = props as RHFComponentMap["Input"];
return <Input {...hops} {...field} />;
return <Input {...hops} {...field} aria-label={field.name} />;
})()}

{/* ----------------------------------------------------------------------------- */}
{rhf === "Textarea" &&
(() => {
const hops = props as RHFComponentMap["Textarea"];
return <Textarea {...hops} {...field} />;
return (
<Textarea {...hops} {...field} aria-label={field.name} />
);
})()}

{/* ----------------------------------------------------------------------------- */}
{rhf === "Switch" &&
(() => {
const hops = props as RHFComponentMap["Switch"];
return <Switch {...hops} {...field} />;
return <Switch {...hops} {...field} aria-label={field.name} />;
})()}

{/* ----------------------------------------------------------------------------- */}
Expand All @@ -115,7 +117,7 @@ export const RHFSlot = <
onValueChange={field.onChange}
defaultValue={field.value}
>
<SelectTrigger {...hops}>
<SelectTrigger {...hops} aria-label={field.name}>
<SelectValue {...hops} />
</SelectTrigger>
<SelectContent className="overflow-auto max-h-60">
Expand Down Expand Up @@ -143,7 +145,11 @@ export const RHFSlot = <
return (
<div key={`OPT-${OPT.value}`} className="flex flex-col">
<div className="flex gap-2 items-center">
<RadioGroupItem value={OPT.value} id={OPT.value} />
<RadioGroupItem
value={OPT.value}
id={OPT.value}
aria-label={OPT.value}
/>
{
<FormLabel
className="font-normal"
Expand Down Expand Up @@ -214,6 +220,7 @@ export const RHFSlot = <
dependency={OPT.dependency}
parentValue={field.value}
changeMethod={field.onChange}
aria-label={field.name}
/>
{field.value?.includes(OPT.value) &&
!!OPT.slots &&
Expand Down
Loading
Loading