10-user-journey.md и mockup/INDEX.md сверены с 33 снимками; досняты Playwright модалка «Изменить статус», формы ГК/ЮЛ/ГСК/ГСЗ, меню ЮЛ и ГСК, раскрытый срез ЕХ, режимы редактирования вкладок отчёта, экран 03. Раздел «Что осталось уточнить» закрыт (экрана проверки ОСВ в макете нет). Расхождения макета с DBML/правилами — OQ-31…OQ-44; OQ-45…OQ-47 — по контракту и seed Среза 0; OQ-29 дополнен ролями макета. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
247 lines
15 KiB
Python
247 lines
15 KiB
Python
#!/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())
|