SurfSense/.cursor/skills/playwright-testing/testing-patterns/file-operations.md
2026-05-10 04:19:55 +05:30

10 KiB

File Upload & Download Testing

For advanced patterns (progress tracking, cancellation, retry logic), see file-upload-download.md

Table of Contents

  1. File Downloads
  2. File Uploads
  3. Drag and Drop
  4. File Content Verification

File Downloads

Basic Download

test("download PDF report", async ({ page }) => {
  await page.goto("/reports");

  // Start waiting for download before clicking
  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Download PDF" }).click();
  const download = await downloadPromise;

  // Verify filename
  expect(download.suggestedFilename()).toBe("report.pdf");

  // Save to specific path
  await download.saveAs("./downloads/report.pdf");
});

Download with Custom Path

test("download to temp directory", async ({ page }, testInfo) => {
  await page.goto("/exports");

  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("link", { name: "Export CSV" }).click();
  const download = await downloadPromise;

  // Save to test output directory
  const path = testInfo.outputPath(download.suggestedFilename());
  await download.saveAs(path);

  // Attach to test report
  await testInfo.attach("downloaded-file", { path });
});

Verify Download Content

import fs from "fs";
import path from "path";

test("verify CSV content", async ({ page }, testInfo) => {
  await page.goto("/data");

  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Export" }).click();
  const download = await downloadPromise;

  const filePath = testInfo.outputPath("export.csv");
  await download.saveAs(filePath);

  // Read and verify content
  const content = fs.readFileSync(filePath, "utf-8");
  expect(content).toContain("Name,Email,Status");
  expect(content).toContain("John Doe");

  // Verify row count
  const rows = content.trim().split("\n");
  expect(rows.length).toBeGreaterThan(1);
});

Multiple Downloads

test("download multiple files", async ({ page }) => {
  await page.goto("/batch-export");

  await page.getByRole("checkbox", { name: "Select All" }).check();

  // Collect all downloads
  const downloads: Download[] = [];
  page.on("download", (download) => downloads.push(download));

  await page.getByRole("button", { name: "Download Selected" }).click();

  // Wait for all downloads
  await expect.poll(() => downloads.length, { timeout: 30000 }).toBe(5);

  // Verify each download
  for (const download of downloads) {
    expect(download.suggestedFilename()).toMatch(/\.pdf$/);
  }
});

Download Fixture

// fixtures/download.fixture.ts
import { test as base, Download } from "@playwright/test";
import fs from "fs";
import path from "path";

type DownloadFixtures = {
  downloadDir: string;
  downloadAndVerify: (
    trigger: () => Promise<void>,
    expectedFilename: string,
  ) => Promise<string>;
};

export const test = base.extend<DownloadFixtures>({
  downloadDir: async ({}, use, testInfo) => {
    const dir = testInfo.outputPath("downloads");
    fs.mkdirSync(dir, { recursive: true });
    await use(dir);
  },

  downloadAndVerify: async ({ page, downloadDir }, use) => {
    await use(async (trigger, expectedFilename) => {
      const downloadPromise = page.waitForEvent("download");
      await trigger();
      const download = await downloadPromise;

      expect(download.suggestedFilename()).toBe(expectedFilename);

      const filePath = path.join(downloadDir, expectedFilename);
      await download.saveAs(filePath);
      return filePath;
    });
  },
});

File Uploads

Basic Upload

test("upload profile picture", async ({ page }) => {
  await page.goto("/settings/profile");

  // Upload file
  await page
    .getByLabel("Profile Picture")
    .setInputFiles("./fixtures/avatar.png");

  // Verify preview
  await expect(page.getByAltText("Profile preview")).toBeVisible();

  await page.getByRole("button", { name: "Save" }).click();
  await expect(page.getByText("Profile updated")).toBeVisible();
});

Multiple File Upload

test("upload multiple documents", async ({ page }) => {
  await page.goto("/documents/upload");

  await page
    .getByLabel("Documents")
    .setInputFiles([
      "./fixtures/doc1.pdf",
      "./fixtures/doc2.pdf",
      "./fixtures/doc3.pdf",
    ]);

  // Verify all files listed
  await expect(page.getByText("doc1.pdf")).toBeVisible();
  await expect(page.getByText("doc2.pdf")).toBeVisible();
  await expect(page.getByText("doc3.pdf")).toBeVisible();

  await page.getByRole("button", { name: "Upload All" }).click();
  await expect(page.getByText("3 files uploaded")).toBeVisible();
});

Upload with File Chooser

