feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
from .base_page import BasePage
|
||||
from .web import (
|
||||
LoginPage,
|
||||
DashboardPage,
|
||||
UserManagementPage,
|
||||
RoleManagementPage,
|
||||
MenuManagementPage,
|
||||
)
|
||||
from .uniapp import AlmanacPage, CalendarPage, UserPage
|
||||
|
||||
__all__ = [
|
||||
"BasePage",
|
||||
"LoginPage",
|
||||
"DashboardPage",
|
||||
"UserManagementPage",
|
||||
"RoleManagementPage",
|
||||
"MenuManagementPage",
|
||||
"AlmanacPage",
|
||||
"CalendarPage",
|
||||
"UserPage",
|
||||
]
|
||||
@@ -0,0 +1,214 @@
|
||||
"""
|
||||
基础页面类
|
||||
|
||||
所有页面对象的基类,提供通用方法。
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional, List
|
||||
from playwright.sync_api import Page, Locator, expect
|
||||
|
||||
|
||||
class BasePage(ABC):
|
||||
"""基础页面类"""
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
self.page = page
|
||||
self.base_url = base_url
|
||||
|
||||
@abstractmethod
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到指定路径"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
pass
|
||||
|
||||
def wait_for_load(self, timeout: int = 30000) -> None:
|
||||
"""等待页面加载完成"""
|
||||
self.page.wait_for_load_state("networkidle", timeout=timeout)
|
||||
|
||||
def wait_for_selector(
|
||||
self, selector: str, timeout: int = 10000, state: str = "visible"
|
||||
) -> Locator:
|
||||
"""等待元素出现"""
|
||||
return self.page.wait_for_selector(selector, timeout=timeout, state=state)
|
||||
|
||||
def click_element(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""点击元素"""
|
||||
self.wait_for_selector(selector, timeout=timeout).click()
|
||||
|
||||
def fill_input(self, selector: str, value: str, timeout: int = 10000) -> None:
|
||||
"""填充输入框"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
element.fill(value)
|
||||
|
||||
def get_text(self, selector: str, timeout: int = 10000) -> str:
|
||||
"""获取元素文本"""
|
||||
return (
|
||||
self.wait_for_selector(selector, timeout=timeout).text_content() or ""
|
||||
)
|
||||
|
||||
def get_input_value(self, selector: str, timeout: int = 10000) -> str:
|
||||
"""获取输入框值"""
|
||||
return (
|
||||
self.wait_for_selector(selector, timeout=timeout).input_value() or ""
|
||||
)
|
||||
|
||||
def is_element_visible(self, selector: str, timeout: int = 5000) -> bool:
|
||||
"""检查元素是否可见"""
|
||||
try:
|
||||
self.page.wait_for_selector(
|
||||
selector, timeout=timeout, state="visible"
|
||||
)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def is_element_enabled(self, selector: str, timeout: int = 5000) -> bool:
|
||||
"""检查元素是否可用"""
|
||||
try:
|
||||
element = self.page.wait_for_selector(
|
||||
selector, timeout=timeout, state="visible"
|
||||
)
|
||||
return element.is_enabled()
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_elements_count(self, selector: str) -> int:
|
||||
"""获取元素数量"""
|
||||
return len(self.page.locator(selector).all())
|
||||
|
||||
def select_option(self, selector: str, value: str, timeout: int = 10000) -> None:
|
||||
"""选择下拉选项"""
|
||||
self.wait_for_selector(selector, timeout=timeout).select_option(value)
|
||||
|
||||
def check_checkbox(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""勾选复选框"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
if not element.is_checked():
|
||||
element.check()
|
||||
|
||||
def uncheck_checkbox(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""取消勾选复选框"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
if element.is_checked():
|
||||
element.uncheck()
|
||||
|
||||
def scroll_to_element(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""滚动到元素"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
element.scroll_into_view_if_needed()
|
||||
|
||||
def take_screenshot(self, name: str, full_page: bool = False) -> str:
|
||||
"""截图"""
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
screenshot_dir = Path("reports/screenshots")
|
||||
screenshot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{name}_{timestamp}.png"
|
||||
filepath = screenshot_dir / filename
|
||||
|
||||
self.page.screenshot(path=str(filepath), full_page=full_page)
|
||||
return str(filepath)
|
||||
|
||||
def get_current_url(self) -> str:
|
||||
"""获取当前URL"""
|
||||
return self.page.url
|
||||
|
||||
def go_back(self) -> None:
|
||||
"""返回上一页"""
|
||||
self.page.go_back()
|
||||
|
||||
def reload(self) -> None:
|
||||
"""刷新页面"""
|
||||
self.page.reload()
|
||||
|
||||
def wait_for_timeout(self, timeout: int) -> None:
|
||||
"""等待指定时间(毫秒)"""
|
||||
self.page.wait_for_timeout(timeout)
|
||||
|
||||
def assert_element_text(
|
||||
self, selector: str, expected_text: str, timeout: int = 10000
|
||||
) -> None:
|
||||
"""断言元素文本"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
expect(element).to_have_text(expected_text)
|
||||
|
||||
def assert_element_contains_text(
|
||||
self, selector: str, expected_text: str, timeout: int = 10000
|
||||
) -> None:
|
||||
"""断言元素包含文本"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
expect(element).to_contain_text(expected_text)
|
||||
|
||||
def assert_url_contains(self, text: str) -> None:
|
||||
"""断言URL包含文本"""
|
||||
expect(self.page).to_have_url(lambda url: text in url)
|
||||
|
||||
def assert_element_visible(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""断言元素可见"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
expect(element).to_be_visible()
|
||||
|
||||
def assert_element_hidden(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""断言元素隐藏"""
|
||||
element = self.page.locator(selector)
|
||||
expect(element).to_be_hidden(timeout=timeout)
|
||||
|
||||
def hover_element(self, selector: str, timeout: int = 10000) -> None:
|
||||
"""悬停在元素上"""
|
||||
self.wait_for_selector(selector, timeout=timeout).hover()
|
||||
|
||||
def drag_and_drop(
|
||||
self, source_selector: str, target_selector: str, timeout: int = 10000
|
||||
) -> None:
|
||||
"""拖拽元素"""
|
||||
source = self.wait_for_selector(source_selector, timeout=timeout)
|
||||
target = self.wait_for_selector(target_selector, timeout=timeout)
|
||||
source.drag_to(target)
|
||||
|
||||
def upload_file(self, selector: str, file_path: str, timeout: int = 10000) -> None:
|
||||
"""上传文件"""
|
||||
self.wait_for_selector(selector, timeout=timeout).set_input_files(file_path)
|
||||
|
||||
def press_key(self, selector: str, key: str, timeout: int = 10000) -> None:
|
||||
"""按键"""
|
||||
self.wait_for_selector(selector, timeout=timeout).press(key)
|
||||
|
||||
def get_element_attribute(
|
||||
self, selector: str, attribute: str, timeout: int = 10000
|
||||
) -> str:
|
||||
"""获取元素属性"""
|
||||
return (
|
||||
self.wait_for_selector(selector, timeout=timeout).get_attribute(attribute)
|
||||
or ""
|
||||
)
|
||||
|
||||
def get_element_css_property(
|
||||
self, selector: str, property_name: str, timeout: int = 10000
|
||||
) -> str:
|
||||
"""获取元素CSS属性"""
|
||||
element = self.wait_for_selector(selector, timeout=timeout)
|
||||
return element.evaluate(f"el => getComputedStyle(el).{property_name}")
|
||||
|
||||
def wait_for_element_to_disappear(
|
||||
self, selector: str, timeout: int = 10000
|
||||
) -> None:
|
||||
"""等待元素消失"""
|
||||
self.page.wait_for_selector(selector, state="detached", timeout=timeout)
|
||||
|
||||
def wait_for_response(self, url_pattern: str, timeout: int = 30000):
|
||||
"""等待网络响应"""
|
||||
with self.page.expect_response(url_pattern, timeout=timeout) as response_info:
|
||||
pass
|
||||
return response_info.value
|
||||
|
||||
def wait_for_navigation(self, timeout: int = 30000):
|
||||
"""等待页面导航"""
|
||||
self.page.wait_for_load_state("networkidle", timeout=timeout)
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Uniapp端页面对象模型
|
||||
|
||||
提供黄历小程序的页面对象封装。
|
||||
"""
|
||||
|
||||
from .almanac_page import AlmanacPage
|
||||
from .calendar_page import CalendarPage
|
||||
from .user_page import UserPage
|
||||
|
||||
__all__ = [
|
||||
"AlmanacPage",
|
||||
"CalendarPage",
|
||||
"UserPage",
|
||||
]
|
||||
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
黄历页面
|
||||
|
||||
Uniapp黄历页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class AlmanacPage(BasePage):
|
||||
"""黄历页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/pages/almanac/index"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".page-title",
|
||||
"date_display": ".date-display",
|
||||
"solar_date": ".solar-date",
|
||||
"lunar_date": ".lunar-date",
|
||||
"ganzhi": ".ganzhi",
|
||||
"shengxiao": ".shengxiao",
|
||||
"yi_list": ".yi-list",
|
||||
"ji_list": ".ji-list",
|
||||
"yi_items": ".yi-item",
|
||||
"ji_items": ".ji-item",
|
||||
"shichen_table": ".shichen-table",
|
||||
"shichen_rows": ".shichen-row",
|
||||
"chongsha": ".chongsha",
|
||||
"wuxing": ".wuxing",
|
||||
"taishen": ".taishen",
|
||||
"caishen": ".caishen",
|
||||
"prev_date_btn": ".prev-date",
|
||||
"next_date_btn": ".next-date",
|
||||
"date_picker": ".date-picker",
|
||||
"search_btn": ".search-btn",
|
||||
"tab_calendar": ".tab-calendar",
|
||||
"tab_almanac": ".tab-almanac",
|
||||
"tab_user": ".tab-user",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到黄历页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["date_display"])
|
||||
|
||||
def get_solar_date(self) -> str:
|
||||
"""获取公历日期"""
|
||||
return self.get_text(self.LOCATORS["solar_date"])
|
||||
|
||||
def get_lunar_date(self) -> str:
|
||||
"""获取农历日期"""
|
||||
return self.get_text(self.LOCATORS["lunar_date"])
|
||||
|
||||
def get_ganzhi(self) -> str:
|
||||
"""获取干支"""
|
||||
return self.get_text(self.LOCATORS["ganzhi"])
|
||||
|
||||
def get_shengxiao(self) -> str:
|
||||
"""获取生肖"""
|
||||
return self.get_text(self.LOCATORS["shengxiao"])
|
||||
|
||||
def get_yi_items(self) -> list:
|
||||
"""获取宜事项列表"""
|
||||
items = self.page.locator(self.LOCATORS["yi_items"]).all()
|
||||
return [item.text_content() for item in items]
|
||||
|
||||
def get_ji_items(self) -> list:
|
||||
"""获取忌事项列表"""
|
||||
items = self.page.locator(self.LOCATORS["ji_items"]).all()
|
||||
return [item.text_content() for item in items]
|
||||
|
||||
def get_yi_count(self) -> int:
|
||||
"""获取宜事项数量"""
|
||||
return self.get_elements_count(self.LOCATORS["yi_items"])
|
||||
|
||||
def get_ji_count(self) -> int:
|
||||
"""获取忌事项数量"""
|
||||
return self.get_elements_count(self.LOCATORS["ji_items"])
|
||||
|
||||
def click_prev_date(self) -> None:
|
||||
"""点击前一天"""
|
||||
self.click_element(self.LOCATORS["prev_date_btn"])
|
||||
|
||||
def click_next_date(self) -> None:
|
||||
"""点击后一天"""
|
||||
self.click_element(self.LOCATORS["next_date_btn"])
|
||||
|
||||
def click_date_picker(self) -> None:
|
||||
"""点击日期选择器"""
|
||||
self.click_element(self.LOCATORS["date_picker"])
|
||||
|
||||
def click_search(self) -> None:
|
||||
"""点击搜索按钮"""
|
||||
self.click_element(self.LOCATORS["search_btn"])
|
||||
|
||||
def click_tab_calendar(self) -> None:
|
||||
"""点击万年历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_calendar"])
|
||||
|
||||
def click_tab_almanac(self) -> None:
|
||||
"""点击黄历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_almanac"])
|
||||
|
||||
def click_tab_user(self) -> None:
|
||||
"""点击我的Tab"""
|
||||
self.click_element(self.LOCATORS["tab_user"])
|
||||
|
||||
def get_chongsha(self) -> str:
|
||||
"""获取冲煞信息"""
|
||||
return self.get_text(self.LOCATORS["chongsha"])
|
||||
|
||||
def get_wuxing(self) -> str:
|
||||
"""获取五行信息"""
|
||||
return self.get_text(self.LOCATORS["wuxing"])
|
||||
|
||||
def get_taishen(self) -> str:
|
||||
"""获取胎神信息"""
|
||||
return self.get_text(self.LOCATORS["taishen"])
|
||||
|
||||
def get_caishen(self) -> str:
|
||||
"""获取财神方位"""
|
||||
return self.get_text(self.LOCATORS["caishen"])
|
||||
|
||||
def get_shichen_count(self) -> int:
|
||||
"""获取时辰数量"""
|
||||
return self.get_elements_count(self.LOCATORS["shichen_rows"])
|
||||
|
||||
def has_yi_section(self) -> bool:
|
||||
"""检查是否有宜事项区域"""
|
||||
return self.is_element_visible(self.LOCATORS["yi_list"])
|
||||
|
||||
def has_ji_section(self) -> bool:
|
||||
"""检查是否有忌事项区域"""
|
||||
return self.is_element_visible(self.LOCATORS["ji_list"])
|
||||
|
||||
def wait_for_data_load(self, timeout: int = 10000) -> None:
|
||||
"""等待数据加载完成"""
|
||||
self.wait_for_selector(self.LOCATORS["solar_date"], timeout=timeout)
|
||||
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
日历页面
|
||||
|
||||
Uniapp日历页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class CalendarPage(BasePage):
|
||||
"""日历页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/pages/calendar/index"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page": ".page",
|
||||
"calendar_grid": ".calendar-grid-container",
|
||||
"calendar_card": ".calendar-card",
|
||||
"month_title": ".month-title",
|
||||
"year_display": ".year-display",
|
||||
"prev_month_btn": ".nav-button",
|
||||
"next_month_btn": ".nav-button",
|
||||
"today_btn": ".today-button",
|
||||
"day_number": ".day-number",
|
||||
"day_lunar": ".day-lunar",
|
||||
"selected_date": ".calendar-card-selected",
|
||||
"current_date": ".calendar-card-today",
|
||||
"weekday_header": ".weekday",
|
||||
"tab_calendar": ".nav-item:nth-child(1)",
|
||||
"tab_almanac": ".nav-item:nth-child(2)",
|
||||
"tab_user": ".nav-item:nth-child(3)",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到日历页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["calendar_grid"])
|
||||
|
||||
def get_month_display(self) -> str:
|
||||
"""获取月份显示"""
|
||||
return self.get_text(self.LOCATORS["month_title"])
|
||||
|
||||
def get_year_display(self) -> str:
|
||||
"""获取年份显示"""
|
||||
month_text = self.get_text(self.LOCATORS["month_title"])
|
||||
if month_text:
|
||||
import re
|
||||
match = re.search(r'(\d{4})年', month_text)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
def click_prev_month(self) -> None:
|
||||
"""点击上一月"""
|
||||
nav_buttons = self.page.locator(self.LOCATORS["prev_month_btn"]).all()
|
||||
if len(nav_buttons) > 0:
|
||||
nav_buttons[0].click()
|
||||
|
||||
def click_next_month(self) -> None:
|
||||
"""点击下一月"""
|
||||
nav_buttons = self.page.locator(self.LOCATORS["next_month_btn"]).all()
|
||||
if len(nav_buttons) > 1:
|
||||
nav_buttons[1].click()
|
||||
|
||||
def click_today(self) -> None:
|
||||
"""点击今天按钮"""
|
||||
self.click_element(self.LOCATORS["today_btn"])
|
||||
|
||||
def click_date(self, day: int) -> None:
|
||||
"""点击指定日期"""
|
||||
calendar_cards = self.page.locator(self.LOCATORS["calendar_card"]).all()
|
||||
for card in calendar_cards:
|
||||
day_number = card.locator(self.LOCATORS["day_number"]).text_content()
|
||||
if day_number and int(day_number) == day:
|
||||
card.click()
|
||||
break
|
||||
|
||||
def get_date_cells_count(self) -> int:
|
||||
"""获取日期单元格数量"""
|
||||
return self.get_elements_count(self.LOCATORS["calendar_card"])
|
||||
|
||||
def get_selected_date(self) -> str:
|
||||
"""获取选中的日期"""
|
||||
import time
|
||||
time.sleep(1)
|
||||
selected = self.page.locator(self.LOCATORS["selected_date"]).first
|
||||
if selected and selected.count() > 0:
|
||||
day_number = selected.locator(self.LOCATORS["day_number"])
|
||||
if day_number.count() > 0:
|
||||
return day_number.text_content() or ""
|
||||
return ""
|
||||
|
||||
def has_lunar_text(self, day: int) -> bool:
|
||||
"""检查指定日期是否有农历显示"""
|
||||
calendar_cards = self.page.locator(self.LOCATORS["calendar_card"]).all()
|
||||
for card in calendar_cards:
|
||||
day_number = card.locator(self.LOCATORS["day_number"]).text_content()
|
||||
if day_number and int(day_number) == day:
|
||||
return card.locator(self.LOCATORS["day_lunar"]).count() > 0
|
||||
return False
|
||||
|
||||
def get_lunar_text(self, day: int) -> str:
|
||||
"""获取指定日期的农历文本"""
|
||||
calendar_cards = self.page.locator(self.LOCATORS["calendar_card"]).all()
|
||||
for card in calendar_cards:
|
||||
day_number = card.locator(self.LOCATORS["day_number"]).text_content()
|
||||
if day_number and int(day_number) == day:
|
||||
return card.locator(self.LOCATORS["day_lunar"]).text_content() or ""
|
||||
return ""
|
||||
|
||||
def click_tab_calendar(self) -> None:
|
||||
"""点击万年历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_calendar"])
|
||||
|
||||
def click_tab_almanac(self) -> None:
|
||||
"""点击黄历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_almanac"])
|
||||
|
||||
def click_tab_user(self) -> None:
|
||||
"""点击我的Tab"""
|
||||
self.click_element(self.LOCATORS["tab_user"])
|
||||
|
||||
def has_holiday_mark(self, day: int) -> bool:
|
||||
"""检查指定日期是否有节假日标记"""
|
||||
calendar_cards = self.page.locator(self.LOCATORS["calendar_card"]).all()
|
||||
for card in calendar_cards:
|
||||
day_number = card.locator(self.LOCATORS["day_number"]).text_content()
|
||||
if day_number and int(day_number) == day:
|
||||
return card.locator(self.LOCATORS["current_date"]).count() > 0
|
||||
return False
|
||||
|
||||
def wait_for_calendar_load(self, timeout: int = 10000) -> None:
|
||||
"""等待日历加载完成"""
|
||||
self.wait_for_selector(self.LOCATORS["calendar_card"], timeout=timeout)
|
||||
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
用户页面
|
||||
|
||||
Uniapp用户页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class UserPage(BasePage):
|
||||
"""用户页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/pages/user/index"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".page-title",
|
||||
"user_avatar": ".user-avatar",
|
||||
"user_name": ".user-name",
|
||||
"user_stats": ".user-stats",
|
||||
"stat_days": ".stat-days",
|
||||
"stat_favorites": ".stat-favorites",
|
||||
"stat_important": ".stat-important",
|
||||
"menu_favorites": ".menu-favorites",
|
||||
"menu_important": ".menu-important",
|
||||
"menu_feedback": ".menu-feedback",
|
||||
"menu_settings": ".menu-settings",
|
||||
"menu_about": ".menu-about",
|
||||
"tab_calendar": ".tab-calendar",
|
||||
"tab_almanac": ".tab-almanac",
|
||||
"tab_user": ".tab-user",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到用户页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["user_avatar"])
|
||||
|
||||
def get_user_name(self) -> str:
|
||||
"""获取用户名"""
|
||||
return self.get_text(self.LOCATORS["user_name"])
|
||||
|
||||
def get_stat_days(self) -> str:
|
||||
"""获取使用天数"""
|
||||
return self.get_text(self.LOCATORS["stat_days"])
|
||||
|
||||
def get_stat_favorites(self) -> str:
|
||||
"""获取收藏数"""
|
||||
return self.get_text(self.LOCATORS["stat_favorites"])
|
||||
|
||||
def get_stat_important(self) -> str:
|
||||
"""获取重要日期数"""
|
||||
return self.get_text(self.LOCATORS["stat_important"])
|
||||
|
||||
def click_menu_favorites(self) -> None:
|
||||
"""点击我的收藏菜单"""
|
||||
self.click_element(self.LOCATORS["menu_favorites"])
|
||||
|
||||
def click_menu_important(self) -> None:
|
||||
"""点击重要日期菜单"""
|
||||
self.click_element(self.LOCATORS["menu_important"])
|
||||
|
||||
def click_menu_feedback(self) -> None:
|
||||
"""点击意见反馈菜单"""
|
||||
self.click_element(self.LOCATORS["menu_feedback"])
|
||||
|
||||
def click_menu_settings(self) -> None:
|
||||
"""点击设置菜单"""
|
||||
self.click_element(self.LOCATORS["menu_settings"])
|
||||
|
||||
def click_menu_about(self) -> None:
|
||||
"""点击关于我们菜单"""
|
||||
self.click_element(self.LOCATORS["menu_about"])
|
||||
|
||||
def click_tab_calendar(self) -> None:
|
||||
"""点击万年历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_calendar"])
|
||||
|
||||
def click_tab_almanac(self) -> None:
|
||||
"""点击黄历Tab"""
|
||||
self.click_element(self.LOCATORS["tab_almanac"])
|
||||
|
||||
def click_tab_user(self) -> None:
|
||||
"""点击我的Tab"""
|
||||
self.click_element(self.LOCATORS["tab_user"])
|
||||
|
||||
def has_user_avatar(self) -> bool:
|
||||
"""检查是否有用户头像"""
|
||||
return self.is_element_visible(self.LOCATORS["user_avatar"])
|
||||
|
||||
def wait_for_user_data(self, timeout: int = 10000) -> None:
|
||||
"""等待用户数据加载"""
|
||||
self.wait_for_selector(self.LOCATORS["user_name"], timeout=timeout)
|
||||
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Admin端页面对象模型
|
||||
|
||||
提供后台管理系统的页面对象封装。
|
||||
"""
|
||||
|
||||
from .login_page import LoginPage
|
||||
from .dashboard_page import DashboardPage
|
||||
from .user_management_page import UserManagementPage
|
||||
from .role_management_page import RoleManagementPage
|
||||
from .menu_management_page import MenuManagementPage
|
||||
|
||||
__all__ = [
|
||||
"LoginPage",
|
||||
"DashboardPage",
|
||||
"UserManagementPage",
|
||||
"RoleManagementPage",
|
||||
"MenuManagementPage",
|
||||
]
|
||||
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
仪表盘页面
|
||||
|
||||
Admin后台仪表盘页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class DashboardPage(BasePage):
|
||||
"""仪表盘页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/dashboard"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".dashboard h1, .dashboard-title",
|
||||
"sidebar_menu": ".el-menu, .sidebar",
|
||||
"menu_items": ".el-menu-item",
|
||||
"user_info": ".user-info, .user-avatar",
|
||||
"logout_button": ".user-info",
|
||||
"stat_cards": ".stat-card, .el-card",
|
||||
"charts": ".chart-container",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到仪表盘页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["stat_cards"], timeout=5000)
|
||||
|
||||
def get_page_title(self) -> str:
|
||||
"""获取页面标题"""
|
||||
return self.get_text(self.LOCATORS["page_title"])
|
||||
|
||||
def get_menu_items(self) -> list:
|
||||
"""获取菜单项列表"""
|
||||
items = self.page.locator(self.LOCATORS["menu_items"]).all()
|
||||
return [item.text_content() for item in items]
|
||||
|
||||
def click_menu_item(self, menu_text: str) -> None:
|
||||
"""点击菜单项"""
|
||||
self.page.locator(self.LOCATORS["menu_items"]).filter(
|
||||
has_text=menu_text
|
||||
).click()
|
||||
|
||||
def is_menu_item_visible(self, menu_text: str) -> bool:
|
||||
"""检查菜单项是否可见"""
|
||||
try:
|
||||
self.page.locator(self.LOCATORS["menu_items"]).filter(
|
||||
has_text=menu_text
|
||||
).wait_for(timeout=5000)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def click_logout(self) -> None:
|
||||
"""点击登出按钮"""
|
||||
try:
|
||||
user_info = self.page.locator(self.LOCATORS["user_info"])
|
||||
if user_info.is_visible():
|
||||
user_info.click()
|
||||
self.wait_for_timeout(500)
|
||||
logout_option = self.page.locator('.el-dropdown-menu__item:has-text("退出"), .el-dropdown-menu__item:has-text("logout"), .el-dropdown-menu__item:has-text("登出")')
|
||||
if logout_option.count() > 0:
|
||||
logout_option.first.click()
|
||||
return
|
||||
self.page.evaluate('localStorage.clear(); sessionStorage.clear();')
|
||||
self.page.goto(f"{self.base_url}/login")
|
||||
except Exception as e:
|
||||
self.page.evaluate('localStorage.clear(); sessionStorage.clear();')
|
||||
self.page.goto(f"{self.base_url}/login")
|
||||
|
||||
def get_stat_cards_count(self) -> int:
|
||||
"""获取统计卡片数量"""
|
||||
return self.get_elements_count(self.LOCATORS["stat_cards"])
|
||||
|
||||
def get_charts_count(self) -> int:
|
||||
"""获取图表数量"""
|
||||
return self.get_elements_count(self.LOCATORS["charts"])
|
||||
|
||||
def wait_for_data_load(self, timeout: int = 10000) -> None:
|
||||
"""等待数据加载完成"""
|
||||
self.wait_for_timeout(2000) # 等待数据加载动画
|
||||
|
||||
def refresh_data(self) -> None:
|
||||
"""刷新数据"""
|
||||
self.reload()
|
||||
self.wait_for_load()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
登录页面
|
||||
|
||||
Admin后台登录页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class LoginPage(BasePage):
|
||||
"""登录页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/login"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"username_input": '[data-testid="username-input"] input, input[data-testid="username-input"]',
|
||||
"password_input": '[data-testid="password-input"] input, input[data-testid="password-input"]',
|
||||
"submit_button": '[data-testid="login-button"], button[type="submit"]',
|
||||
"error_message": ".el-message--error .el-message__content, .el-message--error, .el-notification__content, .error-message",
|
||||
"page_title": ".login-title, h1",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到登录页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["username_input"])
|
||||
|
||||
def login(self, username: str, password: str) -> None:
|
||||
"""
|
||||
执行登录操作
|
||||
|
||||
Args:
|
||||
username: 用户名
|
||||
password: 密码
|
||||
"""
|
||||
self.fill_username(username)
|
||||
self.fill_password(password)
|
||||
self.click_submit()
|
||||
|
||||
def fill_username(self, username: str) -> None:
|
||||
"""填写用户名"""
|
||||
self.fill_input(self.LOCATORS["username_input"], username)
|
||||
|
||||
def fill_password(self, password: str) -> None:
|
||||
"""填写密码"""
|
||||
self.fill_input(self.LOCATORS["password_input"], password)
|
||||
|
||||
def click_submit(self) -> None:
|
||||
"""点击登录按钮"""
|
||||
self.click_element(self.LOCATORS["submit_button"])
|
||||
|
||||
def get_error_message(self) -> str:
|
||||
"""获取错误消息"""
|
||||
if self.is_element_visible(self.LOCATORS["error_message"], timeout=3000):
|
||||
return self.get_text(self.LOCATORS["error_message"])
|
||||
return ""
|
||||
|
||||
def has_error_message(self) -> bool:
|
||||
"""检查是否有错误消息"""
|
||||
self.wait_for_timeout(1000)
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["error_message"], timeout=5000
|
||||
)
|
||||
|
||||
def clear_form(self) -> None:
|
||||
"""清空表单"""
|
||||
self.page.locator(self.LOCATORS["username_input"]).fill('')
|
||||
self.page.locator(self.LOCATORS["password_input"]).fill('')
|
||||
|
||||
def is_submit_enabled(self) -> bool:
|
||||
"""检查提交按钮是否可用"""
|
||||
return self.is_element_enabled(self.LOCATORS["submit_button"])
|
||||
|
||||
def get_page_title(self) -> str:
|
||||
"""获取页面标题"""
|
||||
return self.get_text(self.LOCATORS["page_title"])
|
||||
|
||||
def wait_for_redirect(self, timeout: int = 10000) -> None:
|
||||
"""等待页面跳转"""
|
||||
self.page.wait_for_url("**/dashboard**", timeout=timeout)
|
||||
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
菜单管理页面
|
||||
|
||||
Admin后台菜单管理页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class MenuManagementPage(BasePage):
|
||||
"""菜单管理页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/system/menu"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".page-title, h1",
|
||||
"create_button": ".el-button--primary, .el-button:has-text('新增')",
|
||||
"menu_tree": ".el-tree, .tree-container",
|
||||
"tree_nodes": ".el-tree-node, .tree-node",
|
||||
"dialog": ".el-dialog",
|
||||
"dialog_title": ".el-dialog__title",
|
||||
"form_name": 'input[name="name"], input[placeholder*="名称"]',
|
||||
"form_path": 'input[name="path"], input[placeholder*="路径"]',
|
||||
"form_component": 'input[name="component"], input[placeholder*="组件"]',
|
||||
"form_icon": 'input[name="icon"], input[placeholder*="图标"]',
|
||||
"form_sort": 'input[name="sort"], input[placeholder*="排序"]',
|
||||
"form_type": '.el-radio-group',
|
||||
"form_parent": '.el-select',
|
||||
"form_submit": '.el-dialog__footer .el-button--primary',
|
||||
"form_cancel": '.el-dialog__footer .el-button--default',
|
||||
"delete_confirm": ".el-message-box__btns .el-button--primary",
|
||||
"success_message": ".el-message--success",
|
||||
"error_message": ".el-message--error",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到菜单管理页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["menu_tree"])
|
||||
|
||||
def click_create_button(self) -> None:
|
||||
"""点击新建按钮"""
|
||||
self.click_element(self.LOCATORS["create_button"])
|
||||
|
||||
def expand_tree_node(self, node_name: str) -> None:
|
||||
"""展开树节点"""
|
||||
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
|
||||
".el-tree-node__label"
|
||||
).filter(has_text=node_name).locator("..").locator("..").locator(
|
||||
".el-tree-node__expand-icon"
|
||||
).first
|
||||
node.click()
|
||||
|
||||
def click_node_edit(self, node_name: str) -> None:
|
||||
"""点击节点编辑"""
|
||||
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
|
||||
".el-tree-node__label"
|
||||
).filter(has_text=node_name).locator("..").locator("..")
|
||||
edit_btn = node.locator(".el-button--primary").first
|
||||
edit_btn.click()
|
||||
|
||||
def click_node_delete(self, node_name: str) -> None:
|
||||
"""点击节点删除"""
|
||||
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
|
||||
".el-tree-node__label"
|
||||
).filter(has_text=node_name).locator("..").locator("..")
|
||||
delete_btn = node.locator(".el-button--danger").first
|
||||
delete_btn.click()
|
||||
|
||||
def confirm_delete(self) -> None:
|
||||
"""确认删除"""
|
||||
self.click_element(self.LOCATORS["delete_confirm"])
|
||||
|
||||
def fill_form_name(self, name: str) -> None:
|
||||
"""填写表单菜单名称"""
|
||||
self.fill_input(self.LOCATORS["form_name"], name)
|
||||
|
||||
def fill_form_path(self, path: str) -> None:
|
||||
"""填写表单菜单路径"""
|
||||
self.fill_input(self.LOCATORS["form_path"], path)
|
||||
|
||||
def fill_form_component(self, component: str) -> None:
|
||||
"""填写表单组件"""
|
||||
self.fill_input(self.LOCATORS["form_component"], component)
|
||||
|
||||
def fill_form_icon(self, icon: str) -> None:
|
||||
"""填写表单图标"""
|
||||
self.fill_input(self.LOCATORS["form_icon"], icon)
|
||||
|
||||
def fill_form_sort(self, sort: int) -> None:
|
||||
"""填写表单排序"""
|
||||
self.fill_input(self.LOCATORS["form_sort"], str(sort))
|
||||
|
||||
def select_form_type(self, menu_type: str) -> None:
|
||||
"""选择表单菜单类型"""
|
||||
self.page.locator(self.LOCATORS["form_type"]).locator(
|
||||
".el-radio"
|
||||
).filter(has_text=menu_type).click()
|
||||
|
||||
def select_form_parent(self, parent_name: str) -> None:
|
||||
"""选择表单父级菜单"""
|
||||
self.click_element(self.LOCATORS["form_parent"])
|
||||
self.page.locator(".el-select-dropdown__item").filter(
|
||||
has_text=parent_name
|
||||
).click()
|
||||
|
||||
def click_form_submit(self) -> None:
|
||||
"""点击表单提交按钮"""
|
||||
self.click_element(self.LOCATORS["form_submit"])
|
||||
|
||||
def click_form_cancel(self) -> None:
|
||||
"""点击表单取消按钮"""
|
||||
self.click_element(self.LOCATORS["form_cancel"])
|
||||
|
||||
def is_dialog_visible(self) -> bool:
|
||||
"""检查对话框是否可见"""
|
||||
return self.is_element_visible(self.LOCATORS["dialog"])
|
||||
|
||||
def get_dialog_title(self) -> str:
|
||||
"""获取对话框标题"""
|
||||
return self.get_text(self.LOCATORS["dialog_title"])
|
||||
|
||||
def has_success_message(self) -> bool:
|
||||
"""检查是否有成功消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["success_message"], timeout=3000
|
||||
)
|
||||
|
||||
def has_error_message(self) -> bool:
|
||||
"""检查是否有错误消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["error_message"], timeout=3000
|
||||
)
|
||||
|
||||
def get_tree_nodes_count(self) -> int:
|
||||
"""获取树节点数量"""
|
||||
return self.get_elements_count(self.LOCATORS["tree_nodes"])
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
角色管理页面
|
||||
|
||||
Admin后台角色管理页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class RoleManagementPage(BasePage):
|
||||
"""角色管理页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/system/role"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".page-title, h1",
|
||||
"create_button": ".el-button--primary",
|
||||
"search_input": ".search-input input",
|
||||
"search_button": ".search-btn",
|
||||
"table": ".el-table, table.el-table",
|
||||
"table_rows": ".el-table__row, tr.el-table__row",
|
||||
"dialog": ".el-dialog",
|
||||
"dialog_title": ".el-dialog__title",
|
||||
"form_name": 'input[name="name"], input[placeholder*="名称"]',
|
||||
"form_code": 'input[name="code"], input[placeholder*="编码"]',
|
||||
"form_description": 'textarea[name="description"], textarea[placeholder*="描述"]',
|
||||
"form_status": '.el-select',
|
||||
"form_submit": '.el-dialog__footer .el-button--primary',
|
||||
"form_cancel": '.el-dialog__footer .el-button--default',
|
||||
"permission_tree": ".el-tree",
|
||||
"delete_confirm": ".el-message-box__btns .el-button--primary",
|
||||
"success_message": ".el-message--success",
|
||||
"error_message": ".el-message--error",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到角色管理页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
return self.is_element_visible(self.LOCATORS["table"])
|
||||
|
||||
def click_create_button(self) -> None:
|
||||
"""点击新建按钮"""
|
||||
self.click_element(self.LOCATORS["create_button"])
|
||||
|
||||
def fill_search(self, keyword: str) -> None:
|
||||
"""填写搜索关键词"""
|
||||
self.fill_input(self.LOCATORS["search_input"], keyword)
|
||||
|
||||
def click_search(self) -> None:
|
||||
"""点击搜索按钮"""
|
||||
self.click_element(self.LOCATORS["search_button"])
|
||||
|
||||
def get_table_rows_count(self) -> int:
|
||||
"""获取表格行数"""
|
||||
return self.get_elements_count(self.LOCATORS["table_rows"])
|
||||
|
||||
def click_row_edit(self, row_index: int = 0) -> None:
|
||||
"""点击行编辑按钮"""
|
||||
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
|
||||
edit_btn = row.locator(".el-button--primary").first
|
||||
edit_btn.click()
|
||||
|
||||
def click_row_delete(self, row_index: int = 0) -> None:
|
||||
"""点击行删除按钮"""
|
||||
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
|
||||
delete_btn = row.locator(".el-button--danger").first
|
||||
delete_btn.click()
|
||||
|
||||
def confirm_delete(self) -> None:
|
||||
"""确认删除"""
|
||||
self.click_element(self.LOCATORS["delete_confirm"])
|
||||
|
||||
def fill_form_name(self, name: str) -> None:
|
||||
"""填写表单角色名称"""
|
||||
self.fill_input(self.LOCATORS["form_name"], name)
|
||||
|
||||
def fill_form_code(self, code: str) -> None:
|
||||
"""填写表单角色编码"""
|
||||
self.fill_input(self.LOCATORS["form_code"], code)
|
||||
|
||||
def fill_form_description(self, description: str) -> None:
|
||||
"""填写表单角色描述"""
|
||||
self.fill_input(self.LOCATORS["form_description"], description)
|
||||
|
||||
def select_form_status(self, status: str) -> None:
|
||||
"""选择表单状态"""
|
||||
self.click_element(self.LOCATORS["form_status"])
|
||||
self.page.locator(".el-select-dropdown__item").filter(
|
||||
has_text=status
|
||||
).click()
|
||||
|
||||
def click_form_submit(self) -> None:
|
||||
"""点击表单提交按钮"""
|
||||
self.click_element(self.LOCATORS["form_submit"])
|
||||
|
||||
def click_form_cancel(self) -> None:
|
||||
"""点击表单取消按钮"""
|
||||
self.click_element(self.LOCATORS["form_cancel"])
|
||||
|
||||
def is_dialog_visible(self) -> bool:
|
||||
"""检查对话框是否可见"""
|
||||
return self.is_element_visible(self.LOCATORS["dialog"])
|
||||
|
||||
def get_dialog_title(self) -> str:
|
||||
"""获取对话框标题"""
|
||||
return self.get_text(self.LOCATORS["dialog_title"])
|
||||
|
||||
def check_permission(self, permission_name: str) -> None:
|
||||
"""勾选权限"""
|
||||
self.page.locator(self.LOCATORS["permission_tree"]).locator(
|
||||
".el-tree-node__label"
|
||||
).filter(has_text=permission_name).locator("..").locator(
|
||||
".el-checkbox__input"
|
||||
).first.click()
|
||||
|
||||
def has_success_message(self) -> bool:
|
||||
"""检查是否有成功消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["success_message"], timeout=3000
|
||||
)
|
||||
|
||||
def has_error_message(self) -> bool:
|
||||
"""检查是否有错误消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["error_message"], timeout=3000
|
||||
)
|
||||
|
||||
def wait_for_table_load(self, timeout: int = 10000) -> None:
|
||||
"""等待表格加载完成"""
|
||||
self.wait_for_selector(self.LOCATORS["table_rows"], timeout=timeout)
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
用户管理页面
|
||||
|
||||
Admin后台用户管理页面的页面对象模型。
|
||||
"""
|
||||
|
||||
from playwright.sync_api import Page
|
||||
from ..base_page import BasePage
|
||||
|
||||
|
||||
class UserManagementPage(BasePage):
|
||||
"""用户管理页面"""
|
||||
|
||||
# 页面路径
|
||||
PATH = "/users"
|
||||
|
||||
# 元素定位器
|
||||
LOCATORS = {
|
||||
"page_title": ".user-management h1, .page-title",
|
||||
"create_button": ".card-header .el-button--primary, .el-button:has-text('新增用户')",
|
||||
"search_input": ".search-card input[v-model='queryParams.username'], .search-card .el-input input",
|
||||
"search_button": ".search-card .el-button--primary, .el-button:has-text('搜索')",
|
||||
"reset_button": ".search-card .el-button:not(.el-button--primary), .el-button:has-text('重置')",
|
||||
"table": ".table-card .el-table, .el-table",
|
||||
"table_rows": ".el-table__row",
|
||||
"pagination": ".el-pagination",
|
||||
"dialog": ".el-dialog",
|
||||
"dialog_title": ".el-dialog__title",
|
||||
"form_username": '.el-dialog input[v-model="formState.username"], .el-dialog input[placeholder*="用户名"]',
|
||||
"form_nickname": '.el-dialog input[v-model="formState.nickname"], .el-dialog input[placeholder*="昵称"]',
|
||||
"form_email": '.el-dialog input[v-model="formState.email"], .el-dialog input[placeholder*="邮箱"]',
|
||||
"form_phone": '.el-dialog input[v-model="formState.phone"], .el-dialog input[placeholder*="手机"]',
|
||||
"form_status": '.el-dialog .el-select',
|
||||
"form_submit": '.el-dialog__footer .el-button--primary',
|
||||
"form_cancel": '.el-dialog__footer .el-button:not(.el-button--primary)',
|
||||
"delete_confirm": ".el-message-box__btns .el-button--primary, .el-popconfirm .el-button--primary",
|
||||
"success_message": ".el-message--success .el-message__content, .el-message--success",
|
||||
"error_message": ".el-message--error .el-message__content, .el-message--error",
|
||||
}
|
||||
|
||||
def __init__(self, page: Page, base_url: str = ""):
|
||||
super().__init__(page, base_url)
|
||||
|
||||
def navigate(self, path: str = "") -> None:
|
||||
"""导航到用户管理页面"""
|
||||
target_path = path if path else self.PATH
|
||||
self.page.goto(f"{self.base_url}{target_path}")
|
||||
self.wait_for_load()
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""检查页面是否加载完成"""
|
||||
try:
|
||||
self.wait_for_timeout(2000)
|
||||
return self.is_element_visible(self.LOCATORS["table"], timeout=10000)
|
||||
except:
|
||||
return False
|
||||
|
||||
def click_create_button(self) -> None:
|
||||
"""点击新建按钮"""
|
||||
self.click_element(self.LOCATORS["create_button"])
|
||||
|
||||
def fill_search(self, keyword: str) -> None:
|
||||
"""填写搜索关键词"""
|
||||
self.fill_input(self.LOCATORS["search_input"], keyword)
|
||||
|
||||
def click_search(self) -> None:
|
||||
"""点击搜索按钮"""
|
||||
self.click_element(self.LOCATORS["search_button"])
|
||||
|
||||
def click_reset(self) -> None:
|
||||
"""点击重置按钮"""
|
||||
self.click_element(self.LOCATORS["reset_button"])
|
||||
|
||||
def get_table_rows_count(self) -> int:
|
||||
"""获取表格行数"""
|
||||
return self.get_elements_count(self.LOCATORS["table_rows"])
|
||||
|
||||
def get_first_row_text(self) -> str:
|
||||
"""获取第一行文本"""
|
||||
rows = self.page.locator(self.LOCATORS["table_rows"]).all()
|
||||
if rows:
|
||||
return rows[0].text_content() or ""
|
||||
return ""
|
||||
|
||||
def click_row_edit(self, row_index: int = 0) -> None:
|
||||
"""点击行编辑按钮"""
|
||||
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
|
||||
edit_btn = row.locator(".el-button--primary").first
|
||||
edit_btn.click()
|
||||
|
||||
def click_row_delete(self, row_index: int = 0) -> None:
|
||||
"""点击行删除按钮"""
|
||||
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
|
||||
delete_btn = row.locator(".el-button--danger").first
|
||||
delete_btn.click()
|
||||
|
||||
def confirm_delete(self) -> None:
|
||||
"""确认删除"""
|
||||
self.click_element(self.LOCATORS["delete_confirm"])
|
||||
|
||||
def fill_form_username(self, username: str) -> None:
|
||||
"""填写表单用户名"""
|
||||
self.fill_input(self.LOCATORS["form_username"], username)
|
||||
|
||||
def fill_form_nickname(self, nickname: str) -> None:
|
||||
"""填写表单昵称"""
|
||||
self.fill_input(self.LOCATORS["form_nickname"], nickname)
|
||||
|
||||
def fill_form_email(self, email: str) -> None:
|
||||
"""填写表单邮箱"""
|
||||
self.fill_input(self.LOCATORS["form_email"], email)
|
||||
|
||||
def fill_form_phone(self, phone: str) -> None:
|
||||
"""填写表单电话"""
|
||||
self.fill_input(self.LOCATORS["form_phone"], phone)
|
||||
|
||||
def select_form_status(self, status: str) -> None:
|
||||
"""选择表单状态"""
|
||||
self.click_element(self.LOCATORS["form_status"])
|
||||
self.page.locator(".el-select-dropdown__item").filter(
|
||||
has_text=status
|
||||
).click()
|
||||
|
||||
def click_form_submit(self) -> None:
|
||||
"""点击表单提交按钮"""
|
||||
self.click_element(self.LOCATORS["form_submit"])
|
||||
|
||||
def click_form_cancel(self) -> None:
|
||||
"""点击表单取消按钮"""
|
||||
self.click_element(self.LOCATORS["form_cancel"])
|
||||
|
||||
def is_dialog_visible(self) -> bool:
|
||||
"""检查对话框是否可见"""
|
||||
try:
|
||||
dialog = self.page.locator(self.LOCATORS["dialog"])
|
||||
if dialog.count() == 0:
|
||||
return False
|
||||
is_visible = dialog.is_visible()
|
||||
if not is_visible:
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def get_dialog_title(self) -> str:
|
||||
"""获取对话框标题"""
|
||||
return self.get_text(self.LOCATORS["dialog_title"])
|
||||
|
||||
def has_success_message(self) -> bool:
|
||||
"""检查是否有成功消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["success_message"], timeout=3000
|
||||
)
|
||||
|
||||
def has_error_message(self) -> bool:
|
||||
"""检查是否有错误消息"""
|
||||
return self.is_element_visible(
|
||||
self.LOCATORS["error_message"], timeout=3000
|
||||
)
|
||||
|
||||
def wait_for_table_load(self, timeout: int = 10000) -> None:
|
||||
"""等待表格加载完成"""
|
||||
try:
|
||||
self.wait_for_timeout(2000)
|
||||
self.wait_for_selector(self.LOCATORS["table_rows"], timeout=timeout)
|
||||
except:
|
||||
pass
|
||||
|
||||
def search_and_wait(self, keyword: str, timeout: int = 10000) -> None:
|
||||
"""搜索并等待结果"""
|
||||
self.fill_search(keyword)
|
||||
self.click_search()
|
||||
self.wait_for_table_load(timeout)
|
||||
Reference in New Issue
Block a user