5D3420315FA54219AB2427B0C674AD66auto

This commit is contained in:
34BFF76918A9449ABAB323149DDCEA73 2026-08-13 12:38:46 +08:00
parent 7c9217d197
commit fa53a0524f
25 changed files with 63 additions and 44 deletions

View File

@ -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": "",

View File

@ -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

View File

@ -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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -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)": "账期支付",

View File

@ -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")