Skip to main content

Record browser interactions into a JSON recording, then replay them as a repeatable, reportable test

Project description

autowebtest

Record browser interactions (clicks, form fills, navigations, and assertions against the DOM/console/network/cookies) into a JSON recording, then replay that recording headlessly (or headed) as a repeatable test, with a generated HTML report at the end.

Project layout

src/autowebtest/       package source (src-layout)
  cli.py                argparse entry point (`autowebtest` console script)
  recorder.py           drives the recording browser session, exposes bridge
                         functions the injected overlay calls into
  player.py             replays a recording.json, writes the HTML report
  browser_log.py        shared console/network buffer used by both of the above
  proxy.py               proxy passthrough for the launched browser
  assets/overlay.js      the in-page recorder/inspector UI, injected via
                         `page.add_init_script`, running in a shadow DOM
tests/                  pytest unit tests for the pure-Python logic above
                         (condition matching, CLI argument/path resolution,
                         HTML report generation) — no browser required
pyproject.toml          package metadata, build backend, pytest config

autowebtest.egg-info/ and .venv/ are local build/dev artifacts (gitignored).

Setup

pip install autowebtest
playwright install chromium

Or for local development, install the package in editable mode from a clone of this repo:

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
playwright install chromium

Run the test suite with:

pytest

Install the extra engines you plan to use for replay (Chromium is installed by the setup steps above):

playwright install firefox webkit

Quick start

autowebtest record https://google.com recording.json   # record a session
autowebtest run recording.json                         # replay it, headed, in Chromium

A bare filename like recording.json (no directory component) is written under ./testcases/ by convention — the command above actually creates testcases/recording.json. Give an explicit path (e.g. checkout/login.json) to opt out. autowebtest run looks a bare filename up under ./testcases/ too if it isn't sitting in the current directory.

Replaying writes a pass/fail summary to the console and an HTML report under ./reports/, named after the recording (recording.jsonreports/recording.html).

Recording a session

autowebtest record https://google.com recording.json

This opens a Chromium window on the given URL with a toolbar (Start / Stop) injected at the top of the page.

  • Click Start to begin recording. From that point on, clicks, form fills, and page navigations are captured automatically as steps.
  • Right-click anywhere on the page (once recording) to open the recorder's menu:
    • Inspect Element — opens the inspector panel (below), focused on whichever element you just right-clicked.
    • Stop Recording — same as clicking Stop in the toolbar.
  • Click Stop in the toolbar (or use the menu) to end the session. The browser closes and the recording is written to the output path you gave (recording.json above).

The inspector panel

The inspector is a persistent panel docked to the bottom of the page, similar to a browser's DevTools, with four tabs: Elements, Console, Network, and Cookies. Switching tabs swaps the list on the left; the right-side builder panel adapts to whichever tab is active and is how you turn something you clicked on into a recorded assertion step.

  • Drag the handle at the top edge of the panel to resize it; dragging it down past a minimum height snaps it to a minimized, header-only bar (or click the ▁ / ▔ button in the header to toggle minimize/maximize directly). The panel keeps recording while minimized — maximize it again (or re-select an element) to see the tree.
  • Close the inspector with the button in its header or the Escape key.
  • Every tab's builder has Save (records the condition as a step; the panel stays open so you can keep adding more) and Clear (resets the builder without saving).

Elements tab

