How to Build Repeatable Run Dashboards with Flask Using Webwright
You can build a repeatable run dashboard by deploying the Flask application in assets/task_showcase/, which reads JSON execution traces generated by src/webwright/utils/serialize.py and exposes endpoints to view and re-trigger Webwright tasks via the webwright run CLI.
Webwright is a Python framework for creating autonomous web-agents that separates agents, models, tools, and environment components. To visualize and re-execute these tasks consistently, Microsoft provides a lightweight Flask dashboard in assets/task_showcase/ that renders repeatable run interfaces without requiring complex frontend frameworks. This implementation allows you to audit, share, and reproduce autonomous web-agent executions through a simple web UI.
Understanding the Dashboard Architecture
The dashboard operates on a simple file-based architecture: Webwright serializes each run to JSON, and Flask renders those static files through Jinja2 templates.
Core Components
The showcase application consists of three primary elements located under assets/task_showcase/:
app.py– Initializes the Flask application, configures the data directory (assets/task_showcase/data/), and registers routes for listing, viewing, and re-running tasks.templates/dashboard.html– Renders a table view of all saved runs with metadata, timestamps, and action buttons.templates/task.html– Displays the full JSON payload of a specific execution trace and provides a re-run interface.
Data Flow
When you invoke webwright run <script>.py, the CLI entry point in src/webwright/run/cli.py triggers the serialization utility from src/webwright/utils/serialize.py. This utility writes deterministic execution traces to assets/task_showcase/data/<timestamp>.json, capturing the script path, inputs, model calls, and tool results. Flask reads these JSON files at runtime—either at startup or on each request—to build the in-memory task registry.
Setting Up the Flask Application
The entry point assets/task_showcase/app.py creates a minimal WSGI application that serves two primary routes: a root dashboard (/) and a task detail view (/task/<id>).
# assets/task_showcase/app.py
from flask import Flask, render_template, request, redirect, url_for
import json
import os
import subprocess
from pathlib import Path
app = Flask(__name__)
DATA_DIR = Path(__file__).parent / "data"
def load_tasks():
"""Load all JSON task files from the data directory."""
tasks = []
for p in sorted(DATA_DIR.glob("*.json"), reverse=True):
with p.open() as f:
data = json.load(f)
tasks.append({
"id": p.stem,
"script": data.get("script", "unknown.py"),
"timestamp": data.get("timestamp"),
"description": data.get("description", ""),
})
return tasks
@app.route("/")
def dashboard():
tasks = load_tasks()
return render_template("dashboard.html", tasks=tasks)
@app.route("/task/<task_id>")
def task_detail(task_id):
path = DATA_DIR / f"{task_id}.json"
with path.open() as f:
task = json.load(f)
return render_template("task.html", task=task)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0")
Creating the Dashboard Views
The templates use standard Jinja2 syntax to render task metadata and provide interaction points for re-execution.
The List View (dashboard.html)
The dashboard template displays a table where each row represents a single Webwright run, linking to the detail view and including a form to trigger re-runs.
<!-- assets/task_showcase/templates/dashboard.html -->
<!doctype html>
<title>Webwright Dashboard</title>
<h1>Repeatable Runs</h1>
<table>
<tr><th>ID</th><th>Script</th><th>When</th><th>Actions</th></tr>
{% for t in tasks %}
<tr>
<td>{{ t.id }}</td>
<td>{{ t.script }}</td>
<td>{{ t.timestamp }}</td>
<td>
<a href="{{ url_for('task_detail', task_id=t.id) }}">View</a>
<form action="{{ url_for('task_run', task_id=t.id) }}" method="post" style="display:inline;">
<button type="submit">Re-run</button>
</form>
</td>
</tr>
{% endfor %}
</table>
The Detail View (task.html)
The task template renders the full JSON execution trace using the tojson filter for formatting, allowing developers to inspect model calls and tool results.
<!-- assets/task_showcase/templates/task.html -->
<!doctype html>
<title>Run {{ task.id }}</title>
<h1>Run {{ task.id }}</h1>
<pre>{{ task | tojson(indent=2) }}</pre>
<form action="{{ url_for('task_run', task_id=task.id) }}" method="post">
<button type="submit">Re-run this task</button>
</form>
Serializing Webwright Runs
Repeatability depends on the deterministic JSON serialization implemented in src/webwright/utils/serialize.py. When the webwright run command executes (handled by src/webwright/run/cli.py), it automatically generates a JSON payload containing:
- The original script path and command-line arguments
- Execution timestamp and environment configuration
- Complete traces of agent decisions, model invocations, and tool outputs
These files land in assets/task_showcase/data/, making them version-controllable and auditable outside the application.
Implementing the Re-run Logic
The repeatability guarantee comes from re-executing the original webwright run command with identical arguments stored in the JSON payload. The dashboard implements this through a POST endpoint that spawns a subprocess.
@app.route("/task/<task_id>/run", methods=["POST"])
def task_run(task_id):
"""Re-execute a Webwright task using the stored configuration."""
path = DATA_DIR / f"{task_id}.json"
with path.open() as f:
task = json.load(f)
script = task.get("script")
if script:
# Spawn the Webwright CLI with the original script
subprocess.Popen(["webwright", "run", script])
return redirect(url_for("dashboard"))
Because Webwright's run command is deterministic when provided the same configuration and seed, re-executing via this endpoint yields identical behavior to the original run.
Summary
- The Flask dashboard in
assets/task_showcase/provides a zero-dependency frontend for Webwright execution traces. - JSON serialization via
src/webwright/utils/serialize.pycaptures complete run contexts, enabling deterministic re-execution. - The re-run endpoint spawns the
webwright runCLI subprocess using arguments stored in the JSON payload, ensuring repeatability. - All execution data remains in version-controllable JSON files, facilitating audit trails and team collaboration.
Frequently Asked Questions
Where does Webwright store the JSON files for the dashboard?
Webwright writes execution traces to assets/task_showcase/data/<timestamp>.json via the serialization utility in src/webwright/utils/serialize.py. This occurs automatically when you run tasks using the webwright run command as implemented in src/webwright/run/cli.py.
How does the dashboard ensure runs are repeatable?
The dashboard stores the original script path and all execution parameters in the JSON payload. When you click "Re-run," the Flask application spawns a subprocess calling webwright run with the identical script and configuration, leveraging Webwright's deterministic execution model to reproduce the same agent behavior.
Can I customize the Flask templates for my own branding?
Yes. The templates in assets/task_showcase/templates/ use standard Jinja2 syntax. You can modify dashboard.html and task.html to include custom CSS, additional metadata columns, or corporate branding without modifying the Python backend, provided you maintain the expected template variables.
Is it safe to use subprocess in the re-run endpoint for production?
The current implementation in assets/task_showcase/app.py uses subprocess.Popen for simplicity. For production deployments, you should implement input validation on the stored script paths, use a task queue (such as Celery or RQ) instead of direct subprocess calls, and restrict the dashboard to authenticated users to prevent arbitrary code execution.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →