mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
web: Fix numeric values in search select inputs, search input fixes (#16928)
* web: Fix numeric values in search select inputs. * web: Fix ARIA attributes on menu items. * web: Fix issues surrounding nested modal actions, selectors, labels. * web: Prepare group forms for testing, ARIA, etc. * web: Clarify when spinner buttons are busy. * web: Fix dark theme toggle input visibility. * web: Fix issue where tests complete before optional search inputs load. * web: Add user creation tests, group creation. Flesh out fixtures.
This commit is contained in:
@@ -78,6 +78,47 @@ export class FormFixture extends PageFixture {
|
||||
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.
|
||||
*
|
||||
|
||||
56
web/e2e/fixtures/NavigatorFixture.ts
Normal file
56
web/e2e/fixtures/NavigatorFixture.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export const GOOD_USERNAME = "test-admin@goauthentik.io";
|
||||
export const GOOD_PASSWORD = "test-runner";
|
||||
|
||||
export const BAD_USERNAME = "bad-username@bad-login.io";
|
||||
export const BAD_PASSWORD = "-this-is-a-bad-password-";
|
||||
|
||||
export interface LoginInit {
|
||||
username?: string;
|
||||
password?: string;
|
||||
to?: URL | string;
|
||||
}
|
||||
|
||||
export class NavigatorFixture extends PageFixture {
|
||||
static fixtureName = "Navigator";
|
||||
|
||||
constructor(page: Page, testName: string) {
|
||||
super({ page, testName });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the current page to navigate to the given pathname.
|
||||
*
|
||||
* This method is useful to verify that a navigation has completed after an action
|
||||
* automatically updates the URL, such as form submissions or link clicks.
|
||||
*
|
||||
* @see {@linkcode navigate} for navigation.
|
||||
*
|
||||
* @param to The pathname or URL to wait for.
|
||||
*/
|
||||
public waitForPathname = async (to: string | URL): Promise<void> => {
|
||||
const expectedPathname = typeof to === "string" ? to : to.pathname;
|
||||
|
||||
this.logger.info(`Waiting for URL to change to ${expectedPathname}`);
|
||||
|
||||
await this.page.waitForURL(`**${expectedPathname}**`);
|
||||
|
||||
this.logger.info(`URL changed to ${this.page.url()}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to the given URL or pathname, and wait for the navigation to complete.
|
||||
*/
|
||||
public navigate = async (to: URL | string | null | undefined): Promise<void> => {
|
||||
if (!to) {
|
||||
throw new TypeError("No URL or pathname given to navigate to.");
|
||||
}
|
||||
|
||||
await this.page.goto(to.toString());
|
||||
|
||||
await this.waitForPathname(to);
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { ConsoleLogger, FixtureLogger } from "#logger/node";
|
||||
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export interface PageFixtureOptions {
|
||||
export interface PageFixtureInit {
|
||||
page: Page;
|
||||
testName: string;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export abstract class PageFixture {
|
||||
protected readonly page: Page;
|
||||
protected readonly testName: string;
|
||||
|
||||
constructor({ page, testName }: PageFixtureOptions) {
|
||||
constructor({ page, testName }: PageFixtureInit) {
|
||||
this.page = page;
|
||||
this.testName = testName;
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ export type ClickByRole = (
|
||||
export class PointerFixture extends PageFixture {
|
||||
public static fixtureName = "Pointer";
|
||||
|
||||
/**
|
||||
* A high-level click function that simplifies clicking on buttons and links.
|
||||
*/
|
||||
public click = (
|
||||
name: string | RegExp,
|
||||
optionsOrRole?: ARIAOptions | ARIARole,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PageFixture } from "#e2e/fixtures/PageFixture";
|
||||
|
||||
import { Page } from "@playwright/test";
|
||||
import { NavigatorFixture } from "#e2e/fixtures/NavigatorFixture";
|
||||
import { PageFixture, PageFixtureInit } from "#e2e/fixtures/PageFixture";
|
||||
|
||||
export const GOOD_USERNAME = "test-admin@goauthentik.io";
|
||||
export const GOOD_PASSWORD = "test-runner";
|
||||
@@ -14,11 +13,17 @@ export interface LoginInit {
|
||||
to?: URL | string;
|
||||
}
|
||||
|
||||
export interface SessionFixtureInit extends PageFixtureInit {
|
||||
navigator: NavigatorFixture;
|
||||
}
|
||||
|
||||
export class SessionFixture extends PageFixture {
|
||||
static fixtureName = "Session";
|
||||
|
||||
public static readonly pathname = "/if/flow/default-authentication-flow/";
|
||||
|
||||
protected navigator: NavigatorFixture;
|
||||
|
||||
//#region Selectors
|
||||
|
||||
public $identificationStage = this.page.locator("ak-stage-identification");
|
||||
@@ -46,8 +51,9 @@ export class SessionFixture extends PageFixture {
|
||||
|
||||
//#endregion
|
||||
|
||||
constructor(page: Page, testName: string) {
|
||||
constructor({ page, testName, navigator }: SessionFixtureInit) {
|
||||
super({ page, testName });
|
||||
this.navigator = navigator;
|
||||
}
|
||||
|
||||
//#region Specific interactions
|
||||
@@ -89,13 +95,7 @@ export class SessionFixture extends PageFixture {
|
||||
|
||||
await this.$submitButton.click();
|
||||
|
||||
const expectedPathname = typeof to === "string" ? to : to.pathname;
|
||||
|
||||
this.logger.info(`Waiting for URL to change to ${expectedPathname}`);
|
||||
|
||||
await this.page.waitForURL(`**${expectedPathname}**`);
|
||||
|
||||
this.logger.info(`URL changed to ${this.page.url()}`);
|
||||
await this.navigator.waitForPathname(to);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { FormFixture } from "#e2e/fixtures/FormFixture";
|
||||
import { NavigatorFixture } from "#e2e/fixtures/NavigatorFixture";
|
||||
import { PointerFixture } from "#e2e/fixtures/PointerFixture";
|
||||
import { SessionFixture } from "#e2e/fixtures/SessionFixture";
|
||||
|
||||
@@ -13,6 +14,7 @@ export { expect } from "@playwright/test";
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
interface E2EFixturesTestScope {
|
||||
navigator: NavigatorFixture;
|
||||
session: SessionFixture;
|
||||
pointer: PointerFixture;
|
||||
form: FormFixture;
|
||||
@@ -23,15 +25,19 @@ interface E2EWorkerScope {
|
||||
}
|
||||
|
||||
export const test = base.extend<E2EFixturesTestScope, E2EWorkerScope>({
|
||||
session: async ({ page }, use, { title }) => {
|
||||
await use(new SessionFixture(page, title));
|
||||
navigator: async ({ page }, use, { title }) => {
|
||||
await use(new NavigatorFixture(page, title));
|
||||
},
|
||||
|
||||
session: async ({ page, navigator }, use, { title: testName }) => {
|
||||
await use(new SessionFixture({ page, testName, navigator }));
|
||||
},
|
||||
|
||||
form: async ({ page }, use, { title }) => {
|
||||
await use(new FormFixture(page, title));
|
||||
},
|
||||
|
||||
pointer: async ({ page }, use, { title }) => {
|
||||
await use(new PointerFixture({ page, testName: title }));
|
||||
pointer: async ({ page }, use, { title: testName }) => {
|
||||
await use(new PointerFixture({ page, testName }));
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user