Many automation tasks require being logged in. The naive approach is to fill the login form every time, but that is slow and breaks if the page changes.
Playwright lets you save and restore browser sessions using storage_state:
from playwright.sync_api import sync_playwright
def save_session(url: str, username: str, password: str, save_path: str):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto(url)
page.fill("#username", username)
page.fill("#password", password)
page.click("#login-button")
page.wait_for_url("**/dashboard")
context = browser.contexts[0]
context.storage_state(path=save_path)
print(f"Session saved to {save_path}")
browser.close()
save_session(
"https://example.com/login",
"your-username",
"your-password",
"session.json"
)
Once you have the session file, you can reuse it without logging in again:
from playwright.sync_api import sync_playwright
def load_session(start_url: str, session_path: str):
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="./profile",
storage_state=session_path
)
page = context.new_page()
page.goto(start_url)
if "login" not in page.url:
print("Session restored, already logged in")
else:
print("Session expired, need to login again")
browser = context.browser
browser.close()
The storage_state file contains cookies and localStorage data. It works across browser launches.
For sites with 2FA, automate the login once manually and save the session. Then reuse it for all subsequent runs:
def login_with_2fa_once(save_path: str):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://github.com/login")
page.fill("#login_field", "your-email")
page.fill("#password", "your-password")
page.click("[name=commit]")
input("Enter the 2FA code and press Enter here after logging in...")
context = browser.contexts[0]
context.storage_state(path=save_path)
print(f"Session with 2FA saved to {save_path}")
browser.close()
This pauses and waits for you to enter the 2FA code manually. After that the session is saved and can be reused.
You can also handle session expiration by checking the current URL and retrying the login:
def with_session(url: str, session_path: str, login_fn):
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="./profile",
storage_state=session_path
)
page = context.new_page()
page.goto(url)
if "login" in page.url:
print("Session expired, logging in again")
login_fn(page)
context.storage_state(path=session_path)
return page
This pattern checks if the page redirected to a login page and refreshes the session automatically.
Using saved sessions turns a multi step login into a one second page load.
That's all for now.
Thanks for reading!
Top comments (1)
Solid writeup. The storage_state approach works great until you hit sites with aggressive cookie expiry or 2FA that rotates every session. I ran a project scraping 30+ sites daily and writing login logic per site became a maintenance nightmare. Switched to browser-act CLI which handles session persistence natively -- just open the browser once in headed mode, log in manually, and subsequent runs reuse the session without any storage_state boilerplate. The
browser opencommand with a session name is all you need. Saved me from maintaining login scripts for each target.