5D3420315FA54219AB2427B0C674AD66auto
|
|
@ -5,7 +5,7 @@
|
||||||
"ProInfo": {
|
"ProInfo": {
|
||||||
"CodeCustomCount": "0",
|
"CodeCustomCount": "0",
|
||||||
"ProAbsolutePath": "D:/RPA/AutoRpa",
|
"ProAbsolutePath": "D:/RPA/AutoRpa",
|
||||||
"ProChange": "2026/08/13 11:38:45",
|
"ProChange": "2026/08/13 12:38:45",
|
||||||
"ProColorRef": "4",
|
"ProColorRef": "4",
|
||||||
"ProCreate": "2026/08/12 10:13:31",
|
"ProCreate": "2026/08/12 10:13:31",
|
||||||
"ProDesc": "",
|
"ProDesc": "",
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,25 @@ class ApaBrowserAdapter:
|
||||||
continue
|
continue
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def switch_to_frame(self, selectors: list) -> None:
|
||||||
|
"""切换到匹配到的 iframe(客商选择弹窗等)。"""
|
||||||
|
self._ensure_open()
|
||||||
|
last_error = None
|
||||||
|
for sel in selectors:
|
||||||
|
try:
|
||||||
|
el = self._find(sel, timeout=1)
|
||||||
|
self._driver.switch_to.frame(el)
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
last_error = e
|
||||||
|
continue
|
||||||
|
raise RuntimeError(f"switch_to_frame failed: {selectors}, last: {last_error}")
|
||||||
|
|
||||||
|
def switch_to_default_content(self) -> None:
|
||||||
|
"""切回主文档(顶层 frame)。"""
|
||||||
|
self._ensure_open()
|
||||||
|
self._driver.switch_to.default_content()
|
||||||
|
|
||||||
def select_first(self, selectors: list, value: str) -> None:
|
def select_first(self, selectors: list, value: str) -> None:
|
||||||
self._ensure_open()
|
self._ensure_open()
|
||||||
from selenium.webdriver.support.ui import Select
|
from selenium.webdriver.support.ui import Select
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class BrowserAdapter(Protocol):
|
||||||
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: ...
|
def wait_for_text(self, text: str, timeout_seconds: int = 15) -> None: ...
|
||||||
def wait_for_element(self, selectors: list[str], timeout_seconds: int = 15) -> None: ...
|
def wait_for_element(self, selectors: list[str], timeout_seconds: int = 15) -> None: ...
|
||||||
def element_exists(self, selectors: list[str]) -> bool: ...
|
def element_exists(self, selectors: list[str]) -> bool: ...
|
||||||
|
def switch_to_frame(self, selectors: list[str]) -> None: ...
|
||||||
|
def switch_to_default_content(self) -> None: ...
|
||||||
def select_first(self, selectors: list[str], value: str) -> None: ...
|
def select_first(self, selectors: list[str], value: str) -> None: ...
|
||||||
def upload_file_first(self, selectors: list[str], file_path: str) -> None: ...
|
def upload_file_first(self, selectors: list[str], file_path: str) -> None: ...
|
||||||
def read_first(self, selectors: list[str], value_name: str = "") -> str: ...
|
def read_first(self, selectors: list[str], value_name: str = "") -> str: ...
|
||||||
|
|
@ -154,32 +156,21 @@ ADD_ROW_SELECTORS = [
|
||||||
"button:contains(添加行)",
|
"button:contains(添加行)",
|
||||||
]
|
]
|
||||||
|
|
||||||
# 客商选择窗口(双击客户名称后弹出)选择器。若真实页面改版,仅需调整此处。
|
# 客商选择窗口(点击客户名称旁「选择」按钮弹出)。若真实页面改版,仅需调整此处。
|
||||||
CUSTOMER_PICKER = {
|
CUSTOMER_PICKER = {
|
||||||
"search_input": [
|
"trigger": [
|
||||||
'input[placeholder*="客商"]',
|
'div.inputselectsgl[onclick*="fdCustomerEdit"]',
|
||||||
'input[placeholder*="编码"]',
|
'div[class*="inputselect"][onclick*="fdCustomer"]',
|
||||||
'input[placeholder*="搜索"]',
|
|
||||||
'input[name*="search"]',
|
|
||||||
'input[class*="search"]',
|
|
||||||
],
|
],
|
||||||
"search_button": [
|
"frame": [
|
||||||
"text=搜索",
|
'iframe[src*="common-template"]',
|
||||||
"text=查询",
|
'iframe[src*="KmRelativeMain"]',
|
||||||
"button:contains(搜索)",
|
'iframe[src*="relative"]',
|
||||||
"a:contains(搜索)",
|
|
||||||
],
|
|
||||||
"result_rows": [
|
|
||||||
"//table//tr[contains(.,'{code}')]",
|
|
||||||
"//*[contains(@class,'list')]//*[contains(text(),'{code}')]",
|
|
||||||
"text={code}",
|
|
||||||
],
|
|
||||||
"confirm_button": [
|
|
||||||
"text=确定",
|
|
||||||
"text=确认",
|
|
||||||
"button:contains(确定)",
|
|
||||||
"button:contains(确认)",
|
|
||||||
],
|
],
|
||||||
|
"search_input": ["input.search-box-text"],
|
||||||
|
"search_button": ["input.search-box-btn"],
|
||||||
|
"result_checkbox": ['input[name="List_Selected"]'],
|
||||||
|
"confirm_button": ["div.btn.ok", "div.lui_toolbar_btn_def"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -216,16 +207,11 @@ def fill_contract_signatory_info(browser: BrowserAdapter, ctx: ContractFlowConte
|
||||||
if not ctx.customer_code:
|
if not ctx.customer_code:
|
||||||
raise SupplierContractDisabled("客商操作码(customerCode)为空,无法加载客商")
|
raise SupplierContractDisabled("客商操作码(customerCode)为空,无法加载客商")
|
||||||
|
|
||||||
# 双击客户名称字段打开客商选择窗口,按编码搜索并带回客商
|
# 点击客户名称字段旁的「选择」按钮打开客商选择窗口,按编码搜索并带回客商
|
||||||
browser.double_click_first([F["customer_name"]])
|
browser.click_first(CUSTOMER_PICKER["trigger"])
|
||||||
_pick_customer_from_dialog(browser, ctx.customer_code)
|
_pick_customer_from_dialog(browser, ctx.customer_code)
|
||||||
loaded_name = _wait_customer_name(browser, timeout=8)
|
loaded_name = _wait_customer_name(browser, timeout=8)
|
||||||
|
|
||||||
# 兜底:双击未弹出选择窗时,直接填客商操作码触发自动加载
|
|
||||||
if not loaded_name:
|
|
||||||
browser.fill_first([F["customer_code"]], ctx.customer_code)
|
|
||||||
loaded_name = _wait_customer_name(browser, timeout=8)
|
|
||||||
|
|
||||||
if not loaded_name:
|
if not loaded_name:
|
||||||
browser.screenshot("contract-customer-load-failed")
|
browser.screenshot("contract-customer-load-failed")
|
||||||
raise SupplierContractDisabled(
|
raise SupplierContractDisabled(
|
||||||
|
|
@ -255,13 +241,19 @@ def _wait_customer_name(browser: BrowserAdapter, timeout: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def _pick_customer_from_dialog(browser: BrowserAdapter, code: str) -> None:
|
def _pick_customer_from_dialog(browser: BrowserAdapter, code: str) -> None:
|
||||||
browser.wait_for_element(CUSTOMER_PICKER["search_input"], timeout_seconds=10)
|
# 客商选择窗口是一个 iframe 弹窗:切入后搜索编码 → 选中结果复选框 → 切回主文档。
|
||||||
browser.fill_first(CUSTOMER_PICKER["search_input"], code)
|
# 单选模式下点击复选框即触发回填并关闭弹窗;若未自动关闭再点确认按钮兜底。
|
||||||
browser.click_first(CUSTOMER_PICKER["search_button"])
|
browser.wait_for_element(CUSTOMER_PICKER["frame"], timeout_seconds=10)
|
||||||
time.sleep(1)
|
browser.switch_to_frame(CUSTOMER_PICKER["frame"])
|
||||||
rows = [sel.format(code=code) for sel in CUSTOMER_PICKER["result_rows"]]
|
try:
|
||||||
browser.click_first(rows)
|
browser.fill_first(CUSTOMER_PICKER["search_input"], code)
|
||||||
browser.click_first(CUSTOMER_PICKER["confirm_button"])
|
browser.click_first(CUSTOMER_PICKER["search_button"])
|
||||||
|
time.sleep(1)
|
||||||
|
browser.wait_for_element(CUSTOMER_PICKER["result_checkbox"], timeout_seconds=10)
|
||||||
|
browser.click_first(CUSTOMER_PICKER["result_checkbox"])
|
||||||
|
time.sleep(1)
|
||||||
|
finally:
|
||||||
|
browser.switch_to_default_content()
|
||||||
|
|
||||||
|
|
||||||
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
def fill_contract_basic_info(browser: BrowserAdapter, ctx: ContractFlowContext) -> None:
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -32,13 +32,13 @@ def build_ctx() -> ContractFlowContext:
|
||||||
contract_url=CONTRACT_URL,
|
contract_url=CONTRACT_URL,
|
||||||
username="test002",
|
username="test002",
|
||||||
password="1",
|
password="1",
|
||||||
supplier_name="测试供应商",
|
supplier_name="重庆亿昊药业有限公司",
|
||||||
order_date="2026-08-12",
|
order_date="2026-08-12",
|
||||||
source_type="YYC",
|
source_type="YYC",
|
||||||
template_type="NON_FORMAT",
|
template_type="NON_FORMAT",
|
||||||
template_name="非格式-采购协议",
|
template_name="非格式-采购协议",
|
||||||
customer_code="CUST-001",
|
customer_code="DW0100046361",
|
||||||
customer_name="测试供应商",
|
customer_name="重庆亿昊药业有限公司",
|
||||||
contract_category="采购协议",
|
contract_category="采购协议",
|
||||||
settlement_type="付款",
|
settlement_type="付款",
|
||||||
contract_background="自动化测试合同起草",
|
contract_background="自动化测试合同起草",
|
||||||
|
|
@ -149,10 +149,10 @@ def main():
|
||||||
|
|
||||||
# 校验关键字段
|
# 校验关键字段
|
||||||
expected = {
|
expected = {
|
||||||
"extendDataFormInfo.value(fd_customeropcode)": "CUST-001",
|
"extendDataFormInfo.value(fd_customeropcode)": "DW0100046361",
|
||||||
"extendDataFormInfo.value(fd_customername)": "测试供应商",
|
"extendDataFormInfo.value(fd_customername)": "重庆亿昊药业有限公司",
|
||||||
"extendDataFormInfo.value(fd_contract_cate_type)": "采购协议",
|
"extendDataFormInfo.value(fd_contract_cate_type)": "采购协议",
|
||||||
"fdAccount": "50000",
|
"fdAccount": "50000.00",
|
||||||
"fdSigndate": "2026-08-12",
|
"fdSigndate": "2026-08-12",
|
||||||
"fdEnddate": "2026-12-31",
|
"fdEnddate": "2026-12-31",
|
||||||
"extendDataFormInfo.value(fd_paytype)": "账期支付",
|
"extendDataFormInfo.value(fd_paytype)": "账期支付",
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,14 @@ class YiSaiQiBrowserAdapter:
|
||||||
"""Flowchart node: 判断元素是否存在."""
|
"""Flowchart node: 判断元素是否存在."""
|
||||||
raise NotImplementedError(f"Map to YiSaiQi element-exists activity: {selectors=}")
|
raise NotImplementedError(f"Map to YiSaiQi element-exists activity: {selectors=}")
|
||||||
|
|
||||||
|
def switch_to_frame(self, selectors: list[str]) -> None:
|
||||||
|
"""Flowchart node: 切换到 iframe(客商选择弹窗等)."""
|
||||||
|
raise NotImplementedError(f"Map to YiSaiQi switch-to-frame activity: {selectors=}")
|
||||||
|
|
||||||
|
def switch_to_default_content(self) -> None:
|
||||||
|
"""Flowchart node: 切回主文档."""
|
||||||
|
raise NotImplementedError("Map to YiSaiQi switch-to-default-content activity")
|
||||||
|
|
||||||
def read_body_text(self) -> str:
|
def read_body_text(self) -> str:
|
||||||
"""Flowchart node: 读取页面 body 文本."""
|
"""Flowchart node: 读取页面 body 文本."""
|
||||||
raise NotImplementedError("Map to YiSaiQi read-body-text activity")
|
raise NotImplementedError("Map to YiSaiQi read-body-text activity")
|
||||||
|
|
|
||||||