@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,361 @@
|
|
|
1
|
+
# Assertions & Waiting
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Web-First Assertions](#web-first-assertions)
|
|
6
|
+
2. [Generic Assertions](#generic-assertions)
|
|
7
|
+
3. [Soft Assertions](#soft-assertions)
|
|
8
|
+
4. [Waiting Strategies](#waiting-strategies)
|
|
9
|
+
5. [Polling & Retrying](#polling--retrying)
|
|
10
|
+
6. [Custom Matchers](#custom-matchers)
|
|
11
|
+
|
|
12
|
+
## Web-First Assertions
|
|
13
|
+
|
|
14
|
+
Auto-retry until condition is met or timeout. Always prefer these over generic assertions.
|
|
15
|
+
|
|
16
|
+
### Locator Assertions
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { expect } from "@playwright/test";
|
|
20
|
+
|
|
21
|
+
// Visibility
|
|
22
|
+
await expect(page.getByRole("button")).toBeVisible();
|
|
23
|
+
await expect(page.getByRole("button")).toBeHidden();
|
|
24
|
+
await expect(page.getByRole("button")).not.toBeVisible();
|
|
25
|
+
|
|
26
|
+
// Enabled/Disabled
|
|
27
|
+
await expect(page.getByRole("button")).toBeEnabled();
|
|
28
|
+
await expect(page.getByRole("button")).toBeDisabled();
|
|
29
|
+
|
|
30
|
+
// Text content
|
|
31
|
+
await expect(page.getByRole("heading")).toHaveText("Welcome");
|
|
32
|
+
await expect(page.getByRole("heading")).toHaveText(/welcome/i);
|
|
33
|
+
await expect(page.getByRole("heading")).toContainText("Welcome");
|
|
34
|
+
|
|
35
|
+
// Count
|
|
36
|
+
await expect(page.getByRole("listitem")).toHaveCount(5);
|
|
37
|
+
|
|
38
|
+
// Attributes
|
|
39
|
+
await expect(page.getByRole("link")).toHaveAttribute("href", "/home");
|
|
40
|
+
await expect(page.getByRole("img")).toHaveAttribute("alt", /logo/i);
|
|
41
|
+
|
|
42
|
+
// CSS
|
|
43
|
+
await expect(page.getByRole("button")).toHaveClass(/primary/);
|
|
44
|
+
await expect(page.getByRole("button")).toHaveCSS("color", "rgb(0, 0, 255)");
|
|
45
|
+
|
|
46
|
+
// Input values
|
|
47
|
+
await expect(page.getByLabel("Email")).toHaveValue("user@example.com");
|
|
48
|
+
await expect(page.getByLabel("Email")).toBeEmpty();
|
|
49
|
+
|
|
50
|
+
// Focus
|
|
51
|
+
await expect(page.getByLabel("Email")).toBeFocused();
|
|
52
|
+
|
|
53
|
+
// Checked state
|
|
54
|
+
await expect(page.getByRole("checkbox")).toBeChecked();
|
|
55
|
+
await expect(page.getByRole("checkbox")).not.toBeChecked();
|
|
56
|
+
|
|
57
|
+
// Editable state
|
|
58
|
+
await expect(page.getByLabel("Name")).toBeEditable();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Page Assertions
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// URL
|
|
65
|
+
await expect(page).toHaveURL("/dashboard");
|
|
66
|
+
await expect(page).toHaveURL(/\/dashboard/);
|
|
67
|
+
|
|
68
|
+
// Title
|
|
69
|
+
await expect(page).toHaveTitle("Dashboard - MyApp");
|
|
70
|
+
await expect(page).toHaveTitle(/dashboard/i);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Response Assertions
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const response = await page.request.get("/api/users");
|
|
77
|
+
await expect(response).toBeOK();
|
|
78
|
+
await expect(response).not.toBeOK();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Generic Assertions
|
|
82
|
+
|
|
83
|
+
Use for non-UI values. Do NOT retry - execute immediately.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Equality
|
|
87
|
+
expect(value).toBe(5);
|
|
88
|
+
expect(object).toEqual({ name: "Test" });
|
|
89
|
+
expect(array).toContain("item");
|
|
90
|
+
|
|
91
|
+
// Truthiness
|
|
92
|
+
expect(value).toBeTruthy();
|
|
93
|
+
expect(value).toBeFalsy();
|
|
94
|
+
expect(value).toBeNull();
|
|
95
|
+
expect(value).toBeUndefined();
|
|
96
|
+
expect(value).toBeDefined();
|
|
97
|
+
|
|
98
|
+
// Numbers
|
|
99
|
+
expect(value).toBeGreaterThan(5);
|
|
100
|
+
expect(value).toBeLessThanOrEqual(10);
|
|
101
|
+
expect(value).toBeCloseTo(5.5, 1);
|
|
102
|
+
|
|
103
|
+
// Strings
|
|
104
|
+
expect(string).toMatch(/pattern/);
|
|
105
|
+
expect(string).toContain("substring");
|
|
106
|
+
|
|
107
|
+
// Arrays/Objects
|
|
108
|
+
expect(array).toHaveLength(3);
|
|
109
|
+
expect(object).toHaveProperty("key", "value");
|
|
110
|
+
|
|
111
|
+
// Exceptions
|
|
112
|
+
expect(() => fn()).toThrow();
|
|
113
|
+
expect(() => fn()).toThrow("error message");
|
|
114
|
+
await expect(asyncFn()).rejects.toThrow();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Soft Assertions
|
|
118
|
+
|
|
119
|
+
Continue test execution after failure, report all failures at end.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
test("check multiple elements", async ({ page }) => {
|
|
123
|
+
await page.goto("/dashboard");
|
|
124
|
+
|
|
125
|
+
// Won't stop on first failure
|
|
126
|
+
await expect.soft(page.getByRole("heading")).toHaveText("Dashboard");
|
|
127
|
+
await expect.soft(page.getByRole("button", { name: "Save" })).toBeEnabled();
|
|
128
|
+
await expect.soft(page.getByText("Welcome")).toBeVisible();
|
|
129
|
+
|
|
130
|
+
// Test continues; all failures reported at end
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Soft Assertions with Early Exit
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
test("check form", async ({ page }) => {
|
|
138
|
+
await expect.soft(page.getByRole("form")).toBeVisible();
|
|
139
|
+
|
|
140
|
+
// Exit early if form not visible (pointless to check fields)
|
|
141
|
+
if (expect.soft.hasFailures()) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await expect.soft(page.getByLabel("Name")).toBeVisible();
|
|
146
|
+
await expect.soft(page.getByLabel("Email")).toBeVisible();
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Waiting Strategies
|
|
151
|
+
|
|
152
|
+
### Auto-Waiting (Default)
|
|
153
|
+
|
|
154
|
+
Actions automatically wait for:
|
|
155
|
+
|
|
156
|
+
- Element to be attached to DOM
|
|
157
|
+
- Element to be visible
|
|
158
|
+
- Element to be stable (no animations)
|
|
159
|
+
- Element to be enabled
|
|
160
|
+
- Element to receive events
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// These auto-wait
|
|
164
|
+
await page.click("button");
|
|
165
|
+
await page.fill("input", "text");
|
|
166
|
+
await page.getByRole("button").click();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Wait for Navigation
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
// Wait for URL change
|
|
173
|
+
await page.waitForURL("/dashboard");
|
|
174
|
+
await page.waitForURL(/\/dashboard/);
|
|
175
|
+
|
|
176
|
+
// Wait for navigation after action
|
|
177
|
+
await Promise.all([
|
|
178
|
+
page.waitForURL("**/dashboard"),
|
|
179
|
+
page.click('a[href="/dashboard"]'),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
// Or without Promise.all
|
|
183
|
+
const urlPromise = page.waitForURL("**/dashboard");
|
|
184
|
+
await page.click("a");
|
|
185
|
+
await urlPromise;
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Wait for Network
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// Wait for specific response
|
|
192
|
+
const responsePromise = page.waitForResponse("**/api/users");
|
|
193
|
+
await page.click("button");
|
|
194
|
+
const response = await responsePromise;
|
|
195
|
+
expect(response.status()).toBe(200);
|
|
196
|
+
|
|
197
|
+
// Wait for request
|
|
198
|
+
const requestPromise = page.waitForRequest("**/api/submit");
|
|
199
|
+
await page.click("button");
|
|
200
|
+
const request = await requestPromise;
|
|
201
|
+
|
|
202
|
+
// Wait for no network activity
|
|
203
|
+
await page.waitForLoadState("networkidle");
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Wait for Element State
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Wait for element to appear
|
|
210
|
+
await page.getByRole("dialog").waitFor({ state: "visible" });
|
|
211
|
+
|
|
212
|
+
// Wait for element to disappear
|
|
213
|
+
await page.getByText("Loading...").waitFor({ state: "hidden" });
|
|
214
|
+
|
|
215
|
+
// Wait for element to be attached
|
|
216
|
+
await page.getByTestId("result").waitFor({ state: "attached" });
|
|
217
|
+
|
|
218
|
+
// Wait for element to be detached
|
|
219
|
+
await page.getByTestId("modal").waitFor({ state: "detached" });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Wait for Function
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Wait for arbitrary condition
|
|
226
|
+
await page.waitForFunction(() => {
|
|
227
|
+
return document.querySelector(".loaded") !== null;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// With arguments
|
|
231
|
+
await page.waitForFunction(
|
|
232
|
+
(selector) => document.querySelector(selector)?.textContent === "Ready",
|
|
233
|
+
".status",
|
|
234
|
+
);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Polling & Retrying
|
|
238
|
+
|
|
239
|
+
### toPass() for Polling
|
|
240
|
+
|
|
241
|
+
Retry until block passes or times out:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
await expect(async () => {
|
|
245
|
+
const response = await page.request.get("/api/status");
|
|
246
|
+
expect(response.status()).toBe(200);
|
|
247
|
+
|
|
248
|
+
const data = await response.json();
|
|
249
|
+
expect(data.ready).toBe(true);
|
|
250
|
+
}).toPass({
|
|
251
|
+
intervals: [1000, 2000, 5000], // Retry intervals
|
|
252
|
+
timeout: 30000,
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### expect.poll()
|
|
257
|
+
|
|
258
|
+
Poll a function until assertion passes:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// Poll API until condition met
|
|
262
|
+
await expect
|
|
263
|
+
.poll(
|
|
264
|
+
async () => {
|
|
265
|
+
const response = await page.request.get("/api/job/123");
|
|
266
|
+
return (await response.json()).status;
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
intervals: [1000, 2000, 5000],
|
|
270
|
+
timeout: 30000,
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
.toBe("completed");
|
|
274
|
+
|
|
275
|
+
// Poll DOM value
|
|
276
|
+
await expect.poll(() => page.getByTestId("counter").textContent()).toBe("10");
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Custom Matchers
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// playwright.config.ts or fixtures
|
|
283
|
+
import { expect } from "@playwright/test";
|
|
284
|
+
|
|
285
|
+
expect.extend({
|
|
286
|
+
async toHaveDataLoaded(page: Page) {
|
|
287
|
+
const locator = page.getByTestId("data-container");
|
|
288
|
+
let pass = false;
|
|
289
|
+
let message = "";
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await expect(locator).toBeVisible();
|
|
293
|
+
await expect(locator).not.toContainText("Loading");
|
|
294
|
+
pass = true;
|
|
295
|
+
} catch (e) {
|
|
296
|
+
message = `Expected data to be loaded but found loading state`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { pass, message: () => message };
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Extend TypeScript types
|
|
304
|
+
declare global {
|
|
305
|
+
namespace PlaywrightTest {
|
|
306
|
+
interface Matchers<R> {
|
|
307
|
+
toHaveDataLoaded(): Promise<R>;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Usage
|
|
313
|
+
await expect(page).toHaveDataLoaded();
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Timeouts
|
|
317
|
+
|
|
318
|
+
### Configure Timeouts
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// playwright.config.ts
|
|
322
|
+
export default defineConfig({
|
|
323
|
+
timeout: 30000, // Test timeout
|
|
324
|
+
expect: {
|
|
325
|
+
timeout: 5000, // Assertion timeout
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Per-test timeout
|
|
330
|
+
test("long test", async ({ page }) => {
|
|
331
|
+
test.setTimeout(60000);
|
|
332
|
+
// ...
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Per-assertion timeout
|
|
336
|
+
await expect(page.getByRole("button")).toBeVisible({ timeout: 10000 });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Best Practices
|
|
340
|
+
|
|
341
|
+
| Do | Don't |
|
|
342
|
+
| ------------------------------ | ------------------------------ |
|
|
343
|
+
| Use web-first assertions | Use generic assertions for DOM |
|
|
344
|
+
| Let auto-waiting work | Add unnecessary explicit waits |
|
|
345
|
+
| Use `toPass()` for polling | Write manual retry loops |
|
|
346
|
+
| Configure appropriate timeouts | Use `waitForTimeout()` |
|
|
347
|
+
| Check specific conditions | Wait for arbitrary time |
|
|
348
|
+
|
|
349
|
+
## Anti-Patterns to Avoid
|
|
350
|
+
|
|
351
|
+
| Anti-Pattern | Problem | Solution |
|
|
352
|
+
| --------------------------------------------------------- | ----------------------------- | -------------------------------------------- |
|
|
353
|
+
| `await page.waitForTimeout(5000)` | Slow, flaky, arbitrary timing | Use auto-waiting or `waitForResponse` |
|
|
354
|
+
| `await new Promise(resolve => setTimeout(resolve, 1000))` | Same as above | Use `waitForResponse` or element state waits |
|
|
355
|
+
| Generic assertions on DOM elements | No auto-retry, flaky | Use web-first assertions with `expect()` |
|
|
356
|
+
|
|
357
|
+
## Related References
|
|
358
|
+
|
|
359
|
+
- **Debugging timeout issues**: See [debugging.md](../debugging/debugging.md) for troubleshooting
|
|
360
|
+
- **Fixing flaky tests**: See [debugging.md](../debugging/debugging.md) for race condition solutions
|
|
361
|
+
- **Network interception**: See [test-suite-structure.md](test-suite-structure.md) for API mocking
|