Browser Automation with Python: Log In, Download Reports and Fill Forms with Playwright

For many Hong Kong office workers, the first task of the day is logging into one portal after another: the bank back office for monthly statements, a supplier site to check stock, a logistics platform to trace shipments, a government site to submit a form, an ad platform to copy down the numbers. Each action takes only seconds, but multiplied across a dozen portals and twenty working days, it adds up. Worse, the steps are identical every time, require no judgement, and leave you with nothing to show for the effort.

This kind of repetitive clicking inside a browser is exactly what browser automation is built for. Python has several tools for the job, and Playwright is the smoothest of the current generation: it opens a real browser and follows your scripted steps — clicking, typing, waiting for downloads — at human speed, but without getting tired or skipping a step.

How is Playwright different from Selenium?

Selenium is the veteran tool and most tutorials still use it, but in practice the most common failure is clicking an element before it has loaded: the page is half a second slow and the script throws an error, so people scatter sleep(3) everywhere and end up with something slow and unreliable. Playwright waits automatically: before clicking, it waits for the element to be present, visible and clickable, and only then times out. It also drives Chromium, Firefox and WebKit, can emulate a phone screen, and ships with codegen, which records your clicks as code — a big help for colleagues who are not front-end developers.

Step 1: Install

Install the Python package, then download the browser itself (Chromium is about 150MB):

pip install playwright playwright install chromium

The second line is not optional. Playwright does not use the Chrome already on your machine; it uses its own bundled build so behaviour is identical on every computer.

Use case 1: Log in and download a monthly statement

Here is the most common scenario. Suppose that on the first of every month you log into a bank or supplier portal and download a PDF statement:

from playwright.sync_api import sync_playwright import os, datetime USER = os.environ["PORTAL_USER"] PWD = os.environ["PORTAL_PASS"] month = datetime.date.today().strftime("%Y%m") with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page(accept_downloads=True) page.goto("https://portal.example.com/login") page.get_by_label("Account").fill(USER) page.get_by_label("Password").fill(PWD) page.get_by_role("button", name="Sign in").click() page.wait_for_url("**/dashboard") with page.expect_download() as dl: page.get_by_role("link", name="Download statement").click() dl.value.save_as("statement_" + month + ".pdf") browser.close() print("Downloaded statement_" + month + ".pdf")

Line by line

  • sync_playwright() — creates the automation environment; everything runs inside the with block, which cleans up on exit.
  • launch(headless=True) — runs without showing a browser window. For your first script, set it to False so you can watch what happens; debugging is far quicker.
  • accept_downloads=True — required, otherwise a file triggered by a click simply vanishes.
  • get_by_label / get_by_role — finds elements by the text you actually see on screen ("Account", "Password", "Sign in"), so you do not have to study the HTML and the script survives most redesigns.
  • wait_for_url("**/dashboard") — waits for the redirect before continuing; the most practical way to confirm a successful login.
  • expect_download() — wraps the click that triggers a download, after which save_as() stores it at the path and filename you choose.
  • os.environ — reads the username and password from environment variables. Never hard-code them, and never commit them to GitHub.

Use case 2: Read a CSV, batch-query and screenshot

Another frequent job: you have a list of order or customer numbers, you paste each one into a system to check its status, then screenshot the result. Same fields, different input — a loop handles it best:

import csv from pathlib import Path from playwright.sync_api import sync_playwright Path("shots").mkdir(exist_ok=True) with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context(storage_state="login.json") page = context.new_page() page.goto("https://portal.example.com/orders") with open("orders.csv", encoding="utf-8") as f: for row in csv.DictReader(f): oid = row["order_id"] page.get_by_label("Order no.").fill(oid) page.get_by_role("button", name="Search").click() page.wait_for_selector(".result-card") page.screenshot(path="shots/" + oid + ".png", full_page=True) print("Captured", oid) browser.close()

Note the line storage_state="login.json". Playwright can save the cookies and session from a logged-in browser into a json file; loading it later means you skip the login page entirely, and a password change will not break the script. Log in manually once, then add context.storage_state(path="login.json") to save it.

Four things to watch in production

  • Never locate elements by position — page.locator("div:nth-child(3) > button") breaks the moment the site is redesigned; text-based locators (get_by_role, get_by_text) are far more durable.
  • Two-factor authentication needs a human — for systems that send an SMS code, a saved storage_state usually avoids the prompt. If a code is genuinely required every time, go semi-automatic: let the script open the page and a person types the code.
  • Do not use this to grab limited slots or break a site's terms — automation is for your own repetitive work, not for circumventing someone else's rules; aggressive request rates get your IP blocked and cost more than they save.
  • Add error handling and logging — wrap each step in try / except, and on failure take a screenshot and write one log line, so you know which portal and which step went wrong.

What the savings actually look like

Take an admin colleague who handles six portal reports a month at eight minutes each: roughly 48 minutes a month spent purely on logging in, clicking, downloading and renaming. Once a script is written, a run takes two to three minutes, and it can be scheduled at start-up so the files are waiting when you arrive. Batch order lookups are even clearer: a hundred orders at one minute each is an hour and a half by hand, against ten to fifteen minutes by script — time you can spend on something else entirely.

The real value is not the minutes saved. It is that these small, easily-forgotten, judgement-free steps become fixed: nothing is missed when someone is on leave, no number is copied wrongly, and no step is skipped because the day got busy.

Summary

Python Tutorial: Playwright is easier to start with than it looks — two install commands, and the core is just five actions: open a browser, go to a URL, fill a field, click a button, wait for a download. Start with the simplest, lowest-risk scenario, such as downloading one report from one portal every morning; confirm it is stable, then add the CSV loop and screenshots. Keep three rules: credentials in environment variables, locators based on visible text, and error logging on every step.