Files
authentik/web/test/browser/roles.test.ts
Teffen Ellis b6496950bf web: Close modal on route navigation (#21622)
* Close dialog on navigation.

* web: update dialog, form, and sidebar styles with logical properties and scroll shadows

Migrate dialog padding CSS variables from physical (top/right/bottom/left) to logical
(block-start/inline-end/block-end/inline-start) naming. Add scroll shadow utility
class (ak-m-scroll-shadows) for scrollable regions. Rework radio input and form
control styles including transparent backgrounds, checkbox-style indicators, and
improved hover states. Refactor FormGroup marker to use CSS custom properties for
open/closed states. Move sidebar padding from nav container to scrollable list.

* web: refine elements and components for accessibility, type safety, and consistency

Add ARIA role and label to dialog body, apply scroll shadow classes to modal body,
sidebar nav, and wizard main. Update ak-status-label to support tri-state
(good/bad/null) rendering with ts-pattern matching and a neutral label. Simplify
FormGroup by removing wrapper div around default slot, adding part attributes for
header styling, and changing description to nullable type. Clean up LogViewer and
StaticTable with proper access modifiers, override annotations, and nullable item
types. Simplify ak-switch-input checked binding and remove unused slot attribute
from ak-radio-input help text.

* web: modernize application pages with modalInvoker and updated form patterns

Refactor ApplicationCheckAccessForm to use static form metadata properties
(verboseName, submitVerb, createLabel), formatAPISuccessMessage, and a private
CoreApi instance. Migrate ApplicationViewPage from ak-forms-modal slots to the
modalInvoker directive for both edit and check-access actions. Accept nullable
input in createPaginatedResponse for better null-safety. Fix casing of dropdown
menu items in ApplicationListPage.

* web: migrate remaining view pages to modalInvoker (#21592)

* Fix visibility check, search params.

* Add scroll shadow.

* Partial revert of input layout.

* Tidy groups.

* Fix check access form invoker, styles.

* Optional sizing.

* Lowercase

* Revise checkbox style.

* Close dialog on navigation.

* Fix padding.

* Touch up shadow heights.

* Migrate remaining view pages to modalInvoker, add e2e coverage.

* Fix alignment.

* Fix click handler, add placeholders.

* Fix issue where form field is not serialized.
2026-04-16 19:04:29 +02:00

173 lines
5.8 KiB
TypeScript

import { expect, test } from "#e2e";
import { randomName } from "#e2e/utils/generators";
import { IDGenerator } from "@goauthentik/core/id";
test.describe("Roles", () => {
const roleNames = new Map<string, string>();
//#region Lifecycle
test.beforeEach("Prepare role", async ({ session }, { testId }) => {
const seed = IDGenerator.randomID(6);
const roleName = `${randomName(seed)} (${seed})`;
roleNames.set(testId, roleName);
await test.step("Authenticate", async () => {
await session.login({
to: "/if/admin/#/identity/roles",
});
});
});
//#endregion
//#region Tests
test("Simple role", async ({ form, pointer, page }, testInfo) => {
const roleName = roleNames.get(testInfo.testId)!;
const { fill, search } = form;
const { click } = pointer;
const dialog = page.getByRole("dialog", { name: "New Role" });
await test.step("Create role", async () => {
await expect(dialog, "Dialog is initially closed").toBeHidden();
await click("New Role", "button");
await expect(dialog, "Dialog opens").toBeVisible();
await fill(/^Role Name/, roleName, dialog);
await dialog.getByRole("button", { name: "Create Role" }).click();
await expect(dialog, "Dialog closes after creating role").toBeHidden();
});
await test.step("Verify role creation", async () => {
const $role = await search(roleName);
await expect($role, "Role is visible in the table").toBeVisible();
});
});
test("Edit role", async ({ form, pointer, page }, testInfo) => {
const roleName = roleNames.get(testInfo.testId)!;
const { fill, search } = form;
const { click } = pointer;
const newRoleDialog = page.getByRole("dialog", { name: "New Role" });
const editRoleDialog = page.getByRole("dialog", { name: "Edit Role" });
await test.step("Create role", async () => {
await click("New Role", "button");
await expect(newRoleDialog, "Dialog opens").toBeVisible();
await fill(/^Role Name/, roleName, newRoleDialog);
await newRoleDialog.getByRole("button", { name: "Create Role" }).click();
await expect(newRoleDialog, "Dialog closes after creating role").toBeHidden();
});
const updatedName = `${roleName} Edited`;
await test.step("Edit role via row action", async () => {
const $role = await search(roleName);
await expect($role, "Role is visible").toBeVisible();
await $role.getByRole("button", { name: "Edit" }).click();
await expect(editRoleDialog, "Edit dialog opens").toBeVisible();
const nameInput = editRoleDialog.getByRole("textbox", { name: /Role Name/ });
await expect(nameInput, "Name input is visible").toBeVisible();
await expect(nameInput, "Name is pre-filled").toHaveValue(roleName);
await nameInput.fill(updatedName);
await editRoleDialog.getByRole("button", { name: "Save Changes" }).click();
await expect(editRoleDialog, "Edit dialog closes after saving").toBeHidden();
});
await test.step("Verify role was updated", async () => {
const $updatedRole = await search(updatedName);
await expect($updatedRole, "Updated role is visible in the table").toBeVisible();
});
});
test("Edit role from view page", async ({ navigator, form, pointer, page }, testInfo) => {
const roleName = roleNames.get(testInfo.testId)!;
const { fill, search } = form;
const { click } = pointer;
const newRoleDialog = page.getByRole("dialog", { name: "New Role" });
const editRoleDialog = page.getByRole("dialog", { name: "Edit Role" });
await test.step("Create role", async () => {
await click("New Role", "button");
await expect(newRoleDialog, "Dialog opens").toBeVisible();
await fill(/^Role Name/, roleName, newRoleDialog);
await newRoleDialog.getByRole("button", { name: "Create Role" }).click();
await expect(newRoleDialog, "Dialog closes after creating role").toBeHidden();
});
await test.step("Navigate to role view page", async () => {
const $role = await search(roleName);
await expect($role, "Role is visible").toBeVisible();
const viewLink = $role.getByRole("link", { name: "view details" });
await expect(viewLink, "View details link is visible").toBeVisible();
const viewURL = await viewLink.evaluate((el: HTMLAnchorElement) => el.href);
await navigator.navigate(viewURL);
});
const updatedName = `${roleName} View Edited`;
await test.step("Edit role from view page", async () => {
await expect(editRoleDialog, "Edit dialog is initially closed").toBeHidden();
await click("Edit", "button");
await expect(editRoleDialog, "Edit dialog opens").toBeVisible();
const nameInput = editRoleDialog.getByRole("textbox", { name: /Role Name/ });
await expect(nameInput, "Name input is visible").toBeVisible();
await expect(nameInput, "Name is pre-filled").toHaveValue(roleName);
await nameInput.fill(updatedName);
await editRoleDialog.getByRole("button", { name: "Save Changes" }).click();
await expect(editRoleDialog, "Edit dialog closes after saving").toBeHidden();
});
await test.step("Verify role name updated on view page", async () => {
await expect(
page.getByText(updatedName),
"Updated role name is visible on view page",
).toBeVisible();
});
});
//#endregion
});