"""Read-only UI acceptance; no review, upload, retry or production writes.""" import json from pathlib import Path import urllib.request from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.firefox.options import Options from selenium.webdriver.firefox.service import Service from selenium.webdriver.support.ui import WebDriverWait, Select BASE = 'http://127.0.0.1:8088' ROOT = Path(__file__).resolve().parents[1] items = [] while True: with urllib.request.urlopen(f'{BASE}/api/v1/source-items?limit=100&offset={len(items)}', timeout=10) as response: page = json.load(response) items.extend(page['data']) if len(items) >= page['summary']['total']: break assert page['data'] assert len(items) == len({x['id'] for x in items}) == page['summary']['total'] assert all(x['title'] for x in items) assert not any(x['directory_path'].startswith('/') or '://' in x['directory_path'] for x in items) (ROOT/'tmp/document-ui-catalog.json').write_text(json.dumps(items,ensure_ascii=False)) options = Options() options.add_argument('--headless') options.binary_location = '/snap/firefox/current/usr/lib/firefox/firefox' driver = webdriver.Firefox(options=options, service=Service('/snap/bin/geckodriver')) wait = WebDriverWait(driver,30,poll_frequency=.15) driver.set_window_size(1440,1100) def text(): return driver.find_element(By.TAG_NAME,'body').text def nav(label): driver.find_element(By.XPATH,f"//nav//button[contains(.,'{label}')]").click() def loaded(): wait.until(lambda d: not d.find_element(By.XPATH,"//header//button[contains(.,'刷新')]").get_attribute('disabled')) def root_fits(): assert driver.execute_script('return document.documentElement.scrollWidth <= window.innerWidth'), 'root overflow' try: driver.get(BASE) wait.until(lambda d: d.find_element(By.TAG_NAME,'h1').text == '来源与目录') loaded() assert not driver.find_elements(By.CSS_SELECTOR,'[role="alert"]') assert '未接通目录' in text() assert '只走这 8 步' not in text() expected_external = sum(x['visibility']=='external' for x in items) counts = driver.find_element(By.CSS_SELECTOR,'.document-counts').text assert str(len(items)) in counts and str(expected_external) in counts first = next(x for x in items if x['directory_path'] and x['source_type']=='pp') folder = next(e for e in driver.find_elements(By.CSS_SELECTOR,'.folder.child') if first['directory_path'] in e.text) folder.click() wait.until(lambda d: first['directory_path'] in d.find_element(By.CSS_SELECTOR,'.document-location').text) rows = driver.find_elements(By.CSS_SELECTOR,'.document-table tbody tr') assert rows and all(first['directory_path'] in r.text for r in rows) driver.find_element(By.CSS_SELECTOR,'.folder').click() search = driver.find_element(By.CSS_SELECTOR,'input[type="search"]') search.send_keys(first['title']) wait.until(lambda d: first['title'] in d.find_element(By.CSS_SELECTOR,'.document-table').text) driver.find_element(By.CSS_SELECTOR,'.file-link').click() detail = wait.until(lambda d:d.find_element(By.CSS_SELECTOR,'.document-inspector')) assert detail.is_displayed() and first['title'] in detail.text assert '原文预览尚未接通' in detail.text driver.find_element(By.XPATH,"//button[.='关闭详情']").click() search.clear() search.send_keys(' ') selects = driver.find_elements(By.CSS_SELECTOR,'.document-filters select') Select(selects[0]).select_by_value('internal') wait.until(lambda d: '筛选结果 5 份' in d.find_element(By.CSS_SELECTOR,'.document-location').text) assert len(driver.find_elements(By.CSS_SELECTOR,'.document-table tbody tr')) == 5 Select(selects[0]).select_by_value('') root_fits() assert driver.execute_script('return document.documentElement.scrollHeight < window.innerHeight + 300'), 'document table should scroll inside its panel' driver.get_full_page_screenshot_as_file(str(ROOT/'tmp/DOCUMENT_WORKBENCH_FIREFOX.png')) nav('解析监控') wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='解析监控') loaded() before = driver.execute_script("return performance.getEntriesByType('resource').filter(x=>x.name.includes('/source-items?')).length") WebDriverWait(driver,25).until(lambda d:d.execute_script("return performance.getEntriesByType('resource').filter(x=>x.name.includes('/source-items?')).length") > before) loaded() assert '解析完成不代表已上线' in text() nav('待处理文件') wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='待处理文件') expected_attention = sum(x['content_state']=='over_limit' or x['processing_state'] in ('failed','stalled') for x in items) assert f'筛选结果 {expected_attention} 份' in text() nav('来源连接') wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源连接') assert not driver.find_element(By.CSS_SELECTOR,'.scan-audit-panel').get_attribute('open') nav('发布管理') wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='从审核到安全发布') assert not driver.find_element(By.CSS_SELECTOR,'.release-help').get_attribute('open') assert '只走这 8 步' not in text() assert not driver.find_elements(By.XPATH,"//button[.='授权生产发布']") nav('来源与目录') loaded() for width in (1440,1024,768): driver.set_window_size(width,1000) root_fits() driver.refresh() wait.until(lambda d:d.find_element(By.TAG_NAME,'h1').text=='来源与目录') assert driver.find_element(By.CSS_SELECTOR,'.tab-list').text.count('解析监控')==1 result = {'catalog':len(items),'titles':sum(bool(x['title']) for x in items),'categories':sum(bool(x['directory_path']) for x in items),'external':expected_external,'attention':expected_attention,'search':'pass','directory_filter':'pass','scope_filter':'pass','file_drawer':'pass','automatic_refresh':'pass','tabs_restore':'pass','root_overflow':False,'write_requests':0} (ROOT/'tmp/document-ui-acceptance.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)) print(json.dumps(result,ensure_ascii=False)) finally: driver.quit()