"""Integration: detached DOM, real Node Worker via labelled test bridge. No native browser admission."""
import asyncio,json,hashlib,datetime,traceback
from playwright.async_api import async_playwright
from офлайн_Node_мост import ROOT,BROWSER,open_offline
E=ROOT/'доказательства/редакция-4.9.0';rows=[]
async def test(name,fn):
 try:detail=await fn();rows.append({'проверка':name,'пройдено':True,'результат':detail or 'Все явные утверждения выполнены.'})
 except Exception:rows.append({'проверка':name,'пройдено':False,'ошибка':traceback.format_exc()})
 print(name,rows[-1]['пройдено'],flush=True)
async def hold(page):
 await page.evaluate('''()=>{const original=window.__digest;window.__digest=async(...args)=>{window.__digest=original;return await new Promise(resolve=>{window.__heldDigest=true;window.__releaseDigest=async()=>resolve(await original(...args));});};}''')
async def main():
 async with async_playwright() as p:
  b=await p.chromium.launch(executable_path=BROWSER,headless=True,args=['--no-sandbox']);page,errors,_=await open_offline(b,'пакет-обжалования.html',390);page.set_default_timeout(12000)
  async def load():
   await page.locator('#импорт-дела').set_input_files({'name':'ИС-дело.json','mimeType':'application/json','buffer':(E/'ИС-причины-дело.json').read_bytes()});await page.wait_for_function("document.querySelector('#история-событий').textContent.includes('ИС-ПРИЧИНА')");assert len(json.loads(await page.locator('#история-событий').inner_text()))>189
   await page.locator('#дело-дата').fill('2026-09-06');await page.locator('#дело-дата').dispatch_event('change')
  await test('Длинная папка импортируется через настоящий Node Worker без потери событий',load)
  async def revise():
   await page.locator('#редактор-документа [name="название"]').fill('ИС: изменённое название');await page.locator('#редактор-документа [name="основание_изменения"]').fill('ИС: положительный повтор сохранения после исправления сравнения снимка');await page.locator('#редактор-документа button[type=submit]').click();await page.wait_for_function("document.querySelector('#статус-дела').textContent.includes('внесены в историю')");assert 'ИС: изменённое название' in await page.locator('#документы-дела').inner_text()
  await test('Положительное сохранение документа не ошибочно объявляет изменение снимка',revise)
  async def race():
   await page.locator('#редактор-документа [name="название"]').fill('ИС: не должно сохраниться');await hold(page);await page.locator('#редактор-документа button[type=submit]').click();await page.wait_for_function('window.__heldDigest');await page.locator('#редактор-документа [name="название"]').fill('ИС: поздняя правка');await page.evaluate('window.__releaseDigest()');await page.wait_for_function("document.querySelector('#статус-дела').textContent.includes('Изменение не внесено')");assert 'ИС: поздняя правка' not in await page.locator('#документы-дела').inner_text()
  await test('Правка во время await не подменяет сохранённый документ',race)
  f=page.locator('#форма-события-исправления')
  async def event():
   before=len(json.loads(await page.locator('#история-событий').inner_text()));await f.locator('[name=действие]').select_option('исправление источника');await f.locator('[name=цель]').fill('источник');await f.locator('[name=момент_события]').fill(await page.evaluate('new Date().toISOString()'));await f.locator('[name=источник]').fill('ИС: действие только в испытании; реального исправления нет.');await f.locator('[name=проверяющий]').fill('ИС: проверяющий');await f.locator('button[type=submit]').click();await page.wait_for_function("document.querySelector('#статус-дела').textContent.includes('Событие и его отпечаток добавлены')");after=len(json.loads(await page.locator('#история-событий').inner_text()));assert after==before+1;return {'до':before,'после':after}
  await test('Положительное внесение события исправления сохраняет новую запись',event)
  async def event_race():
   before=await page.locator('#история-событий').inner_text();await page.evaluate('window.__heldDigest=false');await hold(page);await f.locator('button[type=submit]').click();await page.wait_for_function('window.__heldDigest');await f.locator('[name=источник]').fill('ИС: позднее изменение источника');await page.evaluate('window.__releaseDigest()');await page.wait_for_function("document.querySelector('#статус-дела').textContent.includes('Событие не внесено')");assert await page.locator('#история-событий').inner_text()==before
  await test('Правка формы события во время await оставляет историю без добавления',event_race)
  async def explanation():
   old=await page.locator('#история-событий').inner_text();await page.locator('#объяснить-остатки').click();await page.wait_for_function("document.querySelector('#статус-дела').textContent.includes('Объяснение остатков подготовлено')");txt=await page.locator('#объяснение-причины').inner_text();assert 'прежний состав' in txt or 'отозвано' in txt or 'ухудшение' in txt;assert old==await page.locator('#история-событий').inner_text();data=await page.evaluate('async()=>({name:__saved.at(-1).name,text:await __saved.at(-1).blob.text()})');out=json.loads(data['text']);assert out['ша256'] and out['отпечаток_папки'];(E/'ИС-интерфейс-объяснение.json').write_text(data['text']);assert await page.locator('#объяснение-причины script,#объяснение-причины img').count()==0
   widths=[]
   for w in [320,390,768,1280,1920]:
    await page.set_viewport_size({'width':w,'height':900});assert await page.evaluate('document.documentElement.scrollWidth-innerWidth')<=2;widths.append(w)
   await page.set_viewport_size({'width':390,'height':900});await page.locator('#объяснение-причины').scroll_into_view_if_needed();await page.screenshot(path=str(E/'причины-и-события-390.png'));return {'ширины':widths,'экспорт':'Blob перехвачен стендом; системное сохранение не подтверждено'}
  await test('Причины отрисованы текстом; полный экспорт и история сохранены, длинные опоры переносятся',explanation)
  await test('Нет необработанных исключений страницы',lambda:assert_no_errors(errors));await page.close();await b.close()
 d={'редакция':'4.9.0-wip','дата':datetime.datetime.now(datetime.timezone.utc).isoformat(),'всего':len(rows),'пройдено':sum(x['пройдено'] for x in rows),'проверки':rows,'сценарий_SHA256':hashlib.sha256(__import__('pathlib').Path(__file__).read_bytes()).hexdigest(),'методика_SHA256':hashlib.sha256((ROOT/'ресурсы/сценарии/методика.js').read_bytes()).hexdigest(),'команда':'python инструменты/испытать-события-интерфейса-4-9.py','предел':'Изолированный DOM и тестовый транспорт к настоящему Node Worker. digest в главном DOM — Python hashlib. Сеть, хранилище, Blob и Worker транспорта подменены. Это не native browser Worker/CSP/печать/скачивание/межвкладочная транзакция.'};(E/'события-интерфейса.json').write_text(json.dumps(d,ensure_ascii=False,indent=2)+'\n');return d['всего']==d['пройдено']
async def assert_no_errors(errors):assert not errors,errors
if __name__=='__main__':raise SystemExit(0 if asyncio.run(main()) else 1)
