08ea5fbe98
添加用户管理视图、API和状态管理文件
429 lines
14 KiB
Python
429 lines
14 KiB
Python
from playwright.sync_api import Page, Locator
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
class TableHelper:
|
|
"""表格辅助工具类"""
|
|
|
|
def __init__(self, page: Page):
|
|
"""初始化表格辅助工具
|
|
|
|
Args:
|
|
page: Playwright页面对象
|
|
"""
|
|
self.page = page
|
|
|
|
def get_row_count(self, table_selector: str, timeout: int = 10000) -> int:
|
|
"""获取表格行数
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
表格行数
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
rows = self.page.locator(f"{table_selector} tbody tr")
|
|
return rows.count()
|
|
|
|
def get_column_count(self, table_selector: str, timeout: int = 10000) -> int:
|
|
"""获取表格列数
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
表格列数
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
header_row = self.page.locator(f"{table_selector} thead tr")
|
|
if header_row.count() > 0:
|
|
cells = header_row.locator("th, td")
|
|
return cells.count()
|
|
|
|
first_row = self.page.locator(f"{table_selector} tbody tr:first-child")
|
|
cells = first_row.locator("td")
|
|
return cells.count()
|
|
|
|
def get_cell_text(
|
|
self, table_selector: str, row_index: int, col_index: int, timeout: int = 10000
|
|
) -> str:
|
|
"""获取单元格文本
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
col_index: 列索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
单元格文本
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
cell = self.page.locator(
|
|
f"{table_selector} tbody tr:nth-child({row_index + 1}) td:nth-child({col_index + 1})"
|
|
)
|
|
return cell.text_content()
|
|
|
|
def get_row_data(self, table_selector: str, row_index: int, timeout: int = 10000) -> List[str]:
|
|
"""获取整行数据
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
行数据列表
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr:nth-child({row_index + 1})")
|
|
cells = row.locator("td")
|
|
|
|
row_data = []
|
|
for i in range(cells.count()):
|
|
row_data.append(cells.nth(i).text_content())
|
|
|
|
return row_data
|
|
|
|
def get_all_rows(self, table_selector: str, timeout: int = 10000) -> List[List[str]]:
|
|
"""获取所有行数据
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
所有行数据列表
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
rows = self.page.locator(f"{table_selector} tbody tr")
|
|
|
|
all_rows = []
|
|
for i in range(rows.count()):
|
|
row_data = self.get_row_data(table_selector, i, timeout)
|
|
all_rows.append(row_data)
|
|
|
|
return all_rows
|
|
|
|
def get_column_data(
|
|
self, table_selector: str, col_index: int, timeout: int = 10000
|
|
) -> List[str]:
|
|
"""获取整列数据
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
col_index: 列索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
列数据列表
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
rows = self.page.locator(f"{table_selector} tbody tr")
|
|
|
|
column_data = []
|
|
for i in range(rows.count()):
|
|
cell_text = self.get_cell_text(table_selector, i, col_index, timeout)
|
|
column_data.append(cell_text)
|
|
|
|
return column_data
|
|
|
|
def get_headers(self, table_selector: str, timeout: int = 10000) -> List[str]:
|
|
"""获取表头
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
表头列表
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
header_row = self.page.locator(f"{table_selector} thead tr")
|
|
|
|
if header_row.count() > 0:
|
|
headers = header_row.locator("th")
|
|
header_list = []
|
|
for i in range(headers.count()):
|
|
header_list.append(headers.nth(i).text_content())
|
|
return header_list
|
|
|
|
return []
|
|
|
|
def click_row(self, table_selector: str, row_index: int, timeout: int = 10000) -> None:
|
|
"""点击表格行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr:nth-child({row_index + 1})")
|
|
row.click()
|
|
|
|
def click_cell(
|
|
self, table_selector: str, row_index: int, col_index: int, timeout: int = 10000
|
|
) -> None:
|
|
"""点击单元格
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
col_index: 列索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
cell = self.page.locator(
|
|
f"{table_selector} tbody tr:nth-child({row_index + 1}) td:nth-child({col_index + 1})"
|
|
)
|
|
cell.click()
|
|
|
|
def find_row_by_cell_text(
|
|
self, table_selector: str, search_text: str, col_index: int = 0, timeout: int = 10000
|
|
) -> Optional[int]:
|
|
"""通过单元格文本查找行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
search_text: 要搜索的文本
|
|
col_index: 搜索的列索引
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
找到的行索引,未找到则返回None
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
rows = self.page.locator(f"{table_selector} tbody tr")
|
|
|
|
for i in range(rows.count()):
|
|
cell_text = self.get_cell_text(table_selector, i, col_index, timeout)
|
|
if cell_text and search_text in cell_text:
|
|
return i
|
|
|
|
return None
|
|
|
|
def find_rows_by_cell_text(
|
|
self, table_selector: str, search_text: str, col_index: int = 0, timeout: int = 10000
|
|
) -> List[int]:
|
|
"""通过单元格文本查找所有匹配行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
search_text: 要搜索的文本
|
|
col_index: 搜索的列索引
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
找到的行索引列表
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
rows = self.page.locator(f"{table_selector} tbody tr")
|
|
|
|
matching_rows = []
|
|
for i in range(rows.count()):
|
|
cell_text = self.get_cell_text(table_selector, i, col_index, timeout)
|
|
if cell_text and search_text in cell_text:
|
|
matching_rows.append(i)
|
|
|
|
return matching_rows
|
|
|
|
def sort_table_by_column(
|
|
self, table_selector: str, col_index: int, ascending: bool = True, timeout: int = 10000
|
|
) -> None:
|
|
"""按列排序表格
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
col_index: 列索引(从0开始)
|
|
ascending: 是否升序排序
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
header = self.page.locator(f"{table_selector} thead tr th:nth-child({col_index + 1})")
|
|
header.click()
|
|
|
|
if not ascending:
|
|
header.click()
|
|
|
|
def filter_table(self, table_selector: str, filter_text: str, timeout: int = 10000) -> None:
|
|
"""过滤表格
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
filter_text: 过滤文本
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
|
|
filter_input = self.page.locator(f"{table_selector} ~ .filter-input, .search-input")
|
|
if filter_input.count() > 0:
|
|
filter_input.fill(filter_text)
|
|
self.page.keyboard.press("Enter")
|
|
|
|
def select_row_checkbox(
|
|
self, table_selector: str, row_index: int, timeout: int = 10000
|
|
) -> None:
|
|
"""选择行的复选框
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr:nth-child({row_index + 1})")
|
|
checkbox = row.locator("input[type='checkbox']")
|
|
|
|
if checkbox.count() > 0:
|
|
checkbox.check(force=True)
|
|
|
|
def select_all_rows(self, table_selector: str, timeout: int = 10000) -> None:
|
|
"""选择所有行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
|
|
select_all_checkbox = self.page.locator(
|
|
f"{table_selector} thead input[type='checkbox'], {table_selector} ~ .select-all-checkbox"
|
|
)
|
|
|
|
if select_all_checkbox.count() > 0:
|
|
select_all_checkbox.check(force=True)
|
|
|
|
def deselect_all_rows(self, table_selector: str, timeout: int = 10000) -> None:
|
|
"""取消选择所有行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
|
|
select_all_checkbox = self.page.locator(
|
|
f"{table_selector} thead input[type='checkbox'], {table_selector} ~ .select-all-checkbox"
|
|
)
|
|
|
|
if select_all_checkbox.count() > 0:
|
|
select_all_checkbox.uncheck(force=True)
|
|
|
|
def get_row_by_id(
|
|
self, table_selector: str, row_id: str, timeout: int = 10000
|
|
) -> Optional[Locator]:
|
|
"""通过ID获取行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_id: 行ID
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
行元素,未找到则返回None
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr[data-id='{row_id}']")
|
|
|
|
if row.count() > 0:
|
|
return row
|
|
|
|
return None
|
|
|
|
def click_row_action_button(
|
|
self, table_selector: str, row_index: int, action: str = "edit", timeout: int = 10000
|
|
) -> None:
|
|
"""点击行操作按钮
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
action: 操作类型(edit/delete/view等)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr:nth-child({row_index + 1})")
|
|
|
|
action_button = row.locator(f"button.{action}, .{action}-button")
|
|
if action_button.count() > 0:
|
|
action_button.click()
|
|
else:
|
|
actions_cell = row.locator("td:last-child")
|
|
actions_cell.click()
|
|
|
|
def wait_for_table_load(
|
|
self, table_selector: str, timeout: int = 10000
|
|
) -> None:
|
|
"""等待表格加载完成
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
self.page.wait_for_timeout(500)
|
|
|
|
def is_table_empty(self, table_selector: str, timeout: int = 10000) -> bool:
|
|
"""检查表格是否为空
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
表格是否为空
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
|
|
empty_message = self.page.locator(
|
|
f"{table_selector} ~ .no-data, {table_selector} ~ .empty-message"
|
|
)
|
|
|
|
if empty_message.count() > 0 and empty_message.is_visible():
|
|
return True
|
|
|
|
return self.get_row_count(table_selector, timeout) == 0
|
|
|
|
def get_table_data_as_dict(
|
|
self, table_selector: str, timeout: int = 10000
|
|
) -> List[Dict[str, str]]:
|
|
"""获取表格数据为字典列表
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
timeout: 元素等待超时时间(毫秒)
|
|
|
|
Returns:
|
|
表格数据字典列表,键为列名,值为单元格内容
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
|
|
headers = self.get_headers(table_selector, timeout)
|
|
all_rows = self.get_all_rows(table_selector, timeout)
|
|
|
|
table_data = []
|
|
for row_data in all_rows:
|
|
row_dict = {}
|
|
for i, header in enumerate(headers):
|
|
if i < len(row_data):
|
|
row_dict[header] = row_data[i]
|
|
table_data.append(row_dict)
|
|
|
|
return table_data
|
|
|
|
def scroll_to_row(self, table_selector: str, row_index: int, timeout: int = 10000) -> None:
|
|
"""滚动到指定行
|
|
|
|
Args:
|
|
table_selector: 表格选择器
|
|
row_index: 行索引(从0开始)
|
|
timeout: 元素等待超时时间(毫秒)
|
|
"""
|
|
self.page.wait_for_selector(table_selector, timeout=timeout, state="visible")
|
|
row = self.page.locator(f"{table_selector} tbody tr:nth-child({row_index + 1})")
|
|
row.scroll_into_view_if_needed()
|