Skip to content

feat: rerun attempt history from merged outputs (#310) - #332

Open
timdegroot1996 wants to merge 17 commits into
mainfrom
feat/310-rerun-attempts
Open

timdegroot1996 wants to merge 17 commits into
mainfrom
feat/310-rerun-attempts

Conversation

@timdegroot1996

Copy link
Copy Markdown
Collaborator

Closes #310

Also brings the fixture-generator branch to main (merge commit e09aa37): fixture generator, regenerated fixtures, faster robot pipeline, scripts/example.py.

Problem

With robot --rerunfailed, a test that fails on the first attempt and passes on the retry is invisible in the dashboard: only the final status of the merged output is stored. Consistent first-attempt failures are a real signal, not noise.

Approach

rebot --merge already records every attempt (status + message) of a re-executed test in the test message as HTML, and it is the workflow Robot Framework documents for --rerunfailed. The dashboard now reads that history instead of linking separate uploads: one run stays one run (a rerun output alone would show up as a 5-of-100-tests run in every graph), and no new CLI/server/listener surface is needed. Upload the merged output.xml only — see the new Reruns docs page.

What changed

Processing / storage

  • tests.attempts column (JSON, first attempt → last), inline migration. The test message is the final attempt's message; previously the merge HTML (*HTML* <span class="merge">…) ended up in the messages graph.
  • Merged suites have no start time in rebot → fallback to the earliest test start.

Dashboard

  • Test section filter bar: Reruns select — Mark Reruns (default): re-executed tests get a blue border, tooltip lists every attempt with its message; Final Result: exactly as before; First Attempt: status of the first try. Applies to Test Statistics (timeline + scatter), Most Flaky / Recent Most Flaky, Most Failed / Recent Most Failed and Messages timelines.
  • Most Flaky counts a status change inside the attempt history as a flip (a test that only passes on retry every night is flaky, even though every run ends green).
  • Compare page: own Reruns select for the Tests graph.
  • Overview run cards: Rerun: 49 (fixed 22) line next to Passed/Failed/Skipped.
  • Stat widgets: Re-executed Tests, Recovered On Rerun, Failed All Attempts. Tables page: attempts column.

Fixtures

  • RERUNS in the generator: WebshopUI run 7 (one rerun) and WebshopAPI run 5 (two reruns) are re-executed and merged, so the fixtures, reference screenshots and the example dashboard show the feature.

Docs: docs/reruns.md (+ sidebar, index card, README), graphs-tables, custom-database-class, generator README, CONTRIBUTING, testing skill.

Tests

  • Python 401, JS 297 (parser, DB round trip, processor rows, rebot-merge end to end, start-time fallback, flaky/messages/most-failed/stats/helpers data modules).
  • Robot (Docker, full pabot run): 66 tests, 62 passed, 0 failed, 4 skipped — new Validate Dashboard Test Statistics Rerun View (merges two fixtures with rebot at runtime, asserts the marked bars per view on the Test and Compare pages) and Validate Compare Exception Table Base View; references refreshed from the Linux run.

🤖 Generated with Claude Code

timdegroot1996 and others added 14 commits September 14, 2026 03:28
Reimplements the exception-tracking feature from the stale PR #253 on a fresh
branch off main (5 months of drift made the original diff conflict in 10
files), using its diff as the spec rather than resolving merge conflicts.

- New ExceptionProcessor (processors.py): a ResultVisitor that tracks
  TRY/EXCEPT depth and records the message of the innermost failing keyword
  per TRY block (nested TRY, FOR-loops inside TRY, and keyword-wrapping-
  keyword cases all only count the leaf failure, not parents/ancestors).
- New `exceptions` SQLite table (queries.py, database.py): created for both
  fresh and legacy databases, inserted per-run, included in get_data(), and
  cleaned up in _remove_run() — all defensively tolerant of a missing table
  for custom database classes.
- New "Keyword Exceptions" graph (bar/timeline, keyword.js +
  graph_data/exceptions.js) and "Table Exception" (tables.js), wired through
  the existing graphMetadata-driven layout system — no changes needed to
  layout.js/graphs.js/settings.js since graph visibility and GridStack
  placement are fully data-driven from graphMetadata.js.
- filter.js: filteredExceptions flows through the same timezone/filter/sort
  pipeline as the other filtered arrays.

Validated end-to-end against a real Robot Framework suite exercising nested
TRY/EXCEPT, a FOR loop inside TRY, and a keyword wrapping another failing
keyword — exception counts matched hand-counted expectations exactly, the
`exceptions` table and dashboard HTML embed the right data, and the bundled
JS (extracted from the generated HTML) is syntax-clean with no duplicate
function declarations.

Tests: 30 new Python tests (ExceptionProcessor unit tests, database
insert/get/remove-run round-trips including a missing-table legacy-DB case,
dashboard placeholder embedding) and 10 new JS tests for
get_exceptions_data() (bar/timeline aggregation, sorting, limits, aliasing).
340 Python / 266 JS tests passing overall.

Docs: docs/graphs-tables.md, .github/skills/dashboard.md, CLAUDE.md,
.github/copilot-instructions.md, README.md, setup.py long_description.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
# Conflicts:
#	.github/copilot-instructions.md
#	CLAUDE.md
The new Exceptions tile makes the keyword statistics section taller than
the 1600px test viewport, so the page scrolled and the sticky navbar bled
into the element screenshot. Raise the viewport height to 2000px and
update the reference screenshot with the Linux-rendered result.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Two issues surfaced by the new test fixtures:

- --messageconfig patterns were inlined with str(list).replace("'", '"'),
  so any quote in a pattern broke the JS string literal and left the
  dashboard on the loading spinner. Use json.dumps and escape for the
  single-quoted literal that data.js JSON.parses.
- Run metadata was deduplicated through a set, so its order changed per
  process (hash randomisation). Use dict.fromkeys to dedupe in document
  order; the old fixtures had no metadata so this never showed.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Replace the 15 hand-made output.xml/log.html fixtures with 18 generated
from two simulated projects (WebshopUI, WebshopAPI) whose behaviour is
declared in tests/robot/resources/generator/libraries/profiles.py:
persistent, flaky, broken-since and fixed-since tests, outage runs, an
all-green and a pass+skip-only run, TRY/EXCEPT exceptions, feature-flag
skips, duration trends and run/suite metadata, so every dashboard graph
has data. Timestamps are shifted to a fixed schedule and durations
scaled, so regenerating keeps run identities stable.

The generator suites are not picked up by any runner; robot.toml gives
RobotCode the python-path for their fake libraries.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Python unit tests pick fixtures by glob instead of file name. Robot CLI
and database references and reference screenshots regenerated in the
Docker image; obsolete overview screenshots removed. The timezone mask
in the database comparison accepts missing microseconds and negative
offsets, which the new fixtures use.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
CI robot job took ~4.7 min: ~110 s of container setup and 155 s of
tests. Now 75 s of tests and a checkout + pip install:

- Browser suites generate one shared dashboard per run (pabot lock)
  instead of parsing all 18 fixtures per test; each test still gets its
  own browser context. Cleanup lives in __init__.robot because pabot
  loads it without a resolvable path for Resource imports.
- Chart animations are disabled through tests/robot/resources/
  test_config.json (-j). The settings UI "duration" only scales the
  stagger delay in graph_config.js, Chart.js still animated 1 s, which
  the old Sleep 1s was covering.
- Wait For Dashboard Idle replaces that sleep: Open Dashboard injects a
  test-only window.dashboard_is_idle() (resources/scripts/
  dashboard_idle.js, unit-tested) that is true only when no spinner,
  overlay, modal/backdrop, jQuery fade or Chart.js animation is active
  and 50 ms have passed since. It must stay synchronous: Playwright's
  waitForFunction treats a returned Promise as truthy.
- pabot runs 4 processes (ROBOT_PROCESSES overrides). Failed tests are
  rerun once and merged with rebot --merge, so a transient browser
  crash or timing race does not fail the pipeline.
- The robot job runs in a prebuilt image published to GHCR by the new
  test-image.yml workflow (from the existing robot Dockerfile) instead
  of installing pip, the test requirements and rfbrowser every run.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Cross-platform replacement for the hard-coded command list in
example.bat: derives tags, versions, timezones and custom filters from
the generated fixtures and copies the result into example/. example.bat
is now a one-line wrapper. Example dashboard, database and message
config rebuilt from the new fixtures.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The Browser library installs its own Playwright (node side), which can
be newer than the Playwright base image's, so rfbrowser init used to
download a second full browser set next to the base image's three.
Drop the base browsers, install only chromium for the library's
version and flatten the result into a single layer so the deleted files
and caches are really gone: 4.9 GB -> 2.3 GB, same fonts and system
libraries so the reference screenshots are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
A single flattened layer is downloaded and decompressed serially, which
made the container start slower than with the larger layered image.
Copy the filesystem in a few similar-sized layers instead so they are
pulled in parallel, and push them zstd-compressed from test-image.yml.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Brings the fixture generator, the regenerated output.xml fixtures, the
faster robot pipeline and the example script onto this branch so they
reach main together with the rerun feature.

Conflict resolution:
- 02_overview.robot: Webshop project names from fixture-generator, but
  without the per-project version filter tests that #322 removed
  (and their prj*V1_1.png references).
- test_processors.py: the generated fixtures contain TRY/EXCEPT
  exceptions, so the exceptions assertion from fixture-generator wins.
- Overview/keyword reference screenshots: fixture-generator versions;
  they are refreshed from a Docker run in the follow-up commit.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
`robot --rerunfailed` + `rebot --merge` keeps only the final result of a
re-executed test, but records every earlier attempt (status + message)
in the test message as HTML. The dashboard now parses that history into
a JSON `attempts` column on the tests table (oldest to newest) and
stores the final attempt's message as the test message instead of the
raw merge HTML that used to end up in the messages graph. Merged
suites lose their start time in rebot; the earliest test start is used
as a fallback so the runs/suites tables no longer show null.

Front end:
- Test section filter bar gets a Reruns select (Mark Reruns default /
  Final Result / First Attempt), persisted in settings.switch.
  Re-executed tests get a blue border in the Test Statistics timeline
  and scatter, Most Flaky, Recent Most Flaky, Most Failed, Recent Most
  Failed and Messages timelines; tooltips list every attempt with its
  message. First Attempt shows the status of the first try. Final
  Result behaves exactly as before.
- Most Flaky counts a status change inside the attempt history as a
  flip, so a test that only passes on retry every night shows up.
- Compare page gets its own Reruns select for the Tests graph.
- Overview run cards show "Rerun: N (fixed M)" next to the status
  lines when tests were re-executed.
- Stat widgets: Re-executed Tests, Recovered On Rerun, Failed All
  Attempts. Tables page: attempts column on the tests table.

The Reruns control lives in the section filter bars because the graph
headers cannot fit a third control at the default tile width; the bar
row gap is an integer so a wrapped bar does not shift the sections
below by a sub-pixel amount.

Tests: parser/DB/processor unit tests, JS unit tests for the graph data
modules, a robot test that merges two fixtures with rebot and checks the
marked bars per view, and a robot test for the exceptions table.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
RERUNS in generate.py re-executes the failed tests of WebshopUI run 7
(one rerun) and WebshopAPI run 5 (two reruns) with robot --rerunfailed
and merges the attempts with rebot --merge, so the fixtures carry an
attempt history for the rerun feature. The merged output keeps the
scheduled start as `generated`, so run identities and file names are
unchanged.

Fixtures regenerated; reference screenshots, database_output and the
example dashboard refreshed from the Docker image.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
timdegroot1996 and others added 3 commits September 18, 2026 03:33
buildx attaches provenance/SBOM attestations by default, which turns the
push into an image index whose children appear as "untagged" versions of
the GHCR package. Deleting one of those in the package UI breaks the
tagged image (manifest unknown on pull), which is what took the CI job
container down on 2026-09-18. Disable the attestations so the package
has exactly one version per push and nothing that looks deletable.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Any upload path works after `rebot --merge` (script, /add-outputs, admin
page); only the listener does not, because it fires when the robot run
ends, before the merged file exists. Also import `re` names directly in
processors.py to match the other imports.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
robotframework 7.4.2 -> 7.5 and robotframework-browser 20.0.0 -> 20.4.0
(chromium 1234); the reference screenshots are unchanged. test-image.yml
keeps the three newest package versions and deletes the rest after
every push, so refreshing the image no longer leaves old versions
behind.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Support rerun/retry history by linking separate original + rerun uploads

1 participant