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.
This commit is contained in:
102
web/tests/pageobjects/controls.ts
Normal file
102
web/tests/pageobjects/controls.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { browser } from "@wdio/globals";
|
||||
import { match } from "ts-pattern";
|
||||
import { ChainablePromiseArray, Key } from "webdriverio";
|
||||
|
||||
browser.addCommand('findByText', async function(items: ChainablePromiseArray, text: string) {
|
||||
let item: WebdriverIO.Element | undefined = undefined;
|
||||
for (const i of items) {
|
||||
const label = await i.getText();
|
||||
if (label.indexOf(text) !== -1) {
|
||||
item = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}, true);
|
||||
|
||||
export async function 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);
|
||||
}
|
||||
|
||||
export async function setTextInput(name: string, value: string) {
|
||||
const control = await $(`input[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
await control.setValue(value);
|
||||
}
|
||||
|
||||
export async function 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();
|
||||
}
|
||||
|
||||
export async function setTypeCreate(name: string, value: string) {
|
||||
const control = await $(`ak-wizard-page-type-create[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
const cards = ;
|
||||
const selection = await findByText(await control.$$("div.pf-c-card__title"), value);
|
||||
await selection.scrollIntoView();
|
||||
await selection.click();
|
||||
}
|
||||
|
||||
export async function setFormGroup(name: string, setting: "open" | "closed") {
|
||||
const formGroup = await $(`.//span[contains(., "${name}")]`);
|
||||
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 () => {});
|
||||
}
|
||||
|
||||
export async function clickButton(name: string, ctx?: WebdriverIO.Element) {
|
||||
const context = ctx ?? browser;
|
||||
const buttons = await context.$$("button");
|
||||
let button: WebdriverIO.Element;
|
||||
for (const b of buttons) {
|
||||
const label = await b.getText();
|
||||
if (label.indexOf(name) !== -1) {
|
||||
button = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
await button.scrollIntoView();
|
||||
await button.click();
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { browser } from "@wdio/globals";
|
||||
import { match } from "ts-pattern";
|
||||
import { Key } from "webdriverio";
|
||||
|
||||
const CLICK_TIME_DELAY = 250;
|
||||
@ -7,6 +8,7 @@ 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
|
||||
@ -31,7 +33,6 @@ export default class Page {
|
||||
* 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"]');
|
||||
@ -55,6 +56,77 @@ export default class Page {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user