BROWSER INTERACTIONS · LESSON 15 OF 24
File uploads and downloads
Exercise files without depending on a machine-specific chooser.
- setInputFiles
- download event
- outputPath
- JSON
Your runnable checkpoint
Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.
Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.
1. Understand the concepts
setInputFiles can supply a path or an in-memory file payload. A payload keeps a small fixture self-contained and avoids requiring a file in a learner-specific directory.
For downloads, begin waiting before clicking. Save the result into testInfo.outputPath so parallel tests have separate artifact paths. Read the file and assert its contents instead of trusting only the filename.
The lab upload control reports the selected filename; it does not upload to a remote server. The export button downloads the current browser-local task array as JSON.
Use small synthetic files. Avoid private documents in tests or reports. A file test should say which behavior it proves: selection, validation, transfer, or content generation.
2. Build and run
These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.
npm install
npx playwright install chromium
npm run check
npm testtests/lesson.spec.ts
import { test, expect } from '@playwright/test';
import { readFile } from 'node:fs/promises';
test('select a file and inspect an export', async ({ page }, testInfo) => {
await page.goto('/');
await page.getByLabel('Choose practice file').setInputFiles({ name: 'notes.txt', mimeType: 'text/plain', buffer: Buffer.from('Practice only') });
await expect(page.locator('#file-status')).toHaveText('notes.txt');
const waiting = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export tasks' }).click();
const download = await waiting;
expect(download.suggestedFilename()).toBe('tasks.json');
const file = testInfo.outputPath('tasks.json');
await download.saveAs(file);
expect(JSON.parse(await readFile(file, 'utf8'))).toEqual([]);
});
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 2 : undefined,
timeout: 30000,
expect: { timeout: 5000 },
reporter: [['list'], ['html', { open: 'never' }]],
use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});
package.json
{
"name": "edupoly-playwright-uploads-and-downloads",
"version": "1.0.0",
"private": true,
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:ui": "playwright test --ui",
"check": "tsc --noEmit",
"report": "playwright show-report",
"serve": "node server.mjs"
},
"devDependencies": {
"@playwright/test": "1.56.1",
"typescript": "5.9.3",
"@types/node": "22.20.5"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": [
"node"
],
"noEmit": true
},
"include": [
"tests/**/*.ts",
"pages/**/*.ts",
"playwright.config.ts"
]
}
.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env
3. Trace the code and check the result
- An in-memory payload makes the upload fixture portable.
- The download wait precedes the click.
- The file is saved in a test-specific directory and parsed.
Verify your result
- The selected filename appears.
- The downloaded JSON contains an empty array in a fresh context.
4. Practice independently
Create a task before exporting and assert its title in the JSON.
Show the explanation
After adding a task, expect the parsed array to contain an object with that title and done: false. Do not assert a fixed random id.
When something goes wrong
If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.
