setInputFiles() do Playwright contorna o seletor de arquivos do sistema operacional definindo arquivos diretamente no elemento , por isso você consegue automatizar uploads sem que o navegador abra nenhum diálogo. Downloads funcionam diferente: você captura o evento download com page.waitForEvent('download'), mas precisa registrar o listener antes de disparar o download. Se o download acontecer enquanto você ainda está configurando o listener, o evento é perdido.
Por que os diálogos nativos de arquivo não podem ser automatizados com cliques
O seletor de arquivos nativo (o diálogo do sistema operacional que abre ao clicar em "Escolher arquivo") roda fora do motor de renderização do navegador. Pertence ao sistema operacional. O Playwright controla o navegador; não tem mecanismo para entrar em uma janela do Explorador de Arquivos ou do Finder e selecionar um arquivo.
Isso é intencional do ponto de vista de segurança. Se o JavaScript pudesse acionar e completar um diálogo de seleção de arquivo programaticamente, seria um caminho trivial para roubar arquivos da máquina do usuário. Os navegadores isolam essa interação especificamente para impedir isso.
A abordagem padrão para automatizar uploads contorna o diálogo completamente. Em vez de simular um clique que abre o diálogo, você interage diretamente com o elemento subjacente e diz qual arquivo usar. É exatamente o que setInputFiles() faz.
setInputFiles(): a abordagem padrão
A maioria dos formulários de upload usa um elemento , mesmo quando visualmente escondido atrás de um botão estilizado. setInputFiles() define os arquivos nesse input programaticamente, contornando o diálogo completamente.
import { test, expect } from '@playwright/test';
import path from 'path';
test('faz upload de um arquivo único', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/file-upload');
const filePath = path.join(__dirname, 'fixtures/sample.pdf');
await page.locator('input[type="file"]').setInputFiles(filePath);
await page.getByRole('button', { name: 'Upload' }).click();
await expect(page.getByText('Upload successful')).toBeVisible();
});O padrão path.join(__dirname, ...) é importante aqui. Caminhos absolutos são mais confiáveis do que relativos. __dirname dá o diretório do arquivo de teste atual, então o caminho do fixture resolve corretamente independente de onde você roda o teste.
Se o input tem um atributo name ou id, ou está associado a um label, use um locator mais descritivo:
// Por texto do label
await page.getByLabel('Anexar documento').setInputFiles(filePath);
// Por atributo name
await page.locator('input[name="resume"]').setInputFiles(filePath);setInputFiles() respeita o atributo accept do input. Se o input aceita apenas imagens (accept="image/*") e você tenta definir um .pdf, o Playwright não vai lançar erro, mas o navegador pode rejeitar o arquivo silenciosamente. Use sempre arquivos que correspondam aos tipos aceitos pelo input.Fazendo upload de múltiplos arquivos de uma vez
Quando o input permite múltiplos arquivos (), passe um array de caminhos para setInputFiles():
test('faz upload de múltiplos arquivos de uma vez', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/file-upload');
const files = [
path.join(__dirname, 'fixtures/document-a.pdf'),
path.join(__dirname, 'fixtures/document-b.pdf'),
path.join(__dirname, 'fixtures/image.png'),
];
await page.locator('input[type="file"]').setInputFiles(files);
await page.getByRole('button', { name: 'Upload' }).click();
// Verifica que os três arquivos aparecem na confirmação
await expect(page.getByText('document-a.pdf')).toBeVisible();
await expect(page.getByText('document-b.pdf')).toBeVisible();
await expect(page.getByText('image.png')).toBeVisible();
});Para limpar a seleção e começar de novo, passe um array vazio:
// Remove todos os arquivos selecionados
await page.locator('input[type="file"]').setInputFiles([]);Útil quando o fluxo do teste envolve mudar a seleção de arquivo antes de enviar, ou quando você precisa resetar o estado do input entre steps.
Se precisar construir arquivos de teste dinamicamente em vez de ler do disco, setInputFiles() também aceita um buffer:
test('faz upload de arquivo criado em memória', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/file-upload');
await page.locator('input[type="file"]').setInputFiles({
name: 'relatorio-gerado.txt',
mimeType: 'text/plain',
buffer: Buffer.from('Conteúdo do relatório\nLinha 2\nLinha 3'),
});
await page.getByRole('button', { name: 'Upload' }).click();
await expect(page.getByText('relatorio-gerado.txt')).toBeVisible();
});A abordagem com buffer é ideal quando o conteúdo do arquivo importa para o teste, mas você não quer manter arquivos em disco para cada variação.
Upload por drag-and-drop
Algumas interfaces de upload usam uma drop zone em vez de um input de arquivo: uma A abordagem aqui é disparar os eventos de drag manualmente usando Mais trabalhoso do que Alguns botões de upload são elementos A ordem importa: registre o listener de Você também pode verificar se o input aceita múltiplos arquivos antes de defini-los: Downloads funcionam por um mecanismo diferente. Quando o navegador dispara um download, o Playwright emite um evento Por padrão, o Playwright salva downloads em um diretório temporário que é limpo quando o contexto do navegador fecha. O objeto Capturar o download é o primeiro passo. Fazer assertions sobre o conteúdo é o que torna o teste significativo. Para arquivos binários, você pode verificar o tamanho em vez do conteúdo: Para formatos estruturados como JSON, deserialize e faça assertions na estrutura: Testes de upload e download deixam arquivos em disco. Ao rodar testes em paralelo ou em ambiente de CI, arquivos sobrando de uma execução podem contaminar outra. A ferramenta certa é uma fixture do Playwright que faz o cleanup automaticamente. Crie uma fixture que fornece um diretório limpo de downloads e o remove após cada teste: Testes que usam essa fixture recebem um diretório isolado e novo para cada execução: Para fixtures de upload, você pode gerar arquivos de teste temporários no setup da fixture e limpá-los depois: Esse padrão mantém a infraestrutura de teste honesta: se um teste falha e o cleanup nunca roda em um Clique no botão primeiro, aguarde o input aparecer, depois chame Sim. Intercepte a URL do download e retorne um status de erro: Sim, usa o timeout de action padrão (geralmente 30 segundos). Se seu download demorar mais, passe um timeout customizado: Se o download abre em uma nova página, escute no contexto do navegador: O arquivo fica no diretório interno temporário do Playwright até o contexto do navegador fechar, depois é deletado. Sempre chame dragover e drop. Não tem para chamar setInputFiles().
page.dispatchEvent() com um objeto DataTransfer populado via page.evaluate():test('faz upload de arquivo via drag-and-drop', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/file-upload');
const dropZone = page.locator('.drop-zone');
const filePath = path.join(__dirname, 'fixtures/sample.pdf');
// Lê o conteúdo do arquivo e cria um DataTransfer com ele
const fileContent = require('fs').readFileSync(filePath);
const base64 = fileContent.toString('base64');
await dropZone.evaluate(
(element, { base64Content, fileName, mimeType }) => {
const dataTransfer = new DataTransfer();
const byteCharacters = atob(base64Content);
const byteArray = new Uint8Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteArray[i] = byteCharacters.charCodeAt(i);
}
const file = new File([byteArray], fileName, { type: mimeType });
dataTransfer.items.add(file);
element.dispatchEvent(new DragEvent('dragenter', { dataTransfer, bubbles: true }));
element.dispatchEvent(new DragEvent('dragover', { dataTransfer, bubbles: true }));
element.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true }));
},
{ base64Content: base64, fileName: 'sample.pdf', mimeType: 'application/pdf' }
);
await expect(page.getByText('sample.pdf')).toBeVisible();
});setInputFiles(), mas é a abordagem correta para drop zones controladas por JavaScript. Se a drop zone também tiver um input de arquivo oculto que é ativado após o drop, a abordagem mais simples com setInputFiles() pode funcionar. Inspecione o DOM para verificar. oculto que você pode apontar com setInputFiles(). Confira no DevTools primeiro — economiza bastante complexidade.Escutando eventos filechooser
ou page.waitForEvent('filechooser') do Playwright permite interceptar o diálogo que abre quando esse botão é clicado:
test('lida com botão de upload customizado', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/file-upload');
const filePath = path.join(__dirname, 'fixtures/sample.pdf');
// Configura o listener ANTES do clique
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Choose File' }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(filePath);
await expect(page.getByText('sample.pdf')).toBeVisible();
});filechooser antes de clicar no botão. Se o diálogo disparar e resolver antes de você começar a escutar, você vai esperar para sempre.fileChooser.setFiles() aceita os mesmos argumentos que setInputFiles(): um caminho único, um array de caminhos, ou um objeto de buffer. Para múltiplos arquivos:
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: 'Attach Files' }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles([
path.join(__dirname, 'fixtures/attachment-1.pdf'),
path.join(__dirname, 'fixtures/attachment-2.pdf'),
]);const fileChooser = await fileChooserPromise;
console.log('Aceita múltiplos:', fileChooser.isMultiple());
await fileChooser.setFiles(filePath);Downloads: capturando o evento de download
download na página. Você o intercepta com page.waitForEvent('download') e recebe um objeto Download para trabalhar.test('faz download de arquivo de relatório', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/reports');
// Registra o listener antes de disparar o download
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();
const download = await downloadPromise;
// Salva o arquivo em um local conhecido
const savePath = path.join(__dirname, 'downloads/report.csv');
await download.saveAs(savePath);
// Verifica que o arquivo existe
const { existsSync } = require('fs');
expect(existsSync(savePath)).toBe(true);
});saveAs() copia o arquivo para um local que você controla, para inspecionar depois que o download completa.download expõe o nome de arquivo sugerido antes de você salvar:const download = await downloadPromise;
console.log('Nome sugerido:', download.suggestedFilename());
await download.saveAs(
path.join(__dirname, `downloads/${download.suggestedFilename()}`)
);suggestedFilename() retorna o que o servidor enviou no header Content-Disposition, que é exatamente o que um usuário veria como nome de arquivo padrão no diálogo de salvar.
Verificando o conteúdo do download
import { test, expect } from '@playwright/test';
import path from 'path';
import fs from 'fs';
test('CSV exportado contém os dados corretos', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();
const download = await downloadPromise;
// Verifica o nome do arquivo
expect(download.suggestedFilename()).toMatch(/^report-\d{4}-\d{2}-\d{2}\.csv$/);
// Salva e lê o conteúdo
const savePath = path.join(__dirname, 'downloads/report.csv');
await download.saveAs(savePath);
const content = fs.readFileSync(savePath, 'utf-8');
// Verifica linha de cabeçalho
expect(content).toContain('id,destination,status,notes');
// Verifica que o arquivo não está vazio além do cabeçalho
const rows = content.trim().split('\n');
expect(rows.length).toBeGreaterThan(1);
});test('PDF baixado não está vazio', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export PDF' }).click();
const download = await downloadPromise;
const savePath = path.join(__dirname, 'downloads/report.pdf');
await download.saveAs(savePath);
const stats = fs.statSync(savePath);
expect(download.suggestedFilename()).toBe('report.pdf');
expect(stats.size).toBeGreaterThan(1000); // Um PDF real tem mais de 1KB
});test('JSON exportado tem a estrutura esperada', async ({ page }) => {
await page.goto('https://lab.becomeqa.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export JSON' }).click();
const download = await downloadPromise;
const savePath = path.join(__dirname, 'downloads/export.json');
await download.saveAs(savePath);
const content = JSON.parse(fs.readFileSync(savePath, 'utf-8'));
expect(Array.isArray(content)).toBe(true);
expect(content[0]).toHaveProperty('destination');
expect(content[0]).toHaveProperty('status');
});Limpando arquivos de teste com fixtures
// fixtures/file-test.ts
import { test as base } from '@playwright/test';
import fs from 'fs';
import path from 'path';
type FileTestFixtures = {
downloadDir: string;
testFilePath: (filename: string) => string;
};
export const test = base.extend<FileTestFixtures>({
downloadDir: async ({}, use) => {
const dir = path.join(__dirname, `../downloads/test-${Date.now()}`);
fs.mkdirSync(dir, { recursive: true });
await use(dir);
// Cleanup após o teste
fs.rmSync(dir, { recursive: true, force: true });
},
testFilePath: async ({ downloadDir }, use) => {
const getPath = (filename: string) => path.join(downloadDir, filename);
await use(getPath);
},
});
export { expect } from '@playwright/test';// tests/download.spec.ts
import { test, expect } from '../fixtures/file-test';
import path from 'path';
test('faz download para diretório isolado', async ({ page, downloadDir, testFilePath }) => {
await page.goto('https://lab.becomeqa.com/reports');
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();
const download = await downloadPromise;
const savePath = testFilePath(download.suggestedFilename());
await download.saveAs(savePath);
const content = require('fs').readFileSync(savePath, 'utf-8');
expect(content).toContain('destination');
// downloadDir e seu conteúdo são removidos automaticamente após este teste
});export const test = base.extend<{
uploadFixturePath: string;
}>({
uploadFixturePath: async ({}, use) => {
const filePath = path.join(__dirname, `../fixtures/temp-${Date.now()}.txt`);
fs.writeFileSync(filePath, 'Conteúdo do arquivo de teste temporário');
await use(filePath);
fs.rmSync(filePath, { force: true });
},
});beforeAll/afterAll, o teardown da fixture ainda roda. O Playwright chama o teardown das fixtures mesmo quando os testes falham.FAQ
E se o input de arquivo estiver oculto ou com display: none?
setInputFiles() funciona em inputs ocultos: ignora o estado visual do elemento. Se você receber um erro de visibilidade, use { force: true } como opção: await page.locator('input[type="file"]').setInputFiles(filePath, { force: true }).
Como lidar com upload que exige clicar em um botão primeiro para revelar o input?
setInputFiles(). Se o botão abre o diálogo nativo em vez disso, use page.waitForEvent('filechooser') antes do clique.await page.route('/export/csv', route => route.fulfill({ status: 500 })). Depois verifique que a UI mostra uma mensagem de erro adequada. O evento download não vai disparar nesse caso.page.waitForEvent('download') tem timeout?
page.waitForEvent('download', { timeout: 60000 }).const download = await browserContext.waitForEvent('download'). Isso captura downloads de qualquer página no contexto.saveAs()?
saveAs() se precisar inspecionar o arquivo.