Skip to content

Commit 9983dc7

Browse files
Elaborate Playwright seeding section in 5.1: request fixture, beforeAll, globalSetup patterns
1 parent 0532278 commit 9983dc7

1 file changed

Lines changed: 126 additions & 6 deletions

File tree

blogs/series-5-devops-data/5.1-database-seeding.md

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,19 +444,139 @@ The seed value makes generation **deterministic**. Given the same seed, Bogus al
444444

445445
---
446446

447-
## 🧪 Playwright Seeding Placeholder
447+
## 🧪 Playwright Seeding via the API
448448

449-
`Tests/AngularNetTutorial-Playwright/tests/seed.spec.ts` exists as a placeholder for E2E-driven seeding:
449+
`Tests/AngularNetTutorial-Playwright/tests/seed.spec.ts` is a placeholder for E2E-driven seeding. The startup seeder gives every developer a consistent base of 1,000 employees — but sometimes an E2E test needs something more specific: a known number of positions, a particular department, or a clean slate before a destructive test run. That's what this file is for.
450+
451+
### Why E2E Tests Need Their Own Seeding
452+
453+
The startup seeder runs once on first launch. After that, tests that create, update, or delete records permanently change the database. If three tests run in sequence and test 2 deletes a record that test 3 expects to find, test 3 fails intermittently depending on run order. E2E-driven seeding solves this by resetting or expanding the dataset to a known state before the tests that depend on it.
454+
455+
Three common patterns:
456+
457+
* **Add before** — seed extra records before a test that needs more data than startup seeding provides (e.g., pagination tests that need 200+ positions)
458+
* **Reset between** — call the seed endpoint in `beforeEach` to restore a known state before each destructive test
459+
* **Seed in global setup** — run once before the entire test suite to guarantee a baseline without relying on startup seeding
460+
461+
### The Playwright `request` Fixture
462+
463+
Playwright provides a `request` fixture — an `APIRequestContext` that makes HTTP calls without launching a browser. It is the right tool for calling the seed API in tests because:
464+
465+
* No browser overhead — seed calls complete in milliseconds
466+
* Available in `beforeAll`, `beforeEach`, and in `globalSetup`
467+
* Uses the same `baseURL` from `playwright.config.ts`
468+
* Returns a full `APIResponse` with status, headers, and body
469+
470+
### Calling the Seed Endpoint
471+
472+
The `InsertMockPositionCommand` is exposed as `POST /api/v1/positions/seed`. Here is a complete implementation of `seed.spec.ts`:
473+
474+
```typescript
475+
// Tests/AngularNetTutorial-Playwright/tests/seed.spec.ts
476+
import { test, expect } from '@playwright/test';
477+
478+
const API_BASE = process.env.API_URL ?? 'https://localhost:44378';
479+
480+
test.describe('Database seeding via API', () => {
481+
482+
test('seed positions', async ({ request }) => {
483+
// POST /api/v1/positions/seed?rowCount=25
484+
// InsertMockPositionCommand generates positions with valid FK references
485+
const response = await request.post(`${API_BASE}/api/v1/positions/seed`, {
486+
params: { rowCount: 25 },
487+
});
488+
489+
expect(response.status()).toBe(200);
490+
491+
const rowsInserted: number = await response.json();
492+
console.log(`Seeded ${rowsInserted} positions`);
493+
expect(rowsInserted).toBeGreaterThan(0);
494+
});
495+
496+
});
497+
```
498+
499+
**`params: { rowCount: 25 }`** — Playwright serializes this as a query string: `?rowCount=25`. The command handler uses this value to control how many positions Bogus generates.
500+
501+
**`request.post()`** — uses the `request` fixture from the `test` parameters. No browser is opened. The call completes before the test body continues.
502+
503+
**`await response.json()`** — the endpoint returns the count of rows inserted as a plain integer. The assertion confirms at least one record was created.
504+
505+
### Seeding in `beforeAll` for a Test Suite
506+
507+
When multiple tests in the same file need the seeded data, call the seed endpoint in `beforeAll` so it runs once before the suite rather than repeating it in every test:
450508

451509
```typescript
452-
test.describe('Test group', () => {
453-
test('seed', async ({ page }) => {
454-
// generate code here.
510+
// Tests/AngularNetTutorial-Playwright/tests/positions.spec.ts
511+
import { test, expect, request as playwrightRequest } from '@playwright/test';
512+
513+
const API_BASE = process.env.API_URL ?? 'https://localhost:44378';
514+
515+
test.describe('Positions list', () => {
516+
517+
test.beforeAll(async ({ request }) => {
518+
// Ensure at least 50 positions exist before any test in this suite runs
519+
await request.post(`${API_BASE}/api/v1/positions/seed`, {
520+
params: { rowCount: 50 },
521+
});
522+
});
523+
524+
test('shows positions in the table', async ({ page }) => {
525+
await page.goto('/positions');
526+
await expect(page.getByRole('row')).toHaveCount.greaterThan(10);
455527
});
528+
529+
test('pagination works with enough rows', async ({ page }) => {
530+
await page.goto('/positions');
531+
await expect(page.getByTestId('next-page-btn')).toBeEnabled();
532+
});
533+
534+
});
535+
```
536+
537+
### Seeding in Global Setup
538+
539+
For seeding that should run once before the **entire Playwright suite** — not just one file — use a `globalSetup` script. `playwright.config.ts` already has the hook point:
540+
541+
```typescript
542+
// playwright.config.ts
543+
export default defineConfig({
544+
globalSetup: './global-setup.ts',
545+
// ...
456546
});
457547
```
458548

459-
When an E2E test suite needs a specific dataset before running, this is where you'd implement it — call the `InsertMockPositionCommand` endpoint via the `request` fixture, or trigger a specific seed via the API. The file exists to remind you this pattern is available.
549+
```typescript
550+
// Tests/AngularNetTutorial-Playwright/global-setup.ts
551+
import { request } from '@playwright/test';
552+
553+
const API_BASE = process.env.API_URL ?? 'https://localhost:44378';
554+
555+
async function globalSetup() {
556+
const apiContext = await request.newContext({ baseURL: API_BASE });
557+
558+
const response = await apiContext.post('/api/v1/positions/seed', {
559+
params: { rowCount: 100 },
560+
});
561+
562+
if (!response.ok()) {
563+
throw new Error(`Seed failed: ${response.status()} ${await response.text()}`);
564+
}
565+
566+
console.log('Global seed complete');
567+
await apiContext.dispose();
568+
}
569+
570+
export default globalSetup;
571+
```
572+
573+
`request.newContext()` in `globalSetup` creates a standalone API context outside of any test. `await apiContext.dispose()` releases it when done. Throwing an error from `globalSetup` fails the entire run immediately with a clear message rather than silently running tests against an unseeded database.
574+
575+
### When to Use Each Approach
576+
577+
* **`seed.spec.ts` standalone test** — run manually from Swagger or CI when you need to top up data in a shared environment
578+
* **`beforeAll` in a spec file** — guarantee a data baseline for one specific test suite without affecting others
579+
* **`globalSetup`** — guarantee the baseline for the entire run; appropriate when all tests depend on the same starting state
460580

461581
---
462582

0 commit comments

Comments
 (0)