From 7ec697b0582056a865c9704b6683e1398a74502b Mon Sep 17 00:00:00 2001 From: Timshel Date: Mon, 8 Jul 2024 17:23:53 +0200 Subject: [PATCH] Add playwright create/login test for all db --- .dockerignore | 3 + playwright/.gitignore | 6 + playwright/Dockerfile | 27 ++ playwright/README.md | 83 ++++++ playwright/docker-compose.yml | 71 +++++ playwright/global-setup.ts | 13 + playwright/global-utils.ts | 191 +++++++++++++ playwright/package-lock.json | 340 ++++++++++++++++++++++++ playwright/package.json | 19 ++ playwright/playwright.config.ts | 96 +++++++ playwright/test.env | 50 ++++ playwright/tests/login.spec.ts | 54 ++++ playwright/tests/setups/db-setup.ts | 7 + playwright/tests/setups/db-teardown.ts | 11 + playwright/tests/setups/db-test.ts | 9 + playwright/webvault-resolver/Dockerfile | 12 + playwright/webvault-resolver/build.sh | 21 ++ 17 files changed, 1013 insertions(+) create mode 100644 playwright/.gitignore create mode 100644 playwright/Dockerfile create mode 100644 playwright/README.md create mode 100644 playwright/docker-compose.yml create mode 100644 playwright/global-setup.ts create mode 100644 playwright/global-utils.ts create mode 100644 playwright/package-lock.json create mode 100644 playwright/package.json create mode 100644 playwright/playwright.config.ts create mode 100644 playwright/test.env create mode 100644 playwright/tests/login.spec.ts create mode 100644 playwright/tests/setups/db-setup.ts create mode 100644 playwright/tests/setups/db-teardown.ts create mode 100644 playwright/tests/setups/db-test.ts create mode 100644 playwright/webvault-resolver/Dockerfile create mode 100755 playwright/webvault-resolver/build.sh diff --git a/.dockerignore b/.dockerignore index c7ffe132cf3..7e255aaf513 100644 --- a/.dockerignore +++ b/.dockerignore @@ -38,3 +38,6 @@ web-vault # Vaultwarden Resources resources + +# Playwright tests +playwright diff --git a/playwright/.gitignore b/playwright/.gitignore new file mode 100644 index 00000000000..8746d597aa0 --- /dev/null +++ b/playwright/.gitignore @@ -0,0 +1,6 @@ +logs +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ +temp diff --git a/playwright/Dockerfile b/playwright/Dockerfile new file mode 100644 index 00000000000..20e964f59f3 --- /dev/null +++ b/playwright/Dockerfile @@ -0,0 +1,27 @@ +FROM docker.io/library/debian:bookworm-slim + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +RUN apt-get update \ + && apt-get install -y --no-install-recommends docker.io docker-compose git nodejs npm \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + libmariadb-dev-compat \ + libpq5 \ + openssl + +RUN mkdir /playwright +WORKDIR /playwright + +COPY package.json . +RUN npm install && npx playwright install-deps && npx playwright install firefox + +COPY docker-compose.yml Dockerfile test.env ./ +COPY webvault-resolver ./webvault-resolver + +COPY *.ts test.env ./ +COPY tests ./tests + +ENTRYPOINT ["/usr/bin/npx", "playwright"] +CMD ["test"] diff --git a/playwright/README.md b/playwright/README.md new file mode 100644 index 00000000000..a97d9f3e9e9 --- /dev/null +++ b/playwright/README.md @@ -0,0 +1,83 @@ +# 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 `docker-compose`. +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 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/stefan0xC/bw_web_builds +export PW_WV_COMMIT_HASH=c5b5279a8478385cee66a8cdfc804690cffe0315 +DOCKER_BUILDKIT=1 docker-compose --env-file test.env up --build WebVault +``` diff --git a/playwright/docker-compose.yml b/playwright/docker-compose.yml new file mode 100644 index 00000000000..dd6cb6611fd --- /dev/null +++ b/playwright/docker-compose.yml @@ -0,0 +1,71 @@ +version: '3.8' + +services: + VaultWarden: + container_name: playwright_vaultwarden + image: playwright_vaultwarden + build: + context: .. + dockerfile: Dockerfile + entrypoint: /bin/bash + restart: "no" + + WebVault: + container_name: playwright_web-vault_resolver + image: playwright_web-vault_resolver + build: + context: webvault-resolver + dockerfile: Dockerfile + args: + REPO_URL: ${PW_WV_REPO_URL} # Ex: https://github.com/stefan0xC/bw_web_builds + COMMIT_HASH: ${PW_WV_COMMIT_HASH} # Ex: c5b5279a8478385cee66a8cdfc804690cffe0315 + restart: "no" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - VaultWarden + + Playwright: + container_name: playwright_playwright + image: playwright_playwright + network_mode: "host" + build: + context: . + dockerfile: Dockerfile + restart: "no" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: ["WebVault"] + + 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 diff --git a/playwright/global-setup.ts b/playwright/global-setup.ts new file mode 100644 index 00000000000..f662f635a8a --- /dev/null +++ b/playwright/global-setup.ts @@ -0,0 +1,13 @@ +import { firefox, type FullConfig } from '@playwright/test'; +import { exec, execSync } from 'node:child_process'; +import fs from 'fs'; + +async function globalSetup(config: FullConfig) { + fs.rmSync("temp/web-vault", { recursive: true, force: true }); + + execSync("mkdir -p temp/logs"); + execSync("docker cp playwright_vaultwarden:/vaultwarden temp/vaultwarden", { stdio: "inherit" }); + execSync("docker cp playwright_web-vault:/web-vault temp/web-vault", { stdio: "inherit" }); +} + +export default globalSetup; diff --git a/playwright/global-utils.ts b/playwright/global-utils.ts new file mode 100644 index 00000000000..43d7b3c4254 --- /dev/null +++ b/playwright/global-utils.ts @@ -0,0 +1,191 @@ +import { type Browser, type TestInfo } from '@playwright/test'; +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); +} + +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(){ + fs.rmSync("temp/db.sqlite3", { force: true }); + fs.rmSync("temp/db.sqlite3-shm", { force: true }); + fs.rmSync("temp/db.sqlite3-wal", { force: true }); +} + +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 } + } +} + +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`); + + const vw_log = fs.openSync("temp/logs/vaultwarden.log", "a"); + var proc = spawn("temp/vaultwarden", { + env: { ...process.env, ...env, ...dbConfig(testInfo) }, + stdio: [process.stdin, vw_log, vw_log] + }); + + await waitFor("/", browser); + + console.log(`Vaultwarden running on: ${process.env.DOMAIN}`); + + return proc; +} + +async function stopVaultwarden(proc, testInfo: TestInfo, resetDB: Boolean = true) { + console.log(`Vaultwarden stopping`); + proc.kill(); +} + +async function restartVaultwarden(proc, page: Page, testInfo: TestInfo, env, resetDB: Boolean = true) { + stopVaultwarden(proc, testInfo, resetDB); + return startVaultwarden(page.context().browser(), testInfo, env, resetDB); +} + +export { loadEnv, waitFor, startComposeService, stopComposeService, startVaultwarden, stopVaultwarden, restartVaultwarden }; diff --git a/playwright/package-lock.json b/playwright/package-lock.json new file mode 100644 index 00000000000..e782f18450c --- /dev/null +++ b/playwright/package-lock.json @@ -0,0 +1,340 @@ +{ + "name": "scenarios", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "scenarios", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "mysql2": "^3.10.2", + "pg": "^8.12.0" + }, + "devDependencies": { + "@playwright/test": "^1.45.1", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6" + } + }, + "node_modules/@playwright/test": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz", + "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==", + "dev": true, + "dependencies": { + "playwright": "1.45.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", + "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "dev": true, + "dependencies": { + "dotenv": "^16.4.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "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/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } + }, + "node_modules/mysql2": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.2.tgz", + "integrity": "sha512-KCXPEvAkO0RcHPr362O5N8tFY2fXvbjfkPvRY/wGumh4EOemo9Hm5FjQZqv/pCmrnuxGu5OxnSENG0gTXqKMgQ==", + "dependencies": { + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/pg": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", + "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "dependencies": { + "pg-connection-string": "^2.6.4", + "pg-pool": "^3.6.2", + "pg-protocol": "^1.6.1", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", + "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", + "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", + "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/playwright": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz", + "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==", + "dev": true, + "dependencies": { + "playwright-core": "1.45.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz", + "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/playwright/package.json b/playwright/package.json new file mode 100644 index 00000000000..798caa48f2e --- /dev/null +++ b/playwright/package.json @@ -0,0 +1,19 @@ +{ + "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" + }, + "dependencies": { + "mysql2": "^3.10.2", + "pg": "^8.12.0" + } +} diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts new file mode 100644 index 00000000000..fa84b1713fe --- /dev/null +++ b/playwright/playwright.config.ts @@ -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: 10 * 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'), +}); diff --git a/playwright/test.env b/playwright/test.env new file mode 100644 index 00000000000..24ca2d37f63 --- /dev/null +++ b/playwright/test.env @@ -0,0 +1,50 @@ +################################################################## +### Shared Playwright conf test file Vaultwarden and Databases ### +################################################################## + +##################### +# Playwright Config # +##################### +PW_KEEP_SERVICE_RUNNNING=${PW_KEEP_SERVICE_RUNNNING:-false} + +############# +# Test user # +############# +TEST_USER=test +TEST_USER_PASSWORD=${TEST_USER} +TEST_USER_MAIL="${TEST_USER}@example.com" + +###################### +# Vaultwarden Config # +###################### +DATA_FOLDER=temp +WEB_VAULT_FOLDER=temp/web-vault/ + +ROCKET_PORT=8003 +DOMAIN=http://127.0.0.1:${ROCKET_PORT} + +########################### +# 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 diff --git a/playwright/tests/login.spec.ts b/playwright/tests/login.spec.ts new file mode 100644 index 00000000000..ba301024812 --- /dev/null +++ b/playwright/tests/login.spec.ts @@ -0,0 +1,54 @@ +import { test, expect, type TestInfo } from '@playwright/test'; +const utils = require('../global-utils'); + +utils.loadEnv(); + +var proc; + +test.beforeAll('Setup', async ({ browser }, testInfo: TestInfo) => { + proc = await utils.startVaultwarden(browser, testInfo, {}); +}); + +test.afterAll('Teardown', async ({}, testInfo: TestInfo) => { + utils.stopVaultwarden(proc, testInfo); +}); + +test('Account creation', async ({ page }) => { + // 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(process.env.TEST_USER_MAIL); + await page.getByLabel('Name').fill(process.env.TEST_USER); + await page.getByLabel('Master password\n (required)', { exact: true }).fill('Master password'); + await page.getByLabel('Re-type master password').fill('Master password'); + await page.getByRole('button', { name: 'Create account' }).click(); + + // Back to the login page + await expect(page).toHaveTitle('Vaultwarden Web'); + await page.getByLabel('Your new account has been created') + await page.getByRole('button', { name: 'Continue' }).click(); + + // Unlock page + await page.getByLabel('Master password').fill('Master 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 }) => { + // 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.getByLabel('Master password').fill('Master 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/); +}); diff --git a/playwright/tests/setups/db-setup.ts b/playwright/tests/setups/db-setup.ts new file mode 100644 index 00000000000..eb37fdc1025 --- /dev/null +++ b/playwright/tests/setups/db-setup.ts @@ -0,0 +1,7 @@ +import { test } from './db-test'; + +const utils = require('../../global-utils'); + +test('DB start', async ({ serviceName }) => { + utils.startComposeService(serviceName); +}); diff --git a/playwright/tests/setups/db-teardown.ts b/playwright/tests/setups/db-teardown.ts new file mode 100644 index 00000000000..5f753a9d2ce --- /dev/null +++ b/playwright/tests/setups/db-teardown.ts @@ -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); + } +}); diff --git a/playwright/tests/setups/db-test.ts b/playwright/tests/setups/db-test.ts new file mode 100644 index 00000000000..4a72d37c57c --- /dev/null +++ b/playwright/tests/setups/db-test.ts @@ -0,0 +1,9 @@ +import { test as base } from '@playwright/test'; + +export type TestOptions = { + serviceName: string; +}; + +export const test = base.extend({ + serviceName: ['', { option: true }], +}); diff --git a/playwright/webvault-resolver/Dockerfile b/playwright/webvault-resolver/Dockerfile new file mode 100644 index 00000000000..a1cca22f70f --- /dev/null +++ b/playwright/webvault-resolver/Dockerfile @@ -0,0 +1,12 @@ +FROM docker + +arg REPO_URL +arg COMMIT_HASH + +ENV COMMIT_HASH=${COMMIT_HASH} + +RUN if [[ -n "${REPO_URL}" ]]; then git clone ${REPO_URL} /bw_web_builds && cd /bw_web_builds && git reset --hard "${COMMIT_HASH}" ; fi + +COPY build.sh /build.sh + +ENTRYPOINT ["/build.sh"] diff --git a/playwright/webvault-resolver/build.sh b/playwright/webvault-resolver/build.sh new file mode 100755 index 00000000000..e3826e3b093 --- /dev/null +++ b/playwright/webvault-resolver/build.sh @@ -0,0 +1,21 @@ +#!/bin/ash + +CANARY=/playwright_web-vault_resolver-done + +if [ -f $CANARY ] +then + echo "Web vault selection should already be done. Will not run." + exit 0 +fi + +docker rm -f playwright_web-vault || true + +if [[ -n "$COMMIT_HASH" ]]; then + cd /bw_web_builds + docker build . -t "playwright_web-vault_$COMMIT_HASH" + docker create --name playwright_web-vault "playwright_web-vault_$COMMIT_HASH:latest" +else + docker create --name playwright_web-vault playwright_vaultwarden:latest +fi + +touch $CANARY