Files

93 lines
3.5 KiB
Python

"""Seam 1 page check for the AI web dashboard (React + Vite), served by its server stub.
Page under test: set PAGE_CHECK_AI_DIR to a generated dashboard folder (the one with
package.json, index.html and server_stub.py). Without it, the AI web starter template
is filled with TEST_FILL and built in a cache folder. Either way the folder is built
with npm, and its server_stub.py runs in this process, so tests can set run and
review state directly through the `stub` fixture.
Contract envelopes go in through window.__pyMsg, as in the no-AI check. Each action
the page POSTs to {API_BASE}/<slot> is recorded in window.__calls as [slot, ...args].
Every page test runs in light, dark and narrow mode. Shared helpers: tests/page_check_common.py.
"""
import hashlib
import os
import re
import shutil
import subprocess
import tempfile
import threading
from pathlib import Path
import pytest
from page_check_common import MODES, TEST_FILL, load_stub, open_page
ROOT = Path(__file__).resolve().parents[2]
TEMPLATE = ROOT / "skills" / "rpa-dashboard-ai" / "templates" / "web"
TEST_FILL = {**TEST_FILL, "APP_NAME": "Test Extractor", "API_BASE": "/api"}
RECORD_ACTIONS = """
window.__calls = [];
const realFetch = window.fetch;
window.fetch = (url, opts = {}) => {
const m = String(url).match(/\\/api\\/([a-z-]+)$/);
if (m && opts.method === 'POST') window.__calls.push([m[1].replace(/-/g, '_'), ...JSON.parse(opts.body || '[]')]);
return realFetch(url, opts);
};
"""
SKIP_COPY = shutil.ignore_patterns("node_modules", "dist", "__pycache__")
@pytest.fixture(scope="session")
def site():
"""The dashboard folder under test: server_stub.py, index.html and the sources."""
if os.environ.get("PAGE_CHECK_AI_DIR"):
return Path(os.environ["PAGE_CHECK_AI_DIR"]).resolve()
pkg = (TEMPLATE / "package.json").read_bytes() + (TEMPLATE / "package-lock.json").read_bytes()
out = Path(tempfile.gettempdir()) / f"rpa-dashboard-ai-web-{hashlib.sha1(pkg).hexdigest()[:10]}"
shutil.rmtree(out / "src", ignore_errors=True) # node_modules is kept between runs
shutil.copytree(TEMPLATE, out, dirs_exist_ok=True, ignore=SKIP_COPY)
index = out / "index.html"
index.write_text(re.sub(r"\{\{(\w+)\}\}", lambda m: TEST_FILL[m[1]], index.read_text(encoding="utf-8")),
encoding="utf-8")
return out
@pytest.fixture(scope="session")
def built(site):
npm = shutil.which("npm") or pytest.skip("npm not on PATH")
if not (site / "node_modules").exists():
subprocess.run([npm, "ci" if (site / "package-lock.json").exists() else "install"], cwd=site, check=True)
r = subprocess.run([npm, "run", "build"], cwd=site, capture_output=True, text=True)
assert r.returncode == 0, r.stdout + r.stderr
return site
@pytest.fixture(scope="session")
def stub_module(site):
return load_stub(site)
@pytest.fixture(scope="session")
def server(stub_module):
srv = stub_module.make_server("127.0.0.1", 0)
threading.Thread(target=srv.serve_forever, daemon=True).start()
yield f"http://127.0.0.1:{srv.server_address[1]}"
srv.shutdown()
@pytest.fixture
def stub(stub_module, server):
"""The running stub, reset to its sample data. Tests change stub.RUNS / stub.ROWS directly."""
stub_module.reset()
return stub_module
@pytest.fixture(scope="session")
def page_url(built, server):
return server + "/"
@pytest.fixture(params=list(MODES))
def page(request, browser, page_url, stub):
yield from open_page(browser, page_url, request.param, RECORD_ACTIONS)