feat(cross_end): add driver-to-settlement E2E test with SQL retry
- Add DriverFreightFlowPage (Android): 28-step flow covering accept/load/unload - Add FreightAccountingPage (Web): accounting/audit/finance-payment/shipper-payment - Add db_helper: update_risk_control_status via pymysql - Fix ensure_logged_in: coordinate tap on 'mine' tab + login check - Add test_driver_to_settlement.py: full cross-end E2E with SQL retry Co-authored-by: xst <xst@ntocc.com>
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
"""司机 APP 货运流程 Page Object — 货源→接单→装货→卸货→获取单号。
|
||||
|
||||
基于 resources/selectors/正常流程-司机.md 中的元素定位。
|
||||
使用 Appium UiAutomator2 定位策略。
|
||||
"""
|
||||
|
||||
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.app._base_page import AppBasePage
|
||||
|
||||
|
||||
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.cancel_btn = (AppiumBy.ID, "android:id/button2")
|
||||
|
||||
# ── 吨位 ──
|
||||
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 _safe_tap_by_text(self, text: str, timeout_s: float = 5.0):
|
||||
"""容错点击:元素不存在时静默跳过。"""
|
||||
try:
|
||||
self._tap_by_text(text, timeout_s)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 接单流程 (步骤 1-5)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
def accept_order_flow(self, quantity: str = "1"):
|
||||
"""接单完整流程:货源 tab → 货源单 → 数量 → 协议 → 接单。"""
|
||||
self._wait_and_click(self.freight_source_tab) # ① 点击货源
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.freight_order_item) # ② 点击货源单
|
||||
self.driver.implicitly_wait(2)
|
||||
self._wait_and_fill(self.quantity_input, quantity) # ③ 填写数量
|
||||
self._wait_and_click(self.agreement_checkbox) # ④ 勾选协议
|
||||
self._wait_and_click(self.accept_order_btn) # ⑤ 接单
|
||||
self.driver.implicitly_wait(2)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 装货流程 (步骤 6-17)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
def load_goods_flow(self, weight: str = "10"):
|
||||
"""装货完整流程。"""
|
||||
self._tap_by_text("装货") # ⑥ 点击装货
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.cancel_btn) # ⑦ 点击取消(系统弹窗)
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_fill(self.weight_input, weight) # ⑧ 输入吨位
|
||||
self._wait_and_click(self.select_image_btn) # ⑨ 选择图片
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.album_btn) # ⑩ 相册
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.image_checkbox) # ⑪ 勾选图片
|
||||
self._wait_and_click(self.done_btn) # ⑫ 完成
|
||||
self.driver.implicitly_wait(2)
|
||||
self._wait_and_click(self.agreement_checkbox) # ⑬ 勾选协议
|
||||
self._wait_and_click(self.load_unload_confirm) # ⑭ 装货确认
|
||||
self.driver.implicitly_wait(1)
|
||||
self._swipe_up(self.contract_webview) # ⑮ 滑动合同
|
||||
self.driver.implicitly_wait(1)
|
||||
self._safe_tap_by_text("已知晓", timeout_s=5.0) # ⑯ 已知晓(容错)
|
||||
self._safe_tap_by_text("确定", timeout_s=5.0) # ⑰ 确定(容错)
|
||||
self.driver.implicitly_wait(2)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 卸货流程 (步骤 19-27)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
def unload_goods_flow(self, weight: str = "10"):
|
||||
"""卸货完整流程。"""
|
||||
self._tap_by_text("卸货") # ⑲ 点击卸货
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.cancel_btn) # ⑳ 点击取消(系统弹窗)
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_fill(self.weight_input, weight) # ㉑ 输入吨位
|
||||
self._wait_and_click(self.select_image_btn) # ㉒ 选择图片
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.album_btn) # ㉓ 相册
|
||||
self.driver.implicitly_wait(1)
|
||||
self._wait_and_click(self.image_checkbox) # ㉔ 勾选图片
|
||||
self._wait_and_click(self.done_btn) # ㉕ 完成
|
||||
self.driver.implicitly_wait(2)
|
||||
self._wait_and_click(self.load_unload_confirm) # ㉖ 卸货确认
|
||||
self.driver.implicitly_wait(1)
|
||||
self._safe_tap_by_text("确定", timeout_s=5.0) # ㉗ 确定(容错)
|
||||
self.driver.implicitly_wait(2)
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 获取单号 (步骤 28)
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
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 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()
|
||||
Reference in New Issue
Block a user