# _session_save_test.py — 세션 전체 수동 저장 버튼(요구) 기능 게이트 [CEO 2026-07-11]
# 상단 오른쪽 SAFE 배지 옆 '💾 저장' 버튼을 실제 DOM 클릭 → 새로고침 → 복원을 검증.
# 티칭포인트 + DXF 도면배치 + 용접/제품 파라미터 3종이 모두 라운드트립 되는지 확인.
# 사용: python 05_BROWSER_TESTS/_session_save_test.py  (서버 8090 필요)
import asyncio, sys
from playwright.async_api import async_playwright

URL='http://localhost:8090/DOZIKWORKS_OS_v5_locked.dc.html?health=1'
SEG_A='[[[0,0],[100,0],[100,80],[0,80],[0,0]]]'

async def ready(pg):
    for _ in range(90):
        await pg.wait_for_timeout(1000)
        if await pg.evaluate("!!(window.__dzw&&window.__dzw.three&&window.__dzw._grp&&window.__dzw._T&&window.DZW5_PROJECT)"):
            return True
    return False

async def main():
    fails=[]; errs=[]
    async with async_playwright() as p:
        br=await p.chromium.launch(channel='msedge', headless=True)
        pg=await br.new_page(viewport={'width':1536,'height':864})
        pg.on('console', lambda m: errs.append(m.text[:200]) if m.type=='error' else None)
        pg.on('pageerror', lambda e: errs.append('PAGEERROR: '+str(e)[:200]))
        await pg.goto(URL)
        if not await ready(pg): print('== FAIL: 앱 로딩 실패 =='); sys.exit(2)
        await pg.wait_for_timeout(2500)

        # 깨끗한 상태
        await pg.evaluate("['dzw_session_snapshot','dzw_project_autosave','dzw_teach_pts_v2','dzw_dxf_saved_layout','dzw_dxf_tf_T.json'].forEach(k=>localStorage.removeItem(k))")

        # ── 세션 작업물 3종 세팅 ──
        # 1) 티칭포인트 (teach 블럭 저장키에 직접)
        await pg.evaluate("localStorage.setItem('dzw_teach_pts_v2',JSON.stringify([{id:'P001',type:'일반',cond:'W01',x:11,y:22,z:33,rx:0,ry:0,rz:0},{id:'P002',type:'일반',cond:'W01',x:44,y:55,z:66,rx:0,ry:0,rz:0}]))")
        # 2) DXF 도면 배치
        await pg.evaluate(f"""(()=>{{ const a=window.__dzw;
            a._dxfData={{name:'T.json', segs:{SEG_A}}};
            a._dxfTf={{s:1,rot:0,tx:321,ty:-12}}; a._dxfLocked=true; a._dxfMatched=true; a._dxfSelected=true;
            a._dxfDraw();
        }})()""")
        # 3) 용접 파라미터
        await pg.evaluate("(()=>{ const a=window.__dzw; a._weldZOfsMm=7; a._weldSpeedCmMin=42; })()")

        # ── 실제 DOM 버튼 클릭 ──
        btn=await pg.query_selector('#dzw-session-save-btn')
        okBtn = btn is not None
        print(('PASS' if okBtn else 'FAIL'), 'BTN 버튼 존재 — SAFE 배지 옆 #dzw-session-save-btn')
        if not okBtn: fails.append('BTN')
        else:
            # 위치가 화면 상단 오른쪽인지 확인
            box=await btn.bounding_box()
            okPos = box and box['x']>1100 and box['y']<160
            print(('PASS' if okPos else 'FAIL'), 'POS 상단 오른쪽 위치', box)
            if not okPos: fails.append('POS')
            await btn.click()
            await pg.wait_for_timeout(500)

        # 저장 결과 확인 (localStorage)
        snap=await pg.evaluate("localStorage.getItem('dzw_session_snapshot')")
        auto=await pg.evaluate("localStorage.getItem('dzw_project_autosave')")
        dxf=await pg.evaluate("localStorage.getItem('dzw_dxf_saved_layout')")
        okSave = snap and 'savedTs' in snap and auto and '"weld"' in auto and dxf and '"T.json"' in dxf
        print(('PASS' if okSave else 'FAIL'), 'SAVE 클릭 후 스냅샷+자동저장+DXF 저장됨')
        if not okSave: fails.append('SAVE')

        # 버튼 라벨에 저장시각 표시
        lab=await pg.evaluate("(()=>{ const b=document.getElementById('dzw-session-save-btn'); const l=b&&b.querySelector('.dzw-save-lab'); return l?l.textContent:''; })()")
        okLab = lab.startswith('저장 ') and ':' in lab
        print(('PASS' if okLab else 'FAIL'), 'LABEL 버튼에 마지막 저장시각 표시', repr(lab))
        if not okLab: fails.append('LABEL')

        # ── 새로고침 → 복원 (3종 라운드트립) ──
        await pg.reload()
        if not await ready(pg): print('== FAIL: 재로딩 실패 =='); sys.exit(2)
        await pg.wait_for_timeout(3000)  # autoRestore + 400ms dxf 복원 + 여유

        rst=await pg.evaluate("""(()=>{ const a=window.__dzw;
            let pts=[]; try{ pts=JSON.parse(localStorage.getItem('dzw_teach_pts_v2')||'[]'); }catch(_){}
            return { pts:pts.length, dxf:!!a._dxfData, dxfName:a._dxfData&&a._dxfData.name,
                     dxfTx:a._dxfTf&&a._dxfTf.tx, weldZ:a._weldZOfsMm, weldSpd:a._weldSpeedCmMin,
                     snap:!!localStorage.getItem('dzw_session_snapshot') }; })()""")
        okPts = rst['pts']==2
        print(('PASS' if okPts else 'FAIL'), 'R-티칭 — 포인트 2개 복원', rst['pts'])
        if not okPts: fails.append('R-PTS')
        okDxf = rst['dxf'] and rst['dxfName']=='T.json' and abs((rst['dxfTx'] or 0)-321)<0.5
        print(('PASS' if okDxf else 'FAIL'), 'R-도면 — DXF T.json(tx=321) 복원', rst['dxfName'], rst['dxfTx'])
        if not okDxf: fails.append('R-DXF')
        okWeld = abs((rst['weldZ'] or 0)-7)<0.01 and abs((rst['weldSpd'] or 0)-42)<0.01
        print(('PASS' if okWeld else 'FAIL'), 'R-용접 — zOfs=7·speed=42 복원', rst['weldZ'], rst['weldSpd'])
        if not okWeld: fails.append('R-WELD')

        await br.close()

    real_errs=[e for e in errs if 'favicon' not in e and 'Failed to load resource' not in e]
    print('콘솔 에러:', len(real_errs))
    for e in real_errs[:5]: print('  -', e)
    if fails or real_errs:
        print('== FAIL:', fails, '(console errs:', len(real_errs), ') =='); sys.exit(1)
    print('== PASS: 세션 전체 저장/복원 전 항목 통과 ==')

asyncio.run(main())
