别再傻傻每次跑测试都登录了!用Playwright的storageState保存登录态,效率翻倍
Playwright实战:用storageState实现登录态持久化,告别重复认证
每次执行需要登录的自动化测试时,你是否厌倦了反复输入账号密码?当测试套件包含数十个依赖登录状态的用例时,重复认证不仅浪费时间,还会拖慢整个CI/CD流程。本文将深入探讨如何利用Playwright的storageState功能,将登录状态保存为JSON文件,实现"一次认证,多次复用"的高效测试模式。
1. 为什么需要持久化登录状态
在自动化测试中,登录操作往往是耗时大户。以一个典型电商平台为例,完整的登录流程可能涉及:
- 加载登录页面(2-3秒)
- 填写用户名密码(1秒)
- 提交表单等待跳转(3-5秒)
- 二次验证(如有,额外5-10秒)
假设测试套件包含20个需要登录的用例,每次执行都重新登录将浪费至少10分钟在认证环节。更糟糕的是,当登录接口不稳定时,这种重复操作会显著增加测试的脆弱性。
Playwright的浏览器上下文隔离机制虽然保证了测试的独立性,但也意味着默认情况下不同测试之间无法共享认证状态。这就是storageState的价值所在——它允许我们将认证后的上下文状态序列化为JSON文件,包含:
- Cookies
- LocalStorage
- IndexedDB数据
# 典型登录状态保存文件结构示例 { "cookies": [ { "name": "session_id", "value": "abc123", "domain": "example.com", "path": "/", "expires": 1735689600, "httpOnly": true, "secure": true, "sameSite": "Lax" } ], "origins": [ { "origin": "https://example.com", "localStorage": [ {"name": "user_token", "value": "xyz789"} ] } ] }2. 实战:保存和复用登录状态
2.1 基础保存与加载
让我们通过一个完整示例演示如何保存GitHub登录状态。首先创建auth_setup.py:
from playwright.sync_api import sync_playwright def save_github_auth_state(): with sync_playwright() as p: browser = p.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() # 执行登录流程 page.goto('https://github.com/login') page.fill('#login_field', 'your_username') page.fill('#password', 'your_password') page.click('[name="commit"]') # 验证登录成功 assert "GitHub" in page.title() # 保存状态到文件 context.storage_state(path="github_auth.json") context.close() browser.close() if __name__ == "__main__": save_github_auth_state()运行后会生成github_auth.json文件。接下来在测试中复用这个状态:
from playwright.sync_api import sync_playwright def test_with_auth(): with sync_playwright() as p: browser = p.chromium.launch() context = browser.new_context(storage_state="github_auth.json") page = context.new_page() # 直接跳转到需要认证的页面 page.goto('https://github.com/settings/profile') assert "Profile" in page.title() context.close() browser.close()2.2 多环境适配技巧
实际项目中,我们通常需要处理不同环境的认证。推荐采用这种目录结构:
tests/ ├── auth_states/ │ ├── dev_auth.json │ ├── staging_auth.json │ └── prod_auth.json ├── conftest.py └── test_dashboard.py在pytest的fixture中动态加载对应环境的状态:
# conftest.py import pytest from playwright.sync_api import Browser @pytest.fixture def auth_context(browser: Browser, request): env = request.config.getoption("--env") state_file = f"auth_states/{env}_auth.json" return browser.new_context(storage_state=state_file)使用时通过命令行参数指定环境:
pytest --env=staging3. 高级应用场景
3.1 多用户角色切换
对于需要测试不同权限角色的系统,可以维护多个状态文件:
roles = { "admin": "auth/admin.json", "editor": "auth/editor.json", "viewer": "auth/viewer.json" } def test_role_access(): for role, state_file in roles.items(): context = browser.new_context(storage_state=state_file) page = context.new_page() # 执行角色特定测试 ...3.2 CI/CD集成
在持续集成环境中,建议将认证状态作为缓存资源。GitHub Actions配置示例:
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/cache@v3 with: path: auth_states/ key: ${{ runner.os }}-auth-${{ hashFiles('auth_scripts/*') }} - name: Refresh auth if needed run: | if [ ! -f auth_states/prod_auth.json ]; then python auth_scripts/generate_prod_auth.py fi - name: Run tests run: pytest3.3 状态自动刷新
为避免认证过期,可以设置定期刷新任务:
import schedule import time from auth_setup import save_github_auth_state def refresh_auth(): print("Refreshing auth state...") save_github_auth_state() # 每6小时刷新一次 schedule.every(6).hours.do(refresh_auth) while True: schedule.run_pending() time.sleep(60)4. 安全与最佳实践
4.1 敏感信息处理
认证状态文件包含敏感数据,应采取以下保护措施:
gitignore配置:
# .gitignore *.json !auth_states/example_auth.json # 仅保留示例文件环境变量存储凭据:
page.fill('#login_field', os.getenv('TEST_USER')) page.fill('#password', os.getenv('TEST_PWD'))文件加密(使用cryptography库):
from cryptography.fernet import Fernet key = Fernet.generate_key() cipher_suite = Fernet(key) # 加密 with open('auth.json', 'rb') as f: encrypted = cipher_suite.encrypt(f.read()) # 解密 decrypted = cipher_suite.decrypt(encrypted)
4.2 性能对比
下表展示了不同方式的耗时对比(100次测试迭代):
| 方式 | 总耗时 | 平均每次认证耗时 |
|---|---|---|
| 每次登录 | 152s | 1.52s |
| 复用状态 | 28s | 0.28s |
| 无认证 | 25s | 0.25s |
注意:测试环境为本地开发机,网络延迟约50ms
4.3 常见问题排查
问题1:加载状态后仍然跳转到登录页
- 检查状态文件是否包含目标域的正确cookie
- 确认cookie的过期时间(expires字段)
- 验证网站是否使用Session Storage而非LocalStorage
问题2:跨域认证失败
- 确保相关域都在cookie的domain列表中
- 对于OAuth流程,可能需要手动设置多个域的cookie
# 手动添加跨域cookie context.add_cookies([ { "name": "auth_token", "value": "xyz123", "domain": ".example.com", "path": "/" }, { "name": "sso_token", "value": "abc456", "domain": ".auth.example.com", "path": "/" } ])问题3:CI环境中状态失效
- 检查CI服务器的时区设置
- 确认网络策略允许访问认证域名
- 考虑使用无头模式预生成状态文件
