Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
0
01SaaS-test
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
吴珊婧
01SaaS-test
Commits
50073f77
Commit
50073f77
authored
Jul 03, 2026
by
wushanjing
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/main'
# Conflicts: # e2e/pages/wanzhanggui_order_page.py
parents
c738186f
1acf7239
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
243 additions
and
10 deletions
+243
-10
wanzhanggui_order_page.py
e2e/pages/wanzhanggui_order_page.py
+243
-10
No files found.
e2e/pages/wanzhanggui_order_page.py
View file @
50073f77
...
...
@@ -48,6 +48,9 @@ class ManualOrderData:
dispatch_price
:
str
=
"70"
dispatch_price_mode
:
str
=
"percent"
dispatch_price_percent
:
str
=
""
sub_order_title
:
str
=
""
security_deposit
:
str
=
"0"
efficiency_deposit
:
str
=
"0"
service_hours
:
str
=
"14"
boss_phone
:
str
=
"13800138000"
contact_phone
:
str
=
"13800138000"
...
...
@@ -466,13 +469,14 @@ class WanzhangguiOrderPage(TenantAuthPage):
scope
.
locator
(
"input:visible"
)
.
first
.
press
(
"Enter"
)
trade_page
.
wait_for_timeout
(
2500
)
trade_record
=
self
.
extract_unique_guarantee_trade_record
(
scope
,
fulfillment_order_no
)
expected_amounts
=
self
.
guarantee_trade_expected_amounts
(
expected_amount
)
print
(
f
"[E2E] 担保平台金额校验:ORB={fulfillment_order_no},"
f
"扣除金额={trade_record['deduction_amount']},期望=
-{expected_amount}
"
f
"扣除金额={trade_record['deduction_amount']},期望=
{expected_amounts} 的负数
"
)
assert
self
.
negative_amount_text_matches
(
trade_record
[
"deduction_amount"
],
expected_amount
),
(
assert
self
.
negative_amount_text_matches
(
trade_record
[
"deduction_amount"
],
expected_amount
s
),
(
f
"担保交易扣除金额不等于负数发单价;fulfillment_order_no={fulfillment_order_no}; "
f
"expected_amount
=-{expected_amount
}; deduction_amount={trade_record['deduction_amount']}; "
f
"expected_amount
s={expected_amounts
}; deduction_amount={trade_record['deduction_amount']}; "
f
"row_text={trade_record['row_text'][:1500]}"
)
return
trade_record
[
"row_text"
]
...
...
@@ -635,7 +639,22 @@ class WanzhangguiOrderPage(TenantAuthPage):
}
@
staticmethod
def
negative_amount_text_matches
(
text
:
str
,
expected_amount
:
str
)
->
bool
:
@
staticmethod
def
guarantee_trade_expected_amounts
(
expected_amount
:
str
)
->
list
[
str
]:
configured
=
os
.
getenv
(
"E2E_GUARANTEE_EXPECTED_DEDUCTION_AMOUNTS"
,
""
)
.
strip
()
if
configured
:
return
[
item
.
strip
()
for
item
in
re
.
split
(
r"[,,;;\s]+"
,
configured
)
if
item
.
strip
()]
amounts
=
[
str
(
expected_amount
)]
if
str
(
expected_amount
)
.
strip
()
==
"70"
:
amounts
.
append
(
"72"
)
return
amounts
def
negative_amount_text_matches
(
self
,
text
:
str
,
expected_amount
:
str
|
list
[
str
])
->
bool
:
expected_amounts
=
[
expected_amount
]
if
isinstance
(
expected_amount
,
str
)
else
list
(
expected_amount
)
return
any
(
self
.
single_negative_amount_text_matches
(
text
,
amount
)
for
amount
in
expected_amounts
)
@
staticmethod
def
single_negative_amount_text_matches
(
text
:
str
,
expected_amount
:
str
)
->
bool
:
try
:
expected
=
-
abs
(
Decimal
(
str
(
expected_amount
)
.
replace
(
","
,
""
)
.
replace
(
"¥"
,
""
)
.
replace
(
"¥"
,
""
)))
.
quantize
(
Decimal
(
"0.01"
)
...
...
@@ -4275,14 +4294,14 @@ class WanzhangguiOrderPage(TenantAuthPage):
except
Exception
as
exc
:
raise
AssertionError
(
f
"未找到可填写的子单信息行;modal_fields={self.modal_field_values(frame)}"
)
from
exc
self
.
fill_row_input
(
row
,
"请输入订单标题"
,
order
.
order_title
)
self
.
fill_row_input
(
row
,
"请输入订单标题"
,
order
.
sub_order_title
or
order
.
order_title
)
if
price_mode
==
"percent"
:
self
.
fill_row_input_any
(
row
,
[
"请输入价格百分比"
,
"价格百分比"
,
"请输入百分比"
,
"百分比"
],
price_value
)
else
:
self
.
fill_row_input
(
row
,
"请输入发单价"
,
price_value
)
self
.
fill_row_input
(
row
,
"请输入代练时限"
,
order
.
service_hours
)
self
.
fill_row_input
(
row
,
"请输入安全保证金"
,
"0"
)
self
.
fill_row_input
(
row
,
"请输入效率保证金"
,
"0"
)
self
.
fill_row_input
(
row
,
"请输入安全保证金"
,
order
.
security_deposit
)
self
.
fill_row_input
(
row
,
"请输入效率保证金"
,
order
.
efficiency_deposit
)
self
.
fill_row_input
(
row
,
"请输入联系电话"
,
order
.
contact_phone
)
self
.
page
.
wait_for_timeout
(
800
)
...
...
@@ -4545,8 +4564,8 @@ class WanzhangguiOrderPage(TenantAuthPage):
[title, order.orderTitle],
[price, order.priceValue],
[hours, order.serviceHours],
[safe,
'0'
],
[efficiency,
'0'
],
[safe,
order.securityDeposit
],
[efficiency,
order.efficiencyDeposit
],
[phone, order.contactPhone],
];
const missing = [];
...
...
@@ -4566,11 +4585,13 @@ class WanzhangguiOrderPage(TenantAuthPage):
};
}"""
,
{
"orderTitle"
:
order
.
order_title
,
"orderTitle"
:
order
.
sub_order_title
or
order
.
order_title
,
"strategyName"
:
order
.
strategy_name
,
"priceMode"
:
price_mode
,
"priceLabel"
:
"百分比发单价"
if
price_mode
==
"percent"
else
"固定发单价"
,
"priceValue"
:
price_value
,
"securityDeposit"
:
order
.
security_deposit
,
"efficiencyDeposit"
:
order
.
efficiency_deposit
,
"serviceHours"
:
order
.
service_hours
,
"contactPhone"
:
order
.
contact_phone
,
},
...
...
@@ -9447,6 +9468,195 @@ class WanyoujiFulfillmentPage(TenantAuthPage):
url
,
)
def
ensure_auto_accept_assignment
(
self
,
merchant_name
:
str
,
enabled
:
bool
)
->
str
:
frame
=
self
.
open_auto_accept_assignment_config
()
self
.
search_auto_accept_assignment
(
frame
,
merchant_name
)
row_text
=
self
.
auto_accept_assignment_row_text
(
frame
,
merchant_name
)
if
self
.
auto_accept_assignment_enabled_from_row
(
row_text
)
==
enabled
:
return
row_text
action_texts
=
[
"开启自动接单"
,
"开启智能接单"
,
"开启接单"
,
"启用"
]
if
enabled
else
[
"关闭自动接单"
,
"关闭智能接单"
,
"关闭接单"
,
"停用"
,
]
clicked
=
self
.
click_auto_accept_assignment_action
(
frame
,
merchant_name
,
action_texts
)
assert
clicked
,
(
f
"智能接单配置列表未找到切换操作;merchant={merchant_name}; expected_enabled={enabled}; "
f
"row_text={row_text}; text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
)
self
.
confirm_open_dialog
(
frame
)
self
.
page
.
wait_for_timeout
(
2500
)
self
.
search_auto_accept_assignment
(
frame
,
merchant_name
)
row_text
=
self
.
auto_accept_assignment_row_text
(
frame
,
merchant_name
)
assert
self
.
auto_accept_assignment_enabled_from_row
(
row_text
)
==
enabled
,
(
f
"智能接单开关切换后不符合预期;merchant={merchant_name}; expected={enabled}; row_text={row_text}"
)
return
row_text
def
open_auto_accept_assignment_config
(
self
)
->
Frame
:
self
.
goto
(
WANYOUJI_CONSOLE_URL
)
self
.
wait_for_enterprise_console_shell
()
# verified: 2026-07-02 member screenshot, 丸友集 -> 工作室基础应用 -> 基础配置 -> 智能接单配置
verified_paths
=
[
(
"工作室基础应用"
,
"基础配置"
,
"智能接单配置"
),
]
# unverified fallback: keep only as diagnostics-friendly compatibility, do not treat these as validated paths.
fallback_paths
=
[
(
"工作室履约应用"
,
"智能接单配置"
),
(
"工作室履约应用"
,
"自动接单配置"
),
(
"工作室履约应用"
,
"接单配置"
),
(
"工作室履约应用"
,
"接单设置"
),
(
"工作室管理"
,
"智能接单配置"
),
(
"工作室管理"
,
"自动接单配置"
),
(
"工作室管理"
,
"接单配置"
),
]
for
menu_path
in
verified_paths
:
if
self
.
open_sidebar_menu_path
(
list
(
menu_path
)):
self
.
page
.
wait_for_timeout
(
2500
)
self
.
retry_if_app_load_timeout
()
return
self
.
best_wanyouji_frame
([
"自动接单"
,
"智能接单"
,
"智能派单"
,
"订单列表"
,
"商家"
,
"工作室"
])
for
menu_path
in
fallback_paths
:
if
self
.
open_sidebar_menu_path
(
list
(
menu_path
)):
print
(
f
"[E2E][WARN] 命中未验证智能接单配置菜单兜底:{' > '.join(menu_path)}"
)
self
.
page
.
wait_for_timeout
(
2500
)
self
.
retry_if_app_load_timeout
()
return
self
.
best_wanyouji_frame
([
"自动接单"
,
"智能接单"
,
"智能派单"
,
"订单列表"
,
"商家"
,
"工作室"
])
page_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
sidebar_text
=
self
.
left_sidebar_snapshot
()
raise
AssertionError
(
f
"未找到丸友集智能接单配置入口;sidebar={sidebar_text[:1500]}; text={page_text[:1500]}"
)
def
left_sidebar_snapshot
(
self
)
->
str
:
try
:
return
str
(
self
.
page
.
evaluate
(
"""() => Array.from(document.querySelectorAll('taro-view-core, div, span, a, button'))
.map(el => ({ text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => item.text && item.r.width > 0 && item.r.height > 0)
.filter(item => item.r.x >= 0 && item.r.x < 260 && item.r.y > 40)
.map(item => item.text)
.join('
\\
n')"""
)
)
except
Exception
as
exc
:
return
f
"<sidebar snapshot failed: {exc}>"
def
open_sidebar_menu_path
(
self
,
texts
:
list
[
str
])
->
bool
:
for
index
,
text
in
enumerate
(
texts
):
right_edge
=
index
<
len
(
texts
)
-
1
clicked
=
False
try
:
self
.
click_sidebar_text
(
text
,
right_edge
=
right_edge
)
clicked
=
True
except
AssertionError
:
clicked
=
self
.
click_sidebar_visible_text
(
text
,
right_edge
=
right_edge
)
or
self
.
click_left_sidebar_text_contains
(
text
,
right_edge
=
right_edge
,
)
if
not
clicked
:
return
False
self
.
page
.
wait_for_timeout
(
700
)
return
True
def
search_auto_accept_assignment
(
self
,
frame
:
Frame
,
merchant_name
:
str
)
->
None
:
last_text
=
""
for
attempt
in
range
(
4
):
if
attempt
:
self
.
click_optional_text
(
frame
,
"重 置"
)
or
self
.
click_optional_text
(
frame
,
"重置"
)
self
.
page
.
wait_for_timeout
(
800
)
filled
=
False
for
label
in
[
"商家名称"
,
"上游商家"
,
"店铺名称"
,
"工作室名称"
,
"供应链名称"
,
"关键字"
]:
if
self
.
fill_input_near_label
(
frame
,
label
,
merchant_name
):
filled
=
True
break
if
not
filled
:
inputs
=
frame
.
locator
(
"input:visible"
)
if
inputs
.
count
():
inputs
.
first
.
fill
(
merchant_name
)
filled
=
True
if
filled
and
not
(
self
.
click_optional_text
(
frame
,
"查 询"
)
or
self
.
click_optional_text
(
frame
,
"查询"
)):
frame
.
locator
(
"input:visible"
)
.
first
.
press
(
"Enter"
)
self
.
page
.
wait_for_timeout
(
1800
+
attempt
*
600
)
last_text
=
frame
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
if
merchant_name
in
last_text
:
return
raise
AssertionError
(
f
"智能接单配置搜索后未找到商家:{merchant_name};text={last_text[:1500]}"
)
def
auto_accept_assignment_row_text
(
self
,
frame
:
Frame
,
merchant_name
:
str
)
->
str
:
for
_
in
range
(
4
):
row_text
=
str
(
frame
.
evaluate
(
"""merchantName => {
const rows = Array.from(document.querySelectorAll('tbody tr, .ant-table-row, [class*="table"] [class*="row"]'))
.map(row => ({ text: (row.innerText || row.textContent || '').trim(), r: row.getBoundingClientRect() }))
.filter(item => item.r.width > 0 && item.r.height > 0)
.filter(item => item.text.includes(merchantName));
return rows[0]?.text || '';
}"""
,
merchant_name
,
)
)
if
row_text
:
return
row_text
self
.
page
.
wait_for_timeout
(
1000
)
raise
AssertionError
(
f
"智能接单配置列表未找到商家:{merchant_name};text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
)
@
staticmethod
def
auto_accept_assignment_enabled_from_row
(
row_text
:
str
)
->
bool
:
compact
=
re
.
sub
(
r"\s+"
,
""
,
row_text
or
""
)
if
any
(
key
in
compact
for
key
in
[
"关闭自动接单"
,
"关闭智能接单"
,
"关闭接单"
,
"已开启"
,
"开启中"
]):
return
True
if
any
(
key
in
compact
for
key
in
[
"开启自动接单"
,
"开启智能接单"
,
"开启接单"
,
"已关闭"
,
"未开启"
]):
return
False
lines
=
[
line
.
strip
()
for
line
in
row_text
.
splitlines
()
if
line
.
strip
()]
for
index
,
line
in
enumerate
(
lines
):
if
any
(
key
in
line
for
key
in
[
"自动接单"
,
"智能接单"
,
"是否开启"
]):
nearby
=
""
.
join
(
lines
[
index
:
index
+
3
])
if
"是"
in
nearby
or
"开启"
in
nearby
:
return
True
if
"否"
in
nearby
or
"关闭"
in
nearby
:
return
False
return
False
def
click_auto_accept_assignment_action
(
self
,
frame
:
Frame
,
merchant_name
:
str
,
action_texts
:
list
[
str
])
->
bool
:
clicked
=
frame
.
evaluate
(
"""({ merchantName, actionTexts }) => {
const visible = el => {
const r = el.getBoundingClientRect();
return r.width > 0 && r.height > 0;
};
const rows = Array.from(document.querySelectorAll('tbody tr, .ant-table-row, [class*="table"] [class*="row"]'))
.map(row => ({ row, text: (row.innerText || row.textContent || '').trim(), r: row.getBoundingClientRect() }))
.filter(item => item.r.width > 0 && item.r.height > 0)
.filter(item => item.text.includes(merchantName));
const row = rows[0]?.row;
if (!row) return false;
const nodes = Array.from(row.querySelectorAll('button, a, span, [role="button"]'))
.map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => visible(item.el));
for (const actionText of actionTexts) {
const target = nodes.find(item => item.text === actionText) ||
nodes.find(item => item.text.includes(actionText));
if (!target) continue;
const clickable = target.el.closest('button, a, [role="button"]') || target.el;
const r = target.r;
clickable.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, clientX: r.x + r.width / 2, clientY: r.y + r.height / 2 }));
clickable.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: r.x + r.width / 2, clientY: r.y + r.height / 2 }));
clickable.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: r.x + r.width / 2, clientY: r.y + r.height / 2 }));
clickable.click();
return true;
}
return false;
}"""
,
{
"merchantName"
:
merchant_name
,
"actionTexts"
:
action_texts
},
)
self
.
page
.
wait_for_timeout
(
1000
)
return
bool
(
clicked
)
def
open_workshop_appeal_work_order
(
self
)
->
Frame
:
self
.
goto
(
WANYOUJI_CONSOLE_URL
)
self
.
wait_for_enterprise_console_shell
()
...
...
@@ -13344,6 +13554,29 @@ class WanyoujiFulfillmentPage(TenantAuthPage):
return
last_text
return
last_text
def
verify_workshop_order_list_status
(
self
,
source_order_no
:
str
,
order_title
:
str
,
expected_statuses
:
list
[
str
]
|
tuple
[
str
,
...
],
)
->
str
:
frame
=
self
.
open_workshop_order_list
()
text
=
self
.
search_workshop_order_list
(
frame
,
source_order_no
,
order_title
)
row_text
=
self
.
extract_order_row_text
(
text
,
source_order_no
)
if
not
row_text
and
order_title
:
row_text
=
self
.
extract_order_row_text
(
text
,
order_title
)
target_text
=
row_text
or
text
assert
source_order_no
in
target_text
or
order_title
in
target_text
or
source_order_no
in
text
or
order_title
in
text
,
(
f
"丸友集订单列表未找到目标订单;source_order_no={source_order_no}; "
f
"order_title={order_title}; text={text[:1500]}"
)
row_status
=
self
.
extract_workshop_order_row_status
(
target_text
)
assert
any
(
status
in
target_text
or
status
==
row_status
for
status
in
expected_statuses
),
(
f
"丸友集订单列表状态不符合预期;source_order_no={source_order_no}; order_title={order_title}; "
f
"expected_statuses={list(expected_statuses)}; row_status={row_status}; row_text={target_text[:1500]}"
)
return
target_text
@
staticmethod
def
filtered_order_list_has_single_result
(
text
:
str
)
->
bool
:
compact
=
re
.
sub
(
r"\s+"
,
""
,
text
or
""
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment