Skip to content

Runner API

The Python-side functions and data classes for talking to a viur-testing-armed server. Used by hosts that drive their own runner (a custom smoke harness, an internal CI tool, …).

The canonical Playwright e2e wiring lives in the npm companion package @spltz/viur-testing and is driven by createGlobalSetup() — the primitives below are the same handshake, exposed for Python callers that want to reuse it.

require_test_mode

require_test_mode

require_test_mode(base_url: str, *, expected_database: str = DEFAULT_DATABASE, expected_namespace: str | None | Any = _UNSET, expected_project_id: str | None = None, timeout: float = 5.0, _opener: Callable[[Request, float], Any] | None = None) -> ServerStatus

Block until the running server confirms it is in test mode.

Parameters:

Name Type Description Default
base_url str

Origin of the server under test, e.g. http://localhost:8080.

required
expected_database str

Database name we expect the server to be on. Default viur-tests.

DEFAULT_DATABASE
expected_namespace str | None | Any

When supplied, the server's namespace must match exactly. Pass None (or the empty string "") to assert the server is on the default namespace; omit the argument entirely to skip the check. Empty string is normalised to None so callers can pass os.environ.get(...) directly without having to special-case unset vs. set-but-empty — matches the server-side convention from the VIUR_TESTING namespace part.

_UNSET
expected_project_id str | None

If set, the server's project_id must match. Use this when your CI knows which GCP project the dev server is bound to.

None
timeout float

HTTP timeout in seconds.

5.0
_opener Callable[[Request, float], Any] | None

Injection seam for tests.

None

Returns:

Type Description
ServerStatus

A :class:ServerStatus snapshot, including the session token.

Raises:

Type Description
TestModePreflightError

if any check fails. The caller must treat this as a hard stop and not run any test.

Source code in src/viur/testing/runner.py
def require_test_mode(
    base_url: str,
    *,
    expected_database: str = DEFAULT_DATABASE,
    expected_namespace: str | None | t.Any = _UNSET,
    expected_project_id: str | None = None,
    timeout: float = 5.0,
    _opener: t.Callable[[urllib.request.Request, float], t.Any] | None = None,
) -> ServerStatus:
    """Block until the running server confirms it is in test mode.

    :param base_url: Origin of the server under test,
        e.g. ``http://localhost:8080``.
    :param expected_database: Database name we expect the server to be on.
        Default ``viur-tests``.
    :param expected_namespace: When supplied, the server's ``namespace``
        must match exactly. Pass ``None`` (or the empty string ``""``)
        to assert the server is on the default namespace; omit the
        argument entirely to skip the check. Empty string is normalised
        to ``None`` so callers can pass ``os.environ.get(...)`` directly
        without having to special-case unset vs. set-but-empty —
        matches the server-side convention from the ``VIUR_TESTING``
        namespace part.
    :param expected_project_id: If set, the server's ``project_id`` must
        match. Use this when your CI knows which GCP project the dev
        server is bound to.
    :param timeout: HTTP timeout in seconds.
    :param _opener: Injection seam for tests.
    :raises TestModePreflightError: if any check fails. The caller must
        treat this as a hard stop and not run any test.
    :returns: A :class:`ServerStatus` snapshot, including the session token.
    """
    if expected_namespace == "":
        expected_namespace = None

    server = _do_request(_build_status_request(base_url), timeout, _opener)

    if server.get("test_mode") is not True:
        raise TestModePreflightError(
            f"Server reports test_mode={server.get('test_mode')!r}; "
            "refusing to run tests against a non-test instance."
        )
    if server.get("is_dev_server") is not True:
        raise TestModePreflightError(
            f"Server reports is_dev_server={server.get('is_dev_server')!r}; "
            "refusing to run tests against anything that is not a local dev server."
        )
    if server.get("database") != expected_database:
        raise TestModePreflightError(
            f"Server reports database={server.get('database')!r}, "
            f"expected {expected_database!r}."
        )
    if expected_namespace is not _UNSET and server.get("namespace") != expected_namespace:
        raise TestModePreflightError(
            f"Server reports namespace={server.get('namespace')!r}, "
            f"expected {expected_namespace!r}."
        )
    if expected_project_id is not None and server.get("project_id") != expected_project_id:
        raise TestModePreflightError(
            f"Server reports project_id={server.get('project_id')!r}, "
            f"expected {expected_project_id!r}."
        )

    token = server.get("token")
    if not isinstance(token, str) or not token:
        raise TestModePreflightError(
            "Server response is missing a non-empty 'token' string."
        )

    reported_hash = server.get("token_hash")
    expected_hash = hashlib.sha256(token.encode("utf-8")).hexdigest()
    if reported_hash != expected_hash:
        raise TestModePreflightError(
            "Server's token_hash does not match the sha256 of the returned token."
        )

    return ServerStatus(
        database=server["database"],
        namespace=server.get("namespace"),
        project_id=server["project_id"],
        token=token,
        token_hash=reported_hash,
        version=server.get("version", "unknown"),
    )

finish

finish

finish(base_url: str, token: str | None = None, *, timeout: float = 5.0, _opener: Callable[[Request, float], Any] | None = None) -> dict

End the session: tell the server to delete the token entity.

/_test/config/finish is a bootstrap endpoint that deletes the singleton token entity, so it needs no credential of its own — the token argument is accepted for call-site compatibility but ignored.

Parameters:

Name Type Description Default
base_url str

Origin of the server under test.

required
token str | None

Ignored (kept for backwards compatibility).

None
timeout float

HTTP timeout in seconds.

5.0
_opener Callable[[Request, float], Any] | None

Injection seam for tests.

None

Returns:

Type Description
dict

The parsed JSON response ({"finished": True, "had_token": ...}).

Raises:

Type Description
TestModePreflightError

on transport errors or non-2xx responses.

Source code in src/viur/testing/runner.py
def finish(
    base_url: str,
    token: str | None = None,
    *,
    timeout: float = 5.0,
    _opener: t.Callable[[urllib.request.Request, float], t.Any] | None = None,
) -> dict:
    """End the session: tell the server to delete the token entity.

    ``/_test/config/finish`` is a bootstrap endpoint that deletes the
    singleton token entity, so it needs no credential of its own — the
    ``token`` argument is accepted for call-site compatibility but ignored.

    :param base_url: Origin of the server under test.
    :param token: Ignored (kept for backwards compatibility).
    :param timeout: HTTP timeout in seconds.
    :param _opener: Injection seam for tests.
    :returns: The parsed JSON response
        (``{"finished": True, "had_token": ...}``).
    :raises TestModePreflightError: on transport errors or non-2xx responses.
    """
    return _do_request(_build_finish_request(base_url), timeout, _opener)

ServerStatus

ServerStatus dataclass

Validated server-side snapshot returned by /_test/config/status.

Source code in src/viur/testing/runner.py
@dataclasses.dataclass(frozen=True)
class ServerStatus:
    """Validated server-side snapshot returned by ``/_test/config/status``."""

    database: str
    namespace: str | None
    project_id: str
    token: str
    token_hash: str
    version: str

TestModePreflightError

TestModePreflightError

Bases: RuntimeError

Raised when the runner cannot confirm the server is in test mode.

Source code in src/viur/testing/runner.py
class TestModePreflightError(RuntimeError):
    """Raised when the runner cannot confirm the server is in test mode."""