PSA/ee/server/playwright.config.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

460 lines
20 KiB
TypeScript

import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'path';
import { applyPlaywrightDatabaseEnv } from './src/__tests__/integration/utils/playwrightDatabaseConfig';
function loadPlaywrightEnv(): string | null {
const candidates = ['.env', '.env.test', '.env.example']
.map((filename) => path.resolve(__dirname, filename))
.filter((candidate) => fs.existsSync(candidate));
const selected = candidates[0] ?? null;
if (!selected) {
console.warn(`[Playwright] couldn't find env file: ${path.resolve(__dirname, '.env')}`);
return null;
}
dotenv.config({ path: selected });
return selected;
}
loadPlaywrightEnv();
const dockerComposeEnvFile = fs.existsSync(path.resolve(__dirname, '.env'))
? 'ee/server/.env'
: 'ee/server/.env.test';
// Playwright runs should be self-contained and must not depend on developer filesystem secrets.
// Playwright runs should be self-contained. Use an isolated filesystem-backed tenant secret store
// (env provider is read-only and cannot persist per-tenant configuration like integrations).
process.env.SECRET_READ_CHAIN = process.env.SECRET_READ_CHAIN || 'filesystem,env';
process.env.SECRET_WRITE_PROVIDER = process.env.SECRET_WRITE_PROVIDER || 'filesystem';
process.env.SECRET_FS_BASE_PATH = process.env.SECRET_FS_BASE_PATH || 'secrets-playwright';
process.env.PLAYWRIGHT_FAKE_GOOGLE_OAUTH = process.env.PLAYWRIGHT_FAKE_GOOGLE_OAUTH || 'true';
process.env.NEXT_PUBLIC_PLAYWRIGHT_FAKE_GOOGLE_OAUTH =
process.env.NEXT_PUBLIC_PLAYWRIGHT_FAKE_GOOGLE_OAUTH || 'true';
process.env.GOOGLE_OAUTH_CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID || 'playwright-google-client-id';
process.env.GOOGLE_OAUTH_CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET || 'playwright-google-client-secret';
// Don't set STORAGE_LOCAL_BASE_PATH - we want to use MinIO for tests
// const storageBasePath = path.resolve(__dirname, 'playwright-storage');
// if (!fs.existsSync(storageBasePath)) {
// fs.mkdirSync(storageBasePath, { recursive: true });
// }
// process.env.STORAGE_LOCAL_BASE_PATH = process.env.STORAGE_LOCAL_BASE_PATH || storageBasePath;
// If Postgres runs in Docker and is published to a different host/port,
// override the Playwright DB connection to hit the direct Postgres port instead
// of PgBouncer.
//
// IMPORTANT: Do not fall back to DB_HOST/DB_PORT from `ee/server/.env` here.
// That file is a developer runtime env and may use non-default ports; Playwright
// should be self-contained and only deviate from defaults when explicitly configured.
const directDbHost =
process.env.DB_DIRECT_HOST ||
process.env.PLAYWRIGHT_DB_HOST ||
process.env.EXPOSE_DB_HOST ||
'localhost';
const directDbPort =
process.env.DB_DIRECT_PORT ||
process.env.PLAYWRIGHT_DB_PORT ||
process.env.EXPOSE_DB_PORT ||
'5432';
process.env.DB_DIRECT_HOST = process.env.DB_DIRECT_HOST || directDbHost;
process.env.DB_DIRECT_PORT = process.env.DB_DIRECT_PORT || directDbPort;
process.env.PLAYWRIGHT_DB_HOST = process.env.PLAYWRIGHT_DB_HOST || directDbHost;
process.env.PLAYWRIGHT_DB_PORT = process.env.PLAYWRIGHT_DB_PORT || directDbPort;
// Playwright runs should be deterministic and not inherit a developer's NODE_ENV,
// since it affects auth cookie naming and other runtime branches.
process.env.NODE_ENV = 'test';
function runPortProbe(start: number, span: number, strict: boolean): { success: boolean; port?: number; error?: string } {
const script = `const net = require('net');
const start = Number(process.argv[1]);
const attempts = Number(process.argv[2]);
const strictMode = process.argv[3] === 'strict';
if (!Number.isFinite(start)) {
console.error('invalid-port');
process.exit(2);
}
function tryPort(port, attemptsLeft) {
const server = net.createServer();
server.unref();
server.once('error', (err) => {
if (strictMode || attemptsLeft <= 0) {
console.error(err && err.code ? err.code : 'EADDRINUSE');
process.exit(1);
}
tryPort(port + 1, attemptsLeft - 1);
});
server.listen(port, () => {
server.close(() => {
process.stdout.write(String(port));
process.exit(0);
});
});
}
tryPort(start, attempts);
`;
const result = spawnSync(process.execPath, ['--input-type=commonjs', '-e', script, String(start), String(span), strict ? 'strict' : 'flex'], {
encoding: 'utf-8',
});
if (result.status === 0 && result.stdout) {
return { success: true, port: Number(result.stdout.trim()) };
}
const error = (result.stderr || result.error?.message || '').trim();
return { success: false, error: error || 'port-probe-failed' };
}
function isPortProbePermissionError(error?: string): boolean {
if (!error) return false;
const normalized = error.trim().toUpperCase();
// Some sandboxed environments forbid binding/listening, causing EPERM/EACCES.
return normalized.includes('EPERM') || normalized.includes('EACCES');
}
function resolveWebPortSync(): number {
const preferred = Number(process.env.PLAYWRIGHT_APP_PORT || process.env.APP_PORT || 3300);
if (process.env.PLAYWRIGHT_APP_PORT_LOCKED === 'true' && process.env.PLAYWRIGHT_APP_PORT) {
if (!Number.isFinite(preferred)) {
throw new Error(`Invalid PLAYWRIGHT_APP_PORT value: ${process.env.PLAYWRIGHT_APP_PORT}`);
}
return preferred;
}
if (process.env.PLAYWRIGHT_APP_PORT) {
if (!Number.isFinite(preferred)) {
throw new Error(`Invalid PLAYWRIGHT_APP_PORT value: ${process.env.PLAYWRIGHT_APP_PORT}`);
}
const check = runPortProbe(preferred, 0, true);
if (!check.success || typeof check.port !== 'number') {
if (isPortProbePermissionError(check.error)) {
console.warn(`[Playwright] Port probe blocked (${check.error}); using PLAYWRIGHT_APP_PORT=${preferred} without validation.`);
return preferred;
}
// Even if a preferred port is provided, fall back to scanning for a nearby
// available port to avoid spurious failures when developers have multiple
// environments running locally.
const fallback = runPortProbe(preferred, 25, false);
if (!fallback.success || typeof fallback.port !== 'number') {
throw new Error(`PLAYWRIGHT_APP_PORT=${preferred} is unavailable (${check.error || 'unknown error'}).`);
}
return fallback.port;
}
return check.port;
}
const probe = runPortProbe(preferred, 25, false);
if (!probe.success || typeof probe.port !== 'number') {
if (isPortProbePermissionError(probe.error)) {
console.warn(`[Playwright] Port probe blocked (${probe.error}); defaulting to port ${preferred}.`);
return preferred;
}
throw new Error(`Unable to find an available port for the Playwright dev server (${probe.error || 'probe failed'}).`);
}
return probe.port;
}
function resolveServicePortSync(envVarName: string, fallbackPort: number): number {
const preferred = Number(process.env[envVarName] || fallbackPort);
if (!Number.isFinite(preferred)) {
throw new Error(`Invalid ${envVarName} value: ${process.env[envVarName]}`);
}
const lockedVarName = `${envVarName}_LOCKED`;
if (process.env[lockedVarName] === 'true') {
return preferred;
}
// If the preferred port is taken, scan forward for an available one (up to 25).
const probe = runPortProbe(preferred, 25, false);
if (!probe.success || typeof probe.port !== 'number') {
if (isPortProbePermissionError(probe.error)) {
console.warn(`[Playwright] Port probe blocked (${probe.error}); using ${envVarName}=${preferred} without validation.`);
return preferred;
}
throw new Error(`Unable to find an available port for ${envVarName} starting at ${preferred} (${probe.error || 'probe failed'}).`);
}
return probe.port;
}
const PORT_CACHE_KEY = Symbol.for('__ALGA_PLAYWRIGHT_PORT__');
function getCachedWebPort(): number {
const cached = globalThis[PORT_CACHE_KEY];
if (typeof cached === 'number' && Number.isFinite(cached)) {
return cached;
}
console.log('[Playwright] env before port detection', {
PLAYWRIGHT_APP_PORT: process.env.PLAYWRIGHT_APP_PORT,
APP_PORT: process.env.APP_PORT,
PORT: process.env.PORT,
});
const resolved = resolveWebPortSync();
globalThis[PORT_CACHE_KEY] = resolved;
console.log(`[Playwright] using dev server port ${resolved}`);
return resolved;
}
const resolvedWebPort = getCachedWebPort();
const webHost = process.env.PLAYWRIGHT_APP_HOST || 'localhost';
const resolvedBaseUrl = process.env.PLAYWRIGHT_BASE_URL || `http://${webHost}:${resolvedWebPort}`;
const resolvedHostForEnv = (() => {
try {
return new URL(resolvedBaseUrl).host;
} catch {
return `${webHost}:${resolvedWebPort}`;
}
})();
process.env.PLAYWRIGHT_APP_PORT = String(resolvedWebPort);
process.env.PLAYWRIGHT_APP_PORT_LOCKED = 'true';
process.env.HOST = process.env.HOST || resolvedHostForEnv;
process.env.APP_PORT = process.env.APP_PORT || String(resolvedWebPort);
process.env.EXPOSE_SERVER_PORT = process.env.EXPOSE_SERVER_PORT || String(resolvedWebPort);
process.env.PORT = process.env.PORT || String(resolvedWebPort);
// Reserve ports for dockerized Playwright deps (postgres/redis/worker).
const resolvedPlaywrightDbPort = resolveServicePortSync('PLAYWRIGHT_DB_PORT', Number(directDbPort || 5439));
process.env.PLAYWRIGHT_DB_PORT = String(resolvedPlaywrightDbPort);
process.env.PLAYWRIGHT_DB_PORT_LOCKED = 'true';
process.env.DB_DIRECT_PORT = String(resolvedPlaywrightDbPort);
process.env.PLAYWRIGHT_DB_PORT = String(resolvedPlaywrightDbPort);
const resolvedPlaywrightRedisPort = resolveServicePortSync('REDIS_PORT', Number(process.env.REDIS_PORT || 16379));
process.env.REDIS_PORT = String(resolvedPlaywrightRedisPort);
process.env.REDIS_PORT_LOCKED = 'true';
const resolvedPlaywrightHocuspocusPort = resolveServicePortSync(
'PLAYWRIGHT_HOCUSPOCUS_PORT',
Number(process.env.PLAYWRIGHT_HOCUSPOCUS_PORT || process.env.EXPOSE_HOCUSPOCUS_PORT || 1234)
);
process.env.PLAYWRIGHT_HOCUSPOCUS_PORT = String(resolvedPlaywrightHocuspocusPort);
process.env.PLAYWRIGHT_HOCUSPOCUS_PORT_LOCKED = 'true';
process.env.EXPOSE_HOCUSPOCUS_PORT = String(resolvedPlaywrightHocuspocusPort);
process.env.NEXT_PUBLIC_HOCUSPOCUS_URL =
process.env.NEXT_PUBLIC_HOCUSPOCUS_URL || `ws://localhost:${resolvedPlaywrightHocuspocusPort}`;
process.env.HOCUSPOCUS_JWT_SECRET =
process.env.HOCUSPOCUS_JWT_SECRET || 'dev-hocuspocus-jwt-secret';
process.env.REDIS_PASSWORD = process.env.REDIS_PASSWORD || 'sebastian123';
const skipWorkflowWorker = parseTruthyEnv(process.env.PLAYWRIGHT_SKIP_WORKFLOW_WORKER);
// Apply Playwright-specific database configuration (after port resolution).
applyPlaywrightDatabaseEnv();
function parseTruthyEnv(value?: string): boolean {
if (!value) return false;
const normalized = value.trim().toLowerCase();
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'y' || normalized === 'on';
}
// Decide whether Playwright should manage starting the dev server.
// - In CI, some pipelines start the server externally and only want tests to connect.
// - However, if no explicit base URL is provided, disabling webServer causes hard-to-debug ECONNREFUSED.
const explicitBaseUrlProvided = Boolean(
process.env.PLAYWRIGHT_BASE_URL || process.env.EE_BASE_URL || process.env.NEXTAUTH_URL
);
const isCi = parseTruthyEnv(process.env.CI);
const shouldRunWebServer =
process.env.PW_WEBSERVER === 'true'
? true
: process.env.PW_WEBSERVER === 'false'
? false
: !(isCi && explicitBaseUrlProvided);
// If Playwright is managing the dev server, make the computed base URL authoritative.
// This avoids a mismatch where an env file sets EE_BASE_URL/NEXTAUTH_URL to a different port.
if (shouldRunWebServer && !process.env.PLAYWRIGHT_BASE_URL) {
process.env.EE_BASE_URL = resolvedBaseUrl;
process.env.NEXTAUTH_URL = resolvedBaseUrl;
} else {
process.env.EE_BASE_URL = process.env.EE_BASE_URL || resolvedBaseUrl;
process.env.NEXTAUTH_URL = process.env.NEXTAUTH_URL || resolvedBaseUrl;
}
console.log('[Playwright] webServer', {
CI: process.env.CI,
isCi,
explicitBaseUrlProvided,
shouldRunWebServer,
resolvedBaseUrl,
EE_BASE_URL: process.env.EE_BASE_URL,
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
});
/**
* Playwright configuration for EE server integration tests
* @see https://playwright.dev/docs/test-configuration
*/
export default defineConfig({
testDir: './src/__tests__/integration',
// Run all Playwright integration tests in this folder
testMatch: ['**/*.playwright.test.ts'],
/* Global setup file */
globalSetup: './playwright.global-setup.ts',
/* Global teardown file */
globalTeardown: './playwright.global-teardown.ts',
/* Run tests in files in parallel */
fullyParallel: false, // Disabled for database isolation
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry control: CI default 2, local default 0, override with PW_RETRIES */
retries: process.env.PW_RETRIES !== undefined
? Number(process.env.PW_RETRIES)
: (process.env.CI ? 2 : 0),
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : 1, // Single worker for database isolation
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [
['list'],
['json', { outputFile: 'playwright-report/results.json' }],
['html', { open: 'never' }]
],
/* 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: resolvedBaseUrl,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Take screenshot on failure */
screenshot: 'only-on-failure',
/* Record video on failure */
video: 'retain-on-failure',
/* Global test timeout */
actionTimeout: 15000,
navigationTimeout: 30000,
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
launchOptions: {
headless: false,
args: [
'--host-resolver-rules=MAP portal.acme.local 127.0.0.1,MAP canonical.localhost 127.0.0.1,MAP localhost 127.0.0.1'
],
},
},
},
// Uncomment for cross-browser testing
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
],
/* Run your local dev server before starting the tests */
webServer: shouldRunWebServer ? {
// Reset DB once per session before starting the dev server.
command:
'cd ../../' +
` && PLAYWRIGHT_DB_PORT=${resolvedPlaywrightDbPort} REDIS_PORT=${resolvedPlaywrightRedisPort} PLAYWRIGHT_HOCUSPOCUS_PORT=${resolvedPlaywrightHocuspocusPort} REDIS_PASSWORD=${process.env.REDIS_PASSWORD} HOCUSPOCUS_JWT_SECRET=${process.env.HOCUSPOCUS_JWT_SECRET} docker compose -f docker-compose.playwright-workflow-deps.yml -p alga-psa-playwright-workflow --env-file ${dockerComposeEnvFile} up -d --wait --wait-timeout 60 postgres-playwright redis-playwright` +
' && node --import tsx/esm scripts/bootstrap-playwright-db.ts' +
(skipWorkflowWorker
? ` && PLAYWRIGHT_DB_PORT=${resolvedPlaywrightDbPort} REDIS_PORT=${resolvedPlaywrightRedisPort} PLAYWRIGHT_HOCUSPOCUS_PORT=${resolvedPlaywrightHocuspocusPort} REDIS_PASSWORD=${process.env.REDIS_PASSWORD} HOCUSPOCUS_JWT_SECRET=${process.env.HOCUSPOCUS_JWT_SECRET} docker compose -f docker-compose.playwright-workflow-deps.yml -p alga-psa-playwright-workflow --env-file ${dockerComposeEnvFile} up -d --build --wait --wait-timeout 120 hocuspocus-playwright`
: ` && PLAYWRIGHT_DB_PORT=${resolvedPlaywrightDbPort} REDIS_PORT=${resolvedPlaywrightRedisPort} PLAYWRIGHT_HOCUSPOCUS_PORT=${resolvedPlaywrightHocuspocusPort} REDIS_PASSWORD=${process.env.REDIS_PASSWORD} HOCUSPOCUS_JWT_SECRET=${process.env.HOCUSPOCUS_JWT_SECRET} docker compose -f docker-compose.playwright-workflow-deps.yml -p alga-psa-playwright-workflow --env-file ${dockerComposeEnvFile} up -d --build --wait --wait-timeout 120 workflow-worker-playwright hocuspocus-playwright`) +
' && NEXT_PUBLIC_EDITION=enterprise npm run dev',
url: resolvedBaseUrl,
// Default to starting a fresh server (we also need to bring up dockerized deps + reset the DB).
// Opt-in reuse with PW_REUSE=true for local iteration.
reuseExistingServer: process.env.PW_REUSE === 'true',
timeout: 300000,
stdout: 'pipe',
stderr: 'pipe',
env: {
...process.env,
// Use a writable filesystem secret store for tenant secrets; keep app secrets in env.
SECRET_READ_CHAIN: process.env.SECRET_READ_CHAIN || 'filesystem,env',
SECRET_WRITE_PROVIDER: process.env.SECRET_WRITE_PROVIDER || 'filesystem',
SECRET_FS_BASE_PATH: process.env.SECRET_FS_BASE_PATH || 'secrets-playwright',
NEXT_PUBLIC_EDITION: 'enterprise',
E2E_AUTH_BYPASS: 'true',
EE_BASE_URL: resolvedBaseUrl,
NEXTAUTH_URL: resolvedBaseUrl,
HOST: resolvedHostForEnv,
HOSTNAME: webHost,
PORT: String(resolvedWebPort),
APP_PORT: String(resolvedWebPort),
EXPOSE_SERVER_PORT: String(resolvedWebPort),
NEXT_PUBLIC_APP_URL: resolvedBaseUrl,
NEXT_PUBLIC_SITE_URL: resolvedBaseUrl,
NEXT_PUBLIC_API_BASE_URL: resolvedBaseUrl,
NEXT_PUBLIC_EXTERNAL_APP_URL: resolvedBaseUrl,
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET || 'test-nextauth-secret',
NEXT_PUBLIC_DISABLE_FEATURE_FLAGS: process.env.NEXT_PUBLIC_DISABLE_FEATURE_FLAGS ?? 'true',
NEXT_PUBLIC_HOCUSPOCUS_URL:
process.env.NEXT_PUBLIC_HOCUSPOCUS_URL || `ws://localhost:${resolvedPlaywrightHocuspocusPort}`,
HOCUSPOCUS_JWT_SECRET: process.env.HOCUSPOCUS_JWT_SECRET || 'dev-hocuspocus-jwt-secret',
REDIS_PASSWORD: process.env.REDIS_PASSWORD || 'sebastian123',
// Explicitly set DB config for Playwright - override server/.env settings
DB_HOST: process.env.PLAYWRIGHT_DB_HOST || process.env.DB_HOST || 'localhost',
DB_PORT: process.env.PLAYWRIGHT_DB_PORT || process.env.DB_PORT || '5439',
DB_NAME: process.env.PLAYWRIGHT_DB_NAME || process.env.DB_NAME || 'alga_contract_wizard_test',
DB_NAME_SERVER: process.env.PLAYWRIGHT_DB_NAME || process.env.DB_NAME_SERVER || 'alga_contract_wizard_test',
DB_USER: process.env.PLAYWRIGHT_DB_ADMIN_USER || process.env.DB_USER || 'postgres',
DB_PASSWORD: process.env.PLAYWRIGHT_DB_ADMIN_PASSWORD || process.env.DB_PASSWORD || '',
DB_USER_SERVER: process.env.PLAYWRIGHT_DB_ADMIN_USER || process.env.DB_USER_SERVER || 'postgres',
DB_PASSWORD_SERVER: process.env.PLAYWRIGHT_DB_ADMIN_PASSWORD || process.env.DB_PASSWORD_SERVER || '',
DB_PASSWORD_ADMIN: process.env.PLAYWRIGHT_DB_ADMIN_PASSWORD || process.env.DB_PASSWORD_ADMIN || '',
// Use S3/MinIO for file uploads (not local storage)
// MinIO test instance runs on port 9002 (separate from Payload MinIO on 9000)
STORAGE_DEFAULT_PROVIDER: 's3', // Use S3/MinIO instead of local storage
STORAGE_S3_ENDPOINT: process.env.STORAGE_S3_ENDPOINT || 'http://localhost:9002',
STORAGE_S3_ACCESS_KEY: process.env.STORAGE_S3_ACCESS_KEY || 'minioadmin',
STORAGE_S3_SECRET_KEY: process.env.STORAGE_S3_SECRET_KEY || 'minioadmin',
STORAGE_S3_BUCKET: process.env.STORAGE_S3_BUCKET || 'alga-test',
STORAGE_S3_REGION: process.env.STORAGE_S3_REGION || 'us-east-1',
STORAGE_S3_FORCE_PATH_STYLE: process.env.STORAGE_S3_FORCE_PATH_STYLE || 'true',
// Redis is required for the event bus; prefer a local instance for Playwright.
REDIS_HOST: process.env.REDIS_HOST || 'localhost',
REDIS_PORT: process.env.REDIS_PORT || '6379',
}
} : undefined,
/* Global test timeout */
timeout: 60000,
/* Test output directory */
outputDir: 'playwright-test-results/',
});