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.
116 lines
5.1 KiB
Python
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
|