22 changed files with 3472 additions and 4 deletions
			
			
		| @ -0,0 +1,6 @@ | |||
| logs | |||
| node_modules/ | |||
| /test-results/ | |||
| /playwright-report/ | |||
| /playwright/.cache/ | |||
| temp | |||
| @ -0,0 +1,88 @@ | |||
| # Integration tests | |||
| 
 | |||
| This allows running integration tests using [Playwright](https://playwright.dev/). | |||
| \ | |||
| It usse its own [test.env](/test/scenarios/test.env) with different ports to not collide with a running dev instance. | |||
| 
 | |||
| ## Install | |||
| 
 | |||
| This rely on `docker` and the `compose` [plugin](https://docs.docker.com/compose/install/). | |||
| Databases (`Mariadb`, `Mysql` and `Postgres`) and `Playwright` will run in containers. | |||
| 
 | |||
| ### Running Playwright outside docker | |||
| 
 | |||
| It's possible to run `Playwright` outside of the container, this remove the need to rebuild the image for each change. | |||
| You'll additionally need `nodejs` then run: | |||
| 
 | |||
| ```bash | |||
| npm install | |||
| npx playwright install-deps | |||
| npx playwright install firefox | |||
| ``` | |||
| 
 | |||
| ## Usage | |||
| 
 | |||
| To run all the tests: | |||
| 
 | |||
| ```bash | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright | |||
| ``` | |||
| 
 | |||
| To force a rebuild of the Playwright image: | |||
| ```bash | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env build Playwright | |||
| ``` | |||
| 
 | |||
| To access the ui to easily run test individually and debug if needed (will not work in docker): | |||
| 
 | |||
| ```bash | |||
| npx playwright test --ui | |||
| ``` | |||
| 
 | |||
| ### DB | |||
| 
 | |||
| Projects are configured to allow to run tests only on specific database. | |||
| \ | |||
| You can use: | |||
| 
 | |||
| ```bash | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=mariadb | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=mysql | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=postgres | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=sqlite | |||
| ``` | |||
| 
 | |||
| ### Running specific tests | |||
| 
 | |||
| To run a whole file you can : | |||
| 
 | |||
| ```bash | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=sqlite tests/login.spec.ts | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=sqlite login | |||
| ``` | |||
| 
 | |||
| To run only a specifc test (It might fail if it has dependency): | |||
| 
 | |||
| ```bash | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=sqlite -g "Account creation" | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env run Playwright test --project=sqlite tests/login.spec.ts:16 | |||
| ``` | |||
| 
 | |||
| ## 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 start it manually. | |||
| 
 | |||
| ```bash | |||
| npx playwright codegen "http://127.0.0.1:8000" | |||
| ``` | |||
| 
 | |||
| ## Override web-vault | |||
| 
 | |||
| It's possible to change the `web-vault` used by referencing a different `bw_web_builds` commit. | |||
| 
 | |||
| ```bash | |||
| export PW_WV_REPO_URL=https://github.com/Timshel/oidc_web_builds.git | |||
| export PW_WV_COMMIT_HASH=8707dc76df3f0cceef2be5bfae37bb29bd17fae6 | |||
| DOCKER_BUILDKIT=1 docker compose --env-file test.env build Playwright | |||
| ``` | |||
| @ -0,0 +1,40 @@ | |||
| FROM docker.io/library/debian:bookworm-slim | |||
| 
 | |||
| SHELL ["/bin/bash", "-o", "pipefail", "-c"] | |||
| 
 | |||
| ENV DEBIAN_FRONTEND=noninteractive | |||
| 
 | |||
| RUN apt-get update \ | |||
|     && apt-get install -y ca-certificates curl \ | |||
|     && curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \ | |||
|     && chmod a+r /etc/apt/keyrings/docker.asc \ | |||
|     && echo "deb [signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list \ | |||
|     && apt-get update \ | |||
|     && apt-get install -y --no-install-recommends \ | |||
|         containerd.io \ | |||
|         docker-buildx-plugin \ | |||
|         docker-ce \ | |||
|         docker-ce-cli \ | |||
|         docker-compose-plugin \ | |||
|         git \ | |||
|         libmariadb-dev-compat \ | |||
|         libpq5 \ | |||
|         nodejs \ | |||
|         npm \ | |||
|         openssl \ | |||
|     && rm -rf /var/lib/apt/lists/* | |||
| 
 | |||
| RUN mkdir /playwright | |||
| WORKDIR /playwright | |||
| 
 | |||
| COPY package.json . | |||
| RUN npm install && npx playwright install-deps && npx playwright install firefox | |||
| 
 | |||
| COPY docker-compose.yml test.env ./ | |||
| COPY compose ./compose | |||
| 
 | |||
| COPY *.ts test.env ./ | |||
| COPY tests ./tests | |||
| 
 | |||
| ENTRYPOINT ["/usr/bin/npx", "playwright"] | |||
| CMD ["test"] | |||
| @ -0,0 +1,39 @@ | |||
| FROM playwright_vaultwarden_prebuilt AS vaultwarden | |||
| 
 | |||
| FROM node:18-bookworm AS build | |||
| 
 | |||
| arg REPO_URL | |||
| arg COMMIT_HASH | |||
| 
 | |||
| ENV REPO_URL=$REPO_URL | |||
| ENV COMMIT_HASH=$COMMIT_HASH | |||
| 
 | |||
| COPY --from=vaultwarden /web-vault /web-vault | |||
| COPY build.sh /build.sh | |||
| RUN /build.sh | |||
| 
 | |||
| ######################## RUNTIME IMAGE  ######################## | |||
| FROM docker.io/library/debian:bookworm-slim | |||
| 
 | |||
| ENV DEBIAN_FRONTEND=noninteractive | |||
| 
 | |||
| # Create data folder and Install needed libraries | |||
| RUN mkdir /data && \ | |||
|     apt-get update && apt-get install -y \ | |||
|         --no-install-recommends \ | |||
|         ca-certificates \ | |||
|         curl \ | |||
|         libmariadb-dev-compat \ | |||
|         libpq5 \ | |||
|         openssl && \ | |||
|     rm -rf /var/lib/apt/lists/* | |||
| 
 | |||
| # Copies the files from the context (Rocket.toml file and web-vault) | |||
| # and the binary from the "build" stage to the current stage | |||
| WORKDIR / | |||
| 
 | |||
| COPY --from=vaultwarden /start.sh . | |||
| COPY --from=vaultwarden /vaultwarden . | |||
| COPY --from=build /web-vault ./web-vault | |||
| 
 | |||
| ENTRYPOINT ["/start.sh"] | |||
| @ -0,0 +1,24 @@ | |||
| #!/bin/bash | |||
| 
 | |||
| echo $REPO_URL | |||
| echo $COMMIT_HASH | |||
| 
 | |||
| if [[ ! -z "$REPO_URL" ]] && [[ ! -z "$COMMIT_HASH" ]] ; then | |||
|     rm -rf /web-vault | |||
| 
 | |||
|     mkdir bw_web_builds; | |||
|     cd bw_web_builds; | |||
| 
 | |||
|     git -c init.defaultBranch=main init | |||
|     git remote add origin "$REPO_URL" | |||
|     git fetch --depth 1 origin "$COMMIT_HASH" | |||
|     git -c advice.detachedHead=false checkout FETCH_HEAD | |||
| 
 | |||
|     export VAULT_VERSION=$(cat Dockerfile | grep "ARG VAULT_VERSION" | cut -d "=" -f2) | |||
|     ./scripts/checkout_web_vault.sh | |||
|     ./scripts/patch_web_vault.sh | |||
|     ./scripts/build_web_vault.sh | |||
|     printf '{"version":"%s"}' "$COMMIT_HASH" > ./web-vault/apps/web/build/vw-version.json | |||
| 
 | |||
|     mv ./web-vault/apps/web/build /web-vault | |||
| fi | |||
| @ -0,0 +1,76 @@ | |||
| services: | |||
|   VaultwardenPrebuild: | |||
|     container_name: playwright_vaultwarden_prebuilt | |||
|     image: playwright_vaultwarden_prebuilt | |||
|     build: | |||
|       context: .. | |||
|       dockerfile: Dockerfile | |||
|     entrypoint: /bin/bash | |||
|     restart: "no" | |||
| 
 | |||
|   Vaultwarden: | |||
|     container_name: playwright_vaultwarden-${ENV:-dev} | |||
|     image: playwright_vaultwarden-${ENV:-dev} | |||
|     network_mode: "host" | |||
|     build: | |||
|       context: compose/vaultwarden | |||
|       dockerfile: Dockerfile | |||
|       args: | |||
|         REPO_URL: ${PW_WV_REPO_URL:-} | |||
|         COMMIT_HASH: ${PW_WV_COMMIT_HASH:-} | |||
|     env_file: ${ENV}.env | |||
|     environment: | |||
|       - DATABASE_URL | |||
|       - I_REALLY_WANT_VOLATILE_STORAGE | |||
|       - SMTP_HOST | |||
|       - SMTP_FROM | |||
|       - SMTP_DEBUG | |||
|     restart: "no" | |||
| 
 | |||
|   Playwright: | |||
|     container_name: playwright_playwright | |||
|     image: playwright_playwright | |||
|     network_mode: "host" | |||
|     build: | |||
|       context: . | |||
|       dockerfile: compose/playwright/Dockerfile | |||
|     environment: | |||
|       - PW_WV_REPO_URL | |||
|       - PW_WV_COMMIT_HASH | |||
|     restart: "no" | |||
|     volumes: | |||
|       - /var/run/docker.sock:/var/run/docker.sock | |||
|       - ..:/project | |||
| 
 | |||
|   Mariadb: | |||
|     container_name: playwright_mariadb | |||
|     image: mariadb:11.2.4 | |||
|     env_file: test.env | |||
|     healthcheck: | |||
|       test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] | |||
|       start_period: 10s | |||
|       interval: 10s | |||
|     ports: | |||
|       - ${MARIADB_PORT}:3306 | |||
| 
 | |||
|   Mysql: | |||
|     container_name: playwright_mysql | |||
|     image: mysql:8.4.1 | |||
|     env_file: test.env | |||
|     healthcheck: | |||
|       test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] | |||
|       start_period: 10s | |||
|       interval: 10s | |||
|     ports: | |||
|       - ${MYSQL_PORT}:3306 | |||
| 
 | |||
|   Postgres: | |||
|     container_name: playwright_postgres | |||
|     image: postgres:16.3 | |||
|     env_file: test.env | |||
|     healthcheck: | |||
|       test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] | |||
|       start_period: 20s | |||
|       interval: 30s | |||
|     ports: | |||
|       - ${POSTGRES_PORT}:5432 | |||
| @ -0,0 +1,22 @@ | |||
| import { firefox, type FullConfig } from '@playwright/test'; | |||
| import { execSync } from 'node:child_process'; | |||
| import fs from 'fs'; | |||
| 
 | |||
| const utils = require('./global-utils'); | |||
| 
 | |||
| utils.loadEnv(); | |||
| 
 | |||
| async function globalSetup(config: FullConfig) { | |||
|     // Are we running in docker and the project is mounted ?
 | |||
|     const path = (fs.existsSync("/project/playwright/playwright.config.ts") ? "/project/playwright" : "."); | |||
|     execSync(`docker compose --project-directory ${path} --env-file test.env build VaultwardenPrebuild`, { | |||
|         env: { ...process.env }, | |||
|         stdio: "inherit" | |||
|     }); | |||
|     execSync(`docker compose --project-directory ${path} --env-file test.env build Vaultwarden`, { | |||
|         env: { ...process.env }, | |||
|         stdio: "inherit" | |||
|     }); | |||
| } | |||
| 
 | |||
| export default globalSetup; | |||
| @ -0,0 +1,207 @@ | |||
| import { type Browser, type TestInfo } from '@playwright/test'; | |||
| import { EventEmitter } from "events"; | |||
| import { execSync } from 'node:child_process'; | |||
| 
 | |||
| import dotenv from 'dotenv'; | |||
| import dotenvExpand from 'dotenv-expand'; | |||
| 
 | |||
| const fs = require("fs"); | |||
| const { spawn } = require('node:child_process'); | |||
| 
 | |||
| function loadEnv(){ | |||
|     var myEnv = dotenv.config({ path: 'test.env' }); | |||
|     dotenvExpand.expand(myEnv); | |||
| 
 | |||
|     return { | |||
|         user1: { | |||
|             email: process.env.TEST_USER_MAIL, | |||
|             name: process.env.TEST_USER_MAIL, | |||
|             password: process.env.TEST_USER_PASSWORD, | |||
|         }, | |||
|         user2: { | |||
|             email: process.env.TEST_USER2_MAIL, | |||
|             name: process.env.TEST_USER2_MAIL, | |||
|             password: process.env.TEST_USER2_PASSWORD, | |||
|         }, | |||
|         user3: { | |||
|             email: process.env.TEST_USER3_MAIL, | |||
|             name: process.env.TEST_USER3_MAIL, | |||
|             password: process.env.TEST_USER3_PASSWORD, | |||
|         }, | |||
|     } | |||
| } | |||
| 
 | |||
| async function waitFor(url: String, browser: Browser) { | |||
|     var ready = false; | |||
|     var context; | |||
| 
 | |||
|     do { | |||
|         try { | |||
|             context = await browser.newContext(); | |||
|             const page = await context.newPage(); | |||
|             await page.waitForTimeout(500); | |||
|             const result = await page.goto(url); | |||
|             ready = result.status() === 200; | |||
|         } catch(e) { | |||
|             if( !e.message.includes("CONNECTION_REFUSED") ){ | |||
|                 throw e; | |||
|             } | |||
|         } finally { | |||
|             await context.close(); | |||
|         } | |||
|     } while(!ready); | |||
| } | |||
| 
 | |||
| function startComposeService(serviceName: String){ | |||
|     console.log(`Starting ${serviceName}`); | |||
|     execSync(`docker compose --env-file test.env  up -d ${serviceName}`); | |||
| } | |||
| 
 | |||
| function stopComposeService(serviceName: String){ | |||
|     console.log(`Stopping ${serviceName}`); | |||
|     execSync(`docker compose --env-file test.env  stop ${serviceName}`); | |||
| } | |||
| 
 | |||
| function wipeSqlite(){ | |||
|     console.log(`Delete Vaultwarden container to wipe sqlite`); | |||
|     execSync(`docker compose --env-file test.env stop Vaultwarden`); | |||
|     execSync(`docker compose --env-file test.env rm -f Vaultwarden`); | |||
| } | |||
| 
 | |||
| async function wipeMariaDB(){ | |||
|     var mysql = require('mysql2/promise'); | |||
|     var ready = false; | |||
|     var connection; | |||
| 
 | |||
|     do { | |||
|         try { | |||
|             connection = await mysql.createConnection({ | |||
|                 user: process.env.MARIADB_USER, | |||
|                 host: "127.0.0.1", | |||
|                 database: process.env.MARIADB_DATABASE, | |||
|                 password: process.env.MARIADB_PASSWORD, | |||
|                 port: process.env.MARIADB_PORT, | |||
|             }); | |||
| 
 | |||
|             await connection.execute(`DROP DATABASE ${process.env.MARIADB_DATABASE}`); | |||
|             await connection.execute(`CREATE DATABASE ${process.env.MARIADB_DATABASE}`); | |||
|             console.log('Successfully wiped mariadb'); | |||
|             ready = true; | |||
|         } catch (err) { | |||
|             console.log(`Error when wiping mariadb: ${err}`); | |||
|         } finally { | |||
|             if( connection ){ | |||
|                 connection.end(); | |||
|             } | |||
|         } | |||
|         await new Promise(r => setTimeout(r, 1000)); | |||
|     } while(!ready); | |||
| } | |||
| 
 | |||
| async function wipeMysqlDB(){ | |||
|     var mysql = require('mysql2/promise'); | |||
|     var ready = false; | |||
|     var connection; | |||
| 
 | |||
|     do{ | |||
|         try { | |||
|             connection = await mysql.createConnection({ | |||
|                 user: process.env.MYSQL_USER, | |||
|                 host: "127.0.0.1", | |||
|                 database: process.env.MYSQL_DATABASE, | |||
|                 password: process.env.MYSQL_PASSWORD, | |||
|                 port: process.env.MYSQL_PORT, | |||
|             }); | |||
| 
 | |||
|             await connection.execute(`DROP DATABASE ${process.env.MYSQL_DATABASE}`); | |||
|             await connection.execute(`CREATE DATABASE ${process.env.MYSQL_DATABASE}`); | |||
|             console.log('Successfully wiped mysql'); | |||
|             ready = true; | |||
|         } catch (err) { | |||
|             console.log(`Error when wiping mysql: ${err}`); | |||
|         } finally { | |||
|             if( connection ){ | |||
|                 connection.end(); | |||
|             } | |||
|         } | |||
|         await new Promise(r => setTimeout(r, 1000)); | |||
|     } while(!ready); | |||
| } | |||
| 
 | |||
| async function wipePostgres(){ | |||
|     const { Client } = require('pg'); | |||
| 
 | |||
|     const client = new Client({ | |||
|         user: process.env.POSTGRES_USER, | |||
|         host: "127.0.0.1", | |||
|         database: "postgres", | |||
|         password: process.env.POSTGRES_PASSWORD, | |||
|         port: process.env.POSTGRES_PORT, | |||
|     }); | |||
| 
 | |||
|     try { | |||
|         await client.connect(); | |||
|         await client.query(`DROP DATABASE ${process.env.POSTGRES_DB}`); | |||
|         await client.query(`CREATE DATABASE ${process.env.POSTGRES_DB}`); | |||
|         console.log('Successfully wiped postgres'); | |||
|     } catch (err) { | |||
|         console.log(`Error when wiping postgres: ${err}`); | |||
|     } finally { | |||
|         client.end(); | |||
|     } | |||
| } | |||
| 
 | |||
| function dbConfig(testInfo: TestInfo){ | |||
|     switch(testInfo.project.name) { | |||
|         case "postgres": return { | |||
|             DATABASE_URL: `postgresql://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@127.0.0.1:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DB}` | |||
|         } | |||
|         case "mariadb": return { | |||
|             DATABASE_URL: `mysql://${process.env.MARIADB_USER}:${process.env.MARIADB_PASSWORD}@127.0.0.1:${process.env.MARIADB_PORT}/${process.env.MARIADB_DATABASE}` | |||
|         } | |||
|         case "mysql": return { | |||
|             DATABASE_URL: `mysql://${process.env.MYSQL_USER}:${process.env.MYSQL_PASSWORD}@127.0.0.1:${process.env.MYSQL_PORT}/${process.env.MYSQL_DATABASE}` | |||
|         } | |||
|         default: return { I_REALLY_WANT_VOLATILE_STORAGE: true } | |||
|     } | |||
| } | |||
| 
 | |||
| /** | |||
|  *  All parameters passed in `env` need to be added to the docker-compose.yml | |||
|  **/ | |||
| async function startVaultwarden(browser: Browser, testInfo: TestInfo, env = {}, resetDB: Boolean = true) { | |||
|     if( resetDB ){ | |||
|         switch(testInfo.project.name) { | |||
|             case "postgres": | |||
|                 await wipePostgres(); | |||
|                 break; | |||
|             case "mariadb": | |||
|                 await wipeMariaDB(); | |||
|                 break; | |||
|             case "mysql": | |||
|                 await wipeMysqlDB(); | |||
|                 break; | |||
|             default: | |||
|                 wipeSqlite(); | |||
|         } | |||
|     } | |||
| 
 | |||
|     console.log(`Starting Vaultwarden`); | |||
|     execSync(`docker compose --env-file test.env up -d Vaultwarden`, { | |||
|         env: { ...env, ...dbConfig(testInfo) }, | |||
|     }); | |||
|     await waitFor("/", browser); | |||
|     console.log(`Vaultwarden running on: ${process.env.DOMAIN}`); | |||
| } | |||
| 
 | |||
| async function stopVaultwarden() { | |||
|     console.log(`Vaultwarden stopping`); | |||
|     execSync(`docker compose --env-file test.env stop Vaultwarden`); | |||
| } | |||
| 
 | |||
| async function restartVaultwarden(page: Page, testInfo: TestInfo, env, resetDB: Boolean = true) { | |||
|     stopVaultwarden(); | |||
|     return startVaultwarden(page.context().browser(), testInfo, env, resetDB); | |||
| } | |||
| 
 | |||
| export { loadEnv, waitFor, startComposeService, stopComposeService, startVaultwarden, stopVaultwarden, restartVaultwarden }; | |||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| @ -0,0 +1,20 @@ | |||
| { | |||
|     "name": "scenarios", | |||
|     "version": "1.0.0", | |||
|     "description": "", | |||
|     "main": "index.js", | |||
|     "scripts": {}, | |||
|     "keywords": [], | |||
|     "author": "", | |||
|     "license": "ISC", | |||
|     "devDependencies": { | |||
|         "@playwright/test": "^1.45.1", | |||
|         "dotenv": "^16.4.5", | |||
|         "dotenv-expand": "^11.0.6", | |||
|         "maildev": "github:timshel/maildev#3.0.0-rc1" | |||
|     }, | |||
|     "dependencies": { | |||
|         "mysql2": "^3.10.2", | |||
|         "pg": "^8.12.0" | |||
|     } | |||
| } | |||
| @ -0,0 +1,96 @@ | |||
| import { defineConfig, devices } from '@playwright/test'; | |||
| import { exec } from 'node:child_process'; | |||
| 
 | |||
| const utils = require('./global-utils'); | |||
| 
 | |||
| utils.loadEnv(); | |||
| 
 | |||
| /** | |||
|  * See https://playwright.dev/docs/test-configuration.
 | |||
|  */ | |||
| export default defineConfig({ | |||
|     testDir: 'tests', | |||
|     /* 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: 20 * 1000, | |||
|     expect: { 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: process.env.DOMAIN, | |||
|         browserName: 'firefox', | |||
|         /* 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: 'mariadb-setup', | |||
|             testMatch: 'tests/setups/db-setup.ts', | |||
|             use: { serviceName: "Mariadb" }, | |||
|             teardown: 'mariadb-teardown', | |||
|         }, | |||
|         { | |||
|             name: 'mysql-setup', | |||
|             testMatch: 'tests/setups/db-setup.ts', | |||
|             use: { serviceName: "Mysql" }, | |||
|             teardown: 'mysql-teardown', | |||
|         }, | |||
|         { | |||
|             name: 'postgres-setup', | |||
|             testMatch: 'tests/setups/db-setup.ts', | |||
|             use: { serviceName: "Postgres" }, | |||
|             teardown: 'postgres-teardown', | |||
|         }, | |||
| 
 | |||
|         { | |||
|             name: 'mariadb', | |||
|             testIgnore: 'tests/setups', | |||
|             dependencies: ['mariadb-setup'], | |||
|         }, | |||
|         { | |||
|             name: 'mysql', | |||
|             testIgnore: 'tests/setups', | |||
|             dependencies: ['mysql-setup'], | |||
|         }, | |||
|         { | |||
|             name: 'postgres', | |||
|             testIgnore: 'tests/setups', | |||
|             dependencies: ['postgres-setup'], | |||
|         }, | |||
|         { | |||
|             name: 'sqlite', | |||
|             testIgnore: 'tests/setups', | |||
|         }, | |||
| 
 | |||
|         { | |||
|             name: 'mariadb-teardown', | |||
|             testMatch: 'tests/setups/db-teardown.ts', | |||
|             use: { serviceName: "Mariadb" }, | |||
|         }, | |||
|         { | |||
|             name: 'mysql-teardown', | |||
|             testMatch: 'tests/setups/db-teardown.ts', | |||
|             use: { serviceName: "Mysql" }, | |||
|         }, | |||
|         { | |||
|             name: 'postgres-teardown', | |||
|             testMatch: 'tests/setups/db-teardown.ts', | |||
|             use: { serviceName: "Postgres" }, | |||
|         }, | |||
|     ], | |||
| 
 | |||
|     globalSetup: require.resolve('./global-setup'), | |||
| }); | |||
| @ -0,0 +1,71 @@ | |||
| ################################################################## | |||
| ### Shared Playwright conf test file Vaultwarden and Databases ### | |||
| ################################################################## | |||
| 
 | |||
| ENV=test | |||
| COMPOSE_IGNORE_ORPHANS=True | |||
| DOCKER_BUILDKIT=1 | |||
| VAULTWARDEN_SMTP_FROM=vaultwarden@playwright.test | |||
| 
 | |||
| ##################### | |||
| # Playwright Config # | |||
| ##################### | |||
| PW_KEEP_SERVICE_RUNNNING=${PW_KEEP_SERVICE_RUNNNING:-false} | |||
| 
 | |||
| ##################### | |||
| # Maildev Config 	# | |||
| ##################### | |||
| MAILDEV_HTTP_PORT=1081 | |||
| MAILDEV_SMTP_PORT=1026 | |||
| MAILDEV_HOST=127.0.0.1 | |||
| 
 | |||
| ############# | |||
| # Test user # | |||
| ############# | |||
| TEST_USER=test | |||
| TEST_USER_PASSWORD=Master Password | |||
| TEST_USER_MAIL=${TEST_USER}@example.com | |||
| 
 | |||
| TEST_USER2=test2 | |||
| TEST_USER2_PASSWORD=Master Password | |||
| TEST_USER2_MAIL=${TEST_USER2}@example.com | |||
| 
 | |||
| TEST_USER3=test3 | |||
| TEST_USER3_PASSWORD=Master Password | |||
| TEST_USER3_MAIL=${TEST_USER3}@example.com | |||
| 
 | |||
| ###################### | |||
| # Vaultwarden Config # | |||
| ###################### | |||
| ROCKET_PORT=8003 | |||
| DOMAIN=http://127.0.0.1:${ROCKET_PORT} | |||
| SMTP_SECURITY=off | |||
| SMTP_PORT=${MAILDEV_SMTP_PORT} | |||
| SMTP_FROM_NAME=Vaultwarden | |||
| SMTP_TIMEOUT=5 | |||
| 
 | |||
| ########################### | |||
| # Docker MariaDb container# | |||
| ########################### | |||
| MARIADB_PORT=3307 | |||
| MARIADB_ROOT_PASSWORD=vaultwarden | |||
| MARIADB_USER=vaultwarden | |||
| MARIADB_PASSWORD=vaultwarden | |||
| MARIADB_DATABASE=vaultwarden | |||
| 
 | |||
| ########################### | |||
| # Docker Mysql container# | |||
| ########################### | |||
| MYSQL_PORT=3309 | |||
| MYSQL_ROOT_PASSWORD=vaultwarden | |||
| MYSQL_USER=vaultwarden | |||
| MYSQL_PASSWORD=vaultwarden | |||
| MYSQL_DATABASE=vaultwarden | |||
| 
 | |||
| ############################ | |||
| # Docker Postgres container# | |||
| ############################ | |||
| POSTGRES_PORT=5433 | |||
| POSTGRES_USER=vaultwarden | |||
| POSTGRES_PASSWORD=vaultwarden | |||
| POSTGRES_DB=vaultwarden | |||
| @ -0,0 +1,159 @@ | |||
| import { test, expect, type TestInfo } from '@playwright/test'; | |||
| import { MailDev } from 'maildev'; | |||
| 
 | |||
| const utils = require('../global-utils'); | |||
| import { createAccount, logUser } from './setups/user'; | |||
| 
 | |||
| let users = utils.loadEnv(); | |||
| 
 | |||
| let mailserver; | |||
| 
 | |||
| test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { | |||
|     mailserver = new MailDev({ | |||
|         port: process.env.MAILDEV_SMTP_PORT, | |||
|         web: { port: process.env.MAILDEV_HTTP_PORT }, | |||
|     }) | |||
| 
 | |||
|     await mailserver.listen(); | |||
| 
 | |||
|     await utils.startVaultwarden(browser, testInfo, { | |||
|         SMTP_HOST: process.env.MAILDEV_HOST, | |||
|         SMTP_FROM: process.env.VAULTWARDEN_SMTP_FROM, | |||
|     }); | |||
| }); | |||
| 
 | |||
| test.afterAll('Teardown', async ({}) => { | |||
|     utils.stopVaultwarden(); | |||
|     if( mailserver ){ | |||
|         await mailserver.close(); | |||
|     } | |||
| }); | |||
| 
 | |||
| test('Account creation', async ({ page }) => { | |||
|     const emails = mailserver.iterator(users.user1.email); | |||
| 
 | |||
|     await createAccount(test, page, users.user1); | |||
| 
 | |||
|     const { value: created } = await emails.next(); | |||
|     expect(created.subject).toBe("Welcome"); | |||
|     expect(created.from[0]?.address).toBe(process.env.VAULTWARDEN_SMTP_FROM); | |||
| 
 | |||
|     // Back to the login page
 | |||
|     await expect(page).toHaveTitle('Vaultwarden Web'); | |||
|     await expect(page.getByTestId("toast-message")).toHaveText(/Your new account has been created/); | |||
|     await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|     // Unlock page
 | |||
|     await page.getByLabel('Master password').fill(users.user1.password); | |||
|     await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|     // We are now in the default vault page
 | |||
|     await expect(page).toHaveTitle(/Vaults/); | |||
| 
 | |||
|     const { value: logged } = await emails.next(); | |||
|     expect(logged.subject).toBe("New Device Logged In From Firefox"); | |||
|     expect(logged.to[0]?.address).toBe(process.env.TEST_USER_MAIL); | |||
|     expect(logged.from[0]?.address).toBe(process.env.VAULTWARDEN_SMTP_FROM); | |||
| 
 | |||
|     emails.return(); | |||
| }); | |||
| 
 | |||
| test('Login', async ({ context, page }) => { | |||
|     const emails = mailserver.iterator(users.user1.email); | |||
| 
 | |||
|     await logUser(test, page, users.user1); | |||
| 
 | |||
|     await test.step('new device email', async () => { | |||
|         const { value: logged } = await emails.next(); | |||
|         expect(logged.subject).toBe("New Device Logged In From Firefox"); | |||
|         expect(logged.from[0]?.address).toBe(process.env.VAULTWARDEN_SMTP_FROM); | |||
|     }); | |||
| 
 | |||
|     await test.step('verify email', async () => { | |||
|         await page.getByText('Verify your account\'s email').click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText(/Check your email inbox for a verification link/); | |||
|         await page.locator('#toast-container').getByRole('button').click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveCount(0); | |||
| 
 | |||
|         const { value: verify } = await emails.next(); | |||
|         expect(verify.subject).toBe("Verify Your Email"); | |||
|         expect(verify.from[0]?.address).toBe(process.env.VAULTWARDEN_SMTP_FROM); | |||
| 
 | |||
|         const page2 = await context.newPage(); | |||
|         await page2.setContent(verify.html); | |||
|         const link = await page2.getByTestId("verify").getAttribute("href"); | |||
|         await page2.close(); | |||
| 
 | |||
|         await page.goto(link); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText("Account email verified"); | |||
|     }); | |||
| 
 | |||
|     emails.return(); | |||
| }); | |||
| 
 | |||
| test('Activaite 2fa', async ({ context, page }) => { | |||
|     const emails = mailserver.buffer(users.user1.email); | |||
| 
 | |||
|     await logUser(test, page, users.user1); | |||
| 
 | |||
|     await page.getByRole('button', { name: users.user1.name }).click(); | |||
|     await page.getByRole('menuitem', { name: 'Account settings' }).click(); | |||
|     await page.getByLabel('Security').click(); | |||
|     await page.getByRole('link', { name: 'Two-step login' }).click(); | |||
|     await page.locator('li').filter({ hasText: 'Email Verification codes will' }).getByRole('button').click(); | |||
|     await page.getByLabel('Master password (required)').fill(users.user1.password); | |||
|     await page.getByRole('button', { name: 'Continue' }).click(); | |||
|     await page.getByRole('button', { name: 'Send email' }).click(); | |||
| 
 | |||
|     const codeMail = await emails.next((mail) => mail.subject === "Vaultwarden Login Verification Code"); | |||
|     const page2 = await context.newPage(); | |||
|     await page2.setContent(codeMail.html); | |||
|     const code = await page2.getByTestId("2fa").innerText(); | |||
|     await page2.close(); | |||
| 
 | |||
|     await page.getByLabel('2. Enter the resulting 6').fill(code); | |||
|     await page.getByRole('button', { name: 'Turn on' }).click(); | |||
|     await page.getByRole('heading', { name: 'Turned on', exact: true }); | |||
| 
 | |||
|     emails.close(); | |||
| }); | |||
| 
 | |||
| test('2fa', async ({ context, page }) => { | |||
|     const emails = mailserver.buffer(users.user1.email); | |||
| 
 | |||
|     await test.step('login', async () => { | |||
|         await page.goto('/'); | |||
| 
 | |||
|         await page.getByLabel(/Email address/).fill(users.user1.email); | |||
|         await page.getByRole('button', { name: 'Continue' }).click(); | |||
|         await page.getByLabel('Master password').fill(users.user1.password); | |||
|         await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|         const codeMail = await emails.next((mail) => mail.subject === "Vaultwarden Login Verification Code"); | |||
|         const page2 = await context.newPage(); | |||
|         await page2.setContent(codeMail.html); | |||
|         const code = await page2.getByTestId("2fa").innerText(); | |||
|         await page2.close(); | |||
| 
 | |||
|         await page.getByLabel('Verification code').fill(code); | |||
|         await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|         await expect(page).toHaveTitle(/Vaults/); | |||
|     }) | |||
| 
 | |||
|     await test.step('disable', async () => { | |||
|         await page.getByRole('button', { name: 'Test' }).click(); | |||
|         await page.getByRole('menuitem', { name: 'Account settings' }).click(); | |||
|         await page.getByLabel('Security').click(); | |||
|         await page.getByRole('link', { name: 'Two-step login' }).click(); | |||
|         await page.locator('li').filter({ hasText: 'Email Turned on Verification' }).getByRole('button').click(); | |||
|         await page.getByLabel('Master password (required)').click(); | |||
|         await page.getByLabel('Master password (required)').fill(users.user1.password); | |||
|         await page.getByRole('button', { name: 'Continue' }).click(); | |||
|         await page.getByRole('button', { name: 'Turn off' }).click(); | |||
|         await page.getByRole('button', { name: 'Yes' }).click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText(/Two-step login provider turned off/); | |||
|     }); | |||
| 
 | |||
|     emails.close(); | |||
| }); | |||
| @ -0,0 +1,32 @@ | |||
| import { test, expect, type Page, type TestInfo } from '@playwright/test'; | |||
| 
 | |||
| import * as utils from "../global-utils"; | |||
| import { createAccount, logUser } from './setups/user'; | |||
| 
 | |||
| let users = utils.loadEnv(); | |||
| 
 | |||
| test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { | |||
|     await utils.startVaultwarden(browser, testInfo, {}); | |||
| }); | |||
| 
 | |||
| test.afterAll('Teardown', async ({}, testInfo: TestInfo) => { | |||
|     utils.stopVaultwarden(testInfo); | |||
| }); | |||
| 
 | |||
| test('Account creation', async ({ page }) => { | |||
|     // Landing page
 | |||
|     await createAccount(test, page, users.user1); | |||
| 
 | |||
|     await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|     // Unlock page
 | |||
|     await page.getByLabel('Master password').fill(users.user1.password); | |||
|     await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|     // We are now in the default vault page
 | |||
|     await expect(page).toHaveTitle(/Vaults/); | |||
| }); | |||
| 
 | |||
| test('Master password login', async ({ page }) => { | |||
|     await logUser(test, page, users.user1); | |||
| }); | |||
| @ -0,0 +1,167 @@ | |||
| import { test, expect, type TestInfo } from '@playwright/test'; | |||
| import { MailDev } from 'maildev'; | |||
| 
 | |||
| import * as utils from "../global-utils"; | |||
| import { createAccount, logUser } from './setups/user'; | |||
| 
 | |||
| let users = utils.loadEnv(); | |||
| 
 | |||
| let mailserver, user1Mails, user2Mails, user3Mails; | |||
| 
 | |||
| test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { | |||
|     mailserver = new MailDev({ | |||
|         port: process.env.MAILDEV_SMTP_PORT, | |||
|         web: { port: process.env.MAILDEV_HTTP_PORT }, | |||
|     }) | |||
| 
 | |||
|     await mailserver.listen(); | |||
| 
 | |||
|     await utils.startVaultwarden(browser, testInfo, { | |||
|         SMTP_HOST: process.env.MAILDEV_HOST, | |||
|         SMTP_FROM: process.env.VAULTWARDEN_SMTP_FROM, | |||
|     }); | |||
| 
 | |||
|     user1Mails = mailserver.iterator(users.user1.email); | |||
|     user2Mails = mailserver.iterator(users.user2.email); | |||
|     user3Mails = mailserver.iterator(users.user3.email); | |||
| }); | |||
| 
 | |||
| test.afterAll('Teardown', async ({}, testInfo: TestInfo) => { | |||
|     utils.stopVaultwarden(testInfo); | |||
|     if( mailserver ){ | |||
|         await mailserver.close(); | |||
| 
 | |||
|         [user1Mails, user2Mails, user3Mails].map((mails) => { | |||
|             if(mails){ | |||
|                 mails.return(); | |||
|             } | |||
|         }); | |||
|     } | |||
| }); | |||
| 
 | |||
| test('Create user3', async ({ page }) => { | |||
|     await createAccount(test, page, users.user3, user3Mails); | |||
| }); | |||
| 
 | |||
| test('Invite users', async ({ page }) => { | |||
|     await createAccount(test, page, users.user1, user1Mails); | |||
|     await logUser(test, page, users.user1, user1Mails); | |||
| 
 | |||
|     await test.step('Create Org', async () => { | |||
|         await page.getByRole('link', { name: 'New organization' }).click(); | |||
|         await page.getByLabel('Organization name (required)').fill('Test'); | |||
|         await page.getByRole('button', { name: 'Submit' }).click(); | |||
|         await page.locator('div').filter({ hasText: 'Members' }).nth(2).click(); | |||
|     }); | |||
| 
 | |||
|     await test.step('Invite user2', async () => { | |||
|         await page.getByRole('button', { name: 'Invite member' }).click(); | |||
|         await page.getByLabel('Email (required)').fill(users.user2.email); | |||
|         await page.getByRole('tab', { name: 'Collections' }).click(); | |||
|         await page.locator('label').filter({ hasText: 'Grant access to all current' }).click(); | |||
|         await page.getByRole('button', { name: 'Save' }).click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText('User(s) invited'); | |||
|     }); | |||
| 
 | |||
|     await test.step('Invite user3', async () => { | |||
|         await page.getByRole('button', { name: 'Invite member' }).click(); | |||
|         await page.getByLabel('Email (required)').fill(users.user3.email); | |||
|         await page.getByRole('tab', { name: 'Collections' }).click(); | |||
|         await page.locator('label').filter({ hasText: 'Grant access to all current' }).click(); | |||
|         await page.getByRole('button', { name: 'Save' }).click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText('User(s) invited'); | |||
|     }); | |||
| }); | |||
| 
 | |||
| test('invited with new account', async ({ page }) => { | |||
|     const { value: invited } = await user2Mails.next(); | |||
|     expect(invited.subject).toContain("Join Test") | |||
| 
 | |||
|     await test.step('Create account', async () => { | |||
|         await page.setContent(invited.html); | |||
|         const link = await page.getByTestId("invite").getAttribute("href"); | |||
|         await page.goto(link); | |||
|         await expect(page).toHaveTitle(/Create account | Vaultwarden Web/); | |||
| 
 | |||
|         await page.getByLabel('Name').fill(users.user2.name); | |||
|         await page.getByLabel('Master password\n   (required)', { exact: true }).fill(users.user2.password); | |||
|         await page.getByLabel('Re-type master password').fill(users.user2.password); | |||
|         await page.getByRole('button', { name: 'Create account' }).click(); | |||
| 
 | |||
|         // Back to the login page
 | |||
|         await expect(page).toHaveTitle('Vaultwarden Web'); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText(/Your new account has been created/); | |||
| 
 | |||
|         const { value: welcome } = await user2Mails.next(); | |||
|         expect(welcome.subject).toContain("Welcome") | |||
|     }); | |||
| 
 | |||
|     await test.step('Login', async () => { | |||
|         await page.getByLabel(/Email address/).fill(users.user2.email); | |||
|         await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|         // Unlock page
 | |||
|         await page.getByLabel('Master password').fill(users.user2.password); | |||
|         await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|         // We are now in the default vault page
 | |||
|         await expect(page).toHaveTitle(/Vaults/); | |||
|         await expect(page.getByTestId("toast-title")).toHaveText("Invitation accepted"); | |||
| 
 | |||
|         const { value: logged } = await user2Mails.next(); | |||
|         expect(logged.subject).toContain("New Device Logged"); | |||
|     }); | |||
| 
 | |||
|     const { value: accepted } = await user1Mails.next(); | |||
|     expect(accepted.subject).toContain("Invitation to Test accepted") | |||
| }); | |||
| 
 | |||
| test('invited with existing account', async ({ page }) => { | |||
|     const { value: invited } = await user3Mails.next(); | |||
|     expect(invited.subject).toContain("Join Test") | |||
| 
 | |||
|     await page.setContent(invited.html); | |||
|     const link = await page.getByTestId("invite").getAttribute("href"); | |||
| 
 | |||
|     await page.goto(link); | |||
| 
 | |||
|     // We should be on login page with email prefilled
 | |||
|     await expect(page).toHaveTitle(/Vaultwarden Web/); | |||
|     await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|     // Unlock page
 | |||
|     await page.getByLabel('Master password').fill(users.user3.password); | |||
|     await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|     // We are now in the default vault page
 | |||
|     await expect(page).toHaveTitle(/Vaults/); | |||
|     await expect(page.getByTestId("toast-title")).toHaveText("Invitation accepted"); | |||
| 
 | |||
|     const { value: logged } = await user3Mails.next(); | |||
|     expect(logged.subject).toContain("New Device Logged") | |||
| 
 | |||
|     const { value: accepted } = await user1Mails.next(); | |||
|     expect(accepted.subject).toContain("Invitation to Test accepted") | |||
| }); | |||
| 
 | |||
| test('Confirm invited user', async ({ page }) => { | |||
|     await logUser(test, page, users.user1, user1Mails); | |||
|     await page.getByLabel('Switch products').click(); | |||
|     await page.getByRole('link', { name: ' Admin Console' }).click(); | |||
|     await page.getByLabel('Members').click(); | |||
| 
 | |||
|     await test.step('Accept user2', async () => { | |||
|         await page.getByRole('row', { name: users.user2.name }).getByLabel('Options').click(); | |||
|         await page.getByRole('menuitem', { name: 'Confirm' }).click(); | |||
|         await page.getByRole('button', { name: 'Confirm' }).click(); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText(/confirmed/); | |||
| 
 | |||
|         const { value: logged } = await user2Mails.next(); | |||
|         expect(logged.subject).toContain("Invitation to Test confirmed"); | |||
|     }); | |||
| }); | |||
| 
 | |||
| test('Organization is visible', async ({ page }) => { | |||
|     await logUser(test, page, users.user2, user2Mails); | |||
|     await page.getByLabel('vault: Test').click(); | |||
| }); | |||
| @ -0,0 +1,7 @@ | |||
| import { test } from './db-test'; | |||
| 
 | |||
| const utils = require('../../global-utils'); | |||
| 
 | |||
| test('DB start', async ({ serviceName }) => { | |||
| 	utils.startComposeService(serviceName); | |||
| }); | |||
| @ -0,0 +1,11 @@ | |||
| import { test } from './db-test'; | |||
| 
 | |||
| const utils = require('../../global-utils'); | |||
| 
 | |||
| utils.loadEnv(); | |||
| 
 | |||
| test('DB teardown ?', async ({ serviceName }) => { | |||
|     if( process.env.PW_KEEP_SERVICE_RUNNNING !== "true" ) { | |||
|         utils.stopComposeService(serviceName); | |||
|     } | |||
| }); | |||
| @ -0,0 +1,9 @@ | |||
| import { test as base } from '@playwright/test'; | |||
| 
 | |||
| export type TestOptions = { | |||
|   serviceName: string; | |||
| }; | |||
| 
 | |||
| export const test = base.extend<TestOptions>({ | |||
|   serviceName: ['', { option: true }], | |||
| }); | |||
| @ -0,0 +1,47 @@ | |||
| import { expect, type Browser,Page } from '@playwright/test'; | |||
| 
 | |||
| export async function createAccount(test, page: Page, user: { email: string, name: string, password: string }, emails) { | |||
|     await test.step('Create user', async () => { | |||
|         // Landing page
 | |||
|         await page.goto('/'); | |||
|         await page.getByRole('link', { name: 'Create account' }).click(); | |||
| 
 | |||
|         // Back to Vault create account
 | |||
|         await expect(page).toHaveTitle(/Create account | Vaultwarden Web/); | |||
|         await page.getByLabel(/Email address/).fill(user.email); | |||
|         await page.getByLabel('Name').fill(user.name); | |||
|         await page.getByLabel('Master password\n   (required)', { exact: true }).fill(user.password); | |||
|         await page.getByLabel('Re-type master password').fill(user.password); | |||
|         await page.getByRole('button', { name: 'Create account' }).click(); | |||
| 
 | |||
|         // Back to the login page
 | |||
|         await expect(page).toHaveTitle('Vaultwarden Web'); | |||
|         await expect(page.getByTestId("toast-message")).toHaveText(/Your new account has been created/); | |||
| 
 | |||
|         if( emails ){ | |||
|             const { value: welcome } = await emails.next(); | |||
|             expect(welcome.subject).toContain("Welcome"); | |||
|         } | |||
|     }); | |||
| } | |||
| 
 | |||
| export async function logUser(test, page: Page, user: { email: string, password: string }, emails) { | |||
|     await test.step('Log user', async () => { | |||
|         // Landing page
 | |||
|         await page.goto('/'); | |||
|         await page.getByLabel(/Email address/).fill(user.email); | |||
|         await page.getByRole('button', { name: 'Continue' }).click(); | |||
| 
 | |||
|         // Unlock page
 | |||
|         await page.getByLabel('Master password').fill(user.password); | |||
|         await page.getByRole('button', { name: 'Log in with master password' }).click(); | |||
| 
 | |||
|         // We are now in the default vault page
 | |||
|         await expect(page).toHaveTitle(/Vaults/); | |||
| 
 | |||
|         if( emails ){ | |||
|             const { value: logged } = await emails.next(); | |||
|             expect(logged.subject).toContain("New Device Logged"); | |||
|         } | |||
|     }); | |||
| } | |||
					Loading…
					
					
				
		Reference in new issue