The default tab. Shows the full page as a scrollable, expandable DOM tree, rendered like a real browser's Elements panel — actual opening tags with every attribute inline (e.g. <link rel="canonical" href="https://example.com/">), text-node content as its own indented leaf line, and a closing tag once a node is expanded.

  • Click the ▸/▾ arrow to expand/collapse a node's children; click a node's opening tag to select it.

  • Track pointer — a checkbox at the top of the tab. When checked, hovering any element on the page (not just in the tree) highlights it with its tag#id.class badge, and left-clicking it re-opens/re-focuses the inspector on that element instead of performing the page's normal click action — uncheck it to interact with the page normally again. Off by default; stays checked across closing/reopening the inspector within the same origin, and resets to off when you stop recording or navigate cross-origin.

  • Search — a text box (like Cmd+F in DevTools) that matches against each element's rendered opening tag plus its own text, so you can search loosely (a tag name, .a-class, #an-id) or precisely with quoted attribute syntax exactly as it appears in the source, e.g. searching rel="canonical" matches only <link rel="canonical" ...>, not <link rel="stylesheet" ...>. Use the ▲/▼ buttons or Enter/Shift+Enter to cycle through matches; each match jumps to, expands, and selects that node.

  • Selecting a node (by clicking it in the tree, via search, or via right-click → Inspect Element) draws a persistent (dashed) highlight around that element on the page, so you can see exactly what you're targeting even after you stop hovering — it tracks the element's position on scroll/resize and clears when you select something else.

  • Selecting a node fills the builder with:

    • Tag — the element's tag, editable via a dropdown of common tags.
    • Field — a scrollable list of every attribute the element has (id, class, href, src, data-*, aria-*, etc.), plus a synthetic Text / value row (its trimmed text content, or value for form controls) which is selected by default. Click any row to build the condition against that field instead — the Value field is pre-filled from whichever field is selected and shows which attribute it's checking.
    • ConditionExists, Does not exist (these hide the Value field — they check only that a matching element is/isn't on the page, by its recorded selector), Equal, Equal (ignore case), Greater than, Greater or equal, Smaller than, Smaller or equal, Like (SQL-style, % as wildcard, e.g. %openai%), or Custom regex.
    • Value — pre-filled from the selected field; edit as needed.
    • Choosing Custom regex reveals an extra Regex field (pre-filled from the element's id/class + tag) — the assertion then matches by regex instead of by condition, but still checks it against whichever field is selected.

    Example: select the <h1>, leave Field on Text / value, set Condition to Like, Value to %Search results% → records an assertion step that passes if any h1 matching the recorded selector contains that text. Or select a <link rel="canonical">, set Condition to Exists → records a step that fails if that link disappears from a page later.

Console tab

Lists every console message logged by the page so far. Clicking a message fills the builder with its log type and text; Log type can be narrowed to log/info/warning/error/debug or left as Any, and Condition supports the same vocabulary as Elements plus Contains, Exists, and Does not exist (the last two ignore the Value field).

Example: click a captured console.error("Failed to fetch") message, set Log type to error, Condition to Contains, Value to Failed to fetch → records a step that fails if that error is logged during replay.

Network tab

Lists every resource the page has loaded, filterable by URL substring via the text box above the list. Clicking a resource fills the builder with its URL as the URL pattern (matched by Contains or Regex) and a Check:

  • Resource present — just an Exists/Does not exist toggle on whether a matching request was made at all.
  • Status code — numeric condition (Equal, Greater than, etc.) against the response's HTTP status.
  • HTTP method — condition against the request's HTTP method (e.g. Equal / POST), pre-filled from whichever resource you clicked.
  • Response header — a header name field plus the same condition vocabulary as Console, checked against that header's value.

Example: click a POST https://api.example.com/save entry, set Check to HTTP method, Condition to Equal, Value to POST → records a step that fails if that endpoint is ever called with a different method. Or set Check to Status code, Condition to Equal, Value to 200.

Cookies tab

Lists the page's cookies (including httpOnly ones, read on the Python side via the browser context rather than document.cookie). Clicking a cookie fills the builder with its name and value; Condition works the same as Console/Network.

Example: click the session_id cookie, set Condition to Exists → records a step that fails if the user is ever logged out (cookie missing) during replay.

Recording format

{
  "url": "https://google.com",
  "startedAt": "2026-07-10T12:00:00.000Z",
  "endedAt": "2026-07-10T12:05:00.000Z",
  "steps": [
    {"type": "start", "timestamp": "...", "url": "...", "title": "Google"},
    {"type": "click", "timestamp": "...", "selector": "#hplogo", "tag": "img", "id": "hplogo", "classes": [], "text": "Google", "href": null, "actionableKind": "button"},
    {"type": "input", "timestamp": "...", "selector": "input[name='q']", "tag": "input", "value": "openai", "ariaLabel": "Search"},
    {"type": "navigation", "timestamp": "...", "url": "https://www.google.com/search?q=openai", "title": "openai - Google Search"},
    {"type": "assertion", "timestamp": "...", "tag": "div", "selector": "div.g", "condition": "like", "value": "%Search results%"},
    {"type": "assertion", "timestamp": "...", "tag": "a", "selector": "a.result-link", "attribute": "href", "condition": "like", "value": "%openai.com%"},
    {"type": "assertion", "timestamp": "...", "tag": "span", "selector": "#count", "condition": "greaterEqual", "value": "10"},
    {"type": "assertion", "timestamp": "...", "tag": "div", "selector": "div.g", "condition": "regex", "regex": "^div\\.g$", "value": "Search results"},
    {"type": "assertion", "timestamp": "...", "tag": "link", "selector": "link[rel='canonical']", "condition": "exists", "value": ""},
    {"type": "console", "timestamp": "...", "logType": "error", "condition": "contains", "value": "Failed to fetch"},
    {"type": "console", "timestamp": "...", "logType": "any", "condition": "exists", "value": ""},
    {"type": "resource", "timestamp": "...", "urlPattern": "/api/user", "matchType": "contains", "check": "exists", "condition": "exists", "value": ""},
    {"type": "resource", "timestamp": "...", "urlPattern": "\\.png$", "matchType": "regex", "check": "status", "condition": "equal", "value": "200"},
    {"type": "resource", "timestamp": "...", "urlPattern": "/api/save", "matchType": "contains", "check": "method", "condition": "equal", "value": "POST"},
    {"type": "resource", "timestamp": "...", "urlPattern": "/api/user", "matchType": "contains", "check": "header", "headerName": "content-type", "condition": "like", "value": "%json%"},
    {"type": "cookie", "timestamp": "...", "name": "session_id", "condition": "exists", "value": ""},
    {"type": "cookie", "timestamp": "...", "name": "locale", "condition": "equal", "value": "en-IN"},
    {"type": "stop", "timestamp": "...", "url": "...", "title": "openai - Google Search"}
  ]
}
  • assertion steps' condition is exists/notExists (element-tab only, checked purely against the recorded selector — no tag-name fallback), or one of equal, equalIgnoreCase, greater/greaterEqual/smaller/ smallerEqual, like, or regex (which uses the separate regex field instead of selector/tag to find candidates).
  • console/resource/cookie steps' condition is equal, equalIgnoreCase, contains, like, regex, greater/greaterEqual/smaller/smallerEqual, or exists/notExists (which ignore value). logType is one of any/log/info/warning/error/debug.
  • resource steps' matchType is contains/regex (matched against the resource's full URL). Its check is exists, status, method, or header (header also carries headerName, looked up case-insensitively).

Replaying a recording

autowebtest run recording.json                # Chromium (default)
autowebtest run recording.json --firefox       # or --mozilla
autowebtest run recording.json --safari        # or --webkit
autowebtest run recording.json --headless      # run without a visible window

This opens the target url from the recording, then replays each step in order:

  • click / input steps re-run against the saved selector (page.click / page.fill).
  • assertion steps with condition: "regex" re-check the saved regex against every element's tag#id.class descriptor (and its HTML as a fallback); it passes if a matching element's checked field (its text/value, or the saved attribute if one was recorded) contains the saved value.
  • assertion steps with condition: "exists"/"notExists" pass iff the saved selector does/doesn't match anything on the page — no fallback scan, so it's a precise presence check for that exact element.
  • Other assertion steps look up candidates by the saved selector (falling back to every element with the saved tag), then check the saved condition against each candidate's checked field (text/value, or the saved attribute if one was recorded): equal, equalIgnoreCase (string comparison), greater/greaterEqual/smaller/smallerEqual (numeric if both sides parse as numbers, else lexicographic), or like (% treated as a wildcard, case-insensitive). It passes if any candidate satisfies the condition.
  • start / stop / navigation steps are informational and skipped — any navigation triggered by a preceding click happens naturally and the player waits for the network to settle before moving on. Navigation prints the destination page's saved title ("Navigation to <title> page", falling back to the URL if no title was captured).
  • click and input log lines describe what was interacted with based on the element kind (captured at record time in actionableKind/ariaLabel), falling back to the raw selector when the relevant label is empty:
    • input / select — the field's aria-label ("Click on '<aria-label>' field" / "Fill '<aria-label>' field with '<value>'").
    • anchor (<a>) — its text plus the href it points to ("Click on '<text>' link (href=<href>)").
    • button — the text of the first non-empty <span> nested inside it, falling back to the button's own text or aria-label/title for icon-only buttons ("Click on '<text>' button").
  • console steps pass if any captured console message (optionally narrowed by logType) satisfies the condition against its text — or, for exists/ notExists, if any/no such message was captured at all.
  • resource steps first find every network response/failure whose URL matches urlPattern (substring or regex, per matchType). For check: "exists" it passes iff a match was found (exists) or wasn't (notExists); for "status" it passes if any match's HTTP status satisfies the condition; for "method" it passes if any match's HTTP method satisfies the condition; for "header" it passes if any match's headerName response header satisfies the condition.
  • cookie steps look up the named cookie via the browser context (so httpOnly cookies are visible too) and check its value against the condition, or its presence for exists/notExists.

A pass/fail line is printed per step, with a summary at the end. The process exits with a non-zero status if any step failed, so it can be used as a CI check.

--safari/--webkit uses Playwright's WebKit engine, the closest automatable stand-in for Safari — there is no true Safari automation outside Apple's own WebDriver on macOS.

HTML report

Every autowebtest run also writes a self-contained HTML report under ./reports/, named after the recording with its extension swapped to .html — e.g. testcases/recording.jsonreports/recording.html, or testcases/checkout.v2.jsonreports/checkout.v2.html. The console prints its path (autowebtest: HTML report written to ...) after the run summary.

The report has a header with the recording name, target URL, engine, and generation time; summary cards for overall result and passed/failed/skipped counts; and a table of every step with its action description, pass/fail/skip badge, and failure reason (for failed steps). It has no external dependencies, so it can be opened directly in a browser or attached to a CI artifact.

Known limitations (v1)

  • Toolbar recording state is kept in sessionStorage, which is scoped per-origin. It's reconciled against the Python-side session on every navigation, so the recording on/off indicator stays accurate across origins. Steps already captured are unaffected either way since they're stored on the Python side. Track pointer (the checkbox in the Elements tab) follows the same per-origin sessionStorage persistence — it stays checked across closing/reopening the inspector or navigating within the same origin, but resets to off on a cross-origin navigation or when you stop recording.
  • Replay is best-effort: it doesn't wait/retry beyond a fixed timeout per step, and assertion matching is a heuristic (selector/tag scan + condition or regex check against text/value), not a strict re-derivation of what was clicked during recording.
  • console/resource assertions are evaluated against everything captured from page load up to that point during replay, not strictly scoped to "since the previous step" — the same best-effort philosophy as DOM assertions above.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

autowebtest-0.1.1.tar.gz (43.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

autowebtest-0.1.1-py3-none-any.whl (35.4 kB view details)

Uploaded Python 3

File details

Details for the file autowebtest-0.1.1.tar.gz.

File metadata

  • Download URL: autowebtest-0.1.1.tar.gz
  • Upload date:
  • Size: 43.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for autowebtest-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c82dc215b02c7d18b91e5910dbd1d0db66135f610e87931d882aa727191f3847
MD5 c03de2fe014787ef42171e28fe284a47
BLAKE2b-256 09bb324bdcb1033cbb6da514f5b449231433f46215221b63e0ad3534cdf64ee5

See more details on using hashes here.

File details

Details for the file autowebtest-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: autowebtest-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 35.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.1

File hashes

Hashes for autowebtest-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2597c449d206f45407ecd1e9cff6027c8053ae9e2b9eef37c59bf8f1f9b8a255
MD5 7666c9a4375ee636de4c1482dd6bf9b2
BLAKE2b-256 ecc4eb36bf212ac5ecac7ed493fe085ceaf12dcd6f684ea4ddf0a1c659e823ec

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page