web: revise tests for wizard
This commit replaces the previous WDIO instance with a more formal and straightforward process using the [pageobjects](https://martinfowler.com/bliki/PageObject.html). In this form, every major component has its own test suite, and a test is a sequence of exercises of those components. A test then becomes something as straightforward as: ``` 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" ); ``` Whether or not there's another layer of DSL in there or not, this is a pretty nice idiom for maintaining tests.
This commit is contained in:
parent
53f89ef2f8
commit
0a43ea286e
3
tests/wdio/.gitignore
vendored
3
tests/wdio/.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
reports/
|
|
||||||
|
|
||||||
# Created by https://www.gitignore.io/api/node
|
# Created by https://www.gitignore.io/api/node
|
||||||
# Edit at https://www.gitignore.io/?templates=node
|
# Edit at https://www.gitignore.io/?templates=node
|
||||||
|
@ -108,5 +107,3 @@ tmp/
|
||||||
temp/
|
temp/
|
||||||
|
|
||||||
# End of https://www.gitignore.io/api/node
|
# End of https://www.gitignore.io/api/node
|
||||||
api/**
|
|
||||||
storybook-static/
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
.PHONY: help
|
|
||||||
help: ## Print out this help message.
|
|
||||||
@M=$$(perl -ne 'm/((\w|-)*):.*##/ && print length($$1)."\n"' Makefile | \
|
|
||||||
sort -nr | head -1) && \
|
|
||||||
perl -ne "m/^((\w|-)*):.*##\s*(.*)/ && print(sprintf(\"%s: %s\t%s\n\", \$$1, \" \"x($$M-length(\$$1)), \$$3))" Makefile
|
|
||||||
@echo ""
|
|
||||||
|
|
||||||
.PHONY: update-local-chromedriver
|
|
||||||
update-local-chromedriver: ## Update the chrome driver to match the local chrome version, restoring package.json
|
|
||||||
@ scripts/update_local_chromedriver
|
|
||||||
|
|
||||||
.PHONY: check-chromedriver
|
|
||||||
check-chromedriver: ## Report if the chrome driver and the local chrome version match
|
|
||||||
@ scripts/check_local_chromedriver
|
|
||||||
|
|
||||||
RUNNER=npx wdio wdio.conf.js
|
|
||||||
|
|
||||||
.PHONY: application-plus-ldap
|
|
||||||
application-plus-ldap: check-chromedriver ## Run the "Wizard: Application With LDAP Provider, successful" test
|
|
||||||
@ ${RUNNER} --spec=./tests/application-plus-ldap.test.js
|
|
||||||
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const CLICK_TIME_DELAY = 250;
|
|
||||||
|
|
||||||
async function text(selector, value) {
|
|
||||||
const input = await $(selector);
|
|
||||||
return await input.setValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function button(selector) {
|
|
||||||
const button = await $(selector);
|
|
||||||
return await button.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function search(searchSelector, buttonSelector) {
|
|
||||||
const inputBind = await $(searchSelector);
|
|
||||||
await inputBind.click();
|
|
||||||
const searchBlock = await $('>>>div[data-managed-by="ak-search-select"]');
|
|
||||||
const target = searchBlock.$(buttonSelector);
|
|
||||||
return await target.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pause(selector) {
|
|
||||||
if (selector) {
|
|
||||||
return await $(selector).waitForDisplayed();
|
|
||||||
}
|
|
||||||
return await browser.pause(CLICK_TIME_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function waitfor(selector) {
|
|
||||||
return await $(selector).waitForDisplayed();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deletebox(selector) {
|
|
||||||
return await $(selector)
|
|
||||||
.parentElement()
|
|
||||||
.parentElement()
|
|
||||||
.$(".pf-c-table__check")
|
|
||||||
.$('input[type="checkbox"]')
|
|
||||||
.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
exports.$AkSel = {
|
|
||||||
button,
|
|
||||||
pause,
|
|
||||||
search,
|
|
||||||
text,
|
|
||||||
waitfor,
|
|
||||||
deletebox,
|
|
||||||
};
|
|
18738
tests/wdio/package-lock.json
generated
18738
tests/wdio/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,23 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "authentik-live-tests",
|
"name": "my-new-project",
|
||||||
"version": "1.0.0",
|
"type": "module",
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@wdio/junit-reporter": "^8.15.6",
|
"@wdio/cli": "^8.16.11",
|
||||||
"@wdio/local-runner": "^8.15.6",
|
"@wdio/local-runner": "^8.16.11",
|
||||||
"@wdio/mocha-framework": "^8.15.6",
|
"@wdio/mocha-framework": "^8.16.11",
|
||||||
"@wdio/spec-reporter": "^8.15.6",
|
"@wdio/spec-reporter": "^8.16.9",
|
||||||
"@wdio/sync": "^7.27.0",
|
"ts-node": "^10.9.1",
|
||||||
"chromedriver": "^116.0.0",
|
"typescript": "^5.2.2",
|
||||||
"wdio-chromedriver-service": "^8.1.1",
|
"wdio-wait-for": "^3.0.7"
|
||||||
"wdio-safaridriver-service": "^2.1.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"scripts": {
|
||||||
"@wdio/cli": "^8.15.6",
|
"wdio": "wdio run ./wdio.conf.ts"
|
||||||
"@wdio/types": "^8.15.7",
|
|
||||||
"prettier": "^3.0.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# -u: Treat an unset variable as a fatal syntax error.
|
|
||||||
# -eo pipefile: Fail on first error, even if it happens inside a pipeline.
|
|
||||||
set -ueo pipefail
|
|
||||||
|
|
||||||
VERBOSE=""
|
|
||||||
if [ "$#" -gt 0 ] && [ "$1" = "-v" ]; then
|
|
||||||
VERBOSE="verbose";
|
|
||||||
fi;
|
|
||||||
|
|
||||||
if [ "$#" -gt 0 ] && [ "$1" = "-h" ]; then
|
|
||||||
echo "Usage: "
|
|
||||||
echo " -v: On success, show a message. (Default behavior only shows a message on failure)"
|
|
||||||
echo " -h: This help message"
|
|
||||||
echo ""
|
|
||||||
exit 0
|
|
||||||
fi;
|
|
||||||
|
|
||||||
|
|
||||||
# The path to the working folder for the test project, as a subfolder of the monorepo. This will be
|
|
||||||
# help us find where the driver is kept for comparison.
|
|
||||||
SUBFOLDER="wdio"
|
|
||||||
|
|
||||||
# The variant of Chrome we expect under Linux. There are a lot of variants, like Midori, chromium,
|
|
||||||
# chromium-browser, etc. If you're not running the version supplied by Google, you'll have to change
|
|
||||||
# this variable.
|
|
||||||
LINUX_VARIANT="google-chrome"
|
|
||||||
|
|
||||||
CURRENT_OS=$(uname -s)
|
|
||||||
if [ "$CURRENT_OS" == "Linux" ]; then
|
|
||||||
CHROME_LOCATION=$(command -v "$LINUX_VARIANT")
|
|
||||||
if [ "$CHROME_LOCATION" == "" ]; then
|
|
||||||
echo "Could not find google-chrome installed on this Linux system."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CHROME_VERSION=$("$LINUX_VARIANT" --version)
|
|
||||||
else
|
|
||||||
CHROME_LOCATION="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
||||||
if [ ! -f "$CHROME_LOCATION" ]; then
|
|
||||||
echo "Could not find Google Chrome app installed on this MacOS system."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CHROME_VERSION=$("$CHROME_LOCATION" --version)
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHROME_MAJOR_VER=$(echo "$CHROME_VERSION" | sed 's/^Google Chrome //' | cut -d'.' -f1)
|
|
||||||
PROJECT_TOPLEVEL=$(git rev-parse --show-toplevel)
|
|
||||||
TEST_HOME=$(find $PROJECT_TOPLEVEL -not \( -path "*/node_modules" -prune \) -type d -name "$SUBFOLDER" | head -1)
|
|
||||||
DRIVER_VER=$(grep '^ "version":' "$TEST_HOME/node_modules/chromedriver/package.json")
|
|
||||||
DRIVER_MAJOR_VER=$(echo "$DRIVER_VER" | cut -d':' -f2 | sed 's/"//g' | cut -d'.' -f1 | sed 's/ *//')
|
|
||||||
|
|
||||||
if [ "$CHROME_MAJOR_VER" -ne "$DRIVER_MAJOR_VER" ]; then
|
|
||||||
echo "Driver: $DRIVER_MAJOR_VER, Chrome: $CHROME_MAJOR_VER, update required."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$VERBOSE" ]; then
|
|
||||||
echo "Driver: $DRIVER_MAJOR_VER, Chrome: $CHROME_MAJOR_VER. No update required."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# SUCCESS!
|
|
||||||
exit 0
|
|
|
@ -1,77 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# The below is helpful because Chrome is an evergreen browser, meaning that it will auto-updated on
|
|
||||||
# most desktop computers without even informing the user. So we need a way, if you hit the mismatch,
|
|
||||||
# to update the local chromedriver easily.
|
|
||||||
#
|
|
||||||
# Testing with WDIO requires a specific version of the chromedriver, specified in the current
|
|
||||||
# package.json. Updating the chromedriver will change that version number in package.json.
|
|
||||||
#
|
|
||||||
# The environment block derives the major version for the local (MacOS) version of chrome and the
|
|
||||||
# current driver version for the project and, if they're mismatch, updates the local driver then
|
|
||||||
# resets the content of package.json.
|
|
||||||
|
|
||||||
# -u: Treat an unset variable as a fatal syntax error.
|
|
||||||
# -eo pipefile: Fail on first error, even if it happens inside a pipeline.
|
|
||||||
set -ueo pipefail
|
|
||||||
|
|
||||||
VERBOSE=""
|
|
||||||
if [ "$#" -gt 0 ] && [ "$1" = "-v" ]; then
|
|
||||||
VERBOSE="verbose";
|
|
||||||
fi;
|
|
||||||
|
|
||||||
if [ "$#" -gt 0 ] && [ "$1" = "-h" ]; then
|
|
||||||
echo "Usage: "
|
|
||||||
echo " -v: On success, show a message. (Default behavior only shows a message on failure)"
|
|
||||||
echo " -h: This help message"
|
|
||||||
echo ""
|
|
||||||
exit 0
|
|
||||||
fi;
|
|
||||||
|
|
||||||
# The path to the working folder for the test project, as a subfolder of the monorepo. This will be
|
|
||||||
# help us find where the driver is kept for comparison.
|
|
||||||
SUBFOLDER="wdio"
|
|
||||||
|
|
||||||
# The variant of Chrome we expect under Linux. There are a lot of variants, like Midori, chromium,
|
|
||||||
# chromium-browser, etc. If you're not running the version supplied by Google, you'll have to change
|
|
||||||
# this variable.
|
|
||||||
LINUX_VARIANT="google-chrome"
|
|
||||||
|
|
||||||
CURRENT_OS=$(uname -s)
|
|
||||||
|
|
||||||
CURRENT_OS=$(uname -s)
|
|
||||||
if [ "$CURRENT_OS" == "Linux" ]; then
|
|
||||||
CHROME_LOCATION=$(command -v "$LINUX_VARIANT")
|
|
||||||
if [ "$CHROME_LOCATION" == "" ]; then
|
|
||||||
echo "Could not find google-chrome installed on this Linux system."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CHROME_VERSION=$("$LINUX_VARIANT" --version)
|
|
||||||
else
|
|
||||||
CHROME_LOCATION="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
||||||
if [ ! -f "$CHROME_LOCATION" ]; then
|
|
||||||
echo "Could not find Google Chrome app installed on this MacOS system."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
CHROME_VERSION=$("$CHROME_LOCATION" --version)
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHROME_MAJOR_VER=$(echo "$CHROME_VERSION" | sed 's/^Google Chrome //' | cut -d'.' -f1)
|
|
||||||
PROJECT_TOPLEVEL=$(git rev-parse --show-toplevel)
|
|
||||||
TEST_HOME=$(find $PROJECT_TOPLEVEL -not \( -path "*/node_modules" -prune \) -type d -name "$SUBFOLDER" | head -1)
|
|
||||||
DRIVER_VER=$(grep '^ "version":' "$TEST_HOME/node_modules/chromedriver/package.json")
|
|
||||||
DRIVER_MAJOR_VER=$(echo "$DRIVER_VER" | cut -d':' -f2 | sed 's/"//g' | cut -d'.' -f1 | sed 's/ *//')
|
|
||||||
|
|
||||||
if [ "$CHROME_MAJOR_VER" -ne "$DRIVER_MAJOR_VER" ]; then
|
|
||||||
echo "Driver: $DRIVER_MAJOR_VER, Chrome: $CHROME_MAJOR_VER, updating..."
|
|
||||||
npm install "chromedriver@$CHROME_MAJOR_VER"
|
|
||||||
git checkout package.json package-lock.json
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$VERBOSE" ]; then
|
|
||||||
echo "Driver: $DRIVER_MAJOR_VER, Chrome: $CHROME_MAJOR_VER. No update required."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# SUCCESS!
|
|
||||||
exit 0
|
|
13
tests/wdio/test/pageobjects/admin-overview.page.ts
Normal file
13
tests/wdio/test/pageobjects/admin-overview.page.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
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();
|
25
tests/wdio/test/pageobjects/admin.page.ts
Normal file
25
tests/wdio/test/pageobjects/admin.page.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { browser } from "@wdio/globals";
|
||||||
|
import Page from "../pageobjects/page.js";
|
||||||
|
|
||||||
|
const CLICK_TIME_DELAY = 250;
|
||||||
|
|
||||||
|
export default class AdminPage extends Page {
|
||||||
|
public get pageHeader() {
|
||||||
|
return $(">>>ak-page-header h1");
|
||||||
|
}
|
||||||
|
|
||||||
|
async openApplicationsListPage() {
|
||||||
|
await this.open("if/admin/#/core/applications");
|
||||||
|
}
|
||||||
|
|
||||||
|
public open(path: string) {
|
||||||
|
return browser.url(`http://localhost:9000/${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(selector?: string) {
|
||||||
|
if (selector) {
|
||||||
|
return $(selector).waitForDisplayed();
|
||||||
|
}
|
||||||
|
return browser.pause(CLICK_TIME_DELAY);
|
||||||
|
}
|
||||||
|
}
|
10
tests/wdio/test/pageobjects/application-form.view.ts
Normal file
10
tests/wdio/test/pageobjects/application-form.view.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
import Page from "./page.js";
|
||||||
|
|
||||||
|
export class ApplicationForm extends Page {
|
||||||
|
get name() {
|
||||||
|
return $('>>>ak-form-element-horizontal input[name="name"]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ApplicationForm();
|
40
tests/wdio/test/pageobjects/application-wizard.page.ts
Normal file
40
tests/wdio/test/pageobjects/application-wizard.page.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
import AdminPage from "./admin.page.js";
|
||||||
|
import ApplicationForm from "./application-form.view.js";
|
||||||
|
import LdapForm from "./ldap-form.view.js";
|
||||||
|
import OauthForm from "./oauth-form.view.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub page containing specific selectors and methods for a specific page
|
||||||
|
*/
|
||||||
|
class ApplicationWizardView extends AdminPage {
|
||||||
|
/**
|
||||||
|
* define selectors using getter methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
ldap = LdapForm;
|
||||||
|
oauth = OauthForm;
|
||||||
|
app = ApplicationForm;
|
||||||
|
|
||||||
|
get wizardTitle() {
|
||||||
|
return $(">>>ak-application-wizard-commit-application h1.pf-c-title");
|
||||||
|
}
|
||||||
|
|
||||||
|
get providerList() {
|
||||||
|
return $(">>>ak-application-wizard-authentication-method-choice");
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextButton() {
|
||||||
|
return $(">>>ak-wizard-frame footer button.pf-m-primary");
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProviderType(type: string) {
|
||||||
|
return await this.providerList.$(`>>>input[value="${type}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get commitMessage() {
|
||||||
|
return $(">>>ak-application-wizard-commit-application h1.pf-c-title");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ApplicationWizardView();
|
17
tests/wdio/test/pageobjects/applications-list.page.ts
Normal file
17
tests/wdio/test/pageobjects/applications-list.page.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
import AdminPage from "./admin.page.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub page containing specific selectors and methods for a specific page
|
||||||
|
*/
|
||||||
|
class ApplicationsListPage extends AdminPage {
|
||||||
|
/**
|
||||||
|
* define selectors using getter methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
async startCreateApplicationWizard() {
|
||||||
|
await $('>>>ak-wizard-frame button[slot="trigger"]').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new ApplicationsListPage();
|
13
tests/wdio/test/pageobjects/ldap-form.view.ts
Normal file
13
tests/wdio/test/pageobjects/ldap-form.view.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import Page from "./page.js";
|
||||||
|
|
||||||
|
export class LdapForm extends Page {
|
||||||
|
async setBindFlow(selector: string) {
|
||||||
|
await this.searchSelect(
|
||||||
|
'>>>ak-tenanted-flow-search[name="authorizationFlow"] input[type="text"]',
|
||||||
|
"authorizationFlow",
|
||||||
|
`button*=${selector}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LdapForm();
|
63
tests/wdio/test/pageobjects/login.page.ts
Normal file
63
tests/wdio/test/pageobjects/login.page.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
import Page from "./page.js";
|
||||||
|
import UserLibraryPage from "./user-library.page.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub page containing specific selectors and methods for a specific page
|
||||||
|
*/
|
||||||
|
class LoginPage extends Page {
|
||||||
|
/**
|
||||||
|
* define selectors using getter methods
|
||||||
|
*/
|
||||||
|
get inputUsername() {
|
||||||
|
return $('>>>input[name="uidField"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
get inputPassword() {
|
||||||
|
return $('>>>input[name="password"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
get btnSubmit() {
|
||||||
|
return $('>>>button[type="submit"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
get authFailure() {
|
||||||
|
return $(">>>h4.pf-c-alert__title");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a method to encapsule automation code to interact with the page
|
||||||
|
* e.g. to login using username and password
|
||||||
|
*/
|
||||||
|
async username(username: string) {
|
||||||
|
await this.inputPassword.isDisplayed();
|
||||||
|
await this.inputUsername.setValue(username);
|
||||||
|
await this.btnSubmit.isEnabled();
|
||||||
|
await this.btnSubmit.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async password(password: string) {
|
||||||
|
await this.inputPassword.isDisplayed();
|
||||||
|
await this.inputPassword.setValue(password);
|
||||||
|
await this.btnSubmit.isEnabled();
|
||||||
|
await this.btnSubmit.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(username: string, password: string) {
|
||||||
|
await this.username(username);
|
||||||
|
await this.pause();
|
||||||
|
await this.password(password);
|
||||||
|
await this.pause();
|
||||||
|
await this.pause(">>>div.header h1");
|
||||||
|
return UserLibraryPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overwrite specific options to adapt it to page object
|
||||||
|
*/
|
||||||
|
open() {
|
||||||
|
return super.open("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new LoginPage();
|
13
tests/wdio/test/pageobjects/oauth-form.view.ts
Normal file
13
tests/wdio/test/pageobjects/oauth-form.view.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import Page from "./page.js";
|
||||||
|
|
||||||
|
export class OauthForm extends Page {
|
||||||
|
async setAuthorizationFlow(selector: string) {
|
||||||
|
await this.searchSelect(
|
||||||
|
'>>>ak-flow-search[name="authorizationFlow"] input[type="text"]',
|
||||||
|
"authorizationFlow",
|
||||||
|
`button*=${selector}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new OauthForm();
|
32
tests/wdio/test/pageobjects/page.ts
Normal file
32
tests/wdio/test/pageobjects/page.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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 open(path: string) {
|
||||||
|
return browser.url(`http://localhost:9000/${path}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause(selector?: string) {
|
||||||
|
if (selector) {
|
||||||
|
return $(selector).waitForDisplayed();
|
||||||
|
}
|
||||||
|
return browser.pause(CLICK_TIME_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchSelect(searchSelector: string, managedSelector: string, buttonSelector: string) {
|
||||||
|
const inputBind = await $(searchSelector);
|
||||||
|
await inputBind.click();
|
||||||
|
const searchBlock = await $(`>>>div[data-managed-for="${managedSelector}"]`);
|
||||||
|
const target = searchBlock.$(buttonSelector);
|
||||||
|
return await target.click();
|
||||||
|
}
|
||||||
|
}
|
1
tests/wdio/test/pageobjects/types.ts
Normal file
1
tests/wdio/test/pageobjects/types.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type Constructor<T = object> = new (...args: any[]) => T;
|
22
tests/wdio/test/pageobjects/user-library.page.ts
Normal file
22
tests/wdio/test/pageobjects/user-library.page.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { $ } from "@wdio/globals";
|
||||||
|
import Page from "./page.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sub page containing specific selectors and methods for a specific page
|
||||||
|
*/
|
||||||
|
class UserLibraryPage extends Page {
|
||||||
|
/**
|
||||||
|
* define selectors using getter methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
public get pageHeader() {
|
||||||
|
return $('>>>h1[aria-level="1"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async goToAdmin() {
|
||||||
|
await $('>>>a[href="/if/admin"]').click();
|
||||||
|
await $(">>>ak-admin-overview").waitForDisplayed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UserLibraryPage();
|
35
tests/wdio/test/specs/application-wizard-ldap.ts
Normal file
35
tests/wdio/test/specs/application-wizard-ldap.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
37
tests/wdio/test/specs/application-wizard-oauth.ts
Normal file
37
tests/wdio/test/specs/application-wizard-oauth.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
21
tests/wdio/test/specs/bad-logins.ts
Normal file
21
tests/wdio/test/specs/bad-logins.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { expect } from "@wdio/globals";
|
||||||
|
import LoginPage from "../pageobjects/login.page.js";
|
||||||
|
import UserLibraryPage from "../pageobjects/user-library.page.js";
|
||||||
|
|
||||||
|
describe("Log into Authentik", () => {
|
||||||
|
it("should fail on a bad username", async () => {
|
||||||
|
await LoginPage.open();
|
||||||
|
await LoginPage.username("bad-username@bad-logio.io");
|
||||||
|
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.pause();
|
||||||
|
await LoginPage.password("-this-is-a-bad-password-");
|
||||||
|
const failure = await LoginPage.authFailure;
|
||||||
|
expect(failure).toHaveText("Failed to authenticate.");
|
||||||
|
});
|
||||||
|
});
|
11
tests/wdio/test/specs/good-login.ts
Normal file
11
tests/wdio/test/specs/good-login.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { expect } from "@wdio/globals";
|
||||||
|
import LoginPage from "../pageobjects/login.page.js";
|
||||||
|
import UserLibraryPage from "../pageobjects/user-library.page.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");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
function randomPrefix() {
|
export function randomId() {
|
||||||
let dt = new Date().getTime();
|
let dt = new Date().getTime();
|
||||||
return "xxxxxxxx".replace(/x/g, (c) => {
|
return "xxxxxxxx".replace(/x/g, (c) => {
|
||||||
const r = (dt + Math.random() * 16) % 16 | 0;
|
const r = (dt + Math.random() * 16) % 16 | 0;
|
||||||
|
@ -7,11 +7,9 @@ function randomPrefix() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToSlug(text) {
|
export function convertToSlug(text: string) {
|
||||||
return text
|
return text
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/ /g, "-")
|
.replace(/ /g, "-")
|
||||||
.replace(/[^\w-]+/g, "");
|
.replace(/[^\w-]+/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { randomPrefix, convertToSlug };
|
|
|
@ -1,93 +0,0 @@
|
||||||
const { execSync } = require("child_process");
|
|
||||||
const { readdirSync } = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const { $AkSel } = require("../lib/idiom");
|
|
||||||
const { randomPrefix, convertToSlug } = require("../lib/utils");
|
|
||||||
|
|
||||||
const CLICK_TIME_DELAY = 250;
|
|
||||||
|
|
||||||
const login = [
|
|
||||||
["text", '>>>input[name="uidField"]', "ken@goauthentik.io"],
|
|
||||||
["button", '>>>button[type="submit"]'],
|
|
||||||
["pause"],
|
|
||||||
["text", '>>>input[name="password"]', "eat10bugs"],
|
|
||||||
["button", '>>>button[type="submit"]'],
|
|
||||||
["pause", ">>>div.header h1"],
|
|
||||||
];
|
|
||||||
|
|
||||||
const navigateToWizard = [
|
|
||||||
["button", '>>>a[href="/if/admin"]'],
|
|
||||||
["waitfor", ">>>ak-admin-overview"],
|
|
||||||
["button", '>>>a[href="#/core/applications;%7B%22createForm%22%3Atrue%7D"]'],
|
|
||||||
["waitfor", ">>>ak-application-list"],
|
|
||||||
["button", '>>>ak-wizard-frame button[slot="trigger"]']
|
|
||||||
];
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const ldapApplication = (theCode) => ([
|
|
||||||
["text", '>>>ak-form-element-horizontal input[name="name"]', `This Is My Application ${theCode}`],
|
|
||||||
["button", ">>>ak-wizard-frame footer button.pf-m-primary"],
|
|
||||||
["button", '>>>input[value="ldapprovider"]'],
|
|
||||||
["button", ">>>ak-wizard-frame footer button.pf-m-primary"],
|
|
||||||
["search", '>>>ak-tenanted-flow-search input[type="text"]', "button*=default-authentication-flow",],
|
|
||||||
["text", '>>>ak-form-element-horizontal input[name="tlsServerName"]', "example.goauthentik.io"],
|
|
||||||
["button", ">>>ak-wizard-frame footer button.pf-m-primary"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const deleteProvider = (theSlug) => ([
|
|
||||||
["button", '>>>ak-sidebar-item a[href="#/core/providers"]'],
|
|
||||||
["deletebox", `>>>a[href="#/core/applications/${theSlug}"]`],
|
|
||||||
["button", '>>>ak-forms-delete-bulk button[slot="trigger"]'],
|
|
||||||
["button", '>>>ak-forms-delete-bulk div[role="dialog"] ak-spinner-button'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const deleteApplication = (theSlug) => ([
|
|
||||||
["button", '>>>ak-sidebar-item a[href="#/core/applications"'],
|
|
||||||
["deletebox", `>>>a[href="#/core/applications/${theSlug}"]`],
|
|
||||||
["button", '>>>ak-forms-delete-bulk button[slot="trigger"]'],
|
|
||||||
["button", '>>>ak-forms-delete-bulk div[role="dialog"] ak-spinner-button']
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
function prepApplicationAndSlug() {
|
|
||||||
const newSuffix = randomPrefix();
|
|
||||||
const thisApplication = ldapApplication(newSuffix);
|
|
||||||
return [thisApplication, convertToSlug(thisApplication[0][2])];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runSequence(sequence, watch = false) {
|
|
||||||
for ([command, ...args] of sequence) {
|
|
||||||
await $AkSel[command].apply($, args);
|
|
||||||
if (watch) {
|
|
||||||
await browser.pause(250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Login", () => {
|
|
||||||
it("Should correctly log in to Authentik}", async () => {
|
|
||||||
const [theApplication, theSlug] = prepApplicationAndSlug();
|
|
||||||
|
|
||||||
await browser.reloadSession();
|
|
||||||
await browser.url("http://localhost:9000");
|
|
||||||
|
|
||||||
runSequence(login, true);
|
|
||||||
|
|
||||||
const home = await $(">>>div.header h1");
|
|
||||||
await expect(home).toHaveText("My applications");
|
|
||||||
|
|
||||||
await runSequence(navigateToWizard, true);
|
|
||||||
await runSequence(theApplication, true)
|
|
||||||
|
|
||||||
const success = await $(">>>ak-application-wizard-commit-application h1.pf-c-title");
|
|
||||||
await expect(success).toHaveText("Your application has been saved");
|
|
||||||
|
|
||||||
await $AkSel.button(">>>ak-wizard-frame .pf-c-wizard__footer-cancel button");
|
|
||||||
|
|
||||||
await runSequence(deleteProvider(theSlug), true);
|
|
||||||
await runSequence(deleteApplication(theSlug), true);
|
|
||||||
|
|
||||||
const expectedApplication = await $(`>>>a[href="#/core/applications/${theSlug}"]`);
|
|
||||||
expect(expectedApplication).not.toExist();
|
|
||||||
});
|
|
||||||
});
|
|
25
tests/wdio/tsconfig.json
Normal file
25
tests/wdio/tsconfig.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "es2022",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"@wdio/globals/types",
|
||||||
|
"expect-webdriverio",
|
||||||
|
"@wdio/mocha-framework"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"test"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,294 +0,0 @@
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const debug = process.env.DEBUG;
|
|
||||||
const defaultTimeoutInterval = 60000;
|
|
||||||
const buildNumber = process.env.BUILD_NUMBER ? process.env.BUILD_NUMBER : "0";
|
|
||||||
const reportsOutputDir = "./reports";
|
|
||||||
|
|
||||||
exports.config = {
|
|
||||||
//
|
|
||||||
// ====================
|
|
||||||
// Runner Configuration
|
|
||||||
// ====================
|
|
||||||
//
|
|
||||||
// WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or
|
|
||||||
// on a remote machine).
|
|
||||||
runner: "local",
|
|
||||||
//
|
|
||||||
// ==================
|
|
||||||
// Specify Test Files
|
|
||||||
// ==================
|
|
||||||
// Define which test specs should run. The pattern is relative to the directory
|
|
||||||
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
|
|
||||||
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
|
|
||||||
// directory is where your package.json resides, so `wdio` will be called from there.
|
|
||||||
//
|
|
||||||
specs: ["./tests/*.js"],
|
|
||||||
// Patterns to exclude.
|
|
||||||
exclude: [
|
|
||||||
// 'path/to/excluded/files'
|
|
||||||
],
|
|
||||||
//
|
|
||||||
// ============
|
|
||||||
// Capabilities
|
|
||||||
// ============
|
|
||||||
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
|
|
||||||
// time. Depending on the number of capabilities, WebdriverIO launches several test
|
|
||||||
// sessions. Within your capabilities you can overwrite the spec and exclude options in
|
|
||||||
// order to group specific specs to a specific capability.
|
|
||||||
//
|
|
||||||
// First, you can define how many instances should be started at the same time. Let's
|
|
||||||
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
|
|
||||||
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
|
|
||||||
// files and you set maxInstances to 10, all spec files will get tested at the same time
|
|
||||||
// and 30 processes will get spawned. The property handles how many capabilities
|
|
||||||
// from the same test should run tests.
|
|
||||||
//
|
|
||||||
maxInstances: 10,
|
|
||||||
//
|
|
||||||
// If you have trouble getting all important capabilities together, check out the
|
|
||||||
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
|
||||||
// https://docs.saucelabs.com/reference/platforms-configurator
|
|
||||||
//
|
|
||||||
capabilities: [
|
|
||||||
{
|
|
||||||
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
|
|
||||||
// grid with only 5 firefox instances available you can make sure that not more than
|
|
||||||
// 5 instances get started at a time.
|
|
||||||
maxInstances: 1,
|
|
||||||
//
|
|
||||||
browserName: "Safari",
|
|
||||||
// acceptInsecureCerts: true,
|
|
||||||
// If outputDir is provided WebdriverIO can capture driver session logs
|
|
||||||
// it is possible to configure which logTypes to include/exclude.
|
|
||||||
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
|
|
||||||
// excludeDriverLogs: ['bugreport', 'server'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
//
|
|
||||||
// ===================
|
|
||||||
// Test Configurations
|
|
||||||
// ===================
|
|
||||||
// Define all options that are relevant for the WebdriverIO instance here
|
|
||||||
//
|
|
||||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
|
||||||
logLevel: "info",
|
|
||||||
//
|
|
||||||
// Set specific log levels per logger
|
|
||||||
// loggers:
|
|
||||||
// - webdriver, webdriverio
|
|
||||||
// - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
|
||||||
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
|
||||||
// - @wdio/local-runner
|
|
||||||
// - @wdio/sumologic-reporter
|
|
||||||
// - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
|
|
||||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
|
||||||
// logLevels: {
|
|
||||||
// webdriver: 'info',
|
|
||||||
// '@wdio/applitools-service': 'info'
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// If you only want to run your tests until a specific amount of tests have failed use
|
|
||||||
// bail (default is 0 - don't bail, run all tests).
|
|
||||||
bail: 0,
|
|
||||||
//
|
|
||||||
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
|
|
||||||
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
|
|
||||||
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
|
|
||||||
// gets prepended directly.
|
|
||||||
baseUrl: "http://localhost",
|
|
||||||
//
|
|
||||||
// Default timeout for all waitFor* commands.
|
|
||||||
waitforTimeout: 10000,
|
|
||||||
//
|
|
||||||
// Default timeout in milliseconds for request
|
|
||||||
// if browser driver or grid doesn't send response
|
|
||||||
connectionRetryTimeout: 120000,
|
|
||||||
//
|
|
||||||
// Default request retries count
|
|
||||||
connectionRetryCount: 3,
|
|
||||||
//
|
|
||||||
// Test runner services
|
|
||||||
// Services take over a specific job you don't want to take care of. They enhance
|
|
||||||
// your test setup with almost no effort. Unlike plugins, they don't add new
|
|
||||||
// commands. Instead, they hook themselves up into the test process.
|
|
||||||
services: ["safaridriver"],
|
|
||||||
|
|
||||||
// Framework you want to run your specs with.
|
|
||||||
// The following are supported: Mocha, Jasmine, and Cucumber
|
|
||||||
// see also: https://webdriver.io/docs/frameworks.html
|
|
||||||
//
|
|
||||||
// Make sure you have the wdio adapter package for the specific framework installed
|
|
||||||
// before running any tests.
|
|
||||||
framework: "mocha",
|
|
||||||
//
|
|
||||||
// The number of times to retry the entire specfile when it fails as a whole
|
|
||||||
// specFileRetries: 1,
|
|
||||||
//
|
|
||||||
// Delay in seconds between the spec file retry attempts
|
|
||||||
// specFileRetriesDelay: 0,
|
|
||||||
//
|
|
||||||
// Whether or not retried specfiles should be retried immediately or deferred to the end of the queue
|
|
||||||
// specFileRetriesDeferred: false,
|
|
||||||
//
|
|
||||||
// Test reporter for stdout.
|
|
||||||
// The only one supported by default is 'dot'
|
|
||||||
// see also: https://webdriver.io/docs/dot-reporter.html
|
|
||||||
reporters: [
|
|
||||||
"spec",
|
|
||||||
[
|
|
||||||
"junit",
|
|
||||||
{
|
|
||||||
outputDir: reportsOutputDir,
|
|
||||||
outputFileFormat(options) {
|
|
||||||
return `authentik-${buildNumber}-${options.cid}.xml`;
|
|
||||||
},
|
|
||||||
errorOptions: {
|
|
||||||
failure: "message",
|
|
||||||
stacktrace: "stack",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
//
|
|
||||||
// Options to be passed to Mocha.
|
|
||||||
// See the full list at http://mochajs.org/
|
|
||||||
mochaOpts: {
|
|
||||||
ui: "bdd",
|
|
||||||
timeout: debug ? 24 * 60 * 60 * 1000 : defaultTimeoutInterval,
|
|
||||||
},
|
|
||||||
//
|
|
||||||
// =====
|
|
||||||
// Hooks
|
|
||||||
// =====
|
|
||||||
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
|
|
||||||
// it and to build services around it. You can either apply a single function or an array of
|
|
||||||
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
|
|
||||||
// resolved to continue.
|
|
||||||
/**
|
|
||||||
* Gets executed once before all workers get launched.
|
|
||||||
* @param {Object} config wdio configuration object
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
*/
|
|
||||||
// onPrepare: function (config, capabilities) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed before a worker process is spawned and can be used to initialise specific service
|
|
||||||
* for that worker as well as modify runtime environments in an async fashion.
|
|
||||||
* @param {String} cid capability id (e.g 0-0)
|
|
||||||
* @param {[type]} caps object containing capabilities for session that will be spawn in the worker
|
|
||||||
* @param {[type]} specs specs to be run in the worker process
|
|
||||||
* @param {[type]} args object that will be merged with the main configuration once worker is initialised
|
|
||||||
* @param {[type]} execArgv list of string arguments passed to the worker process
|
|
||||||
*/
|
|
||||||
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
|
||||||
* to manipulate configurations depending on the capability or spec.
|
|
||||||
* @param {Object} config wdio configuration object
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
|
||||||
*/
|
|
||||||
// beforeSession: function (config, capabilities, specs) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed before test execution begins. At this point you can access to all global
|
|
||||||
* variables like `browser`. It is the perfect place to define custom commands.
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
|
||||||
* @param {Object} browser instance of created browser/device session
|
|
||||||
*/
|
|
||||||
// before: function (capabilities, specs) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Runs before a WebdriverIO command gets executed.
|
|
||||||
* @param {String} commandName hook command name
|
|
||||||
* @param {Array} args arguments that command would receive
|
|
||||||
*/
|
|
||||||
// beforeCommand: function (commandName, args) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Hook that gets executed before the suite starts
|
|
||||||
* @param {Object} suite suite details
|
|
||||||
*/
|
|
||||||
// beforeSuite: function (suite) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Function to be executed before a test (in Mocha/Jasmine) starts.
|
|
||||||
*/
|
|
||||||
// beforeTest: function (test, context) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
|
|
||||||
* beforeEach in Mocha)
|
|
||||||
*/
|
|
||||||
// beforeHook: function (test, context) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
|
|
||||||
* afterEach in Mocha)
|
|
||||||
*/
|
|
||||||
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Function to be executed after a test (in Mocha/Jasmine).
|
|
||||||
*/
|
|
||||||
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
|
|
||||||
// },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook that gets executed after the suite has ended
|
|
||||||
* @param {Object} suite suite details
|
|
||||||
*/
|
|
||||||
// afterSuite: function (suite) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Runs after a WebdriverIO command gets executed
|
|
||||||
* @param {String} commandName hook command name
|
|
||||||
* @param {Array} args arguments that command would receive
|
|
||||||
* @param {Number} result 0 - command success, 1 - command error
|
|
||||||
* @param {Object} error error object if any
|
|
||||||
*/
|
|
||||||
// afterCommand: function (commandName, args, result, error) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed after all tests are done. You still have access to all global variables from
|
|
||||||
* the test.
|
|
||||||
* @param {Number} result 0 - test pass, 1 - test fail
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
* @param {Array.<String>} specs List of spec file paths that ran
|
|
||||||
*/
|
|
||||||
// after: function (result, capabilities, specs) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed right after terminating the webdriver session.
|
|
||||||
* @param {Object} config wdio configuration object
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
* @param {Array.<String>} specs List of spec file paths that ran
|
|
||||||
*/
|
|
||||||
// afterSession: function (config, capabilities, specs) {
|
|
||||||
// },
|
|
||||||
/**
|
|
||||||
* Gets executed after all workers got shut down and the process is about to exit. An error
|
|
||||||
* thrown in the onComplete hook will result in the test run failing.
|
|
||||||
* @param {Object} exitCode 0 - success, 1 - fail
|
|
||||||
* @param {Object} config wdio configuration object
|
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
|
||||||
* @param {<Object>} results object containing test results
|
|
||||||
*/
|
|
||||||
onComplete(exitCode, config, capabilities, results) {
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
fs.writeFileSync(path.join(reportsOutputDir, "./failure.txt"), "Tests failed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Gets executed when a refresh happens.
|
|
||||||
* @param {String} oldSessionId session ID of the old session
|
|
||||||
* @param {String} newSessionId session ID of the new session
|
|
||||||
*/
|
|
||||||
// onReload: function(oldSessionId, newSessionId) {
|
|
||||||
// }
|
|
||||||
};
|
|
|
@ -1,30 +1,36 @@
|
||||||
const fs = require("fs");
|
import type { Options } from "@wdio/types";
|
||||||
const path = require("path");
|
export const config: Options.Testrunner = {
|
||||||
|
|
||||||
const debug = process.env.DEBUG;
|
|
||||||
const defaultTimeoutInterval = 200000;
|
|
||||||
const buildNumber = process.env.BUILD_NUMBER ? process.env.BUILD_NUMBER : "0";
|
|
||||||
const reportsOutputDir = "./reports";
|
|
||||||
|
|
||||||
exports.config = {
|
|
||||||
//
|
//
|
||||||
// ====================
|
// ====================
|
||||||
// Runner Configuration
|
// Runner Configuration
|
||||||
// ====================
|
// ====================
|
||||||
//
|
// WebdriverIO supports running e2e tests as well as unit and component tests.
|
||||||
// WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or
|
|
||||||
// on a remote machine).
|
|
||||||
runner: "local",
|
runner: "local",
|
||||||
|
autoCompileOpts: {
|
||||||
|
autoCompile: true,
|
||||||
|
tsNodeOpts: {
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
transpileOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
// ==================
|
// ==================
|
||||||
// Specify Test Files
|
// Specify Test Files
|
||||||
// ==================
|
// ==================
|
||||||
// Define which test specs should run. The pattern is relative to the directory
|
// Define which test specs should run. The pattern is relative to the directory
|
||||||
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
|
// of the configuration file being run.
|
||||||
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
|
|
||||||
// directory is where your package.json resides, so `wdio` will be called from there.
|
|
||||||
//
|
//
|
||||||
specs: ["./tests/*.js"],
|
// The specs are defined as an array of spec files (optionally using wildcards
|
||||||
|
// that will be expanded). The test for each spec file will be run in a separate
|
||||||
|
// worker process. In order to have a group of spec files run in the same worker
|
||||||
|
// process simply enclose them in an array within the specs array.
|
||||||
|
//
|
||||||
|
// If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script),
|
||||||
|
// then the current working directory is where your `package.json` resides, so `wdio`
|
||||||
|
// will be called from there.
|
||||||
|
//
|
||||||
|
specs: ["./test/specs/**/*.ts"],
|
||||||
// Patterns to exclude.
|
// Patterns to exclude.
|
||||||
exclude: [
|
exclude: [
|
||||||
// 'path/to/excluded/files'
|
// 'path/to/excluded/files'
|
||||||
|
@ -45,27 +51,18 @@ exports.config = {
|
||||||
// and 30 processes will get spawned. The property handles how many capabilities
|
// and 30 processes will get spawned. The property handles how many capabilities
|
||||||
// from the same test should run tests.
|
// from the same test should run tests.
|
||||||
//
|
//
|
||||||
maxInstances: 10,
|
maxInstances: 1,
|
||||||
//
|
//
|
||||||
// If you have trouble getting all important capabilities together, check out the
|
// If you have trouble getting all important capabilities together, check out the
|
||||||
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
||||||
// https://docs.saucelabs.com/reference/platforms-configurator
|
// https://saucelabs.com/platform/platform-configurator
|
||||||
//
|
//
|
||||||
capabilities: [
|
capabilities: [
|
||||||
{
|
{
|
||||||
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
|
|
||||||
// grid with only 5 firefox instances available you can make sure that not more than
|
|
||||||
// 5 instances get started at a time.
|
|
||||||
maxInstances: 1,
|
|
||||||
//
|
|
||||||
browserName: "chrome",
|
browserName: "chrome",
|
||||||
acceptInsecureCerts: true,
|
|
||||||
// If outputDir is provided WebdriverIO can capture driver session logs
|
|
||||||
// it is possible to configure which logTypes to include/exclude.
|
|
||||||
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
|
|
||||||
// excludeDriverLogs: ['bugreport', 'server'],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
//
|
//
|
||||||
// ===================
|
// ===================
|
||||||
// Test Configurations
|
// Test Configurations
|
||||||
|
@ -73,20 +70,20 @@ exports.config = {
|
||||||
// Define all options that are relevant for the WebdriverIO instance here
|
// Define all options that are relevant for the WebdriverIO instance here
|
||||||
//
|
//
|
||||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||||
logLevel: "warn",
|
logLevel: "info",
|
||||||
//
|
//
|
||||||
// Set specific log levels per logger
|
// Set specific log levels per logger
|
||||||
// loggers:
|
// loggers:
|
||||||
// - webdriver, webdriverio
|
// - webdriver, webdriverio
|
||||||
// - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
// - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
||||||
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
||||||
// - @wdio/local-runner
|
// - @wdio/local-runner
|
||||||
// - @wdio/sumologic-reporter
|
// - @wdio/sumologic-reporter
|
||||||
// - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
|
// - @wdio/cli, @wdio/config, @wdio/utils
|
||||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||||
// logLevels: {
|
// logLevels: {
|
||||||
// webdriver: 'info',
|
// webdriver: 'info',
|
||||||
// '@wdio/applitools-service': 'info'
|
// '@wdio/appium-service': 'info'
|
||||||
// },
|
// },
|
||||||
//
|
//
|
||||||
// If you only want to run your tests until a specific amount of tests have failed use
|
// If you only want to run your tests until a specific amount of tests have failed use
|
||||||
|
@ -113,11 +110,11 @@ exports.config = {
|
||||||
// Services take over a specific job you don't want to take care of. They enhance
|
// Services take over a specific job you don't want to take care of. They enhance
|
||||||
// your test setup with almost no effort. Unlike plugins, they don't add new
|
// your test setup with almost no effort. Unlike plugins, they don't add new
|
||||||
// commands. Instead, they hook themselves up into the test process.
|
// commands. Instead, they hook themselves up into the test process.
|
||||||
services: ["chromedriver"],
|
// services: [],
|
||||||
|
//
|
||||||
// Framework you want to run your specs with.
|
// Framework you want to run your specs with.
|
||||||
// The following are supported: Mocha, Jasmine, and Cucumber
|
// The following are supported: Mocha, Jasmine, and Cucumber
|
||||||
// see also: https://webdriver.io/docs/frameworks.html
|
// see also: https://webdriver.io/docs/frameworks
|
||||||
//
|
//
|
||||||
// Make sure you have the wdio adapter package for the specific framework installed
|
// Make sure you have the wdio adapter package for the specific framework installed
|
||||||
// before running any tests.
|
// before running any tests.
|
||||||
|
@ -129,35 +126,20 @@ exports.config = {
|
||||||
// Delay in seconds between the spec file retry attempts
|
// Delay in seconds between the spec file retry attempts
|
||||||
// specFileRetriesDelay: 0,
|
// specFileRetriesDelay: 0,
|
||||||
//
|
//
|
||||||
// Whether or not retried specfiles should be retried immediately or deferred to the end of the queue
|
// Whether or not retried spec files should be retried immediately or deferred to the end of the queue
|
||||||
// specFileRetriesDeferred: false,
|
// specFileRetriesDeferred: false,
|
||||||
//
|
//
|
||||||
// Test reporter for stdout.
|
// Test reporter for stdout.
|
||||||
// The only one supported by default is 'dot'
|
// The only one supported by default is 'dot'
|
||||||
// see also: https://webdriver.io/docs/dot-reporter.html
|
// see also: https://webdriver.io/docs/dot-reporter
|
||||||
reporters: [
|
reporters: ["spec"],
|
||||||
"spec",
|
|
||||||
[
|
|
||||||
"junit",
|
|
||||||
{
|
|
||||||
outputDir: reportsOutputDir,
|
|
||||||
outputFileFormat(options) {
|
|
||||||
return `authentik-${buildNumber}-${options.cid}.xml`;
|
|
||||||
},
|
|
||||||
errorOptions: {
|
|
||||||
failure: "message",
|
|
||||||
stacktrace: "stack",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Options to be passed to Mocha.
|
// Options to be passed to Mocha.
|
||||||
// See the full list at http://mochajs.org/
|
// See the full list at http://mochajs.org/
|
||||||
mochaOpts: {
|
mochaOpts: {
|
||||||
ui: "bdd",
|
ui: "bdd",
|
||||||
timeout: debug ? 24 * 60 * 60 * 1000 : defaultTimeoutInterval,
|
timeout: 60000,
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
// =====
|
// =====
|
||||||
|
@ -169,7 +151,7 @@ exports.config = {
|
||||||
// resolved to continue.
|
// resolved to continue.
|
||||||
/**
|
/**
|
||||||
* Gets executed once before all workers get launched.
|
* Gets executed once before all workers get launched.
|
||||||
* @param {Object} config wdio configuration object
|
* @param {object} config wdio configuration object
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
*/
|
*/
|
||||||
// onPrepare: function (config, capabilities) {
|
// onPrepare: function (config, capabilities) {
|
||||||
|
@ -177,42 +159,52 @@ exports.config = {
|
||||||
/**
|
/**
|
||||||
* Gets executed before a worker process is spawned and can be used to initialise specific service
|
* Gets executed before a worker process is spawned and can be used to initialise specific service
|
||||||
* for that worker as well as modify runtime environments in an async fashion.
|
* for that worker as well as modify runtime environments in an async fashion.
|
||||||
* @param {String} cid capability id (e.g 0-0)
|
* @param {string} cid capability id (e.g 0-0)
|
||||||
* @param {[type]} caps object containing capabilities for session that will be spawn in the worker
|
* @param {object} caps object containing capabilities for session that will be spawn in the worker
|
||||||
* @param {[type]} specs specs to be run in the worker process
|
* @param {object} specs specs to be run in the worker process
|
||||||
* @param {[type]} args object that will be merged with the main configuration once worker is initialised
|
* @param {object} args object that will be merged with the main configuration once worker is initialized
|
||||||
* @param {[type]} execArgv list of string arguments passed to the worker process
|
* @param {object} execArgv list of string arguments passed to the worker process
|
||||||
*/
|
*/
|
||||||
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
|
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
|
||||||
// },
|
// },
|
||||||
|
/**
|
||||||
|
* Gets executed just after a worker process has exited.
|
||||||
|
* @param {string} cid capability id (e.g 0-0)
|
||||||
|
* @param {number} exitCode 0 - success, 1 - fail
|
||||||
|
* @param {object} specs specs to be run in the worker process
|
||||||
|
* @param {number} retries number of retries used
|
||||||
|
*/
|
||||||
|
// onWorkerEnd: function (cid, exitCode, specs, retries) {
|
||||||
|
// },
|
||||||
/**
|
/**
|
||||||
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
||||||
* to manipulate configurations depending on the capability or spec.
|
* to manipulate configurations depending on the capability or spec.
|
||||||
* @param {Object} config wdio configuration object
|
* @param {object} config wdio configuration object
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||||
|
* @param {string} cid worker id (e.g. 0-0)
|
||||||
*/
|
*/
|
||||||
// beforeSession: function (config, capabilities, specs) {
|
// beforeSession: function (config, capabilities, specs, cid) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Gets executed before test execution begins. At this point you can access to all global
|
* Gets executed before test execution begins. At this point you can access to all global
|
||||||
* variables like `browser`. It is the perfect place to define custom commands.
|
* variables like `browser`. It is the perfect place to define custom commands.
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
* @param {Array.<String>} specs List of spec file paths that are to be run
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
||||||
* @param {Object} browser instance of created browser/device session
|
* @param {object} browser instance of created browser/device session
|
||||||
*/
|
*/
|
||||||
// before: function (capabilities, specs) {
|
// before: function (capabilities, specs) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Runs before a WebdriverIO command gets executed.
|
* Runs before a WebdriverIO command gets executed.
|
||||||
* @param {String} commandName hook command name
|
* @param {string} commandName hook command name
|
||||||
* @param {Array} args arguments that command would receive
|
* @param {Array} args arguments that command would receive
|
||||||
*/
|
*/
|
||||||
// beforeCommand: function (commandName, args) {
|
// beforeCommand: function (commandName, args) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Hook that gets executed before the suite starts
|
* Hook that gets executed before the suite starts
|
||||||
* @param {Object} suite suite details
|
* @param {object} suite suite details
|
||||||
*/
|
*/
|
||||||
// beforeSuite: function (suite) {
|
// beforeSuite: function (suite) {
|
||||||
// },
|
// },
|
||||||
|
@ -234,30 +226,37 @@ exports.config = {
|
||||||
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
|
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Function to be executed after a test (in Mocha/Jasmine).
|
* Function to be executed after a test (in Mocha/Jasmine only)
|
||||||
|
* @param {object} test test object
|
||||||
|
* @param {object} context scope object the test was executed with
|
||||||
|
* @param {Error} result.error error object in case the test fails, otherwise `undefined`
|
||||||
|
* @param {*} result.result return object of test function
|
||||||
|
* @param {number} result.duration duration of test
|
||||||
|
* @param {boolean} result.passed true if test has passed, otherwise false
|
||||||
|
* @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }`
|
||||||
*/
|
*/
|
||||||
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
|
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
|
||||||
// },
|
// },
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook that gets executed after the suite has ended
|
* Hook that gets executed after the suite has ended
|
||||||
* @param {Object} suite suite details
|
* @param {object} suite suite details
|
||||||
*/
|
*/
|
||||||
// afterSuite: function (suite) {
|
// afterSuite: function (suite) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Runs after a WebdriverIO command gets executed
|
* Runs after a WebdriverIO command gets executed
|
||||||
* @param {String} commandName hook command name
|
* @param {string} commandName hook command name
|
||||||
* @param {Array} args arguments that command would receive
|
* @param {Array} args arguments that command would receive
|
||||||
* @param {Number} result 0 - command success, 1 - command error
|
* @param {number} result 0 - command success, 1 - command error
|
||||||
* @param {Object} error error object if any
|
* @param {object} error error object if any
|
||||||
*/
|
*/
|
||||||
// afterCommand: function (commandName, args, result, error) {
|
// afterCommand: function (commandName, args, result, error) {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Gets executed after all tests are done. You still have access to all global variables from
|
* Gets executed after all tests are done. You still have access to all global variables from
|
||||||
* the test.
|
* the test.
|
||||||
* @param {Number} result 0 - test pass, 1 - test fail
|
* @param {number} result 0 - test pass, 1 - test fail
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
* @param {Array.<String>} specs List of spec file paths that ran
|
* @param {Array.<String>} specs List of spec file paths that ran
|
||||||
*/
|
*/
|
||||||
|
@ -265,7 +264,7 @@ exports.config = {
|
||||||
// },
|
// },
|
||||||
/**
|
/**
|
||||||
* Gets executed right after terminating the webdriver session.
|
* Gets executed right after terminating the webdriver session.
|
||||||
* @param {Object} config wdio configuration object
|
* @param {object} config wdio configuration object
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
* @param {Array.<String>} specs List of spec file paths that ran
|
* @param {Array.<String>} specs List of spec file paths that ran
|
||||||
*/
|
*/
|
||||||
|
@ -274,20 +273,17 @@ exports.config = {
|
||||||
/**
|
/**
|
||||||
* Gets executed after all workers got shut down and the process is about to exit. An error
|
* Gets executed after all workers got shut down and the process is about to exit. An error
|
||||||
* thrown in the onComplete hook will result in the test run failing.
|
* thrown in the onComplete hook will result in the test run failing.
|
||||||
* @param {Object} exitCode 0 - success, 1 - fail
|
* @param {object} exitCode 0 - success, 1 - fail
|
||||||
* @param {Object} config wdio configuration object
|
* @param {object} config wdio configuration object
|
||||||
* @param {Array.<Object>} capabilities list of capabilities details
|
* @param {Array.<Object>} capabilities list of capabilities details
|
||||||
* @param {<Object>} results object containing test results
|
* @param {<Object>} results object containing test results
|
||||||
*/
|
*/
|
||||||
onComplete(exitCode, config, capabilities, results) {
|
// onComplete: function(exitCode, config, capabilities, results) {
|
||||||
if (exitCode !== 0) {
|
// },
|
||||||
fs.writeFileSync(path.join(reportsOutputDir, "./failure.txt"), "Tests failed");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* Gets executed when a refresh happens.
|
* Gets executed when a refresh happens.
|
||||||
* @param {String} oldSessionId session ID of the old session
|
* @param {string} oldSessionId session ID of the old session
|
||||||
* @param {String} newSessionId session ID of the new session
|
* @param {string} newSessionId session ID of the new session
|
||||||
*/
|
*/
|
||||||
// onReload: function(oldSessionId, newSessionId) {
|
// onReload: function(oldSessionId, newSessionId) {
|
||||||
// }
|
// }
|
|
@ -9,7 +9,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||||
import { TemplateResult, html, nothing } from "lit";
|
import { TemplateResult, css, html, nothing } from "lit";
|
||||||
|
|
||||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
||||||
import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";
|
import PFProgressStepper from "@patternfly/patternfly/components/ProgressStepper/progress-stepper.css";
|
||||||
|
@ -43,14 +43,40 @@ const idleState: State = { state: "idle", label: "" };
|
||||||
const runningState: State = { state: "running", label: msg("Saving Application...") };
|
const runningState: State = { state: "running", label: msg("Saving Application...") };
|
||||||
const errorState: State = {
|
const errorState: State = {
|
||||||
state: "error",
|
state: "error",
|
||||||
label: msg(html`There was an error in saving your application.<br />The error message was:`),
|
label: msg("There was an error in saving your application:"),
|
||||||
};
|
};
|
||||||
const doneState: State = { state: "done", label: msg("Your application has been saved") };
|
const doneState: State = { state: "done", label: msg("Your application has been saved") };
|
||||||
|
|
||||||
|
function extract(o: Record<string, any>): string[] {
|
||||||
|
function inner(o: Record<string, any>): string[] {
|
||||||
|
if (typeof o !== "object") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(o)) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
return Object.keys(o)
|
||||||
|
.map((k) => inner(o[k]))
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
return inner(o);
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("ak-application-wizard-commit-application")
|
@customElement("ak-application-wizard-commit-application")
|
||||||
export class ApplicationWizardCommitApplication extends BasePanel {
|
export class ApplicationWizardCommitApplication extends BasePanel {
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [...super.styles, PFBullseye, PFEmptyState, PFTitle, PFProgressStepper];
|
return [
|
||||||
|
...super.styles,
|
||||||
|
PFBullseye,
|
||||||
|
PFEmptyState,
|
||||||
|
PFTitle,
|
||||||
|
PFProgressStepper,
|
||||||
|
css`
|
||||||
|
.pf-c-title {
|
||||||
|
padding-bottom: var(--pf-global--spacer--md);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
|
@ -67,15 +93,15 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||||
this.response = undefined;
|
this.response = undefined;
|
||||||
this.commitState = runningState;
|
this.commitState = runningState;
|
||||||
const provider = providerModelsList.find(
|
const provider = providerModelsList.find(
|
||||||
({ formName }) => formName === this.wizard.providerModel,
|
({ formName }) => formName === this.wizard.providerModel
|
||||||
);
|
);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not determine provider model from user request: ${JSON.stringify(
|
`Could not determine provider model from user request: ${JSON.stringify(
|
||||||
this.wizard,
|
this.wizard,
|
||||||
null,
|
null,
|
||||||
2,
|
2
|
||||||
)}`,
|
)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,33 +117,25 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(
|
async send(
|
||||||
data: TransactionApplicationRequest,
|
data: TransactionApplicationRequest
|
||||||
): Promise<TransactionApplicationResponse | void> {
|
): Promise<TransactionApplicationResponse | void> {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
const timeout = new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, 1200);
|
new CoreApi(DEFAULT_CONFIG)
|
||||||
});
|
.coreTransactionalApplicationsUpdate({
|
||||||
const network = new CoreApi(DEFAULT_CONFIG).coreTransactionalApplicationsUpdate({
|
|
||||||
transactionApplicationRequest: data,
|
transactionApplicationRequest: data,
|
||||||
});
|
})
|
||||||
|
.then((response: TransactionApplicationResponse) => {
|
||||||
Promise.allSettled([network, timeout]).then(([network_resolution]) => {
|
this.response = response;
|
||||||
if (network_resolution.status === "rejected") {
|
|
||||||
this.commitState = errorState;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (network_resolution.status === "fulfilled") {
|
|
||||||
if (!network_resolution.value.valid) {
|
|
||||||
this.commitState = errorState;
|
|
||||||
this.errors = network_resolution.value.logs;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.response = network_resolution.value;
|
|
||||||
this.dispatchCustomEvent(EVENT_REFRESH);
|
this.dispatchCustomEvent(EVENT_REFRESH);
|
||||||
this.dispatchWizardUpdate({ status: "submitted" });
|
this.dispatchWizardUpdate({ status: "submitted" });
|
||||||
this.commitState = doneState;
|
this.commitState = doneState;
|
||||||
}
|
})
|
||||||
|
.catch((resolution: any) => {
|
||||||
|
resolution.response.json().then((body: Record<string, any>) => {
|
||||||
|
this.errors = extract(body);
|
||||||
|
this.commitState = errorState;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +153,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||||
${this.errors.length > 0
|
${this.errors.length > 0
|
||||||
? html`<ul>
|
? html`<ul>
|
||||||
${this.errors.map(
|
${this.errors.map(
|
||||||
(msg) => html`<li><code>${msg}</code></li>`,
|
(msg) => html`<li><code>${msg}</code></li>`
|
||||||
)}
|
)}
|
||||||
</ul>`
|
</ul>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter"
|
||||||
|
|
||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
import { property, query } from "lit/decorators.js";
|
import { property, query } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
|
||||||
import { FlowsApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
|
import { FlowsApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
|
||||||
import type { Flow, FlowsInstancesListRequest } from "@goauthentik/api";
|
import type { Flow, FlowsInstancesListRequest } from "@goauthentik/api";
|
||||||
|
@ -123,6 +124,7 @@ export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement)
|
||||||
.renderElement=${renderElement}
|
.renderElement=${renderElement}
|
||||||
.renderDescription=${renderDescription}
|
.renderDescription=${renderDescription}
|
||||||
.value=${getFlowValue}
|
.value=${getFlowValue}
|
||||||
|
.name=${this.name}
|
||||||
?blankable=${!this.required}
|
?blankable=${!this.required}
|
||||||
>
|
>
|
||||||
</ak-search-select>
|
</ak-search-select>
|
||||||
|
|
|
@ -83,7 +83,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.shadowRoot
|
this.shadowRoot
|
||||||
?.querySelectorAll<HTMLInputElement>(
|
?.querySelectorAll<HTMLInputElement>(
|
||||||
".pf-c-form-control.pf-c-select__toggle-typeahead",
|
".pf-c-form-control.pf-c-select__toggle-typeahead"
|
||||||
)
|
)
|
||||||
.forEach((input) => {
|
.forEach((input) => {
|
||||||
input.blur();
|
input.blur();
|
||||||
|
@ -134,6 +134,9 @@ export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.dropdownContainer = document.createElement("div");
|
this.dropdownContainer = document.createElement("div");
|
||||||
this.dropdownContainer.dataset["managedBy"] = "ak-search-select";
|
this.dropdownContainer.dataset["managedBy"] = "ak-search-select";
|
||||||
|
if (this.name) {
|
||||||
|
this.dropdownContainer.dataset["managedFor"] = this.name;
|
||||||
|
}
|
||||||
document.body.append(this.dropdownContainer);
|
document.body.append(this.dropdownContainer);
|
||||||
this.updateData();
|
this.updateData();
|
||||||
this.addEventListener(EVENT_REFRESH, this.updateData);
|
this.addEventListener(EVENT_REFRESH, this.updateData);
|
||||||
|
@ -261,7 +264,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||||
</ul>
|
</ul>
|
||||||
</div>`,
|
</div>`,
|
||||||
this.dropdownContainer,
|
this.dropdownContainer,
|
||||||
{ host: this },
|
{ host: this }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +306,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AKElement) {
|
||||||
// Check if we're losing focus to one of our dropdown items, and if such don't blur
|
// Check if we're losing focus to one of our dropdown items, and if such don't blur
|
||||||
if (ev.relatedTarget instanceof HTMLButtonElement) {
|
if (ev.relatedTarget instanceof HTMLButtonElement) {
|
||||||
const parentMenu = ev.relatedTarget.closest(
|
const parentMenu = ev.relatedTarget.closest(
|
||||||
"ul.pf-c-dropdown__menu.pf-m-static",
|
"ul.pf-c-dropdown__menu.pf-m-static"
|
||||||
);
|
);
|
||||||
if (parentMenu && parentMenu.id === this.dropdownUID) {
|
if (parentMenu && parentMenu.id === this.dropdownUID) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -67,7 +67,7 @@ export class LibraryPage extends AKElement {
|
||||||
this.filteredApps = this.apps?.results;
|
this.filteredApps = this.apps?.results;
|
||||||
if (this.filteredApps === undefined) {
|
if (this.filteredApps === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Application.results should never be undefined when passed to the Library Page.",
|
"Application.results should never be undefined when passed to the Library Page."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.addEventListener(SEARCH_UPDATED, this.searchUpdated);
|
this.addEventListener(SEARCH_UPDATED, this.searchUpdated);
|
||||||
|
@ -136,7 +136,9 @@ export class LibraryPage extends AKElement {
|
||||||
render() {
|
render() {
|
||||||
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
|
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
|
||||||
<div class="pf-c-content header">
|
<div class="pf-c-content header">
|
||||||
<h1 id="library-page-title">${msg("My applications")}</h1>
|
<h1 role="heading" aria-level="1" id="library-page-title">
|
||||||
|
${msg("My applications")}
|
||||||
|
</h1>
|
||||||
${this.uiConfig.searchEnabled ? this.renderSearch() : html``}
|
${this.uiConfig.searchEnabled ? this.renderSearch() : html``}
|
||||||
</div>
|
</div>
|
||||||
<section class="pf-c-page__main-section">
|
<section class="pf-c-page__main-section">
|
||||||
|
@ -144,7 +146,7 @@ export class LibraryPage extends AKElement {
|
||||||
this.apps,
|
this.apps,
|
||||||
html`${this.filteredApps.find(appHasLaunchUrl)
|
html`${this.filteredApps.find(appHasLaunchUrl)
|
||||||
? this.renderApps()
|
? this.renderApps()
|
||||||
: this.renderEmptyState()}`,
|
: this.renderEmptyState()}`
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</main>`;
|
</main>`;
|
||||||
|
|
Reference in a new issue