From 66a6c7bbe341ecb3364fb590b8f0a318a5980c0b Mon Sep 17 00:00:00 2001 From: DeathGun44 Date: Sat, 4 Apr 2026 00:50:59 +0530 Subject: [PATCH] WEB-900: Docker-backed E2E testing infrastructure --- .github/workflows/playwright.yml | 97 ++++++++++++++++++++++++---- Dockerfile | 2 + config/e2e/fineract.env | 40 ++++++++++++ config/e2e/nginx-e2e.conf.template | 29 +++++++++ config/e2e/postgresql-init.sh | 11 ++++ docker-compose.e2e.yml | 95 +++++++++++++++++++++++++++ package.json | 5 ++ playwright.config.ts | 28 ++++---- playwright/README.md | 79 +++++++++++++++------- playwright/fixtures/fineract-api.ts | 76 ++++++++++++++++++++++ playwright/fixtures/test-fixtures.ts | 31 +++++++++ playwright/global-setup.ts | 43 ++++++++++++ scripts/e2e-docker.sh | 76 ++++++++++++++++++++++ 13 files changed, 559 insertions(+), 53 deletions(-) create mode 100644 config/e2e/fineract.env create mode 100644 config/e2e/nginx-e2e.conf.template create mode 100755 config/e2e/postgresql-init.sh create mode 100644 docker-compose.e2e.yml create mode 100644 playwright/fixtures/fineract-api.ts create mode 100644 playwright/fixtures/test-fixtures.ts create mode 100644 playwright/global-setup.ts create mode 100755 scripts/e2e-docker.sh diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ad70912212..d07d215520 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,22 +1,35 @@ -name: Playwright E2E Tests +name: E2E Tests (Playwright + Fineract) on: push: - branches: [main, dev, dev-angular-19] + branches: [main, dev] pull_request: - branches: [main, dev, dev-angular-19] + branches: [main, dev] + +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true jobs: - playwright: - name: Run Playwright Tests + e2e: + name: Playwright E2E runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 + + env: + CI: true + E2E_BASE_URL: http://localhost:4200 + E2E_FINERACT_URL: https://localhost:8443 + E2E_USERNAME: mifos + E2E_PASSWORD: password + E2E_TENANT_ID: default + NODE_TLS_REJECT_UNAUTHORIZED: '0' steps: - name: Checkout code uses: actions/checkout@v6 - - name: Use Node.js 22x + - name: Use Node.js 24 uses: actions/setup-node@v6 with: node-version: '24.14.0' @@ -28,13 +41,65 @@ jobs: - name: Install Playwright browsers run: npx playwright install --with-deps chromium + - name: Start E2E infrastructure + run: docker compose -f docker-compose.e2e.yml up -d --build + + - name: Wait for PostgreSQL + run: | + timeout 60 bash -c ' + until docker exec e2e-postgres pg_isready -U postgres 2>/dev/null; do + sleep 2 + done + ' + echo "✅ PostgreSQL ready" + + - name: Wait for Fineract backend + run: | + timeout 300 bash -c ' + until docker ps --filter "health=healthy" --filter "name=e2e-fineract" | grep -q healthy; do + echo " … Fineract not healthy yet" + sleep 10 + done + ' + curl -fk --retry 30 --retry-all-errors --connect-timeout 10 --retry-delay 10 \ + https://localhost:8443/fineract-provider/actuator/health + echo "" + echo "✅ Fineract ready" + + - name: Verify Fineract initialization complete + run: | + AUTH_HEADER=$(echo -n "${E2E_USERNAME}:${E2E_PASSWORD}" | base64 | tr -d '\n') + timeout 120 bash -c " + until curl -fsk \ + -H 'Fineract-Platform-TenantId: ${E2E_TENANT_ID}' \ + -H 'Authorization: Basic ${AUTH_HEADER}' \ + https://localhost:8443/fineract-provider/api/v1/offices 2>/dev/null | grep -q 'Head Office'; do + echo ' … waiting for seed data' + sleep 5 + done + " + echo "✅ Fineract initialization complete" + + - name: Wait for web-app + run: | + curl -f --retry 20 --retry-all-errors --connect-timeout 5 --retry-delay 5 \ + http://localhost:4200 > /dev/null 2>&1 + echo "✅ Web-app ready" + - name: Run Playwright tests - run: npx playwright test --reporter=html,github - env: - CI: true - FINERACT_API_URL: https://demo.mifos.community - E2E_USERNAME: mifos - E2E_PASSWORD: password + run: npx playwright test --reporter=html,github --workers=1 + + - name: Dump Docker logs on failure + if: failure() + run: | + echo "=== Fineract ===" + docker logs e2e-fineract --tail 100 2>&1 || true + echo "=== PostgreSQL ===" + docker logs e2e-postgres --tail 50 2>&1 || true + echo "=== Web-App ===" + docker logs e2e-web-app --tail 50 2>&1 || true + echo "=== Memory ===" + docker stats --no-stream || true - name: Upload Playwright report uses: actions/upload-artifact@v7 @@ -51,3 +116,9 @@ jobs: name: test-results path: test-results/ retention-days: 7 + + - name: Tear down E2E infrastructure + if: always() + run: | + docker compose -f docker-compose.e2e.yml down -v --remove-orphans + docker system prune -f diff --git a/Dockerfile b/Dockerfile index 95c6b2cb5b..220209b564 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,8 @@ ENV PATH=/usr/src/app/node_modules/.bin:$PATH ENV PUPPETEER_DOWNLOAD_HOST=$PUPPETEER_DOWNLOAD_HOST_ARG ENV PUPPETEER_CHROMIUM_REVISION=$PUPPETEER_CHROMIUM_REVISION_ARG ENV PUPPETEER_SKIP_DOWNLOAD=$PUPPETEER_SKIP_DOWNLOAD_ARG +ENV CYPRESS_INSTALL_BINARY=0 +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 COPY ./ /usr/src/app/ diff --git a/config/e2e/fineract.env b/config/e2e/fineract.env new file mode 100644 index 0000000000..58bea3efdd --- /dev/null +++ b/config/e2e/fineract.env @@ -0,0 +1,40 @@ +# Fineract E2E Environment — PostgreSQL-only + +# ── Spring Profiles ── +SPRING_PROFILES_ACTIVE=test,diagnostics + +# ── PostgreSQL Connection (Docker service: "db") ── +FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME=org.postgresql.Driver +FINERACT_HIKARI_JDBC_URL=jdbc:postgresql://db:5432/fineract_tenants +FINERACT_HIKARI_USERNAME=postgres +# Well-known Fineract E2E password — intentionally committed, not a secret. +FINERACT_HIKARI_PASSWORD=skdcnwauicn2ucnaecasdsajdnizucawencascdca + +# ── Tenant Database ── +FINERACT_DEFAULT_TENANTDB_HOSTNAME=db +FINERACT_DEFAULT_TENANTDB_PORT=5432 +FINERACT_DEFAULT_TENANTDB_UID=postgres +FINERACT_DEFAULT_TENANTDB_PWD=skdcnwauicn2ucnaecasdsajdnizucawencascdca +FINERACT_DEFAULT_TENANTDB_NAME=fineract_default + +# ── SSL & HTTP ── +FINERACT_SERVER_SSL_ENABLED=true +FINERACT_INSECURE_HTTP_CLIENT=true + +# ── Node Identity ── +FINERACT_NODE_ID=1 + +# ── External Events (no ActiveMQ in E2E) ── +FINERACT_EXTERNAL_EVENTS_ENABLED=false + +# ── HikariCP Pool (tuned for E2E) ── +FINERACT_HIKARI_MINIMUM_IDLE=3 +FINERACT_HIKARI_MAXIMUM_POOL_SIZE=10 + +# ── Data Seeding ── +# INITIALIZATION_ENABLED is NOT a Fineract server env var (it belongs to +# fineract-e2e-tests-runner's Gradle Cucumber runner). Do not set it here. +# +# Liquibase auto-seeds: Head Office, default tenant config, schema. +# Domain data (products, GL accounts, clients) must be seeded via the +# FineractApiClient fixture in test beforeAll hooks. diff --git a/config/e2e/nginx-e2e.conf.template b/config/e2e/nginx-e2e.conf.template new file mode 100644 index 0000000000..a4374aa0be --- /dev/null +++ b/config/e2e/nginx-e2e.conf.template @@ -0,0 +1,29 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # ${E2E_PROXY_TARGET} is replaced by envsubst; $host/$scheme etc. are + # safe because NGINX_ENVSUBST_FILTER restricts to ^(FINERACT_|MIFOS_|...). + location /fineract-provider/ { + proxy_pass ${E2E_PROXY_TARGET}/fineract-provider/; + proxy_ssl_verify off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_read_timeout 120s; + } + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} diff --git a/config/e2e/postgresql-init.sh b/config/e2e/postgresql-init.sh new file mode 100755 index 0000000000..cc29fbafd3 --- /dev/null +++ b/config/e2e/postgresql-init.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Creates the two databases Fineract requires at first boot. +# POSTGRES_USER (postgres) is the default superuser — no CREATE USER needed. +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE DATABASE fineract_tenants; + CREATE DATABASE fineract_default; +EOSQL + +echo "✓ E2E databases created: fineract_tenants, fineract_default" diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml new file mode 100644 index 0000000000..66895ba93b --- /dev/null +++ b/docker-compose.e2e.yml @@ -0,0 +1,95 @@ +services: + db: + image: postgres:18.3-alpine + container_name: e2e-postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: skdcnwauicn2ucnaecasdsajdnizucawencascdca + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - ./config/e2e/postgresql-init.sh:/docker-entrypoint-initdb.d/01-init.sh:ro + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U postgres'] + interval: 5s + timeout: 5s + retries: 10 + ports: + - '5432:5432' + mem_limit: 768m + shm_size: 256m + tmpfs: + - /var/lib/postgresql/data + command: > + postgres + -c shared_buffers=128MB + -c work_mem=4MB + -c maintenance_work_mem=64MB + + fineract: + image: apache/fineract:latest + container_name: e2e-fineract + depends_on: + db: + condition: service_healthy + env_file: + - ./config/e2e/fineract.env + environment: + JAVA_TOOL_OPTIONS: >- + -Xmx1536m -Xms512m + -XX:+UseG1GC -XX:MaxGCPauseMillis=200 + -Djava.security.egd=file:/dev/./urandom + healthcheck: + test: + [ + 'CMD-SHELL', + 'wget -qO- --no-check-certificate https://localhost:8443/fineract-provider/actuator/health || exit 1' + ] + interval: 10s + timeout: 10s + retries: 60 + start_period: 120s + ports: + - '8443:8443' + mem_limit: 2g + + web-app: + build: + context: . + dockerfile: Dockerfile + args: + PUPPETEER_SKIP_DOWNLOAD_ARG: 'true' + container_name: e2e-web-app + depends_on: + fineract: + condition: service_healthy + environment: + # Nginx proxy target (internal docker DNS) + E2E_PROXY_TARGET: https://fineract:8443 + + # Angular app configuration (resolved by host browser via proxy) + FINERACT_API_URLS: http://localhost:4200 + FINERACT_API_URL: http://localhost:4200 + + FINERACT_API_PROVIDER: /fineract-provider/api + FINERACT_API_VERSION: /v1 + FINERACT_PLATFORM_TENANT_IDENTIFIER: default + MIFOS_DEFAULT_LANGUAGE: en-US + MIFOS_SUPPORTED_LANGUAGES: en-US + MIFOS_PRELOAD_CLIENTS: 'false' + MIFOS_DEFAULT_CHAR_DELIMITER: ',' + NGINX_ENVSUBST_FILTER: '^(FINERACT_|MIFOS_|ENABLE_|EXTERNAL_|E2E_PROXY_TARGET)' + volumes: + - ./config/e2e/nginx-e2e.conf.template:/etc/nginx/templates/default.conf.template:ro + ports: + - '4200:80' + mem_limit: 256m + # Override CMD: Dockerfile's /bin/sh CMD causes the nginx entrypoint to + # skip template processing (it checks $1 == "nginx"). Process both + # templates explicitly. Only substitute $E2E_PROXY_TARGET in the nginx + # template so $host, $scheme, $uri etc. are left for nginx. + command: >- + /bin/sh -c " + envsubst '$$E2E_PROXY_TARGET' < /etc/nginx/templates/default.conf.template > /etc/nginx/conf.d/default.conf && + envsubst < /usr/share/nginx/html/assets/env.template.js > /usr/share/nginx/html/assets/env.js && + nginx -g 'daemon off;' + " diff --git a/package.json b/package.json index 8e3657d62d..a9d1bc1941 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,11 @@ "playwright:ui": "playwright test --ui", "playwright:headed": "playwright test --headed", "playwright:debug": "playwright test --debug", + "playwright:ci": "playwright test --reporter=html,github --workers=1", + "e2e:docker": "bash scripts/e2e-docker.sh", + "e2e:docker:up": "docker compose -f docker-compose.e2e.yml up -d --build", + "e2e:docker:down": "docker compose -f docker-compose.e2e.yml down -v --remove-orphans", + "e2e:docker:logs": "docker compose -f docker-compose.e2e.yml logs -f", "headers:check": "node scripts/check-file-headers.js", "headers:add": "node scripts/add-file-headers.js" }, diff --git a/playwright.config.ts b/playwright.config.ts index 4336c65b77..a2fd55b29d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,6 +18,7 @@ import { defineConfig, devices } from '@playwright/test'; * @see https://playwright.dev/docs/test-configuration */ export default defineConfig({ + globalSetup: process.env.CI ? './playwright/global-setup.ts' : undefined, // Test directory testDir: './playwright/tests', @@ -43,8 +44,8 @@ export default defineConfig({ // Global test settings use: { - // Base URL for the Angular app - baseURL: 'http://localhost:4200', + // Base URL for the Angular app (aligned with global-setup.ts) + baseURL: process.env.E2E_BASE_URL || 'http://localhost:4200', // Handle self-signed certificates from Fineract backend ignoreHTTPSErrors: true, @@ -87,19 +88,12 @@ export default defineConfig({ } ], - // Web server configuration - // In CI: builds production bundle, then serves it with e2e-server.js - // (Express + http-proxy-middleware for /fineract-provider/* proxy) - // Locally: reuses existing ng serve if running - webServer: { - command: process.env.CI ? 'npm run build && node playwright/e2e-server.js' : 'npm run start', - url: 'http://localhost:4200', - reuseExistingServer: !process.env.CI, - timeout: 180000, - // Add retry logic for server startup - ...(process.env.CI && { - stdout: 'pipe', - stderr: 'pipe' - }) - } + webServer: process.env.CI + ? undefined + : { + command: 'npm run start', + url: 'http://localhost:4200', + reuseExistingServer: true, + timeout: 180000 + } }); diff --git a/playwright/README.md b/playwright/README.md index 3e474eb252..168a2bb4bc 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -1,44 +1,54 @@ -# Playwright E2E Testing +# Playwright E2E Testing Production-grade End-to-End testing infrastructure for Mifos® X Web App using [Playwright](https://playwright.dev/). -## Prerequisites +## Running E2E with Docker (Full Stack) -Before running the tests, ensure the local environment is running: +### One-command (recommended) -1. **Backend:** Ensure Apache Fineract® is running on `https://localhost:8443` -2. **Frontend:** Serve the Angular app: - ```bash - ng serve - ``` +```bash +npm run e2e:docker # All tests +npm run e2e:docker -- --grep login # Filter by name +npm run e2e:docker -- --headed # See the browser +npm run e2e:docker -- --debug # Step through +``` -## Quick Start +### Manual control (debugging infrastructure) ```bash -# Run all tests -npm run playwright - -# Run with UI mode (recommended for debugging) -npm run playwright:ui +npm run e2e:docker:up # Start stack (Postgres + Fineract + Nginx) +npm run e2e:docker:logs # Watch logs (separate terminal) +npm run playwright # Run tests +npm run e2e:docker:down # Tear down +``` -# Run with visible browser -npm run playwright:headed +## Running Locally (without Docker) -# Debug mode (step through tests) -npm run playwright:debug -``` +1. **Backend:** Ensure Apache Fineract® is running on `https://localhost:8443` +2. **Frontend:** Serve the Angular app: + ```bash + npm run start + ``` +3. **Run tests:** + ```bash + npm run playwright + ``` ## Architecture This framework follows the **Page Object Model (POM)** pattern: -``` +```text playwright/ +├── fixtures/ +│ ├── fineract-api.ts # Typed Fineract REST client for data seeding +│ └── test-fixtures.ts # test.extend with fineractApi fixture +├── global-setup.ts # CI-only: validates backend connectivity ├── pages/ -│ ├── BasePage.ts # Abstract base class with common utilities -│ └── LoginPage.ts # Login page object +│ ├── BasePage.ts # Abstract base class with common utilities +│ └── LoginPage.ts # Login page object └── tests/ - └── login.spec.ts # Login smoke tests + └── login.spec.ts # Login smoke tests ``` ### Design Principles @@ -50,6 +60,29 @@ playwright/ | **Maintainability** | Centralized locators per page | | **CI/CD Optimization** | Artifacts only on failure | +## Data Seeding Strategy + +Fineract auto-seeds via Liquibase at boot: + +- Head Office, default tenant config, schema structure + +For domain-specific test data (clients, loans, savings), seed via API fixtures: + +```typescript +import { test } from '../fixtures/test-fixtures'; + +test.beforeAll(async ({ fineractApi }) => { + await fineractApi.createClient({ + officeId: 1, + firstname: 'Test', + lastname: 'Client' + // ... required Fineract fields + }); +}); +``` + +See `playwright/fixtures/fineract-api.ts` for all available methods. + ## Configuration ### SSL Certificate Handling diff --git a/playwright/fixtures/fineract-api.ts b/playwright/fixtures/fineract-api.ts new file mode 100644 index 0000000000..aa0d21f0f2 --- /dev/null +++ b/playwright/fixtures/fineract-api.ts @@ -0,0 +1,76 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { APIRequestContext, APIResponse, request } from '@playwright/test'; + +export class FineractApiClient { + private ctx!: APIRequestContext; + + constructor( + private baseUrl: string, + private tenantId: string, + private username: string, + private password: string + ) {} + + async init(): Promise { + this.ctx = await request.newContext({ + baseURL: this.baseUrl, + ignoreHTTPSErrors: true, + extraHTTPHeaders: { + 'Fineract-Platform-TenantId': this.tenantId, + Authorization: `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`, + 'Content-Type': 'application/json' + } + }); + } + + private async validateResponse(res: APIResponse, operation: string): Promise { + if (!res.ok()) { + throw new Error(`Fineract API error [${operation}]: ${res.status()} ${res.statusText()}`); + } + return res.json(); + } + + async healthCheck(): Promise { + const res = await this.ctx.get('/fineract-provider/actuator/health'); + return res.ok(); + } + + async getOffices(): Promise { + const res = await this.ctx.get('/fineract-provider/api/v1/offices'); + return this.validateResponse(res, 'getOffices'); + } + + async createClient(data: Record): Promise { + const res = await this.ctx.post('/fineract-provider/api/v1/clients', { data }); + return this.validateResponse(res, 'createClient'); + } + + /** + * Creates a savings account. + * @param overrides Must include mandatory Fineract fields: + * submittedOnDate, dateFormat, locale, nominalAnnualInterestRate, + * interestCompoundingPeriodType, interestPostingPeriodType, + * interestCalculationType, interestCalculationDaysInYearType + */ + async createSavingsAccount( + clientId: number, + productId: number, + overrides: Record = {} + ): Promise { + const res = await this.ctx.post('/fineract-provider/api/v1/savingsaccounts', { + data: { ...overrides, clientId, productId } + }); + return this.validateResponse(res, 'createSavingsAccount'); + } + + async dispose(): Promise { + await this.ctx?.dispose(); + } +} diff --git a/playwright/fixtures/test-fixtures.ts b/playwright/fixtures/test-fixtures.ts new file mode 100644 index 0000000000..fc01f35aab --- /dev/null +++ b/playwright/fixtures/test-fixtures.ts @@ -0,0 +1,31 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { test as base } from '@playwright/test'; + +import { FineractApiClient } from './fineract-api'; + +type E2EFixtures = { + fineractApi: FineractApiClient; +}; + +export const test = base.extend({ + fineractApi: async ({}, use) => { + const api = new FineractApiClient( + process.env.E2E_FINERACT_URL || 'https://localhost:8443', + process.env.E2E_TENANT_ID || 'default', + process.env.E2E_USERNAME || 'mifos', + process.env.E2E_PASSWORD || 'password' + ); + await api.init(); + await use(api); + await api.dispose(); + } +}); + +export { expect } from '@playwright/test'; diff --git a/playwright/global-setup.ts b/playwright/global-setup.ts new file mode 100644 index 0000000000..264ef60d42 --- /dev/null +++ b/playwright/global-setup.ts @@ -0,0 +1,43 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { request } from '@playwright/test'; + +async function globalSetup(): Promise { + const fineractUrl = process.env.E2E_FINERACT_URL || 'https://localhost:8443'; + const webAppUrl = process.env.E2E_BASE_URL || 'http://localhost:4200'; + + const ctx = await request.newContext({ ignoreHTTPSErrors: true }); + + try { + const fineractRes = await ctx.get(`${fineractUrl}/fineract-provider/actuator/health`, { + timeout: 15000 + }); + if (!fineractRes.ok()) { + throw new Error(`Fineract returned ${fineractRes.status()} at ${fineractUrl}`); + } + + const webAppRes = await ctx.get(webAppUrl, { timeout: 15000 }); + if (!webAppRes.ok()) { + throw new Error(`Web-app returned ${webAppRes.status()} at ${webAppUrl}`); + } + + console.log('✅ Global setup: Fineract + web-app verified'); + } catch (error) { + throw new Error( + `FATAL: E2E infrastructure unreachable.\n` + + `Fineract: ${fineractUrl}\nWeb-app: ${webAppUrl}\n` + + `Run: docker compose -f docker-compose.e2e.yml up -d\n` + + `Error: ${error}` + ); + } finally { + await ctx.dispose(); + } +} + +export default globalSetup; diff --git a/scripts/e2e-docker.sh b/scripts/e2e-docker.sh new file mode 100755 index 0000000000..a0f6ae9db1 --- /dev/null +++ b/scripts/e2e-docker.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# E2E Docker Runner — boots Fineract stack, runs Playwright, tears down. +# +# Usage: +# npm run e2e:docker # Run all tests +# npm run e2e:docker -- --grep login # Filter tests +# npm run e2e:docker -- --headed # See the browser +# npm run e2e:docker -- --debug # Step through +set -euo pipefail + +# GNU timeout is required (not available by default on macOS). +# Install via: brew install coreutils +if ! command -v timeout &>/dev/null; then + echo "❌ 'timeout' command not found. On macOS: brew install coreutils" >&2 + exit 1 +fi + +COMPOSE_FILE="docker-compose.e2e.yml" + +cleanup() { + local exit_code=$? + echo "" + echo "🧹 Tearing down E2E infrastructure..." + docker compose -f "$COMPOSE_FILE" down -v --remove-orphans 2>/dev/null || true + echo "✅ Cleanup complete" + exit $exit_code +} +trap cleanup EXIT + +echo "🚀 Starting E2E infrastructure..." +docker compose -f "$COMPOSE_FILE" up -d --build + +echo "⏳ Waiting for services..." + +# Phase 1: PostgreSQL +timeout 60 bash -c ' + until docker exec e2e-postgres pg_isready -U postgres 2>/dev/null; do sleep 2; done +' +echo "✅ PostgreSQL ready" + +# Phase 2: Fineract actuator +timeout 300 bash -c ' + until docker ps --filter "health=healthy" --filter "name=e2e-fineract" | grep -q healthy; do + echo " … waiting for Fineract" + sleep 10 + done +' +echo "✅ Fineract healthy" + +# Phase 2.5: Liquibase seeding (Head Office) +E2E_USERNAME="${E2E_USERNAME:-mifos}" +E2E_PASSWORD="${E2E_PASSWORD:-password}" +E2E_TENANT_ID="${E2E_TENANT_ID:-default}" +# Portable base64: tr -d '\n' handles macOS/Linux newline differences +AUTH_HEADER=$(echo -n "${E2E_USERNAME}:${E2E_PASSWORD}" | base64 | tr -d '\n') + +timeout 120 bash -c " + until curl -fsk \ + -H 'Fineract-Platform-TenantId: ${E2E_TENANT_ID}' \ + -H 'Authorization: Basic ${AUTH_HEADER}' \ + https://localhost:8443/fineract-provider/api/v1/offices 2>/dev/null | grep -q 'Head Office'; do + echo ' … waiting for Liquibase seed data' + sleep 5 + done +" +echo "✅ Liquibase seed data ready" + +# Phase 3: Web-app +curl -f --retry 20 --retry-all-errors --connect-timeout 5 --retry-delay 3 \ + http://localhost:4200 > /dev/null 2>&1 +echo "✅ Web-app ready" + +echo "" +echo "🧪 Running Playwright tests..." +# "$@" forwards flags from: npm run e2e:docker -- --grep login +npx playwright test --workers=1 "$@"