Chapter 11Lesson 01~165 minutes

File Uploads, Downloads, Cookies, Storage, and Session State: Core Concepts and Mental Model

Browser automation crosses several state stores that look similar from test code but have different owners and lifetimes. This lesson maps files, cookies, Web Storage, profiles, and remote transfer boundaries before any mutation occurs.

File stateCookiesWeb StorageProfilesGrid boundaries

Learning objectives

  • Distinguish test-runner files, browser upload selection, browser downloads, cookies, Web Storage, and profile state.
  • Explain local versus remote/Grid file path semantics and why a path string is not universally meaningful.
  • Define cookie domain/path/expiry/SameSite scope and distinguish cookies from localStorage/sessionStorage.
  • Explain browser-profile state versus per-WebDriver-session state and why personal profiles are unsafe test dependencies.
  • Perform read-only provenance and state inspection before creating files, cookies, or storage keys.
  • Connect state isolation to reproducible CI and parallel execution.

1. The practical problem: “session state” is not one thing

A browser test can pass locally and fail in CI even when locators and waits are correct. The hidden cause is often ownership: the upload file exists on the test runner but not on the remote browser host; a download lands in yesterday's directory; a cookie belongs to a different host; localStorage survives because a profile was reused; or a test assumes deleting cookies also deletes every authentication/session artifact.

Chapter 10 taught that DOM state has boundaries. Chapter 11 applies the same discipline to browser-held state and filesystem state.

Core rule: name the state store, owner, scope, and cleanup action before mutating it. “Clear the session” is too vague for reliable automation.

2. Mental model: ownership and transport

File and browser-state ownership boundaries

The following diagram visualizes the relationships described in Mental model: ownership and transport. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.

flowchart TD
  T[Test process] -->|absolute local path| F[Upload file]
  T --> W[WebDriver session]
  F --> I[input type=file]
  W --> B[Browser context]
  B --> A[AUT origin]
  A --> C[Cookies: domain/path/expiry/sameSite]
  A --> LS[localStorage: origin-scoped]
  A --> SS[sessionStorage: origin + top-level context]
  B --> P[Browser profile / cache / preferences]
  B --> D[Browser download manager]
  D --> DD[Per-test download directory]
  W -. Remote/Grid .-> R[Remote browser host]
  T -. file detector transfer .-> R
  R -. downloadable-file API when enabled .-> T

The test process owns generated input files and evidence. The browser owns its current profile, cookie jar, storage areas, and download manager. The AUT origin determines which cookies and Web Storage entries are visible. A remote/Grid session introduces another machine boundary: the path C:\work\input.txt on the runner is not automatically a path on the node.

3. State stores are different contracts

The following table organizes the key choices and evidence for State stores are different contracts. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Store Scope / owner Typical mutation Deterministic cleanup
Upload source test-runner filesystem; remote sessions may transfer it send absolute path to input[type=file] delete generated source after test
Download browser download manager → configured directory or remote download store click/download request remove per-test directory; remote store may need delete call
Cookie current browser session, constrained by domain/path/expiry/security attributes add_cookie / application response delete_cookie or delete_all_cookies
localStorage origin scoped; can persist with profile page-context Web Storage API remove key/clear for that origin
sessionStorage origin + top-level browsing context lifetime page-context Web Storage API remove/clear or close context/session
Browser profile browser process/profile directory preferences/cache/history/storage use disposable profile; quit then remove directory

4. Upload semantics: Selenium does not drive the OS chooser

The supported WebDriver technique is to locate an actual HTML file input and send an absolute path. Selenium deliberately avoids automating the native file picker.

from pathlib import Path
from selenium.webdriver.common.by import By

upload_path = Path("input.txt").resolve()
assert upload_path.is_file()
driver.find_element(By.CSS_SELECTOR, 'input[type="file"]').send_keys(str(upload_path))

For local WebDriver, the browser and test code run on the same machine boundary, so the path is directly usable. With Python RemoteWebDriver, the default LocalFileDetector recognizes a local file path and supports transfer to the remote end. The engineering rule is still: never assume the remote node can directly see the runner's filesystem path.

5. Download semantics: browser-managed output is not a normal WebDriver element

A download leaves DOM interaction and enters browser/file state. For the mandatory Chromium-local path, this chapter uses a unique per-test directory configured before session creation, then verifies the resulting bytes from the test process.

Selenium also exposes remote downloadable-file commands when downloads are enabled for a RemoteWebDriver session. Those commands solve a different problem: transferring a completed browser download from the remote node back to the test runner. They do not turn browser download progress into a portable DOM-style synchronization primitive.

