Files
Aislo/resources/tester/test_506_3_b07_browser.py
T

269 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""506-3 화면 — 자기 포트(5177) Playwright 머리 없는 브라우저 · 검증_sub2 · `AISLO_LIVE_DB=1` 일 때만.
- B07 표지에 선 하나 → CAD 자동백업 열쇠 = `OPEN_WEB_CAD__RECOVERY__b07:{프로젝트}:cover`
- 특성 판 색 칸 다섯 번 끌기 → 되돌리기 한 번 = 처음 색
- 미리받기가 도는 중 [저장] → 「저장했습니다」 · 머묾 글 없음 · 저장 동안 새 미리받기 GET 없음
- [초기화] 물음 글 = 프로젝트 도각 안내(밝음 · 어두움 한 장씩 · tmp/shots_506_3) → [취소]
- B09 [인쇄 미리보기] = PUT /estimation 뒤 #/b09-print · complete 안 부름
- 단계 행 · B07 · B09 storage 를 먼저 떠 두고 끝에 되돌림
AISLO_LIVE_DB=1 ./venv/Scripts/python.exe -m pytest resources/tester/test_506_3_b07_browser.py -q -s
"""
from __future__ import annotations
import asyncio
import json
import os
import shutil
import sys
import time
import urllib.request
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from helper_verify_project import own_ports # noqa: E402
BASE = own_ports()[1] # 화면 서버 = OWNERS.md 이 폴더 줄
PROJECT = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(OWNERS.md)
SHOTS = ROOT / "tmp" / "shots_506_3"
COLS = (
"state",
"progress_percent",
"params",
"message",
"started_at",
"completed_at",
"updated_at",
)
def _up() -> bool:
try:
urllib.request.urlopen(BASE, timeout=3)
return True
except Exception:
return False
pytestmark = pytest.mark.skipif(
os.environ.get("AISLO_LIVE_DB") != "1" or not _up(),
reason="AISLO_LIVE_DB=1 · 5177 서버 있을 때만(검증_sub2 storage · 단계 행을 씀)",
)
async def _keep(mode: str, held: dict) -> None:
"""단계 행 · projects 상태 · B07 · B09 storage 떠 두기(save) · 되돌리기(restore)."""
import aiomysql
from common_util.common_util_storage import resolve_stored_project_path
from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
conn = await aiomysql.connect(
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, db=DB_NAME,
charset="utf8mb4", autocommit=False,
) # fmt: skip
try:
async with conn.cursor(aiomysql.DictCursor) as cur:
if mode == "save":
await cur.execute("SELECT storage_path FROM projects WHERE id=%s", (PROJECT,))
root = Path(resolve_stored_project_path((await cur.fetchone())["storage_path"]))
await cur.execute(
"SELECT * FROM project_workflow_stages WHERE project_id=%s", (PROJECT,)
)
held["stages"] = await cur.fetchall()
await cur.execute("SELECT status, updated_at FROM projects WHERE id=%s", (PROJECT,))
held["project"] = await cur.fetchone()
held["copy"] = ROOT / "tmp" / f"pt_506_3_keep_{int(time.time())}"
held["dirs"] = [
d for d in root.iterdir() if d.is_dir() and d.name.startswith(("B07", "B09"))
]
for d in held["dirs"]:
shutil.copytree(d, held["copy"] / d.name)
return
for row in held["stages"]:
await cur.execute(
f"UPDATE project_workflow_stages SET {', '.join(f'{k}=%s' for k in COLS)} "
"WHERE id=%s",
(*[row[k] for k in COLS], row["id"]),
)
project = held["project"]
await cur.execute(
"UPDATE projects SET status=%s, updated_at=%s WHERE id=%s",
(project["status"], project["updated_at"], PROJECT),
)
await conn.commit()
for d in held["dirs"]:
shutil.rmtree(d)
shutil.copytree(held["copy"] / d.name, d)
finally:
conn.close()
@pytest.fixture(scope="module")
def ctx():
sync = pytest.importorskip("playwright.sync_api")
cred = json.loads((ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
held: dict = {}
asyncio.run(_keep("save", held))
try:
with sync.sync_playwright() as play:
chrome = play.chromium.launch(channel="msedge", headless=True)
context = chrome.new_context(viewport={"width": 1600, "height": 950})
login = {k: cred[k] for k in ("email", "password")}
r = context.request.post(f"{BASE}/api/auth/login/request", data=login, timeout=60000)
assert r.ok, f"로그인 실패 {r.status}" # 비밀번호는 안 찍음
context.add_init_script(f"localStorage.setItem('frd_current_project_id', '{PROJECT}')")
yield context
chrome.close()
finally:
asyncio.run(_keep("restore", held))
def _cad(page):
return next(f for f in page.frames if "/b07-cad/" in f.url)
def _type(page, *lines: str) -> None:
for line in lines:
page.keyboard.type(line)
page.keyboard.press("Enter")
def test_b07_저장_길_화면(ctx):
page = ctx.new_page()
gets: list[tuple[float, str]] = []
puts: list[tuple[float, float]] = []
page.on(
"request",
lambda q: (
q.method == "GET"
and "/design-drawings/" in q.url
and gets.append((time.time(), q.url.rsplit("/", 1)[-1]))
),
)
starts: dict = {}
page.on(
"request",
lambda q: q.method == "PUT" and "/confirm" in q.url and starts.update(t=time.time()),
)
page.on(
"requestfinished",
lambda q: (
q.method == "PUT" and "/confirm" in q.url and puts.append((starts["t"], time.time()))
),
)
page.goto(f"{BASE}/#/b07-design-detail", wait_until="domcontentloaded")
page.wait_for_selector(
'.b07-drawing-button[data-drawing-id="cover"][data-active="true"]', timeout=90000
)
page.wait_for_selector(".cad-host[data-loading='false']", timeout=90000)
cad = _cad(page)
# 선 하나 — 표지 미저장 · 자동백업
cad.locator("canvas").first.click(position={"x": 900, "y": 600})
page.keyboard.press("Escape")
_type(page, "l", "100,100", "400,100")
page.keyboard.press("Escape")
page.keyboard.press("Control+a")
cad.wait_for_timeout(300)
# 특성 판 색 다섯 번 끌기 → 되돌리기 한 번
if cad.locator(".cad-inspector[data-collapsed='true']").count():
cad.locator(".cad-inspector__collapse").click()
cad.locator(".cad-inspector-tabs button", has_text="특성").click()
color = cad.locator(".cad-properties-editor input[type=color]")
color.wait_for(timeout=10000)
first = color.input_value()
for value in ("#110000", "#220000", "#330000", "#440000", "#550000"):
color.evaluate(
"(el, v) => { Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')"
".set.call(el, v); el.dispatchEvent(new Event('input', { bubbles: true })); }",
value,
)
cad.wait_for_timeout(60)
painted = color.input_value()
cad.wait_for_timeout(600) # 멎음 → 한 칸
cad.locator("canvas").first.click(position={"x": 600, "y": 60}) # 초점 = 캔버스(고름은 다시)
page.keyboard.press("Control+z")
page.keyboard.press("Control+a")
cad.wait_for_timeout(300)
undone = color.input_value()
print(f"색 처음 {first} · 끈 뒤 {painted} · 되돌리기 한 번 {undone}")
assert painted == "#550000" and undone == first
# 자동백업 열쇠(적재 뒤 5 s 쉼 + 5 s 미룸)
cad.wait_for_timeout(11000)
keys = cad.evaluate(
"Object.keys(localStorage).filter(k => k.startsWith('OPEN_WEB_CAD__RECOVERY'))"
)
print("자동백업 열쇠", keys)
assert f"OPEN_WEB_CAD__RECOVERY__b07:{PROJECT}:cover" in keys
assert "OPEN_WEB_CAD__RECOVERY__cover" not in keys
# 미리받기 도는 중 [저장]
before = len(gets)
page.get_by_role("button", name="저장", exact=True).click()
toast = page.wait_for_selector(
".ui-toast:has-text('저장했습니다'), .ui-toast:has-text('머묾')", timeout=90000
).inner_text()
page.wait_for_function(
"() => document.querySelector('.b07-drawing-button[data-drawing-id=cover]')?.dataset.confirmed === 'true'",
timeout=90000,
)
put_start, put_end = puts[-1]
during = [name for t, name in gets if put_start < t < put_end]
print(
f"[저장] 앞 미리받기 GET {before} · PUT {put_end - put_start:.2f} s · 알림 「{toast}」 · 저장 중 새 GET {during}"
)
assert before > 1 # 미리받기 도는 중
assert "머묾" not in toast and "저장했습니다" in toast
assert during == []
# [초기화] 물음 글 → [취소]
page.get_by_role("button", name="초기화", exact=True).click()
message = page.wait_for_selector(".ui-confirm__message", timeout=10000)
text = message.inner_text()
print("초기화 물음", text)
assert "프로젝트 도각은 그대로" in text and "[기본 도각 복원]" in text
SHOTS.mkdir(parents=True, exist_ok=True)
panel = page.locator(".ui-confirm__panel")
panel.screenshot(path=str(SHOTS / "reset_light.png"))
page.evaluate("document.documentElement.setAttribute('data-theme', 'dark')")
panel.screenshot(path=str(SHOTS / "reset_dark.png"))
page.evaluate("document.documentElement.setAttribute('data-theme', 'light')")
page.locator(".ui-confirm__actions button", has_text="취소").click()
page.close()
def test_b09_인쇄_미리보기_저장_뒤_이동(ctx):
page = ctx.new_page()
calls: list[str] = []
page.on(
"request",
lambda q: (
"/estimation" in q.url
and q.method != "GET"
and calls.append(f"{q.method} {q.url.split('/estimation')[-1] or '/'}")
),
)
page.goto(f"{BASE}/#/b09-estimation", wait_until="domcontentloaded")
button = page.wait_for_selector("[data-b09-print]", timeout=90000)
page.wait_for_timeout(6000) # 원가 자료 받기
user = ctx.request.get(f"{BASE}/api/projects/{PROJECT}/estimation").json().get("user") or {}
# 고친 값 있음 표 — 지금 서버 값 그대로(PUT 이 같은 값을 씀)
page.evaluate(
"([k, v]) => sessionStorage.setItem(k, v)",
[f"aislo:draft:b09-estimation:{PROJECT}", json.dumps(user, ensure_ascii=False)],
)
button.click()
page.wait_for_url("**/#/b09-print", timeout=60000)
print("B09 [인쇄 미리보기] 요청", calls)
assert calls[:1] == ["PUT /"]
assert not any("complete" in call for call in calls)
page.close()