Files
order_system/tools/tracking_browser_smoke.py
T

141 lines
7.9 KiB
Python

"""Quick tracking dialogs and responsive cards, using isolated sample orders."""
import json
import os
import sys
import tempfile
import threading
from datetime import date, datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RUN = Path(tempfile.mkdtemp(prefix='tracking-ui-', dir=ROOT/'tools/out'))
os.environ['PO_DATA_DIR'] = str(RUN)
sys.path.insert(0, str(ROOT/'po_app'))
from app import app, Session
from models import ApprovalLog, Order, User
from playwright.sync_api import sync_playwright, expect
from waitress.server import create_server
from werkzeug.security import generate_password_hash
PASSWORD = 'Isolated-Tracking-2026'
with Session() as db:
users = {}
for name, role in [('采购员', 'maker'), ('审批员', 'approver'), ('管理员', 'admin')]:
user = User(username=role, name=name, role=role, active=True,
must_change_password=False, session_version=1,
password_hash=generate_password_hash(PASSWORD))
db.add(user)
db.flush()
users[role] = user.id
samples = [
('博远精密(测试)', 'unpaid', 'pending', 'pending', True),
('华星电子(测试)', 'partial', 'delivered', 'received', False),
('恒达机电(测试)', 'paid', 'partial', 'received', False),
('新力材料(测试)', 'paid', 'delivered', 'received', False),
('中科配件(测试)', 'unknown', 'unknown', 'unknown', False),
('宏光模具(测试)', 'paid', 'delivered', 'pending', False),
]
for i, (supplier, payment, delivery, invoice, prepay) in enumerate(samples, 1):
order = Order(order_no='PO20260917-%03d' % i, supplier_name=supplier,
created_by=users['maker'], created_by_name='采购员',
status='approved', order_date='2026-09-17', total_amount=1200*i,
revision=1, approved_by='审批员', approved_at=datetime.now(),
payment_status=payment, delivery_status=delivery, invoice_status=invoice,
needs_prepayment=prepay, fulfillment_revision=1, payment_revision=1)
if i == 4:
order.fulfillment_revision = 0
db.add(order)
db.commit()
server = create_server(app, host='127.0.0.1', port=0)
threading.Thread(target=server.run, daemon=True).start()
base = 'http://127.0.0.1:%s' % server.effective_port
errors = []
try:
with sync_playwright() as p:
browser = p.chromium.launch(channel='msedge', headless=True)
def login(role):
page = browser.new_page(viewport={'width': 1440, 'height': 1100})
page.on('pageerror', lambda error: errors.append(str(error)))
page.goto(base+'/login')
page.locator('[name=username]').fill(role)
page.locator('[name=password]').fill(PASSWORD)
page.get_by_role('button', name='登 录').click()
page.wait_for_url(base+'/')
return page
maker, approver, admin = [login(role) for role in ('maker', 'approver', 'admin')]
admin.screenshot(path=str(RUN/'desktop.png'), full_page=True)
assert maker.locator('[data-quick-tracking][href$="/payment"]').count() == 0
assert approver.locator('[data-quick-tracking][href$="/follow-up"]').count() == 0
assert admin.locator('.tracking-table tbody tr').first.get_attribute('data-order-id') == '4'
for width in (320, 390, 768, 1024):
approver.set_viewport_size({'width': width, 'height': 844})
for path in ('/', '/orders', '/orders/2/view'):
approver.goto(base+path)
assert approver.evaluate('document.documentElement.scrollWidth <= innerWidth'), (width, path)
if width <= 768 and path != '/orders/2/view':
assert approver.locator('table.order-cards').evaluate('(el) => el.scrollWidth <= el.clientWidth + 1'), (width, path)
approver.set_viewport_size({'width': 390, 'height': 844})
approver.goto(base+'/')
approver.screenshot(path=str(RUN/'mobile.png'), full_page=True)
# Inline validation retains user input; a stale form cannot overwrite a newer record.
approver.locator('tr[data-order-id="2"] [data-quick-tracking]').click()
dialog = approver.locator('#tracking-dialog')
expect(dialog.locator('[name=payment_status]')).to_have_value('partial')
dialog.locator('[name=payment_status]').select_option('unpaid')
dialog.get_by_role('button', name='保存付款状态').click()
expect(dialog.locator('#tracking-dialog-error')).to_contain_text('更正原因')
expect(dialog.locator('[name=payment_status]')).to_have_value('unpaid')
dialog.locator('[name=payment_status]').select_option('paid')
dialog.locator('[name=payment_date]').fill(date.today().isoformat())
dialog.locator('[name=payment_note]').fill('款项已付清')
# Another authorized user changes the same order while this dialog is open.
admin.locator('tr[data-order-id="2"] a[href$="/payment"]').click()
ad = admin.locator('#tracking-dialog')
ad.locator('[name=payment_date]').fill(date.today().isoformat())
ad.locator('[name=payment_note]').fill('另一位经办人已核对首款')
ad.get_by_role('button', name='保存付款状态').click()
expect(ad).not_to_be_visible()
dialog.get_by_role('button', name='保存付款状态').click()
expect(dialog.locator('#tracking-dialog-error')).to_contain_text('已经更新')
expect(dialog.get_by_role('button', name='保存付款状态')).to_be_disabled()
dialog.locator('#tracking-reload').click()
expect(dialog.locator('[name=payment_note]')).to_have_value('另一位经办人已核对首款')
dialog.locator('[name=payment_status]').select_option('paid')
dialog.locator('[name=payment_note]').fill('款项已付清')
approver.screenshot(path=str(RUN/'payment-dialog-mobile.png'))
dialog.get_by_role('button', name='保存付款状态').click()
expect(dialog).not_to_be_visible()
expect(approver.locator('.tracking-table tr[data-order-id="2"]')).to_have_count(0)
assert approver.url == base+'/'
# Maker updates delivery and invoice directly from the order list.
maker.goto(base+'/orders?kw=001')
maker.locator('a[data-quick-tracking]').click()
md = maker.locator('#tracking-dialog')
md.locator('[name=invoice_status]').select_option('received')
md.locator('[name=delivery_status]').select_option('partial')
md.locator('[name=fulfillment_note]').fill('首批货已到,剩余预计周五交齐。')
md.get_by_role('button', name='保存采购跟进').click()
expect(md).not_to_be_visible()
assert maker.url == base+'/orders?kw=001'
expect(maker.locator('[data-progress=delivery] [aria-current=step]')).to_have_text('部分交货')
maker.goto(base+'/orders/1/view')
expect(maker.locator('#order-tracking')).to_contain_text('最近跟进:')
expect(maker.locator('#order-tracking')).to_contain_text('首批货已到,剩余预计周五交齐。')
maker.locator('#order-tracking').screenshot(path=str(RUN/'detail.png'))
# A standard page remains usable when JavaScript is unavailable.
fallback = browser.new_context(java_script_enabled=False)
fallback.add_cookies(maker.context.cookies())
tab = fallback.new_page()
tab.goto(base+'/orders/1/tracking/follow-up')
assert tab.locator('#follow-up-form').count() == 1
assert not errors, errors
browser.close()
with Session() as db:
assert db.get(Order, 2).payment_status == 'paid'
assert db.get(Order, 1).delivery_status == 'partial'
assert db.query(ApprovalLog).filter_by(user_name='采购员', action='更新采购跟进').count() == 1
print(json.dumps({'result': 'PASS', 'output': str(RUN), 'js_errors': errors}, ensure_ascii=False))
finally:
server.close()