mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
web: bump API Client version, remove Webdriver dependencies (#16836)
* web: bump API Client version Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * web: Remove WDIO tests. * web: bump tmp package. --------- Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com> Co-authored-by: Teffen Ellis <teffen@goauthentik.io>
This commit is contained in:
committed by
GitHub
parent
68684d1731
commit
a7b02bcef4
@@ -1,40 +0,0 @@
|
||||
/// <reference types="@wdio/globals/types" />
|
||||
/// <reference types="./types/webdriver.js" />
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {WebdriverIO.Browser} browser
|
||||
*/
|
||||
export function addCommands(browser) {
|
||||
/**
|
||||
* @file Custom WDIO browser commands
|
||||
*/
|
||||
|
||||
browser.addCommand(
|
||||
"focus",
|
||||
/**
|
||||
* @this {HTMLElement}
|
||||
*/
|
||||
// @ts-ignore
|
||||
function () {
|
||||
this.focus();
|
||||
|
||||
return this;
|
||||
},
|
||||
/* attachToElement */ true,
|
||||
);
|
||||
|
||||
browser.addCommand(
|
||||
"blur",
|
||||
/**
|
||||
* @this {HTMLElement}
|
||||
*/
|
||||
// @ts-ignore
|
||||
function () {
|
||||
this.blur();
|
||||
|
||||
return this;
|
||||
},
|
||||
/* attachToElement */ true,
|
||||
);
|
||||
}
|
||||
Generated
+188
-7424
File diff suppressed because it is too large
Load Diff
+3
-50
@@ -24,12 +24,8 @@
|
||||
"pseudolocalize": "node ./scripts/pseudolocalize.mjs",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "wireit",
|
||||
"test": "wireit",
|
||||
"test:e2e": "wireit",
|
||||
"test:e2e:next": "playwright test",
|
||||
"test:e2e:watch": "wireit",
|
||||
"test:next": "vitest",
|
||||
"test:watch": "wireit",
|
||||
"test": "vitest",
|
||||
"test:e2e": "playwright test",
|
||||
"tsc": "wireit",
|
||||
"watch": "run-s build-locales esbuild:watch"
|
||||
},
|
||||
@@ -200,10 +196,6 @@
|
||||
"@rollup/rollup-darwin-arm64": "^4.50.2",
|
||||
"@rollup/rollup-linux-arm64-gnu": "^4.50.2",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.50.2",
|
||||
"@wdio/browser-runner": "^9.19.2",
|
||||
"@wdio/cli": "^9.19.2",
|
||||
"@wdio/spec-reporter": "^9.19.2",
|
||||
"@web/test-runner": "^0.20.2",
|
||||
"chromedriver": "^140.0.2",
|
||||
"p-iteration": "^1.1.8"
|
||||
},
|
||||
@@ -277,17 +269,13 @@
|
||||
"lint:components": {
|
||||
"command": "lit-analyzer src"
|
||||
},
|
||||
"lint:types:tests": {
|
||||
"command": "tsc --noEmit -p ./tests"
|
||||
},
|
||||
"lint:types": {
|
||||
"command": "tsc -p .",
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--max_old_space_size=8192"
|
||||
},
|
||||
"dependencies": [
|
||||
"build-locales",
|
||||
"lint:types:tests"
|
||||
"build-locales"
|
||||
]
|
||||
},
|
||||
"lint:lockfile": {
|
||||
@@ -313,41 +301,6 @@
|
||||
"NODE_OPTIONS": "--max_old_space_size=8192"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"command": "wdio ./wdio.conf.mjs --logLevel=warn",
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e": {
|
||||
"command": "wdio run ./tests/wdio.conf.mjs",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"CI": "true",
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:e2e:watch": {
|
||||
"command": "wdio run ./tests/wdio.conf.mjs",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "./tests/tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"test:watch": {
|
||||
"command": "wdio run ./wdio.conf.mjs",
|
||||
"dependencies": [
|
||||
"build"
|
||||
],
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "tsconfig.test.json"
|
||||
}
|
||||
},
|
||||
"tsc": {
|
||||
"command": "tsc -p .",
|
||||
"env": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type KnipConfig } from "knip";
|
||||
|
||||
const config: KnipConfig = {
|
||||
"entry": [
|
||||
entry: [
|
||||
"./src/admin/AdminInterface/AdminInterface.ts",
|
||||
"./src/user/UserInterface.ts",
|
||||
"./src/flow/FlowInterface.ts",
|
||||
@@ -10,18 +10,18 @@ const config: KnipConfig = {
|
||||
"./src/standalone/loading/index.ts",
|
||||
"./src/polyfill/poly.ts",
|
||||
],
|
||||
"project": ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs", ".storybook/*.ts"],
|
||||
project: ["src/**/*.ts", "src/**/*.js", "./scripts/*.mjs", ".storybook/*.ts"],
|
||||
// "ignore": ["src/**/*.test.ts", "src/**/*.stories.ts"],
|
||||
// Prevent Knip from complaining about web components, which export their classes but also
|
||||
// export their registration, and we don't always use both.
|
||||
"ignoreExportsUsedInFile": true,
|
||||
"typescript": {
|
||||
ignoreExportsUsedInFile: true,
|
||||
typescript: {
|
||||
config: ["tsconfig.json"],
|
||||
},
|
||||
"wireit": {
|
||||
wireit: {
|
||||
config: ["package.json"],
|
||||
},
|
||||
"storybook": {
|
||||
storybook: {
|
||||
config: [".storybook/{main,test-runner}.{js,ts}"],
|
||||
entry: [
|
||||
".storybook/{manager,preview}.{js,jsx,ts,tsx}",
|
||||
@@ -29,7 +29,7 @@ const config: KnipConfig = {
|
||||
],
|
||||
project: [".storybook/**/*.{js,jsx,ts,tsx}"],
|
||||
},
|
||||
"eslint": {
|
||||
eslint: {
|
||||
entry: [
|
||||
"eslint.config.mjs",
|
||||
"scripts/eslint.precommit.mjs",
|
||||
@@ -40,9 +40,6 @@ const config: KnipConfig = {
|
||||
],
|
||||
config: ["package.json"],
|
||||
},
|
||||
"webdriver-io": {
|
||||
config: ["wdio.conf.js"],
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import "../AdminSettingsFooterLinks.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-admin-settings-footer-link", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-admin-settings-footer-link")?.remove();
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render an empty control", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
await expect(await link.getProperty("toJson")).toEqual({ name: "", href: "" });
|
||||
});
|
||||
|
||||
it("should not be valid if just a name is filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
await expect(await link.getProperty("toJson")).toEqual({ name: "foo", href: "" });
|
||||
});
|
||||
|
||||
it("should be valid if just a URL is filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "",
|
||||
href: "https://foo.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("should be valid if both are filled in", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await link.$('input[name="href"]').setValue("https://foo.com");
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "foo",
|
||||
href: "https://foo.com",
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be valid if the URL is not valid", async () => {
|
||||
render(html`<ak-admin-settings-footer-link name="link"></ak-admin-settings-footer-link>`);
|
||||
const link = await $("ak-admin-settings-footer-link");
|
||||
await link.$('input[name="name"]').setValue("foo");
|
||||
await link.$('input[name="href"]').setValue("never://foo.com");
|
||||
await expect(await link.getProperty("toJson")).toEqual({
|
||||
name: "foo",
|
||||
href: "never://foo.com",
|
||||
});
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -1,154 +0,0 @@
|
||||
import "./EnterpriseStatusCard.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { LicenseForecast, LicenseSummary, LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-enterprise-status-card", () => {
|
||||
it("should not error when no data is loaded", async () => {
|
||||
render(html`<ak-enterprise-status-card></ak-enterprise-status-card>`);
|
||||
|
||||
const status = await $("ak-enterprise-status-card");
|
||||
await expect(status).toHaveText(msg("Loading"));
|
||||
});
|
||||
|
||||
it("should render empty when unlicensed", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Unlicensed,
|
||||
internalUsers: 0,
|
||||
externalUsers: 0,
|
||||
latestValid: new Date(0),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Unlicensed"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "0");
|
||||
});
|
||||
|
||||
it("should show warnings when full", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Valid,
|
||||
internalUsers: 123,
|
||||
externalUsers: 123,
|
||||
latestValid: new Date(),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
});
|
||||
|
||||
it("should show infinity when not licensed for a user type", async () => {
|
||||
const forecast: LicenseForecast = {
|
||||
externalUsers: 123,
|
||||
internalUsers: 123,
|
||||
forecastedExternalUsers: 123,
|
||||
forecastedInternalUsers: 123,
|
||||
};
|
||||
const summary: LicenseSummary = {
|
||||
status: LicenseSummaryStatusEnum.Valid,
|
||||
internalUsers: 123,
|
||||
externalUsers: 0,
|
||||
latestValid: new Date(),
|
||||
licenseFlags: [],
|
||||
};
|
||||
render(
|
||||
html`<ak-enterprise-status-card .forecast=${forecast} .summary=${summary}>
|
||||
</ak-enterprise-status-card>`,
|
||||
);
|
||||
|
||||
const status = await $("ak-enterprise-status-card").$(
|
||||
">>>.pf-c-description-list__description > .pf-c-description-list__text",
|
||||
);
|
||||
await expect(status).toExist();
|
||||
await expect(status).toHaveText(msg("Valid"));
|
||||
|
||||
const internalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#internalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(internalUserProgress).toExist();
|
||||
await expect(internalUserProgress).toHaveAttr("aria-valuenow", "100");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
|
||||
const externalUserProgress = await $("ak-enterprise-status-card").$(
|
||||
">>>#externalUsers > .pf-c-progress__bar",
|
||||
);
|
||||
await expect(externalUserProgress).toExist();
|
||||
await expect(externalUserProgress).toHaveAttr("aria-valuenow", "∞");
|
||||
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#internalUsers"),
|
||||
).toHaveElementClass("pf-m-warning");
|
||||
await expect(
|
||||
await $("ak-enterprise-status-card").$(">>>#externalUsers"),
|
||||
).toHaveElementClass("pf-m-danger");
|
||||
});
|
||||
});
|
||||
@@ -1,149 +0,0 @@
|
||||
import "../ak-select-table.js";
|
||||
|
||||
import { nutritionDbUSDA as unsortedNutritionDbUSDA } from "../stories/sample_nutrition_db.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, browser } from "@wdio/globals";
|
||||
import { expect } from "expect-webdriverio";
|
||||
import { slug } from "github-slugger";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
type SortableRecord = Record<string, string | number>;
|
||||
|
||||
const dbSort = (a: SortableRecord, b: SortableRecord) =>
|
||||
a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
||||
const alphaSort = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
|
||||
const nutritionDbUSDA = unsortedNutritionDbUSDA.toSorted(dbSort);
|
||||
|
||||
const columns = ["Name", "Calories", "Protein", "Fiber", "Sugar"];
|
||||
const content = nutritionDbUSDA.map(({ name, calories, sugar, fiber, protein }) => ({
|
||||
key: slug(name),
|
||||
content: [name, calories, protein, fiber, sugar].map((a) => html`${a}`),
|
||||
}));
|
||||
|
||||
const item3 = nutritionDbUSDA[2];
|
||||
|
||||
describe("Select Table", () => {
|
||||
let selecttable: WebdriverIO.Element;
|
||||
let table: WebdriverIO.Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(
|
||||
html`<ak-select-table .content=${content} .columns=${columns}> </ak-select-table>`,
|
||||
document.body,
|
||||
);
|
||||
// @ts-ignore
|
||||
selecttable = await $("ak-select-table");
|
||||
// @ts-ignore
|
||||
table = await selecttable.$(">>>table");
|
||||
});
|
||||
|
||||
it("it should render a select table", async () => {
|
||||
expect(table).toBeDisplayed();
|
||||
});
|
||||
|
||||
it("the table should have as many entries as the data source", async () => {
|
||||
const rows = await table.$(">>>tbody").$$(">>>tr");
|
||||
expect(rows.length).toBe(content.length);
|
||||
});
|
||||
|
||||
it(`the third item ought to have the name ${item3.name}`, async () => {
|
||||
const rows = await table.$(">>>tbody").$$(">>>tr");
|
||||
const cells = await rows[2].$$(">>>td");
|
||||
const cell1Text = await cells[1].getText();
|
||||
expect(cell1Text).toEqual(item3.name);
|
||||
});
|
||||
|
||||
it("Selecting one item ought to result in the value of the table being set", async () => {
|
||||
const rows = await table.$(">>>tbody").$$(">>>tr");
|
||||
const control = await rows[2].$$(">>>td")[0].$(">>>input");
|
||||
await control.click();
|
||||
expect(await selecttable.getValue()).toEqual(slug(item3.name));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await browser.execute(() => {
|
||||
document.body.querySelector("ak-select-table")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multiselect Table", () => {
|
||||
let selecttable: WebdriverIO.Element;
|
||||
let table: WebdriverIO.Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(
|
||||
html`<ak-select-table multiple .content=${content} .columns=${columns}>
|
||||
</ak-select-table>`,
|
||||
document.body,
|
||||
);
|
||||
// @ts-ignore
|
||||
selecttable = await $("ak-select-table");
|
||||
// @ts-ignore
|
||||
table = await selecttable.$(">>>table");
|
||||
});
|
||||
|
||||
it("it should render the select-all control", async () => {
|
||||
const thead = await table.$(">>>thead");
|
||||
const selall = await thead.$$(">>>tr")[0].$$(">>>td")[0];
|
||||
if (selall === undefined) {
|
||||
throw new Error("Could not find table header");
|
||||
}
|
||||
const input = await selall.$(">>>input");
|
||||
expect(await input.getProperty("name")).toEqual("select-all-input");
|
||||
});
|
||||
|
||||
it("it should set the value when one input is clicked", async () => {
|
||||
const input = await table.$(">>>tbody").$$(">>>tr")[2].$$(">>>td")[0].$(">>>input");
|
||||
await input.click();
|
||||
expect(await selecttable.getValue()).toEqual(slug(nutritionDbUSDA[2].name));
|
||||
});
|
||||
|
||||
it("it should select all when that control is clicked", async () => {
|
||||
const selall = await table.$(">>>thead").$$(">>>tr")[0].$$(">>>td")[0];
|
||||
if (selall === undefined) {
|
||||
throw new Error("Could not find table header");
|
||||
}
|
||||
const input = await selall.$(">>>input");
|
||||
await input.click();
|
||||
const value = await selecttable.getValue();
|
||||
const values = value.split(";").toSorted(alphaSort).join(";");
|
||||
const expected = nutritionDbUSDA.map((a) => slug(a.name)).join(";");
|
||||
expect(values).toEqual(expected);
|
||||
});
|
||||
|
||||
it("it should clear all when that control is clicked twice", async () => {
|
||||
const selall = await table.$(">>>thead").$$(">>>tr")[0].$$(">>>td")[0];
|
||||
if (selall === undefined) {
|
||||
throw new Error("Could not find table header");
|
||||
}
|
||||
const input = await selall.$(">>>input");
|
||||
await input.click();
|
||||
const value = await selecttable.getValue();
|
||||
const values = value.split(";").toSorted(alphaSort).join(";");
|
||||
const expected = nutritionDbUSDA.map((a) => slug(a.name)).join(";");
|
||||
expect(values).toEqual(expected);
|
||||
await input.click();
|
||||
const newvalue = await selecttable.getValue();
|
||||
expect(newvalue).toEqual("");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await browser.execute(() => {
|
||||
document.body.querySelector("ak-select-table")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import "#elements/ak-table/ak-simple-table";
|
||||
|
||||
import { nutritionDbUSDA } from "../stories/sample_nutrition_db.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, browser } from "@wdio/globals";
|
||||
import { expect } from "expect-webdriverio";
|
||||
import { slug } from "github-slugger";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
const columns = ["Name", "Calories", "Protein", "Fiber", "Sugar"];
|
||||
const content = nutritionDbUSDA.map(({ name, calories, sugar, fiber, protein }) => ({
|
||||
key: slug(name),
|
||||
content: [name, calories, protein, fiber, sugar].map((a) => html`${a}`),
|
||||
}));
|
||||
|
||||
describe("Simple Table", () => {
|
||||
let table: WebdriverIO.Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(
|
||||
html`<ak-simple-table .content=${content} .columns=${columns}> </ak-simple-table>`,
|
||||
document.body,
|
||||
);
|
||||
// @ts-ignore
|
||||
table = await $("ak-simple-table").$(">>>table");
|
||||
});
|
||||
|
||||
it("it should render a simple table", async () => {
|
||||
expect(table).toBeDisplayed();
|
||||
});
|
||||
|
||||
it("the table should have as many entries as the data source", async () => {
|
||||
const tbody = await table.$(">>>tbody");
|
||||
const rows = await tbody.$$(">>>tr");
|
||||
expect(rows.length).toBe(content.length);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await browser.execute(() => {
|
||||
document.body.querySelector("ak-simple-table")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import "../AggregateCard.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-aggregate-card", () => {
|
||||
it("should render the standard card without an icon, link, or subtext", async () => {
|
||||
render(
|
||||
html`<ak-aggregate-card header="Loading"
|
||||
><p>This is the main content</p></ak-aggregate-card
|
||||
>`,
|
||||
);
|
||||
const component = await $("ak-aggregate-card");
|
||||
await expect(await component.$(">>>.pf-c-card__header a")).not.toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title i")).not.toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title")).toHaveText("Loading");
|
||||
await expect(await component.$(">>>.pf-c-card__body")).toHaveText(
|
||||
"This is the main content",
|
||||
);
|
||||
await expect(await component.$(">>>.subtext")).not.toExist();
|
||||
});
|
||||
|
||||
it("should render the standard card with an icon", async () => {
|
||||
render(
|
||||
html`<ak-aggregate-card icon="fa fa-bath" header="Loading"
|
||||
><p>This is the main content</p></ak-aggregate-card
|
||||
>`,
|
||||
);
|
||||
const component = await $("ak-aggregate-card");
|
||||
await expect(await component.$(">>>.pf-c-card__title i")).toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title")).toHaveText("Loading");
|
||||
await expect(await component.$(">>>.pf-c-card__body")).toHaveText(
|
||||
"This is the main content",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render the standard card with an icon, a link, and slotted content", async () => {
|
||||
render(
|
||||
html`<ak-aggregate-card icon="fa fa-bath" header="Loading" headerLink="http://localhost"
|
||||
><p>This is the main content</p></ak-aggregate-card
|
||||
>`,
|
||||
);
|
||||
const component = await $("ak-aggregate-card");
|
||||
await expect(await component.$(">>>.pf-c-card__header a")).toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title i")).toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title")).toHaveText("Loading");
|
||||
await expect(await component.$(">>>.pf-c-card__body")).toHaveText(
|
||||
"This is the main content",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render the standard card with an icon, a link, and subtext", async () => {
|
||||
render(
|
||||
html`<ak-aggregate-card
|
||||
icon="fa fa-bath"
|
||||
header="Loading"
|
||||
headerLink="http://localhost"
|
||||
subtext="Xena had subtext"
|
||||
><p>This is the main content</p></ak-aggregate-card
|
||||
>`,
|
||||
);
|
||||
const component = await $("ak-aggregate-card");
|
||||
await expect(await component.$(">>>.pf-c-card__header a")).toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title i")).toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__title")).toHaveText("Loading");
|
||||
await expect(await component.$(">>>.subtext")).toHaveText("Xena had subtext");
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
import "../AggregatePromiseCard.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
const DELAY = 1000; // milliseconds
|
||||
|
||||
describe("ak-aggregate-card-promise", () => {
|
||||
it("should render the promise card and display the message after a 1 second timeout", async () => {
|
||||
const text = "RESULT";
|
||||
const runThis = (timeout: number, value: string) =>
|
||||
new Promise((resolve, _reject) => setTimeout(resolve, timeout, value));
|
||||
const promise = runThis(DELAY, text);
|
||||
render(html`<ak-aggregate-card-promise .promise=${promise}></ak-aggregate-card-promise>`);
|
||||
|
||||
const component = await $("ak-aggregate-card-promise");
|
||||
// Assert we're in pre-resolve mode
|
||||
await expect(await component.$(">>>.pf-c-card__header a")).not.toExist();
|
||||
await expect(await component.$(">>>ak-spinner")).toExist();
|
||||
await promise;
|
||||
await expect(await component.$(">>>ak-spinner")).not.toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__body")).toHaveText("RESULT");
|
||||
});
|
||||
|
||||
it("should render the promise card and display failure after a 1 second timeout", async () => {
|
||||
const text = "EXPECTED FAILURE";
|
||||
const runThis = (timeout: number, value: string) =>
|
||||
new Promise((_resolve, reject) => setTimeout(reject, timeout, value));
|
||||
const promise = runThis(DELAY, text);
|
||||
render(
|
||||
html`<ak-aggregate-card-promise
|
||||
.promise=${promise}
|
||||
failureMessage=${text}
|
||||
></ak-aggregate-card-promise>`,
|
||||
);
|
||||
|
||||
const component = await $("ak-aggregate-card-promise");
|
||||
// Assert we're in pre-resolve mode
|
||||
await expect(await component.$(">>>.pf-c-card__header a")).not.toExist();
|
||||
await expect(await component.$(">>>ak-spinner")).toExist();
|
||||
try {
|
||||
await promise;
|
||||
} catch (_e: unknown) {
|
||||
await expect(await component.$(">>>ak-spinner")).not.toExist();
|
||||
await expect(await component.$(">>>.pf-c-card__body")).toHaveText(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import "../QuickActionsCard.js";
|
||||
|
||||
import { QuickAction } from "../QuickActionsCard.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
const ACTIONS: QuickAction[] = [
|
||||
["Create a new application", "/core/applications"],
|
||||
["Check the logs", "/events/log"],
|
||||
["Explore integrations", "https://integrations.goauthentik.io/", true],
|
||||
["Manage users", "/identity/users"],
|
||||
["Check the release notes", "https://goauthentik.io/docs/releases/", true],
|
||||
];
|
||||
|
||||
describe("ak-quick-actions-card", () => {
|
||||
it("display ak-quick-actions-card", async () => {
|
||||
render(
|
||||
html`<ak-quick-actions-card
|
||||
title="Alt Title"
|
||||
.actions=${ACTIONS}
|
||||
></ak-quick-actions-card>`,
|
||||
);
|
||||
const component = await $("ak-quick-actions-card");
|
||||
const items = await component.$$(">>>.pf-c-list li");
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
await expect(Array.from(items).length).toEqual(5);
|
||||
await expect(await component.$(">>>.pf-c-list li:nth-of-type(4)")).toHaveText(
|
||||
"Manage users",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import { $, browser } from "@wdio/globals";
|
||||
|
||||
browser.addCommand(
|
||||
"focus",
|
||||
function () {
|
||||
browser.execute(function (domElement) {
|
||||
domElement.focus();
|
||||
// @ts-ignore
|
||||
}, this);
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Search Select View Driver
|
||||
*
|
||||
* This class provides a collection of easy-to-use methods for interacting with and examining the
|
||||
* results of an interaction with an `ak-search-select-view` via WebdriverIO.
|
||||
*
|
||||
* It's hoped that with the OUIA tags, we could automate testing further. The OUIA tag would
|
||||
* instruct the test harness "use this driver to test this component," and we could test Forms and
|
||||
* Tables with a small DSL of test language concepts
|
||||
*/
|
||||
|
||||
export class AkSearchSelectViewDriver {
|
||||
constructor(
|
||||
public element: WebdriverIO.Element,
|
||||
public menu: WebdriverIO.Element,
|
||||
) {
|
||||
/* no op */
|
||||
}
|
||||
|
||||
static async build(element: WebdriverIO.Element) {
|
||||
const tagname = await element.getTagName();
|
||||
const comptype = await element.getAttribute("data-ouia-component-type");
|
||||
if (comptype !== "ak-search-select-view") {
|
||||
throw new Error(
|
||||
`SearchSelectView driver passed incorrect component. Expected ak-search-select-view, got ${comptype ? `'${comptype}` : `No test data type, tag name: '${tagname}'`}`,
|
||||
);
|
||||
}
|
||||
const id = await element.getAttribute("data-ouia-component-id");
|
||||
const menu = await $(`[data-ouia-component-id="menu-${id}"]`);
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
return new AkSearchSelectViewDriver(element, menu);
|
||||
}
|
||||
|
||||
get open() {
|
||||
return this.element.getProperty("open");
|
||||
}
|
||||
|
||||
async input() {
|
||||
return await this.element.$(">>>input");
|
||||
}
|
||||
|
||||
async listElements() {
|
||||
return await this.menu.$$(">>>li");
|
||||
}
|
||||
|
||||
async focusOnInput() {
|
||||
// @ts-ignore
|
||||
await (await this.input()).focus();
|
||||
}
|
||||
|
||||
async inputIsVisible() {
|
||||
return await this.element.$(">>>input").isDisplayed();
|
||||
}
|
||||
|
||||
async menuIsVisible() {
|
||||
return (await this.menu.isExisting()) && (await this.menu.isDisplayed());
|
||||
}
|
||||
|
||||
async clickInput() {
|
||||
return await (await this.input()).click();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import "../ak-search-select-view.js";
|
||||
|
||||
import { sampleData } from "../stories/sampleData.js";
|
||||
import { AkSearchSelectViewDriver } from "./ak-search-select-view.comp.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, browser, expect } from "@wdio/globals";
|
||||
import { slug } from "github-slugger";
|
||||
import { Key } from "webdriverio";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
const longGoodForYouPairs = {
|
||||
grouped: false,
|
||||
options: sampleData.map(({ produce }) => [slug(produce), produce]),
|
||||
};
|
||||
|
||||
describe("Search select: Test Input Field", () => {
|
||||
let select: AkSearchSelectViewDriver;
|
||||
|
||||
beforeEach(async () => {
|
||||
render(
|
||||
html`<ak-search-select-view .options=${longGoodForYouPairs}> </ak-search-select-view>`,
|
||||
document.body,
|
||||
);
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
select = await AkSearchSelectViewDriver.build(await $("ak-search-select-view"));
|
||||
});
|
||||
|
||||
it("should open the menu when the input is clicked", async () => {
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.menuIsVisible()).toBe(false);
|
||||
await select.clickInput();
|
||||
expect(await select.open).toBe(true);
|
||||
// expect(await select.menuIsVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("should not open the menu when the input is focused", async () => {
|
||||
expect(await select.open).toBe(false);
|
||||
await select.focusOnInput();
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.menuIsVisible()).toBe(false);
|
||||
});
|
||||
|
||||
it("should close the menu when the input is clicked a second time", async () => {
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.menuIsVisible()).toBe(false);
|
||||
await select.clickInput();
|
||||
expect(await select.menuIsVisible()).toBe(true);
|
||||
expect(await select.open).toBe(true);
|
||||
await select.clickInput();
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.open).toBe(false);
|
||||
});
|
||||
|
||||
it("should open the menu from a focused but closed input when a search is begun", async () => {
|
||||
expect(await select.open).toBe(false);
|
||||
await select.focusOnInput();
|
||||
expect(await select.open).toBe(false);
|
||||
expect(await select.menuIsVisible()).toBe(false);
|
||||
await browser.keys("A");
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
select = await AkSearchSelectViewDriver.build(await $("ak-search-select-view"));
|
||||
expect(await select.open).toBe(true);
|
||||
expect(await select.menuIsVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it("should update the list as the user types", async () => {
|
||||
await select.focusOnInput();
|
||||
await browser.keys("Ap");
|
||||
await expect(await select.menuIsVisible()).toBe(true);
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
const elements = Array.from(await select.listElements());
|
||||
await expect(elements.length).toBe(2);
|
||||
});
|
||||
|
||||
it("set the value when a match is close", async () => {
|
||||
await select.focusOnInput();
|
||||
await browser.keys("Ap");
|
||||
await expect(await select.menuIsVisible()).toBe(true);
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
const elements = Array.from(await select.listElements());
|
||||
await expect(elements.length).toBe(2);
|
||||
await browser.keys(Key.Tab);
|
||||
await expect(await (await select.input()).getValue()).toBe("Apples");
|
||||
});
|
||||
|
||||
it("should close the menu when the user clicks away", async () => {
|
||||
document.body.insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
'<input id="a-separate-component" type="text" />',
|
||||
);
|
||||
const input = await browser.$("#a-separate-component");
|
||||
|
||||
await select.clickInput();
|
||||
expect(await select.open).toBe(true);
|
||||
await input.click();
|
||||
expect(await select.open).toBe(false);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await browser.execute(() => {
|
||||
document.body.querySelector("#a-separate-component")?.remove();
|
||||
document.body.querySelector("ak-search-select-view")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,117 +0,0 @@
|
||||
import "../ak-search-select.js";
|
||||
|
||||
import { SearchSelect } from "../ak-search-select.js";
|
||||
import { sampleData, type ViewSample } from "../stories/sampleData.js";
|
||||
import { AkSearchSelectViewDriver } from "./ak-search-select-view.comp.js";
|
||||
|
||||
/* eslint-env jest */
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { bound } from "#elements/decorators/bound";
|
||||
import { render } from "#elements/tests/utils";
|
||||
import { CustomListenerElement } from "#elements/utils/eventEmitter";
|
||||
|
||||
import { $, browser, expect } from "@wdio/globals";
|
||||
import { slug } from "github-slugger";
|
||||
|
||||
import { html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
|
||||
const renderElement = (fruit: ViewSample) => fruit.produce;
|
||||
|
||||
const renderDescription = (fruit: ViewSample) => html`${fruit.desc}`;
|
||||
|
||||
const renderValue = (fruit: ViewSample | undefined) => slug(fruit?.produce ?? "");
|
||||
|
||||
@customElement("ak-mock-search-group")
|
||||
export class MockSearch extends CustomListenerElement(AKElement) {
|
||||
/**
|
||||
* The current fruit
|
||||
*
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String, reflect: true })
|
||||
fruit?: string;
|
||||
|
||||
@query("ak-search-select")
|
||||
search!: SearchSelect<ViewSample>;
|
||||
|
||||
selectedFruit?: ViewSample;
|
||||
|
||||
get value() {
|
||||
return this.selectedFruit ? renderValue(this.selectedFruit) : undefined;
|
||||
}
|
||||
|
||||
@bound
|
||||
handleSearchUpdate(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this.selectedFruit = ev.detail.value;
|
||||
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
@bound
|
||||
selected(fruit: ViewSample) {
|
||||
return this.fruit === slug(fruit.produce);
|
||||
}
|
||||
|
||||
@bound
|
||||
fetchObjects() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const resolver = (resolve: any) => {
|
||||
this.addEventListener("resolve", () => {
|
||||
resolve(sampleData);
|
||||
});
|
||||
};
|
||||
return new Promise(resolver);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ak-search-select
|
||||
.fetchObjects=${this.fetchObjects}
|
||||
.renderElement=${renderElement}
|
||||
.renderDescription=${renderDescription}
|
||||
.value=${renderValue}
|
||||
.selected=${this.selected}
|
||||
managed
|
||||
@ak-change=${this.handleSearchUpdate}
|
||||
blankable
|
||||
>
|
||||
</ak-search-select>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
describe("Search select: event driven startup", () => {
|
||||
let select: AkSearchSelectViewDriver;
|
||||
let wrapper: SearchSelect<ViewSample>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await render(html`<ak-mock-search-group></ak-mock-search-group>`, document.body);
|
||||
// @ts-ignore
|
||||
wrapper = await $(">>>ak-search-select");
|
||||
});
|
||||
|
||||
it("should shift from the loading indicator to search select view on fetch event completed", async () => {
|
||||
expect(await wrapper).toBeExisting();
|
||||
expect(await $(">>>ak-search-select-loading-indicator")).toBeDisplayed();
|
||||
await browser.execute(() => {
|
||||
const mock = document.querySelector("ak-mock-search-group");
|
||||
mock?.dispatchEvent(new Event("resolve"));
|
||||
});
|
||||
expect(await $(">>>ak-search-select-loading-indicator")).not.toBeDisplayed();
|
||||
// @ts-expect-error "Another ChainablePromise mistake"
|
||||
select = await AkSearchSelectViewDriver.build(await $(">>>ak-search-select-view"));
|
||||
expect(await select).toBeExisting();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await browser.execute(() => {
|
||||
document.body.querySelector("ak-mock-search-group")?.remove();
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
import "../Alert.js";
|
||||
|
||||
import { akAlert, Level } from "../Alert.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-alert", () => {
|
||||
it("should render an alert with the enum", async () => {
|
||||
render(html`<ak-alert level=${Level.Info}>This is an alert</ak-alert>`, document.body);
|
||||
|
||||
await expect(await $("ak-alert").$("div")).not.toHaveElementClass("pf-m-inline");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-c-alert");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with the attribute", async () => {
|
||||
render(html`<ak-alert level="info">This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert with an inline class and the default level", async () => {
|
||||
render(html`<ak-alert inline>This is an alert</ak-alert>`, document.body);
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-warning");
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-inline");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
|
||||
it("should render an alert as a function call", async () => {
|
||||
render(akAlert({ level: "info" }, "This is an alert"));
|
||||
await expect(await $("ak-alert").$("div")).toHaveElementClass("pf-m-info");
|
||||
await expect(await $("ak-alert").$(".pf-c-alert__title")).toHaveText("This is an alert");
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import "../Divider.js";
|
||||
|
||||
import { akDivider } from "../Divider.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-divider", () => {
|
||||
it("should render the divider", async () => {
|
||||
render(html`<ak-divider></ak-divider>`);
|
||||
const empty = await $("ak-divider");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
|
||||
it("should render the divider with the specified text", async () => {
|
||||
render(html`<ak-divider><span>Your Message Here</span></ak-divider>`);
|
||||
const span = await $("ak-divider").$(">>>span");
|
||||
await expect(span).toExist();
|
||||
await expect(span).toHaveText("Your Message Here");
|
||||
});
|
||||
|
||||
it("should render the divider as a function with the specified text", async () => {
|
||||
render(akDivider("Your Message As A Function"));
|
||||
const divider = await $("ak-divider");
|
||||
await expect(divider).toExist();
|
||||
await expect(divider).toHaveText("Your Message As A Function");
|
||||
});
|
||||
|
||||
it("should render the divider as a function", async () => {
|
||||
render(akDivider());
|
||||
const empty = await $("ak-divider");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import "../EmptyState.js";
|
||||
|
||||
import { akEmptyState } from "../EmptyState.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-empty-state", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-empty-state")?.remove();
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-empty-state default-label></ak-empty-state>`);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-title");
|
||||
await expect(header).toHaveText("Loading");
|
||||
});
|
||||
|
||||
it("should handle standard boolean", async () => {
|
||||
render(html`<ak-empty-state loading>Waiting</ak-empty-state>`);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-title");
|
||||
await expect(header).toHaveText("Waiting");
|
||||
});
|
||||
|
||||
it("should render a static empty state", async () => {
|
||||
render(html`<ak-empty-state><span>${msg("No messages found")}</span> </ak-empty-state>`);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
await expect(empty).toHaveClass("fa-question-circle");
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-title");
|
||||
await expect(header).toHaveText("No messages found");
|
||||
});
|
||||
|
||||
it("should render a slotted message", async () => {
|
||||
render(
|
||||
html`<ak-empty-state
|
||||
><span>${msg("No messages found")}</span>
|
||||
<p slot="body">Try again with a different filter</p>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
|
||||
const message = await $("ak-empty-state").$(">>>.pf-c-empty-state__body").$(">>>p");
|
||||
await expect(message).toHaveText("Try again with a different filter");
|
||||
});
|
||||
|
||||
it("should render as a function call", async () => {
|
||||
render(akEmptyState({ loading: true }, "Being Thoughtful"));
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-empty-state__body");
|
||||
await expect(header).toHaveText("Being Thoughtful");
|
||||
});
|
||||
|
||||
it("should render as a complex function call", async () => {
|
||||
render(
|
||||
akEmptyState(
|
||||
{ loading: true },
|
||||
html` <span slot="body">Introspecting</span>
|
||||
<span slot="primary">... carefully</span>`,
|
||||
),
|
||||
);
|
||||
|
||||
const empty = await $("ak-empty-state").$(">>>.pf-c-empty-state__icon");
|
||||
await expect(empty).toExist();
|
||||
|
||||
const header = await $("ak-empty-state").$(">>>.pf-c-empty-state__body");
|
||||
await expect(header).toHaveText("Introspecting");
|
||||
|
||||
const primary = await $("ak-empty-state").$(">>>.pf-c-empty-state__primary");
|
||||
await expect(primary).toHaveText("... carefully");
|
||||
});
|
||||
});
|
||||
@@ -1,95 +0,0 @@
|
||||
import "../Expand.js";
|
||||
|
||||
import { akExpand } from "../Expand.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-expand", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-expand")?.remove();
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the expansion content hidden by default", async () => {
|
||||
render(html`<ak-expand><p>This is the expanded text</p></ak-expand>`);
|
||||
const text = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(text).not.toBeDisplayed();
|
||||
});
|
||||
|
||||
it("should render the expansion content visible on demand", async () => {
|
||||
render(html`<ak-expand expanded><p>This is the expanded text</p></ak-expand>`);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the expanded text");
|
||||
});
|
||||
|
||||
it("should respond to the click event", async () => {
|
||||
render(html`<ak-expand><p>This is the expanded text</p></ak-expand>`);
|
||||
let content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).not.toBeDisplayed();
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
|
||||
await control.click();
|
||||
content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).toBeDisplayed();
|
||||
|
||||
await control.click();
|
||||
content = await $("ak-expand").$(">>>.pf-c-expandable-section__content");
|
||||
await expect(content).toExist();
|
||||
await expect(content).not.toBeDisplayed();
|
||||
});
|
||||
|
||||
it("should honor the header properties", async () => {
|
||||
render(
|
||||
html`<ak-expand text-open="Close it" text-closed="Open it" expanded
|
||||
><p>This is the expanded text</p></ak-expand
|
||||
>`,
|
||||
);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the expanded text");
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Close it",
|
||||
);
|
||||
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
await control.click();
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Open it",
|
||||
);
|
||||
});
|
||||
|
||||
it("should honor the header properties via a function call", async () => {
|
||||
render(
|
||||
akExpand(
|
||||
{ "expanded": true, "text-open": "Close it now", "text-closed": "Open it now" },
|
||||
html`<p>This is the new text.</p>`,
|
||||
),
|
||||
);
|
||||
const paragraph = await $("ak-expand").$(">>>p");
|
||||
await expect(paragraph).toExist();
|
||||
await expect(paragraph).toBeDisplayed();
|
||||
await expect(paragraph).toHaveText("This is the new text.");
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Close it now",
|
||||
);
|
||||
const control = await $("ak-expand").$(">>>button");
|
||||
await control.click();
|
||||
await expect(await $("ak-expand").$(".pf-c-expandable-section__toggle-text")).toHaveText(
|
||||
"Open it now",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import "../Label.js";
|
||||
|
||||
import { akLabel, PFColor } from "../Label.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-label", () => {
|
||||
it("should render a label with the enum", async () => {
|
||||
render(html`<ak-label color=${PFColor.Red}>This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-c-label");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).not.toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-red");
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-times");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a label with the attribute", async () => {
|
||||
render(html`<ak-label color="success">This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-green");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compact label with the default level", async () => {
|
||||
render(html`<ak-label compact>This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-info-circle");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render a compact label with an icon and the default level", async () => {
|
||||
render(html`<ak-label compact icon="fa-coffee">This is a label</ak-label>`);
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-grey");
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass(
|
||||
"pf-m-compact",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
await expect(await $("ak-label").$(">>>i.fas")).toHaveElementClass("fa-coffee");
|
||||
});
|
||||
|
||||
it("should render a label with the function", async () => {
|
||||
render(akLabel({ color: "success" }, "This is a label"));
|
||||
await expect(await $("ak-label").$(">>>span.pf-c-label")).toHaveElementClass("pf-m-green");
|
||||
await expect(await $("ak-label").$(">>>.pf-c-label__content")).toHaveText(
|
||||
"This is a label",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
import "../LoadingOverlay.js";
|
||||
|
||||
import { akLoadingOverlay } from "../LoadingOverlay.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
describe("ak-loading-overlay", () => {
|
||||
it("should render the default loader", async () => {
|
||||
render(html`<ak-loading-overlay></ak-loading-overlay>`);
|
||||
|
||||
const empty = await $("ak-loading-overlay");
|
||||
await expect(empty).toExist();
|
||||
});
|
||||
|
||||
it("should render a slotted message", async () => {
|
||||
render(
|
||||
html`<ak-loading-overlay>
|
||||
<p>Try again with a different filter</p>
|
||||
</ak-loading-overlay>`,
|
||||
);
|
||||
|
||||
const message = await $("ak-loading-overlay").$(">>>p");
|
||||
await expect(message).toHaveText("Try again with a different filter");
|
||||
});
|
||||
|
||||
it("as a function should render a slotted message", async () => {
|
||||
render(akLoadingOverlay({}, "Try again with another filter"));
|
||||
const overlay = await $("ak-loading-overlay");
|
||||
await expect(overlay).toHaveText("Try again with another filter");
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import "#admin/admin-settings/AdminSettingsFooterLinks";
|
||||
import "../ak-array-input.js";
|
||||
|
||||
import { render } from "#elements/tests/utils";
|
||||
|
||||
import { FooterLink } from "@goauthentik/api";
|
||||
|
||||
import { $, expect } from "@wdio/globals";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
const sampleItems: FooterLink[] = [
|
||||
{ name: "authentik", href: "https://goauthentik.io" },
|
||||
{ name: "authentik docs", href: "https://docs.goauthentik.io/docs/" },
|
||||
];
|
||||
|
||||
describe("ak-array-input", () => {
|
||||
afterEach(async () => {
|
||||
await browser.execute(async () => {
|
||||
await document.body.querySelector("ak-array-input")?.remove();
|
||||
if (document.body._$litPart$) {
|
||||
// @ts-expect-error expression of type '"_$litPart$"' is added by Lit
|
||||
await delete document.body._$litPart$;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const component = (items: FooterLink[] = []) =>
|
||||
render(
|
||||
html` <ak-array-input
|
||||
id="ak-array-input"
|
||||
.items=${items}
|
||||
.newItem=${() => ({ name: "", href: "" })}
|
||||
.row=${(f?: FooterLink) =>
|
||||
html`<ak-admin-settings-footer-link name="footerLink" .footerLink=${f}>
|
||||
</ak-admin-settings-footer-link>`}
|
||||
validate
|
||||
></ak-array-input>`,
|
||||
);
|
||||
|
||||
it("should render an empty control", async () => {
|
||||
await component();
|
||||
const link = await $("ak-array-input");
|
||||
await browser.pause(500);
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual([]);
|
||||
});
|
||||
|
||||
it("should render a populated component", async () => {
|
||||
await component(sampleItems);
|
||||
const link = await $("ak-array-input");
|
||||
await browser.pause(500);
|
||||
await expect(await link.getProperty("isValid")).toStrictEqual(true);
|
||||
await expect(await link.getProperty("toJson")).toEqual(sampleItems);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { applyDocumentTheme } from "#common/theme";
|
||||
|
||||
import { render as litRender, TemplateResult } from "lit";
|
||||
|
||||
/**
|
||||
* A special version of render that ensures our stylesheets:
|
||||
*
|
||||
* - Will always be available to all elements under test.
|
||||
* - Ensure they look right during testing.
|
||||
* - CSS-based checks for visibility will return correct values.
|
||||
*/
|
||||
export const render = (body: TemplateResult) => {
|
||||
applyDocumentTheme();
|
||||
|
||||
return litRender(body, document.body);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import AdminPage from "./admin.page.js";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
class AdminOverviewPage extends AdminPage {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
}
|
||||
|
||||
export default new AdminOverviewPage();
|
||||
@@ -1,13 +0,0 @@
|
||||
import Page from "../pageobjects/page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export default class AdminPage extends Page {
|
||||
public pageHeader() {
|
||||
return $(">>>ak-page-navbar").$(".page-title");
|
||||
}
|
||||
|
||||
async openApplicationsListPage() {
|
||||
await this.open("if/admin/#/core/applications");
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import AdminPage from "./admin.page.js";
|
||||
import ApplicationForm from "./forms/application.form.js";
|
||||
import ForwardProxyForm from "./forms/forward-proxy.form.js";
|
||||
import LdapForm from "./forms/ldap.form.js";
|
||||
import OauthForm from "./forms/oauth.form.js";
|
||||
import RadiusForm from "./forms/radius.form.js";
|
||||
import SamlForm from "./forms/saml.form.js";
|
||||
import ScimForm from "./forms/scim.form.js";
|
||||
import TransparentProxyForm from "./forms/transparent-proxy.form.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
|
||||
class ApplicationWizardView extends AdminPage {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
ldap = LdapForm;
|
||||
oauth = OauthForm;
|
||||
transparentProxy = TransparentProxyForm;
|
||||
forwardProxy = ForwardProxyForm;
|
||||
saml = SamlForm;
|
||||
scim = ScimForm;
|
||||
radius = RadiusForm;
|
||||
app = ApplicationForm;
|
||||
|
||||
async wizardTitle() {
|
||||
return await $(">>>.pf-c-wizard__title");
|
||||
}
|
||||
|
||||
async providerList() {
|
||||
return await $(">>>ak-application-wizard-provider-choice-step");
|
||||
}
|
||||
|
||||
async nextButton() {
|
||||
return await $('>>>button[data-ouid-button-kind="wizard-next"]');
|
||||
}
|
||||
|
||||
async getProviderType(type: string) {
|
||||
// @ts-expect-error "TSC does not understand the ChainablePromiseElement type at all."
|
||||
return await this.providerList().$(`>>>input[value="${type}"]`);
|
||||
}
|
||||
|
||||
async submitPage() {
|
||||
return await $(">>>ak-application-wizard-submit-step");
|
||||
}
|
||||
|
||||
async successMessage() {
|
||||
return await $('>>>[data-ouid-component-state="submitted"]');
|
||||
}
|
||||
}
|
||||
|
||||
type Pair = [string, string];
|
||||
|
||||
// Define a getter for each provider type in the radio button collection.
|
||||
|
||||
const providerValues: Pair[] = [
|
||||
["oauth2provider", "oauth2Provider"],
|
||||
["ldapprovider", "ldapProvider"],
|
||||
["proxyprovider", "proxyProvider"],
|
||||
["radiusprovider", "radiusProvider"],
|
||||
["samlprovider", "samlProvider"],
|
||||
["scimprovider", "scimProvider"],
|
||||
];
|
||||
|
||||
providerValues.forEach(([value, name]: Pair) => {
|
||||
Object.defineProperties(ApplicationWizardView.prototype, {
|
||||
[name]: {
|
||||
get: async function () {
|
||||
return await (
|
||||
await this.providerList()
|
||||
).$(`>>>div[data-ouid-component-name="${value}"]`);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default new ApplicationWizardView();
|
||||
@@ -1,22 +0,0 @@
|
||||
import AdminPage from "./admin.page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
class ApplicationsListPage extends AdminPage {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
async startWizardButton() {
|
||||
return await $('>>>button[data-ouia-component-id="start-application-wizard"]');
|
||||
}
|
||||
|
||||
async open() {
|
||||
return await super.open("if/admin/#/core/applications");
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApplicationsListPage();
|
||||
@@ -1,227 +0,0 @@
|
||||
import { browser } from "@wdio/globals";
|
||||
import { Key } from "webdriverio";
|
||||
|
||||
export async function doBlur(el: WebdriverIO.Element | ChainablePromiseElement) {
|
||||
const element = await el;
|
||||
browser.execute((element) => element.blur(), element);
|
||||
}
|
||||
|
||||
export function tap<A>(a: A) {
|
||||
console.log("TAP:", a);
|
||||
return a;
|
||||
}
|
||||
|
||||
const makeComparator = (value: string | RegExp) =>
|
||||
typeof value === "string"
|
||||
? (sample: string) => sample === value
|
||||
: (sample: string) => value.test(sample);
|
||||
|
||||
export async function checkIsPresent(name: string) {
|
||||
await expect(await $(name)).toBeDisplayed();
|
||||
}
|
||||
|
||||
export async function clickButton(name: string, ctx?: WebdriverIO.Element) {
|
||||
const context = ctx ?? browser;
|
||||
const button = await (async () => {
|
||||
for await (const button of context.$$("button")) {
|
||||
if ((await button.isDisplayed()) && (await button.getText()).indexOf(name) !== -1) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(button && (await button.isDisplayed()))) {
|
||||
throw new Error(`Unable to find button '${name}'`);
|
||||
}
|
||||
|
||||
await button.scrollIntoView();
|
||||
await button.click();
|
||||
await doBlur(button);
|
||||
}
|
||||
|
||||
export async function clickToggleGroup(name: string, value: string | RegExp) {
|
||||
const comparator = makeComparator(value);
|
||||
const button = await (async () => {
|
||||
for await (const button of $(`>>>[data-ouid-component-name=${name}]`).$$(
|
||||
">>>.pf-c-toggle-group__button",
|
||||
)) {
|
||||
if (comparator(await button.$(">>>.pf-c-toggle-group__text").getText())) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(button && (await button?.isDisplayed()))) {
|
||||
throw new Error(`Unable to locate toggle button ${name}:${value.toString()}`);
|
||||
}
|
||||
|
||||
await button.scrollIntoView();
|
||||
await button.click();
|
||||
await doBlur(button);
|
||||
}
|
||||
|
||||
export async function setFormGroup(name: string | RegExp, setting: "open" | "closed") {
|
||||
const comparator = makeComparator(name);
|
||||
const formGroup = await (async () => {
|
||||
for await (const group of browser.$$(">>>ak-form-group")) {
|
||||
// Delightfully, wizards may have slotted elements that *exist* but are not *attached*,
|
||||
// and this can break the damn tests.
|
||||
if (!(await group.isDisplayed())) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
comparator(
|
||||
await group.$(">>>div.pf-c-form__field-group-header-title-text").getText(),
|
||||
)
|
||||
) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(formGroup && (await formGroup.isDisplayed()))) {
|
||||
throw new Error(`Unable to find ak-form-group[name="${name}"]`);
|
||||
}
|
||||
|
||||
await formGroup.scrollIntoView();
|
||||
|
||||
const open = await formGroup.getAttribute("open").then((value) => value !== null);
|
||||
|
||||
if ((setting === "open" && !open) || (setting === "closed" && open)) {
|
||||
await formGroup.$(">>>summary").click();
|
||||
}
|
||||
|
||||
await doBlur(formGroup);
|
||||
}
|
||||
|
||||
export async function setRadio(name: string, value: string | RegExp) {
|
||||
const control = await $(`>>>ak-radio[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
|
||||
const comparator = makeComparator(value);
|
||||
const item = await (async () => {
|
||||
for await (const item of control.$$(">>>div.pf-c-radio")) {
|
||||
if (comparator(await item.$(">>>.pf-c-radio__label").getText())) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(item && (await item.isDisplayed()))) {
|
||||
throw new Error(`Unable to find a radio that matches ${name}:${value.toString()}`);
|
||||
}
|
||||
|
||||
await item.scrollIntoView();
|
||||
await item.click();
|
||||
await doBlur(control);
|
||||
}
|
||||
|
||||
export async function setSearchSelect(name: string, value: string | RegExp) {
|
||||
const control = await (async () => {
|
||||
try {
|
||||
const control = await $(`>>>ak-search-select[name="${name}"]`);
|
||||
await control.waitForExist({ timeout: 500 });
|
||||
return control;
|
||||
} catch (_e: unknown) {
|
||||
const control = await $(`>>>ak-search-selects-ez[name="${name}"]`);
|
||||
return control;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(control && (await control.isExisting()))) {
|
||||
throw new Error(`Unable to find an ak-search-select variant matching ${name}}`);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
const comparator = makeComparator(value);
|
||||
const button = await (async () => {
|
||||
for await (const button of $(`>>>div[data-managed-for*="${name}"]`)
|
||||
.$(">>>ak-list-select")
|
||||
.$$("button")) {
|
||||
if (comparator(await button.getText())) {
|
||||
return button;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(button && (await button.isDisplayed()))) {
|
||||
throw new Error(
|
||||
`Unable to find an ak-search-select entry matching ${name}:${value.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
await (await button).click();
|
||||
await browser.keys(Key.Tab);
|
||||
await doBlur(control);
|
||||
}
|
||||
|
||||
export async function setTextInput(name: string, value: string) {
|
||||
const control = await $(`>>>input[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
await control.setValue(value);
|
||||
await doBlur(control);
|
||||
}
|
||||
|
||||
export async function setTextareaInput(name: string, value: string) {
|
||||
const control = await $(`>>>textarea[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
await control.setValue(value);
|
||||
await doBlur(control);
|
||||
}
|
||||
|
||||
export async function setToggle(name: string, set: boolean) {
|
||||
const toggle = await $(`>>>input[name="${name}"]`);
|
||||
await toggle.scrollIntoView();
|
||||
await expect(await toggle.getAttribute("type")).toBe("checkbox");
|
||||
const state = await toggle.isSelected();
|
||||
if (set !== state) {
|
||||
const control = await (await toggle.parentElement()).$(">>>.pf-c-switch__toggle");
|
||||
await control.click();
|
||||
await doBlur(control);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setTypeCreate(name: string, value: string | RegExp) {
|
||||
const control = await $(`>>>ak-wizard-page-type-create[name="${name}"]`);
|
||||
await control.scrollIntoView();
|
||||
|
||||
const comparator = makeComparator(value);
|
||||
const card = await (async () => {
|
||||
for await (const card of $(">>>ak-wizard-page-type-create").$$(
|
||||
'>>>[data-ouid-component-type="ak-type-create-grid-card"]',
|
||||
)) {
|
||||
if (comparator(await card.$(">>>.pf-c-card__title").getText())) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
if (!(card && (await card.isDisplayed()))) {
|
||||
throw new Error(`Unable to locate radio card ${name}:${value.toString()}`);
|
||||
}
|
||||
|
||||
await card.scrollIntoView();
|
||||
await card.click();
|
||||
await doBlur(control);
|
||||
}
|
||||
|
||||
export type TestInteraction =
|
||||
| [typeof checkIsPresent, ...Parameters<typeof checkIsPresent>]
|
||||
| [typeof clickButton, ...Parameters<typeof clickButton>]
|
||||
| [typeof clickToggleGroup, ...Parameters<typeof clickToggleGroup>]
|
||||
| [typeof setFormGroup, ...Parameters<typeof setFormGroup>]
|
||||
| [typeof setRadio, ...Parameters<typeof setRadio>]
|
||||
| [typeof setSearchSelect, ...Parameters<typeof setSearchSelect>]
|
||||
| [typeof setTextInput, ...Parameters<typeof setTextInput>]
|
||||
| [typeof setTextareaInput, ...Parameters<typeof setTextareaInput>]
|
||||
| [typeof setToggle, ...Parameters<typeof setToggle>]
|
||||
| [typeof setTypeCreate, ...Parameters<typeof setTypeCreate>];
|
||||
|
||||
export type TestSequence = TestInteraction[];
|
||||
|
||||
export type TestProvider = () => TestSequence;
|
||||
@@ -1,19 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export class ApplicationForm extends Page {
|
||||
async name() {
|
||||
return await $('>>>ak-text-input[name="name"]').$(">>>input");
|
||||
}
|
||||
|
||||
async uiSettings() {
|
||||
return $('>>>ak-form-group[label="UI Settings"]');
|
||||
}
|
||||
|
||||
async launchUrl() {
|
||||
return await $('>>>input[name="metaLaunchUrl"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApplicationForm();
|
||||
@@ -1,19 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export class ForwardProxyForm extends Page {
|
||||
async setAuthorizationFlow(selector: string) {
|
||||
await this.searchSelect(
|
||||
'>>>ak-flow-search[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
selector,
|
||||
);
|
||||
}
|
||||
|
||||
get externalHost() {
|
||||
return $('>>>input[name="externalHost"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default new ForwardProxyForm();
|
||||
@@ -1,13 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
export class LdapForm extends Page {
|
||||
async setBindFlow() {
|
||||
await this.searchSelect(
|
||||
'>>>ak-search-select-view[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
"default-authentication-flow",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new LdapForm();
|
||||
@@ -1,19 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export class OauthForm extends Page {
|
||||
async setAuthorizationFlow(selector: string) {
|
||||
await this.searchSelect(
|
||||
'>>>ak-flow-search[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
`${selector}`,
|
||||
);
|
||||
}
|
||||
|
||||
async providerName() {
|
||||
return await $('>>>ak-form-element-horizontal[name="name"]').$("input");
|
||||
}
|
||||
}
|
||||
|
||||
export default new OauthForm();
|
||||
@@ -1,13 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
export class RadiusForm extends Page {
|
||||
async setAuthenticationFlow(selector: string) {
|
||||
await this.searchSelect(
|
||||
'>>>ak-branded-flow-search[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
selector,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default new RadiusForm();
|
||||
@@ -1,19 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export class SamlForm extends Page {
|
||||
async setAuthorizationFlow(selector: string) {
|
||||
await this.searchSelect(
|
||||
'>>>ak-flow-search[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
selector,
|
||||
);
|
||||
}
|
||||
|
||||
get acsUrl() {
|
||||
return $('>>>input[name="acsUrl"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default new SamlForm();
|
||||
@@ -1,13 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
export class ScimForm extends Page {
|
||||
get url() {
|
||||
return $('>>>input[name="url"]');
|
||||
}
|
||||
|
||||
get token() {
|
||||
return $('>>>input[name="token"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default new ScimForm();
|
||||
@@ -1,23 +0,0 @@
|
||||
import Page from "../page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
export class TransparentProxyForm extends Page {
|
||||
async setAuthorizationFlow(selector: string) {
|
||||
await this.searchSelect(
|
||||
'>>>ak-flow-search[name="authorizationFlow"]',
|
||||
"authorizationFlow",
|
||||
selector,
|
||||
);
|
||||
}
|
||||
|
||||
get externalHost() {
|
||||
return $('>>>input[name="externalHost"]');
|
||||
}
|
||||
|
||||
get internalHost() {
|
||||
return $('>>>input[name="internalHost"]');
|
||||
}
|
||||
}
|
||||
|
||||
export default new TransparentProxyForm();
|
||||
@@ -1,68 +0,0 @@
|
||||
import Page from "./page.js";
|
||||
import UserLibraryPage from "./user-library.page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
class LoginPage extends Page {
|
||||
/**
|
||||
* Selectors
|
||||
*/
|
||||
async inputUsername() {
|
||||
return await $('>>>input[name="uidField"]');
|
||||
}
|
||||
|
||||
async usernameBtnSubmit() {
|
||||
return await $('>>>button[type="submit"]');
|
||||
}
|
||||
|
||||
async inputPassword() {
|
||||
return await $('>>>input[name="password"]');
|
||||
}
|
||||
|
||||
async passwordBtnSubmit() {
|
||||
return await $(">>>ak-stage-password").$('>>>button[type="submit"]');
|
||||
}
|
||||
|
||||
async authFailure() {
|
||||
return await $(">>>.pf-m-error");
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific interactions
|
||||
*/
|
||||
|
||||
async username(username: string) {
|
||||
await (await this.inputUsername()).setValue(username);
|
||||
const submitBtn = await this.usernameBtnSubmit();
|
||||
await submitBtn.waitForEnabled();
|
||||
await submitBtn.click();
|
||||
}
|
||||
|
||||
async password(password: string) {
|
||||
await (await this.inputPassword()).setValue(password);
|
||||
const submitBtn = await this.passwordBtnSubmit();
|
||||
await submitBtn.waitForEnabled();
|
||||
await submitBtn.click();
|
||||
}
|
||||
|
||||
async login(username: string, password: string) {
|
||||
await this.username(username);
|
||||
await this.pause();
|
||||
await this.password(password);
|
||||
await this.pause();
|
||||
await this.pause(">>>header h1");
|
||||
return UserLibraryPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* URL for accessing this page (if necessary)
|
||||
*/
|
||||
open() {
|
||||
return super.open("");
|
||||
}
|
||||
}
|
||||
|
||||
export default new LoginPage();
|
||||
@@ -1,133 +0,0 @@
|
||||
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")
|
||||
).$$("button");
|
||||
let target: WebdriverIO.Element;
|
||||
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;
|
||||
} catch (_e: unknown) {
|
||||
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")
|
||||
).$$("button");
|
||||
|
||||
let target: WebdriverIO.Element;
|
||||
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([await 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();
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import AdminPage from "./admin.page.js";
|
||||
import OauthForm from "./forms/oauth.form.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
|
||||
class ProviderWizardView extends AdminPage {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
oauth = OauthForm;
|
||||
|
||||
get wizardTitle() {
|
||||
return $(">>>ak-wizard .pf-c-wizard__header h1.pf-c-title");
|
||||
}
|
||||
|
||||
get providerList() {
|
||||
return $(">>>ak-provider-wizard-initial");
|
||||
}
|
||||
|
||||
get nextButton() {
|
||||
return $(">>>ak-wizard footer button.pf-m-primary");
|
||||
}
|
||||
|
||||
async getProviderType(type: string) {
|
||||
return await this.providerList.$(`>>>input[value="${type}"]`);
|
||||
}
|
||||
|
||||
get successMessage() {
|
||||
return $('>>>[data-commit-state="success"]');
|
||||
}
|
||||
}
|
||||
|
||||
type Pair = [string, string];
|
||||
|
||||
// Define a getter for each provider type in the radio button collection.
|
||||
|
||||
const providerValues: Pair[] = [["oauth2", "oauth2Provider"]];
|
||||
|
||||
providerValues.forEach(([value, name]: Pair) => {
|
||||
Object.defineProperties(ProviderWizardView.prototype, {
|
||||
[name]: {
|
||||
get: function () {
|
||||
return this.providerList.$(`>>>input[id="ak-provider-${value}-form"]`);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default new ProviderWizardView();
|
||||
@@ -1,48 +0,0 @@
|
||||
import AdminPage from "./admin.page.js";
|
||||
|
||||
import { $, browser } from "@wdio/globals";
|
||||
import { Key } from "webdriverio";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
class ApplicationsListPage extends AdminPage {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
get startWizardButton() {
|
||||
return $('>>>ak-wizard button[slot="trigger"]');
|
||||
}
|
||||
|
||||
get searchInput() {
|
||||
return $('>>>ak-table-search input[name="search"]');
|
||||
}
|
||||
|
||||
searchButton() {
|
||||
return $('>>>ak-table-search button[type="submit"]');
|
||||
}
|
||||
|
||||
// Sufficiently esoteric to justify having its own method
|
||||
async clickSearchButton() {
|
||||
await browser.execute(
|
||||
function (searchButton: unknown) {
|
||||
(searchButton as HTMLButtonElement).focus();
|
||||
},
|
||||
await $('>>>ak-table-search button[type="submit"]'),
|
||||
);
|
||||
|
||||
return await browser.action("key").down(Key.Enter).up(Key.Enter).perform();
|
||||
}
|
||||
|
||||
// Only use after a very precise search. :-)
|
||||
async findProviderRow() {
|
||||
return await $(">>>ak-provider-list td a");
|
||||
}
|
||||
|
||||
async open() {
|
||||
return await super.open("if/admin/#/core/providers");
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApplicationsListPage();
|
||||
@@ -1,23 +0,0 @@
|
||||
import Page from "./page.js";
|
||||
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
*/
|
||||
class UserLibraryPage extends Page {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
public async pageHeader() {
|
||||
return $(">>>header h1");
|
||||
}
|
||||
|
||||
public async goToAdmin() {
|
||||
await $('>>>a[href="/if/admin"]').click();
|
||||
return await $("ak-admin-overview").waitForDisplayed();
|
||||
}
|
||||
}
|
||||
|
||||
export default new UserLibraryPage();
|
||||
@@ -1,131 +0,0 @@
|
||||
import ApplicationWizardView from "../pageobjects/application-wizard.page.js";
|
||||
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
|
||||
import type { TestProvider, TestSequence } from "../pageobjects/controls.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
import { login } from "../utils/login.js";
|
||||
import {
|
||||
completeForwardAuthDomainProxyProviderForm,
|
||||
completeForwardAuthProxyProviderForm,
|
||||
completeLDAPProviderForm,
|
||||
completeOAuth2ProviderForm,
|
||||
completeProxyProviderForm,
|
||||
completeRadiusProviderForm,
|
||||
completeSAMLProviderForm,
|
||||
completeSCIMProviderForm,
|
||||
simpleForwardAuthDomainProxyProviderForm,
|
||||
simpleForwardAuthProxyProviderForm,
|
||||
simpleLDAPProviderForm,
|
||||
simpleOAuth2ProviderForm,
|
||||
simpleProxyProviderForm,
|
||||
simpleRadiusProviderForm,
|
||||
simpleSAMLProviderForm,
|
||||
simpleSCIMProviderForm,
|
||||
} from "./provider-shared-sequences.js";
|
||||
|
||||
// @ts-nocheck
|
||||
// ^^^^^^^^^^^ Because TSC cannot handle metaprogramming, and metaprogramming
|
||||
// via `defineProperties` is how we installed the OUID finders for the various
|
||||
// wizard types.
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
const SUCCESS_MESSAGE = "Your application has been saved";
|
||||
|
||||
async function reachTheApplicationsPage() {
|
||||
await ApplicationsListPage.logout();
|
||||
await login();
|
||||
await ApplicationsListPage.open();
|
||||
await ApplicationsListPage.pause();
|
||||
await expect(await ApplicationsListPage.pageHeader()).toBeDisplayed();
|
||||
await expect(await ApplicationsListPage.pageHeader()).toHaveText("Applications");
|
||||
}
|
||||
|
||||
async function fillOutTheApplication(title: string) {
|
||||
const newPrefix = randomId();
|
||||
|
||||
await (await ApplicationsListPage.startWizardButton()).click();
|
||||
await (await ApplicationWizardView.wizardTitle()).waitForDisplayed();
|
||||
await expect(await ApplicationWizardView.wizardTitle()).toHaveText("New application");
|
||||
await (await ApplicationWizardView.app.name()).setValue(`${title} - ${newPrefix}`);
|
||||
await (await ApplicationWizardView.app.uiSettings()).scrollIntoView();
|
||||
await (await ApplicationWizardView.app.uiSettings()).click();
|
||||
await (await ApplicationWizardView.app.launchUrl()).scrollIntoView();
|
||||
await (await ApplicationWizardView.app.launchUrl()).setValue("http://example.goauthentik.io");
|
||||
await (await ApplicationWizardView.nextButton()).click();
|
||||
await ApplicationWizardView.pause();
|
||||
}
|
||||
|
||||
async function getCommitMessage() {
|
||||
await (await ApplicationWizardView.successMessage()).waitForDisplayed();
|
||||
return await ApplicationWizardView.successMessage();
|
||||
}
|
||||
|
||||
async function fillOutTheProviderAndProceed(provider: TestSequence) {
|
||||
// The wizard automagically provides a name. If it doesn't, that's a bug.
|
||||
const wizardProvider = provider.filter((p) => p.length < 2 || p[1] !== "name");
|
||||
await $(">>>ak-wizard-page-type-create").waitForDisplayed();
|
||||
for await (const field of wizardProvider) {
|
||||
const thefunc = field[0];
|
||||
const args = field.slice(1);
|
||||
console.log(`Running ${args.join(", ")}`);
|
||||
// @ts-expect-error "This is a pretty alien call; I'm not surprised Typescript hates it."
|
||||
await thefunc.apply($, args);
|
||||
}
|
||||
|
||||
await (await ApplicationWizardView.nextButton()).click();
|
||||
await ApplicationWizardView.pause();
|
||||
}
|
||||
|
||||
export async function findWizardTitle() {
|
||||
return await (async () => {
|
||||
for await (const item of $$(">>>ak-wizard-title")) {
|
||||
if ((await item.isExisting()) && (await item.isDisplayed())) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
async function passByPoliciesAndCommit() {
|
||||
const title = await findWizardTitle();
|
||||
// Expect to be on the Bindings panel
|
||||
await expect(await title?.getText()).toEqual("Configure Policy/User/Group Bindings");
|
||||
await (await ApplicationWizardView.nextButton()).click();
|
||||
await ApplicationWizardView.pause();
|
||||
await (await ApplicationWizardView.submitPage()).waitForDisplayed();
|
||||
await (await ApplicationWizardView.nextButton()).click();
|
||||
await expect(await getCommitMessage()).toHaveText(SUCCESS_MESSAGE);
|
||||
}
|
||||
|
||||
async function itShouldConfigureApplicationsViaTheWizard(name: string, provider: TestSequence) {
|
||||
it(`Should successfully configure an application with a ${name} provider`, async () => {
|
||||
await reachTheApplicationsPage();
|
||||
await fillOutTheApplication(name);
|
||||
await fillOutTheProviderAndProceed(provider);
|
||||
await passByPoliciesAndCommit();
|
||||
});
|
||||
}
|
||||
|
||||
const providers: [string, TestProvider][] = [
|
||||
["Simple LDAP", simpleLDAPProviderForm],
|
||||
["Simple OAuth2", simpleOAuth2ProviderForm],
|
||||
["Simple Radius", simpleRadiusProviderForm],
|
||||
["Simple SAML", simpleSAMLProviderForm],
|
||||
["Simple SCIM", simpleSCIMProviderForm],
|
||||
["Simple Proxy", simpleProxyProviderForm],
|
||||
["Simple Forward Auth (single)", simpleForwardAuthProxyProviderForm],
|
||||
["Simple Forward Auth (domain)", simpleForwardAuthDomainProxyProviderForm],
|
||||
["Complete OAuth2", completeOAuth2ProviderForm],
|
||||
["Complete LDAP", completeLDAPProviderForm],
|
||||
["Complete Radius", completeRadiusProviderForm],
|
||||
["Complete SAML", completeSAMLProviderForm],
|
||||
["Complete SCIM", completeSCIMProviderForm],
|
||||
["Complete Proxy", completeProxyProviderForm],
|
||||
["Complete Forward Auth (single)", completeForwardAuthProxyProviderForm],
|
||||
["Complete Forward Auth (domain)", completeForwardAuthDomainProxyProviderForm],
|
||||
];
|
||||
|
||||
describe("Configuring Applications Via the Wizard", () => {
|
||||
for (const [name, provider] of providers) {
|
||||
itShouldConfigureApplicationsViaTheWizard(name, provider());
|
||||
}
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
import ProviderWizardView from "../pageobjects/provider-wizard.page.js";
|
||||
import ProvidersListPage from "../pageobjects/providers-list.page.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
import { login } from "../utils/login.js";
|
||||
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
async function reachTheProvider() {
|
||||
await ProvidersListPage.logout();
|
||||
await login();
|
||||
await ProvidersListPage.open();
|
||||
await expect(await ProvidersListPage.pageHeader()).toHaveText("Providers");
|
||||
|
||||
await ProvidersListPage.startWizardButton.click();
|
||||
await ProviderWizardView.wizardTitle.waitForDisplayed();
|
||||
await expect(await ProviderWizardView.wizardTitle).toHaveText("New provider");
|
||||
}
|
||||
|
||||
describe("Configure Oauth2 Providers", () => {
|
||||
it("Should configure a simple LDAP Application", async () => {
|
||||
const newProviderName = `New OAuth2 Provider - ${randomId()}`;
|
||||
|
||||
await reachTheProvider();
|
||||
|
||||
await $(">>>ak-wizard-page-type-create").waitForDisplayed();
|
||||
await $('>>>div[data-ouid-component-name="oauth2provider"]').scrollIntoView();
|
||||
await $('>>>div[data-ouid-component-name="oauth2provider"]').click();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await ProviderWizardView.pause();
|
||||
|
||||
return await $('>>>ak-form-element-horizontal[name="name"]').$(">>>input");
|
||||
await ProviderWizardView.oauth.setAuthorizationFlow(
|
||||
"default-provider-authorization-explicit-consent",
|
||||
);
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await ProviderWizardView.pause();
|
||||
|
||||
await ProvidersListPage.searchInput.setValue(newProviderName);
|
||||
await ProvidersListPage.clickSearchButton();
|
||||
await ProvidersListPage.pause();
|
||||
|
||||
const newProvider = await ProvidersListPage.findProviderRow();
|
||||
await newProvider.waitForDisplayed();
|
||||
expect(newProvider).toExist();
|
||||
expect(await newProvider.getText()).toHaveText(newProviderName);
|
||||
});
|
||||
});
|
||||
@@ -1,323 +0,0 @@
|
||||
import {
|
||||
checkIsPresent,
|
||||
clickButton,
|
||||
clickToggleGroup,
|
||||
setFormGroup,
|
||||
setRadio,
|
||||
setSearchSelect,
|
||||
setTextareaInput,
|
||||
setTextInput,
|
||||
setToggle,
|
||||
setTypeCreate,
|
||||
type TestProvider,
|
||||
type TestSequence,
|
||||
} from "../pageobjects/controls.js";
|
||||
import { ascii_letters, digits, randomId, randomString } from "../utils/index.js";
|
||||
|
||||
const newObjectName = (prefix: string) => `${prefix} - ${randomId()}`;
|
||||
|
||||
// components.schemas.OAuth2ProviderRequest
|
||||
//
|
||||
// - name
|
||||
// - authentication_flow
|
||||
// - authorization_flow
|
||||
// - invalidation_flow
|
||||
// - property_mappings
|
||||
// - client_type
|
||||
// - client_id
|
||||
// - client_secret
|
||||
// - access_code_validity
|
||||
// - access_token_validity
|
||||
// - refresh_token_validity
|
||||
// - include_claims_in_id_token
|
||||
// - signing_key
|
||||
// - encryption_key
|
||||
// - redirect_uris
|
||||
// - sub_mode
|
||||
// - issuer_mode
|
||||
// - jwks_sources
|
||||
//
|
||||
export const simpleOAuth2ProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "OAuth2/OpenID Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Oauth2 Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
];
|
||||
|
||||
export const completeOAuth2ProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "OAuth2/OpenID Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Oauth2 Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[setFormGroup, /Protocol settings/, "open"],
|
||||
[setRadio, "clientType", "Public"],
|
||||
// Switch back so we can make sure `clientSecret` is available.
|
||||
[setRadio, "clientType", "Confidential"],
|
||||
[checkIsPresent, '[name="clientId"]'],
|
||||
[checkIsPresent, '[name="clientSecret"]'],
|
||||
[setSearchSelect, "signingKey", /authentik Self-signed Certificate/],
|
||||
[setSearchSelect, "encryptionKey", /authentik Self-signed Certificate/],
|
||||
[setFormGroup, /Advanced flow settings/, "open"],
|
||||
[setSearchSelect, "authenticationFlow", /default-source-authentication/],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
[setFormGroup, /Advanced protocol settings/, "open"],
|
||||
[setTextInput, "accessCodeValidity", "minutes=2"],
|
||||
[setTextInput, "accessTokenValidity", "minutes=10"],
|
||||
[setTextInput, "refreshTokenValidity", "days=40"],
|
||||
[setToggle, "includeClaimsInIdToken", false],
|
||||
[checkIsPresent, '[name="redirectUris"]'],
|
||||
[setRadio, "subMode", "Based on the User's username"],
|
||||
[setRadio, "issuerMode", "Same identifier is used for all providers"],
|
||||
[setFormGroup, /Machine-to-Machine authentication settings/, "open"],
|
||||
[checkIsPresent, '[name="jwtFederationSources"]'],
|
||||
[checkIsPresent, '[name="jwtFederationProviders"]'],
|
||||
];
|
||||
|
||||
// components.schemas.LDAPProviderRequest
|
||||
//
|
||||
// - name
|
||||
// - authentication_flow
|
||||
// - authorization_flow
|
||||
// - invalidation_flow
|
||||
// - base_dn
|
||||
// - certificate
|
||||
// - tls_server_name
|
||||
// - uid_start_number
|
||||
// - gid_start_number
|
||||
// - search_mode
|
||||
// - bind_mode
|
||||
// - mfa_support
|
||||
//
|
||||
export const simpleLDAPProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "LDAP Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New LDAP Provider")],
|
||||
// This will never not weird me out.
|
||||
[setFormGroup, /Flow settings/, "open"],
|
||||
[setSearchSelect, "authorizationFlow", /default-authentication-flow/],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
];
|
||||
|
||||
export const completeLDAPProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "LDAP Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New LDAP Provider")],
|
||||
[setFormGroup, /Flow settings/, "open"],
|
||||
[setFormGroup, /Protocol settings/, "open"],
|
||||
[setSearchSelect, "authorizationFlow", /default-authentication-flow/],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
[setTextInput, "baseDn", "DC=ldap-2,DC=goauthentik,DC=io"],
|
||||
[setSearchSelect, "certificate", /authentik Self-signed Certificate/],
|
||||
[checkIsPresent, '[name="tlsServerName"]'],
|
||||
[setTextInput, "uidStartNumber", "2001"],
|
||||
[setTextInput, "gidStartNumber", "4001"],
|
||||
[setRadio, "searchMode", "Direct querying"],
|
||||
[setRadio, "bindMode", "Direct binding"],
|
||||
[setToggle, "mfaSupport", false],
|
||||
];
|
||||
|
||||
export const simpleRadiusProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "Radius Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Radius Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-authentication-flow/],
|
||||
];
|
||||
|
||||
export const completeRadiusProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "Radius Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Radius Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-authentication-flow/],
|
||||
[setFormGroup, /Advanced flow settings/, "open"],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
[setFormGroup, /Protocol settings/, "open"],
|
||||
[setToggle, "mfaSupport", false],
|
||||
[setTextInput, "clientNetworks", ""],
|
||||
[setTextInput, "clientNetworks", "0.0.0.0/0, ::/0"],
|
||||
[setTextInput, "sharedSecret", randomString(128, ascii_letters + digits)],
|
||||
[checkIsPresent, '[name="propertyMappings"]'],
|
||||
];
|
||||
|
||||
// provider_components.schemas.SAMLProviderRequest.yml
|
||||
//
|
||||
// - name
|
||||
// - authentication_flow
|
||||
// - authorization_flow
|
||||
// - invalidation_flow
|
||||
// - property_mappings
|
||||
// - acs_url
|
||||
// - audience
|
||||
// - issuer
|
||||
// - assertion_valid_not_before
|
||||
// - assertion_valid_not_on_or_after
|
||||
// - session_valid_not_on_or_after
|
||||
// - name_id_mapping
|
||||
// - digest_algorithm
|
||||
// - signature_algorithm
|
||||
// - signing_kp
|
||||
// - verification_kp
|
||||
// - encryption_kp
|
||||
// - sign_assertion
|
||||
// - sign_response
|
||||
// - sp_binding
|
||||
// - default_relay_state
|
||||
//
|
||||
export const simpleSAMLProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "SAML Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New SAML Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[setTextInput, "acsUrl", "http://example.com:8000/"],
|
||||
];
|
||||
|
||||
export const completeSAMLProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "SAML Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New SAML Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[setTextInput, "acsUrl", "http://example.com:8000/"],
|
||||
[setTextInput, "issuer", "someone-else"],
|
||||
[setRadio, "spBinding", "Post"],
|
||||
[setTextInput, "audience", ""],
|
||||
[setFormGroup, /Advanced flow settings/, "open"],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
[setSearchSelect, "authenticationFlow", /default-source-authentication/],
|
||||
[setFormGroup, /Advanced protocol settings/, "open"],
|
||||
[checkIsPresent, '[name="propertyMappings"]'],
|
||||
[setSearchSelect, "signingKp", /authentik Self-signed Certificate/],
|
||||
[setSearchSelect, "verificationKp", /authentik Self-signed Certificate/],
|
||||
[setSearchSelect, "encryptionKp", /authentik Self-signed Certificate/],
|
||||
[setSearchSelect, "nameIdMapping", /authentik default SAML Mapping. Username/],
|
||||
[setTextInput, "assertionValidNotBefore", "minutes=-10"],
|
||||
[setTextInput, "assertionValidNotOnOrAfter", "minutes=10"],
|
||||
[setTextInput, "sessionValidNotOnOrAfter", "minutes=172800"],
|
||||
[checkIsPresent, '[name="defaultRelayState"]'],
|
||||
[setRadio, "digestAlgorithm", "SHA512"],
|
||||
[setRadio, "signatureAlgorithm", "RSA-SHA512"],
|
||||
// These are only available after the signingKp is defined.
|
||||
[setToggle, "signAssertion", true],
|
||||
[setToggle, "signResponse", true],
|
||||
];
|
||||
|
||||
// provider_components.schemas.SCIMProviderRequest.yml
|
||||
//
|
||||
// - name
|
||||
// - property_mappings
|
||||
// - property_mappings_group
|
||||
// - url
|
||||
// - verify_certificates
|
||||
// - token
|
||||
// - exclude_users_service_account
|
||||
// - filter_group
|
||||
//
|
||||
export const simpleSCIMProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "SCIM Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New SCIM Provider")],
|
||||
[setTextInput, "url", "http://example.com:8000/"],
|
||||
[setTextInput, "token", "insert-real-token-here"],
|
||||
];
|
||||
|
||||
export const completeSCIMProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "SCIM Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New SCIM Provider")],
|
||||
[setTextInput, "url", "http://example.com:8000/"],
|
||||
[setToggle, "verifyCertificates", false],
|
||||
[setTextInput, "token", "insert-real-token-here"],
|
||||
[setFormGroup, /Protocol settings/, "open"],
|
||||
[setFormGroup, /User filtering/, "open"],
|
||||
[setToggle, "excludeUsersServiceAccount", false],
|
||||
[setSearchSelect, "filterGroup", /authentik Admins/],
|
||||
[setFormGroup, /Attribute mapping/, "open"],
|
||||
[checkIsPresent, '[name="propertyMappings"]'],
|
||||
[checkIsPresent, '[name="propertyMappingsGroup"]'],
|
||||
];
|
||||
|
||||
// provider_components.schemas.ProxyProviderRequest.yml
|
||||
//
|
||||
// - name
|
||||
// - authentication_flow
|
||||
// - authorization_flow
|
||||
// - invalidation_flow
|
||||
// - property_mappings
|
||||
// - internal_host
|
||||
// - external_host
|
||||
// - internal_host_ssl_validation
|
||||
// - certificate
|
||||
// - skip_path_regex
|
||||
// - basic_auth_enabled
|
||||
// - basic_auth_password_attribute
|
||||
// - basic_auth_user_attribute
|
||||
// - mode
|
||||
// - intercept_header_auth
|
||||
// - cookie_domain
|
||||
// - jwks_sources
|
||||
// - access_token_validity
|
||||
// - refresh_token_validity
|
||||
// - refresh_token_validity is not handled in any of our forms. On purpose.
|
||||
// - internal_host_ssl_validation
|
||||
// - only on ProxyMode
|
||||
|
||||
export const simpleProxyProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "Proxy Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Proxy Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[clickToggleGroup, "proxy-type-toggle", "Proxy"],
|
||||
[setTextInput, "externalHost", "http://example.com:8000/"],
|
||||
[setTextInput, "internalHost", "http://example.com:8001/"],
|
||||
];
|
||||
|
||||
export const simpleForwardAuthProxyProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "Proxy Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Forward Auth Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[clickToggleGroup, "proxy-type-toggle", "Forward auth (single application)"],
|
||||
[setTextInput, "externalHost", "http://example.com:8000/"],
|
||||
];
|
||||
|
||||
export const simpleForwardAuthDomainProxyProviderForm: TestProvider = () => [
|
||||
[setTypeCreate, "selectProviderType", "Proxy Provider"],
|
||||
[clickButton, "Next"],
|
||||
[setTextInput, "name", newObjectName("New Forward Auth Domain Level Provider")],
|
||||
[setSearchSelect, "authorizationFlow", /default-provider-authorization-explicit-consent/],
|
||||
[clickToggleGroup, "proxy-type-toggle", "Forward auth (domain level)"],
|
||||
[setTextInput, "externalHost", "http://example.com:8000/"],
|
||||
[setTextInput, "cookieDomain", "somedomain.tld"],
|
||||
];
|
||||
|
||||
const proxyModeCompletions: TestSequence = [
|
||||
[setTextInput, "accessTokenValidity", "hours=36"],
|
||||
[setFormGroup, /Advanced protocol settings/, "open"],
|
||||
[setSearchSelect, "certificate", /authentik Self-signed Certificate/],
|
||||
[checkIsPresent, '[name="propertyMappings"]'],
|
||||
[setTextareaInput, "skipPathRegex", "."],
|
||||
[setFormGroup, /Authentication settings/, "open"],
|
||||
[setToggle, "interceptHeaderAuth", false],
|
||||
[setToggle, "basicAuthEnabled", true],
|
||||
[setTextInput, "basicAuthUserAttribute", "authorized-user"],
|
||||
[setTextInput, "basicAuthPasswordAttribute", "authorized-user-password"],
|
||||
[setFormGroup, /Advanced flow settings/, "open"],
|
||||
[setSearchSelect, "authenticationFlow", /default-source-authentication/],
|
||||
[setSearchSelect, "invalidationFlow", /default-invalidation-flow/],
|
||||
[checkIsPresent, '[name="jwtFederationSources"]'],
|
||||
[checkIsPresent, '[name="jwtFederationProviders"]'],
|
||||
];
|
||||
|
||||
export const completeProxyProviderForm: TestProvider = () => [
|
||||
...simpleProxyProviderForm(),
|
||||
[setToggle, "internalHostSslValidation", false],
|
||||
...proxyModeCompletions,
|
||||
];
|
||||
|
||||
export const completeForwardAuthProxyProviderForm: TestProvider = () => [
|
||||
...simpleForwardAuthProxyProviderForm(),
|
||||
...proxyModeCompletions,
|
||||
];
|
||||
|
||||
export const completeForwardAuthDomainProxyProviderForm: TestProvider = () => [
|
||||
...simpleForwardAuthProxyProviderForm(),
|
||||
...proxyModeCompletions,
|
||||
];
|
||||
@@ -1,98 +0,0 @@
|
||||
import { type TestProvider, type TestSequence } from "../pageobjects/controls.js";
|
||||
import ProviderWizardView from "../pageobjects/provider-wizard.page.js";
|
||||
import ProvidersListPage from "../pageobjects/providers-list.page.js";
|
||||
import { login } from "../utils/login.js";
|
||||
import {
|
||||
completeForwardAuthDomainProxyProviderForm,
|
||||
completeForwardAuthProxyProviderForm,
|
||||
completeLDAPProviderForm,
|
||||
completeOAuth2ProviderForm,
|
||||
completeProxyProviderForm,
|
||||
completeRadiusProviderForm,
|
||||
completeSAMLProviderForm,
|
||||
completeSCIMProviderForm,
|
||||
simpleForwardAuthDomainProxyProviderForm,
|
||||
simpleForwardAuthProxyProviderForm,
|
||||
simpleLDAPProviderForm,
|
||||
simpleOAuth2ProviderForm,
|
||||
simpleProxyProviderForm,
|
||||
simpleRadiusProviderForm,
|
||||
simpleSAMLProviderForm,
|
||||
simpleSCIMProviderForm,
|
||||
} from "./provider-shared-sequences.js";
|
||||
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
async function reachTheProvider() {
|
||||
await ProvidersListPage.logout();
|
||||
await login();
|
||||
await ProvidersListPage.open();
|
||||
await expect(await ProvidersListPage.pageHeader()).toHaveText("Providers");
|
||||
await expect(await containedMessages()).not.toContain("Successfully created provider.");
|
||||
|
||||
await ProvidersListPage.startWizardButton.click();
|
||||
await ProviderWizardView.wizardTitle.waitForDisplayed();
|
||||
await expect(await ProviderWizardView.wizardTitle).toHaveText("New provider");
|
||||
}
|
||||
|
||||
const containedMessages = async () =>
|
||||
await (async () => {
|
||||
const messages = [];
|
||||
for await (const alert of $("ak-message-container").$$("ak-message")) {
|
||||
messages.push(await alert.$("p.pf-c-alert__title").getText());
|
||||
}
|
||||
return messages;
|
||||
})();
|
||||
|
||||
const hasProviderSuccessMessage = async () =>
|
||||
await browser.waitUntil(
|
||||
async () => (await containedMessages()).includes("Successfully created provider."),
|
||||
{ timeout: 1000, timeoutMsg: "Expected to see provider success message." },
|
||||
);
|
||||
|
||||
async function fillOutFields(fields: TestSequence) {
|
||||
for (const field of fields) {
|
||||
const thefunc = field[0];
|
||||
const args = field.slice(1);
|
||||
// @ts-expect-error "This is a pretty alien call, so I'm not surprised Typescript doesn't like it."
|
||||
await thefunc.apply($, args);
|
||||
}
|
||||
}
|
||||
|
||||
async function itShouldConfigureASimpleProvider(name: string, provider: TestSequence) {
|
||||
it(`Should successfully configure a ${name} provider`, async () => {
|
||||
await reachTheProvider();
|
||||
await $("ak-wizard-page-type-create").waitForDisplayed();
|
||||
await fillOutFields(provider);
|
||||
await ProviderWizardView.pause();
|
||||
await ProviderWizardView.nextButton.click();
|
||||
await hasProviderSuccessMessage();
|
||||
});
|
||||
}
|
||||
|
||||
type ProviderTest = [string, TestProvider];
|
||||
|
||||
describe("Configuring Providers", () => {
|
||||
const providers: ProviderTest[] = [
|
||||
["Simple LDAP", simpleLDAPProviderForm],
|
||||
["Simple OAuth2", simpleOAuth2ProviderForm],
|
||||
["Simple Radius", simpleRadiusProviderForm],
|
||||
["Simple SAML", simpleSAMLProviderForm],
|
||||
["Simple SCIM", simpleSCIMProviderForm],
|
||||
["Simple Proxy", simpleProxyProviderForm],
|
||||
["Simple Forward Auth (single application)", simpleForwardAuthProxyProviderForm],
|
||||
["Simple Forward Auth (domain level)", simpleForwardAuthDomainProxyProviderForm],
|
||||
["Complete OAuth2", completeOAuth2ProviderForm],
|
||||
["Complete LDAP", completeLDAPProviderForm],
|
||||
["Complete Radius", completeRadiusProviderForm],
|
||||
["Complete SAML", completeSAMLProviderForm],
|
||||
["Complete SCIM", completeSCIMProviderForm],
|
||||
["Complete Proxy", completeProxyProviderForm],
|
||||
["Complete Forward Auth (single application)", completeForwardAuthProxyProviderForm],
|
||||
["Complete Forward Auth (domain level)", completeForwardAuthDomainProxyProviderForm],
|
||||
];
|
||||
|
||||
for (const [name, provider] of providers) {
|
||||
itShouldConfigureASimpleProvider(name, provider());
|
||||
}
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"target": "es2022",
|
||||
"types": [
|
||||
"node",
|
||||
"@wdio/globals/types",
|
||||
"expect-webdriverio",
|
||||
"@wdio/mocha-framework",
|
||||
"@types/mocha"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["."]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export const BAD_USERNAME = process.env.AK_BAD_USERNAME ?? "bad-username@bad-login.io";
|
||||
export const GOOD_USERNAME = process.env.AK_GOOD_USERNAME ?? "test-admin@goauthentik.io";
|
||||
|
||||
export const BAD_PASSWORD = process.env.AK_BAD_PASSWORD ?? "-this-is-a-bad-password-";
|
||||
export const GOOD_PASSWORD = process.env.AK_GOOD_PASSWORD ?? "test-runner";
|
||||
@@ -1,27 +0,0 @@
|
||||
// Taken from python's string module
|
||||
export const ascii_lowercase = "abcdefghijklmnopqrstuvwxyz";
|
||||
export const ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
export const ascii_letters = ascii_lowercase + ascii_uppercase;
|
||||
export const digits = "0123456789";
|
||||
export const hexdigits = digits + "abcdef" + "ABCDEF";
|
||||
export const octdigits = "01234567";
|
||||
export const punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
|
||||
|
||||
export function randomString(len: number, charset: string): string {
|
||||
const chars = [];
|
||||
const array = new Uint8Array(len);
|
||||
globalThis.crypto.getRandomValues(array);
|
||||
for (let index = 0; index < len; index++) {
|
||||
chars.push(charset[Math.floor(charset.length * (array[index] / Math.pow(2, 8)))]);
|
||||
}
|
||||
return chars.join("");
|
||||
}
|
||||
|
||||
export function randomId() {
|
||||
let dt = new Date().getTime();
|
||||
return "xxxxxxxx".replace(/x/g, (c) => {
|
||||
const r = (dt + Math.random() * 16) % 16 | 0;
|
||||
dt = Math.floor(dt / 16);
|
||||
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||
});
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import { GOOD_PASSWORD, GOOD_USERNAME } from "./constants.js";
|
||||
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
export const login = async () => {
|
||||
await LoginPage.open();
|
||||
await LoginPage.login(GOOD_USERNAME, GOOD_PASSWORD);
|
||||
await expect(await UserLibraryPage.pageHeader()).toHaveText("My applications");
|
||||
};
|
||||
@@ -1,120 +0,0 @@
|
||||
/**
|
||||
* @file WebdriverIO configuration file for **component unit tests**.
|
||||
*
|
||||
* @see https://webdriver.io/docs/configurationfile.html
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
|
||||
import { addCommands } from "../commands.mjs";
|
||||
|
||||
import { createBundleDefinitions } from "#bundler/utils/node";
|
||||
import { inlineCSSPlugin } from "#bundler/vite-plugin-lit-css/node";
|
||||
import { PackageRoot } from "#paths/node";
|
||||
|
||||
const headless = !process.env.HEADLESS || !!process.env.CI;
|
||||
const lemmeSee = !!process.env.WDIO_LEMME_SEE;
|
||||
|
||||
/**
|
||||
* @type {WebdriverIO.Capabilities[]}
|
||||
*/
|
||||
const capabilities = [];
|
||||
|
||||
const DEFAULT_MAX_INSTANCES = 10;
|
||||
|
||||
let maxInstances = 1;
|
||||
|
||||
if (headless) {
|
||||
maxInstances = process.env.MAX_INSTANCES
|
||||
? parseInt(process.env.MAX_INSTANCES, 10)
|
||||
: DEFAULT_MAX_INSTANCES;
|
||||
}
|
||||
|
||||
if (!process.env.WDIO_SKIP_CHROME) {
|
||||
/**
|
||||
* @satisfies {WebdriverIO.Capabilities}
|
||||
*/
|
||||
const chromeBrowserConfig = {
|
||||
"browserName": "chrome",
|
||||
"goog:chromeOptions": {
|
||||
args: ["disable-search-engine-choice-screen"],
|
||||
},
|
||||
};
|
||||
|
||||
if (headless) {
|
||||
chromeBrowserConfig["goog:chromeOptions"].args.push(
|
||||
"headless",
|
||||
"disable-gpu",
|
||||
"no-sandbox",
|
||||
"window-size=1280,672",
|
||||
"browser-test",
|
||||
);
|
||||
}
|
||||
|
||||
capabilities.push(chromeBrowserConfig);
|
||||
}
|
||||
|
||||
if (process.env.WDIO_TEST_SAFARI) {
|
||||
capabilities.push({
|
||||
browserName: "safari",
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.WDIO_TEST_FIREFOX) {
|
||||
capabilities.push({
|
||||
browserName: "firefox",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {WebdriverIO.BrowserRunnerOptions}
|
||||
*/
|
||||
const browserRunnerOptions = {
|
||||
viteConfig: {
|
||||
define: createBundleDefinitions(),
|
||||
plugins: [
|
||||
// ---
|
||||
inlineCSSPlugin(),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @satisfies {WebdriverIO.Config}
|
||||
*/
|
||||
export const config = {
|
||||
runner: ["browser", browserRunnerOptions],
|
||||
|
||||
tsConfigPath: path.resolve(PackageRoot, "tests", "tsconfig.test.json"),
|
||||
|
||||
specs: [path.resolve(PackageRoot, "tests", "specs", "**", "*.ts")],
|
||||
|
||||
exclude: [],
|
||||
|
||||
maxInstances,
|
||||
capabilities,
|
||||
logLevel: "warn",
|
||||
bail: 0,
|
||||
waitforTimeout: 12000,
|
||||
connectionRetryTimeout: 12000,
|
||||
connectionRetryCount: 3,
|
||||
|
||||
framework: "mocha",
|
||||
|
||||
reporters: ["spec"],
|
||||
|
||||
mochaOpts: {
|
||||
ui: "bdd",
|
||||
timeout: 60000,
|
||||
},
|
||||
/**
|
||||
* @param {WebdriverIO.Browser} browser
|
||||
*/
|
||||
before(_capabilities, _specs, browser) {
|
||||
addCommands(browser);
|
||||
},
|
||||
|
||||
afterTest() {
|
||||
if (lemmeSee) return browser.pause(500);
|
||||
},
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
// @file TSConfig used during tests.
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "webdriverio/async", "@wdio/cucumber-framework", "expect-webdriverio"],
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [
|
||||
"ES5",
|
||||
"ES2015",
|
||||
"ES2016",
|
||||
"ES2017",
|
||||
"ES2018",
|
||||
"ES2019",
|
||||
"ES2020",
|
||||
"ESNext",
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"WebWorker"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
-14
@@ -1,14 +0,0 @@
|
||||
declare namespace WebdriverIO {
|
||||
interface Element {
|
||||
/**
|
||||
* Focus on the element.
|
||||
* @monkeypatch
|
||||
*/
|
||||
focus(): Promise<void>;
|
||||
/**
|
||||
* Blur the element.
|
||||
* @monkeypatch
|
||||
*/
|
||||
blur(): Promise<void>;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/**
|
||||
* @file WebdriverIO configuration file for **integration tests**.
|
||||
*
|
||||
* @see https://webdriver.io/docs/configurationfile.html
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
|
||||
import { addCommands } from "./commands.mjs";
|
||||
|
||||
import { createBundleDefinitions } from "#bundler/utils/node";
|
||||
import { inlineCSSPlugin } from "#bundler/vite-plugin-lit-css/node";
|
||||
import { PackageRoot } from "#paths/node";
|
||||
|
||||
import { browser } from "@wdio/globals";
|
||||
|
||||
/// <reference types="@wdio/globals/types" />
|
||||
/// <reference types="./types/webdriver.js" />
|
||||
|
||||
const headless = !process.env.HEADLESS || !!process.env.CI;
|
||||
const lemmeSee = !!process.env.WDIO_LEMME_SEE;
|
||||
|
||||
/**
|
||||
* @type {WebdriverIO.Capabilities[]}
|
||||
*/
|
||||
const capabilities = [];
|
||||
|
||||
if (!process.env.WDIO_SKIP_CHROME) {
|
||||
/**
|
||||
* @satisfies {WebdriverIO.Capabilities}
|
||||
*/
|
||||
const chromeBrowserConfig = {
|
||||
"browserName": "chrome",
|
||||
"goog:chromeOptions": {
|
||||
args: ["disable-search-engine-choice-screen"],
|
||||
},
|
||||
};
|
||||
|
||||
if (headless) {
|
||||
chromeBrowserConfig["goog:chromeOptions"].args.push(
|
||||
"headless",
|
||||
"disable-gpu",
|
||||
"no-sandbox",
|
||||
"window-size=1280,672",
|
||||
"browser-test",
|
||||
);
|
||||
}
|
||||
|
||||
capabilities.push(chromeBrowserConfig);
|
||||
}
|
||||
|
||||
if (process.env.WDIO_TEST_SAFARI) {
|
||||
capabilities.push({
|
||||
browserName: "safari",
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.WDIO_TEST_FIREFOX) {
|
||||
capabilities.push({
|
||||
browserName: "firefox",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {WebdriverIO.BrowserRunnerOptions}
|
||||
*/
|
||||
const browserRunnerOptions = {
|
||||
viteConfig: {
|
||||
define: createBundleDefinitions(),
|
||||
plugins: [
|
||||
// ---
|
||||
inlineCSSPlugin(),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @satisfies {WebdriverIO.Config}
|
||||
*/
|
||||
export const config = {
|
||||
runner: ["browser", browserRunnerOptions],
|
||||
|
||||
tsConfigPath: path.resolve(PackageRoot, "tsconfig.test.json"),
|
||||
|
||||
specs: [path.resolve(PackageRoot, "src", "**", "*.test.ts")],
|
||||
|
||||
exclude: [],
|
||||
maxInstances: 1,
|
||||
capabilities,
|
||||
|
||||
logLevel: "warn",
|
||||
baseUrl: "http://localhost",
|
||||
waitforTimeout: 10000,
|
||||
connectionRetryTimeout: 120000,
|
||||
connectionRetryCount: 3,
|
||||
|
||||
framework: "mocha",
|
||||
reporters: ["spec"],
|
||||
mochaOpts: {
|
||||
ui: "bdd",
|
||||
timeout: 60000,
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {WebdriverIO.Browser} browser
|
||||
*/
|
||||
before(_capabilities, _specs, browser) {
|
||||
addCommands(browser);
|
||||
},
|
||||
|
||||
afterTest() {
|
||||
if (lemmeSee) return browser.pause(500);
|
||||
},
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* @file Web Test Runner configuration.
|
||||
* @see https://modern-web.dev/docs/test-runner/cli-and-configuration/
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {import('@web/test-runner').TestRunnerConfig}
|
||||
*/
|
||||
const config = {
|
||||
files: ["dist/**/*.spec.js"],
|
||||
nodeResolve: {
|
||||
exportConditions: ["browser", "production"],
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
Reference in New Issue
Block a user