@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,377 @@
|
|
|
1
|
+
# File Upload & Download Testing
|
|
2
|
+
|
|
3
|
+
> For advanced patterns (progress tracking, cancellation, retry logic), see [file-upload-download.md](./file-upload-download.md)
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [File Downloads](#file-downloads)
|
|
8
|
+
2. [File Uploads](#file-uploads)
|
|
9
|
+
3. [Drag and Drop](#drag-and-drop)
|
|
10
|
+
4. [File Content Verification](#file-content-verification)
|
|
11
|
+
|
|
12
|
+
## File Downloads
|
|
13
|
+
|
|
14
|
+
### Basic Download
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
test("download PDF report", async ({ page }) => {
|
|
18
|
+
await page.goto("/reports");
|
|
19
|
+
|
|
20
|
+
// Start waiting for download before clicking
|
|
21
|
+
const downloadPromise = page.waitForEvent("download");
|
|
22
|
+
await page.getByRole("button", { name: "Download PDF" }).click();
|
|
23
|
+
const download = await downloadPromise;
|
|
24
|
+
|
|
25
|
+
// Verify filename
|
|
26
|
+
expect(download.suggestedFilename()).toBe("report.pdf");
|
|
27
|
+
|
|
28
|
+
// Save to specific path
|
|
29
|
+
await download.saveAs("./downloads/report.pdf");
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Download with Custom Path
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
test("download to temp directory", async ({ page }, testInfo) => {
|
|
37
|
+
await page.goto("/exports");
|
|
38
|
+
|
|
39
|
+
const downloadPromise = page.waitForEvent("download");
|
|
40
|
+
await page.getByRole("link", { name: "Export CSV" }).click();
|
|
41
|
+
const download = await downloadPromise;
|
|
42
|
+
|
|
43
|
+
// Save to test output directory
|
|
44
|
+
const path = testInfo.outputPath(download.suggestedFilename());
|
|
45
|
+
await download.saveAs(path);
|
|
46
|
+
|
|
47
|
+
// Attach to test report
|
|
48
|
+
await testInfo.attach("downloaded-file", { path });
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Verify Download Content
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import fs from "fs";
|
|
56
|
+
import path from "path";
|
|
57
|
+
|
|
58
|
+
test("verify CSV content", async ({ page }, testInfo) => {
|
|
59
|
+
await page.goto("/data");
|
|
60
|
+
|
|
61
|
+
const downloadPromise = page.waitForEvent("download");
|
|
62
|
+
await page.getByRole("button", { name: "Export" }).click();
|
|
63
|
+
const download = await downloadPromise;
|
|
64
|
+
|
|
65
|
+
const filePath = testInfo.outputPath("export.csv");
|
|
66
|
+
await download.saveAs(filePath);
|
|
67
|
+
|
|
68
|
+
// Read and verify content
|
|
69
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
70
|
+
expect(content).toContain("Name,Email,Status");
|
|
71
|
+
expect(content).toContain("John Doe");
|
|
72
|
+
|
|
73
|
+
// Verify row count
|
|
74
|
+
const rows = content.trim().split("\n");
|
|
75
|
+
expect(rows.length).toBeGreaterThan(1);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Multiple Downloads
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
test("download multiple files", async ({ page }) => {
|
|
83
|
+
await page.goto("/batch-export");
|
|
84
|
+
|
|
85
|
+
await page.getByRole("checkbox", { name: "Select All" }).check();
|
|
86
|
+
|
|
87
|
+
// Collect all downloads
|
|
88
|
+
const downloads: Download[] = [];
|
|
89
|
+
page.on("download", (download) => downloads.push(download));
|
|
90
|
+
|
|
91
|
+
await page.getByRole("button", { name: "Download Selected" }).click();
|
|
92
|
+
|
|
93
|
+
// Wait for all downloads
|
|
94
|
+
await expect.poll(() => downloads.length, { timeout: 30000 }).toBe(5);
|
|
95
|
+
|
|
96
|
+
// Verify each download
|
|
97
|
+
for (const download of downloads) {
|
|
98
|
+
expect(download.suggestedFilename()).toMatch(/\.pdf$/);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Download Fixture
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// fixtures/download.fixture.ts
|
|
107
|
+
import { test as base, Download } from "@playwright/test";
|
|
108
|
+
import fs from "fs";
|
|
109
|
+
import path from "path";
|
|
110
|
+
|
|
111
|
+
type DownloadFixtures = {
|
|
112
|
+
downloadDir: string;
|
|
113
|
+
downloadAndVerify: (
|
|
114
|
+
trigger: () => Promise<void>,
|
|
115
|
+
expectedFilename: string,
|
|
116
|
+
) => Promise<string>;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const test = base.extend<DownloadFixtures>({
|
|
120
|
+
downloadDir: async ({}, use, testInfo) => {
|
|
121
|
+
const dir = testInfo.outputPath("downloads");
|
|
122
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
123
|
+
await use(dir);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
downloadAndVerify: async ({ page, downloadDir }, use) => {
|
|
127
|
+
await use(async (trigger, expectedFilename) => {
|
|
128
|
+
const downloadPromise = page.waitForEvent("download");
|
|
129
|
+
await trigger();
|
|
130
|
+
const download = await downloadPromise;
|
|
131
|
+
|
|
132
|
+
expect(download.suggestedFilename()).toBe(expectedFilename);
|
|
133
|
+
|
|
134
|
+
const filePath = path.join(downloadDir, expectedFilename);
|
|
135
|
+
await download.saveAs(filePath);
|
|
136
|
+
return filePath;
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## File Uploads
|
|
143
|
+
|
|
144
|
+
### Basic Upload
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
test("upload profile picture", async ({ page }) => {
|
|
148
|
+
await page.goto("/settings/profile");
|
|
149
|
+
|
|
150
|
+
// Upload file
|
|
151
|
+
await page
|
|
152
|
+
.getByLabel("Profile Picture")
|
|
153
|
+
.setInputFiles("./fixtures/avatar.png");
|
|
154
|
+
|
|
155
|
+
// Verify preview
|
|
156
|
+
await expect(page.getByAltText("Profile preview")).toBeVisible();
|
|
157
|
+
|
|
158
|
+
await page.getByRole("button", { name: "Save" }).click();
|
|
159
|
+
await expect(page.getByText("Profile updated")).toBeVisible();
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Multiple File Upload
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
test("upload multiple documents", async ({ page }) => {
|
|
167
|
+
await page.goto("/documents/upload");
|
|
168
|
+
|
|
169
|
+
await page
|
|
170
|
+
.getByLabel("Documents")
|
|
171
|
+
.setInputFiles([
|
|
172
|
+
"./fixtures/doc1.pdf",
|
|
173
|
+
"./fixtures/doc2.pdf",
|
|
174
|
+
"./fixtures/doc3.pdf",
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
// Verify all files listed
|
|
178
|
+
await expect(page.getByText("doc1.pdf")).toBeVisible();
|
|
179
|
+
await expect(page.getByText("doc2.pdf")).toBeVisible();
|
|
180
|
+
await expect(page.getByText("doc3.pdf")).toBeVisible();
|
|
181
|
+
|
|
182
|
+
await page.getByRole("button", { name: "Upload All" }).click();
|
|
183
|
+
await expect(page.getByText("3 files uploaded")).toBeVisible();
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Upload with File Chooser
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
test("upload via file chooser dialog", async ({ page }) => {
|
|
191
|
+
await page.goto("/upload");
|
|
192
|
+
|
|
193
|
+
// Handle file chooser
|
|
194
|
+
const fileChooserPromise = page.waitForEvent("filechooser");
|
|
195
|
+
await page.getByRole("button", { name: "Choose File" }).click();
|
|
196
|
+
const fileChooser = await fileChooserPromise;
|
|
197
|
+
|
|
198
|
+
await fileChooser.setFiles("./fixtures/document.pdf");
|
|
199
|
+
|
|
200
|
+
await expect(page.getByText("document.pdf")).toBeVisible();
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Clear and Re-upload
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
test("replace uploaded file", async ({ page }) => {
|
|
208
|
+
await page.goto("/upload");
|
|
209
|
+
|
|
210
|
+
const input = page.getByLabel("Document");
|
|
211
|
+
|
|
212
|
+
// Upload first file
|
|
213
|
+
await input.setInputFiles("./fixtures/old.pdf");
|
|
214
|
+
await expect(page.getByText("old.pdf")).toBeVisible();
|
|
215
|
+
|
|
216
|
+
// Clear selection
|
|
217
|
+
await input.setInputFiles([]);
|
|
218
|
+
|
|
219
|
+
// Upload new file
|
|
220
|
+
await input.setInputFiles("./fixtures/new.pdf");
|
|
221
|
+
await expect(page.getByText("new.pdf")).toBeVisible();
|
|
222
|
+
await expect(page.getByText("old.pdf")).toBeHidden();
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Upload from Buffer
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
test("upload generated file", async ({ page }) => {
|
|
230
|
+
await page.goto("/upload");
|
|
231
|
+
|
|
232
|
+
// Create file content dynamically
|
|
233
|
+
const content = "Name,Email\nJohn,john@example.com";
|
|
234
|
+
|
|
235
|
+
await page.getByLabel("CSV File").setInputFiles({
|
|
236
|
+
name: "users.csv",
|
|
237
|
+
mimeType: "text/csv",
|
|
238
|
+
buffer: Buffer.from(content),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await expect(page.getByText("users.csv")).toBeVisible();
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Drag and Drop
|
|
246
|
+
|
|
247
|
+
### Drag and Drop Upload
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
test("drag and drop file upload", async ({ page }) => {
|
|
251
|
+
await page.goto("/upload");
|
|
252
|
+
|
|
253
|
+
const dropzone = page.getByTestId("dropzone");
|
|
254
|
+
|
|
255
|
+
// Create a DataTransfer with the file
|
|
256
|
+
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
|
|
257
|
+
|
|
258
|
+
// Read file and add to DataTransfer
|
|
259
|
+
const buffer = fs.readFileSync("./fixtures/image.png");
|
|
260
|
+
await page.evaluate(
|
|
261
|
+
async ([dataTransfer, data]) => {
|
|
262
|
+
const file = new File([new Uint8Array(data)], "image.png", {
|
|
263
|
+
type: "image/png",
|
|
264
|
+
});
|
|
265
|
+
dataTransfer.items.add(file);
|
|
266
|
+
},
|
|
267
|
+
[dataTransfer, [...buffer]] as const,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// Dispatch drop event
|
|
271
|
+
await dropzone.dispatchEvent("drop", { dataTransfer });
|
|
272
|
+
|
|
273
|
+
await expect(page.getByText("image.png uploaded")).toBeVisible();
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Simpler Drag and Drop
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
test("drag and drop with setInputFiles", async ({ page }) => {
|
|
281
|
+
await page.goto("/upload");
|
|
282
|
+
|
|
283
|
+
// Most dropzones have a hidden file input
|
|
284
|
+
const input = page.locator('input[type="file"]');
|
|
285
|
+
|
|
286
|
+
// This works even if the input is hidden
|
|
287
|
+
await input.setInputFiles("./fixtures/document.pdf");
|
|
288
|
+
|
|
289
|
+
await expect(page.getByText("document.pdf")).toBeVisible();
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## File Content Verification
|
|
294
|
+
|
|
295
|
+
### Verify PDF Content
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import pdf from "pdf-parse";
|
|
299
|
+
|
|
300
|
+
test("verify PDF content", async ({ page }, testInfo) => {
|
|
301
|
+
await page.goto("/invoice/123");
|
|
302
|
+
|
|
303
|
+
const downloadPromise = page.waitForEvent("download");
|
|
304
|
+
await page.getByRole("button", { name: "Download Invoice" }).click();
|
|
305
|
+
const download = await downloadPromise;
|
|
306
|
+
|
|
307
|
+
const path = testInfo.outputPath("invoice.pdf");
|
|
308
|
+
await download.saveAs(path);
|
|
309
|
+
|
|
310
|
+
// Parse PDF
|
|
311
|
+
const dataBuffer = fs.readFileSync(path);
|
|
312
|
+
const data = await pdf(dataBuffer);
|
|
313
|
+
|
|
314
|
+
expect(data.text).toContain("Invoice #123");
|
|
315
|
+
expect(data.text).toContain("Total: $99.99");
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Verify Excel Content
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import XLSX from "xlsx";
|
|
323
|
+
|
|
324
|
+
test("verify Excel export", async ({ page }, testInfo) => {
|
|
325
|
+
await page.goto("/reports");
|
|
326
|
+
|
|
327
|
+
const downloadPromise = page.waitForEvent("download");
|
|
328
|
+
await page.getByRole("button", { name: "Export Excel" }).click();
|
|
329
|
+
const download = await downloadPromise;
|
|
330
|
+
|
|
331
|
+
const path = testInfo.outputPath("report.xlsx");
|
|
332
|
+
await download.saveAs(path);
|
|
333
|
+
|
|
334
|
+
// Parse Excel
|
|
335
|
+
const workbook = XLSX.readFile(path);
|
|
336
|
+
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
337
|
+
const data = XLSX.utils.sheet_to_json(sheet);
|
|
338
|
+
|
|
339
|
+
expect(data).toHaveLength(10);
|
|
340
|
+
expect(data[0]).toHaveProperty("Name");
|
|
341
|
+
expect(data[0]).toHaveProperty("Email");
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Verify JSON Download
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
test("verify JSON export", async ({ page }, testInfo) => {
|
|
349
|
+
await page.goto("/api-data");
|
|
350
|
+
|
|
351
|
+
const downloadPromise = page.waitForEvent("download");
|
|
352
|
+
await page.getByRole("button", { name: "Export JSON" }).click();
|
|
353
|
+
const download = await downloadPromise;
|
|
354
|
+
|
|
355
|
+
const path = testInfo.outputPath("data.json");
|
|
356
|
+
await download.saveAs(path);
|
|
357
|
+
|
|
358
|
+
const content = JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
359
|
+
|
|
360
|
+
expect(content.users).toHaveLength(5);
|
|
361
|
+
expect(content.exportDate).toBeDefined();
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Anti-Patterns to Avoid
|
|
366
|
+
|
|
367
|
+
| Anti-Pattern | Problem | Solution |
|
|
368
|
+
| ------------------------------------- | ------------------------------- | --------------------------------------------- |
|
|
369
|
+
| Not waiting for download | Race condition, test fails | Always use `waitForEvent("download")` |
|
|
370
|
+
| Hardcoded download paths | Conflicts in parallel runs | Use `testInfo.outputPath()` |
|
|
371
|
+
| Skipping content verification | Download might be empty/corrupt | Verify file content when possible |
|
|
372
|
+
| Using `force: true` for hidden inputs | May not trigger proper events | Use `setInputFiles` on hidden inputs directly |
|
|
373
|
+
|
|
374
|
+
## Related References
|
|
375
|
+
|
|
376
|
+
- **Fixtures**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for download fixture patterns
|
|
377
|
+
- **Debugging**: See [debugging.md](../debugging/debugging.md) for troubleshooting download issues
|