From f98336267d37869dc854134bfbbd4c2f635fbc86 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 19 Dec 2024 14:03:15 +0100 Subject: [PATCH] added axe tests for flows Signed-off-by: Erik Jan de Wit --- js/apps/admin-ui/package.json | 5 +- js/apps/admin-ui/test/autentication/flow.ts | 50 +++++- .../admin-ui/test/autentication/flows.spec.ts | 156 ++++++++++++------ js/apps/admin-ui/test/utils/form.ts | 2 +- js/pnpm-lock.yaml | 19 ++- 5 files changed, 177 insertions(+), 55 deletions(-) diff --git a/js/apps/admin-ui/package.json b/js/apps/admin-ui/package.json index c4728029c06b..f4f14c6d072c 100644 --- a/js/apps/admin-ui/package.json +++ b/js/apps/admin-ui/package.json @@ -88,6 +88,7 @@ } }, "dependencies": { + "@axe-core/playwright": "^4.10.1", "@keycloak/keycloak-admin-client": "workspace:*", "@keycloak/keycloak-ui-shared": "workspace:*", "@patternfly/patternfly": "^5.4.2", @@ -134,10 +135,10 @@ "ldap-server-mock": "^6.0.1", "lightningcss": "^1.28.2", "ts-node": "^10.9.2", + "uuid": "^11.0.3", "vite": "^6.0.3", "vite-plugin-checker": "^0.8.0", "vite-plugin-dts": "^4.3.0", - "vitest": "^2.1.8", - "uuid": "^11.0.3" + "vitest": "^2.1.8" } } diff --git a/js/apps/admin-ui/test/autentication/flow.ts b/js/apps/admin-ui/test/autentication/flow.ts index 8bb83d50e6a4..30e028e1e526 100644 --- a/js/apps/admin-ui/test/autentication/flow.ts +++ b/js/apps/admin-ui/test/autentication/flow.ts @@ -1,5 +1,7 @@ import { Page, expect } from "@playwright/test"; import { confirmModal } from "../utils/modal"; +import AxeBuilder from "@axe-core/playwright"; +import { selectItem } from "../utils/form"; export async function fillDuplicateFlowModal( page: Page, @@ -30,6 +32,29 @@ export async function goToRequiredActions(page: Page) { await page.getByTestId("requiredActions").click(); } +export async function goToPoliciesTab(page: Page) { + await page.getByTestId("policies").click(); +} + +export async function goToOTPPolicyTab(page: Page) { + await goToPoliciesTab(page); + await page.getByTestId("otpPolicy").click(); +} + +export async function goToWebAuthnTab(page: Page) { + await goToPoliciesTab(page); + await page.getByTestId("webauthnPolicy").click(); +} + +export async function goToCIBAPolicyTab(page: Page) { + await goToPoliciesTab(page); + await page.getByTestId("tab-ciba-policy").click(); +} + +export async function addPolicy(page: Page, value) { + await selectItem(page, page.getByTestId("add-policy"), value); +} + const toKey = (name: string) => { return name.replace(/\s/g, "-"); }; @@ -54,12 +79,33 @@ export async function assertSwitchPolicyChecked( page: Page, policyName: string, ) { - expect(getEnabledSwitch(page, policyName)).toBeChecked(); + await expect(getEnabledSwitch(page, policyName)).toBeChecked(); } export async function assertDefaultSwitchPolicyEnabled( page: Page, policyName: string, ) { - expect(getDefaultSwitch(page, policyName)).toBeEnabled(); + await expect(getDefaultSwitch(page, policyName)).toBeEnabled(); +} + +export async function goToCreateItem(page: Page) { + await page.getByRole("link", { name: "Create flow" }).click(); +} + +export async function assertAxeViolations(page: Page) { + const { violations } = await new AxeBuilder({ page }).analyze(); + expect(violations.length, violations.map((v) => v.help).join("\n")).toBe(0); +} + +export async function fillCreateForm( + page: Page, + name: string, + description: string, + type: string, +) { + await page.getByTestId("alias").fill(name); + await page.getByTestId("description").fill(description); + await selectItem(page, page.getByLabel("Flow type"), type); + await page.getByTestId("create").click(); } diff --git a/js/apps/admin-ui/test/autentication/flows.spec.ts b/js/apps/admin-ui/test/autentication/flows.spec.ts index 188605177d96..a32f4940cb8e 100644 --- a/js/apps/admin-ui/test/autentication/flows.spec.ts +++ b/js/apps/admin-ui/test/autentication/flows.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from "@playwright/test"; import { v4 as uuidv4 } from "uuid"; import adminClient from "../../cypress/support/util/AdminClient"; -import { assertRequiredFieldError } from "../utils/form"; +import { assertRequiredFieldError, clickSaveButton } from "../utils/form"; import { login } from "../utils/login"; import { assertNotificationMessage } from "../utils/masthead"; import { goToAuthentication, goToRealm } from "../utils/sidebar"; @@ -14,13 +14,21 @@ import { } from "../utils/table"; import { addExecution, + addPolicy, + assertAxeViolations, assertDefaultSwitchPolicyEnabled, assertExecutionExists, assertSwitchPolicyChecked, clickDefaultSwitchPolicy, clickSwitchPolicy, + fillCreateForm, fillDuplicateFlowModal, + goToCIBAPolicyTab, + goToCreateItem, + goToOTPPolicyTab, + goToPoliciesTab, goToRequiredActions, + goToWebAuthnTab, } from "./flow"; test.describe("Authentication test", () => { @@ -53,7 +61,7 @@ test.describe("Authentication test", () => { }); test("Should fail duplicate with empty flow name", async ({ page }) => { - await selectRowKebab(page, "Browser"); + await selectRowKebab(page, "Direct grant"); await clickRowKebabItem(page, "Duplicate"); await fillDuplicateFlowModal(page, ""); @@ -61,7 +69,7 @@ test.describe("Authentication test", () => { }); test("Should fail duplicate with duplicated name", async ({ page }) => { - await selectRowKebab(page, "Browser"); + await selectRowKebab(page, "Direct grant"); await clickRowKebabItem(page, "Duplicate"); await fillDuplicateFlowModal(page, "browser"); @@ -73,10 +81,11 @@ test.describe("Authentication test", () => { test.describe("Flow details", () => { let flowId: string | undefined; + const flowName = "Copy of browser test"; test.beforeAll(() => adminClient.inRealm(realmName, async () => { - await adminClient.copyFlow("browser", "Copy of browser"); - flowId = (await adminClient.getFlow("Copy of browser"))!.id!; + await adminClient.copyFlow("browser", flowName); + flowId = (await adminClient.getFlow(flowName))!.id!; }), ); test.afterAll(() => @@ -84,10 +93,10 @@ test.describe("Authentication test", () => { ); test("Should add a execution", async ({ page }) => { - await clickTableRowItem(page, "Copy of browser"); + await clickTableRowItem(page, flowName); await addExecution( page, - "Copy of browser forms", + flowName + " forms", "reset-credentials-choose-user", ); @@ -153,55 +162,106 @@ test.describe("Password policies tab", () => { test.beforeEach(async ({ page }) => { await login(page); await goToAuthentication(page); - await page.click("text=Password Policy"); + await goToPoliciesTab(page); }); test("should add password policies", async ({ page }) => { await expect(page.locator('[data-testid="empty-state"]')).toBeVisible(); - await page.selectOption( - '[data-testid="policy-select"]', - "Not Recently Used", - ); - await page.click('[data-testid="save"]'); + await addPolicy(page, "Not Recently Used"); + await clickSaveButton(page); await assertNotificationMessage( page, "Password policies successfully updated", ); }); - - // Additional password policy tests... }); -// // Note: For accessibility tests, you would use the @axe-core/playwright package -// test.describe("Accessibility tests for authentication", () => { -// const realmName = "a11y-realm"; - -// test.beforeAll(async ({ request }) => { -// await request.post("/admin/realms", { -// data: { realm: realmName }, -// }); -// }); - -// test.beforeEach(async ({ page }) => { -// await login(page); -// await goToRealm(page, realmName); -// await goToAuthentication(page); -// }); - -// test.afterAll(async ({ request }) => { -// await request.delete(`/admin/realms/${realmName}`); -// }); - -// test("should pass accessibility checks on main page", async ({ page }) => { -// // Assuming you've configured axe-core -// const violations = await page.evaluate(async () => { -// // @ts-ignore -// const { axe } = window; -// const results = await axe.run(); -// return results.violations; -// }); -// expect(violations.length).toBe(0); -// }); - -// // Additional accessibility tests... -// }); +test.describe("Accessibility tests for authentication", () => { + const realmName = "a11y-realm"; + const flowName = `Flow-${uuidv4()}`; + + test.beforeAll(() => adminClient.createRealm(realmName)); + test.afterAll(() => adminClient.deleteRealm(realmName)); + + test.beforeEach(async ({ page }) => { + await login(page); + await goToRealm(page, realmName); + await goToAuthentication(page); + }); + + test("should pass accessibility checks on main page", async ({ page }) => { + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ authentication tab/ flows sub tab/ creating flow form", async ({ + page, + }) => { + await goToCreateItem(page); + + await assertAxeViolations(page); + await page.getByTestId("cancel").click(); + }); + + test("Check a11y violations on load/ authentication tab/ flows sub tab/ creating flow", async ({ + page, + }) => { + await goToCreateItem(page); + await fillCreateForm( + page, + flowName, + "Some nice description about what this flow does", + "Client flow", + ); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ authentication tab/ flows sub tab/ creating", async ({ + page, + }) => { + await clickTableRowItem(page, "reset credentials"); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ authentication tab/ required actions sub tab", async ({ + page, + }) => { + await goToRequiredActions(page); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ policies tab/ password policy sub tab", async ({ + page, + }) => { + await goToPoliciesTab(page); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ authentication tab/ policies sub tab/ adding policy", async ({ + page, + }) => { + await goToPoliciesTab(page); + await addPolicy(page, "Not Recently Used"); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ policies tab/ otp policy sub tab", async ({ + page, + }) => { + await goToOTPPolicyTab(page); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ policies tab/ WebAuthn Policies sub tab", async ({ + page, + }) => { + await goToWebAuthnTab(page); + await assertAxeViolations(page); + }); + + test("Check a11y violations on load/ policies tab/ CIBA Policy sub tab", async ({ + page, + }) => { + await goToCIBAPolicyTab(page); + await assertAxeViolations(page); + }); +}); diff --git a/js/apps/admin-ui/test/utils/form.ts b/js/apps/admin-ui/test/utils/form.ts index 2f6eee1d3037..d106e1b0f3a7 100644 --- a/js/apps/admin-ui/test/utils/form.ts +++ b/js/apps/admin-ui/test/utils/form.ts @@ -14,7 +14,7 @@ export async function assertFieldError( export async function selectItem(page: Page, field: Locator, value: string) { await field.click(); - await page.getByRole("option", { name: value }).click(); + await page.getByRole("option", { name: value, exact: true }).click(); } export async function clickSaveButton(page: Page) { diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 5d0f7433b6a1..0c294a6366ab 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -150,6 +150,9 @@ importers: apps/admin-ui: dependencies: + '@axe-core/playwright': + specifier: ^4.10.1 + version: 4.10.1(playwright-core@1.49.1) '@keycloak/keycloak-admin-client': specifier: workspace:* version: link:../../libs/keycloak-admin-client @@ -176,7 +179,7 @@ importers: version: 3.1.0(@babel/runtime@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) admin-ui: specifier: 'file:' - version: '@keycloak/keycloak-admin-ui@file:apps/admin-ui(@babel/runtime@7.26.0)(@types/react@18.3.13)(typescript@5.7.2)' + version: '@keycloak/keycloak-admin-ui@file:apps/admin-ui(@babel/runtime@7.26.0)(@types/react@18.3.13)(playwright-core@1.49.1)(typescript@5.7.2)' dagre: specifier: ^0.8.5 version: 0.8.5 @@ -568,6 +571,11 @@ packages: resolution: {integrity: sha512-kNF87HiI4omHC7VzyBZSvqOAXtMlSDRF2YX+O5ya0XKv/7/GYms1opLQ+BQ9twLLDj0WsSFX4MYg0TrinZTxTg==} engines: {node: '>= 10'} + '@axe-core/playwright@4.10.1': + resolution: {integrity: sha512-EV5t39VV68kuAfMKqb/RL+YjYKhfuGim9rgIaQ6Vntb2HgaCaau0h98Y3WEUqW1+PbdzxDtDNjFAipbtZuBmEA==} + peerDependencies: + playwright-core: '>= 1.0.0' + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -5567,6 +5575,11 @@ snapshots: '@ast-grep/napi-win32-ia32-msvc': 0.22.6 '@ast-grep/napi-win32-x64-msvc': 0.22.6 + '@axe-core/playwright@4.10.1(playwright-core@1.49.1)': + dependencies: + axe-core: 4.10.2 + playwright-core: 1.49.1 + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -6077,8 +6090,9 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@keycloak/keycloak-admin-ui@file:apps/admin-ui(@babel/runtime@7.26.0)(@types/react@18.3.13)(typescript@5.7.2)': + '@keycloak/keycloak-admin-ui@file:apps/admin-ui(@babel/runtime@7.26.0)(@types/react@18.3.13)(playwright-core@1.49.1)(typescript@5.7.2)': dependencies: + '@axe-core/playwright': 4.10.1(playwright-core@1.49.1) '@keycloak/keycloak-admin-client': link:libs/keycloak-admin-client '@keycloak/keycloak-ui-shared': link:libs/ui-shared '@patternfly/patternfly': 5.4.2 @@ -6108,6 +6122,7 @@ snapshots: - '@types/react' - encoding - immer + - playwright-core - react-native - typescript