269 lines
10 KiB
Python
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()
|