mirror of
https://github.com/keycloak/keycloak-js.git
synced 2026-05-26 13:50:39 +00:00
Add basic test suite setup (#55)
Closes #53 Signed-off-by: Jon Koops <jonkoops@gmail.com> Signed-off-by: Peter Skopek <pskopek@redhat.com> Co-authored-by: Peter Skopek <pskopek@redhat.com>
This commit is contained in:
+15
-6
@@ -1,11 +1,20 @@
|
|||||||
# Keycloak Javascript adapter
|
# Keycloak JS
|
||||||
|
|
||||||
Client-side JavaScript library that can be used to secure web applications.
|
Most of the contribution rules from the [main Keycloak repository](https://github.com/keycloak/keycloak/blob/main/CONTRIBUTING.md) apply Keycloak JS as well. Documented below are steps unique to the development of Keycloak JS.
|
||||||
|
|
||||||
Most of the contribution rules from the [main Keycloak repository](https://github.com/keycloak/keycloak/blob/main/CONTRIBUTING.md) applies to the
|
## Prerequisites
|
||||||
Keycloak Javascript adapter as well. Below some rules specific to javascript adapter.
|
|
||||||
|
|
||||||
## Building and working with the codebase
|
- [Node.js](https://nodejs.org/en/download) (latest LTS or greater)
|
||||||
|
- [Podman](https://podman.io/) (for testing)
|
||||||
|
|
||||||
TODO
|
## Setup
|
||||||
|
|
||||||
|
Make sure that all dependencies are installed by running the following command in the root of the project:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
To run the tests, follow the instructions in the [test directory](./test/README.md).
|
||||||
Generated
+1568
-199
File diff suppressed because it is too large
Load Diff
+6
-3
@@ -4,9 +4,12 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "A client-side JavaScript OpenID Connect library that can be used to secure web applications.",
|
"description": "A client-side JavaScript OpenID Connect library that can be used to secure web applications.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "standard",
|
"lint": "ts-standard",
|
||||||
"guides": "node docs/guides/guides.mjs $npm_package_version"
|
"guides": "node docs/guides/guides.mjs $npm_package_version"
|
||||||
},
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./lib/keycloak.d.ts",
|
"types": "./lib/keycloak.d.ts",
|
||||||
@@ -37,13 +40,13 @@
|
|||||||
"oauth2",
|
"oauth2",
|
||||||
"authentication"
|
"authentication"
|
||||||
],
|
],
|
||||||
"standard": {
|
"ts-standard": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"lib/*"
|
"lib/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"standard": "^17.1.2"
|
"ts-standard": "^12.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Playwright
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
# Keycloak JS test suite
|
||||||
|
|
||||||
|
This directory contains the test suite for Keycloak JS, which is based on [Playwright](https://playwright.dev/). It contains a suite of integration tests that embed the adapter in various scenarios and tests it against a Keycloak server running in the background.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Run the following command to install the [Playwright browsers](https://playwright.dev/docs/browsers) and required system dependencies:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx playwright install
|
||||||
|
```
|
||||||
|
|
||||||
|
It might be that this command fails due to missing system dependencies, in that case add the `--with-deps` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx playwright install --with-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup on Linux distributions unsupported by Playwright
|
||||||
|
|
||||||
|
Playwright doesn't support some Linux-based distributions, if you are on Linux and the installation steps above did not work for you, follow the steps below, otherwise, skip to [running the tests](#running-the-tests).
|
||||||
|
|
||||||
|
In order to run the tests on unsupported distributions you can use Distrobox to run an Ubuntu 22.04 image on top of your host system, which is supported by Playwright.
|
||||||
|
|
||||||
|
#### 1. Install `distrobox` and `podman` packages
|
||||||
|
|
||||||
|
First, install both [Distrobox](https://distrobox.it/#installation) and [Podman](https://podman.io/docs/installation). Then, create home directory for your Distrobox environment (this helps avoid conflicts with your host system's home directory):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir ~/distrobox
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Create a container environment to run tests
|
||||||
|
|
||||||
|
Create a container in your host (for more information see the [documentation](https://distrobox.it/)):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
distrobox create \
|
||||||
|
--name pw --image ubuntu:22.04 \
|
||||||
|
--home ~/distrobox \
|
||||||
|
--root \
|
||||||
|
--additional-packages "podman libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb" \
|
||||||
|
--unshare-all \
|
||||||
|
--absolutely-disable-root-password-i-am-really-positively-sure
|
||||||
|
```
|
||||||
|
|
||||||
|
Now enter the created Distrobox environment using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
distrobox enter --root pw
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Install Node.js
|
||||||
|
|
||||||
|
Whilst inside of the Distrobox environment, install Node.js by following the instructions from the [Node.js download page](https://nodejs.org/en/download). Using the latest LTS version is recommended.
|
||||||
|
|
||||||
|
|
||||||
|
It should now be possible to install the Playwright browsers by running the following command from the project root:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx playwright install --with-deps
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Make sure you are in the `test` directory. To run the tests headlessly you can run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also possible to run the tests in [various other modes](https://playwright.dev/docs/running-tests), for example, to debug the tests `--debug` can be passed:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test -- --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Speeding up testing
|
||||||
|
|
||||||
|
By default, the tests will run against a Keycloak server that is running the latest version. This server is started by Playwright using Podman by running the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
podman run -p 8080:8080 -p 9000:9000 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin -e KC_HEALTH_ENABLED=true --pull=newer quay.io/keycloak/keycloak:latest start-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you want to run the Keycloak server straight from the distribution (or your local development instance), without using Podman you can run it as follows:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME=admin KC_BOOTSTRAP_ADMIN_PASSWORD=admin KC_HEALTH_ENABLED=true ./bin/kc.sh start-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Every time the tests run the Keycloak server will also be restarted, which can slow down development. You can instead opt to keep a Keycloak server running in the background, and re-use this server. To do so, remove the `gracefulShutdown` section from the Playwright configuration (`playwright.config.ts`):
|
||||||
|
|
||||||
|
```diff
|
||||||
|
{
|
||||||
|
- gracefulShutdown: {
|
||||||
|
- // Podman requires a termination signal to stop.
|
||||||
|
- signal: 'SIGTERM',
|
||||||
|
- timeout: 5000
|
||||||
|
- }
|
||||||
|
},
|
||||||
|
```
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
// Set up Express
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
// Expose 'public' directory and Keycloak JS source.
|
||||||
|
app.use(express.static(path.resolve(import.meta.dirname, 'public')))
|
||||||
|
app.use(express.static(path.resolve(import.meta.dirname, '../../lib')))
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
app.listen(3000)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Keycloak JS Tests</title>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="color-scheme" content="light dark">
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"keycloak-js": "./keycloak.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="modulepreload" href="./keycloak.js">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
import Keycloak from 'keycloak-js'
|
||||||
|
|
||||||
|
globalThis.Keycloak = Keycloak
|
||||||
|
globalThis.keycloak = null
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
parent.postMessage(location.href, location.origin)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "keycloak-js-test",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"test": "playwright test",
|
||||||
|
"app": "node app/app.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@keycloak/keycloak-admin-client": "^26.1.4",
|
||||||
|
"@playwright/test": "^1.51.1",
|
||||||
|
"@types/express": "^5.0.1",
|
||||||
|
"@types/node": "^22.13.14",
|
||||||
|
"express": "^5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from '@playwright/test'
|
||||||
|
import { APP_HOST } from './support/common.ts'
|
||||||
|
|
||||||
|
const KEYCLOAK_VERSION = 'latest'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
webServer: [{
|
||||||
|
command: `podman run -p 8080:8080 -p 9000:9000 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin -e KC_HEALTH_ENABLED=true --pull=newer quay.io/keycloak/keycloak:${KEYCLOAK_VERSION} start-dev`,
|
||||||
|
url: 'http://localhost:9000/health/live',
|
||||||
|
stdout: 'pipe',
|
||||||
|
reuseExistingServer: true,
|
||||||
|
gracefulShutdown: {
|
||||||
|
// Podman requires a termination signal to stop.
|
||||||
|
signal: 'SIGTERM',
|
||||||
|
timeout: 5000
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
command: 'npm run app',
|
||||||
|
port: 3000,
|
||||||
|
stdout: 'pipe'
|
||||||
|
}],
|
||||||
|
use: {
|
||||||
|
baseURL: APP_HOST
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import AdminClient from '@keycloak/keycloak-admin-client'
|
||||||
|
import { ADMIN_PASSWORD, ADMIN_USERNAME, AUTH_SERVER_HOST } from './common.ts'
|
||||||
|
|
||||||
|
export const adminClient = new AdminClient({
|
||||||
|
baseUrl: AUTH_SERVER_HOST
|
||||||
|
})
|
||||||
|
|
||||||
|
await adminClient.auth({
|
||||||
|
username: ADMIN_USERNAME,
|
||||||
|
password: ADMIN_PASSWORD,
|
||||||
|
grantType: 'password',
|
||||||
|
clientId: 'admin-cli'
|
||||||
|
})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const ADMIN_USERNAME = 'admin'
|
||||||
|
export const ADMIN_PASSWORD = 'admin'
|
||||||
|
export const APP_HOST = 'http://localhost:3000'
|
||||||
|
export const AUTH_SERVER_HOST = 'http://localhost:8080'
|
||||||
|
export const CLIENT_ID = 'keycloak-js-test-client'
|
||||||
|
export const AUTHORIZED_USERNAME = 'test-user@localhost'
|
||||||
|
export const AUTHORIZED_PASSWORD = 'password'
|
||||||
|
export const UNAUTHORIZED_USERNAME = 'unauthorized'
|
||||||
|
export const UNAUTHORIZED_PASSWORD = 'password'
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type CredentialRepresentation from '@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation.ts'
|
||||||
|
import { adminClient } from './admin-client.ts'
|
||||||
|
import { APP_HOST, AUTHORIZED_PASSWORD, AUTHORIZED_USERNAME, CLIENT_ID, UNAUTHORIZED_PASSWORD, UNAUTHORIZED_USERNAME } from './common.ts'
|
||||||
|
|
||||||
|
export async function createTestResources (): Promise<string> {
|
||||||
|
const { realmName } = await adminClient.realms.create({
|
||||||
|
realm: crypto.randomUUID(),
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
adminClient.roles.create({
|
||||||
|
realm: realmName,
|
||||||
|
name: 'user',
|
||||||
|
scopeParamRequired: false
|
||||||
|
}),
|
||||||
|
adminClient.roles.create({
|
||||||
|
realm: realmName,
|
||||||
|
name: 'admin',
|
||||||
|
scopeParamRequired: false
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
createUserWithCredential({
|
||||||
|
realm: realmName,
|
||||||
|
enabled: true,
|
||||||
|
username: AUTHORIZED_USERNAME,
|
||||||
|
firstName: 'Authorized',
|
||||||
|
lastName: 'User',
|
||||||
|
email: 'test-user@localhost',
|
||||||
|
emailVerified: true,
|
||||||
|
realmRoles: ['user'],
|
||||||
|
clientRoles: {
|
||||||
|
'realm-management': ['view-realm', 'manage-users'],
|
||||||
|
account: ['view-profile', 'manage-account']
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
temporary: false,
|
||||||
|
type: 'password',
|
||||||
|
value: AUTHORIZED_PASSWORD
|
||||||
|
}),
|
||||||
|
createUserWithCredential({
|
||||||
|
realm: realmName,
|
||||||
|
enabled: true,
|
||||||
|
username: UNAUTHORIZED_USERNAME,
|
||||||
|
firstName: 'Unauthorized',
|
||||||
|
lastName: 'User',
|
||||||
|
email: 'unauthorized@localhost',
|
||||||
|
emailVerified: true
|
||||||
|
}, {
|
||||||
|
temporary: false,
|
||||||
|
type: 'password',
|
||||||
|
value: UNAUTHORIZED_PASSWORD
|
||||||
|
})
|
||||||
|
])
|
||||||
|
|
||||||
|
await adminClient.clients.create({
|
||||||
|
realm: realmName,
|
||||||
|
enabled: true,
|
||||||
|
clientId: CLIENT_ID,
|
||||||
|
redirectUris: [`${APP_HOST}/*`],
|
||||||
|
webOrigins: [APP_HOST],
|
||||||
|
publicClient: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return realmName
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateUserParams = NonNullable<Parameters<typeof adminClient.users.create>[0]>
|
||||||
|
|
||||||
|
async function createUserWithCredential (user: CreateUserParams, credential: CredentialRepresentation): Promise<void> {
|
||||||
|
const { id } = await adminClient.users.create(user)
|
||||||
|
|
||||||
|
await adminClient.users.resetPassword({
|
||||||
|
realm: user.realm,
|
||||||
|
id,
|
||||||
|
credential
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import type { Page } from 'playwright'
|
||||||
|
import type Keycloak from '../../lib/keycloak.d.ts'
|
||||||
|
import type { KeycloakConfig, KeycloakInitOptions, KeycloakLoginOptions, KeycloakLogoutOptions } from '../../lib/keycloak.d.ts'
|
||||||
|
import { APP_HOST, AUTH_SERVER_HOST, AUTHORIZED_PASSWORD, AUTHORIZED_USERNAME, CLIENT_ID } from './common.ts'
|
||||||
|
|
||||||
|
export class TestExecutor {
|
||||||
|
readonly #page: Page
|
||||||
|
readonly #realm: string
|
||||||
|
|
||||||
|
constructor (page: Page, realm: string) {
|
||||||
|
this.#page = page
|
||||||
|
this.#realm = realm
|
||||||
|
}
|
||||||
|
|
||||||
|
async instantiateAdapter (config: KeycloakConfig = { url: AUTH_SERVER_HOST, realm: this.#realm, clientId: CLIENT_ID }): Promise<void> {
|
||||||
|
await this.#ensureOnAppPage()
|
||||||
|
await this.#page.evaluate((config) => {
|
||||||
|
(globalThis as any).keycloak = new (globalThis as any).Keycloak(config)
|
||||||
|
}, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
async initializeAdapter (options: KeycloakInitOptions = { onLoad: 'check-sso' }): Promise<boolean> {
|
||||||
|
await this.#ensureOnAppPage()
|
||||||
|
await this.#ensureInstantiated()
|
||||||
|
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
// Because `.evaluate()` can throw an error if a navigation occurs, we need to capture the result
|
||||||
|
// to differentiate between the error thrown by the adapter and the error thrown by an unexpected navigation.
|
||||||
|
result = await this.#page.evaluate(async (options) => {
|
||||||
|
try {
|
||||||
|
const value = await ((globalThis as any).keycloak as Keycloak).init(options)
|
||||||
|
return { value, error: null }
|
||||||
|
} catch (error) {
|
||||||
|
return { value: null, error }
|
||||||
|
}
|
||||||
|
}, options)
|
||||||
|
} catch {
|
||||||
|
// The only reason an error is thrown here is because the page navigated, which is expected and can be ignored.
|
||||||
|
result = { value: null, error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.error !== null) {
|
||||||
|
// The error is not related to the navigation, so we need to throw it.
|
||||||
|
throw result.error as Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.value ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitLoginForm (username = AUTHORIZED_USERNAME, password = AUTHORIZED_PASSWORD): Promise<void> {
|
||||||
|
await this.#page.getByRole('textbox', { name: 'Username or email' }).fill(username)
|
||||||
|
await this.#page.getByRole('textbox', { name: 'Password' }).fill(password)
|
||||||
|
await this.#page.getByRole('button', { name: 'Sign In' }).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async login (options?: KeycloakLoginOptions): Promise<void> {
|
||||||
|
await this.#assertInstantiated()
|
||||||
|
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
// Because `.evaluate()` can throw an error if a navigation occurs, we need to capture the result
|
||||||
|
// to differentiate between the error thrown by the adapter and the error thrown by an unexpected navigation.
|
||||||
|
result = await this.#page.evaluate(async (options) => {
|
||||||
|
try {
|
||||||
|
await ((globalThis as any).keycloak as Keycloak).login(options)
|
||||||
|
return { error: null }
|
||||||
|
} catch (error) {
|
||||||
|
return { error }
|
||||||
|
}
|
||||||
|
}, options)
|
||||||
|
} catch {
|
||||||
|
// The only reason an error is thrown here is because the page navigated, which is expected and can be ignored.
|
||||||
|
result = { error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.error !== null) {
|
||||||
|
// The error is not related to the navigation, so we need to throw it.
|
||||||
|
throw result.error as Error
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#waitForLoginPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout (options?: KeycloakLogoutOptions): Promise<void> {
|
||||||
|
await this.#assertInstantiated()
|
||||||
|
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
// Because `.evaluate()` can throw an error if a navigation occurs, we need to capture the result
|
||||||
|
// to differentiate between the error thrown by the adapter and the error thrown by an unexpected navigation.
|
||||||
|
result = await this.#page.evaluate(async (options) => {
|
||||||
|
try {
|
||||||
|
await ((globalThis as any).keycloak as Keycloak).logout(options)
|
||||||
|
return { error: null }
|
||||||
|
} catch (error) {
|
||||||
|
return { error }
|
||||||
|
}
|
||||||
|
}, options)
|
||||||
|
} catch {
|
||||||
|
// The only reason an error is thrown here is because the page navigated, which is expected and can be ignored.
|
||||||
|
result = { error: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.error !== null) {
|
||||||
|
// The error is not related to the navigation, so we need to throw it.
|
||||||
|
throw result.error as Error
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.#waitForAppPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #ensureOnAppPage (): Promise<void> {
|
||||||
|
if (!this.#page.url().startsWith(APP_HOST)) {
|
||||||
|
await this.#page.goto(APP_HOST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #ensureInstantiated (): Promise<void> {
|
||||||
|
if (!await this.#isInstantiated()) {
|
||||||
|
await this.instantiateAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #assertInstantiated (): Promise<void> {
|
||||||
|
if (!await this.#isInstantiated()) {
|
||||||
|
throw new Error('The adapter is not instantiated, make sure the adapter is instantiated before calling this method.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #isInstantiated (): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await this.#page.evaluate(() => {
|
||||||
|
return ((globalThis as any).keycloak as Keycloak | null) !== null
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #waitForAppPage (): Promise<void> {
|
||||||
|
await this.#page.waitForURL(APP_HOST + '/**')
|
||||||
|
}
|
||||||
|
|
||||||
|
async #waitForLoginPage (): Promise<void> {
|
||||||
|
await this.#page.waitForURL(AUTH_SERVER_HOST + '/**')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { expect, test } from '@playwright/test'
|
||||||
|
import { createTestResources } from '../support/helpers.ts'
|
||||||
|
import { TestExecutor } from '../support/test-executor.ts'
|
||||||
|
|
||||||
|
test('logs in and out', async ({ page }) => {
|
||||||
|
const realm = await createTestResources()
|
||||||
|
const executor = new TestExecutor(page, realm)
|
||||||
|
// Initially, no user should be authenticated.
|
||||||
|
expect(await executor.initializeAdapter()).toBe(false)
|
||||||
|
// After triggering a login, the user should be authenticated.
|
||||||
|
await executor.login()
|
||||||
|
await executor.submitLoginForm()
|
||||||
|
expect(await executor.initializeAdapter()).toBe(true)
|
||||||
|
// After logging out, the user should no longer be authenticated.
|
||||||
|
await executor.logout()
|
||||||
|
expect(await executor.initializeAdapter()).toBe(false)
|
||||||
|
})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user