Playwright for Python: Authentication


Playwright executes tests in an isolated environment called a browser context. This isolation model improves reproducibility and prevents cascading test failures. Tests can load existing authentication state. This eliminates the need to authenticate in every test and speeds up test execution.

Core idea

No matter what authentication strategy you choose, you will most likely store the authenticated browser state in the file system.

We recommend creating playwright/.auth the directory and adding it to .gitignorethe file. Your authenticator will generate the authenticated browser state and save it to a playwright/.auth file in this directory. Later, the test will reuse this state and start in the already authenticated state.

Bash

mkdir -p playwright/.auth
echo "\nplaywright/.auth" >> .gitignore

PowerShell

New-Item -ItemType Directory -Force -Path playwright\.auth
Add-Content -path .gitignore "`r`nplaywright/.auth"

Batch

md playwright\.auth
echo. >> .gitignore
echo "playwright/.auth" >> .gitignore

Register before each test

The Playwright API can automate interactions with login forms.

Re-executing the login for each test may slow down the execution of the tests. To alleviate this problem, existing authentication state can be reused.

Here is an example of a registration process:

# test_demo.py
from playwright.sync_api import Page, expect
from faker import Faker
import re

faker = Faker()
import random


def test_example(page: Page) -> None:
    page.goto("https://www.gitlink.org.cn")
    page.locator(selector="//a[text()='登录']/following-sibling::a").click()
    page.get_by_text("邮箱注册").click()
    page.locator(selector="//input[@id='register_register_username']").fill(f"glcc_{
      
      random.randint(1, 100)}")
    page.get_by_placeholder("请输入邮箱地址").fill(f"glcc_{
      
      random.randint(1, 100)}@gitlink.com")
    page.get_by_placeholder("请输入验证码").fill("123123")
    page.get_by_placeholder("请输入登录密码").fill("12345678")
    page.get_by_placeholder("请确认登录密码").fill("12345678")
    page.get_by_label("我已阅读并接受《GitLink服务协议条款》").check()
    page.locator("button").filter(has_text="注 册").click()
    expect(page, "检查用户注册后是否跳转到个人主页").to_have_title(re.compile("glcc_"), timeout=5_000)
	
# main.py
import pytest
pytest.main(['--headed', '--browser=chromium', "--browser-channel=chrome"])

Reuse logged in status

Playwright provides a way to reuse logged in status in tests. This way you can log in only once and skip the login step in all tests.

Web applications use cookie or token-based authentication, and the authenticated status is stored in a cookie or local storage. Playwright provides browserContext.storageState([options]) methods for retrieving stored state from an authenticated context and creating a new context with prepopulated state.

Cookies and local storage state can be used across different browsers, depending on the application's authentication model: some applications may require both cookies and local storage.

The following code snippet retrieves the status of an authenticated context and uses that status to create a new context.

Example 1

Utilize login status from other testing methods.

# test_demo.py
from playwright.sync_api import Page, expect
from faker import Faker
import re

faker = Faker()
import random

# 创建一个全局变量来保存存储状态
storage = None


def test_register(page: Page) -> None:
    global storage
    page.goto("https://www.gitlink.org.cn")
    page.locator(selector="//a[text()='登录']/following-sibling::a").click()
    page.get_by_text("邮箱注册").click()
    page.locator(selector="//input[@id='register_register_username']").fill(f"glcc_{
      
      random.randint(1, 100)}")
    page.get_by_placeholder("请输入邮箱地址").fill(f"glcc_23{
      
      random.randint(1, 100)}@gitlink.com")
    page.get_by_placeholder("请输入验证码").fill("123123")
    page.get_by_placeholder("请输入登录密码").fill("12345678")
    page.get_by_placeholder("请确认登录密码").fill("12345678")
    page.get_by_label("我已阅读并接受《GitLink服务协议条款》").check()
    page.locator("button").filter(has_text="注 册").click()
    expect(page, "检查用户注册后是否跳转到个人主页").to_have_title(re.compile("glcc_"), timeout=5_000)
    # 登录成功后获取当前上下文的存储状态,并存储在文件state.json中
    storage = page.context.storage_state(path="state.json")


