Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-test/tool/test_code_gen.py
T

229 lines
8.7 KiB
Python

import re
import pytest
from playwright.async_api import async_playwright, expect
from common.base_page_test import BasePageTest
from common.config import Config
class GenTableTest(BasePageTest):
"""代码生成业务测试"""
async def test_gen_code_flow(self) -> None:
"""测试代码生成全流程:导入 -> 编辑 -> 预览 -> 删除"""
table_name = 'sys_post'
# 1. 导航到代码生成页面
await self.navigate_to_gen_page()
# 确保环境清理 (如果存在 sys_post 则先删除)
await self.ensure_table_not_exists(table_name)
# 2. 导入表
await self.import_table(table_name)
# 3. 编辑表
await self.edit_table(table_name, '测试生成')
# 4. 预览代码
await self.preview_code(table_name)
await self.page.wait_for_timeout(1000)
# 5. 删除表 (清理环境)
await self.delete_table(table_name)
async def navigate_to_gen_page(self) -> None:
"""导航到代码生成页面"""
await self.goto_page(Config.frontend_url + '/tool/gen')
await self.wait_for_selector('.app-container')
async def ensure_table_not_exists(self, table_name: str) -> None:
"""确保表不存在 (如果存在则删除)"""
await self.delete_table(table_name, verify=False)
async def search_table(self, table_name: str) -> None:
"""搜索表"""
# 确保在主搜索表单中搜索
search_form = self.page.locator('.el-form').first
await search_form.get_by_role('textbox', name='表名称').fill(table_name)
await search_form.get_by_role('button', name='搜索').click()
# 等待加载
loading = self.page.locator('.el-loading-mask')
if await loading.count() > 0:
await expect(loading.first).to_be_hidden(timeout=10000)
await self.page.wait_for_timeout(300)
async def import_table(self, table_name: str) -> None:
"""导入表"""
await self.page.get_by_role('button', name='导入').click()
dialog = self.page.locator("div[role='dialog'][aria-label='导入表']")
await dialog.wait_for()
# 搜索要导入的表
await dialog.get_by_placeholder('请输入表名称').fill(table_name)
await dialog.get_by_role('button', name='搜索').click()
# 等待搜索结果
await dialog.locator(f"tr:has-text('{table_name}')").wait_for()
# 选中行
row = dialog.locator('tr').filter(has=self.page.get_by_text(table_name, exact=True))
# 点击复选框
checkbox = row.locator('.el-checkbox')
await checkbox.click()
# 验证已选中
# Element Plus checkbox 选中时,最外层 label.el-checkbox 会有 is-checked 类
await expect(checkbox).to_have_class(re.compile(r'is-checked'))
await self.page.wait_for_timeout(500)
await dialog.get_by_role('button', name='确 定').click()
# 检查是否有"请选择要导入的表"错误
try:
await expect(self.page.get_by_text('请选择要导入的表')).to_be_visible(timeout=2000)
print("ERROR: Selection failed, '请选择要导入的表' appeared.")
except AssertionError:
pass
# 等待一会,让弹窗自动关闭
await self.page.wait_for_timeout(5000)
# 如果弹窗还在,尝试关闭它以免阻塞后续操作
if await dialog.is_visible():
print('WARNING: Import dialog still visible after timeout. Forcing close.')
# Check for error messages
if await self.page.locator('.el-message--error').count() > 0:
msg = await self.page.locator('.el-message--error').all_inner_texts()
print(f'ERROR MESSAGE: {msg}')
# 点击取消关闭弹窗
await dialog.get_by_role('button', name='取 消').click()
await expect(dialog).to_be_hidden()
# 手动刷新列表
await self.page.get_by_role('button', name='搜索').click()
# 验证导入成功 (搜索并在列表中看到)
# 使用 .app-container 限定在主页面表格,避免匹配到弹窗中的隐藏行
await self.search_table(table_name)
row = self.page.locator('.app-container .el-table__body-wrapper tbody tr').filter(
has=self.page.get_by_text(table_name, exact=True)
)
for _i in range(5):
try:
await expect(row.first).to_be_visible(timeout=3000)
break
except AssertionError:
await self.search_table(table_name)
await expect(row.first).to_be_visible(timeout=5000)
async def edit_table(self, table_name: str, remark: str) -> None:
"""编辑表"""
await self.search_table(table_name)
row = self.page.locator(f"tbody tr:has-text('{table_name}')")
# 点击编辑 (操作列第2个按钮,索引1)
# 按钮顺序: 预览, 编辑, 删除, 同步, 生成
await row.locator('button').nth(1).click()
# 等待编辑页面 (tab页)
await self.page.wait_for_selector("div[role='tablist']")
# 修改基本信息 -> 表描述
# 确保在基本信息 Tab
await self.page.get_by_text('基本信息').click()
await self.page.get_by_role('textbox', name='表描述').fill(remark)
# 提交
await self.page.get_by_role('button', name='提交').click()
# 验证回到列表
await self.wait_for_selector('.app-container')
# 验证描述已更新
await self.search_table(table_name)
await self.wait_for_selector(f"tbody tr:has-text('{remark}')")
async def preview_code(self, table_name: str) -> None:
"""预览代码"""
await self.search_table(table_name)
row = self.page.locator(f"tbody tr:has-text('{table_name}')")
# 点击预览 (操作列第1个按钮,索引0)
await row.locator('button').nth(0).click()
# 等待预览弹窗
dialog = self.page.locator("div[role='dialog'][aria-label='代码预览']")
await dialog.wait_for()
# 验证存在代码内容
# pre 可能有多个(多tab),只检查可见的
await dialog.locator('pre:visible').first.wait_for()
content = await dialog.locator('pre:visible').first.text_content()
assert 'class' in content or 'import' in content or 'package' in content
# 关闭预览 (点击右上角关闭按钮)
await dialog.locator('.el-dialog__headerbtn').click()
async def delete_table(self, table_name: str, verify: bool = True) -> None:
"""删除表 (支持删除多条重复数据)"""
# 循环删除直到不存在
for _i in range(5):
await self.search_table(table_name)
# 使用 strict matching
# 限制在 .app-container .el-table__body-wrapper 以避免固定列导致的重复 以及 避免匹配到弹窗中的行
row = self.page.locator('.app-container .el-table__body-wrapper tbody tr').filter(
has=self.page.get_by_text(table_name, exact=True)
)
count = await row.count()
if count == 0:
break
# 针对第一行操作
# 使用 force=True 确保点击,防止遮挡
btns = row.first.locator('button')
await btns.nth(2).click(force=True)
# 处理确认弹窗
await self.page.get_by_role('button', name='确定').click()
# 等待删除成功提示
# 使用 specific selector 避免匹配到代码预览中的文本
await expect(self.page.locator('.el-message__content').filter(has_text='删除成功')).to_be_visible(
timeout=5000
)
# 等待提示消失,防止干扰下一次操作
await expect(self.page.locator('.el-message__content').filter(has_text='删除成功')).to_be_hidden(
timeout=5000
)
if verify:
# 验证删除成功
# 重新搜索验证不存在
await self.search_table(table_name)
# 使用 strict matching
row = self.page.locator('.app-container .el-table__body-wrapper tbody tr').filter(
has=self.page.get_by_text(table_name, exact=True)
)
# 期望找不到或者 count为0
await expect(row).to_have_count(0)
@pytest.mark.asyncio
async def test_gen_table_page() -> None:
"""测试代码生成页面功能"""
async with async_playwright() as p:
test_instance = GenTableTest()
await test_instance.setup(p)
try:
await test_instance.test_gen_code_flow()
finally:
await test_instance.teardown()