#!/usr/bin/env python3 """Досъёмка экранов React-макета dkb_24.09.26, которые не поймал mockup_crawl.py → PNG + report-extra.json. python scripts/mockup_crawl_extra.py docs/mockup/dkb_24.09.26/dkb_24.09.26.html docs/mockup/screens Зависимости: pip install playwright && playwright install chromium Что снимает (имена продолжают нумерацию INDEX.md): 03-report-groups Отчётность → Группы компаний (таблица ГК с кнопкой «Консолидации») 33-01-change-status-modal модалка «Изменить статус» на консолидации в работе (2026 К2, «Консолидация РФ») 43-01-add-version меню «…» консолидации → «Добавить версию» 43-02-consolidation-history меню «…» консолидации → «История изменений» 39-01-edit-documents панель «Первичные документы» → «Редактировать документы» 41-01-document-check-osv клик по строке ОСВ (xlsx) в панели документов — в макете строка readonly, снимок не создаётся 41-02-document-check-edit «Проверка распознавания» PDF-документа → «Редактировать» 51-01…51-04 формы «Создать ГК» / «Добавить ЮЛ» / «Создать ГСК» / «Создать ГСЗ» 77-01-gsk-row-menu, 77-02-edit-gsk меню «…» строки ГСК и форма «Изменить ГСК» 79-le-edit, 79-01-le-move-gk, 79-02-le-history «Изменить ЮЛ», «Перевести в другую ГК», «История изменений» 83-01-ex-snapshot Единое хранилище → раскрытая «Консолидация 2024 К1» (сформированная отчётность + первичка) 31-02-01-corrections-edit, 31-03-01-goodwill-edit, 31-04-01-kfo-edit-comments, 31-05-01-kfo-periods 31-02-02-corrections-wide, 31-01-01-chess-wide те же вкладки при viewport 2800px (все колонки в кадре) """ import asyncio import json import os import sys from playwright.async_api import async_playwright URL = "file://" + os.path.abspath(sys.argv[1]) OUT = sys.argv[2] REPORT = {} async def click_text(pg, text, tag=None, nth=0, exact=False): ok = await pg.evaluate( """([text, tag, nth, exact]) => { const els=[...document.querySelectorAll(tag || 'button, a, [role=tab], [role=menuitem], tr, li, div, span, td')] .filter(e => { const t=(e.innerText||'').trim().replace(/\\s+/g,' '); return (exact ? t===text : t.startsWith(text)) && e.getBoundingClientRect().width>5; }); const e = els[nth]; if(!e) return false; e.scrollIntoView({block:'center'}); e.click(); return true; }""", [text, tag, nth, exact], ) await pg.wait_for_timeout(700) return ok async def click_row_menu(pg, row_index=0, table_index=0): """Клик по последней кнопке («…») в строке таблицы.""" ok = await pg.evaluate( """([ri, ti]) => { const tables=[...document.querySelectorAll('table')]; const t=tables[ti]; if(!t) return 'notable'; const r=[...t.querySelectorAll('tbody tr')][ri]; if(!r) return 'norow'; const bs=[...r.querySelectorAll('button')]; const b=bs[bs.length-1]; if(!b) return 'nobtn'; b.click(); return true; }""", [row_index, table_index], ) await pg.wait_for_timeout(600) return ok async def snap(pg, name, note="", full_page=False): await pg.screenshot(path=f"{OUT}/{name}.png", full_page=full_page) d = await pg.evaluate( """() => { const q=s=>[...document.querySelectorAll(s)]; const t=e=>(e.innerText||'').trim().replace(/\\s+/g,' '); const modal = q('[class*=modal],[class*=Modal],[role=dialog],[class*=drawer],[class*=Drawer],[class*=overlay],[class*=Overlay]') .filter(e=>e.getBoundingClientRect().width>100).map(t).slice(0,1).map(x=>x.slice(0,1500)); const inputs = q('input,textarea,select').filter(e=>e.getBoundingClientRect().width>5) .map(e=>(e.tagName+':'+(e.type||'')+':'+(e.placeholder||'')+':'+(e.value||'').slice(0,40))); return {heads:q('h1,h2,h3,h4').map(t).filter(Boolean).slice(0,12), buttons:[...new Set(q('button').map(t).filter(x=>x&&x.length<60&&!x.includes('CRM ID')&&!x.endsWith('ГСК')))], labels:[...new Set(q('label').map(t).filter(x=>x&&x.length<80))].slice(0,40), inputs, cols:[...new Set(q('th').map(t).filter(Boolean))].slice(0,60), modal, body:t(document.body).slice(0,3000)} }""" ) d["note"] = note REPORT[name] = d print(f"## {name}: heads={d['heads'][:4]} modal={bool(d['modal'])} labels={d['labels'][:12]}") return d async def fresh(pg): await pg.goto(URL) await pg.evaluate("() => { localStorage.clear(); sessionStorage.clear(); }") await pg.goto(URL) await pg.wait_for_timeout(1600) async def dismiss_inn_warning(pg): await click_text(pg, "Перейти", "button", 0, True) async def goto_consolidations(pg): await fresh(pg) await click_text(pg, "Группы компаний", "button") await click_text(pg, "Консолидации", "button") async def step(name, coro): try: await coro except Exception as exc: # noqa: BLE001 — съёмка не должна падать целиком из-за одного экрана print(f"!! {name}: {type(exc).__name__}: {exc}") REPORT.setdefault("_errors", []).append(f"{name}: {exc}") async def main(): async with async_playwright() as p: b = await p.chromium.launch() pg = await b.new_page(viewport={"width": 1600, "height": 1000}) async def s03(): await fresh(pg) await click_text(pg, "Группы компаний", "button") await snap(pg, "03-report-groups", "Отчётность → Группы компаний") async def s33(): await goto_consolidations(pg) await click_text(pg, "К отчетности", "button", 3) # 2026 К2 — «Консолидация РФ» await click_text(pg, "Изменить статус", "button") await snap(pg, "33-01-change-status-modal", "модалка «Изменить статус» на консолидации в работе") async def s43(): await goto_consolidations(pg) await click_row_menu(pg, 0, 0) await click_text(pg, "Добавить версию", None, 0, True) await snap(pg, "43-01-add-version", "меню «…» → Добавить версию") await goto_consolidations(pg) await click_row_menu(pg, 0, 0) await click_text(pg, "История изменений", None, 0, True) await snap(pg, "43-02-consolidation-history", "меню «…» → История изменений") async def s39_41(): await goto_consolidations(pg) await click_text(pg, "2024", "td", 0, False) # выбрать консолидацию → панель «Организации» await click_text(pg, "Документы", "button") await click_text(pg, "Редактировать документы", "button") await snap(pg, "39-01-edit-documents", "панель документов → Редактировать документы") await goto_consolidations(pg) await click_text(pg, "2024", "td", 0, False) await click_text(pg, "Документы", "button") # Строки ОСВ (xlsx) в панели документов имеют класс doc-row--readonly — клик ничего не открывает, # «Проверка распознавания» в макете есть только для PDF (doc-row--previewable). Снимок делаем, # только если экран действительно открылся. cls = await pg.evaluate( """() => { const els=[...document.querySelectorAll('.doc-row')].filter(e=>(e.innerText||'').includes('ОСВ')); const e=els[0]; if(!e) return null; e.click(); return e.className; }""" ) await pg.wait_for_timeout(900) body = await pg.evaluate("() => (document.body.innerText||'').replace(/\\s+/g,' ')") if "Проверка распознавания" in body: await snap(pg, "41-01-document-check-osv", f"клик по строке ОСВ в панели документов ({cls})") else: REPORT["41-01-document-check-osv"] = {"note": f"не снято: строка ОСВ не открывает экран проверки (класс строки: {cls})"} print(f"## 41-01-document-check-osv: не снято — строка ОСВ readonly ({cls})") # PDF-документ (doc-row--previewable) → «Проверка распознавания» → «Редактировать» await goto_consolidations(pg) await click_text(pg, "2024", "td", 0, False) await click_text(pg, "Документы", "button") await pg.evaluate( """() => { const els=[...document.querySelectorAll('.doc-row--previewable')].filter(e=>(e.innerText||'').includes('ОФР')); const e=els[0]; if(e) e.click(); }""" ) await pg.wait_for_timeout(1200) if await click_text(pg, "Редактировать", "button", 0, True): await snap(pg, "41-02-document-check-edit", "Проверка распознавания (PDF) → Редактировать") async def s_wide(): """Широкий viewport: таблицы корректировки и шахматки целиком (в 1600px колонки «Контроль»/«Корректировка» за кадром).""" wide = await b.new_page(viewport={"width": 2800, "height": 1100}) try: await goto_consolidations(wide) await click_text(wide, "К отчетности", "button") await click_text(wide, "Таблица корректировки", "button", 0, True) await dismiss_inn_warning(wide) await wide.evaluate( """() => { const td=[...document.querySelectorAll('table tbody tr td')][0]; const b=td && td.querySelector('button, svg, span'); if(b) b.click(); }""" ) await wide.wait_for_timeout(600) await snap(wide, "31-02-02-corrections-wide", "Таблица корректировки, viewport 2800px, первая пара раскрыта по субсчетам") await click_text(wide, "Шахматка", "button", 0, True) await snap(wide, "31-01-01-chess-wide", "Шахматка, viewport 2800px") finally: await wide.close() async def s51(): for obj, name in [ ("ГК Группа компаний", "51-01-create-gk-form"), ("ЮЛ Юридическое лицо", "51-02-create-le-form"), ("ГСК Группа связанных компаний", "51-03-create-gsk-form"), ("ГСЗ Группа связанных заемщиков", "51-04-create-gsz-form"), ]: await fresh(pg) await click_text(pg, "Создать ГК/ЮЛ", "button") await click_text(pg, obj, "button", 0, True) await snap(pg, name, f"Создать ГК/ЮЛ → {obj}") async def s77(): await fresh(pg) await click_text(pg, "ГСК", "button", 0, False) await click_row_menu(pg, 0, 0) await snap(pg, "77-01-gsk-row-menu", "меню «…» строки ГСК") await click_text(pg, "Изменить ГСК", None, 0, True) await snap(pg, "77-02-edit-gsk", "Изменить ГСК") async def s79(): await fresh(pg) await click_row_menu(pg, 0, 0) await click_text(pg, "Изменить ЮЛ", None, 0, True) await snap(pg, "79-le-edit", "меню ЮЛ → Изменить ЮЛ") await fresh(pg) await click_row_menu(pg, 0, 0) await click_text(pg, "Перевести в другую ГК", None, 0, True) await snap(pg, "79-01-le-move-gk", "меню ЮЛ → Перевести в другую ГК") await fresh(pg) await click_row_menu(pg, 0, 0) await click_text(pg, "История изменений", None, 0, True) await snap(pg, "79-02-le-history", "меню ЮЛ → История изменений") async def s83(): await fresh(pg) await click_text(pg, "Единое хранилище", "button") await click_text(pg, "Консолидация 2024 К1", None, 0, False) await snap(pg, "83-01-ex-snapshot", "ЕХ → раскрыта Консолидация 2024 К1 (верифицирована)", full_page=True) async def s31(): await goto_consolidations(pg) await click_text(pg, "К отчетности", "button") await click_text(pg, "Таблица корректировки", "button", 0, True) await dismiss_inn_warning(pg) await click_text(pg, "Редактировать", "button", 0, True) await snap(pg, "31-02-01-corrections-edit", "Таблица корректировки → Редактировать") await click_text(pg, "Гудвилл", "button", 0, True) await click_text(pg, "Редактировать", "button", 0, True) await snap(pg, "31-03-01-goodwill-edit", "Гудвилл → Редактировать") await click_text(pg, "КФО", "button", 0, True) await click_text(pg, "Редактировать комментарии", "button", 0, True) await snap(pg, "31-04-01-kfo-edit-comments", "КФО → Редактировать комментарии") await click_text(pg, "КФО Общий", "button", 0, True) await click_text(pg, "Выбор периодов", "button", 0, True) await snap(pg, "31-05-01-kfo-periods", "КФО Общий → Выбор периодов") for name, fn in [("03", s03), ("33", s33), ("43", s43), ("39/41", s39_41), ("51", s51), ("77", s77), ("79", s79), ("83", s83), ("31", s31), ("wide", s_wide)]: await step(name, fn()) json.dump(REPORT, open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "mockup", "report-extra.json"), "w"), ensure_ascii=False, indent=1) await b.close() asyncio.run(main())