test("upload via file chooser dialog", async ({ page }) => {
  await page.goto("/upload");

  // Handle file chooser
  const fileChooserPromise = page.waitForEvent("filechooser");
  await page.getByRole("button", { name: "Choose File" }).click();
  const fileChooser = await fileChooserPromise;

  await fileChooser.setFiles("./fixtures/document.pdf");

  await expect(page.getByText("document.pdf")).toBeVisible();
});

Clear and Re-upload

test("replace uploaded file", async ({ page }) => {
  await page.goto("/upload");

  const input = page.getByLabel("Document");

  // Upload first file
  await input.setInputFiles("./fixtures/old.pdf");
  await expect(page.getByText("old.pdf")).toBeVisible();

  // Clear selection
  await input.setInputFiles([]);

  // Upload new file
  await input.setInputFiles("./fixtures/new.pdf");
  await expect(page.getByText("new.pdf")).toBeVisible();
  await expect(page.getByText("old.pdf")).toBeHidden();
});

Upload from Buffer

test("upload generated file", async ({ page }) => {
  await page.goto("/upload");

  // Create file content dynamically
  const content = "Name,Email\nJohn,john@example.com";

  await page.getByLabel("CSV File").setInputFiles({
    name: "users.csv",
    mimeType: "text/csv",
    buffer: Buffer.from(content),
  });

  await expect(page.getByText("users.csv")).toBeVisible();
});

Drag and Drop

Drag and Drop Upload

test("drag and drop file upload", async ({ page }) => {
  await page.goto("/upload");

  const dropzone = page.getByTestId("dropzone");

  // Create a DataTransfer with the file
  const dataTransfer = await page.evaluateHandle(() => new DataTransfer());

  // Read file and add to DataTransfer
  const buffer = fs.readFileSync("./fixtures/image.png");
  await page.evaluate(
    async ([dataTransfer, data]) => {
      const file = new File([new Uint8Array(data)], "image.png", {
        type: "image/png",
      });
      dataTransfer.items.add(file);
    },
    [dataTransfer, [...buffer]] as const,
  );

  // Dispatch drop event
  await dropzone.dispatchEvent("drop", { dataTransfer });

  await expect(page.getByText("image.png uploaded")).toBeVisible();
});

Simpler Drag and Drop

test("drag and drop with setInputFiles", async ({ page }) => {
  await page.goto("/upload");

  // Most dropzones have a hidden file input
  const input = page.locator('input[type="file"]');

  // This works even if the input is hidden
  await input.setInputFiles("./fixtures/document.pdf");

  await expect(page.getByText("document.pdf")).toBeVisible();
});

File Content Verification

Verify PDF Content

import pdf from "pdf-parse";

test("verify PDF content", async ({ page }, testInfo) => {
  await page.goto("/invoice/123");

  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Download Invoice" }).click();
  const download = await downloadPromise;

  const path = testInfo.outputPath("invoice.pdf");
  await download.saveAs(path);

  // Parse PDF
  const dataBuffer = fs.readFileSync(path);
  const data = await pdf(dataBuffer);

  expect(data.text).toContain("Invoice #123");
  expect(data.text).toContain("Total: $99.99");
});

Verify Excel Content

import XLSX from "xlsx";

test("verify Excel export", async ({ page }, testInfo) => {
  await page.goto("/reports");

  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Export Excel" }).click();
  const download = await downloadPromise;

  const path = testInfo.outputPath("report.xlsx");
  await download.saveAs(path);

  // Parse Excel
  const workbook = XLSX.readFile(path);
  const sheet = workbook.Sheets[workbook.SheetNames[0]];
  const data = XLSX.utils.sheet_to_json(sheet);

  expect(data).toHaveLength(10);
  expect(data[0]).toHaveProperty("Name");
  expect(data[0]).toHaveProperty("Email");
});

Verify JSON Download

test("verify JSON export", async ({ page }, testInfo) => {
  await page.goto("/api-data");

  const downloadPromise = page.waitForEvent("download");
  await page.getByRole("button", { name: "Export JSON" }).click();
  const download = await downloadPromise;

  const path = testInfo.outputPath("data.json");
  await download.saveAs(path);

  const content = JSON.parse(fs.readFileSync(path, "utf-8"));

  expect(content.users).toHaveLength(5);
  expect(content.exportDate).toBeDefined();
});

Anti-Patterns to Avoid

Anti-Pattern Problem Solution
Not waiting for download Race condition, test fails Always use waitForEvent("download")
Hardcoded download paths Conflicts in parallel runs Use testInfo.outputPath()
Skipping content verification Download might be empty/corrupt Verify file content when possible
Using force: true for hidden inputs May not trigger proper events Use setInputFiles on hidden inputs directly