Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
testing-patterns/file-operations.md
1# File Upload & Download Testing23> For advanced patterns (progress tracking, cancellation, retry logic), see [file-upload-download.md](./file-upload-download.md)45## Table of Contents671. [File Downloads](#file-downloads)82. [File Uploads](#file-uploads)93. [Drag and Drop](#drag-and-drop)104. [File Content Verification](#file-content-verification)1112## File Downloads1314### Basic Download1516```typescript17test("download PDF report", async ({ page }) => {18await page.goto("/reports");1920// Start waiting for download before clicking21const downloadPromise = page.waitForEvent("download");22await page.getByRole("button", { name: "Download PDF" }).click();23const download = await downloadPromise;2425// Verify filename26expect(download.suggestedFilename()).toBe("report.pdf");2728// Save to specific path29await download.saveAs("./downloads/report.pdf");30});31```3233### Download with Custom Path3435```typescript36test("download to temp directory", async ({ page }, testInfo) => {37await page.goto("/exports");3839const downloadPromise = page.waitForEvent("download");40await page.getByRole("link", { name: "Export CSV" }).click();41const download = await downloadPromise;4243// Save to test output directory44const path = testInfo.outputPath(download.suggestedFilename());45await download.saveAs(path);4647// Attach to test report48await testInfo.attach("downloaded-file", { path });49});50```5152### Verify Download Content5354```typescript55import fs from "fs";56import path from "path";5758test("verify CSV content", async ({ page }, testInfo) => {59await page.goto("/data");6061const downloadPromise = page.waitForEvent("download");62await page.getByRole("button", { name: "Export" }).click();63const download = await downloadPromise;6465const filePath = testInfo.outputPath("export.csv");66await download.saveAs(filePath);6768// Read and verify content69const content = fs.readFileSync(filePath, "utf-8");70expect(content).toContain("Name,Email,Status");71expect(content).toContain("John Doe");7273// Verify row count74const rows = content.trim().split("\n");75expect(rows.length).toBeGreaterThan(1);76});77```7879### Multiple Downloads8081```typescript82test("download multiple files", async ({ page }) => {83await page.goto("/batch-export");8485await page.getByRole("checkbox", { name: "Select All" }).check();8687// Collect all downloads88const downloads: Download[] = [];89page.on("download", (download) => downloads.push(download));9091await page.getByRole("button", { name: "Download Selected" }).click();9293// Wait for all downloads94await expect.poll(() => downloads.length, { timeout: 30000 }).toBe(5);9596// Verify each download97for (const download of downloads) {98expect(download.suggestedFilename()).toMatch(/\.pdf$/);99}100});101```102103### Download Fixture104105```typescript106// fixtures/download.fixture.ts107import { test as base, Download } from "@playwright/test";108import fs from "fs";109import path from "path";110111type DownloadFixtures = {112downloadDir: string;113downloadAndVerify: (114trigger: () => Promise<void>,115expectedFilename: string,116) => Promise<string>;117};118119export const test = base.extend<DownloadFixtures>({120downloadDir: async ({}, use, testInfo) => {121const dir = testInfo.outputPath("downloads");122fs.mkdirSync(dir, { recursive: true });123await use(dir);124},125126downloadAndVerify: async ({ page, downloadDir }, use) => {127await use(async (trigger, expectedFilename) => {128const downloadPromise = page.waitForEvent("download");129await trigger();130const download = await downloadPromise;131132expect(download.suggestedFilename()).toBe(expectedFilename);133134const filePath = path.join(downloadDir, expectedFilename);135await download.saveAs(filePath);136return filePath;137});138},139});140```141142## File Uploads143144### Basic Upload145146```typescript147test("upload profile picture", async ({ page }) => {148await page.goto("/settings/profile");149150// Upload file151await page152.getByLabel("Profile Picture")153.setInputFiles("./fixtures/avatar.png");154155// Verify preview156await expect(page.getByAltText("Profile preview")).toBeVisible();157158await page.getByRole("button", { name: "Save" }).click();159await expect(page.getByText("Profile updated")).toBeVisible();160});161```162163### Multiple File Upload164165```typescript166test("upload multiple documents", async ({ page }) => {167await page.goto("/documents/upload");168169await page170.getByLabel("Documents")171.setInputFiles([172"./fixtures/doc1.pdf",173"./fixtures/doc2.pdf",174"./fixtures/doc3.pdf",175]);176177// Verify all files listed178await expect(page.getByText("doc1.pdf")).toBeVisible();179await expect(page.getByText("doc2.pdf")).toBeVisible();180await expect(page.getByText("doc3.pdf")).toBeVisible();181182await page.getByRole("button", { name: "Upload All" }).click();183await expect(page.getByText("3 files uploaded")).toBeVisible();184});185```186187### Upload with File Chooser188189```typescript190test("upload via file chooser dialog", async ({ page }) => {191await page.goto("/upload");192193// Handle file chooser194const fileChooserPromise = page.waitForEvent("filechooser");195await page.getByRole("button", { name: "Choose File" }).click();196const fileChooser = await fileChooserPromise;197198await fileChooser.setFiles("./fixtures/document.pdf");199200await expect(page.getByText("document.pdf")).toBeVisible();201});202```203204### Clear and Re-upload205206```typescript207test("replace uploaded file", async ({ page }) => {208await page.goto("/upload");209210const input = page.getByLabel("Document");211212// Upload first file213await input.setInputFiles("./fixtures/old.pdf");214await expect(page.getByText("old.pdf")).toBeVisible();215216// Clear selection217await input.setInputFiles([]);218219// Upload new file220await input.setInputFiles("./fixtures/new.pdf");221await expect(page.getByText("new.pdf")).toBeVisible();222await expect(page.getByText("old.pdf")).toBeHidden();223});224```225226### Upload from Buffer227228```typescript229test("upload generated file", async ({ page }) => {230await page.goto("/upload");231232// Create file content dynamically233const content = "Name,Email\nJohn,[email protected]";234235await page.getByLabel("CSV File").setInputFiles({236name: "users.csv",237mimeType: "text/csv",238buffer: Buffer.from(content),239});240241await expect(page.getByText("users.csv")).toBeVisible();242});243```244245## Drag and Drop246247### Drag and Drop Upload248249```typescript250test("drag and drop file upload", async ({ page }) => {251await page.goto("/upload");252253const dropzone = page.getByTestId("dropzone");254255// Create a DataTransfer with the file256const dataTransfer = await page.evaluateHandle(() => new DataTransfer());257258// Read file and add to DataTransfer259const buffer = fs.readFileSync("./fixtures/image.png");260await page.evaluate(261async ([dataTransfer, data]) => {262const file = new File([new Uint8Array(data)], "image.png", {263type: "image/png",264});265dataTransfer.items.add(file);266},267[dataTransfer, [...buffer]] as const,268);269270// Dispatch drop event271await dropzone.dispatchEvent("drop", { dataTransfer });272273await expect(page.getByText("image.png uploaded")).toBeVisible();274});275```276277### Simpler Drag and Drop278279```typescript280test("drag and drop with setInputFiles", async ({ page }) => {281await page.goto("/upload");282283// Most dropzones have a hidden file input284const input = page.locator('input[type="file"]');285286// This works even if the input is hidden287await input.setInputFiles("./fixtures/document.pdf");288289await expect(page.getByText("document.pdf")).toBeVisible();290});291```292293## File Content Verification294295### Verify PDF Content296297```typescript298import pdf from "pdf-parse";299300test("verify PDF content", async ({ page }, testInfo) => {301await page.goto("/invoice/123");302303const downloadPromise = page.waitForEvent("download");304await page.getByRole("button", { name: "Download Invoice" }).click();305const download = await downloadPromise;306307const path = testInfo.outputPath("invoice.pdf");308await download.saveAs(path);309310// Parse PDF311const dataBuffer = fs.readFileSync(path);312const data = await pdf(dataBuffer);313314expect(data.text).toContain("Invoice #123");315expect(data.text).toContain("Total: $99.99");316});317```318319### Verify Excel Content320321```typescript322import XLSX from "xlsx";323324test("verify Excel export", async ({ page }, testInfo) => {325await page.goto("/reports");326327const downloadPromise = page.waitForEvent("download");328await page.getByRole("button", { name: "Export Excel" }).click();329const download = await downloadPromise;330331const path = testInfo.outputPath("report.xlsx");332await download.saveAs(path);333334// Parse Excel335const workbook = XLSX.readFile(path);336const sheet = workbook.Sheets[workbook.SheetNames[0]];337const data = XLSX.utils.sheet_to_json(sheet);338339expect(data).toHaveLength(10);340expect(data[0]).toHaveProperty("Name");341expect(data[0]).toHaveProperty("Email");342});343```344345### Verify JSON Download346347```typescript348test("verify JSON export", async ({ page }, testInfo) => {349await page.goto("/api-data");350351const downloadPromise = page.waitForEvent("download");352await page.getByRole("button", { name: "Export JSON" }).click();353const download = await downloadPromise;354355const path = testInfo.outputPath("data.json");356await download.saveAs(path);357358const content = JSON.parse(fs.readFileSync(path, "utf-8"));359360expect(content.users).toHaveLength(5);361expect(content.exportDate).toBeDefined();362});363```364365## Anti-Patterns to Avoid366367| 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 |373374## Related References375376- **Fixtures**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for download fixture patterns377- **Debugging**: See [debugging.md](../debugging/debugging.md) for troubleshooting download issues378