@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,434 @@
|
|
|
1
|
+
# Global Setup & Teardown
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Global Setup](#global-setup)
|
|
6
|
+
2. [Global Teardown](#global-teardown)
|
|
7
|
+
3. [Database Patterns](#database-patterns)
|
|
8
|
+
4. [Environment Provisioning](#environment-provisioning)
|
|
9
|
+
5. [Setup Projects vs Global Setup](#setup-projects-vs-global-setup)
|
|
10
|
+
6. [Parallel Execution Caveats](#parallel-execution-caveats)
|
|
11
|
+
|
|
12
|
+
## Global Setup
|
|
13
|
+
|
|
14
|
+
### Basic Global Setup
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// global-setup.ts
|
|
18
|
+
import { FullConfig } from "@playwright/test";
|
|
19
|
+
|
|
20
|
+
async function globalSetup(config: FullConfig) {
|
|
21
|
+
console.log("Running global setup...");
|
|
22
|
+
// Perform one-time setup: start services, run migrations, etc.
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default globalSetup;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Configure Global Setup
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// playwright.config.ts
|
|
32
|
+
import { defineConfig } from "@playwright/test";
|
|
33
|
+
|
|
34
|
+
export default defineConfig({
|
|
35
|
+
globalSetup: require.resolve("./global-setup"),
|
|
36
|
+
globalTeardown: require.resolve("./global-teardown"),
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **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.
|
|
41
|
+
|
|
42
|
+
### Global Setup with Return Value
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// global-setup.ts
|
|
46
|
+
async function globalSetup(config: FullConfig): Promise<() => Promise<void>> {
|
|
47
|
+
const server = await startTestServer();
|
|
48
|
+
|
|
49
|
+
// Return cleanup function (alternative to globalTeardown)
|
|
50
|
+
return async () => {
|
|
51
|
+
await server.stop();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default globalSetup;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Access Config in Global Setup
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// global-setup.ts
|
|
62
|
+
import { FullConfig } from "@playwright/test";
|
|
63
|
+
|
|
64
|
+
async function globalSetup(config: FullConfig) {
|
|
65
|
+
const { baseURL } = config.projects[0].use;
|
|
66
|
+
console.log(`Setting up for ${baseURL}`);
|
|
67
|
+
|
|
68
|
+
// Access custom config
|
|
69
|
+
const workers = config.workers;
|
|
70
|
+
const timeout = config.timeout;
|
|
71
|
+
|
|
72
|
+
// Access environment
|
|
73
|
+
const isCI = !!process.env.CI;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default globalSetup;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Global Teardown
|
|
80
|
+
|
|
81
|
+
### Basic Global Teardown
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// global-teardown.ts
|
|
85
|
+
import { FullConfig } from "@playwright/test";
|
|
86
|
+
import fs from "fs";
|
|
87
|
+
|
|
88
|
+
async function globalTeardown(config: FullConfig) {
|
|
89
|
+
console.log("Running global teardown...");
|
|
90
|
+
|
|
91
|
+
// Clean up auth files
|
|
92
|
+
if (fs.existsSync(".auth")) {
|
|
93
|
+
fs.rmSync(".auth", { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Clean up test data
|
|
97
|
+
await cleanupTestDatabase();
|
|
98
|
+
|
|
99
|
+
// Stop services
|
|
100
|
+
await stopTestServices();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default globalTeardown;
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Conditional Teardown
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// global-teardown.ts
|
|
110
|
+
async function globalTeardown(config: FullConfig) {
|
|
111
|
+
// Skip cleanup in CI (containers are discarded anyway)
|
|
112
|
+
if (process.env.CI) {
|
|
113
|
+
console.log("Skipping teardown in CI");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Local cleanup
|
|
118
|
+
await cleanupLocalTestData();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default globalTeardown;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Database Patterns
|
|
125
|
+
|
|
126
|
+
This section covers **one-time database setup** (migrations, snapshots, per-worker databases). For related topics:
|
|
127
|
+
|
|
128
|
+
- **Per-test database fixtures** (isolation, transaction rollback): See [fixtures-hooks.md](fixtures-hooks.md#database-fixtures)
|
|
129
|
+
- **Test data factories** (builders, Faker): See [test-data.md](test-data.md)
|
|
130
|
+
|
|
131
|
+
### Database Migration in Setup
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// global-setup.ts
|
|
135
|
+
import { execSync } from "child_process";
|
|
136
|
+
|
|
137
|
+
async function globalSetup() {
|
|
138
|
+
console.log("Running database migrations...");
|
|
139
|
+
|
|
140
|
+
// Run migrations
|
|
141
|
+
execSync("npx prisma migrate deploy", { stdio: "inherit" });
|
|
142
|
+
|
|
143
|
+
// Seed test data
|
|
144
|
+
execSync("npx prisma db seed", { stdio: "inherit" });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default globalSetup;
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Database Snapshot Pattern
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// global-setup.ts
|
|
154
|
+
import { execSync } from "child_process";
|
|
155
|
+
import fs from "fs";
|
|
156
|
+
|
|
157
|
+
const SNAPSHOT_PATH = "./test-db-snapshot.sql";
|
|
158
|
+
|
|
159
|
+
async function globalSetup() {
|
|
160
|
+
// Check if snapshot exists
|
|
161
|
+
if (fs.existsSync(SNAPSHOT_PATH)) {
|
|
162
|
+
console.log("Restoring database from snapshot...");
|
|
163
|
+
execSync(`psql $DATABASE_URL < ${SNAPSHOT_PATH}`, { stdio: "inherit" });
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// First run: migrate and create snapshot
|
|
168
|
+
console.log("Creating database snapshot...");
|
|
169
|
+
execSync("npx prisma migrate deploy", { stdio: "inherit" });
|
|
170
|
+
execSync("npx prisma db seed", { stdio: "inherit" });
|
|
171
|
+
execSync(`pg_dump $DATABASE_URL > ${SNAPSHOT_PATH}`, { stdio: "inherit" });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default globalSetup;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Test Database per Worker
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// global-setup.ts
|
|
181
|
+
async function globalSetup(config: FullConfig) {
|
|
182
|
+
const workerCount = config.workers || 1;
|
|
183
|
+
|
|
184
|
+
// Create a database for each worker
|
|
185
|
+
for (let i = 0; i < workerCount; i++) {
|
|
186
|
+
const dbName = `test_db_worker_${i}`;
|
|
187
|
+
await createDatabase(dbName);
|
|
188
|
+
await runMigrations(dbName);
|
|
189
|
+
await seedDatabase(dbName);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// global-teardown.ts
|
|
194
|
+
async function globalTeardown(config: FullConfig) {
|
|
195
|
+
const workerCount = config.workers || 1;
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < workerCount; i++) {
|
|
198
|
+
await dropDatabase(`test_db_worker_${i}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Environment Provisioning
|
|
204
|
+
|
|
205
|
+
### Start Services in Setup
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// global-setup.ts
|
|
209
|
+
import { execSync, spawn } from "child_process";
|
|
210
|
+
|
|
211
|
+
let serverProcess: any;
|
|
212
|
+
|
|
213
|
+
async function globalSetup() {
|
|
214
|
+
// Start backend server
|
|
215
|
+
serverProcess = spawn("npm", ["run", "start:test"], {
|
|
216
|
+
stdio: "pipe",
|
|
217
|
+
detached: true,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Wait for server to be ready
|
|
221
|
+
await waitForServer("http://localhost:3000/health", 30000);
|
|
222
|
+
|
|
223
|
+
// Store PID for teardown
|
|
224
|
+
process.env.SERVER_PID = serverProcess.pid.toString();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function waitForServer(url: string, timeout: number) {
|
|
228
|
+
const start = Date.now();
|
|
229
|
+
|
|
230
|
+
while (Date.now() - start < timeout) {
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(url);
|
|
233
|
+
if (response.ok) return;
|
|
234
|
+
} catch {
|
|
235
|
+
// Server not ready yet
|
|
236
|
+
}
|
|
237
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(`Server did not start within ${timeout}ms`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default globalSetup;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Docker Compose Setup
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// global-setup.ts
|
|
250
|
+
import { execSync } from "child_process";
|
|
251
|
+
|
|
252
|
+
async function globalSetup() {
|
|
253
|
+
console.log("Starting Docker services...");
|
|
254
|
+
|
|
255
|
+
execSync("docker-compose -f docker-compose.test.yml up -d", {
|
|
256
|
+
stdio: "inherit",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Wait for services to be healthy
|
|
260
|
+
execSync("docker-compose -f docker-compose.test.yml exec -T db pg_isready", {
|
|
261
|
+
stdio: "inherit",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default globalSetup;
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// global-teardown.ts
|
|
270
|
+
import { execSync } from "child_process";
|
|
271
|
+
|
|
272
|
+
async function globalTeardown() {
|
|
273
|
+
console.log("Stopping Docker services...");
|
|
274
|
+
|
|
275
|
+
execSync("docker-compose -f docker-compose.test.yml down -v", {
|
|
276
|
+
stdio: "inherit",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default globalTeardown;
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Environment Variables Setup
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// global-setup.ts
|
|
287
|
+
import dotenv from "dotenv";
|
|
288
|
+
import path from "path";
|
|
289
|
+
|
|
290
|
+
async function globalSetup() {
|
|
291
|
+
// Load test-specific environment
|
|
292
|
+
const envFile = process.env.CI ? ".env.ci" : ".env.test";
|
|
293
|
+
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
|
|
294
|
+
|
|
295
|
+
// Validate required variables
|
|
296
|
+
const required = ["DATABASE_URL", "API_KEY", "TEST_EMAIL"];
|
|
297
|
+
for (const key of required) {
|
|
298
|
+
if (!process.env[key]) {
|
|
299
|
+
throw new Error(`Missing required environment variable: ${key}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default globalSetup;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Setup Projects vs Global Setup
|
|
308
|
+
|
|
309
|
+
### When to Use Each
|
|
310
|
+
|
|
311
|
+
| Use Global Setup | Use Setup Projects |
|
|
312
|
+
| ------------------------------------- | ---------------------------------------- |
|
|
313
|
+
| One-time setup (migrations, services) | Per-project setup (auth states) |
|
|
314
|
+
| No access to Playwright fixtures | Need page, request fixtures |
|
|
315
|
+
| Runs once before all projects | Can run per-project or have dependencies |
|
|
316
|
+
| Shared across all workers | Can be parallelized |
|
|
317
|
+
|
|
318
|
+
### Setup Project Pattern
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// playwright.config.ts
|
|
322
|
+
export default defineConfig({
|
|
323
|
+
projects: [
|
|
324
|
+
// Setup project
|
|
325
|
+
{
|
|
326
|
+
name: "setup",
|
|
327
|
+
testMatch: /.*\.setup\.ts/,
|
|
328
|
+
},
|
|
329
|
+
// Test projects depend on setup
|
|
330
|
+
{
|
|
331
|
+
name: "chromium",
|
|
332
|
+
use: { ...devices["Desktop Chrome"] },
|
|
333
|
+
dependencies: ["setup"],
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "firefox",
|
|
337
|
+
use: { ...devices["Desktop Firefox"] },
|
|
338
|
+
dependencies: ["setup"],
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
> **For complete authentication setup patterns**, see [fixtures-hooks.md](fixtures-hooks.md#authentication-patterns).
|
|
345
|
+
|
|
346
|
+
### Combining Both
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// playwright.config.ts
|
|
350
|
+
export default defineConfig({
|
|
351
|
+
// Global: Start services, run migrations
|
|
352
|
+
globalSetup: require.resolve("./global-setup"),
|
|
353
|
+
globalTeardown: require.resolve("./global-teardown"),
|
|
354
|
+
|
|
355
|
+
projects: [
|
|
356
|
+
// Setup project: Create auth states
|
|
357
|
+
{ name: "setup", testMatch: /.*\.setup\.ts/ },
|
|
358
|
+
{
|
|
359
|
+
name: "chromium",
|
|
360
|
+
use: {
|
|
361
|
+
...devices["Desktop Chrome"],
|
|
362
|
+
storageState: ".auth/user.json",
|
|
363
|
+
},
|
|
364
|
+
dependencies: ["setup"],
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Parallel Execution Caveats
|
|
371
|
+
|
|
372
|
+
### Understanding Global Setup Execution
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
376
|
+
│ globalSetup runs ONCE │
|
|
377
|
+
│ ↓ │
|
|
378
|
+
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
379
|
+
│ │ Worker 1│ │ Worker 2│ │ Worker 3│ │ Worker 4│ │
|
|
380
|
+
│ │ tests │ │ tests │ │ tests │ │ tests │ │
|
|
381
|
+
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
|
382
|
+
│ ↓ │
|
|
383
|
+
│ globalTeardown runs ONCE │
|
|
384
|
+
└─────────────────────────────────────────────────────────────┘
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Key implications:**
|
|
388
|
+
|
|
389
|
+
- Global setup has **no access** to Playwright fixtures (`page`, `request`, `context`)
|
|
390
|
+
- State created in global setup is **shared** across all workers
|
|
391
|
+
- If tests **modify** shared state, they may conflict with parallel workers
|
|
392
|
+
- Global setup **cannot** react to individual test needs
|
|
393
|
+
|
|
394
|
+
### When to Prefer Worker-Scoped Fixtures
|
|
395
|
+
|
|
396
|
+
Use **worker-scoped fixtures** instead of globalSetup when:
|
|
397
|
+
|
|
398
|
+
| Scenario | Why Fixtures Are Better |
|
|
399
|
+
| ------------------------------------ | ---------------------------------------------------- |
|
|
400
|
+
| Each worker needs isolated resources | Fixtures can create per-worker databases, servers |
|
|
401
|
+
| Setup needs Playwright APIs | Fixtures have access to `page`, `request`, `browser` |
|
|
402
|
+
| Setup depends on test configuration | Fixtures receive test context and options |
|
|
403
|
+
| Resources need cleanup per worker | Worker fixtures auto-cleanup when worker exits |
|
|
404
|
+
|
|
405
|
+
### Common Parallel Pitfall
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// ❌ BAD: Global setup creates ONE user, all workers fight over it
|
|
409
|
+
async function globalSetup() {
|
|
410
|
+
await createUser({ email: "test@example.com" }); // Shared!
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ✅ GOOD: Each worker gets its own user via worker-scoped fixture
|
|
414
|
+
// Uses workerInfo.workerIndex to create unique data per worker
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
> **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).
|
|
418
|
+
|
|
419
|
+
## Anti-Patterns to Avoid
|
|
420
|
+
|
|
421
|
+
| Anti-Pattern | Problem | Solution |
|
|
422
|
+
| ------------------------------ | -------------------------------- | ------------------------------------------ |
|
|
423
|
+
| Heavy setup in globalSetup | Slow test startup | Use setup projects for parallelizable work |
|
|
424
|
+
| Not cleaning up in teardown | Leaks resources, flaky CI | Always clean up or use containers |
|
|
425
|
+
| Hardcoded URLs in setup | Breaks in different environments | Use config.projects[0].use.baseURL |
|
|
426
|
+
| No timeout on service wait | Hangs forever if service fails | Add timeout with clear error |
|
|
427
|
+
| Shared mutable state | Race conditions in parallel | Use worker-scoped fixtures for isolation |
|
|
428
|
+
| Global setup for per-test data | Tests conflict | Use test-scoped fixtures |
|
|
429
|
+
|
|
430
|
+
## Related References
|
|
431
|
+
|
|
432
|
+
- **Fixtures & Auth**: See [fixtures-hooks.md](fixtures-hooks.md) for worker-scoped fixtures and auth patterns
|
|
433
|
+
- **CI/CD**: See [ci-cd.md](../infrastructure-ci-cd/ci-cd.md) for CI setup patterns
|
|
434
|
+
- **Projects**: See [projects-dependencies.md](projects-dependencies.md) for project configuration
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Locator Strategies
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Priority Order](#priority-order)
|
|
6
|
+
2. [User-Facing Locators](#user-facing-locators)
|
|
7
|
+
3. [Filtering & Chaining](#filtering--chaining)
|
|
8
|
+
4. [Dynamic Content](#dynamic-content)
|
|
9
|
+
5. [Shadow DOM](#shadow-dom)
|
|
10
|
+
6. [Iframes](#iframes)
|
|
11
|
+
|
|
12
|
+
## Priority Order
|
|
13
|
+
|
|
14
|
+
Use locators in this order of preference:
|
|
15
|
+
|
|
16
|
+
1. **Role-based** (most resilient): `getByRole`
|
|
17
|
+
2. **Label-based**: `getByLabel`, `getByPlaceholder`
|
|
18
|
+
3. **Text-based**: `getByText`, `getByTitle`
|
|
19
|
+
4. **Test IDs** (when semantic locators aren't possible): `getByTestId`
|
|
20
|
+
5. **CSS/XPath** (last resort): `locator('css=...')`, `locator('xpath=...')`
|
|
21
|
+
|
|
22
|
+
## User-Facing Locators
|
|
23
|
+
|
|
24
|
+
### getByRole
|
|
25
|
+
|
|
26
|
+
Most robust approach - matches how users and assistive technology perceive the page.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Buttons
|
|
30
|
+
page.getByRole("button", { name: "Submit", exact: true }); // exact accessible name
|
|
31
|
+
page.getByRole("button", { name: /submit/i }); // flexible case-insensitive match
|
|
32
|
+
|
|
33
|
+
// Links
|
|
34
|
+
page.getByRole("link", { name: "Home" });
|
|
35
|
+
|
|
36
|
+
// Form elements
|
|
37
|
+
page.getByRole("textbox", { name: "Email" });
|
|
38
|
+
page.getByRole("checkbox", { name: "Remember me" });
|
|
39
|
+
page.getByRole("combobox", { name: "Country" });
|
|
40
|
+
page.getByRole("radio", { name: "Option A" });
|
|
41
|
+
|
|
42
|
+
// Headings
|
|
43
|
+
page.getByRole("heading", { name: "Welcome", level: 1 });
|
|
44
|
+
|
|
45
|
+
// Lists & items
|
|
46
|
+
page.getByRole("list").getByRole("listitem");
|
|
47
|
+
|
|
48
|
+
// Navigation & regions
|
|
49
|
+
page.getByRole("navigation");
|
|
50
|
+
page.getByRole("main");
|
|
51
|
+
page.getByRole("dialog");
|
|
52
|
+
page.getByRole("alert");
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### getByLabel
|
|
56
|
+
|
|
57
|
+
For form elements with associated labels.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Input with <label for="email">
|
|
61
|
+
page.getByLabel("Email address");
|
|
62
|
+
|
|
63
|
+
// Input with aria-label
|
|
64
|
+
page.getByLabel("Search");
|
|
65
|
+
|
|
66
|
+
// Exact match
|
|
67
|
+
page.getByLabel("Email", { exact: true });
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### getByPlaceholder
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
page.getByPlaceholder("Enter your email");
|
|
74
|
+
page.getByPlaceholder(/email/i);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### getByText
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Partial match (default)
|
|
81
|
+
page.getByText("Welcome");
|
|
82
|
+
|
|
83
|
+
// Exact match
|
|
84
|
+
page.getByText("Welcome to our site", { exact: true });
|
|
85
|
+
|
|
86
|
+
// Regex
|
|
87
|
+
page.getByText(/welcome/i);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### getByTestId
|
|
91
|
+
|
|
92
|
+
Configure custom test ID attribute in `playwright.config.ts`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
use: {
|
|
96
|
+
testIdAttribute: "data-testid"; // default
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Usage:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// HTML: <button data-testid="submit-btn">Submit</button>
|
|
104
|
+
page.getByTestId("submit-btn");
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Filtering & Chaining
|
|
108
|
+
|
|
109
|
+
### filter()
|
|
110
|
+
|
|
111
|
+
Narrow down locators:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Filter by text
|
|
115
|
+
page.getByRole("listitem").filter({ hasText: "Product" });
|
|
116
|
+
|
|
117
|
+
// Filter by NOT having text
|
|
118
|
+
page.getByRole("listitem").filter({ hasNotText: "Out of stock" });
|
|
119
|
+
|
|
120
|
+
// Filter by child locator
|
|
121
|
+
page.getByRole("listitem").filter({
|
|
122
|
+
has: page.getByRole("button", { name: "Buy" }),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Combine filters
|
|
126
|
+
page
|
|
127
|
+
.getByRole("listitem")
|
|
128
|
+
.filter({ hasText: "Product" })
|
|
129
|
+
.filter({ has: page.getByText("$9.99") });
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Chaining
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Navigate down the DOM tree
|
|
136
|
+
page.getByRole("article").getByRole("heading");
|
|
137
|
+
|
|
138
|
+
// Get parent/ancestor
|
|
139
|
+
page.getByText("Child").locator("..");
|
|
140
|
+
page.getByText("Child").locator("xpath=ancestor::article");
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### nth() and first()/last()
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
page.getByRole("listitem").first();
|
|
147
|
+
page.getByRole("listitem").last();
|
|
148
|
+
page.getByRole("listitem").nth(2); // 0-indexed
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Dynamic Content
|
|
152
|
+
|
|
153
|
+
### Waiting for Elements
|
|
154
|
+
|
|
155
|
+
Locators auto-wait for actionability by default. For explicit state waiting:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
await page.getByRole("button").waitFor({ state: "visible" });
|
|
159
|
+
await page.getByText("Loading").waitFor({ state: "hidden" });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> **For comprehensive waiting strategies** (element state, navigation, network, polling with `toPass()`), see [assertions-waiting.md](assertions-waiting.md#waiting-strategies).
|
|
163
|
+
|
|
164
|
+
### Lists with Dynamic Items
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Wait for specific count
|
|
168
|
+
await expect(page.getByRole("listitem")).toHaveCount(5);
|
|
169
|
+
|
|
170
|
+
// Get all matching elements
|
|
171
|
+
const items = await page.getByRole("listitem").all();
|
|
172
|
+
for (const item of items) {
|
|
173
|
+
await expect(item).toBeVisible();
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Shadow DOM
|
|
178
|
+
|
|
179
|
+
Playwright pierces shadow DOM by default:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Automatically finds elements inside shadow roots
|
|
183
|
+
page.getByRole("button", { name: "Shadow Button" });
|
|
184
|
+
|
|
185
|
+
// Explicit shadow DOM traversal (if needed)
|
|
186
|
+
page.locator("my-component").locator("internal:shadow=button");
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Iframes
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// By frame name or URL
|
|
193
|
+
const frame = page.frameLocator('iframe[name="content"]');
|
|
194
|
+
await frame.getByRole("button").click();
|
|
195
|
+
|
|
196
|
+
// By index
|
|
197
|
+
const frame = page.frameLocator("iframe").first();
|
|
198
|
+
|
|
199
|
+
// Nested iframes
|
|
200
|
+
const nestedFrame = page.frameLocator("#outer").frameLocator("#inner");
|
|
201
|
+
await nestedFrame.getByText("Content").click();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Debugging Locators
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
// Highlight element in headed mode
|
|
208
|
+
await page.getByRole("button").highlight();
|
|
209
|
+
|
|
210
|
+
// Count matches
|
|
211
|
+
const count = await page.getByRole("listitem").count();
|
|
212
|
+
|
|
213
|
+
// Check if exists without waiting
|
|
214
|
+
const exists = (await page.getByRole("button").count()) > 0;
|
|
215
|
+
|
|
216
|
+
// Use Playwright Inspector
|
|
217
|
+
// PWDEBUG=1 npx playwright test
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Common Issues & Solutions
|
|
221
|
+
|
|
222
|
+
| Issue | Solution |
|
|
223
|
+
| ----------------------- | ------------------------------------------------ |
|
|
224
|
+
| Multiple elements match | Add filters or use `nth()`, `first()`, `last()` |
|
|
225
|
+
| Element not found | Check visibility, wait for load, verify selector |
|
|
226
|
+
| Stale element | Locators are lazy; re-query if DOM changes |
|
|
227
|
+
| Dynamic IDs | Use stable attributes like role, text, test-id |
|
|
228
|
+
| Hidden elements | Use `{ force: true }` only when necessary |
|
|
229
|
+
|
|
230
|
+
## Anti-Patterns to Avoid
|
|
231
|
+
|
|
232
|
+
| Anti-Pattern | Problem | Solution |
|
|
233
|
+
| --------------------------------- | --------------------------------- | ------------------------------------------------- |
|
|
234
|
+
| `page.locator('.btn-primary')` | Brittle, implementation-dependent | `page.getByRole('button', { name: 'Submit' })` |
|
|
235
|
+
| `page.locator('#dynamic-id-123')` | Breaks when IDs change | Use stable attributes like role, text, or test-id |
|
|
236
|
+
| Testing implementation details | Breaks on refactoring | Test user-visible behavior |
|
|
237
|
+
|
|
238
|
+
## Related References
|
|
239
|
+
|
|
240
|
+
- **Debugging selector issues**: See [debugging.md](../debugging/debugging.md) for troubleshooting
|
|
241
|
+
- **Waiting for elements**: See [assertions-waiting.md](assertions-waiting.md) for waiting strategies
|
|
242
|
+
- **Using in Page Objects**: See [page-object-model.md](page-object-model.md) for organizing locators
|