"""司机 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()