diff --git a/test/scenarios/.gitignore b/test/scenarios/.gitignore new file mode 100644 index 0000000000..75e854d8dc --- /dev/null +++ b/test/scenarios/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/test/scenarios/README.md b/test/scenarios/README.md new file mode 100644 index 0000000000..7ff8e253e7 --- /dev/null +++ b/test/scenarios/README.md @@ -0,0 +1,33 @@ +# OpenID Keycloak scenarios + +This allow to run tests to validate SSO login using [Playwright](https://playwright.dev/). +This import the [.env](../oidc/.env) (which need to be created using [.env.temmplate](../oidc/.env.temmplate)) file for user credentials. + +## Install + +```bash +npm install +``` + +## Usage + +To run all the tests: + +```bash +npx playwright test +``` + +To access the ui to easily run test individually and debug if needed: + +```bash +npx playwright test --ui +``` + +## Writing scenario + +When creating new scenario use the recorder to more easily identify elements (in general try to rely on visible hint to identify elements and not hidden ids). +This does not start the server, you will need to run `docker-compose up` in a different terminal. + +```bash +npx playwright codegen "http://127.0.0.1:8000" +``` diff --git a/test/scenarios/package-lock.json b/test/scenarios/package-lock.json new file mode 100644 index 0000000000..027676aded --- /dev/null +++ b/test/scenarios/package-lock.json @@ -0,0 +1,105 @@ +{ + "name": "scenarios", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scenarios", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.38.0", + "@types/node": "^20.6.1", + "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.0.tgz", + "integrity": "sha512-xis/RXXsLxwThKnlIXouxmIvvT3zvQj1JE39GsNieMUrMpb3/GySHDh2j8itCG22qKVD4MYLBp7xB73cUW/UUw==", + "dev": true, + "dependencies": { + "playwright": "1.38.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.1.tgz", + "integrity": "sha512-4LcJvuXQlv4lTHnxwyHQZ3uR9Zw2j7m1C9DfuwoTFQQP4Pmu04O6IfLYgMmHoOCt0nosItLLZAH+sOrRE0Bo8g==", + "dev": true + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.0.tgz", + "integrity": "sha512-fJGw+HO0YY+fU/F1N57DMO+TmXHTrmr905J05zwAQE9xkuwP/QLDk63rVhmyxh03dYnEhnRbsdbH9B0UVVRB3A==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", + "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + } + } +} diff --git a/test/scenarios/package.json b/test/scenarios/package.json new file mode 100644 index 0000000000..b64e564c8e --- /dev/null +++ b/test/scenarios/package.json @@ -0,0 +1,16 @@ +{ + "name": "scenarios", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.38.0", + "@types/node": "^20.6.1", + "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0" + } +} diff --git a/test/scenarios/playwright.config.ts b/test/scenarios/playwright.config.ts new file mode 100644 index 0000000000..685100b4b7 --- /dev/null +++ b/test/scenarios/playwright.config.ts @@ -0,0 +1,48 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './.', + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + timeout: 10 * 1000, + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:8000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'cd ../oidc && docker-compose --profile VaultWarden up --force-recreate -V', + url: 'http://127.0.0.1:8080/realms/test', + reuseExistingServer: !process.env.CI, + timeout: 300 * 1000 + } +}); diff --git a/test/scenarios/tests/login.spec.ts b/test/scenarios/tests/login.spec.ts new file mode 100644 index 0000000000..e93e8cc3cd --- /dev/null +++ b/test/scenarios/tests/login.spec.ts @@ -0,0 +1,55 @@ +import { test, expect } from '@playwright/test'; +import dotenv from 'dotenv'; +import dotenvExpand from 'dotenv-expand'; + +var myEnv = dotenv.config({ path: '../oidc/.env' }) +dotenvExpand.expand(myEnv) + +test('SSO first login', async ({ page }) => { + // Landing page + await page.goto('/'); + await page.getByLabel(/Email address/).fill(process.env.TEST_USER_MAIL); + await page.getByRole('button', { name: 'Continue' }).click(); + + // Unlock page + await page.getByRole('link', { name: /Enterprise single sign-on/ }).click(); + + // Keycloak Login page + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(process.env.TEST_USER); + await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD); + await page.getByRole('button', { name: 'Sign In' }).click(); + + // Back to Vault create account + await expect(page).toHaveTitle(/Set master password/); + await page.getByLabel('Master password', { exact: true }).fill('Master password'); + await page.getByLabel('Re-type master password').fill('Master password'); + await page.getByRole('button', { name: 'Submit' }).click(); + + // We are now in the default vault page + await expect(page).toHaveTitle(/Vaults/); +}); + +test('SSO second login', async ({ page }) => { + // Landing page + await page.goto('/'); + await page.getByLabel(/Email address/).fill(process.env.TEST_USER_MAIL); + await page.getByRole('button', { name: 'Continue' }).click(); + + // Unlock page + await page.getByRole('link', { name: /Enterprise single sign-on/ }).click(); + + // Keycloak Login page + await expect(page.getByRole('heading', { name: 'Sign in to your account' })).toBeVisible(); + await page.getByLabel(/Username/).fill(process.env.TEST_USER); + await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD); + await page.getByRole('button', { name: 'Sign In' }).click(); + + // Back to Vault unlock page + await expect(page).toHaveTitle('Vaultwarden Web'); + await page.getByLabel('Master password').fill('Master password'); + await page.getByRole('button', { name: 'Unlock' }).click(); + + // We are now in the default vault page + await expect(page).toHaveTitle(/Vaults/); +});