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)。
242 lines
11 KiB
Python
242 lines
11 KiB
Python
"""司机 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()
|