File Upload and File Download in Playwright Python (set_input_files, expect_download)

Uploading a profile picture, importing a CSV, exporting a report: file transfer is part of many test flows. Playwright handles both directions without touching the operating system’s file dialogs. In this lesson you will upload real and in-memory files to the SQA Tools practice page, use the file chooser event, and capture, save and verify downloads, including files the page generates on the fly.

Advertisement

📖 What You’ll Learn: set_input_files with a path, several files and in-memory content, checking what was selected, clearing a file input, expect_file_chooser for upload buttons that hide the input, expect_download, suggested_filename, save_as and path, verifying the downloaded content, downloads generated by JavaScript, and where Playwright stores downloads.


📤 Uploading Files: set_input_files

A file upload on a web page is an <input type="file">. Instead of opening the operating system’s file dialog (which automation cannot control), Playwright sets the input’s files directly with set_input_files(). The practice page has one: <input type="file" id="fileUpload">.

📄 tests/conftest.py

import pytest
from playwright.sync_api import Page


@pytest.fixture
def practice(page: Page) -> Page:
    page.goto("https://sqatools.in/automation-practice-page/")
    return page

📄 tests/test_upload.py

from pathlib import Path

from playwright.sync_api import Page, expect

SELECTED_NAMES = "el => [...el.files].map(f => f.name + ' (' + f.size + ' bytes)')"


def test_upload_file_from_disk(practice: Page, tmp_path: Path):
    # Create a real file to upload (in a real project, keep test files in a test_data folder)
    report = tmp_path / "report.csv"
    report.write_text("id,name\n1,Deepesh\n")

    upload = practice.locator("#fileUpload")
    upload.set_input_files(report)

    print("\nselected:", upload.evaluate(SELECTED_NAMES))
    expect(upload).to_have_value("C:\\fakepath\\report.csv")


def test_upload_in_memory_file(practice: Page):
    upload = practice.locator("#fileUpload")
    upload.set_input_files({
        "name": "notes.txt",
        "mimeType": "text/plain",
        "buffer": b"created in memory by the test",
    })
    print("\nin memory:", upload.evaluate(SELECTED_NAMES))
    assert upload.evaluate("el => el.files[0].name") == "notes.txt"


def test_clear_selection(practice: Page, tmp_path: Path):
    file = tmp_path / "a.txt"
    file.write_text("a")
    upload = practice.locator("#fileUpload")
    upload.set_input_files(file)
    upload.set_input_files([])                     # an empty list removes the selection
    expect(upload).to_have_value("")

Line by line:

  • set_input_files(path) accepts a path as a string or pathlib.Path. Relative paths are resolved from the folder where you run pytest.
  • To see what was selected, the test reads the input’s files list in the browser with evaluate: name and size of each file.
  • Browsers never reveal the real path of an uploaded file, so the input’s value is always C:\fakepath\<name>, on every operating system. to_have_value checks exactly that.
  • Instead of a file on disk, you can pass a dictionary with name, mimeType and buffer (bytes). This is handy for generated content and avoids test-data files.
  • set_input_files([]) clears the selection.
  • For inputs with the multiple attribute, pass a list of paths to upload several files at once.

🖥️ Output

selected: ['report.csv (20 bytes)']
in memory: ['notes.txt (29 bytes)']

(The CSV is 20 bytes on Windows, where Python’s write_text stores each line break as two characters, \r\n; on Linux and macOS the same file is 18 bytes.)

📝 Note: set_input_files only selects the file, exactly like a user picking it in the dialog. Whatever the page does next (uploading on change, or on clicking an Upload button) you trigger and verify as usual.


🗂️ Upload Buttons That Hide the Input: expect_file_chooser

Many sites hide the real file input behind a styled button. Clicking the button opens the native file dialog, which Playwright intercepts as a file chooser event:

📄 tests/test_upload.py

def test_file_chooser(page: Page, tmp_path: Path):
    page.set_content("""
        <input type="file" id="hidden-input" style="display:none">
        <button onclick="document.getElementById('hidden-input').click()">Choose photo</button>
        <p id="status"></p>
        <script>
          document.getElementById('hidden-input').onchange = e =>
            document.getElementById('status').textContent = 'Selected: ' + e.target.files[0].name;
        </script>
    """)
    photo = tmp_path / "avatar.png"
    photo.write_bytes(b"\x89PNG fake image bytes")

    with page.expect_file_chooser() as chooser_info:
        page.get_by_role("button", name="Choose photo").click()
    chooser = chooser_info.value
    chooser.set_files(photo)

    expect(page.locator("#status")).to_have_text("Selected: avatar.png")
  • page.expect_file_chooser() starts waiting before the click, like expect_popup() in Lesson 34.
  • chooser.set_files(...) accepts the same arguments as set_input_files. The page’s change event fires, so the page reacts exactly as it would for a real user.
  • You could also call set_input_files directly on the hidden input; it works on hidden inputs too. The file chooser approach is useful when you do not know where the input is.

