Files
authentik/web/tests/pageobjects/page.ts
Ken Sternberg 4af6ecf629 web: Isolate the OAuth2 Provider Form into a reusable rendering function
- Pull the OAuth2 Provider Form `render()` method out into a standalone function.
  - Why: So it can be shared by both the Wizard and the Provider function. The renderer is (or at
    least, can be) a pure function: you give it input and it produces HTML, *and then it stops*.
- Provide a test harness that can test the OAuth2 provider form.
2024-10-22 07:13:04 -07:00

135 lines
5.2 KiB
TypeScript

import { browser } from "@wdio/globals";
import { match } from "ts-pattern";
import { Key } from "webdriverio";
const CLICK_TIME_DELAY = 250;
/**
* Main page object containing all methods, selectors and functionality that is shared across all
* page objects
*/
export default class Page {
/**
* Opens a sub page of the page
* @param path path of the sub page (e.g. /path/to/page.html)
*/
public async open(path: string) {
return await browser.url(`http://localhost:9000/${path}`);
}
public async pause(selector?: string) {
if (selector) {
return await $(selector).waitForDisplayed();
}
return await browser.pause(CLICK_TIME_DELAY);
}
/**
* Target a specific entry in SearchSelect. Requires that the SearchSelect have the `name`
* attribute set, so that the managed selector can find the *right* SearchSelect if there are
* multiple open SearchSelects on the board. See `./ldap-form.view:LdapForm.setBindFlow` for an
* example, and see `./oauth-form.view:OauthForm:setAuthorizationFlow` for a further example of
* why it would be hard to simplify this further (`flow` vs `tentanted-flow` vs a straight-up
* SearchSelect each have different a `searchSelector`).
*/
async searchSelect(searchSelector: string, managedSelector: string, buttonSelector: string) {
const inputBind = await $(searchSelector);
const inputMain = await inputBind.$('input[type="text"]');
await inputMain.click();
const searchBlock = await (
await $(`div[data-managed-for="${managedSelector}"]`).$("ak-list-select")
).shadow$$("button");
let target: WebdriverIO.Element;
// @ts-expect-error "Types break on shadow$$"
for (const button of searchBlock) {
if ((await button.getText()).includes(buttonSelector)) {
target = button;
break;
}
}
// @ts-expect-error "TSC cannot tell if the `for` loop actually performs the assignment."
if (!target) {
throw new Error(`Expected to find an entry matching the spec ${buttonSelector}`);
}
await (await target).click();
await browser.keys(Key.Tab);
}
async setSearchSelect(name: string, value: string) {
const control = await (async () => {
try {
const control = await $(`ak-search-select[name="${name}"]`);
await control.waitForExist({ timeout: 500 });
return control;
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
} catch (_e: any) {
const control = await $(`ak-search-selects-ez[name="${name}"]`);
return control;
}
})();
// Find the search select input control and activate it.
const view = await control.$("ak-search-select-view");
const input = await view.$('input[type="text"]');
await input.scrollIntoView();
await input.click();
// Weirdly necessary because it's portals!
const searchBlock = await (
await $(`div[data-managed-for="${name}"]`).$("ak-list-select")
).shadow$$("button");
// @ts-expect-error "Types break on shadow$$"
for (const button of searchBlock) {
if ((await button.getText()).includes(value)) {
target = button;
break;
}
}
// @ts-expect-error "TSC cannot tell if the `for` loop actually performs the assignment."
if (!target) {
throw new Error(`Expected to find an entry matching the spec ${value}`);
}
await (await target).click();
await browser.keys(Key.Tab);
}
async setTextInput(name: string, value: string) {
const control = await $(`input[name="${name}"}`);
await control.scrollIntoView();
await control.setValue(value);
}
async setRadio(name: string, value: string) {
const control = await $(`ak-radio[name="${name}"]`);
await control.scrollIntoView();
const item = await control.$(`label.*=${value}`).parentElement();
await item.scrollIntoView();
await item.click();
}
async setTypeCreate(name: string, value: string) {
const control = await $(`ak-wizard-page-type-create[name="${name}"]`);
await control.scrollIntoView();
const selection = await $(`.pf-c-card__.*=${value}`);
await selection.scrollIntoView();
await selection.click();
}
async setFormGroup(name: string, setting: "open" | "closed") {
const formGroup = await $(`ak-form-group span[slot="header"].*=${name}`).parentElement();
await formGroup.scrollIntoView();
const toggle = await formGroup.$("div.pf-c-form__field-group-toggle-button button");
await match([toggle.getAttribute("expanded"), setting])
.with(["false", "open"], async () => await toggle.click())
.with(["true", "closed"], async () => await toggle.click())
.otherwise(async () => {});
}
public async logout() {
await browser.url("http://localhost:9000/flows/-/default/invalidation/");
return await this.pause();
}
}