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.
417 lines
19 KiB
417 lines
19 KiB
|
2 days ago
|
# -*- coding: utf-8 -*-
|
||
|
|
r"""票 08:demo/index.html 浏览器级自测(Playwright + chromium headless)。
|
||
|
|
- 零容忍口径:console errors = 0、page errors = 0
|
||
|
|
- 每步截图 shots08/shot-NN-*.png;逐步 PASS/FAIL 记录 → demo-selftest-results.json
|
||
|
|
- 有状态动作选可逆/幂等路径(关注/取关、导入 INSERT 行必 FAILED=D-05 预期);归档/抛公海/交割分配不点(E2E 已覆盖 + D-07 超时 60s)
|
||
|
|
"""
|
||
|
|
import io, sys, os, json
|
||
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||
|
|
from playwright.sync_api import sync_playwright
|
||
|
|
|
||
|
|
DEMO = 'file:///d:/code/crm-backend-matt/.scratch/customer-e2e/demo/index.html'
|
||
|
|
SHOTS = r'd:\code\crm-backend-matt\.scratch\customer-e2e\shots08'
|
||
|
|
XLSX = r'd:\code\crm-backend-matt\.scratch\customer-e2e\_import-heavy.xlsx'
|
||
|
|
ADMIN = '739564171091247104'
|
||
|
|
os.makedirs(SHOTS, exist_ok=True)
|
||
|
|
|
||
|
|
results, console_errors, page_errors = [], [], []
|
||
|
|
_shot = [0]
|
||
|
|
|
||
|
|
def shot(page, name):
|
||
|
|
_shot[0] += 1
|
||
|
|
p = os.path.join(SHOTS, f'shot-{_shot[0]:02d}-{name}.png')
|
||
|
|
page.screenshot(path=p, full_page=True)
|
||
|
|
print(f' [shot] {os.path.basename(p)}')
|
||
|
|
|
||
|
|
def step(name, fn):
|
||
|
|
print(f'== {name}')
|
||
|
|
try:
|
||
|
|
note = fn() or ''
|
||
|
|
results.append({'step': name, 'status': 'PASS', 'note': str(note)})
|
||
|
|
print(f' PASS {note}')
|
||
|
|
except Exception as e:
|
||
|
|
results.append({'step': name, 'status': 'FAIL', 'note': str(e)[:300]})
|
||
|
|
print(f' FAIL {e}')
|
||
|
|
|
||
|
|
def toasts(page):
|
||
|
|
page.wait_for_timeout(500)
|
||
|
|
return [t.inner_text() for t in page.locator('#toast .toast-item').all()]
|
||
|
|
|
||
|
|
def click_text(scope, text, timeout=6000):
|
||
|
|
scope.locator('button', has_text=text).first.click(timeout=timeout)
|
||
|
|
|
||
|
|
def row_count(page, body_id):
|
||
|
|
return page.locator(f'#{body_id} tr').count()
|
||
|
|
|
||
|
|
def sub(page, s):
|
||
|
|
page.locator(f'#tab-detail .subtabs button[data-s={s}]').click()
|
||
|
|
page.wait_for_timeout(700)
|
||
|
|
|
||
|
|
with sync_playwright() as p:
|
||
|
|
browser = p.chromium.launch(headless=True)
|
||
|
|
page = browser.new_page(viewport={'width': 1500, 'height': 950})
|
||
|
|
page.on('console', lambda m: console_errors.append(m.text) if m.type == 'error' else None)
|
||
|
|
page.on('pageerror', lambda e: page_errors.append(str(e)))
|
||
|
|
page.on('dialog', lambda d: d.accept())
|
||
|
|
|
||
|
|
# ---------- 01 打开 + 登录 ----------
|
||
|
|
def s_open():
|
||
|
|
page.goto(DEMO)
|
||
|
|
page.wait_for_load_state('networkidle')
|
||
|
|
assert page.locator('nav.tabs button').count() == 5, '应有 5 个主 tab'
|
||
|
|
shot(page, 'init')
|
||
|
|
return 'tabs=5'
|
||
|
|
step('01 打开 file:// 页面', s_open)
|
||
|
|
|
||
|
|
def s_login():
|
||
|
|
page.fill('#uidInput', ADMIN)
|
||
|
|
page.evaluate('loginByUid()')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
who = page.locator('#whoBox').inner_text()
|
||
|
|
assert '未登录' not in who, f'登录失败 whoBox={who}'
|
||
|
|
n = row_count(page, 'listBody')
|
||
|
|
assert n > 0, '登录后列表应加载数据'
|
||
|
|
shot(page, 'login-mine-list')
|
||
|
|
return f'who={who[:20]} mine rows={n}'
|
||
|
|
step('02 debug token 登录 + 我的客户列表', s_login)
|
||
|
|
|
||
|
|
def s_overview_pool():
|
||
|
|
page.locator('#tab-list .viewbar [data-ws=overview]').click()
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
ov = row_count(page, 'listBody')
|
||
|
|
shot(page, 'overview')
|
||
|
|
page.locator('#tab-list .viewbar [data-ws=pool]').click()
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
pl = row_count(page, 'listBody')
|
||
|
|
info = page.locator('#pageInfo').inner_text()
|
||
|
|
shot(page, 'pool')
|
||
|
|
return f'overview rows={ov} pool rows={pl} info={info}'
|
||
|
|
step('03 三 workspace 切换', s_overview_pool)
|
||
|
|
|
||
|
|
def s_search():
|
||
|
|
page.locator('#tab-list .viewbar [data-ws=mine]').click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
page.fill('#fKeyword', '恒信达')
|
||
|
|
click_text(page.locator('#tab-list .viewbar'), '查询')
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
txt = page.locator('#listBody').inner_text()
|
||
|
|
assert '恒信达' in txt, '检索结果应含恒信达'
|
||
|
|
shot(page, 'search')
|
||
|
|
return 'keyword=恒信达 命中'
|
||
|
|
step('04 列表关键字检索', s_search)
|
||
|
|
|
||
|
|
def s_sumcards():
|
||
|
|
txt = page.locator('#summaryBox').inner_text()
|
||
|
|
assert txt.strip() and '随列表加载' not in txt, '汇总卡应有数据'
|
||
|
|
return txt.replace('\n', ' ')[:60]
|
||
|
|
step('05 汇总卡(board/summary)渲染', s_sumcards)
|
||
|
|
|
||
|
|
def s_batch_focus():
|
||
|
|
page.locator('#listBody tr input[type=checkbox]').first.check()
|
||
|
|
hint = page.locator('#pickHint').inner_text()
|
||
|
|
assert '已勾选 1' in hint, f'勾选提示异常: {hint}'
|
||
|
|
click_text(page.locator('#tab-list'), '批量关注')
|
||
|
|
ts = toasts(page)
|
||
|
|
shot(page, 'batch-focus')
|
||
|
|
assert any('成功' in t or '关注' in t for t in ts), f'批量关注 toast 异常: {ts}'
|
||
|
|
return f'{hint}; toast={ts[-1][:40]}'
|
||
|
|
step('06 勾选 + 批量关注', s_batch_focus)
|
||
|
|
|
||
|
|
# ---------- 详情 6 子页签 ----------
|
||
|
|
def s_open_detail():
|
||
|
|
page.locator('#listBody tr td.row').first.click()
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
assert page.locator('#detailPanel').is_visible(), '详情面板应可见'
|
||
|
|
head = page.locator('#headBox').inner_text()
|
||
|
|
assert '负责人' in head, '公共头部应含负责人'
|
||
|
|
shot(page, 'detail-info')
|
||
|
|
return head.split('\n')[0][:50]
|
||
|
|
step('07 打开详情(detail-head 公共头部)', s_open_detail)
|
||
|
|
|
||
|
|
for tab, kw in [('info', '客户编号'), ('contacts', '新建联系人'), ('follow', '跟进'), ('opps', None), ('members', None), ('oplog', None)]:
|
||
|
|
def f(tab=tab, kw=kw):
|
||
|
|
sub(page, tab)
|
||
|
|
txt = page.locator('#subBox').inner_text()
|
||
|
|
assert txt.strip(), f'页签 {tab} 渲染为空'
|
||
|
|
assert '加载中' not in txt, f'页签 {tab} 停在加载中'
|
||
|
|
if kw:
|
||
|
|
assert kw in txt, f'页签 {tab} 应含「{kw}」'
|
||
|
|
shot(page, f'detail-{tab}')
|
||
|
|
return f'{tab} 渲染 OK'
|
||
|
|
step(f'08 子页签 {tab}', f)
|
||
|
|
|
||
|
|
def s_follow_write():
|
||
|
|
sub(page, 'follow')
|
||
|
|
before = page.locator('#subBox .panel').count()
|
||
|
|
click_text(page.locator('#headBox'), '写跟进')
|
||
|
|
page.fill('#fContent', 'demo 自测跟进:浏览器级自测写入(票 08)')
|
||
|
|
click_text(page.locator('#dlgFoot'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
ts = [t for t in toasts(page)]
|
||
|
|
after = page.locator('#subBox .panel').count()
|
||
|
|
shot(page, 'follow-written')
|
||
|
|
assert '跟进已保存' in ''.join(ts), f'写跟进 toast 异常: {ts}'
|
||
|
|
assert after >= before, '跟进条数应不减'
|
||
|
|
return f'panels {before}->{after}'
|
||
|
|
step('09 写跟进(follow_way_01 契约值)', s_follow_write)
|
||
|
|
|
||
|
|
C_NAME = 'demo-selftest-联系人甲'
|
||
|
|
def contact_rows(): # 数据行=含编辑按钮的行;空态行(无数据 td.empty)无按钮,排除
|
||
|
|
return [r for r in page.locator('#subBox tbody tr').all() if '编辑' in r.inner_text()]
|
||
|
|
def s_contact_create():
|
||
|
|
sub(page, 'contacts')
|
||
|
|
before = len(contact_rows())
|
||
|
|
click_text(page.locator('#headBox'), '+联系人')
|
||
|
|
page.fill('#ctName', C_NAME)
|
||
|
|
page.fill('#ctJob', '采购经理')
|
||
|
|
page.fill('#ctPhone', '13800001111')
|
||
|
|
page.select_option('#ctKey', '1')
|
||
|
|
click_text(page.locator('#dlgFoot'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
ts = toasts(page)
|
||
|
|
after = len(contact_rows())
|
||
|
|
shot(page, 'contact-created')
|
||
|
|
assert '联系人已保存' in ''.join(ts), f'新建联系人 toast 异常: {ts}'
|
||
|
|
assert after == before + 1, f'新建后数据行应+1: {before}->{after}'
|
||
|
|
return f'rows {before}->{after}(含 name/jobTitleName/phone/isKeyContact 全契约字段)'
|
||
|
|
step('10 新建联系人(修复后字段)', s_contact_create)
|
||
|
|
|
||
|
|
def s_contact_edit():
|
||
|
|
row = page.locator('#subBox tbody tr', has_text=C_NAME).first
|
||
|
|
row.locator('button', has_text='编辑').click()
|
||
|
|
page.wait_for_timeout(400)
|
||
|
|
assert page.input_value('#ctName') == C_NAME, '编辑弹窗应回显姓名(c.name 契约)'
|
||
|
|
assert page.input_value('#ctJob') == '采购经理', '编辑弹窗应回显职务(jobTitleName)'
|
||
|
|
page.fill('#ctGift', 'demo 自测礼品备注')
|
||
|
|
click_text(page.locator('#dlgFoot'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
ts = toasts(page)
|
||
|
|
shot(page, 'contact-edited')
|
||
|
|
assert '联系人已保存' in ''.join(ts), f'编辑联系人 toast 异常: {ts}'
|
||
|
|
return 'PUT /{id} 修复路径回显+保存 OK'
|
||
|
|
step('11 编辑联系人(PUT /{id} 修复验证)', s_contact_edit)
|
||
|
|
|
||
|
|
def s_contact_delete():
|
||
|
|
before = len(contact_rows())
|
||
|
|
row = page.locator('#subBox tbody tr', has_text=C_NAME).first
|
||
|
|
row.locator('button', has_text='删除').click()
|
||
|
|
after = before
|
||
|
|
for _ in range(10): # DELETE 成功后 switchSub 异步重载,轮询等待数据行下降
|
||
|
|
page.wait_for_timeout(400)
|
||
|
|
after = len(contact_rows())
|
||
|
|
if after < before:
|
||
|
|
break
|
||
|
|
ts = toasts(page)
|
||
|
|
shot(page, 'contact-deleted')
|
||
|
|
assert after == before - 1, f'删除后数据行应-1: {before}->{after}, toasts={ts}'
|
||
|
|
return f'rows {before}->{after}(DELETE /{id} 修复验证)'
|
||
|
|
step('12 删除联系人(DELETE /{id} 修复验证)', s_contact_delete)
|
||
|
|
|
||
|
|
# ---------- 新建客户三层查重两路 ----------
|
||
|
|
def s_need_confirm():
|
||
|
|
click_text(page.locator('header'), '新建客户')
|
||
|
|
page.fill('#cName', 'e2c-恒信达科技有限公司')
|
||
|
|
page.fill('#cType', 'customer_type_01')
|
||
|
|
page.fill('#cProv', '440000'); page.fill('#cCity', '440100')
|
||
|
|
page.fill('#cInd', 'gov')
|
||
|
|
click_text(page.locator('#dlgFoot'), '创建')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
out = page.locator('#cOut').inner_text()
|
||
|
|
assert '相似命中' in out, f'L2 相似 needConfirm 未触发: {out[:120]}'
|
||
|
|
shot(page, 'create-needconfirm')
|
||
|
|
page.evaluate('dlgClose()')
|
||
|
|
return out.split('\n')[0][:60]
|
||
|
|
step('13 新建客户 L2 相似 → needConfirm 弹出', s_need_confirm)
|
||
|
|
|
||
|
|
def s_credit_block():
|
||
|
|
click_text(page.locator('header'), '新建客户')
|
||
|
|
page.fill('#cName', 'demo-selftest-撞码客户')
|
||
|
|
page.fill('#cType', 'customer_type_01')
|
||
|
|
page.fill('#cProv', '440000'); page.fill('#cCity', '440100')
|
||
|
|
page.fill('#cInd', 'gov')
|
||
|
|
page.fill('#cCredit', '91440101E2CHXDA001') # seed simA 真实信用代码,才能触发 L3 67003
|
||
|
|
click_text(page.locator('#dlgFoot'), '创建')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
out = page.locator('#cOut').inner_text()
|
||
|
|
assert '67003' in out, f'L3 撞码 67003 未拦截(unifiedCreditCode 修复后应生效): {out[:120]}'
|
||
|
|
shot(page, 'create-credit-block')
|
||
|
|
page.evaluate('dlgClose()')
|
||
|
|
return '67003 硬拦展示 OK'
|
||
|
|
step('14 新建客户 L3 信用代码撞码 67003', s_credit_block)
|
||
|
|
|
||
|
|
def s_focus_star():
|
||
|
|
page.locator('#headBox button', has_text='关注').first.click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
t1 = ''.join(toasts(page))
|
||
|
|
page.locator('#headBox button', has_text='取关').first.click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
page.locator('#headBox button', has_text='重点客户').first.click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
page.locator('#headBox button', has_text='取消重点').first.click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
shot(page, 'focus-star')
|
||
|
|
assert '404' not in t1, f'关注请求 404(oneBy URL 形态修复后不应发生): {t1[:80]}'
|
||
|
|
return f'四连击完成(首条 toast: {t1[:40]})'
|
||
|
|
step('15 归属动作:关注/取关/重点/取消重点', s_focus_star)
|
||
|
|
|
||
|
|
# ---------- 交割 ----------
|
||
|
|
def s_transfer_list():
|
||
|
|
page.locator('nav.tabs button[data-tab=transfer]').click()
|
||
|
|
page.wait_for_timeout(1000)
|
||
|
|
txt = page.locator('#trBody').inner_text()
|
||
|
|
assert '暂无交接单' not in txt, f'交接单列表空态(tab 按钮 transferPage(1) 翻页缺陷修复后应显示第 1 页): {txt[:40]}'
|
||
|
|
n = row_count(page, 'trBody')
|
||
|
|
shot(page, 'transfer-list')
|
||
|
|
return f'trBody rows={n}'
|
||
|
|
step('16 交割列表 + 预览', s_transfer_list)
|
||
|
|
|
||
|
|
def s_transfer_preview():
|
||
|
|
click_text(page.locator('#tab-transfer'), '预览名下待交接客户')
|
||
|
|
page.wait_for_timeout(1000)
|
||
|
|
txt = page.locator('#tab-transfer').inner_text()
|
||
|
|
shot(page, 'transfer-preview')
|
||
|
|
assert '待交接' in txt or '客户' in txt, '预览应输出名下客户'
|
||
|
|
page.evaluate('dlgClose()') # 预览是 dlgOpen 弹窗,不关会拦截后续所有点击(第一轮级联失败根因)
|
||
|
|
return 'preview OK'
|
||
|
|
step('17 交割发起前预览', s_transfer_preview)
|
||
|
|
|
||
|
|
def s_transfer_detail():
|
||
|
|
row = page.locator('#trBody tr').first
|
||
|
|
row.locator('button', has_text='详情').click()
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
assert page.locator('#trDetailPanel').is_visible(), '交接单详情面板应可见'
|
||
|
|
txt = page.locator('#trDetailPanel').inner_text()
|
||
|
|
shot(page, 'transfer-detail')
|
||
|
|
return txt.split('\n')[0][:50]
|
||
|
|
step('18 交接单详情(GET /transfer/{id})', s_transfer_detail)
|
||
|
|
|
||
|
|
# ---------- 导入全链(D-05/D-04 展示) ----------
|
||
|
|
def s_import_list():
|
||
|
|
page.locator('nav.tabs button[data-tab=import]').click()
|
||
|
|
page.wait_for_timeout(1000)
|
||
|
|
n = row_count(page, 'impBody')
|
||
|
|
assert n > 0, '导入任务列表应渲染'
|
||
|
|
shot(page, 'import-list')
|
||
|
|
return f'impBody rows={n}'
|
||
|
|
step('19 导入任务列表渲染', s_import_list)
|
||
|
|
|
||
|
|
def s_import_upload():
|
||
|
|
page.set_input_files('#impFile', XLSX)
|
||
|
|
page.select_option('#impMode', 'APPEND_ONLY')
|
||
|
|
click_text(page.locator('#tab-import'), '上传预检')
|
||
|
|
page.wait_for_timeout(2500)
|
||
|
|
ts = ''.join(toasts(page))
|
||
|
|
first = page.locator('#impBody tr').first.inner_text()
|
||
|
|
shot(page, 'import-upload')
|
||
|
|
assert ('预检' in ts or '任务' in ts or 'DRAFT' in ts or '待确认' in first), f'上传预检反馈异常: {ts[:80]} | {first[:60]}'
|
||
|
|
return f'toast={ts[:50]}'
|
||
|
|
step('20 导入上传预检(DRAFT 两段式)', s_import_upload)
|
||
|
|
|
||
|
|
def s_import_confirm():
|
||
|
|
first = page.locator('#impBody tr').first
|
||
|
|
if first.locator('button', has_text='确认执行').count() == 0:
|
||
|
|
return 'SKIP:首行非待确认态'
|
||
|
|
first.locator('button', has_text='确认执行').click()
|
||
|
|
page.wait_for_timeout(3500) # confirm + 1.5s 自动刷新 + 异步执行
|
||
|
|
first = page.locator('#impBody tr').first
|
||
|
|
txt = first.inner_text()
|
||
|
|
shot(page, 'import-result')
|
||
|
|
assert ('DONE' in txt) or ('FAILED' in txt) or ('RUNNING' in txt), f'任务终态未出现(IMP_ST 英文文案): {txt[:80]}'
|
||
|
|
return '首行状态: ' + ' '.join(txt.split())[:50]
|
||
|
|
step('21 确认执行导入(D-05:INSERT 行必 FAILED)', s_import_confirm)
|
||
|
|
|
||
|
|
def s_import_failures():
|
||
|
|
first = page.locator('#impBody tr').first
|
||
|
|
btn = first.locator('button', has_text='失败明细')
|
||
|
|
if btn.count() == 0:
|
||
|
|
first.locator('button', has_text='结果').click()
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
page.evaluate('dlgClose()')
|
||
|
|
return 'SKIP:首行无失败明细入口'
|
||
|
|
btn.click()
|
||
|
|
page.wait_for_timeout(1000)
|
||
|
|
txt = page.locator('#dlgBody').inner_text()
|
||
|
|
shot(page, 'import-failures')
|
||
|
|
page.evaluate('dlgClose()')
|
||
|
|
return ('明细弹窗: ' + ' '.join(txt.split())[:70]) if txt.strip() else '空'
|
||
|
|
step('22 失败明细弹窗(D-04:0 行提示)', s_import_failures)
|
||
|
|
|
||
|
|
# ---------- 设置:提醒规则 + 查重试玩 ----------
|
||
|
|
def s_reminder_rw():
|
||
|
|
page.locator('nav.tabs button[data-tab=settings]').click()
|
||
|
|
page.wait_for_timeout(1000)
|
||
|
|
orig_days = page.input_value('#rFirstDays')
|
||
|
|
page.fill('#rFirstDays', '31')
|
||
|
|
click_text(page.locator('#tab-settings'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
ts1 = ''.join(toasts(page))
|
||
|
|
click_text(page.locator('#tab-settings'), '刷新')
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
now_days = page.input_value('#rFirstDays')
|
||
|
|
shot(page, 'reminder-saved')
|
||
|
|
assert now_days == '31', f'保存 31 后回读应 31: {now_days}'
|
||
|
|
return f'orig={orig_days} -> 31 保存回读 OK({ts1[:30]})'
|
||
|
|
step('23 提醒规则读改写(PUT JSON 全量覆盖)', s_reminder_rw)
|
||
|
|
|
||
|
|
def s_reminder_64023():
|
||
|
|
page.fill('#rFirstDays', '0')
|
||
|
|
click_text(page.locator('#tab-settings'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
ts = ''.join(toasts(page))
|
||
|
|
shot(page, 'reminder-64023')
|
||
|
|
assert '64023' in ts, f'64023 错误提示未出现: {ts[:80]}'
|
||
|
|
click_text(page.locator('#tab-settings'), '刷新')
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
return '64023 提示 OK'
|
||
|
|
step('24 提醒规则 64023 负路径(天数非正)', s_reminder_64023)
|
||
|
|
|
||
|
|
def s_reminder_restore():
|
||
|
|
page.fill('#rFirstDays', '30')
|
||
|
|
click_text(page.locator('#tab-settings'), '保存')
|
||
|
|
page.wait_for_timeout(1200)
|
||
|
|
click_text(page.locator('#tab-settings'), '刷新')
|
||
|
|
page.wait_for_timeout(800)
|
||
|
|
v = page.input_value('#rFirstDays')
|
||
|
|
return f'还原 firstTriggerDays=30(回读 {v})'
|
||
|
|
step('25 提醒规则还原基线', s_reminder_restore)
|
||
|
|
|
||
|
|
def s_checks():
|
||
|
|
page.fill('#ckName', '恒信达')
|
||
|
|
page.locator('#tab-settings button', has_text='check-name').click()
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
o1 = page.locator('#ckOut').inner_text()
|
||
|
|
page.fill('#ckCredit', '91440101MA9ABC1234')
|
||
|
|
page.locator('#tab-settings button', has_text='check-credit-code').click()
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
o2 = page.locator('#ckOut').inner_text()
|
||
|
|
page.fill('#ckPhone', '13812340001')
|
||
|
|
page.locator('#tab-settings button', has_text='check-phone').click()
|
||
|
|
page.wait_for_timeout(900)
|
||
|
|
o3 = page.locator('#ckOut').inner_text()
|
||
|
|
shot(page, 'checks')
|
||
|
|
assert '命中' in o1 or 'hit' in o1.lower() or '[' in o1, f'check-name 输出异常: {o1[:60]}'
|
||
|
|
return 'check-name / check-credit-code / check-phone 三连 OK'
|
||
|
|
step('26 查重试玩三端点', s_checks)
|
||
|
|
|
||
|
|
browser.close()
|
||
|
|
|
||
|
|
# ---------- 汇总 ----------
|
||
|
|
passed = [r for r in results if r['status'] == 'PASS']
|
||
|
|
failed = [r for r in results if r['status'] == 'FAIL']
|
||
|
|
print('\n================ 自测汇总 ================')
|
||
|
|
for r in results:
|
||
|
|
print(f" {r['status']:4} {r['step']} {r['note'][:80]}")
|
||
|
|
print(f'\nsteps: {len(passed)} PASS / {len(failed)} FAIL / 截图 {_shot[0]}')
|
||
|
|
print(f'console errors: {len(console_errors)}')
|
||
|
|
for e in console_errors:
|
||
|
|
print(' CE:', e[:160])
|
||
|
|
print(f'page errors: {len(page_errors)}')
|
||
|
|
for e in page_errors:
|
||
|
|
print(' PE:', e[:160])
|
||
|
|
|
||
|
|
json.dump({'results': results, 'console_errors': console_errors, 'page_errors': page_errors,
|
||
|
|
'shots': _shot[0]},
|
||
|
|
open(os.path.join(os.path.dirname(SHOTS), 'demo-selftest-results.json'), 'w', encoding='utf-8'),
|
||
|
|
ensure_ascii=False, indent=2)
|
||
|
|
print('results json written.')
|