📥 Downloading Files: expect_download

When a click starts a download, wrap it in page.expect_download(). The Download object lets you read the file name, save the file and check its content:

📄 tests/test_download.py

import csv
from pathlib import Path

from playwright.sync_api import Page

PAGE = """
<a id="static" href="data:text/csv;charset=utf-8,id%2Cname%0A1%2CDeepesh%0A2%2CRahul" download="users.csv">Export users</a>
<button id="generate">Generate report</button>
<script>
  document.getElementById('generate').onclick = () => {
    const text = 'Total tests: 42\\nPassed: 40\\nFailed: 2';
    const url = URL.createObjectURL(new Blob([text], { type: 'text/plain' }));
    const a = document.createElement('a');
    a.href = url; a.download = 'summary.txt'; a.click();
  };
</script>
"""


def test_download_csv(page: Page, tmp_path: Path):
    page.set_content(PAGE)

    with page.expect_download() as download_info:
        page.get_by_role("link", name="Export users").click()
    download = download_info.value

    print("\nsuggested name:", download.suggested_filename)
    target = tmp_path / download.suggested_filename
    download.save_as(target)

    with open(target, newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))
    print("rows:", rows)
    assert rows == [{"id": "1", "name": "Deepesh"}, {"id": "2", "name": "Rahul"}]


def test_download_generated_by_javascript(page: Page):
    page.set_content(PAGE)

    with page.expect_download() as download_info:
        page.get_by_role("button", name="Generate report").click()
    download = download_info.value

    assert download.failure() is None              # None means the download succeeded
    content = Path(download.path()).read_text()
    print("\n" + download.suggested_filename + ":", content.splitlines())
    assert "Failed: 2" in content

Line by line:

  • page.expect_download() waits for a download that starts during the with block.
  • download.suggested_filename is the name the site proposed (from the download attribute or the server’s headers).
  • download.save_as(path) copies the file to a location you choose. Here it goes to pytest’s tmp_path, which is cleaned up automatically.
  • download.path() returns where Playwright saved the file temporarily; it waits until the download has finished. download.failure() returns None on success or an error message.
  • The second download is generated by JavaScript in the browser (a Blob turned into a temporary URL), the same technique many “Export” buttons use. Playwright captures it the same way.
  • Python’s csv.DictReader turns the CSV into a list of dictionaries, so the test checks the content, not just that a file arrived.

🖥️ Output

suggested name: users.csv
rows: [{'id': '1', 'name': 'Deepesh'}, {'id': '2', 'name': 'Rahul'}]
summary.txt: ['Total tests: 42', 'Passed: 40', 'Failed: 2']

📁 Where Do Downloads Go?

  • By default, Playwright saves downloads in a temporary folder that is deleted when the browser context closes. With pytest-playwright that happens at the end of each test, so call save_as() if you need the file afterwards.
  • Downloads are accepted by default in pytest-playwright contexts (accept_downloads=True).
  • To keep all downloads in a fixed folder, pass downloads_path in browser_type_launch_args (Lesson 6), but save_as() per test is usually clearer.

⌨️ Terminal

pytest -q -s tests

🖥️ Output

suggested name: users.csv
rows: [{'id': '1', 'name': 'Deepesh'}, {'id': '2', 'name': 'Rahul'}]
.
summary.txt: ['Total tests: 42', 'Passed: 40', 'Failed: 2']
.
selected: ['report.csv (20 bytes)']
.
in memory: ['notes.txt (29 bytes)']
...
6 passed, 1 warning in 7.86s

❓ Frequently Asked Questions

🤔 Can Playwright click “Browse” and pick a file in the Windows dialog?

No automation tool should drive the native dialog. Playwright intercepts it instead: use set_input_files() on the input, or expect_file_chooser() around the click.

🤔 Why is the input value C:\fakepath\… on Linux too?

It is a browser security rule: the real path is never revealed to the page, on any operating system.

🤔 How do I check that the download was a valid Excel or PDF file?

Save it, then open it with a Python library: openpyxl for Excel, pypdf for PDF, csv or json for text formats. Module 7 covers reading test data files.

🤔 My download test fails with a timeout. Why?

The click did not start a download (it may open the file in a new tab instead), or the download started before the with page.expect_download() block. Start waiting first, then click inside the block.


🏁 Summary

  • set_input_files() selects files on an <input type="file">: paths, lists, in-memory buffers, or [] to clear.
  • expect_file_chooser() handles upload buttons that open the native dialog.
  • expect_download() captures downloads; use suggested_filename, save_as(), path() and failure(), then verify the content.
  • Downloads live in a temporary folder that disappears with the context, so save what you need.

That completes Module 4. Test yourself with the Module 4 mock interview, then continue to Module 5: assertions, waits and screenshots.

🎬 Watch the Playwright tutorial series on YouTube!

Subscribe to SqaToolsByDY • Practice on the SQA Tools Automation Practice Page

Leave a Comment