@arcadialdev/arcality 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# Test Data Factories & Generators
|
|
2
|
+
|
|
3
|
+
This file covers **reusable test data builders** (factories, Faker, data generators). For related topics:
|
|
4
|
+
|
|
5
|
+
- **Per-test database fixtures** (isolation, transaction rollback): See [fixtures-hooks.md](fixtures-hooks.md#database-fixtures)
|
|
6
|
+
- **One-time database setup** (migrations, snapshots): See [global-setup.md](global-setup.md#database-patterns)
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Factory Pattern](#factory-pattern)
|
|
11
|
+
2. [Faker Integration](#faker-integration)
|
|
12
|
+
3. [Data-Driven Testing](#data-driven-testing)
|
|
13
|
+
4. [Test Data Fixtures](#test-data-fixtures)
|
|
14
|
+
5. [Database Seeding](#database-seeding)
|
|
15
|
+
|
|
16
|
+
## Factory Pattern
|
|
17
|
+
|
|
18
|
+
### Basic Factory
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// factories/user.factory.ts
|
|
22
|
+
interface User {
|
|
23
|
+
id: string;
|
|
24
|
+
email: string;
|
|
25
|
+
name: string;
|
|
26
|
+
role: "admin" | "user" | "guest";
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let userIdCounter = 0;
|
|
31
|
+
|
|
32
|
+
export function createUser(overrides: Partial<User> = {}): User {
|
|
33
|
+
userIdCounter++;
|
|
34
|
+
return {
|
|
35
|
+
id: `user-${userIdCounter}`,
|
|
36
|
+
email: `user${userIdCounter}@test.com`,
|
|
37
|
+
name: `Test User ${userIdCounter}`,
|
|
38
|
+
role: "user",
|
|
39
|
+
createdAt: new Date(),
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Usage
|
|
45
|
+
const user = createUser();
|
|
46
|
+
const admin = createUser({ role: "admin", name: "Admin User" });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Factory with Traits
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// factories/product.factory.ts
|
|
53
|
+
interface Product {
|
|
54
|
+
id: string;
|
|
55
|
+
name: string;
|
|
56
|
+
price: number;
|
|
57
|
+
stock: number;
|
|
58
|
+
category: string;
|
|
59
|
+
featured: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type ProductTrait = "outOfStock" | "featured" | "expensive" | "sale";
|
|
63
|
+
|
|
64
|
+
const traits: Record<ProductTrait, Partial<Product>> = {
|
|
65
|
+
outOfStock: { stock: 0 },
|
|
66
|
+
featured: { featured: true },
|
|
67
|
+
expensive: { price: 999.99 },
|
|
68
|
+
sale: { price: 9.99 },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let productIdCounter = 0;
|
|
72
|
+
|
|
73
|
+
export function createProduct(
|
|
74
|
+
overrides: Partial<Product> = {},
|
|
75
|
+
...traitNames: ProductTrait[]
|
|
76
|
+
): Product {
|
|
77
|
+
productIdCounter++;
|
|
78
|
+
|
|
79
|
+
const appliedTraits = traitNames.reduce(
|
|
80
|
+
(acc, trait) => ({ ...acc, ...traits[trait] }),
|
|
81
|
+
{},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
id: `prod-${productIdCounter}`,
|
|
86
|
+
name: `Product ${productIdCounter}`,
|
|
87
|
+
price: 29.99,
|
|
88
|
+
stock: 100,
|
|
89
|
+
category: "General",
|
|
90
|
+
featured: false,
|
|
91
|
+
...appliedTraits,
|
|
92
|
+
...overrides,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Usage
|
|
97
|
+
const product = createProduct();
|
|
98
|
+
const featuredProduct = createProduct({}, "featured");
|
|
99
|
+
const saleItem = createProduct({ name: "Sale Item" }, "sale", "featured");
|
|
100
|
+
const soldOut = createProduct({}, "outOfStock");
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Factory with Relationships
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// factories/order.factory.ts
|
|
107
|
+
import { createUser, User } from "./user.factory";
|
|
108
|
+
import { createProduct, Product } from "./product.factory";
|
|
109
|
+
|
|
110
|
+
interface OrderItem {
|
|
111
|
+
product: Product;
|
|
112
|
+
quantity: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface Order {
|
|
116
|
+
id: string;
|
|
117
|
+
user: User;
|
|
118
|
+
items: OrderItem[];
|
|
119
|
+
total: number;
|
|
120
|
+
status: "pending" | "paid" | "shipped" | "delivered";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let orderIdCounter = 0;
|
|
124
|
+
|
|
125
|
+
export function createOrder(overrides: Partial<Order> = {}): Order {
|
|
126
|
+
orderIdCounter++;
|
|
127
|
+
|
|
128
|
+
const user = overrides.user ?? createUser();
|
|
129
|
+
const items = overrides.items ?? [{ product: createProduct(), quantity: 1 }];
|
|
130
|
+
const total = items.reduce(
|
|
131
|
+
(sum, item) => sum + item.product.price * item.quantity,
|
|
132
|
+
0,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
id: `order-${orderIdCounter}`,
|
|
137
|
+
user,
|
|
138
|
+
items,
|
|
139
|
+
total,
|
|
140
|
+
status: "pending",
|
|
141
|
+
...overrides,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Usage
|
|
146
|
+
const order = createOrder();
|
|
147
|
+
const bigOrder = createOrder({
|
|
148
|
+
items: [
|
|
149
|
+
{ product: createProduct({ price: 100 }), quantity: 5 },
|
|
150
|
+
{ product: createProduct({ price: 50 }), quantity: 2 },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Faker Integration
|
|
156
|
+
|
|
157
|
+
### Setup Faker
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm install -D @faker-js/faker
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// factories/faker-user.factory.ts
|
|
165
|
+
import { faker } from "@faker-js/faker";
|
|
166
|
+
|
|
167
|
+
interface User {
|
|
168
|
+
id: string;
|
|
169
|
+
email: string;
|
|
170
|
+
name: string;
|
|
171
|
+
avatar: string;
|
|
172
|
+
address: {
|
|
173
|
+
street: string;
|
|
174
|
+
city: string;
|
|
175
|
+
country: string;
|
|
176
|
+
zipCode: string;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function createFakeUser(overrides: Partial<User> = {}): User {
|
|
181
|
+
return {
|
|
182
|
+
id: faker.string.uuid(),
|
|
183
|
+
email: faker.internet.email(),
|
|
184
|
+
name: faker.person.fullName(),
|
|
185
|
+
avatar: faker.image.avatar(),
|
|
186
|
+
address: {
|
|
187
|
+
street: faker.location.streetAddress(),
|
|
188
|
+
city: faker.location.city(),
|
|
189
|
+
country: faker.location.country(),
|
|
190
|
+
zipCode: faker.location.zipCode(),
|
|
191
|
+
},
|
|
192
|
+
...overrides,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Seeded Faker for Reproducibility
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { faker } from "@faker-js/faker";
|
|
201
|
+
|
|
202
|
+
// Set seed for reproducible data
|
|
203
|
+
faker.seed(12345);
|
|
204
|
+
|
|
205
|
+
export function createDeterministicUser(): User {
|
|
206
|
+
return {
|
|
207
|
+
id: faker.string.uuid(),
|
|
208
|
+
email: faker.internet.email(),
|
|
209
|
+
name: faker.person.fullName(),
|
|
210
|
+
// Same seed = same data every time
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Or seed per test
|
|
215
|
+
test("user profile", async ({ page }) => {
|
|
216
|
+
faker.seed(42); // Reset seed for this test
|
|
217
|
+
const user = createFakeUser();
|
|
218
|
+
// user will always have the same data
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Faker Fixture
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// fixtures/faker.fixture.ts
|
|
226
|
+
import { test as base } from "@playwright/test";
|
|
227
|
+
import { faker } from "@faker-js/faker";
|
|
228
|
+
|
|
229
|
+
type FakerFixtures = {
|
|
230
|
+
fake: typeof faker;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const test = base.extend<FakerFixtures>({
|
|
234
|
+
fake: async ({}, use, testInfo) => {
|
|
235
|
+
// Seed based on test name for reproducibility
|
|
236
|
+
faker.seed(testInfo.title.length);
|
|
237
|
+
await use(faker);
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Usage
|
|
242
|
+
test("create user with fake data", async ({ page, fake }) => {
|
|
243
|
+
await page.goto("/signup");
|
|
244
|
+
|
|
245
|
+
await page.getByLabel("Name").fill(fake.person.fullName());
|
|
246
|
+
await page.getByLabel("Email").fill(fake.internet.email());
|
|
247
|
+
await page.getByLabel("Password").fill(fake.internet.password());
|
|
248
|
+
|
|
249
|
+
await page.getByRole("button", { name: "Sign Up" }).click();
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Data-Driven Testing
|
|
254
|
+
|
|
255
|
+
### test.each with Arrays
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
const loginScenarios = [
|
|
259
|
+
{ email: "user@example.com", password: "pass123", expected: "Dashboard" },
|
|
260
|
+
{ email: "admin@example.com", password: "admin123", expected: "Admin Panel" },
|
|
261
|
+
{
|
|
262
|
+
email: "invalid@example.com",
|
|
263
|
+
password: "wrong",
|
|
264
|
+
expected: "Invalid credentials",
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
for (const { email, password, expected } of loginScenarios) {
|
|
269
|
+
test(`login with ${email}`, async ({ page }) => {
|
|
270
|
+
await page.goto("/login");
|
|
271
|
+
await page.getByLabel("Email").fill(email);
|
|
272
|
+
await page.getByLabel("Password").fill(password);
|
|
273
|
+
await page.getByRole("button", { name: "Sign In" }).click();
|
|
274
|
+
|
|
275
|
+
await expect(page.getByText(expected)).toBeVisible();
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Parameterized Tests
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// data/checkout-scenarios.ts
|
|
284
|
+
export const checkoutScenarios = [
|
|
285
|
+
{
|
|
286
|
+
name: "standard shipping",
|
|
287
|
+
shipping: "standard",
|
|
288
|
+
expectedDays: "5-7 business days",
|
|
289
|
+
expectedCost: "$5.99",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "express shipping",
|
|
293
|
+
shipping: "express",
|
|
294
|
+
expectedDays: "2-3 business days",
|
|
295
|
+
expectedCost: "$14.99",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: "overnight shipping",
|
|
299
|
+
shipping: "overnight",
|
|
300
|
+
expectedDays: "Next business day",
|
|
301
|
+
expectedCost: "$29.99",
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { checkoutScenarios } from "./data/checkout-scenarios";
|
|
308
|
+
|
|
309
|
+
test.describe("shipping options", () => {
|
|
310
|
+
for (const scenario of checkoutScenarios) {
|
|
311
|
+
test(`checkout with ${scenario.name}`, async ({ page }) => {
|
|
312
|
+
await page.goto("/checkout");
|
|
313
|
+
|
|
314
|
+
await page.getByLabel(scenario.shipping, { exact: false }).check();
|
|
315
|
+
|
|
316
|
+
await expect(page.getByText(scenario.expectedDays)).toBeVisible();
|
|
317
|
+
await expect(page.getByText(scenario.expectedCost)).toBeVisible();
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### CSV/JSON Data Source
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import fs from "fs";
|
|
327
|
+
|
|
328
|
+
interface TestCase {
|
|
329
|
+
input: string;
|
|
330
|
+
expected: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Load test data from JSON
|
|
334
|
+
const testCases: TestCase[] = JSON.parse(
|
|
335
|
+
fs.readFileSync("./data/search-tests.json", "utf-8"),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
test.describe("search functionality", () => {
|
|
339
|
+
for (const { input, expected } of testCases) {
|
|
340
|
+
test(`search for "${input}"`, async ({ page }) => {
|
|
341
|
+
await page.goto("/search");
|
|
342
|
+
await page.getByLabel("Search").fill(input);
|
|
343
|
+
await page.getByLabel("Search").press("Enter");
|
|
344
|
+
|
|
345
|
+
await expect(page.getByText(expected)).toBeVisible();
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Test Data Fixtures
|
|
352
|
+
|
|
353
|
+
### Fixture with Factory
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// fixtures/data.fixture.ts
|
|
357
|
+
import { test as base } from "@playwright/test";
|
|
358
|
+
import { createUser, User } from "../factories/user.factory";
|
|
359
|
+
import { createProduct, Product } from "../factories/product.factory";
|
|
360
|
+
|
|
361
|
+
type DataFixtures = {
|
|
362
|
+
testUser: User;
|
|
363
|
+
testProducts: Product[];
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const test = base.extend<DataFixtures>({
|
|
367
|
+
testUser: async ({}, use) => {
|
|
368
|
+
const user = createUser({ name: "E2E Test User" });
|
|
369
|
+
await use(user);
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
testProducts: async ({}, use) => {
|
|
373
|
+
const products = [
|
|
374
|
+
createProduct({ name: "Test Product 1" }),
|
|
375
|
+
createProduct({ name: "Test Product 2" }),
|
|
376
|
+
createProduct({ name: "Test Product 3" }),
|
|
377
|
+
];
|
|
378
|
+
await use(products);
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Usage
|
|
383
|
+
test("add product to cart", async ({ page, testUser, testProducts }) => {
|
|
384
|
+
// Mock API with test data
|
|
385
|
+
await page.route("**/api/user", (route) => route.fulfill({ json: testUser }));
|
|
386
|
+
await page.route("**/api/products", (route) =>
|
|
387
|
+
route.fulfill({ json: testProducts }),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
await page.goto("/products");
|
|
391
|
+
await expect(page.getByText(testProducts[0].name)).toBeVisible();
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Database Seeding
|
|
396
|
+
|
|
397
|
+
### API-Based Seeding
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// fixtures/seed.fixture.ts
|
|
401
|
+
import { test as base, APIRequestContext } from "@playwright/test";
|
|
402
|
+
import { createUser } from "../factories/user.factory";
|
|
403
|
+
|
|
404
|
+
type SeedFixtures = {
|
|
405
|
+
seedUser: (overrides?: Partial<User>) => Promise<User>;
|
|
406
|
+
cleanupUsers: string[];
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
export const test = base.extend<SeedFixtures>({
|
|
410
|
+
cleanupUsers: [],
|
|
411
|
+
|
|
412
|
+
seedUser: async ({ request, cleanupUsers }, use) => {
|
|
413
|
+
await use(async (overrides = {}) => {
|
|
414
|
+
const userData = createUser(overrides);
|
|
415
|
+
|
|
416
|
+
const response = await request.post("/api/test/users", {
|
|
417
|
+
data: userData,
|
|
418
|
+
});
|
|
419
|
+
const user = await response.json();
|
|
420
|
+
|
|
421
|
+
cleanupUsers.push(user.id);
|
|
422
|
+
return user;
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Cleanup after test
|
|
427
|
+
cleanupUsers: async ({ request }, use) => {
|
|
428
|
+
const userIds: string[] = [];
|
|
429
|
+
await use(userIds);
|
|
430
|
+
|
|
431
|
+
// Delete all created users
|
|
432
|
+
for (const id of userIds) {
|
|
433
|
+
await request.delete(`/api/test/users/${id}`);
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Usage
|
|
439
|
+
test("user profile page", async ({ page, seedUser }) => {
|
|
440
|
+
const user = await seedUser({ name: "John Doe" });
|
|
441
|
+
|
|
442
|
+
await page.goto(`/users/${user.id}`);
|
|
443
|
+
await expect(page.getByText("John Doe")).toBeVisible();
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Transaction Rollback Seeding
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
// fixtures/db.fixture.ts
|
|
451
|
+
export const test = base.extend<{}, { db: DbTransaction }>({
|
|
452
|
+
db: [
|
|
453
|
+
async ({}, use) => {
|
|
454
|
+
const client = await pool.connect();
|
|
455
|
+
await client.query("BEGIN");
|
|
456
|
+
|
|
457
|
+
await use({
|
|
458
|
+
query: (sql: string, params?: any[]) => client.query(sql, params),
|
|
459
|
+
seed: async (table: string, data: object) => {
|
|
460
|
+
const keys = Object.keys(data);
|
|
461
|
+
const values = Object.values(data);
|
|
462
|
+
const placeholders = keys.map((_, i) => `$${i + 1}`);
|
|
463
|
+
|
|
464
|
+
const result = await client.query(
|
|
465
|
+
`INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`,
|
|
466
|
+
values,
|
|
467
|
+
);
|
|
468
|
+
return result.rows[0];
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
await client.query("ROLLBACK");
|
|
473
|
+
client.release();
|
|
474
|
+
},
|
|
475
|
+
{ scope: "test" },
|
|
476
|
+
],
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Anti-Patterns to Avoid
|
|
481
|
+
|
|
482
|
+
| Anti-Pattern | Problem | Solution |
|
|
483
|
+
| ------------------------------- | ------------------------------- | -------------------------- |
|
|
484
|
+
| Hardcoded test data | Brittle, repetitive | Use factories |
|
|
485
|
+
| Random data without seed | Non-reproducible failures | Seed faker per test |
|
|
486
|
+
| Shared mutable test data | Tests interfere with each other | Create fresh data per test |
|
|
487
|
+
| Manual data creation everywhere | Duplication, maintenance burden | Centralize in factories |
|
|
488
|
+
|
|
489
|
+
## Related References
|
|
490
|
+
|
|
491
|
+
- **Fixtures**: See [fixtures-hooks.md](fixtures-hooks.md) for fixture patterns
|
|
492
|
+
- **API Testing**: See [test-suite-structure.md](test-suite-structure.md) for API mocking
|