Files
Aislo/resources/tester/test_309_mass_haul_badge.py
T

186 lines
8.3 KiB
Python

"""B05 · B06 종단 누가토량 알약 — 테마 색 · 그래프 안 끌기(PLAN 309 · 요구 202) — 실제 브라우저(5174).
① 색: 밝음 · 어두움 × 보통 · 음수 — 바탕 불투명 · 글자(제목 · 값) 대비 4.5 넘음 · 붉은 계통(빨강이 가장 큼)
② 끌기(마우스): 움직임 · 끈 뒤 click 은 삼킴 · 안 끈 누름은 지나감 · 영역 끝 막힘(오른쪽 아래 · 왼쪽 위 = 처음 자리)
③ 자리 기억: 화면 설정 한 칸(계정 올리기 PUT 에 실림 — 시험은 가로채 서버에 안 씀) · 다시 열어도 · 창 크기 바꿔도 같은 비율
④ B06 도 같은 자리 · 손가락 끌기(터치 pointer)도 같은 길
"""
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent))
import test_device_audit as audit # noqa: E402
KEY = "aislo:pref:mass-haul-badge-pos"
# 그래프 고름 · 종단 편집 흔적 — 고른 것 수 · 되돌리기 단추 켜짐
PICKED = (
"[document.querySelectorAll('.b05-route-profile__body :is(.is-selected, .is-picked,"
" .is-active)').length, location.hash]"
)
BOX = (
"(() => { const b = document.querySelector('.mass-haul-badge'), a = b.parentElement"
".querySelector('.b05-route-profile__body'); const r = b.getBoundingClientRect(),"
" q = a.getBoundingClientRect(); return {l: r.left, t: r.top, r: r.right, b: r.bottom,"
" w: r.width, h: r.height, ar: q.left + a.clientWidth, ab: q.top + a.clientHeight,"
" al: q.left, at: q.top}; })()"
)
CONTRAST = r"""
(theme) => {
document.documentElement.dataset.theme = theme;
const parse = (s) => { const m = s.match(/[\d.]+/g).map(Number);
const k = s.startsWith('color(') ? 255 : 1;
return {r: m[0] * k, g: m[1] * k, b: m[2] * k, a: m.length > 3 ? m[3] : 1}; };
const lum = (c) => { const ch = [c.r, c.g, c.b].map((v) => { v /= 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; });
return 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2]; };
const ratio = (x, y) => { const [h, l] = [lum(x), lum(y)].sort((p, q) => q - p);
return (h + 0.05) / (l + 0.05); };
const badge = document.querySelector('.mass-haul-badge');
const was = badge.classList.contains('is-over');
const out = [];
for (const over of [false, true]) {
badge.classList.toggle('is-over', over);
const bg = parse(getComputedStyle(badge).backgroundColor);
for (const t of ['em', 'strong']) {
const fg = parse(getComputedStyle(badge.querySelector(t)).color);
out.push({over, t, alpha: bg.a, ratio: ratio(fg, bg), red: fg.r > fg.g && fg.r > fg.b});
}
}
badge.classList.toggle('is-over', was);
return out;
}
"""
def _ctx(browser, cred, touch=False, saved=None):
ctx = browser.new_context(
viewport={"width": 1440, "height": 900}, has_touch=touch, is_mobile=False
)
audit._login(ctx, cred)
pid = audit._project_id(ctx)
ctx.add_init_script(f"localStorage.setItem('frd_current_project_id', {json.dumps(pid)})")
pushed = []
# 계정 취향 — 받기는 빈 값(처음 자리부터) · 올리기는 가로채 서버에 안 씀
def prefs(route):
if route.request.method == "PUT":
pushed.append(json.loads(route.request.post_data or "{}").get("prefs", {}))
body = {"prefs": saved or {}} if route.request.method == "GET" else {"ok": True}
route.fulfill(status=200, content_type="application/json", body=json.dumps(body))
ctx.route("**/api/dashboard/me/ui-prefs", prefs)
return ctx, pushed
def _drag(page, dx, dy):
b = page.evaluate(BOX)
x, y = b["l"] + b["w"] / 2, b["t"] + b["h"] / 2
page.mouse.move(x, y)
page.mouse.down()
page.mouse.move(x + dx / 2, y + dy / 2, steps=4)
page.mouse.move(x + dx, y + dy, steps=4)
page.mouse.up()
def _saved(page):
return json.loads(page.evaluate(f"localStorage.getItem({json.dumps(KEY)})") or "null")
@pytest.mark.skipif(not audit._server_up(), reason="sub1 화면 서버(5174) 꺼짐")
def test_mass_haul_badge_color_and_drag():
from playwright.sync_api import sync_playwright
cred = json.loads((audit.ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
with sync_playwright() as p:
browser = p.chromium.launch(channel="msedge", headless=True)
ctx, pushed = _ctx(browser, cred)
page = ctx.new_page()
audit._open(page, "b05-profile")
page.wait_for_selector(".mass-haul-badge:not([hidden])", timeout=30000)
# ①
for theme in ("light", "dark"):
for row in page.evaluate(CONTRAST, theme):
assert row["alpha"] == 1 and row["ratio"] >= 4.5 and row["red"], (theme, row)
page.evaluate("document.documentElement.removeAttribute('data-theme')")
# ② 안 끈 누름 = 지나감 · 끈 뒤 click = 삼킴
page.evaluate(
"window.__clicks = 0; document.addEventListener('click', (e) =>"
" e.target.closest?.('.mass-haul-badge') && window.__clicks++)"
)
start = page.evaluate(BOX)
picked = page.evaluate(PICKED)
page.click(".mass-haul-badge")
assert page.evaluate("window.__clicks") == 1
assert page.evaluate(BOX)["l"] == start["l"]
assert _saved(page) is None
_drag(page, 200, 60)
moved = page.evaluate(BOX)
assert abs(moved["l"] - start["l"] - 200) <= 2 and abs(moved["t"] - start["t"] - 60) <= 2
assert page.evaluate("window.__clicks") == 1
_drag(page, 5000, 5000) # 오른쪽 아래 끝 막힘
end = page.evaluate(BOX)
assert (
end["ar"] - 1 <= end["r"] <= end["ar"] + 1
and end["ab"] - 1 <= end["b"] <= end["ab"] + 1
)
assert _saved(page) == {"x": 1, "y": 1}
_drag(page, -5000, -5000) # 왼쪽 위 끝 = 처음 자리
assert page.evaluate(BOX)["l"] == start["l"] and page.evaluate(BOX)["t"] == start["t"]
assert _saved(page) == {"x": 0, "y": 0}
assert page.evaluate(PICKED) == picked # 끌기가 그래프 고름 · 편집을 안 일으킴
# ③ 비율 기억 · 계정 올리기에 실림 · 다시 열기 · 창 크기
_drag(page, 300, 100)
ratio = _saved(page)
assert 0 < ratio["x"] < 1 and 0 < ratio["y"] < 1
page.wait_for_timeout(2000) # 올리기 모음 1.5초
assert any(KEY in push for push in pushed)
audit._open(page, "b05-profile")
page.wait_for_selector(".mass-haul-badge:not([hidden])", timeout=30000)
b = page.evaluate(BOX)
x0, y0 = start["l"] - start["al"], start["t"] - start["at"]
span_x = b["ar"] - b["al"] - x0 - b["w"]
span_y = b["ab"] - b["at"] - y0 - b["h"]
assert abs((b["l"] - b["al"] - x0) - ratio["x"] * span_x) <= 2, (b, ratio)
assert abs((b["t"] - b["at"] - y0) - ratio["y"] * span_y) <= 2, (b, ratio)
page.set_viewport_size({"width": 1200, "height": 800})
page.wait_for_timeout(800)
b = page.evaluate(BOX)
span_x = b["ar"] - b["al"] - x0 - b["w"]
assert abs((b["l"] - b["al"] - x0) - ratio["x"] * span_x) <= 2, (b, ratio)
assert b["r"] <= b["ar"] + 1 and b["b"] <= b["ab"] + 1
ctx.close()
# ④ B06 같은 자리(계정 값으로 받음) · 손가락 끌기
tctx, _ = _ctx(browser, cred, touch=True, saved={KEY: json.dumps(ratio)})
tp = tctx.new_page()
audit._open(tp, "b06-section")
tp.wait_for_selector(".mass-haul-badge:not([hidden])", timeout=30000)
b = tp.evaluate(BOX)
span_x = b["ar"] - b["al"] - x0 - b["w"]
assert abs((b["l"] - b["al"] - x0) - ratio["x"] * span_x) <= 2, (b, ratio)
cdp = tctx.new_cdp_session(tp)
x, y = b["l"] + b["w"] / 2, b["t"] + b["h"] / 2
cdp.send(
"Input.dispatchTouchEvent", {"type": "touchStart", "touchPoints": [{"x": x, "y": y}]}
)
for i in range(1, 6):
cdp.send(
"Input.dispatchTouchEvent",
{"type": "touchMove", "touchPoints": [{"x": x - 20 * i, "y": y - 8 * i}]},
)
cdp.send("Input.dispatchTouchEvent", {"type": "touchEnd", "touchPoints": []})
after = tp.evaluate(BOX)
assert abs(after["l"] - b["l"] + 100) <= 2 and abs(after["t"] - b["t"] + 40) <= 2, (
b,
after,
)
assert _saved(tp) != ratio
browser.close()