You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
10 KiB
169 lines
10 KiB
# -*- coding: utf-8 -*-
|
|
"""probe_v3proto.py — v3 原型(demo/v3-prototype.html)Playwright 冒烟
|
|
验证口径:默认值入分组列表(最低级守卫/唯一换设/默认值列)+ 键值字段删除(列表/弹窗/载荷无 value、无 isDefault)。
|
|
"""
|
|
import json, pathlib, sys, io
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
HERE = pathlib.Path(__file__).parent
|
|
PROTO = (HERE / 'demo' / 'v3-prototype.html').resolve().as_uri()
|
|
SHOTS = HERE / 'shots'
|
|
SHOTS.mkdir(exist_ok=True)
|
|
|
|
results, console_errors, page_errors = [], [], []
|
|
|
|
def check(name, cond, detail=''):
|
|
results.append({'name': name, 'pass': bool(cond), 'detail': detail})
|
|
print(('PASS ' if cond else 'FAIL ') + name + ((' | ' + detail) if detail and not cond else ''))
|
|
|
|
with sync_playwright() as p:
|
|
b = p.chromium.launch(headless=True)
|
|
pg = b.new_page()
|
|
pg.on('console', lambda m: console_errors.append(m.text) if m.type == 'error' else None)
|
|
pg.on('pageerror', lambda e: page_errors.append(str(e)))
|
|
pg.goto(PROTO)
|
|
pg.wait_for_selector('#groupBody tr')
|
|
|
|
# 1 分组列表默认值列
|
|
headers = [t.inner_text() for t in pg.query_selector_all('#tab-group th')]
|
|
check('分组列表含「默认值」列', any('默认值' in h for h in headers), str(headers))
|
|
|
|
# 2 两级组默认值回显 父名/子名
|
|
row902 = pg.query_selector('tr[data-gid="902"]')
|
|
check('行业组默认值回显「信息技术 / 软件开发」',
|
|
'信息技术 / 软件开发' in row902.query_selector('.cell-default').inner_text())
|
|
|
|
# 3 两级组设默认弹窗:只列二级项(7 个),一级项不可选
|
|
row902.query_selector('text=设置默认值').click()
|
|
pg.wait_for_selector('#dlgDefaultList input[name=defpick]')
|
|
radios = pg.query_selector_all('#dlgDefaultList input[name=defpick]')
|
|
names = [r.evaluate('el => el.closest("label").querySelector("span").textContent') for r in radios]
|
|
lv1 = {'信息技术', '制造业', '金融保险'}
|
|
check('两级组弹窗只列二级项(7 个)', len(radios) == 7, f'{len(radios)} {names}')
|
|
check('一级项不在可选项中', not (set(names) & lv1), str(names))
|
|
check('停用二级项也在列(管理口径)', any(r.is_checked() for r in radios))
|
|
check('当前默认标记唯一', len(pg.query_selector_all('#dlgDefaultList .cur')) == 1)
|
|
pg.screenshot(path=str(SHOTS / 'v3-1-default-dlg-two-level.png'))
|
|
pg.click('#dlgDefault .dfoot button:first-child') # 取消
|
|
|
|
# 4 一级组弹窗:只列一级项(4 个)
|
|
pg.query_selector('tr[data-gid="901"] button.check-default, tr[data-gid="901"] td.ops button:first-child').click()
|
|
pg.wait_for_selector('#dlgDefaultList input[name=defpick]')
|
|
radios1 = pg.query_selector_all('#dlgDefaultList input[name=defpick]')
|
|
names1 = [r.evaluate('el => el.closest("label").querySelector("span").textContent') for r in radios1]
|
|
check('一级组弹窗只列一级项(4 个)', len(radios1) == 4 and '广告' in names1, f'{names1}')
|
|
pg.click('#dlgDefault .dfoot button:first-child')
|
|
|
|
# 5 未设置组(商机阶段)设默认 → 回显 + 载荷
|
|
cell903 = pg.query_selector('tr[data-gid="903"] .cell-default')
|
|
check('未设置组显示「未设置」', '未设置' in cell903.inner_text())
|
|
pg.query_selector('tr[data-gid="903"] td.ops button:first-child').click()
|
|
pg.wait_for_selector('#dlgDefaultList input[name=defpick]')
|
|
pg.query_selector('#dlgDefaultList input[name=defpick][value="932"]').check()
|
|
pg.click('#btnDefaultOk')
|
|
pg.wait_for_timeout(200)
|
|
check('设默认后回显「方案报价」', '方案报价' in pg.query_selector('tr[data-gid="903"] .cell-default').inner_text())
|
|
payload = pg.query_selector('#payloadBox').inner_text()
|
|
check('set-default 载荷 groupId+itemId', 'groupId=903' in payload and 'itemId=932' in payload, payload[:120])
|
|
|
|
# 6 换设唯一:客户来源 广告 → 转介绍,旧默认取消
|
|
pg.query_selector('tr[data-gid="901"] td.ops button:first-child').click()
|
|
pg.wait_for_selector('#dlgDefaultList input[name=defpick][value="912"]')
|
|
pg.query_selector('#dlgDefaultList input[name=defpick][value="912"]').check()
|
|
pg.click('#btnDefaultOk')
|
|
pg.wait_for_timeout(200)
|
|
check('换设后默认值列更新为「转介绍」', '转介绍' in pg.query_selector('tr[data-gid="901"] .cell-default').inner_text())
|
|
pg.query_selector('tr[data-gid="901"] td.ops button:first-child').click()
|
|
pg.wait_for_selector('#dlgDefaultList input[name=defpick]')
|
|
check('重开弹窗「当前默认」标记唯一且在新项', len(pg.query_selector_all('#dlgDefaultList .cur')) == 1
|
|
and '转介绍' in pg.query_selector('#dlgDefaultList .cur').evaluate('el => el.closest("label").textContent'))
|
|
pg.screenshot(path=str(SHOTS / 'v3-2-default-switched.png'))
|
|
pg.click('#dlgDefault .dfoot button:first-child')
|
|
|
|
# 7 切字典项 Tab:表头无键值
|
|
pg.click('#tabbtnItem')
|
|
pg.wait_for_selector('#itemBody tr')
|
|
item_headers = [t.inner_text() for t in pg.query_selector_all('#tab-item th')]
|
|
all_th = item_headers + headers
|
|
check('两个列表表头均无「键值」', not any('键值' in h for h in all_th), str(all_th))
|
|
|
|
# 8 行业展开:子项行 + ★分组默认在二级项上
|
|
pg.query_selector('#fiGroup').select_option('902')
|
|
pg.wait_for_timeout(100)
|
|
pg.query_selector('#itemBody .expander[data-exp="921"]').click()
|
|
pg.wait_for_timeout(100)
|
|
body_txt = pg.query_selector('#itemBody').inner_text()
|
|
check('展开二级行可见「软件开发」', '软件开发' in body_txt)
|
|
check('★分组默认标记在二级项(软件开发)', '软件开发' in body_txt and '★分组默认' in body_txt)
|
|
pg.screenshot(path=str(SHOTS / 'v3-3-item-list-v3.png'))
|
|
|
|
# 9 新增弹窗:无键值输入、无默认勾选(结构化检查真实控件,说明文字除外)
|
|
pg.click('#btnAddItem')
|
|
pg.wait_for_selector('#dlgItem[open]')
|
|
dlg_labels = [b.inner_text() for b in pg.query_selector_all('#dlgItem .dbody > label b')]
|
|
check('新增弹窗表单标签无「字典键值」', not any('键值' in t for t in dlg_labels), str(dlg_labels))
|
|
check('新增弹窗无勾选框(默认勾选已删)', len(pg.query_selector_all('#dlgItem input[type=checkbox]')) == 0)
|
|
check('新增弹窗表单标签齐全(分组/名称/编码/排序/状态/描述)',
|
|
{'所属分组', '字典名称', '字典编码', '排序', '状态', '描述'} <= set(dlg_labels), str(dlg_labels))
|
|
st_sel = pg.query_selector('#fiStatus')
|
|
check('新增弹窗状态下拉默认「启用」', st_sel is not None and st_sel.input_value() == '1')
|
|
|
|
# 10 子项行含编码列;保存载荷无 value / 无 isDefault
|
|
kid_headers = [t.inner_text() for t in pg.query_selector_all('#dlgItem .kids th')]
|
|
check('子项行表头含「子项编码」', any('编码' in h for h in kid_headers), str(kid_headers))
|
|
pg.fill('#fiName', '客户规模')
|
|
pg.click('#btnAddKid')
|
|
kid_inputs = pg.query_selector_all('#kidBody input')
|
|
check('新增子项行编码可填(不填自动生成)',
|
|
kid_inputs[1].get_attribute('readonly') is None and '自动生成' in kid_inputs[1].get_attribute('placeholder'))
|
|
kid_inputs[0].fill('大型企业')
|
|
kid_inputs[1].fill('dict_test01')
|
|
pg.click('#btnItemOk')
|
|
pg.wait_for_timeout(200)
|
|
payload2 = pg.query_selector('#payloadBox').inner_text()
|
|
form_part = payload2.split('\n#')[0] # 只看表单载荷部分,排除 mock 说明注释
|
|
check('saveOrUpdate 载荷含 children[0].name', 'children[0].name=' in form_part, payload2[:200])
|
|
check('saveOrUpdate 载荷含 children[0].code(自填)', 'children[0].code=dict_test01' in form_part, form_part)
|
|
check('saveOrUpdate 载荷含 status=1(新增默认启用)', 'status=1' in form_part, form_part)
|
|
check('saveOrUpdate 载荷无 value / 无 isDefault', 'value=' not in form_part and 'isDefault' not in form_part, form_part)
|
|
pg.screenshot(path=str(SHOTS / 'v3-4-payload-v3.png'))
|
|
|
|
# 11 编辑态:既有子项编码只读(创建后不可改)
|
|
pg.query_selector('#itemBody td.ops button').click()
|
|
pg.wait_for_selector('#dlgItem[open]')
|
|
kid_codes = pg.query_selector_all('#kidBody input.kid-code')
|
|
check('编辑弹窗回显既有子项行(3 行)', len(kid_codes) == 3, str(len(kid_codes)))
|
|
check('既有子项编码只读(创建后不可改)', all(c.get_attribute('readonly') is not None for c in kid_codes))
|
|
check('编辑态状态回显(信息技术=启用)', pg.query_selector('#fiStatus').input_value() == '1')
|
|
pg.screenshot(path=str(SHOTS / 'v3-5-edit-kid-code-readonly.png'))
|
|
pg.click('#dlgItem .dfoot button:first-child')
|
|
|
|
# 12 默认项禁停守卫(mock):编辑当前默认项(转介绍,第 6 步已换设)改停用 → 63013
|
|
pg.query_selector('#fiGroup').select_option('901')
|
|
pg.wait_for_timeout(100)
|
|
pg.query_selector('tr[data-iid="912"] td.ops button').click() # 转介绍=客户来源当前默认
|
|
pg.wait_for_selector('#dlgItem[open]')
|
|
check('编辑态状态回显(转介绍=启用)', pg.query_selector('#fiStatus').input_value() == '1')
|
|
pg.select_option('#fiStatus', '0')
|
|
pg.click('#btnItemOk')
|
|
pg.wait_for_timeout(100)
|
|
err_txt = pg.query_selector('#fiErr').inner_text()
|
|
check('默认项改停用被 63013 拦(mock)', pg.query_selector('#fiErr').get_attribute('hidden') is None and '63013' in err_txt, err_txt)
|
|
check('拦截后弹窗未关闭', pg.query_selector('#dlgItem[open]') is not None)
|
|
pg.screenshot(path=str(SHOTS / 'v3-6-default-disable-guard.png'))
|
|
pg.click('#dlgItem .dfoot button:first-child')
|
|
|
|
b.close()
|
|
|
|
summary = {
|
|
'total': len(results),
|
|
'passed': sum(1 for r in results if r['pass']),
|
|
'console_errors': console_errors,
|
|
'page_errors': page_errors,
|
|
'ALL_PASS': all(r['pass'] for r in results) and not console_errors and not page_errors,
|
|
'results': results,
|
|
}
|
|
(HERE / 'v3-proto-result.json').write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
print(f"\n{summary['passed']}/{summary['total']} PASS, console_errors={len(console_errors)}, page_errors={len(page_errors)}, ALL_PASS={summary['ALL_PASS']}")
|
|
sys.exit(0 if summary['ALL_PASS'] else 1)
|
|
|