Files
flvx/tests/e2e/pages/__init__.py
T

185 lines
6.1 KiB
Python

"""
Page Object Models for FLVX E2E tests.
"""
from typing import Optional
from playwright.sync_api import Page, Locator, expect
class BasePage:
"""Base page object with common functionality."""
def __init__(self, page: Page, base_url: str):
self.page = page
self.base_url = base_url
def navigate(self, path: str = ""):
"""Navigate to a specific path."""
url = f"{self.base_url}{path}"
self.page.goto(url)
self.page.wait_for_load_state("networkidle")
def wait_for_url(self, pattern: str, timeout: int = 5000):
"""Wait for URL to match pattern."""
self.page.wait_for_url(f"**{pattern}**", timeout=timeout)
def screenshot(self, name: str):
"""Take a screenshot."""
self.page.screenshot(path=f"/tmp/{name}.png")
class LoginPage(BasePage):
"""Login page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.username_input: Locator = page.locator('input[placeholder="请输入用户名"]')
self.password_input: Locator = page.locator('input[placeholder="请输入密码"]')
self.login_button: Locator = page.locator('button:has-text("登录")')
self.error_toast: Locator = page.locator('[data-testid="toast-error"], .toast-error')
def goto(self):
"""Navigate to login page."""
self.navigate("/")
def login(self, username: str, password: str) -> bool:
"""Perform login action."""
self.username_input.fill(username)
self.password_input.fill(password)
self.login_button.click()
try:
self.page.wait_for_url("**/dashboard**", timeout=5000)
return True
except Exception:
try:
self.page.wait_for_url("**/change-password**", timeout=2000)
return True
except Exception:
return False
def get_error_message(self) -> Optional[str]:
"""Get error message if present."""
try:
toast = self.page.locator('[role="alert"], .toast').first
if toast.is_visible():
return toast.text_content()
except Exception:
pass
return None
class DashboardPage(BasePage):
"""Dashboard page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.sidebar: Locator = page.locator("nav, [data-testid='sidebar']")
self.logout_button: Locator = page.locator('button:has-text("退出"), [data-testid="logout"]')
def goto(self):
"""Navigate to dashboard."""
self.navigate("/dashboard")
def is_authenticated(self) -> bool:
"""Check if user is authenticated on this page."""
return self.page.url.endswith("/dashboard") or "/dashboard" in self.page.url
def navigate_to(self, menu_item: str):
"""Navigate to a menu item."""
self.page.click(f'text="{menu_item}"')
self.page.wait_for_load_state("networkidle")
class UserPage(BasePage):
"""User management page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.create_button: Locator = page.locator('button:has-text("创建"), button:has-text("新增")')
self.user_table: Locator = page.locator("table")
def goto(self):
"""Navigate to user management page."""
self.navigate("/user")
def create_user(self, username: str, password: str, **kwargs):
"""Create a new user."""
self.create_button.click()
page = self.page
page.fill('input[placeholder*="用户名"], input[name="username"]', username)
page.fill('input[placeholder*="密码"], input[name="password"]', password)
if kwargs.get("name"):
page.fill('input[placeholder*="名称"], input[name="name"]', kwargs["name"])
page.click('button:has-text("确定"), button:has-text("提交")')
page.wait_for_load_state("networkidle")
def delete_user(self, username: str):
"""Delete a user by username."""
row = self.page.locator(f"tr:has-text('{username}')")
row.locator('button:has-text("删除")').click()
self.page.click('button:has-text("确认")')
self.page.wait_for_load_state("networkidle")
class NodePage(BasePage):
"""Node management page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.create_button: Locator = page.locator('button:has-text("创建"), button:has-text("新增")')
self.node_list: Locator = page.locator("[data-testid='node-list'], table, .node-item")
def goto(self):
"""Navigate to node management page."""
self.navigate("/node")
def get_nodes(self) -> list[str]:
"""Get list of node names."""
nodes = []
for item in self.page.locator("tr td:first-child, .node-name").all():
text = item.text_content()
if text and text.strip():
nodes.append(text.strip())
return nodes
class TunnelPage(BasePage):
"""Tunnel management page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.create_button: Locator = page.locator('button:has-text("创建"), button:has-text("新增")')
def goto(self):
"""Navigate to tunnel management page."""
self.navigate("/tunnel")
class ForwardPage(BasePage):
"""Forward management page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.create_button: Locator = page.locator('button:has-text("创建"), button:has-text("新增")')
def goto(self):
"""Navigate to forward management page."""
self.navigate("/forward")
class ConfigPage(BasePage):
"""Configuration page object."""
def __init__(self, page: Page, base_url: str):
super().__init__(page, base_url)
self.save_button: Locator = page.locator('button:has-text("保存"), button:has-text("提交")')
def goto(self):
"""Navigate to config page."""
self.navigate("/config")