You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Playwright+PyTest中使用Fixture初始化页面对象时出现AttributeError的解决方案及最佳实践

Playwright+PyTest中使用Fixture初始化页面对象时出现AttributeError的解决方案及最佳实践

嘿,我看了你遇到的问题,这个错误其实是因为对async fixture的使用方式理解有点偏差,咱们一步步来解决它,顺便聊聊这类场景的最佳实践。

问题根源

你看到的AttributeError: 'coroutine' object has no attribute 'url'和RuntimeWarning,核心原因是你在测试函数里对async fixture做了多余的await操作。

当你定义了async类型的fixture,pytest-asyncio会自动帮你处理异步执行的逻辑:在运行测试前,它会自动await这个fixture,所以测试函数参数里的login_page_fixture已经是fixture yield出来的LoginPage实例了,根本不是coroutine对象。你额外写的login_page = await login_page_fixture,相当于把一个普通的页面对象当成coroutine去等待,反而生成了一个无效的coroutine对象,自然找不到url属性。

快速修复

只需要修改你的测试函数,去掉多余的await即可:

@pytest.mark.asyncio
async def test_login_with_valid_credentials(login_page_fixture):
    # 直接使用fixture参数,它已经是初始化好的LoginPage实例
    print(f"login page url: {login_page_fixture.url}")
    # 后续可以直接调用页面对象的方法,比如跳转、登录
    # await login_page_fixture.goto()
    # await login_page_fixture.login("Admin", "admin123")

进阶最佳实践

咱们再优化一下你的代码结构,让它更符合Playwright+PyTest的最佳实践:

1. 拆分通用fixture,提升复用性

把浏览器、页面、页面对象的fixture拆分开,这样其他测试用例也能复用通用的browser和page fixture:

import pytest
from playwright.async_api import async_playwright
from ...infra.page_objects.login.login_page import LoginPage

# 浏览器fixture:负责启动和关闭浏览器
@pytest.fixture
async def browser():
    async with async_playwright() as playwright:
        # 可以加headless=False方便调试,上线后再改回True
        browser = await playwright.chromium.launch(headless=False)
        yield browser
        await browser.close()

# 页面fixture:负责创建和关闭页面,依赖browser
@pytest.fixture
async def page(browser):
    page = await browser.new_page()
    yield page
    await page.close()

# 登录页fixture:负责初始化页面对象,甚至提前完成页面跳转和校验
@pytest.fixture
async def login_page(page):
    login_page = LoginPage(page)
    # 提前跳转到登录页并校验页面加载完成,测试函数直接用现成的页面对象
    await login_page.goto()
    await login_page.verify_page_loaded()
    yield login_page

2. 修复页面对象里的小问题

你的LoginPage类中verify_page_loaded方法引用了self.login_title,但这个属性没有在__init__里定义,会导致新的报错,补充上对应的元素定位:

from playwright.async_api import Page, expect

class LoginPage:
    def __init__(self, page: Page):
        self.page = page
        self.url = 'https://opensource-demo.orangehrmlive.com/web/index.php/auth/login'
        self.header = page.locator(".orangehrm-login-branding")
        self.header_logo = page.get_by_role("img", name="company-branding")
        self.username_field = page.get_by_placeholder("Username")
        self.password_field = page.get_by_placeholder("Password")
        self.login_button = page.get_by_role("button", name="Login")
        self.forgot_password_link = page.get_by_text("Forgot your password?")
        # 补充login_title的元素定位
        self.login_title = page.locator("h5.orangehrm-login-title")

    async def goto(self):
        await self.page.goto(self.url)

    async def verify_page_loaded(self):
        await expect(self.header).to_be_visible()
        await expect(self.login_title).to_be_visible()

    # 其他方法保持不变...

3. 简化测试函数

现在测试函数可以非常简洁,直接使用初始化好的页面对象:

@pytest.mark.asyncio
async def test_login_with_valid_credentials(login_page):
    print(f"login page url: {login_page.url}")
    # 执行登录操作
    await login_page.login("Admin", "admin123")
    # 这里可以添加断言,比如验证登录后的页面元素
    # 示例:await expect(login_page.page.locator(".oxd-userdropdown-name")).to_contain_text("Admin")

这样调整后,代码结构更清晰,fixture的复用性更高,测试逻辑也更简洁。

备注:内容来源于stack exchange,提问作者autimation_guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 07:03:08