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