6.6 KiB
File Utilities
Principle
Read and validate files (CSV, XLSX, PDF, ZIP) with automatic parsing, type-safe results, and download handling. Simplify file operations in Playwright tests with built-in format support and validation helpers.
Rationale
Testing file operations in Playwright requires boilerplate:
- Manual download handling
- External parsing libraries for each format
- No validation helpers
- Type-unsafe results
- Repetitive path handling
The file-utils module provides:
- Auto-parsing: CSV, XLSX, PDF, ZIP automatically parsed
- Download handling: Single function for UI or API-triggered downloads
- Type-safe: TypeScript interfaces for parsed results
- Validation helpers: Row count, header checks, content validation
- Format support: Multiple sheet support (XLSX), text extraction (PDF), archive extraction (ZIP)
Pattern Examples
Example 1: UI-Triggered CSV Download
Context: User clicks button, CSV downloads, validate contents.
Implementation:
import { handleDownload, readCSV } from '@seontechnologies/playwright-utils/file-utils';
import path from 'node:path';
const DOWNLOAD_DIR = path.join(__dirname, '../downloads');
test('should download and validate CSV', async ({ page }) => {
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: () => page.click('[data-testid="export-csv"]'),
});
const { content } = await readCSV({ filePath: downloadPath });
// Validate headers
expect(content.headers).toEqual(['ID', 'Name', 'Email', 'Role']);
// Validate data
expect(content.data).toHaveLength(10);
expect(content.data[0]).toMatchObject({
ID: expect.any(String),
Name: expect.any(String),
Email: expect.stringMatching(/@/),
});
});
Key Points:
handleDownloadwaits for download, returns file pathreadCSVauto-parses to{ headers, data }- Type-safe access to parsed content
- Clean up downloads in
afterEach
Example 2: XLSX with Multiple Sheets
Context: Excel file with multiple sheets (e.g., Summary, Details, Errors).
Implementation:
import { readXLSX } from '@seontechnologies/playwright-utils/file-utils';
test('should read multi-sheet XLSX', async () => {
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: () => page.click('[data-testid="export-xlsx"]'),
});
const { content } = await readXLSX({ filePath: downloadPath });
// Access specific sheets
const summarySheet = content.sheets.find((s) => s.name === 'Summary');
const detailsSheet = content.sheets.find((s) => s.name === 'Details');
// Validate summary
expect(summarySheet.data).toHaveLength(1);
expect(summarySheet.data[0].TotalRecords).toBe('150');
// Validate details
expect(detailsSheet.data).toHaveLength(150);
expect(detailsSheet.headers).toContain('TransactionID');
});
Key Points:
sheetsarray withnameanddataproperties- Access sheets by name
- Each sheet has its own headers and data
- Type-safe sheet iteration
Example 3: PDF Text Extraction
Context: Validate PDF report contains expected content.
Implementation:
import { readPDF } from '@seontechnologies/playwright-utils/file-utils';
test('should validate PDF report', async () => {
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: () => page.click('[data-testid="download-report"]'),
});
const { content } = await readPDF({ filePath: downloadPath });
// content.text is extracted text from all pages
expect(content.text).toContain('Financial Report Q4 2025');
expect(content.text).toContain('Total Revenue:');
// Validate page count
expect(content.numpages).toBeGreaterThan(10);
});
Key Points:
content.textcontains all extracted textcontent.numpagesfor page count- PDF parsing handles multi-page documents
- Search for specific phrases
Example 4: ZIP Archive Validation
Context: Validate ZIP contains expected files and extract specific file.
Implementation:
import { readZIP } from '@seontechnologies/playwright-utils/file-utils';
test('should validate ZIP archive', async () => {
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: () => page.click('[data-testid="download-backup"]'),
});
const { content } = await readZIP({ filePath: downloadPath });
// Check file list
expect(content.files).toContain('data.csv');
expect(content.files).toContain('config.json');
expect(content.files).toContain('readme.txt');
// Read specific file from archive
const configContent = content.zip.readAsText('config.json');
const config = JSON.parse(configContent);
expect(config.version).toBe('2.0');
});
Key Points:
content.fileslists all files in archivecontent.zip.readAsText()extracts specific files- Validate archive structure
- Read and parse individual files from ZIP
Example 5: API-Triggered Download
Context: API endpoint returns file download (not UI click).
Implementation:
test('should download via API', async ({ page, request }) => {
const downloadPath = await handleDownload({
page,
downloadDir: DOWNLOAD_DIR,
trigger: async () => {
const response = await request.get('/api/export/csv', {
headers: { Authorization: 'Bearer token' },
});
if (!response.ok()) {
throw new Error(`Export failed: ${response.status()}`);
}
},
});
const { content } = await readCSV({ filePath: downloadPath });
expect(content.data).toHaveLength(100);
});
Key Points:
triggercan be async API call- API must return
Content-Dispositionheader - Still need
pagefor download events - Works with authenticated endpoints
Validation Helpers
// CSV validation
const { isValid, errors } = await validateCSV({
filePath: downloadPath,
expectedRowCount: 10,
requiredHeaders: ['ID', 'Name', 'Email'],
});
expect(isValid).toBe(true);
expect(errors).toHaveLength(0);
Download Cleanup Pattern
test.afterEach(async () => {
// Clean up downloaded files
await fs.remove(DOWNLOAD_DIR);
});
Related Fragments
overview.md- Installation and importsapi-request.md- API-triggered downloadsrecurse.md- Poll for file generation completion
Anti-Patterns
❌ Not cleaning up downloads:
test('creates file', async () => {
await handleDownload({ ... })
// File left in downloads folder
})
✅ Clean up after tests:
test.afterEach(async () => {
await fs.remove(DOWNLOAD_DIR);
});