165 lines
7.3 KiB
Python
165 lines
7.3 KiB
Python
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
|