POSA_Copyrighter/tests/operator_gui/test_browser_smoke.py
changukyu 0bfa30d7f5 test: add Playwright E2E harness for operator GUI with decision-flow spec
Introduce a reusable browser-test harness under tests/operator_gui/:
- conftest.py: shared fixtures (operator_server, browser with graceful skip,
  operator_page with screenshot-on-failure artifacts)
- pages/: OperatorWorkbench Page Object encapsulating selectors and actions
- refactor test_browser_smoke.py onto the fixtures + POM (drops duplicated
  server/browser plumbing)
- test_decision_flow.py: approve/hold/reject E2E against the real server,
  covering the reject-requires-memo guard and decision persistence

Pin playwright in requirements-dev.txt (Chromium bundled offline for the
air-gapped target) and ignore the artifacts/ screenshot dir.
2026-06-21 21:10:22 +09:00

116 lines
5.1 KiB
Python

from __future__ import annotations
import pytest
from .pages import OperatorWorkbench
from .pages.workbench_page import SUGGESTED_QUERY_LOADED_STATUS
def _bootstrap_payload():
return {
"submissions": [
{
"id": "SUB-SMOKE1",
"title": "Smoke sample",
"asset": "/assets/case-portrait.svg",
"riskScore": 42,
"riskBand": "medium",
"submittedAt": "2026-06-03 10:00",
"submittedEpoch": 1780452000,
"lastAnalysis": "2026-06-03 10:01",
"applicantStatus": "검토 중",
"decisionStatus": "unreviewed",
"reasons": ["Naver search returned no results"],
"providerState": {"internal": "ok", "naver": "empty", "google": "disabled", "llm": "pending"},
"fileFacts": {"size": "320 x 240", "format": "SVG", "submitted": "2026-06-03 10:00", "analysis": "v1"},
"derivativeNote": "브라우저 smoke test submission",
"recommendation": {"label": "운영자 검토 필요", "detail": "검색 근거가 부족합니다."},
"derivedPreview": {"automatic": False, "entryName": "Smoke sample", "effect": "보강 검색 필요"},
"queryHistory": [
{
"provider": "naver",
"query": "Smoke sample official",
"status": "empty",
"timestamp": "2026-06-03 10:01",
"count": 0,
}
],
"similar": [{"asset": "/assets/case-portrait.svg", "label": "local submission"}],
"evidence": [
{
"id": "ev-smoke-empty",
"group": "naver",
"source": "naver",
"title": "Naver search returned no results",
"confidence": 0,
"query": "Smoke sample official",
"domain": "naver",
"url": "",
"imageUrl": "",
"thumbnailUrl": "",
"pageTitle": "",
"matchType": "empty",
"rank": "",
"providerScore": 0,
"retrievedAt": "2026-06-03 10:01",
"contributed": False,
"sourceEvidenceIds": [],
"status": "active",
}
],
}
],
"submissionQueue": {"label": "smoke", "folderPath": "smoke", "isActive": True},
"providers": [{"id": "naver", "name": "Naver", "enabled": True, "quota": 100, "usage": 0, "status": "ok"}],
"knowledgeEntries": [],
"collectionCandidates": [],
"corrections": [],
"auditEvents": [],
"coverageThresholds": {"coverageGoodRate": 70, "coverageWarnRate": 40, "queryGoodRate": 70, "queryWarnRate": 40},
"searchCoverage": {
"submissions": {"total": 1, "coverageSubmissions": 0},
"queries": {"failed": 0},
"providers": [{"id": "naver", "name": "Naver", "queryEntries": 1, "evidenceSubmissions": 0}],
},
}
def test_browser_smoke_suggested_query_fills_manual_query_without_running_search(operator_server, operator_page):
workbench = OperatorWorkbench(operator_page, operator_server.base_url)
workbench.mock_bootstrap(_bootstrap_payload()).goto()
try:
workbench.wait_for_case("SUB-SMOKE1", timeout=5000)
except Exception as exc:
body = workbench.page.locator("body").inner_text()[:1000]
pytest.fail(f"{exc}\nerrors={workbench.console_errors}\nbody={body}")
workbench.select_case("SUB-SMOKE1")
workbench.use_suggested_query("Smoke sample 저작권")
assert workbench.panel("queries").is_visible()
assert workbench.manual_query.input_value() == "Smoke sample 저작권"
assert workbench.manual_query_status.inner_text() == SUGGESTED_QUERY_LOADED_STATUS, workbench.console_errors
def test_browser_uploads_image_and_selects_new_submission(operator_server, operator_page, tmp_path):
upload_file = tmp_path / "smoke upload.svg"
upload_file.write_text("<svg xmlns='http://www.w3.org/2000/svg' width='80' height='60'></svg>", encoding="utf-8")
workbench = OperatorWorkbench(operator_page, operator_server.base_url)
workbench.goto()
workbench.upload_submission(str(upload_file))
assert workbench.submission_image_name.inner_text() == "smoke upload.svg"
workbench.confirm_upload()
workbench.wait_for_case("smoke-upload", state="attached", timeout=10000)
workbench.workbench_view.wait_for(state="visible", timeout=10000)
assert (operator_server.image_root / "images" / "smoke-upload.svg").exists()
assert (
"smoke-upload 사진이 추가되었습니다. 새 심사 건으로 바로 선택했습니다."
in workbench.submission_import_status.inner_text()
)
assert workbench.case_title.inner_text() == "smoke-upload · smoke-upload"
assert not workbench.console_errors