# How to Build Repeatable Run Dashboards with Flask Using Webwright

> Build repeatable run dashboards with Flask and Webwright. Deploy the Flask app to view and re-trigger Webwright tasks from execution traces using the webwright run CLI.

- Repository: [Microsoft/Webwright](https://github.com/microsoft/Webwright)
- Tags: how-to-guide
- Published: 2026-06-25

---

**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`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/templates/dashboard.html)** – Renders a table view of all saved runs with metadata, timestamps, and action buttons.
- **[`templates/task.html`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/src/webwright/run/cli.py) triggers the serialization utility from [`src/webwright/utils/serialize.py`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/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>`).

```python

# 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`](https://github.com/microsoft/Webwright/blob/main/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.

```html
<!-- 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`](https://github.com/microsoft/Webwright/blob/main/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.

```html
<!-- 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`](https://github.com/microsoft/Webwright/blob/main/src/webwright/utils/serialize.py). When the `webwright run` command executes (handled by [`src/webwright/run/cli.py`](https://github.com/microsoft/Webwright/blob/main/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.

```python
@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.py`](https://github.com/microsoft/Webwright/blob/main/src/webwright/utils/serialize.py) captures complete run contexts, enabling deterministic re-execution.
- The **re-run endpoint** spawns the `webwright run` CLI 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`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/dashboard.html) and [`task.html`](https://github.com/microsoft/Webwright/blob/main/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`](https://github.com/microsoft/Webwright/blob/main/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.