mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-06 10:06:36 +08:00
test: add Playwright e2e test suite for frontend and API
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user