본문으로 건너뛰기

3.2 Upload와 Download

한눈에 보기

  • 보이는 <input type="file">setInputFiles()로 다룹니다.
  • 동적으로 생기는 file chooser는 click 전에 waitForEvent('filechooser')를 시작합니다.
  • Download도 click 전에 waitForEvent('download')를 시작합니다.
  • Download 임시 파일은 context가 닫히면 삭제되므로 assertion 전에 읽거나 saveAs()합니다.
  • 파일 이름뿐 아니라 MIME type, 크기, 내용과 보안 경계를 검증합니다.

Upload와 download의 event 대기 재현 화면

Upload: 경로와 memory buffer

Repository에 작은 fixture file이 있다면 경로를 전달합니다.

import path from 'node:path';

await page.getByLabel('첨부 파일').setInputFiles(
path.join(import.meta.dirname, '../test-data/profile.png'),
);
await expect(page.getByTestId('selected-file')).toHaveText('profile.png');

내용만 중요할 때는 temporary file 없이 buffer를 전달할 수 있습니다.

await page.getByLabel('첨부 파일').setInputFiles({
name: 'evidence.txt',
mimeType: 'text/plain',
buffer: Buffer.from('playwright upload lab\n'),
});

빈 배열을 전달하면 선택을 해제합니다.

await page.getByLabel('첨부 파일').setInputFiles([]);

동적 FileChooser

File input이 click 뒤에 생성되면 event promise를 먼저 만듭니다.

const chooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: '파일 선택' }).click();
const chooser = await chooserPromise;
await chooser.setFiles('test-data/evidence.txt');

아래 순서는 event를 놓칠 수 있습니다.

// 잘못된 순서: click 동안 chooser event가 이미 발생할 수 있다.
await page.getByRole('button', { name: '파일 선택' }).click();
const chooser = await page.waitForEvent('filechooser');

Download 저장과 내용 검증

const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'CSV 내려받기' }).click();
const download = await downloadPromise;

expect(download.suggestedFilename()).toBe('playwright-lab.csv');
const path = await download.path();
expect(path).not.toBeNull();

파일을 suite 밖에서 보존해야 한다면 testInfo.outputPath()를 사용해 test별 output directory에 저장합니다.

test('CSV를 내려받는다', async ({ page }, testInfo) => {
const downloadPromise = page.waitForEvent('download');
await page.getByRole('link', { name: 'CSV 내려받기' }).click();
const download = await downloadPromise;
const saved = testInfo.outputPath(download.suggestedFilename());
await download.saveAs(saved);
await testInfo.attach('downloaded-csv', {
path: saved,
contentType: 'text/csv',
});
});

보안과 신뢰 경계

  • Upload test fixture에 고객 data나 실제 credential을 넣지 않습니다.
  • File path traversal, 확장자 위장, 크기 제한은 UI 메시지와 server response를 함께 확인합니다.
  • Download URL이 authorization 없이 재사용되지 않는지 별도 security test에서 확인합니다.
  • CI artifact에 다운로드 결과를 붙일 때 개인정보와 retention 정책을 검토합니다.

실습

  1. Lab의 evidence.txt를 upload하고 화면에 파일명이 표시되는지 확인합니다.
  2. Memory buffer로 같은 test를 작성합니다.
  3. CSV를 download하고 첫 줄이 id,title인지 검증합니다.
  4. Download를 attachment로 남기고 HTML report에서 엽니다.
  5. 허용되지 않은 확장자 message를 제품 상태로 assertion합니다.

심화 질문

  • Browser UI test에서 file payload 전체를 검증해야 하는가, API test로 분리해야 하는가?
  • 큰 파일 test가 모든 PR에 필요한가, nightly suite가 적합한가?
  • Download 완료와 server-side export job 완료는 같은 event인가?

참고: Actions: Upload files, Downloads