Files
miem_workers/tests/browser_council_editor.py
codex 3fb174899d
CI / test (pull_request) Successful in 10m44s
CI / Fingerprint diagnostics (production deployment temporarily disabled) (pull_request) Skipped
fix: hide council editing controls from read-only viewers
2026-11-12 09:45:12 +00:00

149 lines
8.7 KiB
Python

"""Offline editor UX check: python tests/browser_council_editor.py."""
import os
from pathlib import Path
import sys
from urllib.parse import urlsplit
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from fastapi.testclient import TestClient
from playwright.sync_api import expect, sync_playwright
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import StaticPool
from app.config import Settings, get_settings
from app.db import Base, get_db
from app.main import app
from app.models import Employee, EmployeePublication
from app.security import SESSION_COOKIE, sign_session
from app.services import council
def main():
engine = create_engine('sqlite://', connect_args={'check_same_thread': False}, poolclass=StaticPool)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine)
with factory() as db:
person = Employee(profile_key='editor', full_name='Иванов Иван Иванович',
canonical_url='https://www.hse.ru/staff/test',
current_data={'sections': [{'title': 'Учёные степени', 'items': ['Доктор технических наук']}]})
db.add(person)
db.flush()
db.add(EmployeePublication(employee_id=person.id, title='Исследование передачи данных',
year=2024, publication_type='ARTICLE', raw_data={'journal': 'Журнал связи'}, source_hash='editor'))
db.commit()
def database():
with factory() as db:
yield db
settings = Settings(_env_file=None, session_secret='browser-editor-test', admin_username='admin', admin_password='test')
original = council._articles_by_author
council._articles_by_author = lambda: {}
app.dependency_overrides[get_db] = database
app.dependency_overrides[get_settings] = lambda: settings
client = TestClient(app)
output = Path('.impeccable/review/council-editor')
output.mkdir(parents=True, exist_ok=True)
def serve(route):
request = route.request
url = urlsplit(request.url)
response = client.request(request.method, url.path + ('?' + url.query if url.query else ''),
content=request.post_data_buffer,
headers={k: v for k, v in request.headers.items() if k in {'content-type', 'x-council-edit'}})
route.fulfill(status=response.status_code, body=response.content,
headers={k: v for k, v in response.headers.items() if k in {'content-type', 'content-disposition'}})
try:
with sync_playwright() as playwright:
browser = playwright.chromium.launch(executable_path=os.getenv('BROWSER_EXECUTABLE'))
context = browser.new_context(viewport={'width': 1440, 'height': 1000})
context.route('**/*', serve)
page = context.new_page()
errors = []
page.on('pageerror', lambda error: errors.append(str(error)))
page.goto('http://miem.test/admin/council')
page.locator('.council__candidate-summary').click()
expect(page.locator('.council__article strong')).to_have_text('Исследование передачи данных')
expect(page.locator('[data-council-edit]')).to_have_count(0)
expect(page.locator('[data-council-dialog]')).to_have_count(0)
article = page.locator('[data-council-article]')
pencil = article.locator('[data-council-edit]')
client.cookies.set(SESSION_COOKIE, sign_session('admin', settings))
page.reload()
page.locator('.council__candidate-summary').click()
article.focus()
page.keyboard.press('Enter')
expect(pencil).to_be_focused()
page.keyboard.press('Enter')
expect(page.get_by_label('Название статьи', exact=True)).to_have_value('Исследование передачи данных')
page.get_by_label('Название статьи', exact=True).fill('Уточнённое исследование')
page.get_by_label('Название журнала', exact=True).fill('Новый журнал')
page.get_by_role('dialog').get_by_label('Квартиль').select_option('Q1')
page.screenshot(path=str(output / 'edit-desktop.png'))
page.set_viewport_size({'width': 320, 'height': 844})
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth')
assert page.get_by_role('dialog').evaluate('e => e.scrollWidth <= e.clientWidth')
page.screenshot(path=str(output / 'edit-mobile.png'))
page.get_by_role('button', name='Сохранить', exact=True).click()
expect(article.locator('strong')).to_have_text('Уточнённое исследование')
expect(page.locator('.council__candidate')).to_have_attribute('open', '')
with context.expect_page() as opened:
page.get_by_role('link', name='Статьи, журналы и квартили').click()
window = opened.value
window.wait_for_load_state()
window.on('pageerror', lambda error: errors.append(str(error)))
window.get_by_label('ФИО сотрудника').fill('Иванов')
window.get_by_role('searchbox', name='Название статьи', exact=True).fill('уточнённое')
window.get_by_role('searchbox', name='Название журнала', exact=True).fill('новый')
window.get_by_role('button', name='Найти', exact=True).click()
expect(window.locator('[data-council-article]')).to_have_count(1)
for width in (1440, 390, 320):
window.set_viewport_size({'width': width, 'height': 900})
assert window.evaluate('document.documentElement.scrollWidth <= innerWidth')
if width in (1440, 320):
window.screenshot(path=str(output / f'list-{width}.png'), full_page=True)
window.locator('[data-council-article] strong').click()
window.locator('[data-council-edit]').click()
window.get_by_role('dialog').get_by_label('Квартиль').select_option('Q2')
window.locator('[data-edit-specialties] summary').click()
window.get_by_label('Использовать связь из справочников').uncheck()
window.locator('[name="specialty_codes"][value="1.2.2"]').check()
window.get_by_role('button', name='Сохранить', exact=True).click()
expect(window.get_by_text('Журнал: Новый журнал · Квартиль: Q2', exact=False)).to_be_visible()
expect(window.get_by_text('Специальности журнала: 1.2.2 · ручная связь', exact=True)).to_be_visible()
# The original tab now carries a stale revision.
article.locator('strong').click()
pencil.click()
page.get_by_role('button', name='Сохранить', exact=True).click()
expect(page.get_by_role('alert')).to_contain_text('Данные уже изменены')
page.keyboard.press('Escape')
page.reload()
page.locator('.council__candidate-summary').click()
expect(page.get_by_text('Квартиль: Q2', exact=False)).to_be_visible()
page.get_by_label('Отрасль оценки').select_option('физико-математические')
page.get_by_role('button', name='Применить', exact=True).click()
expect(page.get_by_label('Отрасль оценки')).to_have_value('физико-математические')
page.get_by_label('Специальность ВАК').select_option('1.2.2')
page.get_by_role('button', name='Применить', exact=True).click()
expect(page.locator('.council__candidate')).to_have_count(1)
page.locator('.council__candidate-summary').click()
expect(page.get_by_text('Публикаций по выбранной специальности: 1', exact=True)).to_be_visible()
window.get_by_role('searchbox', name='Название статьи', exact=True).fill('Нет совпадений')
window.get_by_role('button', name='Найти', exact=True).click()
expect(window.get_by_text('Статьи не найдены.', exact=False)).to_be_visible()
assert not errors, errors
browser.close()
finally:
council._articles_by_author = original
app.dependency_overrides.clear()
client.close()
engine.dispose()
print(f'Council editor browser checks passed; screenshots: {output}')
if __name__ == '__main__':
main()