# 在其他测试中,创建新的上下文并使用之前保存的存储状态
def test_new_project(page: Page) -> None:
    global storage
    """
    第1种写法
    创建新的上下文,使用之前存储的状态文件state.json
     # new_context = page.context.browser.new_context(storage_state=storage)
    # new_page = new_context.new_page()
    """
    
    """
    第2种写法
    直接新建一个页面,使用之前存储的状态文件state.json
    """
    new_page = page.context.browser.new_page(storage_state=storage)
    #  TODO 这两种方式上,虽然用例可以执行成功,但是新开了好几个浏览器窗口
    
    # 执行其他测试操作(此时是登录状态)
    new_page.goto("https://www.gitlink.org.cn")
    new_page.locator("#nHeader img").nth(1).click()
    new_page.get_by_text("新建项目").click()
    expect(new_page, "检查是否跳转到了新建项目页面").to_have_title(re.compile("新建项目"), timeout=5_000)
	
# main.py
import pytest
pytest.main(['--headed', '--browser=chromium', "--browser-channel=chrome"])

Example 2

Write the login state in contest.pyas a fixture.

# contest.py
import pytest
import re
from playwright.sync_api import Page, expect


# 定义全局登录
@pytest.fixture(scope="function", autouse=True)
def page(page: Page):
    page.goto("https://www.gitlink.org.cn/login")
    page.locator(selector="//input[@id='login_username']").fill("chytest10")
    page.locator(selector="//input[@id='login_password']").fill("12345678")
    page.locator("span").filter(has_text="登 录").click()
    expect(page, "检查用户登录后是否跳转到个人主页").to_have_title(re.compile("chytest10"), timeout=5_000)
    # 登录成功后获取当前上下文的存储状态,并存储在文件state.json中
    storage = page.context.storage_state(path="state.json")
    new_page = page.context.browser.new_page(storage_state=storage)
    yield new_page
	
	
# test_demo.py
from playwright.sync_api import Page, expect
import re

def test_new_project_02(page: Page) -> None:
    page.goto("https://www.gitlink.org.cn")
    page.locator("#nHeader img").nth(1).click()
    page.get_by_text("新建项目").click()
    expect(page, "检查是否跳转到了新建项目页面").to_have_title(re.compile("新建项目"), timeout=5_000)
	
# main.py
import pytest
pytest.main(['--headed', '--browser=chromium', "--browser-channel=chrome"])

Advanced scene

Reusing authentication state covers cookie and local storage based authentication. In rare cases, Session storage is used to store information related to login status. Session storage is domain-specific and is not persisted across page loads. Playwright does not provide an API to persist Session storage, but you can use the following code snippet to save/load Session storage.

# contest.py
import pytest
import re
import os
from playwright.sync_api import Page, expect


# 定义全局登录
@pytest.fixture(scope="function", autouse=True)
def page(page: Page):
    page.goto("https://www.gitlink.org.cn/login")
    page.locator(selector="//input[@id='login_username']").fill("chytest10")
    page.locator(selector="//input[@id='login_password']").fill("12345678")
    page.locator("span").filter(has_text="登 录").click()
    expect(page, "检查用户登录后是否跳转到个人主页").to_have_title(re.compile("chytest10"), timeout=5_000)
    # 获取当前页面的会话存储
    session_storage = page.evaluate("() => JSON.stringify(sessionStorage)")
    # 将会话存储存储为环境变量
    os.environ["SESSION_STORAGE"] = session_storage
    session_storage = os.environ["SESSION_STORAGE"]
    new_context = page.context
    new_context.add_init_script("""(storage => {
      if (window.location.hostname === 'example.com') {
        const entries = JSON.parse(storage)
        for (const [key, value] of Object.entries(entries)) {
          window.sessionStorage.setItem(key, value)
        }
      }
    })('""" + session_storage + "')")
    new_page = new_context.new_page()
    yield new_page
	
# test_demo.py
from playwright.sync_api import Page, expect
import re


def test_new_project_02(page: Page) -> None:
    page.goto("https://www.gitlink.org.cn")
    page.locator("#nHeader img").nth(1).click()
    page.get_by_text("新建项目").click()
    expect(page, "检查是否跳转到了新建项目页面").to_have_title(re.compile("新建项目"), timeout=5_000)
	
# main.py
import pytest
pytest.main(['--headed', '--browser=chromium', "--browser-channel=chrome"])

Guess you like

Origin blog.csdn.net/FloraCHY/article/details/132711033