import time import os import random import string from framework.core.base_page import BasePage from framework.core.logger import get_logger logger = get_logger("ThreeDGenerationPage") class ThreeDGenerationPage(BasePage): """3D生成页面 POM - 包含生成、等待与保存逻辑""" MENU_TEXT = "3D生成" def __init__(self, page): self.page = page def navigate_to(self): """进入3D生成页面""" logger.info("正在切换到3D生成页面...") self.smart_click(self.MENU_TEXT) try: self.page.wait_for_selector(".p-button:has-text('保存'), .p-tabmenu", timeout=10000) except: pass time.sleep(2) def upload_image(self, file_path): """通过隐藏 input 上传图片""" logger.info(f"📤 准备上传 3D 生成素材: {file_path}") try: # 直接通过 input 元素设置文件 self.page.set_input_files("input[type='file']", file_path) except Exception as e: logger.warning(f"⚠️ 直接注入失败,尝试点击图标触发: {e}") with self.page.expect_file_chooser() as fc_info: self.page.click("button.p-button-icon-only:has(.pi-arrow-up)") fc_info.value.set_files(file_path) time.sleep(2) def start_generation(self, retries=3): """点击底部生成按钮 (图标按钮) - 增加 3 次重试机制 & 15s 业务异常监控""" logger.info(f"🚀 准备点击生成按钮 (最多重试 {retries} 次)...") target_btn = "button.p-button-icon-only:has(.pi-arrow-up)" for i in range(retries): try: # 1. 执行物理点击 self.page.wait_for_selector(target_btn, state="visible", timeout=5000) self.page.click(target_btn) logger.info(f"🖱️ 第 {i+1} 次点击动作已发出") # 2. 持续观测业务状态 (监控 15s) logger.info(f"👀 正在观测第 {i+1} 次生成任务的业务反馈...") is_failed = False for _ in range(15): # 使用显式文本探测,捕捉 Toast 或 页面文字报错 error_detected = self.page.locator("text=/网络错误|Generation failed|failed/i").first.is_visible() if error_detected: logger.warning(f"⚠️ 第 {i+1} 次运行检测到业务异常文字,准备重试") is_failed = True break time.sleep(1) if not is_failed: logger.info(f"✅ 第 {i+1} 次任务成功启动且未检出即时报错") return True # 业务失败且有次数则间隔后重试 if i < retries - 1: time.sleep(2) continue except Exception as e: # 捕捉定位或点击失败 logger.warning(f"⚠️ 第 {i+1} 次点击尝试失败 (物理层): {e}") if i < retries - 1: time.sleep(2) continue # 3. 最终失败处理 error_msg = f"❌ 3D 生成任务在重试 {retries} 次后仍无法启动,程序强行退出" logger.error(error_msg) raise Exception(error_msg) def wait_for_result_and_save(self, timeout=600): """等待右上角结果出现并点击保存""" logger.info("⏳ 等待生成结果...") # 右上角模型参数卡片里出现内容或“保存”按钮变得可用 start_time = time.time() while time.time() - start_time < timeout: content = self.page.content() # 检查是否有反映生成完成的标志,或者截图中的“保存”按钮 if "保存" in content: save_btn = self.page.locator("button:has-text('保存')").nth(0) if save_btn.is_visible() and save_btn.is_enabled(): logger.info("✨ 生成结果已出现,点击[保存]按钮") save_btn.click() return True time.sleep(5) logger.error("❌ 等待结果超时") return False def handle_save_dialog(self): """处理保存模型弹窗及其路径选择流程""" # 1. 生成随机资产名称 random_name = "".join(random.choices(string.ascii_letters + string.digits, k=8)) asset_name = f"Auto3D_{random_name}" logger.info(f"📝 输入资产名称: {asset_name}") # 输入资产名称 name_input = self.page.locator("input[placeholder='请输入资产名称']") name_input.fill(asset_name) time.sleep(1) # 2. 点击保存路径触发弹窗 logger.info("📂 点击保存路径选择器...") path_input = self.page.locator("input[placeholder='请选择要保存的文件夹']") path_input.click() time.sleep(2) # 3. 路径选择弹窗内部操作 logger.info(f"📂 正在开启路径选择弹窗,进行多列导航...") # 修正核心:弹窗标题实为“选择文件” (根据截图中的渲染结果) file_picker_dialog = self.page.locator(".p-dialog:has-text('选择文件')") # 定位左侧 3d生成(勿删) 根目录项 folder_item = file_picker_dialog.locator("span:has-text('3d生成(勿删)')").first folder_item.click() time.sleep(2) # 留出中间列刷新的渲染窗口 # 定位刚才生成的随机文件夹名称 logger.info(f"🎯 正在定位生成的文件夹项: {asset_name}") try: # 延长等待时间 (20s) 提高环境波动兼容性 target_item = file_picker_dialog.locator(f"//span[contains(text(), '{asset_name}')]").last target_item.wait_for(state="attached", timeout=20000) target_item.scroll_into_view_if_needed() target_item.click() logger.info(f"✅ 文件夹已在 [选择文件] 弹窗中成功选中") except Exception as e: logger.warning(f"⚠️ 在 20s 内未能自动选中项 {asset_name},可能是刷新延迟: {e}") # 4. 点击路径选择弹窗的确认按钮 logger.info("⏳ 点击路径选择器[确定]按钮...") # 修正:同步标题匹配为“选择文件” path_dialog_confirm = file_picker_dialog.locator("button:has-text('确定')").last path_dialog_confirm.click() logger.info("✅ 路径跳转弹窗已确认并关闭") time.sleep(2) # 留出 UI 彻底销毁的时间 # 5. 校验路径是否回填 filled_val = path_input.get_attribute("value") logger.info(f"🔍 检查保存路径回填状态: {filled_val}") # 6. 点击保存模型弹窗最终确认按钮 (精准定位保存弹窗确定) logger.info("💾 点击保存模型弹窗最终确认按钮") save_dialog_confirm = self.page.locator(".p-dialog:has-text('保存模型')").locator("button:has-text('确定')").last save_dialog_confirm.wait_for(state="visible", timeout=5000) save_dialog_confirm.click() logger.info("🎉 3D 生成场景全流程保存确认成功") time.sleep(2) return asset_name