web: merge branch 'web/wdio-2' into application-wizard-2-with-api-and-tests
* web/wdio-2: web/test: changed the name of one test to reflect it's 'good' status web/adding tests: added comments and cleaned up some administrative features. web/add webdriverIO testing layer
This commit is contained in:
commit
6c7e30dc7d
|
@ -0,0 +1,4 @@
|
|||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:sonarjs/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "sonarjs"],
|
||||
"rules": {
|
||||
"indent": "off",
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"semi": ["error", "always"],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"sonarjs/cognitive-complexity": ["error", 9],
|
||||
"sonarjs/no-nested-template-literals": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# don't ever lint node_modules
|
||||
node_modules
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 100,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "consistent",
|
||||
"requirePragma": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"plugins": ["@trivago/prettier-plugin-sort-imports"],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
.PHONY: help precommit admin-user test-good-login test-bad-login
|
||||
|
||||
help: ## Show this help
|
||||
@echo "\nSpecify a command. The choices are:\n"
|
||||
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-20s\033[m %s\n", $$1, $$2}'
|
||||
@echo ""
|
||||
|
||||
ROOT := $(shell git rev-parse --show-toplevel 2> /dev/null)
|
||||
WDIO = npm run wdio
|
||||
SPEC = $(WDIO) -- --spec ./test/specs
|
||||
|
||||
LOCAL_BLUEPRINTS=$(ROOT)/blueprints/local
|
||||
|
||||
node_modules: ## Runs `npm install` to prepare this feature
|
||||
npm ci
|
||||
|
||||
precommit: node_modules ## Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
|
||||
npm run precommit
|
||||
|
||||
# Actual tests are down below:
|
||||
|
||||
$(ROOT)/blueprints/local/admin-user.yaml:
|
||||
mkdir -p $(LOCAL_BLUEPRINTS)
|
||||
cp ./blueprints/admin-user.yaml $(LOCAL_BLUEPRINTS)
|
||||
cd $(ROOT) && ak apply_blueprint local/admin-user.yaml
|
||||
|
||||
|
||||
admin-user: $(ROOT)/blueprints/local/admin-user.yaml
|
||||
|
||||
|
||||
test-good-login: node_modules admin-user ## Test that we can log into the server. Requires a running instance of the server.
|
||||
$(SPEC)/good-login.ts
|
||||
|
||||
|
||||
test-bad-login: node_modules admin-user ## Test that bad usernames and passwords create appropriate error messages
|
||||
$(SPEC)/bad-logins.ts
|
|
@ -0,0 +1,18 @@
|
|||
context: {}
|
||||
entries:
|
||||
- attrs:
|
||||
attributes: {}
|
||||
email: test-admin@goauthentik.io
|
||||
is_active: true
|
||||
name: authentik Default Admin
|
||||
password: test-runner
|
||||
path: users
|
||||
type: internal
|
||||
groups:
|
||||
- !Find [authentik_core.group, [name, "authentik Admins"]]
|
||||
conditions: []
|
||||
id: null
|
||||
identifiers:
|
||||
username: akadmin
|
||||
model: authentik_core.user
|
||||
state: present
|
File diff suppressed because it is too large
Load Diff
|
@ -2,15 +2,28 @@
|
|||
"name": "my-new-project",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
"@typescript-eslint/parser": "^6.7.2",
|
||||
"@wdio/cli": "^8.16.11",
|
||||
"@wdio/local-runner": "^8.16.11",
|
||||
"@wdio/mocha-framework": "^8.16.11",
|
||||
"@wdio/spec-reporter": "^8.16.9",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-sonarjs": "^0.21.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2",
|
||||
"wdio-wait-for": "^3.0.7"
|
||||
},
|
||||
"scripts": {
|
||||
"wdio": "wdio run ./wdio.conf.ts"
|
||||
"wdio": "wdio run ./wdio.conf.ts",
|
||||
"lint:precommit": "eslint --max-warnings 0 --config ./.eslintrc.precommit.json $(git status --porcelain . | grep '^[AM?][M?]' | cut -d'/' -f3- | grep -E '\\.(ts|js|tsx|jsx)$')",
|
||||
"lint:spelling": "codespell -D - -D $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-dictionary.txt -I $(git rev-parse --show-toplevel 2> /dev/null)/.github/codespell-words.txt ./test -s",
|
||||
"precommit": "run-s lint:precommit lint:spelling prettier",
|
||||
"prettier-check": "prettier --check .",
|
||||
"prettier": "prettier --write ."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { $ } from "@wdio/globals";
|
||||
import AdminPage from "./admin.page.js";
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { $ } from "@wdio/globals";
|
||||
import AdminPage from "./admin.page.js";
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
|
@ -9,9 +9,14 @@ class ApplicationsListPage extends AdminPage {
|
|||
* define selectors using getter methods
|
||||
*/
|
||||
|
||||
async startCreateApplicationWizard() {
|
||||
await $('>>>ak-wizard-frame button[slot="trigger"]').click();
|
||||
get startWizardButton() {
|
||||
return $('>>>ak-wizard-frame button[slot="trigger"]');
|
||||
}
|
||||
|
||||
async open() {
|
||||
return await super.open("if/admin/#/core/applications");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ApplicationsListPage();
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { $ } from "@wdio/globals";
|
||||
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 {
|
||||
/**
|
||||
* define selectors using getter methods
|
||||
* Selectors
|
||||
*/
|
||||
get inputUsername() {
|
||||
return $('>>>input[name="uidField"]');
|
||||
|
@ -26,20 +26,20 @@ class LoginPage extends Page {
|
|||
}
|
||||
|
||||
/**
|
||||
* a method to encapsule automation code to interact with the page
|
||||
* e.g. to login using username and password
|
||||
* Specific interactions
|
||||
*/
|
||||
|
||||
async username(username: string) {
|
||||
await this.inputPassword.isDisplayed();
|
||||
await this.inputUsername.waitForClickable();
|
||||
await this.inputUsername.setValue(username);
|
||||
await this.btnSubmit.isEnabled();
|
||||
await this.btnSubmit.waitForEnabled();
|
||||
await this.btnSubmit.click();
|
||||
}
|
||||
|
||||
async password(password: string) {
|
||||
await this.inputPassword.isDisplayed();
|
||||
await this.inputPassword.waitForClickable();
|
||||
await this.inputPassword.setValue(password);
|
||||
await this.btnSubmit.isEnabled();
|
||||
await this.btnSubmit.waitForEnabled();
|
||||
await this.btnSubmit.click();
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ class LoginPage extends Page {
|
|||
}
|
||||
|
||||
/**
|
||||
* overwrite specific options to adapt it to page object
|
||||
* URL for accessing this page (if necessary)
|
||||
*/
|
||||
open() {
|
||||
return super.open("");
|
||||
|
|
|
@ -5,7 +5,7 @@ export class OauthForm extends Page {
|
|||
await this.searchSelect(
|
||||
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
|
||||
"authorizationFlow",
|
||||
`button*=${selector}`
|
||||
`button*=${selector}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import { browser } from "@wdio/globals";
|
|||
const CLICK_TIME_DELAY = 250;
|
||||
|
||||
/**
|
||||
* main page object containing all methods, selectors and functionality
|
||||
* that is shared across all page objects
|
||||
* Main page object containing all methods, selectors and functionality that is shared across all
|
||||
* page objects
|
||||
*/
|
||||
export default class Page {
|
||||
/**
|
||||
|
@ -22,6 +22,15 @@ export default class Page {
|
|||
return 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);
|
||||
await inputBind.click();
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export type Constructor<T = object> = new (...args: any[]) => T;
|
|
@ -1,5 +1,5 @@
|
|||
import { $ } from "@wdio/globals";
|
||||
import Page from "./page.js";
|
||||
import { $ } from "@wdio/globals";
|
||||
|
||||
/**
|
||||
* sub page containing specific selectors and methods for a specific page
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { expect } from "@wdio/globals";
|
||||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import AdminOverviewPage from "../pageobjects/admin-overview.page.js";
|
||||
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
|
||||
import ApplicationWizard from "../pageobjects/application-wizard.page.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
|
||||
describe("Configure new Application", () => {
|
||||
it("should navigate to the wizard and configure LDAP", async () => {
|
||||
const newId = randomId();
|
||||
|
||||
await LoginPage.open();
|
||||
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
|
||||
|
||||
expect(await UserLibraryPage.pageHeader).toHaveText("My Applications");
|
||||
await UserLibraryPage.goToAdmin();
|
||||
|
||||
expect(await AdminOverviewPage.pageHeader).toHaveText("Welcome, ");
|
||||
await AdminOverviewPage.openApplicationsListPage();
|
||||
|
||||
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
|
||||
ApplicationsListPage.startCreateApplicationWizard();
|
||||
|
||||
await ApplicationWizard.app.name.setValue(`Test application ${newId}`);
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await (await ApplicationWizard.getProviderType("ldapprovider")).click();
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await ApplicationWizard.ldap.setBindFlow("default-authentication-flow");
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await expect(await ApplicationWizard.commitMessage).toHaveText(
|
||||
"Your application has been saved"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import { expect } from "@wdio/globals";
|
||||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import AdminOverviewPage from "../pageobjects/admin-overview.page.js";
|
||||
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
|
||||
import ApplicationWizard from "../pageobjects/application-wizard.page.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
|
||||
describe("Configure new Application", () => {
|
||||
it("should navigate to the wizard and configure Oauth2", async () => {
|
||||
const newId = randomId();
|
||||
|
||||
await LoginPage.open();
|
||||
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
|
||||
|
||||
expect(await UserLibraryPage.pageHeader).toHaveText("My Applications");
|
||||
await UserLibraryPage.goToAdmin();
|
||||
|
||||
expect(await AdminOverviewPage.pageHeader).toHaveText("Welcome, ");
|
||||
await AdminOverviewPage.openApplicationsListPage();
|
||||
|
||||
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
|
||||
ApplicationsListPage.startCreateApplicationWizard();
|
||||
|
||||
await ApplicationWizard.app.name.setValue(`Test application ${newId}`);
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await (await ApplicationWizard.getProviderType("oauth2provider")).click();
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await ApplicationWizard.oauth.setAuthorizationFlow(
|
||||
"default-provider-authorization-explicit-consent"
|
||||
);
|
||||
await ApplicationWizard.nextButton.click();
|
||||
await expect(await ApplicationWizard.commitMessage).toHaveText(
|
||||
"Your application has been saved"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,20 +1,20 @@
|
|||
import { expect } from "@wdio/globals";
|
||||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import { BAD_PASSWORD, BAD_USERNAME, GOOD_USERNAME } from "../utils/constants.js";
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
describe("Log into Authentik", () => {
|
||||
it("should fail on a bad username", async () => {
|
||||
await LoginPage.open();
|
||||
await LoginPage.username("bad-username@bad-logio.io");
|
||||
await LoginPage.username(BAD_USERNAME);
|
||||
const failure = await LoginPage.authFailure;
|
||||
expect(failure).toHaveText("Failed to authenticate.");
|
||||
});
|
||||
|
||||
it("should fail on a bad password", async () => {
|
||||
await LoginPage.open();
|
||||
await LoginPage.username("ken@goauthentik.io");
|
||||
await LoginPage.username(GOOD_USERNAME);
|
||||
await LoginPage.pause();
|
||||
await LoginPage.password("-this-is-a-bad-password-");
|
||||
await LoginPage.password(BAD_PASSWORD);
|
||||
const failure = await LoginPage.authFailure;
|
||||
expect(failure).toHaveText("Failed to authenticate.");
|
||||
});
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
import { expect } from "@wdio/globals";
|
||||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import { login } from "../utils/login.js";
|
||||
|
||||
describe("Log into Authentik", () => {
|
||||
it("should login with valid credentials", async () => {
|
||||
await LoginPage.open();
|
||||
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
|
||||
await expect(UserLibraryPage.header).toHaveText("My applications");
|
||||
});
|
||||
it("should login with valid credentials and reach the UserLibrary", login);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { login } from "../utils/login.js";
|
||||
import { randomId } from "../utils/index.js";
|
||||
import ApplicationsListPage from "../pageobjects/applications-list.page.js";
|
||||
import ApplicationsWizardView from "../pageobjects/applications-wizard.page.js";
|
||||
import ApplicationForm from "../pageobjects/application-form.view.js";
|
||||
import { expect } from "@wdio/globals";
|
||||
|
||||
describe("Log into Authentik", () => {
|
||||
it("should login with valid credentials and reach the UserLibrary", () => {
|
||||
const newPrefix = randomId();
|
||||
|
||||
await login();
|
||||
await ApplicationsListPage.open();
|
||||
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
|
||||
|
||||
await ApplicationsListPage.startWizardButton.click();
|
||||
await ApplicationsWizardView.wizardTitle.toBeVisible();
|
||||
expect(await ApplicationsWizardView.wizardTitle).toHaveText("Create Application");
|
||||
|
||||
await ApplicationForm.name.setValue(`New LDAP Application - ${newPrefix}`);
|
||||
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
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,15 +0,0 @@
|
|||
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);
|
||||
});
|
||||
}
|
||||
|
||||
export function convertToSlug(text: string) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/ /g, "-")
|
||||
.replace(/[^\w-]+/g, "");
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import LoginPage from "../pageobjects/login.page.js";
|
||||
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||
import { expect } from "@wdio/globals";
|
||||
import { GOOD_PASSWORD, GOOD_USERNAME } from "./constants.js";
|
||||
|
||||
export const login = async () => {
|
||||
await LoginPage.open();
|
||||
await LoginPage.login(GOOD_USERNAME, GOOD_PASSWORD);
|
||||
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
|
||||
};
|
|
@ -3,12 +3,7 @@
|
|||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"target": "es2022",
|
||||
"types": [
|
||||
"node",
|
||||
"@wdio/globals/types",
|
||||
"expect-webdriverio",
|
||||
"@wdio/mocha-framework"
|
||||
],
|
||||
"types": ["node", "@wdio/globals/types", "expect-webdriverio", "@wdio/mocha-framework"],
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
|
@ -19,7 +14,5 @@
|
|||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"test"
|
||||
]
|
||||
"include": ["test"]
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import type { Options } from "@wdio/types";
|
||||
|
||||
export const config: Options.Testrunner = {
|
||||
//
|
||||
// ====================
|
||||
|
@ -59,7 +60,22 @@ export const config: Options.Testrunner = {
|
|||
//
|
||||
capabilities: [
|
||||
{
|
||||
browserName: "chrome",
|
||||
"browserName": "chrome",
|
||||
"goog:chromeOptions": {
|
||||
args: ["--disable-infobars", "--window-size=1280,800"].concat(
|
||||
(function () {
|
||||
return process.env.HEADLESS_CHROME === "1"
|
||||
? [
|
||||
"--headless",
|
||||
"--no-sandbox",
|
||||
"--disable-gpu",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
]
|
||||
: [];
|
||||
})()
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
|
Reference in New Issue