test/framework/business/mirror_assets_page.py

202 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
from framework.core.base_page import BasePage
from framework.core.logger import get_logger
from framework.config.settings import Config
logger = get_logger("MirrorAssetsPage")
class MirrorAssetsPage(BasePage):
"""镜像资产页面操作"""
def __init__(self, page):
super().__init__(page)
def navigate_to(self):
"""导航到镜像资产页面"""
logger.info("🚀 导航到镜像资产页面")
# 根据截图修正 URL
self.page.goto(f"{Config.BASE_URL}/mirror-center/private")
time.sleep(3)
def click_my_mirror(self):
"""进入镜像资产并确保切换到 [我的镜像] (Prod 强化版)"""
logger.info("👉 准备切换至 [我的镜像] 标签")
# 1. 等待页面基础渲染
timeout = 15
start_time = time.time()
while time.time() - start_time < timeout:
# 尝试定位任何包含“我的镜像”的可见元素
loc = self.page.locator("text='我的镜像'").filter(has_not=self.page.locator(".p-hidden")).first
if loc.is_visible():
loc.click()
logger.info("✅ 已点击 [我的镜像]")
break
time.sleep(1)
else:
logger.error(f"❌ 15s 后仍未看到 [我的镜像] 标签。URL: {self.page.url}")
# 这里的 JS 诊断会更彻底一些
diag_log = self.page.evaluate("""() => {
const all = Array.from(document.querySelectorAll('*'));
return all.filter(el => el.innerText && el.innerText.includes('镜像') && el.offsetWidth > 0)
.map(el => ({ tag: el.tagName, text: el.innerText.trim().slice(0, 20), id: el.id, class: el.className }))
.slice(0, 10);
}""")
logger.info(f"🔍 故障诊断 - 包含'镜像'关键词的可见元素: {diag_log}")
raise Exception("未能定位到 [我的镜像] 标签页")
# 2. 确认切换成功 (等待高亮)
try:
self.page.wait_for_selector(".p-highlight, [aria-selected='true'], .active", timeout=5000)
logger.info("✅ 标签页高亮确认成功")
except:
logger.warning("⚠️ 未能确认高亮状态,但已尝试点击")
# 3. 记录当前 Tab 状态
tabs = self.page.evaluate("""() => {
return Array.from(document.querySelectorAll('li, a, span, div'))
.filter(el => ['我的镜像', '群组镜像'].some(t => el.innerText?.includes(t)) && el.offsetWidth > 0)
.map(el => el.innerText.trim().slice(0,10));
}""")
logger.info(f"📊 当前侦测到的标签页: {list(set(tabs))}")
time.sleep(2)
def click_add_button(self):
"""点击 [添加镜像] 按钮"""
logger.info("👉 点击 [添加镜像] 按钮")
self.smart_click("添加镜像")
def click_mirror_in_list(self, mirror_name=None):
"""点击列表末尾的可用镜像 (不限名称)"""
# 调试日志:探测当前所有镜像状态
try:
dom_info = self.page.evaluate("""() => {
const rows = Array.from(document.querySelectorAll('.p-datatable-row, tr, .p-card'));
return rows.filter(r => r.innerText.includes('可用') || r.innerText.includes('Available'))
.map(r => r.innerText.slice(0, 50).replace(/\\n/g, ' '));
}""")
logger.info(f"📊 当前页面可用镜像列表: {dom_info}")
except:
pass
logger.info("👉 尝试开启【可用】镜像列表中最后一个详情页")
# 直接通过状态文案定位行或卡片
selector = "tr:has-text('可用'), div.p-card:has-text('可用'), .p-datatable-row:has-text('可用')"
try:
# 等待至少一个可用镜像
self.page.wait_for_selector(selector, timeout=10000)
locators = self.page.locator(selector)
count = locators.count()
if count > 0:
logger.info(f"✅ 找到 {count} 个可用镜像,准备点击最后一个...")
target = locators.nth(count - 1)
target.scroll_into_view_if_needed()
target.click()
else:
raise Exception("未找到处于 '可用' 状态的镜像")
except Exception as e:
logger.error(f"❌ 镜像列表尝试点击失败: {e}")
# 暴力兜底:点击表格最后一行的第一个单元格
self.page.evaluate("""() => {
const rows = document.querySelectorAll('.p-datatable-row, tr');
if (rows.length > 0) rows[rows.length - 1].click();
}""")
# 跳转监控
logger.info("⏳ 等待跳转至详情页 (检测 [快速创建] 按钮)...")
try:
self.page.wait_for_selector("text='快速创建'", timeout=15000)
logger.info("✅ 已进入详情页")
except:
logger.error("❌ 跳转详情页失败,可能点击未奏效")
raise Exception("进入详情页超时")
def click_quick_create(self):
"""点击 [快速创建] 按钮 (增加 JS 兜底)"""
logger.info("👉 点击 [快速创建] 按钮")
try:
# 策略1Playwright 智能点击
self.smart_click("快速创建", timeout=5000)
except:
# 策略2JS 暴力点击
logger.warning("⚠️ smart_click 失败,正在执行 JS [快速创建] 强制点击")
self.page.evaluate("""() => {
const buttons = Array.from(document.querySelectorAll('button, a, .p-button'));
const target = buttons.find(b => b.innerText && b.innerText.includes('快速创建'));
if (target) {
target.scrollIntoView({block: 'center'});
target.click();
}
}""")
time.sleep(3) # 给弹窗充足的渲染时间
def fill_mirror_name(self, name):
"""输入名称 (增加深度探测与日志)"""
logger.info(f"⌨️ 正在尝试定位 [名称] 输入框...")
# 1. 尝试常见业务 Label (智能探测模式)
for lbl in ["名称", "桌面名称", "实例名称"]:
try:
# 尝试极短的时间验证,快进快出
self.smart_fill(lbl, name, timeout=2000)
logger.info(f"✅ 成功命中标签 [{lbl}]")
return
except:
continue
# 2. 尝试 Placeholder 模式
try:
loc = self.page.locator("input[placeholder*='名称'], input[placeholder*='name']").first
if loc.is_visible():
loc.fill(name)
logger.info("✅ 通过 Placeholder 定位成功")
return
except:
pass
# 3. 终极兜底:弹窗内的第一个可见 input (针对弹窗内 label 定位偏移的情况)
try:
# 找到当前可见的 dialog
dialog = self.page.locator(".p-dialog, [role='dialog']").first
if dialog.is_visible():
target_input = dialog.locator("input:not([type='hidden'])").first
target_input.fill(name)
logger.info("✅ 通过 [弹窗内首个输入框] 定位成功")
return
except:
pass
# 4. 诊断日志:如果走到这里说明彻底找不到,获取当前页面所有输入框上下文,发往日志
try:
dom_report = self.page.evaluate("""() => {
const inputs = Array.from(document.querySelectorAll('input, select, textarea'));
return inputs.map(i => {
const visible = i.offsetWidth > 0;
if(!visible) return null;
return {
tag: i.tagName,
placeholder: i.placeholder,
id: i.id,
className: i.className,
parentText: i.parentElement?.innerText?.slice(0, 30)
};
}).filter(x => x);
}""")
logger.error(f"❌ 无法定位输入框。当前页面所有可见输入框报告: {dom_report}")
except:
pass
raise Exception(f"❌ 无法在镜像创建弹窗找到名称输入框,报告已记录")
def select_sku(self, sku_id):
"""选择资源规格 (复用智能下拉框逻辑)"""
logger.info(f"🎯 镜像资产规格选择: {sku_id}")
self.smart_select("资源规格", sku_id)
def click_create_button(self):
"""点击 创建并开机 按钮"""
logger.info("👉 点击 [创建并开机] 按钮")
# 通常弹窗下方是 确定 或 立即创建
self.smart_click("创建并开机")