Skip to content

File operations

Uploading and downloading files means dealing with OS-level dialogs that most automation tools can't reach. Pydoll handles both for you: it sets the files a chooser asks for, and it captures a download without you polling the filesystem for it to finish.

Upload to a file input

When the page has a real <input type="file">, find it and call set_input_files(). This sets the files directly, without opening the OS dialog.

import asyncio
from pathlib import Path

from pydoll.browser.chromium import Chrome


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://the-internet.herokuapp.com/upload')

        file_input = await tab.find(id='file-upload')
        await file_input.set_input_files(Path('report.pdf'))

        submit = await tab.find(id='file-submit')
        await submit.click()

        result = await tab.find(tag_name='h3')
        print(await result.text)   # "File Uploaded!"

asyncio.run(main())

set_input_files() accepts a str, a pathlib.Path, or a list of them. A Path is the more portable choice, but a plain string works too:

await file_input.set_input_files('report.pdf')
await file_input.set_input_files(Path.home() / 'Documents' / 'report.pdf')

Upload several files at once

For an input marked multiple, pass a list. You can build that list however you like, including with Path.glob():

await file_input.set_input_files([
    Path('report.pdf'),
    Path('cover.png'),
])

# every CSV in a folder
csv_files = list(Path('data').glob('*.csv'))
await file_input.set_input_files(csv_files)

Upload through a file chooser dialog

Many sites hide the file input behind a styled button or a drag-and-drop zone, so clicking it opens the OS file chooser. set_input_files() has nothing to target there. Wrap the click in expect_file_chooser() instead: it intercepts the dialog and sets your files when it opens.

import asyncio
from pathlib import Path

from pydoll.browser.chromium import Chrome


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://example-uploads.test/gallery')

        async with tab.expect_file_chooser(files=Path('cover.png')):
            upload_button = await tab.find(class_name='upload-button')
            await upload_button.click()

        print('File selected through the chooser.')

asyncio.run(main())

The click that opens the dialog goes inside the async with block. When the chooser opens, Pydoll fills it with your files; you never see the native dialog. files takes the same str, Path, or list that set_input_files() accepts.

Which upload method?

Reach for set_input_files() when a real <input type="file"> is in the DOM; it is instant and needs no dialog. Use expect_file_chooser() when the input is hidden and a custom control opens the OS chooser.

Download a file

Wrap the action that starts a download in expect_download(). Pydoll waits for the download to complete and hands you a handle to read it, so you don't poll a directory waiting for the file to appear.

import asyncio

from pydoll.browser.chromium import Chrome


async def main():
    async with Chrome() as browser:
        tab = await browser.start()
        await tab.go_to('https://the-internet.herokuapp.com/download')

        async with tab.expect_download() as download:
            link = await tab.query('.example a')
            await link.click()
            data = await download.read_bytes()

        print(f'Downloaded {len(data)} bytes')

asyncio.run(main())

Read the file inside the block. By default the download lands in a temporary directory that is cleaned up when the block exits, so read_bytes() (or read_base64()) is how you capture the contents. The trigger, here a link click, also goes inside the block.

Keep the file on disk

Pass keep_file_at with a directory to persist the download instead of using a throwaway temp dir. The file remains after the block ends, and download.file_path tells you where it landed:

async with tab.expect_download(keep_file_at='downloads/') as download:
    link = await tab.query('.example a')
    await link.click()
    await download.wait_finished()

print(f'Saved to {download.file_path}')

expect_download() waits up to 60 seconds by default; pass timeout in seconds to change that. The handle also offers read_base64() when you need the contents as a base64 string.

What's next

  • Events: the page events that expect_file_chooser() and expect_download() build on.
  • Screenshots and PDFs: save a page as a PDF instead of downloading one.
  • Element finding: locate the inputs and links these operations act on.