项目结构
project/
├── pages/
│├── __init__.py
│├── base_page.py
│└── login_page.py
├── tests/
│├── __init__.py
│├── conftest.py
│└── test_login.py
├── pytest.ini
└── requirements.txt
1. requirements.txt
pytest==7.4.3
playwright==1.40.0
pytest-playwright==0.4.3
2. base_page.py
from playwright.sync_api import Page, expect
class BasePage:
def __init__(self, page: Page):
self.page = page
def navigate(self, url: str):
self.page.goto(url)
def get_title(self) -> str:
return self.page.title()
def get_url(self) -> str:
return self.page.url
3. login_page.py
from playwright.sync_api import Page, expect
from pages.base_page import BasePage
class LoginPage(BasePage):
# Locators
USERNAME_INPUT = "[data-test='username']"
PASSWORD_INPUT = "[data-test='password']"
LOGIN_BUTTON = "[data-test='login-button']"
ERROR_MESSAGE = "[data-test='error']"
def __init__(self, page: Page):
super().__init__(page)
def load(self):
self.navigate("https://www.saucedemo.com/")
def enter_username(self, username: str):
self.page.locator(self.USERNAME_INPUT).fill(username)
def enter_password(self, password: str):
self.page.locator(self.PASSWORD_INPUT).fill(password)
def click_login(self):
self.page.locator(self.LOGIN_BUTTON).click()
def login(self, username: str, password: str):
self.enter_username(username)
self.enter_password(password)
self.click_login()
def get_error_message(self):
return self.page.locator(self.ERROR_MESSAGE).text_content()
def is_error_displayed(self):
return self.page.locator(self.ERROR_MESSAGE).is_visible()
4. conftest.py
import pytest
from playwright.sync_api import Page, Browser, BrowserContext
from pages.login_page import LoginPage
@pytest.fixture(scope="session")
def browser_type_launch_args():
return {"headless": True}
@pytest.fixture(scope="session")
def browser_context_args():
return {"viewport": {"width": 1920, "height": 1080}}
@pytest.fixture
def login_page(page: Page) -> LoginPage:
return LoginPage(page)
@pytest.fixture
def authenticated_page(login_page: LoginPage):
"""Fixture that logs in and returns the page object"""
login_page.load()
login_page.login("standard_user", "secret_sauce")
yield login_page.page
5. test_login.py
import pytest
from playwright.sync_api import Page, expect
from pages.login_page import LoginPage
class TestLogin:
"""Test cases for SauceDemo login functionality"""
def test_successful_login(self, login_page: LoginPage):
"""Test successful login with valid credentials"""
# Arrange
login_page.load()
# Act
login_page.login("standard_user", "secret_sauce")
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/inventory.html")
expect(login_page.page).to_have_title("Swag Labs")
# Additional assertions
inventory_container = login_page.page.locator("#inventory_container")
expect(inventory_container).to_be_visible()
shopping_cart = login_page.page.locator("#shopping_cart_container")
expect(shopping_cart).to_be_visible()
def test_login_with_invalid_username(self, login_page: LoginPage):
"""Test login with invalid username"""
# Arrange
login_page.load()
# Act
login_page.login("invalid_user", "secret_sauce")
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/")
expect(login_page.is_error_displayed()).to_be_truthy()
error_text = login_page.get_error_message()
assert "Epic sadface: Username and password do not match" in error_text
def test_login_with_invalid_password(self, login_page: LoginPage):
"""Test login with invalid password"""
# Arrange
login_page.load()
# Act
login_page.login("standard_user", "wrong_password")
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/")
expect(login_page.is_error_displayed()).to_be_truthy()
error_text = login_page.get_error_message()
assert "Epic sadface: Username and password do not match" in error_text
def test_login_with_locked_user(self, login_page: LoginPage):
"""Test login with locked out user"""
# Arrange
login_page.load()
# Act
login_page.login("locked_out_user", "secret_sauce")
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/")
expect(login_page.is_error_displayed()).to_be_truthy()
error_text = login_page.get_error_message()
assert "Epic sadface: Sorry, this user has been locked out." in error_text
def test_login_with_empty_credentials(self, login_page: LoginPage):
"""Test login with empty username and password"""
# Arrange
login_page.load()
# Act
login_page.click_login()
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/")
expect(login_page.is_error_displayed()).to_be_truthy()
error_text = login_page.get_error_message()
assert "Epic sadface: Username is required" in error_text
@pytest.mark.parametrize("username,password", [
("standard_user", "secret_sauce"),
("problem_user", "secret_sauce"),
("performance_glitch_user", "secret_sauce"),
])
def test_login_multiple_users(self, login_page: LoginPage, username, password):
"""Test login with multiple valid users"""
# Arrange
login_page.load()
# Act
login_page.login(username, password)
# Assert
expect(login_page.page).to_have_url("https://www.saucedemo.com/inventory.html")
expect(login_page.page.locator(".app_logo")).to_have_text("Swag Labs")
# Cleanup - logout for next test
login_page.page.locator("#react-burger-menu-btn").click()
login_page.page.locator("#logout_sidebar_link").click()
def test_navigation_after_login(self, authenticated_page: Page):
"""Test that user can navigate after successful login"""
page = authenticated_page
# Test navigation to cart
cart_icon = page.locator("#shopping_cart_container")
cart_icon.click()
expect(page).to_have_url("https://www.saucedemo.com/cart.html")
# Test back to products
continue_shopping = page.locator("#continue-shopping")
continue_shopping.click()
expect(page).to_have_url("https://www.saucedemo.com/inventory.html")
6. pytest.ini
[pytest]
addopts =
--verbose
--strict-markers
--tb=short
--capture=no
--disable-warnings
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
smoke: Smoke tests
regression: Regression tests
login: Login related tests
7. 运行测试的脚本 run_tests.py
import subprocess
import sys
def run_tests():
"""Run pytest with appropriate arguments"""
# Install required packages
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
# Install Playwright browsers
subprocess.check_call([sys.executable, "-m", "playwright", "install", "chromium"])
# Run tests
result = subprocess.run([
sys.executable, "-m", "pytest",
"tests/test_login.py::TestLogin::test_successful_login",
"-v",
"--headed"# Remove this to run in headless mode
])
return result.returncode
if __name__ == "__main__":
sys.exit(run_tests())
8. 快速运行指南
安装依赖:
pip install -r requirements.txt
playwright install chromium
运行所有测试:
pytest tests/test_login.py -v
运行特定测试:
pytest tests/test_login.py::TestLogin::test_successful_login -v
运行带UI的测试:
pytest tests/test_login.py --headed
主要特点:
- POM设计模式:页面对象分离,易于维护
- 稳定的定位器:优先使用data-test属性
- 全面的断言:包含URL、标题、元素可见性等断言
- 参数化测试:支持多用户登录测试
- 独立的fixture:提供已认证的page fixture
- 清晰的测试结构:遵循Arrange-Act-Assert模式
- 错误处理:包含各种错误场景的测试
代码可以直接复制运行,只需安装依赖并运行python run_tests.py即可。
2268

被折叠的 条评论
为什么被折叠?



