Skip to main content

_python

TL;DR - Show Me The Code

2-download-a-file/code/python/download.py
import os
import shutil
import pytest
import tempfile
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

@pytest.fixture
def setup_teardown():
download_dir = tempfile.mkdtemp()
options = webdriver.FirefoxOptions()
options.set_preference("browser.download.dir", download_dir)
options.set_preference("browser.download.folderList", 2)
options.set_preference("browser.helperApps.neverAsk.saveToDisk",
"images/jpeg, application/pdf, application/octet-stream")
options.set_preference("pdfjs.disabled", True)
driver = webdriver.Firefox(options=options)
yield driver, download_dir
driver.quit()
shutil.rmtree(download_dir)

def test_file_download(setup_teardown):
driver, download_dir = setup_teardown
driver.get('https://the-internet.herokuapp.com/download')
download_link = driver.find_element(By.CSS_SELECTOR, '.example a')
download_link.click()

# Wait up to 5 seconds for the file to be present (slow connections)
try:
WebDriverWait(driver, 5).until(lambda d: os.listdir(download_dir))
except TimeoutException:
assert False, "no files were downloaded within 5 seconds"

files = os.listdir(download_dir)
files = [os.path.join(download_dir, f) for f in files] # add directory to each filename
assert len(files) > 0, "no files were downloaded"
assert os.path.getsize(files[0]) > 0, "downloaded file was empty"

Code Walkthrough

Importing Libraries

Lines 1 to 8 are pulling in our requisite libraries for interacting with the operating system (e.g., import os), creating a temporary directory and cleaning it up, our testing framework (e.g., import pytest), driving the browser with Selenium (e.g., from selenium import webdriver), locator strategy to find the elements (e.g. from selenium.webdriver.common.by import By), and Selenium's wait functionality (e.g., from selenium.webdriver.support.ui import WebDriverWait).

Setup and Teardown

Lines 12 to 22 are setting up and tearing down the browser instance, a uniquely named temp directory is also created (e.g., download_dir = tempfile.mkdtemp()) to download the file. Firefox is then configured to make it automatically download the file where we want (e.g., in the newly created temp directory).

Here's a breakdown of each of the browser preferences being set:

  • browser.download.dir accepts a string. This is how we set the custom download path. It needs to be an absolute path.
  • browser.download.folderList takes a number. It tells Firefox which download directory to use. 2 tells it to use a custom download path, whereas 1 would use the browser's default path, and 0 would place them on the Desktop.
  • browser.helperApps.neverAsk.saveToDisk tells Firefox when not to prompt for a file download. It accepts a string of the file's MIME type. If you want to specify more than one, you do it with a comma-separated string (which we've done).
  • pdfjs.disabled is for when downloading PDFs. This overrides the sensible default in Firefox that previews PDFs in the browser. It accepts a boolean.

This object is then passed into our instance of Selenium (e.g., driver = webdriver.Firefox(options=options).

The @pytest.fixture decorator is used to create a fixture that sets up and tears down the browser. The yield keyword is used to pause the fixture until the test is completed. After the test is completed, the fixture resumes and executes the teardown code (in this case, driver.quit() and delete the temporal directory).

The Test

Lines 24 to 39 are the test itself.

After visiting the page we find the first download link and click it. The click triggers an automatic download to the temp directory created in the @pytest.fixture. We need to wait for the download to finish, so we use a WebDriverWait with a 5 second timeout. We then check that the temp directory isn't empty and that the file isn't empty. After the file downloads, we perform some rudimentary checks to make sure the unique temp directory isn't empty and then check the file to see that it isn't empty either.

Executing the Test

Before executing the test, we need to install the required libraries. We can do this by running pip install -r requirements.txt from the command-line. The requirements.txt file contains the libraries we need to install.

Toggle to see the requirements.txt file.
2-download-a-file/code/python/requirements.txt
selenium==4.26.0
pytest

After installing the required libraries, we can run the test by executing pytest download.py from the command-line.