Files
lda-wf/tests/examples/test_browser_click_workflow_example.py
T

285 lines
9.5 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from examples.browser_click_workflow.ops import (
CollectSnapshotsInput,
OpenPageInput,
WaitForClickInput,
_active_session_count,
_collect_snapshots,
_get_session,
_open_click_page,
_wait_for_click,
)
from wf_config import load_workflow_config
from wf_server.config import build_workflow_server_from_workflow_config
def test_browser_click_source_simulates_click_and_cleans_up() -> None:
opened = _open_click_page(
OpenPageInput(button_label="Launch Workflow", open_browser=False)
)
assert opened.before.clicked is False
assert opened.before.button_text == "Launch Workflow"
assert opened.before.status_text == "Waiting for click"
clicked = _wait_for_click(
WaitForClickInput(
session_id=opened.session_id, simulate=True, timeout_seconds=2
)
)
result = _collect_snapshots(
CollectSnapshotsInput(
session_id=opened.session_id,
before=opened.before,
after=clicked.after,
)
)
assert result.before.clicked is False
assert result.after.clicked is True
assert result.after.status_text == "Button clicked"
assert result.closed is True
assert _active_session_count() == 0
def test_browser_click_source_human_timeout_cleans_up() -> None:
opened = _open_click_page(OpenPageInput(open_browser=False))
with pytest.raises(TimeoutError, match="timed out waiting for click"):
_wait_for_click(
WaitForClickInput(
session_id=opened.session_id,
simulate=False,
timeout_seconds=0.01,
)
)
_collect_snapshots(
CollectSnapshotsInput(
session_id=opened.session_id,
before=opened.before,
after=opened.before,
)
)
assert _active_session_count() == 0
def test_browser_click_session_events_are_bounded() -> None:
opened = _open_click_page(OpenPageInput(open_browser=False))
try:
session = _get_session(opened.session_id)
for index in range(20):
session.add_event(f"event-{index}")
snapshot = session.snapshot()
assert len(snapshot.events) == 10
assert snapshot.events == [f"event-{index}" for index in range(10, 20)]
finally:
_collect_snapshots(
CollectSnapshotsInput(
session_id=opened.session_id,
before=opened.before,
after=opened.before,
)
)
def test_browser_click_open_browser_failure_does_not_fail_workflow(monkeypatch) -> None:
def fail_open(_url: str) -> bool:
raise RuntimeError("no browser")
monkeypatch.setattr(
"examples.browser_click_workflow.ops.webbrowser.open", fail_open
)
opened = _open_click_page(OpenPageInput(open_browser=True))
try:
assert opened.url.startswith("http://127.0.0.1:")
assert opened.before.clicked is False
finally:
_collect_snapshots(
CollectSnapshotsInput(
session_id=opened.session_id,
before=opened.before,
after=opened.before,
)
)
EXAMPLE_DIR = (
Path(__file__).resolve().parents[2] / "examples" / "browser_click_workflow"
)
@pytest.mark.asyncio
async def test_browser_click_workflow_artifact_deployment_run_path(tmp_path) -> None:
config = load_workflow_config(EXAMPLE_DIR / "wf.config.json")
config.server.store.root = tmp_path / "store"
server = build_workflow_server_from_workflow_config(config)
plan = {
"name": "browser_click_case_study",
"input_schema": {
"type": "object",
"properties": {
"button_label": {"type": "string"},
"open_browser": {"type": "boolean"},
"simulate": {"type": "boolean"},
"timeout_seconds": {"type": "number"},
},
"required": ["button_label", "open_browser", "simulate", "timeout_seconds"],
},
"state_schema": {
"type": "object",
"properties": {
"opened": {"type": "object", "reducer": "wf.std.replace"},
"clicked": {"type": "object", "reducer": "wf.std.replace"},
"result": {"type": "object", "reducer": "wf.std.replace"},
},
},
"output_schema": {
"type": "object",
"properties": {
"before": {"type": "object"},
"after": {"type": "object"},
"closed": {"type": "boolean"},
},
"required": ["before", "after", "closed"],
},
"outcomes": ["ok"],
"start": "open",
"nodes": [
{
"id": "open",
"type": "node",
"node": "local.browser_click.open_click_page",
"input": [
{
"path": {"root": "input", "parts": ["button_label"]},
"target": {"root": "local", "parts": ["button_label"]},
},
{
"path": {"root": "input", "parts": ["open_browser"]},
"target": {"root": "local", "parts": ["open_browser"]},
},
],
"output": [
{
"source": {"root": "local", "parts": []},
"target": {"root": "state", "parts": ["opened"]},
}
],
},
{
"id": "wait",
"type": "node",
"node": "local.browser_click.wait_for_click",
"input": [
{
"path": {"root": "state", "parts": ["opened", "session_id"]},
"target": {"root": "local", "parts": ["session_id"]},
},
{
"path": {"root": "input", "parts": ["simulate"]},
"target": {"root": "local", "parts": ["simulate"]},
},
{
"path": {"root": "input", "parts": ["timeout_seconds"]},
"target": {"root": "local", "parts": ["timeout_seconds"]},
},
],
"output": [
{
"source": {"root": "local", "parts": []},
"target": {"root": "state", "parts": ["clicked"]},
}
],
},
{
"id": "collect",
"type": "node",
"node": "local.browser_click.collect_snapshots",
"input": [
{
"path": {"root": "state", "parts": ["opened", "session_id"]},
"target": {"root": "local", "parts": ["session_id"]},
},
{
"path": {"root": "state", "parts": ["opened", "before"]},
"target": {"root": "local", "parts": ["before"]},
},
{
"path": {"root": "state", "parts": ["clicked", "after"]},
"target": {"root": "local", "parts": ["after"]},
},
],
"output": [
{
"source": {"root": "local", "parts": []},
"target": {"root": "state", "parts": ["result"]},
}
],
},
],
"edges": [
{"from": "open", "outcome": "ok", "to": "wait"},
{"from": "wait", "outcome": "ok", "to": "collect"},
{"from": "collect", "outcome": "ok", "to": "__end__"},
],
"output": [
{
"path": {"root": "state", "parts": ["result", "before"]},
"target": {"root": "local", "parts": ["before"]},
},
{
"path": {"root": "state", "parts": ["result", "after"]},
"target": {"root": "local", "parts": ["after"]},
},
{
"path": {"root": "state", "parts": ["result", "closed"]},
"target": {"root": "local", "parts": ["closed"]},
},
],
}
await server.api.create_artifact_from_plan(
artifact_id="browser_click_case_study",
version=1,
title="Browser Click Case Study",
plan=plan,
outcomes=["ok"],
source_bindings={"local.browser_click": "local.browser_click"},
)
await server.api.save_deployment(
{
"id": "browser_click_case_study.default",
"artifact_id": "browser_click_case_study",
"artifact_version": 1,
"bindings": {"local.browser_click": "local.browser_click"},
}
)
run = await server.api.run_deployment(
deployment_id="browser_click_case_study.default",
workflow_input={
"button_label": "Launch Workflow",
"open_browser": False,
"simulate": True,
"timeout_seconds": 2,
},
)
assert run["status"] == "completed"
output = run["output"]
assert output is not None
assert output["before"]["clicked"] is False
assert output["after"]["clicked"] is True
assert output["after"]["status_text"] == "Button clicked"
assert output["closed"] is True
assert run["trace_count"] >= 3