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: # 策略1:Playwright 智能点击 self.smart_click("快速创建", timeout=5000) except: # 策略2:JS 暴力点击 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("创建并开机")