mirror of
https://github.com/goauthentik/authentik
synced 2026-04-27 18:07:15 +02:00
* Use project relative paths. * Fix tests. * Fix types. * Clean up admin imports. * Move admin import. * Remove or replace references to admin. * Typo fix. * Flesh out ak-modal, about modal. * Flesh out lazy modal. * Fix portal elements not using dialog scope. * Fix url parameters, wizards. * Fix invokers, lazy load. * Fix theming. * Add placeholders, help. * Flesh out command palette. Flesh out styles, command invokers. Continue clean up. Allow slotted content. Flesh out. * Flesh out edit invoker. Prep groups. * Fix odd labeling, legacy situations. * Prepare deprecation of table modal. Clean up serialization. * Tidy types. * Port provider select modal. * Port member select form. * Flesh out role modal. Fix loading state. * Port user group form. * Fix spellcheck. * Fix dialog detection. * Revise types. * Port rac launch modal. * Remove deprecated table modal. * Consistent form action placement. * Consistent casing. * Consistent alignment. * Use more appropriate description. * Flesh out icon. Fix alignment, colors. * Flesh out user search. * Consistent save button. * Clean up labels. * Reduce warning noise. * Clean up label. * Use attribute e2e expects. * Use directive. Fix lifecycle * Fix frequent un-memoized entries. * Fix up closedBy detection. * Tidy alignment. * Fix types, composition. * Fix labels, tests. * Fix up impersonation, labels. * Flesh out. Fix refresh after submit. * Flesh out basic modal test. * Fix ARIA. * Flesh out roles test. * Revise selectors. * Clean up selectors. * Fix impersonation labels, form references. * Fix messages appearing under modals. * Ensure reason is parsed. * Flesh out impersonation test. * Flesh out impersonate test. * Flesh out application tests. Clean up toolbar header, ARIA. * Flesh out wizard test. * Refine weight, order. * Fix up initial values, selectors. * Fix tests. * Fix selector.
251 lines
7.0 KiB
TypeScript
251 lines
7.0 KiB
TypeScript
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
|
import type { LocatorContext } from "#e2e/selectors/types";
|
|
|
|
import { expect, Locator, Page } from "@playwright/test";
|
|
|
|
export class FormFixture extends PageFixture {
|
|
static fixtureName = "Form";
|
|
|
|
//#region Selector Methods
|
|
|
|
//#endregion
|
|
|
|
//#region Field Methods
|
|
|
|
/**
|
|
* Set the value of a text input.
|
|
*
|
|
* @param fieldName The name of the form element.
|
|
* @param value the value to set.
|
|
*/
|
|
public findTextualInput = async (
|
|
fieldName: string | RegExp,
|
|
context: LocatorContext = this.page,
|
|
) => {
|
|
const control = context
|
|
.getByLabel(fieldName, { exact: true })
|
|
.filter({
|
|
hasNot: context.getByRole("presentation"),
|
|
})
|
|
.and(context.locator(":not(button)"))
|
|
.or(
|
|
context.getByRole("textbox", {
|
|
name: fieldName,
|
|
}),
|
|
)
|
|
.or(
|
|
context.getByRole("spinbutton", {
|
|
name: fieldName,
|
|
}),
|
|
);
|
|
|
|
const role = await control.getAttribute("role");
|
|
|
|
if (role === "combobox") {
|
|
// Comboboxes, such as our Query Language input need additional handling...
|
|
const textbox = control.getByRole("textbox");
|
|
|
|
return textbox;
|
|
}
|
|
|
|
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
|
|
|
return control;
|
|
};
|
|
|
|
/**
|
|
* Set the value of a text input.
|
|
*
|
|
* @param target The name of the form element.
|
|
* @param value the value to set.
|
|
*/
|
|
public fill = async (
|
|
target: string | RegExp | Locator,
|
|
value: string,
|
|
context: LocatorContext = this.page,
|
|
): Promise<void> => {
|
|
let control: Locator;
|
|
|
|
if (typeof target === "string" || target instanceof RegExp) {
|
|
control = await this.findTextualInput(target, context);
|
|
} else {
|
|
control = target;
|
|
}
|
|
|
|
await control.fill(value);
|
|
};
|
|
|
|
/**
|
|
* Search for a row containing the given text.
|
|
*/
|
|
public search = async (
|
|
query: string,
|
|
context: LocatorContext = this.page,
|
|
): Promise<Locator> => {
|
|
const searchInput = await this.findTextualInput(/search/i, context);
|
|
// We have to wait for the user to appear in the table,
|
|
// but several UI elements will be rendered asynchronously.
|
|
// We attempt several times to find the user to avoid flakiness.
|
|
|
|
const tries = 10;
|
|
let found = false;
|
|
|
|
for (let i = 0; i < tries; i++) {
|
|
await this.fill(searchInput, query);
|
|
await searchInput.press("Enter");
|
|
|
|
const $rowEntry = context.getByRole("row", {
|
|
name: query,
|
|
});
|
|
|
|
this.logger.info(`${i + 1}/${tries} Waiting for "${query}" to appear in the table`);
|
|
|
|
found = await $rowEntry
|
|
.waitFor({
|
|
timeout: 1500,
|
|
})
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
|
|
if (found) {
|
|
this.logger.info(`"${query}" found in the table`);
|
|
return $rowEntry;
|
|
}
|
|
}
|
|
|
|
throw new Error(`"${query}" not found in the table`);
|
|
};
|
|
|
|
/**
|
|
* Set the value of a radio or checkbox input.
|
|
*
|
|
* @param fieldName The name of the form element.
|
|
* @param value the value to set.
|
|
*/
|
|
public setInputCheck = async (
|
|
fieldName: string,
|
|
value: boolean = true,
|
|
parent: LocatorContext = this.page,
|
|
): Promise<void> => {
|
|
const control = parent.locator("ak-switch-input", {
|
|
hasText: fieldName,
|
|
});
|
|
|
|
await control.scrollIntoViewIfNeeded();
|
|
|
|
await expect(control, `Field (${fieldName}) should be visible`).toBeVisible();
|
|
|
|
const currentChecked = await control
|
|
.getAttribute("checked")
|
|
.then((value) => value !== null);
|
|
|
|
if (currentChecked === value) {
|
|
return;
|
|
}
|
|
|
|
await control.click();
|
|
};
|
|
|
|
/**
|
|
* Set the value of a radio or checkbox input.
|
|
*
|
|
* @param fieldName The name of the form element.
|
|
* @param pattern the value to set.
|
|
*/
|
|
public setRadio = async (
|
|
groupName: string,
|
|
fieldName: string,
|
|
parent: LocatorContext = this.page,
|
|
): Promise<void> => {
|
|
const group = parent.getByRole("radiogroup", { name: groupName });
|
|
|
|
await expect(group, `Field "${groupName}" should be visible`).toBeVisible();
|
|
const control = parent.getByRole("radio", { name: fieldName });
|
|
|
|
await control.setChecked(true);
|
|
};
|
|
|
|
/**
|
|
* Set the value of a search select input.
|
|
*
|
|
* @param fieldLabel The name of the search select element.
|
|
* @param pattern The text to match against the search select entry.
|
|
*/
|
|
public selectSearchValue = async (
|
|
fieldLabel: string,
|
|
pattern: string | RegExp,
|
|
parent: LocatorContext = this.page,
|
|
): Promise<void> => {
|
|
const control = parent.getByRole("textbox", { name: fieldLabel });
|
|
|
|
await expect(
|
|
control,
|
|
`Search select control (${fieldLabel}) should be visible`,
|
|
).toBeVisible();
|
|
|
|
const fieldName = await control.getAttribute("name");
|
|
|
|
if (!fieldName) {
|
|
throw new Error(`Unable to find name attribute on search select (${fieldLabel})`);
|
|
}
|
|
|
|
// Find the search select input control and activate it.
|
|
await control.click();
|
|
|
|
if (typeof pattern === "string") {
|
|
this.fill(control, pattern, parent);
|
|
}
|
|
|
|
const button = this.page
|
|
// ---
|
|
.locator(`div[data-managed-for*="${fieldName}"] button`, {
|
|
hasText: pattern,
|
|
});
|
|
|
|
await expect(button, `Search select entry (${pattern}) should be visible`).toBeVisible();
|
|
|
|
await button.click();
|
|
await this.page.keyboard.press("Tab");
|
|
await control.blur();
|
|
};
|
|
|
|
public setFormGroup = async (
|
|
pattern: string | RegExp,
|
|
value: boolean = true,
|
|
parent: LocatorContext = this.page,
|
|
) => {
|
|
const control = parent
|
|
.locator("ak-form-group", {
|
|
hasText: pattern,
|
|
})
|
|
.first();
|
|
|
|
const currentOpen = await control.getAttribute("open").then((value) => value !== null);
|
|
|
|
if (currentOpen === value) {
|
|
this.logger.debug(`Form group ${pattern} is already ${value ? "open" : "closed"}`);
|
|
return;
|
|
}
|
|
|
|
this.logger.debug(`Toggling form group ${pattern} to ${value ? "open" : "closed"}`);
|
|
|
|
await control.click();
|
|
|
|
if (value) {
|
|
await expect(control).toHaveAttribute("open");
|
|
} else {
|
|
await expect(control).not.toHaveAttribute("open");
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
|
|
//#region Lifecycle
|
|
|
|
constructor(page: Page, testName: string) {
|
|
super({ page, testName });
|
|
}
|
|
|
|
//#endregion
|
|
}
|