Do not use a shared “Downloads” folder as test state. A stale file can make a failed download look successful.

6. Cookies: host/path/security metadata matters

A cookie is not just a name/value pair. WebDriver accepts attributes such as path, domain, secure, httpOnly, expiry, and sameSite. To add a host-scoped test cookie safely, navigate to the target origin first and omit a domain unless the scenario specifically requires one.

driver.get("http://127.0.0.1:8776/")
driver.add_cookie({"name": "academy_mode", "value": "synthetic", "sameSite": "Lax"})
print(driver.get_cookie("academy_mode"))

Trying to add a cookie for an unrelated domain can raise InvalidCookieDomainException. That is a scope error, not a reason to disable browser security.

7. Web Storage is origin-scoped and not a W3C WebDriver command set

localStorage and sessionStorage belong to the page origin. Current Selenium guidance treats their old dedicated command surfaces as deprecated because Web Storage is not part of W3C WebDriver. When a test must inspect this application state, use a narrow page-context script and keep it separate from UI interaction.

driver.get("http://127.0.0.1:8776/")
local_count = driver.execute_script("return window.localStorage.length")
session_count = driver.execute_script("return window.sessionStorage.length")
print("storage_counts", local_count, session_count)
This is not a JavaScript click bypass. The script reads a browser storage API for which classic WebDriver has no standard command. User interactions remain native WebDriver interactions.

8. Profile state is broader than cookies

A browser profile can contain cookies, local storage, caches, preferences, service-worker state, history, downloads, credentials, extensions, and browser-specific data. Therefore delete_all_cookies() does not mean “fresh browser.” For deterministic tests, prefer a new WebDriver session and a disposable profile rather than a personal or shared profile.

9. Read-only inspection before mutation

The following example makes the Read-only inspection before mutation behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

import selenium
from selenium import webdriver

URL = "http://127.0.0.1:8776/"
driver = webdriver.Chrome()
try:
    driver.get(URL)
    caps = driver.capabilities
    snapshot = {
        "selenium": selenium.__version__,
        "session_id": driver.session_id,
        "browser": caps.get("browserName"),
        "browser_version": caps.get("browserVersion"),
        "url": driver.current_url,
        "title": driver.title,
        "cookie_names": [c["name"] for c in driver.get_cookies()],
        "local_storage_keys": driver.execute_script("return Object.keys(window.localStorage)"),
        "session_storage_keys": driver.execute_script("return Object.keys(window.sessionStorage)"),
    }
    print(snapshot)
finally:
    driver.quit()

The snapshot proves current browser/session/origin state without modifying it. In production evidence, record names and counts rather than secret values.

10. Credentials, PII, and artifact boundaries

Profiles and downloads are high-risk evidence sources because they can contain tokens, customer exports, email addresses, or generated documents. Mandatory labs use synthetic text only. Production automation should classify which files are retained, redact sensitive values from logs, restrict artifact access, and set retention limits.

11. DevOps connection: isolation makes reruns meaningful

Parallel CI depends on independent download directories, independent profile/session state, and input files with deterministic provenance. If retry 2 can consume retry 1's cookie or file, the rerun is not evidence about the same test—it is a different experiment.

12. Summary and next step

Uploads, downloads, cookies, Web Storage, and browser profiles are separate state stores. Reliable automation specifies where each lives, how it crosses local/remote boundaries, what proves a change occurred, and how cleanup restores isolation.

Knowledge check

Why should a test not automate the native OS file picker?

Why can a local upload path fail on Grid?

Does deleting cookies clear localStorage?

Why is a shared download directory dangerous in CI?

When is execute_script() appropriate in this chapter?

Next lesson

Operate a complete local file-and-state workflow

Lesson 2 generates a harmless file, uploads it, produces a controlled download, verifies exact bytes, manipulates synthetic cookie/storage state, and cleans every artifact explicitly.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current primary documentation on 2026-08-28. Mandatory examples pin Selenium Python 4.47.0, require Python 3.10+, use a supported locally installed Chromium-family browser with Selenium Manager for driver resolution, and target only 127.0.0.1. Chromium download preferences are intentionally labeled browser-specific. Remote/Grid file-transfer and downloadable-file APIs are optional extensions to the local path. JavaScript execution appears only for Web Storage access, where Selenium explicitly notes local/session storage are not W3C WebDriver commands. UI interactions remain native WebDriver interactions.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.