fix(zto): resolve real-today date picker failure on month-boundary days
The ZTO jQuery Date Range Picker always displays a dual-month view. When today falls on the 1st (or early days) of a month, the same date appears in both panels: a hidden ghost cell (month1, display:none) and a visible cell (month2). Both carry the real-today CSS class, so .first picks the hidden one, causing wait_for(visible) to timeout. Replace DOM-based real-today time extraction with Python datetime computation. Add _zto_find_visible_day to locate the actually visible date cell (skipping hidden ghost cells, trying both midnight and 23:59:59 time variants). Fix _zto_flip_to_target_month to use the same visibility-aware lookup. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import os
|
||||
import re
|
||||
import time
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
|
||||
from inbound_verify.paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
@@ -93,20 +93,50 @@ def _dom_click(locator):
|
||||
)
|
||||
|
||||
|
||||
def _zto_compute_target_time(offset):
|
||||
"""直接从 Python datetime 计算目标日期的毫秒级时间戳(本地时区零点)。
|
||||
|
||||
不再从 DOM 的 real-today 元素读取 time 属性,避免双月视图下 real-today
|
||||
同时出现在 month1(隐藏 ghost cell)和 month2(可见)导致 .first 取到隐藏元素。
|
||||
"""
|
||||
target_date = datetime.now().date() - timedelta(days=offset)
|
||||
target_dt = datetime(target_date.year, target_date.month, target_date.day)
|
||||
return int(target_dt.timestamp() * 1000)
|
||||
|
||||
|
||||
def _zto_find_visible_day(frame_locator, target_time):
|
||||
"""在双月日期控件中查找可见的日期格子。
|
||||
|
||||
jQuery Date Range Picker 双月视图下,同一天可能出现在两个面板中:
|
||||
- month1(左面板)的溢出 ghost cell:display:none,不可见
|
||||
- month2(右面板)的正常 cell:可见
|
||||
|
||||
同一日期在 DOM 中可能有毫秒级差异(零点 vs 23:59:59),遍历匹配并返回
|
||||
第一个 visible 的;无可见匹配返回 None。
|
||||
"""
|
||||
# 尝试两个时间变体:零点 和 23:59:59(部分 checked/selected 格用后者)
|
||||
for time_variant in (target_time, target_time + 86399000):
|
||||
sel = f"td div.day[time='{time_variant}']"
|
||||
cells = frame_locator.locator(sel)
|
||||
count = cells.count()
|
||||
for i in range(count):
|
||||
if cells.nth(i).is_visible():
|
||||
return cells.nth(i)
|
||||
return None
|
||||
|
||||
|
||||
def _zto_flip_to_target_month(frame_locator, page, target_time, max_flips=12):
|
||||
"""中通日历(jQuery-Date-Range-Picker 双月视图)跨月导航:目标日期不在当前视窗时,
|
||||
循环点 .prev 把目标月翻进 month1 视窗。offset 恒指向过去,故只往前翻;步长 1 月/次。
|
||||
用 JS 派发点击(.evaluate("el=>el.click()"))避开 .date-range-length-tip 等 hover 遮挡。
|
||||
循环点 .prev 把目标月翻进视窗。用 _zto_find_visible_day 判可见(跳过隐藏 ghost cell)。
|
||||
返回 True 若目标格子最终可见。"""
|
||||
sel = f"td div.day[time='{target_time}']"
|
||||
for _ in range(max_flips):
|
||||
if frame_locator.locator(sel).first.is_visible():
|
||||
if _zto_find_visible_day(frame_locator, target_time) is not None:
|
||||
return True
|
||||
frame_locator.locator(".date-picker-wrapper .prev").first.evaluate(
|
||||
"el => el.click()"
|
||||
)
|
||||
page.wait_for_timeout(450)
|
||||
return frame_locator.locator(sel).first.is_visible()
|
||||
return _zto_find_visible_day(frame_locator, target_time) is not None
|
||||
|
||||
|
||||
def zto_smart_menu_click(page, menu_path):
|
||||
@@ -170,32 +200,29 @@ def zto_expected_download_impl(page, force=False, date=None):
|
||||
ewb_frame.locator("#beginDate").click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
today_cell = ewb_frame.locator("td div.day.real-today").first
|
||||
today_cell.wait_for(state="visible")
|
||||
# 直接从 Python datetime 计算目标时间戳,不再依赖 DOM real-today(双月视图
|
||||
# 下 real-today 可能同时出现在 month1 隐藏 ghost cell 和 month2 可见 cell,
|
||||
# .first 会取到隐藏的那个导致 wait_for(visible) 超时)。
|
||||
target_time = _zto_compute_target_time(offset)
|
||||
target_cell = _zto_find_visible_day(ewb_frame, target_time)
|
||||
|
||||
today_time_str = today_cell.get_attribute("time")
|
||||
if today_time_str:
|
||||
today_time = int(today_time_str)
|
||||
target_time = today_time - offset * 86400000
|
||||
target_cell = ewb_frame.locator(f"td div.day[time='{target_time}']").first
|
||||
if target_cell is None:
|
||||
print(" ℹ️ 目标日期不在当前视窗,正在翻月导航 ...")
|
||||
if not _zto_flip_to_target_month(ewb_frame, page, target_time):
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
target_cell = _zto_find_visible_day(ewb_frame, target_time)
|
||||
if target_cell is None:
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
|
||||
# 偏移日期跨月时目标格子不在当前双月视窗 → 向前翻月把它带进视窗,不再降级为当天
|
||||
if not target_cell.is_visible():
|
||||
print(" ℹ️ 偏移日期跨月,正在向前翻月导航到目标 ...")
|
||||
if not _zto_flip_to_target_month(ewb_frame, page, target_time):
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
# 日期格子用 _dom_click 直接派发事件(同实到):.click() 会先 hover 格子,触发
|
||||
# “范围长度”提示气泡(.date-range-length-tip)盖住格子导致点击被遮挡超时,
|
||||
# 跨月选中非今日格子时尤为明显。
|
||||
_dom_click(target_cell)
|
||||
page.wait_for_timeout(300)
|
||||
_dom_click(target_cell)
|
||||
else:
|
||||
today_cell.click()
|
||||
page.wait_for_timeout(300)
|
||||
today_cell.click()
|
||||
# 日期格子用 _dom_click 直接派发事件:.click() 会先 hover 格子,触发
|
||||
# "范围长度"提示气泡(.date-range-length-tip)盖住格子导致点击被遮挡超时。
|
||||
_dom_click(target_cell)
|
||||
page.wait_for_timeout(300)
|
||||
_dom_click(target_cell)
|
||||
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
@@ -422,31 +449,29 @@ def zto_actual_download_impl(page, date=None):
|
||||
arr_frame.locator("#daterange").click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
today_cell = arr_frame.locator("td div.day.real-today").first
|
||||
today_cell.wait_for(state="visible")
|
||||
# 直接从 Python datetime 计算目标时间戳,不再依赖 DOM real-today(双月视图
|
||||
# 下 real-today 可能同时出现在 month1 隐藏 ghost cell 和 month2 可见 cell,
|
||||
# .first 会取到隐藏的那个导致 wait_for(visible) 超时)。
|
||||
target_time = _zto_compute_target_time(offset)
|
||||
target_cell = _zto_find_visible_day(arr_frame, target_time)
|
||||
|
||||
today_time_str = today_cell.get_attribute("time")
|
||||
if today_time_str:
|
||||
today_time = int(today_time_str)
|
||||
target_time = today_time - offset * 86400000
|
||||
target_cell = arr_frame.locator(f"td div.day[time='{target_time}']").first
|
||||
if target_cell is None:
|
||||
print(" ℹ️ 目标日期不在当前视窗,正在翻月导航 ...")
|
||||
if not _zto_flip_to_target_month(arr_frame, page, target_time):
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
target_cell = _zto_find_visible_day(arr_frame, target_time)
|
||||
if target_cell is None:
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
|
||||
# 日期格子用 _dom_click 直接派发事件:Playwright 的 .click() 会先 hover 格子,
|
||||
# 触发“范围长度”提示气泡(.date-range-length-tip)盖住格子,导致点击被判遮挡而超时。
|
||||
# 偏移日期跨月时目标格子不在当前双月视窗 → 向前翻月把它带进视窗,不再降级为当天。
|
||||
if not target_cell.is_visible():
|
||||
print(" ℹ️ 偏移日期跨月,正在向前翻月导航到目标 ...")
|
||||
if not _zto_flip_to_target_month(arr_frame, page, target_time):
|
||||
raise RuntimeError(
|
||||
f"翻月后仍无法定位目标日期格子(time={target_time})"
|
||||
)
|
||||
_dom_click(target_cell)
|
||||
page.wait_for_timeout(300)
|
||||
_dom_click(target_cell)
|
||||
else:
|
||||
_dom_click(today_cell)
|
||||
page.wait_for_timeout(300)
|
||||
_dom_click(today_cell)
|
||||
# 日期格子用 _dom_click 直接派发事件:Playwright 的 .click() 会先 hover 格子,
|
||||
# 触发"范围长度"提示气泡(.date-range-length-tip)盖住格子,导致点击被判遮挡而超时。
|
||||
_dom_click(target_cell)
|
||||
page.wait_for_timeout(300)
|
||||
_dom_click(target_cell)
|
||||
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
@@ -695,7 +720,7 @@ def _zto_poll_and_download_tasks(page, export_times, download_dir, final_filenam
|
||||
)
|
||||
|
||||
# ====================================================================
|
||||
# 所有目标文件下载完成后,关闭“导出任务管理”标签页
|
||||
# 所有目标文件下载完成后,关闭"导出任务管理"标签页
|
||||
# ====================================================================
|
||||
print(">> 【导出任务管理】下载完成,正在关闭标签页...")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user