Fix: legit logins only
This commit is contained in:
@@ -0,0 +1,434 @@
|
||||
# Global Setup & Teardown
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Global Setup](#global-setup)
|
||||
2. [Global Teardown](#global-teardown)
|
||||
3. [Database Patterns](#database-patterns)
|
||||
4. [Environment Provisioning](#environment-provisioning)
|
||||
5. [Setup Projects vs Global Setup](#setup-projects-vs-global-setup)
|
||||
6. [Parallel Execution Caveats](#parallel-execution-caveats)
|
||||
|
||||
## Global Setup
|
||||
|
||||
### Basic Global Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { FullConfig } from "@playwright/test";
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
console.log("Running global setup...");
|
||||
// Perform one-time setup: start services, run migrations, etc.
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
### Configure Global Setup
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
globalSetup: require.resolve("./global-setup"),
|
||||
globalTeardown: require.resolve("./global-teardown"),
|
||||
});
|
||||
```
|
||||
|
||||
> **Authentication in Global Setup**: For authentication patterns using storage state in global setup, see [fixtures-hooks.md](fixtures-hooks.md#authentication-patterns). Setup projects are generally preferred for authentication as they provide access to Playwright fixtures.
|
||||
|
||||
### Global Setup with Return Value
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
async function globalSetup(config: FullConfig): Promise<() => Promise<void>> {
|
||||
const server = await startTestServer();
|
||||
|
||||
// Return cleanup function (alternative to globalTeardown)
|
||||
return async () => {
|
||||
await server.stop();
|
||||
};
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
### Access Config in Global Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { FullConfig } from "@playwright/test";
|
||||
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const { baseURL } = config.projects[0].use;
|
||||
console.log(`Setting up for ${baseURL}`);
|
||||
|
||||
// Access custom config
|
||||
const workers = config.workers;
|
||||
const timeout = config.timeout;
|
||||
|
||||
// Access environment
|
||||
const isCI = !!process.env.CI;
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
## Global Teardown
|
||||
|
||||
### Basic Global Teardown
|
||||
|
||||
```typescript
|
||||
// global-teardown.ts
|
||||
import { FullConfig } from "@playwright/test";
|
||||
import fs from "fs";
|
||||
|
||||
async function globalTeardown(config: FullConfig) {
|
||||
console.log("Running global teardown...");
|
||||
|
||||
// Clean up auth files
|
||||
if (fs.existsSync(".auth")) {
|
||||
fs.rmSync(".auth", { recursive: true });
|
||||
}
|
||||
|
||||
// Clean up test data
|
||||
await cleanupTestDatabase();
|
||||
|
||||
// Stop services
|
||||
await stopTestServices();
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
||||
```
|
||||
|
||||
### Conditional Teardown
|
||||
|
||||
```typescript
|
||||
// global-teardown.ts
|
||||
async function globalTeardown(config: FullConfig) {
|
||||
// Skip cleanup in CI (containers are discarded anyway)
|
||||
if (process.env.CI) {
|
||||
console.log("Skipping teardown in CI");
|
||||
return;
|
||||
}
|
||||
|
||||
// Local cleanup
|
||||
await cleanupLocalTestData();
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
||||
```
|
||||
|
||||
## Database Patterns
|
||||
|
||||
This section covers **one-time database setup** (migrations, snapshots, per-worker databases). For related topics:
|
||||
|
||||
- **Per-test database fixtures** (isolation, transaction rollback): See [fixtures-hooks.md](fixtures-hooks.md#database-fixtures)
|
||||
- **Test data factories** (builders, Faker): See [test-data.md](test-data.md)
|
||||
|
||||
### Database Migration in Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { execSync } from "child_process";
|
||||
|
||||
async function globalSetup() {
|
||||
console.log("Running database migrations...");
|
||||
|
||||
// Run migrations
|
||||
execSync("npx prisma migrate deploy", { stdio: "inherit" });
|
||||
|
||||
// Seed test data
|
||||
execSync("npx prisma db seed", { stdio: "inherit" });
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
### Database Snapshot Pattern
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
|
||||
const SNAPSHOT_PATH = "./test-db-snapshot.sql";
|
||||
|
||||
async function globalSetup() {
|
||||
// Check if snapshot exists
|
||||
if (fs.existsSync(SNAPSHOT_PATH)) {
|
||||
console.log("Restoring database from snapshot...");
|
||||
execSync(`psql $DATABASE_URL < ${SNAPSHOT_PATH}`, { stdio: "inherit" });
|
||||
return;
|
||||
}
|
||||
|
||||
// First run: migrate and create snapshot
|
||||
console.log("Creating database snapshot...");
|
||||
execSync("npx prisma migrate deploy", { stdio: "inherit" });
|
||||
execSync("npx prisma db seed", { stdio: "inherit" });
|
||||
execSync(`pg_dump $DATABASE_URL > ${SNAPSHOT_PATH}`, { stdio: "inherit" });
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
### Test Database per Worker
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
async function globalSetup(config: FullConfig) {
|
||||
const workerCount = config.workers || 1;
|
||||
|
||||
// Create a database for each worker
|
||||
for (let i = 0; i < workerCount; i++) {
|
||||
const dbName = `test_db_worker_${i}`;
|
||||
await createDatabase(dbName);
|
||||
await runMigrations(dbName);
|
||||
await seedDatabase(dbName);
|
||||
}
|
||||
}
|
||||
|
||||
// global-teardown.ts
|
||||
async function globalTeardown(config: FullConfig) {
|
||||
const workerCount = config.workers || 1;
|
||||
|
||||
for (let i = 0; i < workerCount; i++) {
|
||||
await dropDatabase(`test_db_worker_${i}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Provisioning
|
||||
|
||||
### Start Services in Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { execSync, spawn } from "child_process";
|
||||
|
||||
let serverProcess: any;
|
||||
|
||||
async function globalSetup() {
|
||||
// Start backend server
|
||||
serverProcess = spawn("npm", ["run", "start:test"], {
|
||||
stdio: "pipe",
|
||||
detached: true,
|
||||
});
|
||||
|
||||
// Wait for server to be ready
|
||||
await waitForServer("http://localhost:3000/health", 30000);
|
||||
|
||||
// Store PID for teardown
|
||||
process.env.SERVER_PID = serverProcess.pid.toString();
|
||||
}
|
||||
|
||||
async function waitForServer(url: string, timeout: number) {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) return;
|
||||
} catch {
|
||||
// Server not ready yet
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
throw new Error(`Server did not start within ${timeout}ms`);
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
### Docker Compose Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import { execSync } from "child_process";
|
||||
|
||||
async function globalSetup() {
|
||||
console.log("Starting Docker services...");
|
||||
|
||||
execSync("docker-compose -f docker-compose.test.yml up -d", {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
// Wait for services to be healthy
|
||||
execSync("docker-compose -f docker-compose.test.yml exec -T db pg_isready", {
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// global-teardown.ts
|
||||
import { execSync } from "child_process";
|
||||
|
||||
async function globalTeardown() {
|
||||
console.log("Stopping Docker services...");
|
||||
|
||||
execSync("docker-compose -f docker-compose.test.yml down -v", {
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
export default globalTeardown;
|
||||
```
|
||||
|
||||
### Environment Variables Setup
|
||||
|
||||
```typescript
|
||||
// global-setup.ts
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
|
||||
async function globalSetup() {
|
||||
// Load test-specific environment
|
||||
const envFile = process.env.CI ? ".env.ci" : ".env.test";
|
||||
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
|
||||
|
||||
// Validate required variables
|
||||
const required = ["DATABASE_URL", "API_KEY", "TEST_EMAIL"];
|
||||
for (const key of required) {
|
||||
if (!process.env[key]) {
|
||||
throw new Error(`Missing required environment variable: ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
```
|
||||
|
||||
## Setup Projects vs Global Setup
|
||||
|
||||
### When to Use Each
|
||||
|
||||
| Use Global Setup | Use Setup Projects |
|
||||
| ------------------------------------- | ---------------------------------------- |
|
||||
| One-time setup (migrations, services) | Per-project setup (auth states) |
|
||||
| No access to Playwright fixtures | Need page, request fixtures |
|
||||
| Runs once before all projects | Can run per-project or have dependencies |
|
||||
| Shared across all workers | Can be parallelized |
|
||||
|
||||
### Setup Project Pattern
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
export default defineConfig({
|
||||
projects: [
|
||||
// Setup project
|
||||
{
|
||||
name: "setup",
|
||||
testMatch: /.*\.setup\.ts/,
|
||||
},
|
||||
// Test projects depend on setup
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
{
|
||||
name: "firefox",
|
||||
use: { ...devices["Desktop Firefox"] },
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
> **For complete authentication setup patterns**, see [fixtures-hooks.md](fixtures-hooks.md#authentication-patterns).
|
||||
|
||||
### Combining Both
|
||||
|
||||
```typescript
|
||||
// playwright.config.ts
|
||||
export default defineConfig({
|
||||
// Global: Start services, run migrations
|
||||
globalSetup: require.resolve("./global-setup"),
|
||||
globalTeardown: require.resolve("./global-teardown"),
|
||||
|
||||
projects: [
|
||||
// Setup project: Create auth states
|
||||
{ name: "setup", testMatch: /.*\.setup\.ts/ },
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
storageState: ".auth/user.json",
|
||||
},
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Parallel Execution Caveats
|
||||
|
||||
### Understanding Global Setup Execution
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ globalSetup runs ONCE │
|
||||
│ ↓ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Worker 1│ │ Worker 2│ │ Worker 3│ │ Worker 4│ │
|
||||
│ │ tests │ │ tests │ │ tests │ │ tests │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ ↓ │
|
||||
│ globalTeardown runs ONCE │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key implications:**
|
||||
|
||||
- Global setup has **no access** to Playwright fixtures (`page`, `request`, `context`)
|
||||
- State created in global setup is **shared** across all workers
|
||||
- If tests **modify** shared state, they may conflict with parallel workers
|
||||
- Global setup **cannot** react to individual test needs
|
||||
|
||||
### When to Prefer Worker-Scoped Fixtures
|
||||
|
||||
Use **worker-scoped fixtures** instead of globalSetup when:
|
||||
|
||||
| Scenario | Why Fixtures Are Better |
|
||||
| ------------------------------------ | ---------------------------------------------------- |
|
||||
| Each worker needs isolated resources | Fixtures can create per-worker databases, servers |
|
||||
| Setup needs Playwright APIs | Fixtures have access to `page`, `request`, `browser` |
|
||||
| Setup depends on test configuration | Fixtures receive test context and options |
|
||||
| Resources need cleanup per worker | Worker fixtures auto-cleanup when worker exits |
|
||||
|
||||
### Common Parallel Pitfall
|
||||
|
||||
```typescript
|
||||
// ❌ BAD: Global setup creates ONE user, all workers fight over it
|
||||
async function globalSetup() {
|
||||
await createUser({ email: "test@example.com" }); // Shared!
|
||||
}
|
||||
|
||||
// ✅ GOOD: Each worker gets its own user via worker-scoped fixture
|
||||
// Uses workerInfo.workerIndex to create unique data per worker
|
||||
```
|
||||
|
||||
> **For worker-scoped fixture patterns** (per-worker databases, unique test data, `workerIndex` isolation), see [fixtures-hooks.md](fixtures-hooks.md#isolate-test-data-between-parallel-workers).
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
| Anti-Pattern | Problem | Solution |
|
||||
| ------------------------------ | -------------------------------- | ------------------------------------------ |
|
||||
| Heavy setup in globalSetup | Slow test startup | Use setup projects for parallelizable work |
|
||||
| Not cleaning up in teardown | Leaks resources, flaky CI | Always clean up or use containers |
|
||||
| Hardcoded URLs in setup | Breaks in different environments | Use config.projects[0].use.baseURL |
|
||||
| No timeout on service wait | Hangs forever if service fails | Add timeout with clear error |
|
||||
| Shared mutable state | Race conditions in parallel | Use worker-scoped fixtures for isolation |
|
||||
| Global setup for per-test data | Tests conflict | Use test-scoped fixtures |
|
||||
|
||||
## Related References
|
||||
|
||||
- **Fixtures & Auth**: See [fixtures-hooks.md](fixtures-hooks.md) for worker-scoped fixtures and auth patterns
|
||||
- **CI/CD**: See [ci-cd.md](../infrastructure-ci-cd/ci-cd.md) for CI setup patterns
|
||||
- **Projects**: See [projects-dependencies.md](projects-dependencies.md) for project configuration
|
||||
Reference in New Issue
Block a user