Files
playwrite/services/page_navigator.py
Misaka_Company 5024384c73 refactor: restructure main_clean.py with dependency injection and interfaces
Refactor discrete material cleaner to improve maintainability and testability:

- Introduce dependency injection pattern for service components
- Add interfaces (IPageNavigator, IMaterialExtractor, IDeletionChecker)
- Extract PageNavigator service for page navigation logic
- Extract MaterialExtractor service for data extraction
- Extract DatabaseDeletionChecker service for deletion logic
- Add MaterialInfo data model for structured data
- Centralize configuration (CleanerConfig, UIConstants)
- Implement separation of concerns across services layer

Also includes comprehensive execution mechanism documentation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 14:17:37 +08:00

172 lines
6.4 KiB
Python
Raw 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.
"""
页面导航服务实现
实现页面导航、iframe 操作等具体逻辑
"""
import re
import time
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
from interfaces.i_page_navigator import IPageNavigator
from config.ui_constants import UIConstants
class PageNavigator(IPageNavigator):
"""页面导航实现"""
def __init__(self, config: UIConstants, verbose: bool = True):
"""
初始化页面导航器
Args:
config: UI 配置
verbose: 是否打印详细日志
"""
self.config = config
self.verbose = verbose
def _print(self, *args, **kwargs):
"""打印日志(如果 verbose=True"""
if self.verbose:
print(*args, **kwargs)
def navigate_to_main_page(self, page) -> tuple:
"""
导航到主页面 iframe
实际返回 (page, main_frame, inner_frame) 的元组
"""
# 点击打开"功能菜单"
main_frame = page.locator(self.config.selectors.FORWARD_FRAME).content_frame
main_frame.locator("i").first.click()
# 点击打开"离散生产订单维护"
with page.expect_popup() as page1_info:
main_frame.get_by_title("离散生产订单维护", exact=True).first.click()
page1 = page1_info.value
# 获取 nested iframe
main_frame = page1.locator(self.config.selectors.FORWARD_FRAME).content_frame
inner_frame_locator = main_frame.locator(self.config.selectors.MAIN_IFRAME)
inner_frame_locator.wait_for(
state="visible", timeout=self.config.timeouts.IFRAME_VISIBLE
)
inner_frame = inner_frame_locator.content_frame
return page1, main_frame, inner_frame
def navigate_to_order_page(self, main_frame, page):
"""导航到订单页面"""
# 这个方法在当前实现中与 navigate_to_main_page 类似
# 返回新的页面对象和内部 iframe
return self.navigate_to_main_page(page)
def navigate_to_material_plan_page(self, page):
"""
导航到备料计划页面
Args:
page: 订单页面对象 (page1)
Returns:
(page2, inner_frame) - 新页面和内部 iframe
"""
# 获取主 iframe
main_frame = page.locator(self.config.selectors.FORWARD_FRAME).content_frame
inner_frame = main_frame.locator(self.config.selectors.MAIN_IFRAME).content_frame
# 点击"更多"
inner_frame.locator(self.config.selectors.HOT_KEY_HEAD).get_by_text(
self.config.selectors.MORE_BUTTON
).click()
# 点击"备料计划"并等待新页面
with page.expect_popup() as page2_info:
inner_frame.get_by_text(self.config.selectors.MATERIAL_PLAN).click()
page2 = page2_info.value
# 获取 nested iframe
main_frame = page2.locator(self.config.selectors.FORWARD_FRAME).content_frame
inner_frame_locator = main_frame.locator(self.config.selectors.MAIN_IFRAME)
inner_frame_locator.wait_for(
state="visible", timeout=self.config.timeouts.IFRAME_VISIBLE
)
inner_frame = inner_frame_locator.content_frame
# 等待备料计划页面加载完成
self._wait_for_plan_page_loaded(inner_frame)
return page2, inner_frame
def setup_query_interface(self, frame):
"""设置查询界面"""
# 点击图标按钮打开查询界面
frame.locator(self.config.selectors.SEARCH_ICON).click()
frame.get_by_text(self.config.selectors.QUERY_BY_ORDER).click()
frame.get_by_role("tab", name=self.config.selectors.TAB_ALL).click()
# 填充并验证显示数量,如果失败则重试
max_retries = self.config.retry.DISPLAY_COUNT_MAX_RETRIES
expected_value = self.config.retry.EXPECTED_DISPLAY_COUNT
for attempt in range(max_retries):
frame.locator(self.config.selectors.DISPLAY_COUNT_INPUT).fill(expected_value)
frame.locator(self.config.selectors.DISPLAY_COUNT_INPUT).press("Enter")
# 检查填充是否成功
actual_value = frame.locator(self.config.selectors.DISPLAY_COUNT_INPUT).input_value()
if actual_value == expected_value:
self._print(f"文本框填充成功: {expected_value}")
break
else:
self._print(
f"{attempt + 1} 次填充失败,实际值: {actual_value},重试..."
)
if attempt == max_retries - 1:
self._print(
f"警告: {max_retries} 次尝试后仍未成功填充,继续执行..."
)
def wait_for_page_loaded(self, frame):
"""等待页面加载完成"""
loading_locator = frame.locator("div").filter(has_text="加载中").nth(1)
try:
loading_locator.wait_for(
state="visible", timeout=self.config.timeouts.LOADING_VISIBLE_WAIT
)
loading_locator.wait_for(
state="hidden", timeout=self.config.timeouts.LOADING_HIDDEN_WAIT
)
except PlaywrightTimeoutError:
# 加载很快完成,或者没有出现加载提示
pass
return True
def _wait_for_plan_page_loaded(self, inner_frame):
"""等待备料计划页面加载完成(内部方法)"""
self._print("等待备料计划页面加载完成...")
plan_code_locator = inner_frame.get_by_text(
re.compile(r"^离散备料计划维护:")
)
plan_code_locator.wait_for(
state="visible", timeout=self.config.timeouts.PLAN_CODE_VISIBLE
)
# 循环检查编码是否已加载
max_wait = self.config.timeouts.PLAN_CODE_MAX_WAIT
wait_interval = self.config.timeouts.PLAN_CHECK_INTERVAL
waited = 0
plan_code = None
while waited < max_wait:
plan_text = plan_code_locator.inner_text()
match = re.search(r"离散备料计划维护:(.+)", plan_text)
if match and match.group(1).strip():
plan_code = match.group(1).strip()
break
time.sleep(wait_interval)
waited += wait_interval
if plan_code:
self._print(f"备料计划页面加载完成,编码: {plan_code}")
else:
self._print(f"警告: 备料计划页面加载超时")