Files
Yb-QaAutomationHub/tests/pages/driver/freight_flow_page.py
T
xst 2aca405267 refactor(pages): PO 层框架化 — 细粒度操作 + 显式等待 + 消除静默吞异常
DriverFreightFlowPage (APP):
- 流程方法拆为单一职责的细粒度操作(open_freight_tab/fill_quantity/upload_photo_from_album/...),
  流程方法仅做编排,公共 API 不变,用例零改动
- 移除全部 implicitly_wait 全局副作用,每步显式等待目标元素
- 容错点击(已知晓/确定)改为 _tap_by_text_if_present,跳过时记录 warning

FreightAccountingPage (Web):
- _go/_click_search 改显式等待(搜索框出现/列表数据行或空态),替代固定 2s/1s sleep
- 表格等待升级为 td:visible,避开 vxe-table 固定列隐藏占位单元格
- 点击行操作按钮后显式等待弹窗出现,替代固定 800ms
- 车队长下拉选择改显式等待下拉项渲染
- 可选弹窗操作统一收敛到 _try_click,跳过时记录 warning;关键确认按钮保持严格

两条 E2E 用例在真实环境回归通过(TC-D2S-001 116s / TC-D2S-002 112s)。
2026-09-02 11:59:46 +08:00

242 lines
11 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.
"""司机 APP 货运流程 Page Object — 细粒度操作 + 流程编排。
结构:
__init__ 定位器集中声明(与 resources/selectors/正常流程-司机.md 对应)
基础操作 显式等待封装(WebDriverWait),无常驻固定 sleep
细粒度操作 单一职责的页面操作(open_freight_tab / fill_quantity / ...)
流程编排 accept_order_flow / load_goods_flow / ... 供用例调用,仅做步骤组合
"""
import logging
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from tests.pages.base.app_base_page import AppBasePage
logger = logging.getLogger(__name__)
class DriverFreightFlowPage(AppBasePage):
"""司机端 APP - 货运完整流程(货源→接单→装货→卸货→获取单号)。"""
def __init__(self, driver, platform: str = "android"):
super().__init__(driver, platform)
# ── 底部导航 ──
# 0=首页, 1=货源, 2=消息, 3=我的
self.freight_source_tab = (
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().resourceId("com.arpa.ynchenggangdriver:id/fixed_bottom_navigation_icon").instance(1)',
)
# ── 货源单列表 ──
self.freight_order_item = (
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().className("android.widget.RelativeLayout").instance(3)',
)
# ── 数量 ──
self.quantity_input = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/et_num")
# ── 协议勾选 ──
self.agreement_checkbox = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/check_xieyi")
# ── 接单 ──
self.accept_order_btn = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/tv_bian")
# ── 吨位 ──
self.weight_input = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/et_dun")
# ── 上传图片 ──
self.select_image_btn = (
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().className("android.widget.ImageView").instance(2)',
)
self.album_btn = (AppiumBy.ANDROID_UIAUTOMATOR, 'new UiSelector().text("相册")')
self.image_checkbox = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/cb_check")
self.done_btn = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/btn_ok")
# ── 装货/卸货确认 ──
self.load_unload_confirm = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/tv_nick")
# ── 合同 ──
self.contract_webview = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/textHtml")
self.acknowledge_btn = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/tv_affirm")
self.confirm_btn = (AppiumBy.ID, "com.arpa.ynchenggangdriver:id/ok_tv3")
# ════════════════════════════════════════════════════════════
# 基础操作(显式等待)
# ════════════════════════════════════════════════════════════
def _wait_and_click(self, locator, timeout_s: float = 10.0):
wait = WebDriverWait(self.driver, timeout=timeout_s)
wait.until(EC.visibility_of_element_located(locator))
self.driver.find_element(*locator).click()
def _wait_and_fill(self, locator, value: str, timeout_s: float = 10.0):
wait = WebDriverWait(self.driver, timeout=timeout_s)
wait.until(EC.visibility_of_element_located(locator))
el = self.driver.find_element(*locator)
el.clear()
el.send_keys(value)
def _tap_by_text(self, text: str, timeout_s: float = 10.0):
locator = (AppiumBy.ANDROID_UIAUTOMATOR, f'new UiSelector().text("{text}")')
self._wait_and_click(locator, timeout_s)
def _tap_by_text_if_present(self, text: str, timeout_s: float = 5.0):
"""可选元素点击:不出现时记录日志并跳过(不静默吞掉)。"""
try:
self._tap_by_text(text, timeout_s)
except Exception:
logger.warning(f"可选元素 '{text}' 在 {timeout_s}s 内未出现,跳过点击")
def _swipe_up(self, locator=None, duration_ms: int = 800):
"""向上滑动。指定 locator 时在元素内滑动,否则全屏滑动。"""
if locator:
try:
wait = WebDriverWait(self.driver, timeout=5.0)
el = wait.until(EC.presence_of_element_located(locator))
size = el.size
loc = el.location
start_x = loc["x"] + size["width"] // 2
start_y = loc["y"] + int(size["height"] * 0.8)
end_y = loc["y"] + int(size["height"] * 0.2)
self.driver.swipe(start_x, start_y, start_x, end_y, duration_ms)
return
except Exception:
pass
# 全屏滑动
size = self.driver.get_window_size()
self.driver.swipe(
start_x=size["width"] // 2,
start_y=int(size["height"] * 0.7),
end_x=size["width"] // 2,
end_y=int(size["height"] * 0.3),
duration=duration_ms,
)
# ════════════════════════════════════════════════════════════
# 细粒度操作: 接单
# ════════════════════════════════════════════════════════════
def open_freight_tab(self):
"""切换到底部导航的货源 tab。"""
self._wait_and_click(self.freight_source_tab)
def select_freight_item(self):
"""选择货源单列表中的一项。"""
self._wait_and_click(self.freight_order_item)
def fill_quantity(self, quantity: str):
"""填写接单数量。"""
self._wait_and_fill(self.quantity_input, quantity)
def check_agreement(self):
"""勾选协议。"""
self._wait_and_click(self.agreement_checkbox)
def tap_accept_order(self):
"""点击接单按钮。"""
self._wait_and_click(self.accept_order_btn)
# ════════════════════════════════════════════════════════════
# 细粒度操作: 装货/卸货
# ════════════════════════════════════════════════════════════
def open_load_dialog(self):
"""点击"装货"入口。"""
self._tap_by_text("装货")
def open_unload_dialog(self):
"""点击"卸货"入口。"""
self._tap_by_text("卸货")
def fill_weight(self, weight: str):
"""输入吨位。"""
self._wait_and_fill(self.weight_input, weight)
def upload_photo_from_album(self):
"""上传图片: 选择图片 → 相册 → 勾选图片 → 完成。"""
self._wait_and_click(self.select_image_btn)
self._wait_and_click(self.album_btn)
self._wait_and_click(self.image_checkbox)
self._wait_and_click(self.done_btn)
def confirm_loading(self):
"""装货确认: 勾选协议 → 点击装货确认。"""
self._wait_and_click(self.agreement_checkbox)
self._wait_and_click(self.load_unload_confirm)
def confirm_unloading(self):
"""卸货确认: 点击卸货确认。"""
self._wait_and_click(self.load_unload_confirm)
def sign_contract(self):
"""签署合同: 滑动合同 webview → 已知晓(可选) → 确定(可选)。"""
self._swipe_up(self.contract_webview)
self._tap_by_text_if_present("已知晓")
self._tap_by_text_if_present("确定")
def tap_confirm_if_present(self):
"""点击"确定"弹窗按钮(可选,部分流程不弹)。"""
self._tap_by_text_if_present("确定")
# ════════════════════════════════════════════════════════════
# 查询
# ════════════════════════════════════════════════════════════
def get_transport_order_no(self, timeout_s: float = 10.0) -> str:
"""获取运输单号。从"运输单号:RHJC..."中正则提取纯单号。"""
import re
locator = (
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().textContains("运输单号")',
)
wait = WebDriverWait(self.driver, timeout=timeout_s)
el = wait.until(EC.visibility_of_element_located(locator))
full_text = el.text
match = re.search(r"RHJC\S+", full_text)
if match:
return match.group()
return full_text.replace("运输单号:", "").replace("运输单号:", "").strip()
# ════════════════════════════════════════════════════════════
# 流程编排(供用例调用)
# ════════════════════════════════════════════════════════════
def accept_order_flow(self, quantity: str = "1"):
"""接单完整流程:货源 tab → 货源单 → 数量 → 协议 → 接单。"""
self.open_freight_tab()
self.select_freight_item()
self.fill_quantity(quantity)
self.check_agreement()
self.tap_accept_order()
def load_goods_flow(self, weight: str = "10"):
"""装货完整流程:装货 → 吨位 → 拍照上传 → 确认 → 合同。"""
self.open_load_dialog()
self.fill_weight(weight)
self.upload_photo_from_album()
self.confirm_loading()
self.sign_contract()
def unload_goods_flow(self, weight: str = "10"):
"""卸货完整流程:卸货 → 吨位 → 拍照上传 → 确认 → 确定。"""
self.open_unload_dialog()
self.fill_weight(weight)
self.upload_photo_from_album()
self.confirm_unloading()
self.tap_confirm_if_present()
def full_driver_flow(self, quantity: str = "1", load_weight: str = "10",
unload_weight: str = "10") -> str:
"""执行完整司机端流程:接单→装货→卸货→返回运输单号。"""
self.accept_order_flow(quantity)
self.load_goods_flow(load_weight)
self.unload_goods_flow(unload_weight)
return self.get_transport_order_no()