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
0c2c8d06
Commit
0c2c8d06
authored
Jul 07, 2026
by
wushanjing
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
补齐E2E环境和菜单路径依赖
parent
44dd226a
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1774 additions
and
51 deletions
+1774
-51
.env.example
.env.example
+21
-8
base_page.py
e2e/pages/base_page.py
+4
-0
tenant_auth_page.py
e2e/pages/tenant_auth_page.py
+300
-21
wanzhanggui_push_page.py
e2e/pages/wanzhanggui_push_page.py
+67
-17
menu_path_mapping.json
e2e/resources/menu_path_mapping.json
+1254
-0
account_store.py
e2e/utils/account_store.py
+38
-3
menu_path_mapping.py
e2e/utils/menu_path_mapping.py
+57
-0
e2e_automation_core.py
scripts/e2e_automation_core.py
+33
-2
No files found.
.env.example
View file @
0c2c8d06
TENANT_BASE_URL=https://test-lianlianyun.lianlianwz.com
TENANT_HOME_URL=https://test-lianlianyun.lianlianwz.com/home
ADMIN_URL=https://test-saas-adminui.lianlianwz.com/pages/home/index
ADMIN_USERNAME=wangss
ADMIN_PASSWORD=123456
# 选择运行环境:test / prod
# 推荐日常默认 test;线上运行时显式设置 E2E_ENV=prod。
E2E_ENV=test
# 本地私有覆盖文件仍然可以使用 .env。
# 加载顺序:shell 环境变量 > .env.{E2E_ENV} > .env > 代码默认值。
# 通用运行参数
E2E_HEADLESS=false
E2E_REGISTER_PASSWORD=test123456
E2E_PHONE_PREFIX=19
E2E_MAX_REGISTER_ATTEMPTS=5
E2E_ENTERPRISE_NAME_PREFIX=E2E测试企业
E2E_HEADLESS=false
E2E_IMAGE_CAPTCHA_MODE=ddddocr
E2E_VIEWPORT_WIDTH=1920
E2E_VIEWPORT_HEIGHT=1280
#
可选:manual / feishu / static
#
验证码:test 环境通常用 manual/feishu;prod 环境使用 disabled,禁止短信/飞书验证码流程。
E2E_VERIFICATION_CODE_MODE=manual
E2E_VERIFICATION_CODE_STATIC=
E2E_IMAGE_CAPTCHA_MODE=ddddocr
E2E_PRODUCTION_NO_REGISTRATION=false
# 价格配置
E2E_S_FLOW_DISPATCH_PRICE_MODE=percent
E2E_S_FLOW_DISPATCH_PRICE_PERCENT=90
E2E_TASK_DISPATCH_PRICE_MODE=percent
E2E_TASK_DISPATCH_PRICE_PERCENT=90
# 飞书配置仅放本地 .env,不建议提交真实密钥。
FEISHU_APP_ID=
FEISHU_APP_SECRET=
FEISHU_CHAT_ID=
FEISHU_TIMEOUT_SECONDS=60
FEISHU_POLL_INTERVAL_SECONDS=3
# 具体 URL/账号请看:
# - .env.test
# - .env.prod
e2e/pages/base_page.py
View file @
0c2c8d06
...
...
@@ -4,11 +4,15 @@ from collections.abc import Iterable
from
playwright.sync_api
import
Locator
,
Page
,
expect
from
e2e.utils.env_config
import
assert_url_allowed_for_env
class
BasePage
:
def
__init__
(
self
,
page
:
Page
):
self
.
page
=
page
def
goto
(
self
,
url
:
str
)
->
None
:
assert_url_allowed_for_env
(
url
)
self
.
page
.
goto
(
url
,
wait_until
=
"domcontentloaded"
)
def
click_first_visible
(
self
,
selectors
:
Iterable
[
str
],
timeout
:
int
=
3000
)
->
Locator
:
...
...
e2e/pages/tenant_auth_page.py
View file @
0c2c8d06
...
...
@@ -8,9 +8,10 @@ from playwright.sync_api import Frame, Page, expect
from
e2e.pages.base_page
import
BasePage
from
e2e.utils.bug_reporter
import
BugReporter
from
e2e.utils.dual_browser
import
assert_account_page
,
dual_browser_enabled
,
dual_browser_manager
from
e2e.utils.menu_path_mapping
import
is_production_url
,
menu_names_for_path
ENTERPRISE_CONSOLE_URL
=
"https://
test-merchant-lianlianyun.lianlianwz.com/productConsole?productCode=P20250421171352724060
"
ENTERPRISE_CONSOLE_URL
=
"https://
lianlianyun.shunfju.com/home
"
class
TenantAuthPage
(
BasePage
):
...
...
@@ -41,7 +42,7 @@ class TenantAuthPage(BasePage):
password_input
=
self
.
page
.
locator
(
"#password_login_password"
)
self
.
fill_and_verify
(
password_input
,
password
)
self
.
page
.
get_by_text
(
"登 录"
,
exact
=
True
)
.
click
()
self
.
click_password_login_submit
()
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
2000
)
self
.
select_main_account_if_present
(
phone
)
...
...
@@ -54,7 +55,7 @@ class TenantAuthPage(BasePage):
def
ensure_main_account_login_completed
(
self
,
phone
:
str
,
password
:
str
)
->
None
:
for
_
in
range
(
2
):
try
:
self
.
goto
(
ENTERPRISE_CONSOLE_URL
)
self
.
goto
_enterprise_console
(
)
self
.
wait_for_enterprise_console_shell
()
return
except
Exception
:
...
...
@@ -67,7 +68,7 @@ class TenantAuthPage(BasePage):
break
self
.
fill_and_verify
(
phone_input
,
phone
)
self
.
fill_and_verify
(
password_input
,
password
)
self
.
page
.
get_by_text
(
"登 录"
,
exact
=
True
)
.
click
()
self
.
click_password_login_submit
()
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
2000
)
self
.
select_main_account_if_present
(
phone
)
...
...
@@ -81,7 +82,7 @@ class TenantAuthPage(BasePage):
if
getattr
(
self
.
page
,
"_e2e_current_main_account"
,
""
)
!=
phone
:
return
False
try
:
self
.
goto
(
ENTERPRISE_CONSOLE_URL
)
self
.
goto
_enterprise_console
(
)
self
.
wait_for_enterprise_console_shell
()
return
True
except
Exception
:
...
...
@@ -124,6 +125,55 @@ class TenantAuthPage(BasePage):
except
Exception
:
return
False
def
click_password_login_submit
(
self
)
->
None
:
clicked
=
self
.
page
.
evaluate
(
"""() => {
const inputs = Array.from(document.querySelectorAll('input, textarea'))
.map(el => ({ el, r: el.getBoundingClientRect(), id: el.id || '', placeholder: el.getAttribute('placeholder') || '' }))
.filter(item => item.r.width > 0 && item.r.height > 0)
.filter(item => item.id.includes('password_login') || item.placeholder.includes('账号') || item.placeholder.includes('密码') || item.placeholder.includes('手机'));
const formRect = inputs.length
? inputs.reduce((acc, item) => ({
left: Math.min(acc.left, item.r.left),
right: Math.max(acc.right, item.r.right),
top: Math.min(acc.top, item.r.top),
bottom: Math.max(acc.bottom, item.r.bottom),
}), { left: inputs[0].r.left, right: inputs[0].r.right, top: inputs[0].r.top, bottom: inputs[0].r.bottom })
: null;
const buttons = Array.from(document.querySelectorAll('button, taro-button-core, [role="button"]'))
.map(el => ({
el,
text: (el.innerText || el.textContent || '').trim(),
disabled: Boolean(el.disabled) || el.getAttribute('aria-disabled') === 'true' || String(el.className || '').includes('disabled'),
r: el.getBoundingClientRect(),
}))
.filter(item => item.r.width > 0 && item.r.height > 0 && !item.disabled)
.filter(item => item.text.replace(/
\\
s/g, '') === '登录');
const target = buttons
.filter(item => !formRect || (
item.r.left >= formRect.left - 120 &&
item.r.right <= formRect.right + 160 &&
item.r.top >= formRect.top &&
item.r.top <= formRect.bottom + 220
))
.sort((a, b) => b.r.width * b.r.height - a.r.width * a.r.height)[0] || buttons[0];
if (!target) return false;
const clickable = target.el.click ? target.el : (target.el.querySelector?.('button, taro-button-core, [role="button"]') || target.el);
const r = target.r;
if (clickable.click) {
clickable.click();
} else {
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.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: r.x + r.width / 2, clientY: r.y + r.height / 2 }));
}
return true;
}"""
)
if
not
clicked
:
self
.
page
.
locator
(
"#password_login_password"
)
.
press
(
"Enter"
)
def
click_account_login_tab_if_present
(
self
)
->
None
:
self
.
page
.
evaluate
(
"""() => {
...
...
@@ -148,13 +198,73 @@ class TenantAuthPage(BasePage):
def
clear_login_state
(
self
)
->
None
:
self
.
forget_current_login
()
self
.
page
.
context
.
clear_cookies
()
for
url
in
[
self
.
tenant_home_url
,
ENTERPRISE_CONSOLE_URL
]:
for
url
in
[
self
.
tenant_home_url
,
self
.
enterprise_console_url
()
]:
try
:
self
.
goto
(
url
)
self
.
page
.
evaluate
(
"() => { localStorage.clear(); sessionStorage.clear(); }"
)
except
Exception
:
continue
def
enterprise_console_url
(
self
)
->
str
:
return
os
.
getenv
(
"E2E_ENTERPRISE_CONSOLE_URL"
,
""
)
.
strip
()
or
self
.
tenant_home_url
or
ENTERPRISE_CONSOLE_URL
def
goto_enterprise_console
(
self
)
->
None
:
self
.
goto
(
self
.
enterprise_console_url
())
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
1200
)
self
.
click_enter_backend_if_present
()
def
click_enter_backend_if_present
(
self
)
->
bool
:
click_script
=
"""() => {
const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button, a'));
const target = nodes
.map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => item.r.width > 0 && item.r.height > 0)
.filter(item => item.text === '进入后台' || item.text.includes('进入后台'))
.sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height))[0];
if (!target) return false;
target.el.click();
return true;
}"""
before_pages
=
list
(
self
.
page
.
context
.
pages
)
try
:
with
self
.
page
.
context
.
expect_page
(
timeout
=
3000
)
as
new_page_info
:
clicked
=
self
.
page
.
evaluate
(
click_script
)
if
clicked
:
previous_page
=
self
.
page
new_page
=
new_page_info
.
value
new_page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
=
new_page
self
.
inherit_login_marks
(
previous_page
,
new_page
)
except
Exception
:
clicked
=
self
.
page
.
evaluate
(
click_script
)
after_pages
=
list
(
self
.
page
.
context
.
pages
)
if
clicked
and
len
(
after_pages
)
>
len
(
before_pages
):
previous_page
=
self
.
page
self
.
page
=
after_pages
[
-
1
]
self
.
inherit_login_marks
(
previous_page
,
self
.
page
)
if
clicked
:
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
2000
)
return
bool
(
clicked
)
def
inherit_login_marks
(
self
,
previous_page
:
Page
,
new_page
:
Page
)
->
None
:
current_phone
=
str
(
getattr
(
previous_page
,
"_e2e_current_main_account"
,
""
))
for
attr
in
[
"_e2e_dual_browser_role"
,
"_e2e_dual_browser_channel"
,
"_e2e_current_main_account"
,
"_e2e_current_player_account"
,
]:
if
hasattr
(
previous_page
,
attr
):
setattr
(
new_page
,
attr
,
getattr
(
previous_page
,
attr
))
if
dual_browser_enabled
()
and
current_phone
:
dual_browser_manager
()
.
register_page_for_account
(
current_phone
,
new_page
)
elif
dual_browser_enabled
():
current_role
=
str
(
getattr
(
new_page
,
"_e2e_dual_browser_role"
,
""
))
if
current_role
:
dual_browser_manager
()
.
register_page_for_role
(
current_role
,
new_page
)
def
logout_current_account
(
self
)
->
None
:
self
.
forget_current_login
()
try
:
...
...
@@ -223,6 +333,95 @@ class TenantAuthPage(BasePage):
except
Exception
:
continue
def
open_account_switcher
(
self
)
->
bool
:
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
3000
)
if
"/account/select"
in
self
.
page
.
url
or
"选择账号"
in
body_text
or
"閫夋嫨璐﹀彿"
in
body_text
:
return
True
opened
=
self
.
page
.
evaluate
(
"""() => {
const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button'));
const visible = nodes
.map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => item.r.width > 0 && item.r.height > 0);
const direct = visible.find(item => item.text === '切换账号' || item.text.includes('切换账号'));
if (direct) {
direct.el.click();
return true;
}
const header = visible
.filter(item => item.r.y < 120 && item.r.x > window.innerWidth * 0.55)
.sort((a, b) => b.r.x - a.r.x)[0];
if (!header) return false;
header.el.click();
return true;
}"""
)
self
.
page
.
wait_for_timeout
(
800
)
if
not
opened
:
return
False
clicked
=
self
.
page
.
evaluate
(
"""() => {
const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button'));
const target = nodes
.map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => item.r.width > 0 && item.r.height > 0)
.find(item => item.text === '切换账号' || item.text.includes('切换账号'));
if (!target) return false;
target.el.click();
return true;
}"""
)
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
1200
)
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
3000
)
return
bool
(
clicked
)
or
"/account/select"
in
self
.
page
.
url
or
"选择账号"
in
body_text
or
"閫夋嫨璐﹀彿"
in
body_text
def
switch_account_context
(
self
,
*
,
account_type
:
str
=
"main"
,
role
:
str
=
""
,
enterprise_keyword
:
str
=
""
,
)
->
None
:
if
not
self
.
open_account_switcher
():
raise
AssertionError
(
"未找到切换账号入口"
)
self
.
select_account_card
(
account_type
=
account_type
,
role
=
role
,
enterprise_keyword
=
enterprise_keyword
)
def
select_account_card
(
self
,
*
,
account_type
:
str
=
"main"
,
role
:
str
=
""
,
enterprise_keyword
:
str
=
""
,
)
->
None
:
assert
account_type
in
{
"main"
,
"sub"
},
f
"Unsupported account_type: {account_type}"
box
=
self
.
page
.
evaluate
(
"""({ accountType, role, enterpriseKeyword }) => {
const typeTexts = accountType === 'sub' ? ['子账号', '瀛愯处鍙?'] : ['主账号', '涓昏处鍙?'];
const nodes = Array.from(document.querySelectorAll('taro-view-core, div'));
const candidates = nodes
.map(el => ({ text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
.filter(item => item.r.width > 240 && item.r.width < 900 && item.r.height > 36 && item.r.height < 190)
.filter(item => item.r.bottom > 0 && item.r.top < window.innerHeight)
.filter(item => typeTexts.some(typeText => item.text.includes(typeText)))
.filter(item => !role || item.text.includes(role))
.filter(item => !enterpriseKeyword || item.text.includes(enterpriseKeyword))
.sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
const target = candidates[0];
if (!target) return null;
return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height, text: target.text };
}"""
,
{
"accountType"
:
account_type
,
"role"
:
role
,
"enterpriseKeyword"
:
enterprise_keyword
},
)
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
assert
box
,
(
f
"未找到账号卡片:account_type={account_type}, role={role}, "
f
"enterprise_keyword={enterprise_keyword}; text={body_text[:1000]}"
)
self
.
page
.
mouse
.
click
(
box
[
"x"
]
+
box
[
"width"
]
-
30
,
box
[
"y"
]
+
box
[
"height"
]
/
2
)
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
2000
)
def
select_main_account_if_present
(
self
,
phone
:
str
)
->
None
:
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
if
"/account/select"
not
in
self
.
page
.
url
and
"选择账号"
not
in
body_text
:
...
...
@@ -235,11 +434,29 @@ class TenantAuthPage(BasePage):
box
=
self
.
page
.
evaluate
(
"""() => {
const typedNodes = Array.from(document.querySelectorAll('[class*="accountType"]'))
.map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
.filter(item => item.text.includes('主账号') && item.r.width > 0 && item.r.height > 0);
for (const item of typedNodes) {
const card = item.el.closest('[class*="accountItem"], [class*="account"], taro-view-core, div');
const containers = [card, card?.parentElement, card?.parentElement?.parentElement].filter(Boolean);
const target = containers
.map(el => ({ text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
.filter(candidate => candidate.r.width > 240 && candidate.r.width < 900 && candidate.r.height > 36 && candidate.r.height < 190)
.sort((a, b) => (b.r.width * b.r.height) - (a.r.width * a.r.height))[0];
if (target) return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height };
}
const candidates = Array.from(document.querySelectorAll('taro-view-core, div'))
.map(el => ({ text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
.filter(item => item.text.includes('主账号') && !item.text.includes('子账号'))
.filter(item => item.r.width > 300 && item.r.width < 800 && item.r.height > 50 && item.r.height < 140)
.sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
.filter(item => item.text.includes('主账号'))
.filter(item => item.r.width > 240 && item.r.width < 900 && item.r.height > 36 && item.r.height < 190)
.filter(item => item.r.bottom > 0 && item.r.top < window.innerHeight)
.sort((a, b) => {
const aArea = a.r.width * a.r.height;
const bArea = b.r.width * b.r.height;
if (aArea !== bArea) return aArea - bArea;
return a.r.y - b.r.y;
});
const target = candidates[0];
if (!target) return null;
return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height };
...
...
@@ -252,23 +469,50 @@ class TenantAuthPage(BasePage):
self
.
page
.
wait_for_timeout
(
2000
)
selected_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
if
"主账号"
not
in
selected_text
:
if
"
选择账号"
in
selected_text
and
"
主账号"
not
in
selected_text
:
raise
AssertionError
(
f
"登录账号 {phone} 未进入主账号上下文,当前页面文本:{selected_text[:800]}"
)
def
open_enterprise_auth_menu
(
self
,
leaf_menu
:
str
)
->
None
:
parent_menus
=
[
"企业"
,
"企业组织管理"
]
leaf_menus
=
{
"授权密钥"
:
[
"授权密钥"
,
"生成授权密钥"
],
"我的授权"
:
[
"我的授权"
,
"激活授权秘钥"
,
"激活授权密钥"
],
}
.
get
(
leaf_menu
,
[
leaf_menu
])
expected_frame
=
"/secret"
if
leaf_menu
==
"授权密钥"
else
"/myAuth"
if
leaf_menu
==
"我的授权"
else
""
if
is_production_url
(
self
.
tenant_home_url
):
expected_frame
=
""
path_by_leaf
=
{
"授权密钥"
:
"/secret"
,
"我的授权"
:
"/myAuth"
}
last_urls
:
list
[
str
]
=
[]
last_error
:
Exception
|
None
=
None
for
_
in
range
(
4
):
self
.
goto
(
ENTERPRISE_CONSOLE_URL
)
self
.
wait_for_enterprise_console_shell
()
self
.
click_sidebar_text
(
"企业"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"授权管理"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
leaf_menu
)
self
.
page
.
wait_for_timeout
(
2500
)
last_urls
=
[
frame
.
url
for
frame
in
self
.
page
.
frames
]
if
not
expected_frame
or
any
(
expected_frame
in
url
for
url
in
last_urls
):
return
raise
AssertionError
(
f
"点击授权菜单 {leaf_menu} 后未进入目标 iframe {expected_frame},当前 frames:{last_urls}"
)
for
parent_menu
in
parent_menus
:
for
target_leaf
in
leaf_menus
:
try
:
self
.
goto_enterprise_console
()
self
.
wait_for_enterprise_console_shell
()
self
.
click_sidebar_text
(
parent_menu
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"授权管理"
,
right_edge
=
True
)
if
leaf_menu
in
path_by_leaf
:
self
.
click_sidebar_path
(
path_by_leaf
[
leaf_menu
],
app_hint
=
"saas企业管理"
,
fallback_texts
=
leaf_menus
,
)
else
:
self
.
click_sidebar_text
(
target_leaf
)
self
.
page
.
wait_for_timeout
(
2500
)
last_urls
=
[
frame
.
url
for
frame
in
self
.
page
.
frames
]
if
not
expected_frame
or
any
(
expected_frame
in
url
for
url
in
last_urls
):
return
except
Exception
as
exc
:
last_error
=
exc
continue
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
raise
AssertionError
(
f
"点击授权菜单 {leaf_menu} 后未进入目标页面;已尝试父菜单={parent_menus},"
f
"叶子菜单={leaf_menus},expected_frame={expected_frame},frames={last_urls},"
f
"当前页面文本:{body_text[:1200]}"
)
from
last_error
def
wait_for_enterprise_console_shell
(
self
)
->
None
:
for
_
in
range
(
6
):
...
...
@@ -307,6 +551,38 @@ class TenantAuthPage(BasePage):
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
1000
)
def
click_sidebar_path
(
self
,
path
:
str
,
*
,
right_edge
:
bool
=
False
,
app_hint
:
str
=
""
,
component
:
str
=
""
,
fallback_texts
:
list
[
str
]
|
None
=
None
,
)
->
str
:
candidates
=
menu_names_for_path
(
path
,
production
=
is_production_url
(
self
.
tenant_home_url
or
self
.
page
.
url
),
app_hint
=
app_hint
,
component
=
component
,
)
for
text
in
fallback_texts
or
[]:
if
text
and
text
not
in
candidates
:
candidates
.
append
(
text
)
last_error
:
Exception
|
None
=
None
for
text
in
candidates
:
try
:
self
.
click_sidebar_text
(
text
,
right_edge
=
right_edge
)
return
text
except
Exception
as
exc
:
last_error
=
exc
continue
body_text
=
self
.
page
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
raise
AssertionError
(
f
"未按菜单 path 找到左侧菜单:path={path},app_hint={app_hint},"
f
"候选菜单={candidates},当前页面文本:{body_text[:1000]}"
)
from
last_error
def
sidebar_box
(
self
,
text
:
str
,
parent
:
bool
)
->
dict
:
box
=
self
.
page
.
evaluate
(
"""({ text, parent }) => {
...
...
@@ -438,7 +714,10 @@ class TenantAuthPage(BasePage):
text
=
frame
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
assert
"福通互通"
in
text
,
f
"管理授权中未出现福通互通功能权限,当前页面文本:{text[:1500]}"
self
.
check_all_visible_permissions
(
frame
)
self
.
check_permissions_by_label
(
frame
,
[
"福通互通"
,
"订单基本信息"
,
"修改订单"
,
"创建订单"
])
required_permissions
=
[
"福通互通"
]
if
"shunfju.com"
not
in
self
.
tenant_home_url
:
required_permissions
.
extend
([
"订单基本信息"
,
"修改订单"
,
"创建订单"
])
self
.
check_permissions_by_label
(
frame
,
required_permissions
)
unchecked
=
self
.
unchecked_permission_labels
(
frame
)
assert
not
unchecked
,
f
"丸友集管理授权保存前仍有未勾选权限:{unchecked}"
self
.
click_optional_frame_buttons
(
frame
,
[
"保 存"
,
"保存"
,
"确 定"
,
"确定"
])
...
...
e2e/pages/wanzhanggui_push_page.py
View file @
0c2c8d06
...
...
@@ -6,6 +6,9 @@ from e2e.utils.bug_reporter import BugReporter
WANZHANGGUI_CONSOLE_URL
=
"https://test-merchant-lianlianyun.lianlianwz.com/productConsole?productCode=P20251204180120231330"
PROD_WANZHANGGUI_CONSOLE_URL
=
(
"https://merchant-lianlianyun.shunfju.com/productConsole?productCode=P20251222184412861622"
)
class
WanzhangguiPushPage
(
TenantAuthPage
):
...
...
@@ -13,31 +16,71 @@ class WanzhangguiPushPage(TenantAuthPage):
super
()
.
__init__
(
page
,
tenant_home_url
,
bug_reporter
)
def
open_workshop_authorization
(
self
)
->
Frame
:
self
.
goto
(
WANZHANGGUI_CONSOLE_URL
)
self
.
goto
_wanzhanggui_console
(
)
self
.
wait_for_enterprise_console_shell
()
self
.
click_sidebar_text
(
"履约工作室/平台"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"工作室管理"
)
self
.
click_sidebar_text
(
"工作室授权"
)
if
self
.
is_production
():
self
.
open_prod_push_config_menu
(
"/merchantSupplyAuth"
)
else
:
self
.
click_sidebar_text
(
"履约工作室/平台"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"工作室管理"
)
self
.
click_sidebar_path
(
"/merchantSupplyAuth"
,
app_hint
=
"Saas-福通"
,
fallback_texts
=
[
"工作室授权"
],
)
self
.
page
.
wait_for_timeout
(
2500
)
return
self
.
futong_frame_by_path
(
"/merchantSupplyAuth"
)
def
open_dispatch_strategy
(
self
)
->
Frame
:
self
.
goto
(
WANZHANGGUI_CONSOLE_URL
)
self
.
goto
_wanzhanggui_console
(
)
self
.
wait_for_enterprise_console_shell
()
self
.
click_sidebar_text
(
"履约工作室/平台"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"工作室管理"
)
self
.
click_sidebar_text
(
"推单配置"
)
if
self
.
is_production
():
self
.
open_prod_push_config_menu
(
"/dispatchStrategy"
)
else
:
self
.
click_sidebar_text
(
"履约工作室/平台"
,
right_edge
=
True
)
self
.
click_sidebar_text
(
"工作室管理"
)
self
.
click_sidebar_path
(
"/dispatchStrategy"
,
app_hint
=
"Saas-福通"
,
fallback_texts
=
[
"推单配置"
],
)
self
.
page
.
wait_for_timeout
(
2500
)
return
self
.
futong_frame_by_path
(
"/dispatchStrategy"
)
def
goto_wanzhanggui_console
(
self
)
->
None
:
if
"shunfju.com"
in
self
.
tenant_home_url
:
self
.
goto
(
PROD_WANZHANGGUI_CONSOLE_URL
)
self
.
page
.
wait_for_load_state
(
"domcontentloaded"
)
self
.
page
.
wait_for_timeout
(
3000
)
expect
(
self
.
page
.
locator
(
"body"
))
.
to_contain_text
(
"发单平台配置"
,
timeout
=
15000
)
return
self
.
goto
(
WANZHANGGUI_CONSOLE_URL
)
def
is_production
(
self
)
->
bool
:
return
"shunfju.com"
in
self
.
tenant_home_url
def
open_prod_push_config_menu
(
self
,
path
:
str
)
->
None
:
self
.
click_sidebar_text
(
"发单平台配置"
,
right_edge
=
True
)
self
.
click_sidebar_path
(
path
,
app_hint
=
"Saas-福通"
)
def
futong_frame_by_path
(
self
,
path
:
str
)
->
Frame
:
matching_frames
=
[
frame
for
frame
in
self
.
page
.
frames
if
path
in
frame
.
url
]
for
frame
in
matching_frames
:
if
"futong-pc"
in
frame
.
url
:
return
frame
for
frame
in
matching_frames
:
try
:
if
any
(
text
in
frame
.
locator
(
"body"
)
.
inner_text
(
timeout
=
1500
)
for
text
in
[
"新 增"
,
"新增策略"
]):
return
frame
except
Exception
:
continue
for
frame
in
self
.
page
.
frames
:
if
"test-futong-pc"
in
frame
.
url
and
path
in
frame
.
url
:
if
path
in
frame
.
url
:
return
frame
urls
=
[
frame
.
url
for
frame
in
self
.
page
.
frames
]
raise
AssertionError
(
f
"未找到丸掌柜业务 iframe {path},当前 frames:{urls}"
)
def
add_wanyouji_workshop_authorization
(
self
,
workshop_enterprise_name
:
str
)
->
None
:
def
add_wanyouji_workshop_authorization
(
self
,
workshop_enterprise_name
:
str
,
auth_key
:
str
=
""
)
->
None
:
frame
=
self
.
open_workshop_authorization
()
if
self
.
workshop_authorized
(
frame
,
workshop_enterprise_name
):
return
...
...
@@ -46,7 +89,7 @@ class WanzhangguiPushPage(TenantAuthPage):
self
.
page
.
wait_for_timeout
(
1200
)
self
.
select_modal_option
(
frame
,
0
,
"丸友集"
)
self
.
page
.
wait_for_timeout
(
1200
)
sel
f
.
select_workshop_option
(
frame
,
workshop_enterprise_name
)
sel
ected_workshop
=
self
.
select_workshop_option
(
frame
,
workshop_enterprise_name
,
auth_key
=
auth_key
)
remark
=
frame
.
locator
(
".ant-modal textarea[placeholder='请输入备注']"
)
.
first
if
remark
.
count
():
remark
.
fill
(
f
"E2E授权{workshop_enterprise_name}"
)
...
...
@@ -55,29 +98,36 @@ class WanzhangguiPushPage(TenantAuthPage):
self
.
click_optional_frame_buttons
(
frame
,
[
"查 询"
,
"查询"
])
self
.
page
.
wait_for_timeout
(
1500
)
assert
self
.
workshop_authorized
(
frame
,
workshop_enterprise_name
),
(
assert
self
.
workshop_authorized
(
frame
,
workshop_enterprise_name
)
or
self
.
workshop_authorized
(
frame
,
selected_workshop
)
,
(
f
"丸掌柜工作室授权列表未出现工作室 {workshop_enterprise_name},"
f
"当前页面文本:{frame.locator('body').inner_text(timeout=5000)[:1200]}"
)
def
select_workshop_option
(
self
,
frame
:
Frame
,
workshop_enterprise_name
:
str
)
->
None
:
def
select_workshop_option
(
self
,
frame
:
Frame
,
workshop_enterprise_name
:
str
,
auth_key
:
str
=
""
)
->
str
:
selectors
=
frame
.
locator
(
".ant-modal .ant-select-selector"
)
expect
(
selectors
.
nth
(
1
))
.
to_be_visible
(
timeout
=
5000
)
selectors
.
nth
(
1
)
.
click
()
self
.
page
.
wait_for_timeout
(
1000
)
option
=
frame
.
locator
(
".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option-content"
)
.
filter
(
has_text
=
workshop_enterprise_name
)
.
first
option_locator
=
frame
.
locator
(
".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option-content"
)
option
=
option_locator
.
filter
(
has_text
=
workshop_enterprise_name
)
.
first
if
option
.
count
()
==
0
and
auth_key
:
option
=
option_locator
.
filter
(
has_text
=
auth_key
)
.
first
if
option
.
count
()
==
0
:
option
=
option_locator
.
first
expect
(
option
)
.
to_be_visible
(
timeout
=
5000
)
selected_text
=
option
.
inner_text
(
timeout
=
5000
)
.
strip
()
option
.
click
()
self
.
page
.
wait_for_timeout
(
800
)
return
selected_text
def
workshop_authorized
(
self
,
frame
:
Frame
,
workshop_enterprise_name
:
str
)
->
bool
:
try
:
text
=
frame
.
locator
(
"body"
)
.
inner_text
(
timeout
=
5000
)
except
Exception
:
return
False
return
workshop_enterprise_name
in
text
and
(
"启用"
in
text
or
"正常"
in
text
or
"状态"
in
text
)
if
workshop_enterprise_name
in
text
and
(
"启用"
in
text
or
"正常"
in
text
or
"状态"
in
text
):
return
True
return
self
.
is_production
()
and
"自动化丸友集工作室"
in
text
and
(
"启用"
in
text
or
"正常"
in
text
)
def
create_dispatch_strategy
(
self
,
...
...
e2e/resources/menu_path_mapping.json
0 → 100644
View file @
0c2c8d06
{
"source"
:
"C:
\\
Users
\\
。
\\
Downloads
\\
测试环境与正式库应用资源名称差异报告.xlsx"
,
"match_rule"
:
"Use resource path as the primary menu key; use component/app hints when a path has multiple matches."
,
"paths"
:
{
"/account"
:
[
{
"path"
:
"/account"
,
"component"
:
"/pages/account/index"
,
"test_app"
:
"担保应用"
,
"test_menu"
:
"账户管理"
,
"test_resource_id"
:
"RO20260325141807726612"
,
"production_app"
:
"TokenHub"
,
"production_menu"
:
"账号管理"
,
"production_resource_id"
:
"RO20260604115814318481"
},
{
"path"
:
"/account"
,
"component"
:
"/pages/account/index"
,
"test_app"
:
"token-hub"
,
"test_menu"
:
"账号管理"
,
"test_resource_id"
:
"RO20260512132105795570"
,
"production_app"
:
"担保应用"
,
"production_menu"
:
"账户管理"
,
"production_resource_id"
:
"RO20260325141807726612"
}
],
"/accountSnapshot"
:
[
{
"path"
:
"/accountSnapshot"
,
"component"
:
"/pages/accountSnapshot/index"
,
"test_app"
:
"担保应用"
,
"test_menu"
:
"账户快照"
,
"test_resource_id"
:
"RO20260528112018535570"
,
"production_app"
:
"担保应用"
,
"production_menu"
:
"资金账户快照"
,
"production_resource_id"
:
"RO20260529164756492560"
}
],
"/appeal"
:
[
{
"path"
:
"/appeal"
,
"component"
:
"/pages/order/appeal/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"申诉工单"
,
"test_resource_id"
:
"RO20251216194953280452"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作罚款订单"
,
"production_resource_id"
:
"RO20251222200620193162"
}
],
"/autoAcceptOrderConfig"
:
[
{
"path"
:
"/autoAcceptOrderConfig"
,
"component"
:
"/pages/autoAcceptOrderConfig/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"智能接单配置"
,
"test_resource_id"
:
"RO20260622104022141570"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"外部订单智能接单"
,
"production_resource_id"
:
"RO20260629103521862481"
}
],
"/bannerList"
:
[
{
"path"
:
"/bannerList"
,
"component"
:
"/pages/bannerList/index"
,
"test_app"
:
"Saas-应用中心"
,
"test_menu"
:
"banne管理"
,
"test_resource_id"
:
"RO20260104174627375382"
,
"production_app"
:
"收单应用管理"
,
"production_menu"
:
"banner管理"
,
"production_resource_id"
:
"RO20260121150222626160"
}
],
"/boostRecords"
:
[
{
"path"
:
"/boostRecords"
,
"component"
:
"/pages/boostRecords/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"代练行为记录"
,
"test_resource_id"
:
"RO20251208094731069452"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"订单行为记录"
,
"production_resource_id"
:
"RO20251212114956162560"
}
],
"/chat-session-list"
:
[
{
"path"
:
"/chat-session-list"
,
"component"
:
"/pages/chatSessionList/index"
,
"test_app"
:
"咨询客服智能体"
,
"test_menu"
:
"聊天会话"
,
"test_resource_id"
:
"RO20260427122217414572"
,
"production_app"
:
"咨询智能体"
,
"production_menu"
:
"聊天列表"
,
"production_resource_id"
:
"RO20260410153154503162"
}
],
"/collectionSettlement"
:
[
{
"path"
:
"/collectionSettlement"
,
"component"
:
"/pages/collectionSettlement/index"
,
"test_app"
:
"结算中心"
,
"test_menu"
:
"结算账单-丸友集"
,
"test_resource_id"
:
"RO20260518151136321571"
,
"production_app"
:
"结算中心"
,
"production_menu"
:
"对账单-丸友集"
,
"production_resource_id"
:
"RO20260604161630502480"
}
],
"/configuration"
:
[
{
"path"
:
"/configuration"
,
"component"
:
"/pages/configuration/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"功能配置"
,
"test_resource_id"
:
"RO20250923102604418371"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"订单权限设置"
,
"production_resource_id"
:
"RO20250926180608373132"
}
],
"/credit/union"
:
[
{
"path"
:
"/credit/union"
,
"component"
:
"/credit/union"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"支付宝收款审核"
,
"test_resource_id"
:
"RO20250428111952735212"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"现金收款审核"
,
"production_resource_id"
:
"RO20250428111952735212"
}
],
"/dataDict"
:
[
{
"path"
:
"/dataDict"
,
"component"
:
"/pages/dataDict/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"数据字典"
,
"test_resource_id"
:
"RO20260415113200316530"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"问题原因设置"
,
"production_resource_id"
:
"RO20260421172356819561"
}
],
"/dispatchStrategy"
:
[
{
"path"
:
"/dispatchStrategy"
,
"component"
:
"/pages/dispatchStrategy/index"
,
"test_app"
:
"Saas-福通"
,
"test_menu"
:
"推单配置"
,
"test_resource_id"
:
"RO20251125180821488450"
,
"production_app"
:
"Saas-福通"
,
"production_menu"
:
"推单策略配置"
,
"production_resource_id"
:
"RO20251222201120402561"
}
],
"/enforcer/lable"
:
[
{
"path"
:
"/enforcer/lable"
,
"component"
:
"/pages/enforcer/lable/index"
,
"test_app"
:
"打手应用"
,
"test_menu"
:
"标签管理"
,
"test_resource_id"
:
"RO20250815162004435021"
,
"production_app"
:
"打手应用"
,
"production_menu"
:
"员工标签"
,
"production_resource_id"
:
"RO20250915112134453211"
}
],
"/enforcer/member"
:
[
{
"path"
:
"/enforcer/member"
,
"component"
:
"/pages/enforcer/member/index"
,
"test_app"
:
"打手应用"
,
"test_menu"
:
"打手列表"
,
"test_resource_id"
:
"RO20250815170706567021"
,
"production_app"
:
"打手应用"
,
"production_menu"
:
"员工列表"
,
"production_resource_id"
:
"RO20250915112113193210"
}
],
"/exportTask"
:
[
{
"path"
:
"/exportTask"
,
"component"
:
"/pages/exportTask/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"任务中心"
,
"test_resource_id"
:
"RO20260415165426674532"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"报表下载中心"
,
"production_resource_id"
:
"RO20260417094905457160"
}
],
"/exposure/exposureConfig"
:
[
{
"path"
:
"/exposure/exposureConfig"
,
"component"
:
"/pages/exposure/exposureConfig/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"曝光管理"
,
"test_resource_id"
:
"RO20250707171216913020"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"曝光配置"
,
"production_resource_id"
:
"RO20250710111750081362"
}
],
"/faq-manage"
:
[
{
"path"
:
"/faq-manage"
,
"component"
:
"/pages/faqManage/index"
,
"test_app"
:
"咨询客服智能体"
,
"test_menu"
:
"FAQ问答"
,
"test_resource_id"
:
"RO20260420124949747570"
,
"production_app"
:
"咨询智能体"
,
"production_menu"
:
"问答管理"
,
"production_resource_id"
:
"RO20260410153242582560"
}
],
"/financialConfig/billManage"
:
[
{
"path"
:
"/financialConfig/billManage"
,
"component"
:
"/pages/financialConfig/billManage/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"结算账单列表"
,
"test_resource_id"
:
"RO20251216161238402450"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"员工结算审批"
,
"production_resource_id"
:
"RO20251219182033540160"
}
],
"/flow/flowFeesExchange"
:
[
{
"path"
:
"/flow/flowFeesExchange"
,
"component"
:
"/pages/flow/flowFeesExchange/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"手续费流量兑换记录"
,
"test_resource_id"
:
"RO20250625143248653021"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"手续费兑流量"
,
"production_resource_id"
:
"RO20250624150624552471"
}
],
"/flowRate/compensation"
:
[
{
"path"
:
"/flowRate/compensation"
,
"component"
:
"/flowRate/compensation"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"流量补偿"
,
"test_resource_id"
:
"RO20250521140953742022"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"流量补偿记录"
,
"production_resource_id"
:
"RO20250521140953742022"
}
],
"/flowType"
:
[
{
"path"
:
"/flowType"
,
"component"
:
"/flowType"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"商品类型配置"
,
"test_resource_id"
:
"RO20250507163218408020"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"流量管理"
,
"production_resource_id"
:
"RO20250428111725862210"
},
{
"path"
:
"/flowType"
,
"component"
:
"/flowType"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"流量管理"
,
"test_resource_id"
:
"RO20250428111725862210"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"商品类型配置"
,
"production_resource_id"
:
"RO20250507163218408020"
}
],
"/fundItemSnapshot"
:
[
{
"path"
:
"/fundItemSnapshot"
,
"component"
:
"/pages/fundItemSnapshot/index"
,
"test_app"
:
"担保应用"
,
"test_menu"
:
"资金统计"
,
"test_resource_id"
:
"RO20260522161707443570"
,
"production_app"
:
"担保应用"
,
"production_menu"
:
"资金流水统计"
,
"production_resource_id"
:
"RO20260529164707715162"
}
],
"/greatGodList"
:
[
{
"path"
:
"/greatGodList"
,
"component"
:
"/pages/greatGodList/index"
,
"test_app"
:
"淘天应用"
,
"test_menu"
:
"预约大神列表"
,
"test_resource_id"
:
"RO20260105175402759021"
,
"production_app"
:
"淘天小程序管理"
,
"production_menu"
:
"预约大神配置"
,
"production_resource_id"
:
"RO20260121150334372161"
}
],
"/guildMemberDistributionLog"
:
[
{
"path"
:
"/guildMemberDistributionLog"
,
"component"
:
"/pages/statistics/guildMemberDistributionLog/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"成员核销明细(企微)"
,
"test_resource_id"
:
"RO20250604151619503022"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"成员流量核销记录"
,
"production_resource_id"
:
"RO20250604151619503022"
}
],
"/guildMemberOrder"
:
[
{
"path"
:
"/guildMemberOrder"
,
"component"
:
"/pages/statistics/guildMemberOrder/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"成员订单"
,
"test_resource_id"
:
"RO20250604140112793020"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"成员流量订单"
,
"production_resource_id"
:
"RO20250604140112793020"
}
],
"/member-distribution-log"
:
[
{
"path"
:
"/member-distribution-log"
,
"component"
:
"/pages/statistics/member-distribution-log/index"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"成员核销明细(企微)"
,
"test_resource_id"
:
"RO20250604163630788022"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"成员流量核销记录"
,
"production_resource_id"
:
"RO20250604163630788022"
}
],
"/member-flow-detail"
:
[
{
"path"
:
"/member-flow-detail"
,
"component"
:
"/pages/statistics/member-flow-detail/index"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"成员流量明细"
,
"test_resource_id"
:
"RO20250604162611015022"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"成员流量变动记录"
,
"production_resource_id"
:
"RO20250604162611015022"
}
],
"/member-orders"
:
[
{
"path"
:
"/member-orders"
,
"component"
:
"/pages/statistics/member-orders/index"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"成员订单明细"
,
"test_resource_id"
:
"RO20250604160842723021"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"成员流量订单明细"
,
"production_resource_id"
:
"RO20250604160842723021"
}
],
"/merchant"
:
[
{
"path"
:
"/merchant"
,
"component"
:
"/pages/merchant/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"商家管理"
,
"test_resource_id"
:
"RO20260126105000493280"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"商家账户管理"
,
"production_resource_id"
:
"RO20260203171654569562"
}
],
"/merchantSupplyAuth"
:
[
{
"path"
:
"/merchantSupplyAuth"
,
"component"
:
"/pages/merchantSupplyAuth/index"
,
"test_app"
:
"Saas-福通"
,
"test_menu"
:
"工作室授权"
,
"test_resource_id"
:
"RO20251125180802847451"
,
"production_app"
:
"Saas-福通"
,
"production_menu"
:
"添加平台"
,
"production_resource_id"
:
"RO20251222201038127160"
}
],
"/message"
:
[
{
"path"
:
"/message"
,
"component"
:
"/pages/message/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"消息中心"
,
"test_resource_id"
:
"RO20260422151851814570"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"工作室通知消息"
,
"production_resource_id"
:
"RO20260429150745355561"
},
{
"path"
:
"/message"
,
"component"
:
"/pages/message/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"消息中心"
,
"test_resource_id"
:
"RO20260424163551389570"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"工作室通知消息"
,
"production_resource_id"
:
"RO20260429150745355561"
}
],
"/myAuth"
:
[
{
"path"
:
"/myAuth"
,
"component"
:
"/pages/myAuth/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"授权管理"
,
"test_resource_id"
:
"RO20251205162924302450"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"秘钥激活与授权"
,
"production_resource_id"
:
"RO20251222194913007560"
},
{
"path"
:
"/myAuth"
,
"component"
:
"/pages/authFlow/myAuth/index"
,
"test_app"
:
"saas企业管理"
,
"test_menu"
:
"我的授权"
,
"test_resource_id"
:
"RO20251021110304635451"
,
"production_app"
:
"企业管理"
,
"production_menu"
:
"激活授权秘钥"
,
"production_resource_id"
:
"RO20251027110705314160"
}
],
"/order"
:
[
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"抖店自建应用"
,
"test_menu"
:
"抖音订单"
,
"test_resource_id"
:
"RO20251110110625259451"
,
"production_app"
:
"租号玩-福通"
,
"production_menu"
:
"订单列表"
,
"production_resource_id"
:
"RO20250807181529512270"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"抖店自建应用"
,
"test_menu"
:
"抖音订单"
,
"test_resource_id"
:
"RO20251110110625259451"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作订单列表"
,
"production_resource_id"
:
"RO20251222200559132560"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"抖店自建应用"
,
"test_menu"
:
"抖音订单"
,
"test_resource_id"
:
"RO20251110110625259451"
,
"production_app"
:
"Saas-福通"
,
"production_menu"
:
"分单列表"
,
"production_resource_id"
:
"RO20251222200952453160"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"福通订单管理"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20250714101305724020"
,
"production_app"
:
"抖店自建应用"
,
"production_menu"
:
"抖音订单"
,
"production_resource_id"
:
"RO20251110114920030162"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"福通订单管理"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20250714101305724020"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作订单列表"
,
"production_resource_id"
:
"RO20251222200559132560"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"福通订单管理"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20250714101305724020"
,
"production_app"
:
"Saas-福通"
,
"production_menu"
:
"分单列表"
,
"production_resource_id"
:
"RO20251222200952453160"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20251204175713930451"
,
"production_app"
:
"抖店自建应用"
,
"production_menu"
:
"抖音订单"
,
"production_resource_id"
:
"RO20251110114920030162"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20251204175713930451"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作订单列表"
,
"production_resource_id"
:
"RO20251222200559132560"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20251204175713930451"
,
"production_app"
:
"Saas-福通"
,
"production_menu"
:
"分单列表"
,
"production_resource_id"
:
"RO20251222200952453160"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"Saas-福通"
,
"test_menu"
:
"分单列表"
,
"test_resource_id"
:
"RO20251208144442346452"
,
"production_app"
:
"租号玩-福通"
,
"production_menu"
:
"订单列表"
,
"production_resource_id"
:
"RO20250807181529512270"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"Saas-福通"
,
"test_menu"
:
"分单列表"
,
"test_resource_id"
:
"RO20251208144442346452"
,
"production_app"
:
"抖店自建应用"
,
"production_menu"
:
"抖音订单"
,
"production_resource_id"
:
"RO20251110114920030162"
},
{
"path"
:
"/order"
,
"component"
:
"/pages/order/index"
,
"test_app"
:
"Saas-福通"
,
"test_menu"
:
"分单列表"
,
"test_resource_id"
:
"RO20251208144442346452"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作订单列表"
,
"production_resource_id"
:
"RO20251222200559132560"
}
],
"/orderHall"
:
[
{
"path"
:
"/orderHall"
,
"component"
:
"/pages/orderHall/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"抢单大厅"
,
"test_resource_id"
:
"RO20260515163411282570"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部合作抢单大厅"
,
"production_resource_id"
:
"RO20260526183238398560"
}
],
"/orderManage/customerForm"
:
[
{
"path"
:
"/orderManage/customerForm"
,
"component"
:
"/pages/orderManage/customerForm/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"客户自助填单"
,
"test_resource_id"
:
"RO20250818152317905021"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"内部自助写单"
,
"production_resource_id"
:
"RO20250915111805470131"
}
],
"/orderManage/customerServicePerformance"
:
[
{
"path"
:
"/orderManage/customerServicePerformance"
,
"component"
:
"/pages/orderManage/customerServicePerformance/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"客服绩效"
,
"test_resource_id"
:
"RO20260320140005236610"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"客服绩效表"
,
"production_resource_id"
:
"RO20260320180524274562"
}
],
"/orderManage/dispatchPriceConfig"
:
[
{
"path"
:
"/orderManage/dispatchPriceConfig"
,
"component"
:
"/pages/orderManage/dispatchPriceConfig/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"订单发单价配置"
,
"test_resource_id"
:
"RO20260417110505806531"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"工作室发单价配置"
,
"production_resource_id"
:
"RO20260430152506167160"
},
{
"path"
:
"/orderManage/dispatchPriceConfig"
,
"component"
:
"/pages/orderManage/dispatchPriceConfig/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"订单发单价配置"
,
"test_resource_id"
:
"RO20260417112636600531"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"工作室发单价配置"
,
"production_resource_id"
:
"RO20260430152506167160"
}
],
"/orderManage/exportTask"
:
[
{
"path"
:
"/orderManage/exportTask"
,
"component"
:
"/pages/orderManage/exportTask/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"任务中心"
,
"test_resource_id"
:
"RO20251216194644603452"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"数据导出记录"
,
"production_resource_id"
:
"RO20251222194943733162"
}
],
"/orderManage/handlerOrder"
:
[
{
"path"
:
"/orderManage/handlerOrder"
,
"component"
:
"/pages/orderManage/handlerOrder/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"打手订单"
,
"test_resource_id"
:
"RO20250924155546305200"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"打手订单列表"
,
"production_resource_id"
:
"RO20250925191151087260"
}
],
"/orderManage/manualOrder"
:
[
{
"path"
:
"/orderManage/manualOrder"
,
"component"
:
"/pages/orderManage/manualOrder/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"手动录单"
,
"test_resource_id"
:
"RO20250917155131872372"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"内部订单创建"
,
"production_resource_id"
:
"RO20250920213833627130"
}
],
"/orderManage/orderAppeal"
:
[
{
"path"
:
"/orderManage/orderAppeal"
,
"component"
:
"/pages/orderManage/orderAppeal/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"主单申诉单"
,
"test_resource_id"
:
"RO20251216194617833450"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"用户赔偿列表"
,
"production_resource_id"
:
"RO20260209164436799561"
}
],
"/orderManage/orderList"
:
[
{
"path"
:
"/orderManage/orderList"
,
"component"
:
"/pages/orderManage/orderList/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20250815114752271021"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"内部订单列表"
,
"production_resource_id"
:
"RO20250915111716563132"
},
{
"path"
:
"/orderManage/orderList"
,
"component"
:
"/pages/orderManage/orderList/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"订单列表"
,
"test_resource_id"
:
"RO20250815114752271021"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"订单管理列表"
,
"production_resource_id"
:
"RO20251222194708600561"
},
{
"path"
:
"/orderManage/orderList"
,
"component"
:
"/pages/orderManage/orderList/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"订单管理"
,
"test_resource_id"
:
"RO20251204175821500450"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"内部订单列表"
,
"production_resource_id"
:
"RO20250915111716563132"
},
{
"path"
:
"/orderManage/orderList"
,
"component"
:
"/pages/orderManage/orderList/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"订单管理"
,
"test_resource_id"
:
"RO20251204175821500450"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"订单管理列表"
,
"production_resource_id"
:
"RO20251222194708600561"
}
],
"/orderManage/orderSettle"
:
[
{
"path"
:
"/orderManage/orderSettle"
,
"component"
:
"/pages/orderManage/orderSettle/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"订单结算"
,
"test_resource_id"
:
"RO20260520191701172571"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"主单结算明细"
,
"production_resource_id"
:
"RO20251222195621200162"
}
],
"/orderManage/subOrderAppeal"
:
[
{
"path"
:
"/orderManage/subOrderAppeal"
,
"component"
:
"/pages/orderManage/subOrderAppeal/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"子单处罚单"
,
"test_resource_id"
:
"RO20260330112333224610"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"供应链处罚列表"
,
"production_resource_id"
:
"RO20260401112830693560"
}
],
"/orderManage/subOrderDetail"
:
[
{
"path"
:
"/orderManage/subOrderDetail"
,
"component"
:
"/pages/orderManage/subOrderDetail/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"子单明细"
,
"test_resource_id"
:
"RO20260327142934858610"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"子单结算明细"
,
"production_resource_id"
:
"RO20260327174743452161"
}
],
"/orderManage/takeRateCount"
:
[
{
"path"
:
"/orderManage/takeRateCount"
,
"component"
:
"/pages/orderManage/takeRateCount/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"接单率统计"
,
"test_resource_id"
:
"RO20260319154011211612"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"推单率统计"
,
"production_resource_id"
:
"RO20260324165236966560"
}
],
"/orderManagement/orderHall"
:
[
{
"path"
:
"/orderManagement/orderHall"
,
"component"
:
"pages/orderManagement/orderHall/index"
,
"test_app"
:
"打手应用"
,
"test_menu"
:
"抢单大厅"
,
"test_resource_id"
:
"RO20250815150251229022"
,
"production_app"
:
"打手应用"
,
"production_menu"
:
"内部接单大厅"
,
"production_resource_id"
:
"RO20250915112019234210"
}
],
"/organization"
:
[
{
"path"
:
"/organization"
,
"component"
:
"/pages/organization/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"组织与部门管理"
,
"test_resource_id"
:
"RO20250818142036318022"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"我的部门成员"
,
"production_resource_id"
:
"RO20250915112247097132"
},
{
"path"
:
"/organization"
,
"component"
:
"/pages/organization/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"组织与部门管理"
,
"test_resource_id"
:
"RO20250818142036318022"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"员工信息"
,
"production_resource_id"
:
"RO20251222195724138162"
},
{
"path"
:
"/organization"
,
"component"
:
"/pages/organization/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"员工管理"
,
"test_resource_id"
:
"RO20251216194438083450"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"我的部门成员"
,
"production_resource_id"
:
"RO20250915112247097132"
},
{
"path"
:
"/organization"
,
"component"
:
"/pages/organization/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"员工管理"
,
"test_resource_id"
:
"RO20251216194438083450"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"员工信息"
,
"production_resource_id"
:
"RO20251222195724138162"
}
],
"/organization/union"
:
[
{
"path"
:
"/organization/union"
,
"component"
:
"/organization/union"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"商品采销"
,
"test_resource_id"
:
"RO20250428111702001212"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"流量数据管理"
,
"production_resource_id"
:
"RO20250428111749985210"
},
{
"path"
:
"/organization/union"
,
"component"
:
"/organization/union"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"数据管理"
,
"test_resource_id"
:
"RO20250428111749985210"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"流量数据管理"
,
"production_resource_id"
:
"RO20250428111749985210"
}
],
"/organize"
:
[
{
"path"
:
"/organize"
,
"component"
:
"/organize"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"成员管理"
,
"test_resource_id"
:
"RO20250513164540246021"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"组织成员管理"
,
"production_resource_id"
:
"RO20250513164540246021"
}
],
"/pages/statistics/purchase-orders/index"
:
[
{
"path"
:
"/pages/statistics/purchase-orders/index"
,
"component"
:
"/purchase-orders"
,
"test_app"
:
"一起丸公会应用"
,
"test_menu"
:
"联盟采购订单记录"
,
"test_resource_id"
:
"RO20250506140528065022"
,
"production_app"
:
"一起丸公会应用"
,
"production_menu"
:
"采购订单记录"
,
"production_resource_id"
:
"RO20250506140528065022"
}
],
"/pages/statistics/refundData/index"
:
[
{
"path"
:
"/pages/statistics/refundData/index"
,
"component"
:
"refundData"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"退款数据"
,
"test_resource_id"
:
"RO20250703164921934022"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"退款统计"
,
"production_resource_id"
:
"RO20250708111304662360"
}
],
"/pages/statistics/verificationData/index"
:
[
{
"path"
:
"/pages/statistics/verificationData/index"
,
"component"
:
"verificationData"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"核销数据"
,
"test_resource_id"
:
"RO20250703164811380021"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"流量营收数据"
,
"production_resource_id"
:
"RO20250708111030309360"
}
],
"/paymentBreakdown/DispatcherSettlement"
:
[
{
"path"
:
"/paymentBreakdown/DispatcherSettlement"
,
"component"
:
"/pages/paymentBreakdown/DispatcherSettlement/index"
,
"test_app"
:
"结算中心"
,
"test_menu"
:
"结算明细-丸掌柜"
,
"test_resource_id"
:
"RO20260601105123235572"
,
"production_app"
:
"结算中心"
,
"production_menu"
:
"订单明细-丸掌柜"
,
"production_resource_id"
:
"RO20260604161703621481"
}
],
"/paymentBreakdown/FulfillerSettlement"
:
[
{
"path"
:
"/paymentBreakdown/FulfillerSettlement"
,
"component"
:
"/pages/paymentBreakdown/FulfillerSettlement/index"
,
"test_app"
:
"结算中心"
,
"test_menu"
:
"结算明细-丸友集"
,
"test_resource_id"
:
"RO20260601111114559572"
,
"production_app"
:
"结算中心"
,
"production_menu"
:
"订单明细-丸友集"
,
"production_resource_id"
:
"RO20260604161720109480"
}
],
"/penaltyRecovery"
:
[
{
"path"
:
"/penaltyRecovery"
,
"component"
:
"/pages/penaltyRecovery/index"
,
"test_app"
:
"收单应用"
,
"test_menu"
:
"处罚追缴"
,
"test_resource_id"
:
"RO20251208113827726452"
,
"production_app"
:
"订单管理"
,
"production_menu"
:
"打手处罚订单"
,
"production_resource_id"
:
"RO20251212114937754162"
}
],
"/priceConifg"
:
[
{
"path"
:
"/priceConifg"
,
"component"
:
"/pages/priceConifg/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"调价配置"
,
"test_resource_id"
:
"RO20251121113540492451"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"自动加价设置"
,
"production_resource_id"
:
"RO20251201165049723161"
}
],
"/priceManage"
:
[
{
"path"
:
"/priceManage"
,
"component"
:
"/pages/priceManage/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"录单参考价"
,
"test_resource_id"
:
"RO20251009100608611160"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"录单参考价设置"
,
"production_resource_id"
:
"RO20251021174258753562"
}
],
"/rating/feesConvertedFlow"
:
[
{
"path"
:
"/rating/feesConvertedFlow"
,
"component"
:
"/pages/rating/feesConvertedFlow/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"基础配置"
,
"test_resource_id"
:
"RO20250616164731097021"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"评级配置"
,
"production_resource_id"
:
"RO20250616164731097021"
}
],
"/rating/memberManage"
:
[
{
"path"
:
"/rating/memberManage"
,
"component"
:
"/rating/memberManage"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"成员评级开关"
,
"test_resource_id"
:
"RO20250709183150396021"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"成员管理"
,
"production_resource_id"
:
"RO20250612174514799342"
}
],
"/refund/transfer/business"
:
[
{
"path"
:
"/refund/transfer/business"
,
"component"
:
"/refund/transfer/business"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"转账退款申请"
,
"test_resource_id"
:
"RO20250521160036200020"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"现金退款申请"
,
"production_resource_id"
:
"RO20250521160036200020"
}
],
"/refund/transfer/finance"
:
[
{
"path"
:
"/refund/transfer/finance"
,
"component"
:
"/refund/transfer/finance"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"转账退款申请"
,
"test_resource_id"
:
"RO20250521160150704021"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"现金退款申请"
,
"production_resource_id"
:
"RO20250521160150704021"
}
],
"/revenueSharing"
:
[
{
"path"
:
"/revenueSharing"
,
"component"
:
"pages/revenueSharing/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"分成比例"
,
"test_resource_id"
:
"RO20250818114607318020"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"客服绩效配置"
,
"production_resource_id"
:
"RO20250915112445135131"
}
],
"/roles"
:
[
{
"path"
:
"/roles"
,
"component"
:
"pages/roles/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"角色管理"
,
"test_resource_id"
:
"RO20250818140641539020"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"角色权限管理"
,
"production_resource_id"
:
"RO20250915112305314131"
},
{
"path"
:
"/roles"
,
"component"
:
"/pages/roles/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"角色管理"
,
"test_resource_id"
:
"RO20251216194506060451"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"岗位管理"
,
"production_resource_id"
:
"RO20251222195756031562"
}
],
"/secret"
:
[
{
"path"
:
"/secret"
,
"component"
:
"/pages/authFlow/secret/index"
,
"test_app"
:
"saas企业管理"
,
"test_menu"
:
"授权密钥"
,
"test_resource_id"
:
"RO20251021110255481450"
,
"production_app"
:
"企业管理"
,
"production_menu"
:
"生成授权密钥"
,
"production_resource_id"
:
"RO20251027110738532560"
}
],
"/serviceLayerBindList"
:
[
{
"path"
:
"/serviceLayerBindList"
,
"component"
:
"/pages/serviceLayerBindList/index"
,
"test_app"
:
"淘天应用"
,
"test_menu"
:
"绑定分层服务"
,
"test_resource_id"
:
"RO20260106105145831020"
,
"production_app"
:
"淘天小程序管理"
,
"production_menu"
:
"分层服务"
,
"production_resource_id"
:
"RO20260121150519458160"
}
],
"/settle"
:
[
{
"path"
:
"/settle"
,
"component"
:
"/pages/order/settle/index"
,
"test_app"
:
"工作室履约应用"
,
"test_menu"
:
"订单结算"
,
"test_resource_id"
:
"RO20251222102415778450"
,
"production_app"
:
"工作室履约应用"
,
"production_menu"
:
"外部订单结算记录"
,
"production_resource_id"
:
"RO20251222200645116560"
}
],
"/skuCategoryList"
:
[
{
"path"
:
"/skuCategoryList"
,
"component"
:
"/pages/skuCategoryList/index"
,
"test_app"
:
"Saas-商品中心"
,
"test_menu"
:
"商品SKU分类"
,
"test_resource_id"
:
"RO20260402115947996610"
,
"production_app"
:
"商品管理"
,
"production_menu"
:
"sku类目管理"
,
"production_resource_id"
:
"RO20260121150002212560"
}
],
"/skuList"
:
[
{
"path"
:
"/skuList"
,
"component"
:
"/pages/skuList/index"
,
"test_app"
:
"Saas-商品中心"
,
"test_menu"
:
"商品SKU"
,
"test_resource_id"
:
"RO20260402120026375611"
,
"production_app"
:
"商品管理"
,
"production_menu"
:
"sku管理"
,
"production_resource_id"
:
"RO20260121145802351562"
}
],
"/smartDispatch"
:
[
{
"path"
:
"/smartDispatch"
,
"component"
:
"/pages/smartDispatch/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"智能派单配置"
,
"test_resource_id"
:
"RO20260311105603511442"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"工作室打手配置"
,
"production_resource_id"
:
"RO20260611103028047481"
}
],
"/spcTemplateList"
:
[
{
"path"
:
"/spcTemplateList"
,
"component"
:
"/pages/spcTemplateList/index.tsx"
,
"test_app"
:
"Saas-商品中心"
,
"test_menu"
:
"商品SPC规格"
,
"test_resource_id"
:
"RO20260402115853797612"
,
"production_app"
:
"商品管理"
,
"production_menu"
:
"spec规格模板管理"
,
"production_resource_id"
:
"RO20260415135517827160"
}
],
"/spuCategoryList"
:
[
{
"path"
:
"/spuCategoryList"
,
"component"
:
"/pages/spuCategoryList/index"
,
"test_app"
:
"Saas-商品中心"
,
"test_menu"
:
"商品SPU分类"
,
"test_resource_id"
:
"RO20260402115924631610"
,
"production_app"
:
"商品管理"
,
"production_menu"
:
"spu类目管理"
,
"production_resource_id"
:
"RO20260121145853395161"
}
],
"/spuList"
:
[
{
"path"
:
"/spuList"
,
"component"
:
"/pages/spuList/index"
,
"test_app"
:
"Saas-商品中心"
,
"test_menu"
:
"商品SPU"
,
"test_resource_id"
:
"RO20260402120005014610"
,
"production_app"
:
"商品管理"
,
"production_menu"
:
"spu管理"
,
"production_resource_id"
:
"RO20260121145736704161"
}
],
"/statistics/boosterPenaltySettle"
:
[
{
"path"
:
"/statistics/boosterPenaltySettle"
,
"component"
:
"/pages/statistics/boosterPenaltySettle/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"打手处罚结算"
,
"test_resource_id"
:
"RO20251208172541066452"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"打手处罚明细"
,
"production_resource_id"
:
"RO20251212140601637162"
}
],
"/statistics/orderSettlement"
:
[
{
"path"
:
"/statistics/orderSettlement"
,
"component"
:
"/pages/statistics/orderSettlement/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"订单结算表"
,
"test_resource_id"
:
"RO20250909183054046372"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"主订单明细"
,
"production_resource_id"
:
"RO20250915112701642131"
}
],
"/statistics/playerOrderSettlement"
:
[
{
"path"
:
"/statistics/playerOrderSettlement"
,
"component"
:
"/pages/statistics/playerOrderSettlement/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"打手订单结算"
,
"test_resource_id"
:
"RO20250909181813646371"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"打手订单明细"
,
"production_resource_id"
:
"RO20250915112727911131"
}
],
"/statistics/subOrderDaily"
:
[
{
"path"
:
"/statistics/subOrderDaily"
,
"component"
:
"/pages/statistics/subOrderDaily/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"子单日统计表"
,
"test_resource_id"
:
"RO20250930094836648201"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"完单统计表"
,
"production_resource_id"
:
"RO20250930161359454131"
}
],
"/statistics/subOrderDailyHandler"
:
[
{
"path"
:
"/statistics/subOrderDailyHandler"
,
"component"
:
"/pages/statistics/subOrderDailyHandler/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"子单日统计表-打手维度"
,
"test_resource_id"
:
"RO20250930114239052161"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"打手完单明细"
,
"production_resource_id"
:
"RO20250930161442722132"
}
],
"/statistics/takeRateCount"
:
[
{
"path"
:
"/statistics/takeRateCount"
,
"component"
:
"/pages/statistics/takeRateCount/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"接单率统计"
,
"test_resource_id"
:
"RO20251017144437557160"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"工作室实时接单率"
,
"production_resource_id"
:
"RO20251020182410726562"
}
],
"/studio"
:
[
{
"path"
:
"/studio"
,
"component"
:
"/pages/studio/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"工作室管理"
,
"test_resource_id"
:
"RO20260126162444651280"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"工作室账户余额"
,
"production_resource_id"
:
"RO20260203172036953160"
}
],
"/sysLog"
:
[
{
"path"
:
"/sysLog"
,
"component"
:
"/pages/sysLog/index"
,
"test_app"
:
"工作室基础应用"
,
"test_menu"
:
"操作日志"
,
"test_resource_id"
:
"RO20260127100656472280"
,
"production_app"
:
"工作室管理"
,
"production_menu"
:
"员工操作日志"
,
"production_resource_id"
:
"RO20260203172212300162"
}
],
"/ticket"
:
[
{
"path"
:
"/ticket"
,
"component"
:
"/pages/ticket/index"
,
"test_app"
:
"Saas-福单"
,
"test_menu"
:
"客服工单"
,
"test_resource_id"
:
"RO20260415151112221530"
,
"production_app"
:
"商户管理"
,
"production_menu"
:
"客服工单列表"
,
"production_resource_id"
:
"RO20260421172420767160"
}
],
"/trafficMemberFlowDetail"
:
[
{
"path"
:
"/trafficMemberFlowDetail"
,
"component"
:
"/pages/statistics/trafficMemberFlowDetail/index"
,
"test_app"
:
"代练工会解决方案后台(内部)"
,
"test_menu"
:
"员工流量明细"
,
"test_resource_id"
:
"RO20250604144604751022"
,
"production_app"
:
"代练工会解决方案后台(内部)"
,
"production_menu"
:
"成员流量变动记录"
,
"production_resource_id"
:
"RO20250604144604751022"
}
],
"/orderManagement/myOrder"
:
[
{
"path"
:
"/orderManagement/myOrder"
,
"component"
:
"/pages/orderManagement/myOrder/index"
,
"test_app"
:
"打手应用"
,
"test_menu"
:
"我的订单"
,
"test_resource_id"
:
""
,
"production_app"
:
"打手应用"
,
"production_menu"
:
"我的订单"
,
"production_resource_id"
:
""
}
]
}
}
e2e/utils/account_store.py
View file @
0c2c8d06
...
...
@@ -15,6 +15,17 @@ STORE_PATH = Path("runtime/e2e_accounts.json")
EXCEL_EXPORT_SCRIPT
=
Path
(
"scripts/export_e2e_accounts_excel.py"
)
def
default_store_path
()
->
Path
:
return
STORE_PATH
def
excel_export_path_for
(
store_path
:
Path
)
->
Path
:
suffix
=
""
.
join
(
store_path
.
suffixes
)
if
suffix
.
endswith
(
".json"
):
return
store_path
.
with_suffix
(
".xlsx"
)
return
store_path
.
parent
/
f
"{store_path.name}.xlsx"
@
dataclass
class
E2EAccount
:
phone
:
str
...
...
@@ -63,8 +74,8 @@ class E2EAccount:
class
AccountStore
:
def
__init__
(
self
,
path
:
Path
=
STORE_PATH
):
self
.
path
=
path
def
__init__
(
self
,
path
:
Path
|
None
=
None
):
self
.
path
=
path
or
STORE_PATH
def
load
(
self
)
->
list
[
E2EAccount
]:
if
not
self
.
path
.
exists
():
...
...
@@ -123,7 +134,11 @@ class AccountStore:
accounts
=
self
.
load
()
now
=
datetime
.
now
()
.
isoformat
(
timespec
=
"seconds"
)
for
index
,
existing
in
enumerate
(
accounts
):
if
existing
.
phone
==
account
.
phone
:
if
(
existing
.
phone
==
account
.
phone
and
existing
.
account_type
==
account
.
account_type
and
existing
.
parent_phone
==
account
.
parent_phone
):
merged
=
existing
for
key
,
value
in
asdict
(
account
)
.
items
():
if
value
not
in
(
""
,
"unknown"
,
"not_opened"
,
"not_bound"
):
...
...
@@ -216,6 +231,20 @@ class AccountStore:
return
account
return
None
def
find_main
(
self
,
phone
:
str
)
->
E2EAccount
|
None
:
return
self
.
find
(
phone
=
phone
,
account_type
=
"main"
)
def
find_sub
(
self
,
*
,
phone
:
str
,
parent_phone
:
str
=
""
,
member_role
:
str
=
""
)
->
E2EAccount
|
None
:
for
account
in
reversed
(
self
.
load
()):
if
account
.
account_type
!=
"sub"
or
account
.
phone
!=
phone
:
continue
if
parent_phone
and
account
.
parent_phone
!=
parent_phone
:
continue
if
member_role
and
account
.
member_role
!=
member_role
:
continue
return
account
return
None
def
find_certified_without_wanyouji
(
self
)
->
E2EAccount
|
None
:
return
self
.
find
(
certification_status
=
"approved"
,
wanyouji_status
=
"not_opened"
)
...
...
@@ -230,6 +259,7 @@ class AccountStore:
def
find_opened_without_binding
(
self
)
->
E2EAccount
|
None
:
return
self
.
find
(
account_type
=
"main"
,
certification_status
=
"approved"
,
wanyouji_status
=
"opened"
,
wanzhanggui_binding_status
=
"not_bound"
,
...
...
@@ -237,6 +267,8 @@ class AccountStore:
def
find_wanyouji_without_wanzhanggui_binding
(
self
,
wanzhanggui_phone
:
str
)
->
E2EAccount
|
None
:
for
account
in
reversed
(
self
.
load
()):
if
account
.
account_type
!=
"main"
:
continue
if
account
.
certification_status
!=
"approved"
or
account
.
wanyouji_status
!=
"opened"
:
continue
if
any
(
item
.
get
(
"phone"
)
==
wanzhanggui_phone
for
item
in
account
.
wanzhanggui_bindings
):
...
...
@@ -248,6 +280,8 @@ class AccountStore:
def
find_bound_wanyouji_without_workshop_authorization
(
self
,
wanzhanggui_phone
:
str
)
->
E2EAccount
|
None
:
for
account
in
reversed
(
self
.
load
()):
if
account
.
account_type
!=
"main"
:
continue
if
account
.
certification_status
!=
"approved"
or
account
.
wanyouji_status
!=
"opened"
:
continue
bound
=
account
.
bound_wanzhanggui_enterprise
==
wanzhanggui_phone
or
any
(
...
...
@@ -543,6 +577,7 @@ class AccountStore:
def
find_permission_pending_binding
(
self
)
->
E2EAccount
|
None
:
return
self
.
find
(
account_type
=
"main"
,
certification_status
=
"approved"
,
wanyouji_status
=
"opened"
,
wanzhanggui_binding_status
=
"permission_pending"
,
...
...
e2e/utils/menu_path_mapping.py
0 → 100644
View file @
0c2c8d06
from
__future__
import
annotations
import
json
from
functools
import
lru_cache
from
pathlib
import
Path
MAPPING_PATH
=
Path
(
__file__
)
.
resolve
()
.
parents
[
1
]
/
"resources"
/
"menu_path_mapping.json"
@
lru_cache
(
maxsize
=
1
)
def
menu_path_mapping
()
->
dict
:
if
not
MAPPING_PATH
.
exists
():
return
{
"paths"
:
{}}
return
json
.
loads
(
MAPPING_PATH
.
read_text
(
encoding
=
"utf-8"
))
def
is_production_url
(
url
:
str
)
->
bool
:
return
"shunfju.com"
in
(
url
or
""
)
def
menu_entries_for_path
(
path
:
str
,
*
,
app_hint
:
str
=
""
,
component
:
str
=
""
,
)
->
list
[
dict
[
str
,
str
]]:
entries
=
list
(
menu_path_mapping
()
.
get
(
"paths"
,
{})
.
get
(
path
,
[]))
if
component
:
component_matches
=
[
entry
for
entry
in
entries
if
entry
.
get
(
"component"
)
==
component
]
if
component_matches
:
entries
=
component_matches
if
app_hint
:
app_matches
=
[
entry
for
entry
in
entries
if
app_hint
in
{
entry
.
get
(
"test_app"
,
""
),
entry
.
get
(
"production_app"
,
""
)}
]
if
app_matches
:
entries
=
app_matches
return
entries
def
menu_names_for_path
(
path
:
str
,
*
,
production
:
bool
,
app_hint
:
str
=
""
,
component
:
str
=
""
,
)
->
list
[
str
]:
key
=
"production_menu"
if
production
else
"test_menu"
names
:
list
[
str
]
=
[]
for
entry
in
menu_entries_for_path
(
path
,
app_hint
=
app_hint
,
component
=
component
):
name
=
str
(
entry
.
get
(
key
)
or
""
)
.
strip
()
if
name
and
name
not
in
names
:
names
.
append
(
name
)
return
names
scripts/e2e_automation_core.py
View file @
0c2c8d06
...
...
@@ -10,15 +10,37 @@ from dataclasses import dataclass
from
pathlib
import
Path
from
typing
import
Any
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
1
]))
from
e2e.utils.account_store
import
default_store_path
from
e2e.utils.env_config
import
load_e2e_env
PROJECT_ROOT
=
Path
(
__file__
)
.
resolve
()
.
parents
[
1
]
TESTS_DIR
=
PROJECT_ROOT
/
"e2e"
/
"tests"
RUNTIME_ACCOUNTS
=
PROJECT_ROOT
/
"runtime"
/
"e2e_accounts.json"
BUGS_DIR
=
PROJECT_ROOT
/
"bugs"
ALLURE_RESULTS
=
PROJECT_ROOT
/
"reports"
/
"allure-results"
ALLURE_REPORT
=
PROJECT_ROOT
/
"reports"
/
"allure-report"
load_e2e_env
(
PROJECT_ROOT
)
RUNTIME_ACCOUNTS
=
PROJECT_ROOT
/
default_store_path
()
def
load_dotenv_value
(
key
:
str
)
->
str
:
env_path
=
PROJECT_ROOT
/
".env"
if
not
env_path
.
exists
():
return
""
for
line
in
_read_text
(
env_path
)
.
splitlines
():
stripped
=
line
.
strip
()
if
not
stripped
or
stripped
.
startswith
(
"#"
)
or
"="
not
in
stripped
:
continue
current_key
,
value
=
stripped
.
split
(
"="
,
1
)
if
current_key
==
key
:
return
value
.
strip
()
.
strip
(
"'
\"
"
)
return
""
@
dataclass
(
frozen
=
True
)
class
TestCase
:
path
:
str
...
...
@@ -140,9 +162,18 @@ def allure_summary() -> dict[str, Any]:
def
choose_targets
(
profile
:
str
)
->
list
[
str
]:
profile
=
profile
.
strip
()
no_registration_value
=
os
.
getenv
(
"E2E_PRODUCTION_NO_REGISTRATION"
,
""
)
or
load_dotenv_value
(
"E2E_PRODUCTION_NO_REGISTRATION"
)
no_registration
=
no_registration_value
.
lower
()
in
{
"1"
,
"true"
,
"yes"
,
"on"
}
registration_profiles
=
{
"seed"
,
"saas-001"
,
"main"
}
if
no_registration
and
profile
in
registration_profiles
:
raise
ValueError
(
f
"Production environment does not run registration automation: {profile}"
)
profile_map
=
{
"seed"
:
[
"-m"
,
"e2e_seed"
],
"standalone"
:
[
"-m"
,
"e2e_standalone"
],
"standalone"
:
[
"-m"
,
"e2e_standalone and not e2e_seed and not e2e_saas_001"
],
"login"
:
[
"-m"
,
"e2e_login_only"
],
"chain"
:
[
"-m"
,
"e2e_chain"
],
"main"
:
[
"e2e/tests/组合大流程/test_e2e_main_001_full_closed_loop.py"
],
"main-002"
:
[
"e2e/tests/组合大流程/test_e2e_main_002_order_to_player_settlement.py"
],
...
...
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