Skip to content

Fix flaky view_to_iframe and django_form submit tests - #302

Merged
Archmonger merged 5 commits into
mainfrom
fix-flaky-iframe-component-test
Sep 23, 2026
Merged

Archmonger merged 5 commits into
mainfrom
fix-flaky-iframe-component-test

Conversation

@Archmonger

@Archmonger Archmonger commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

Umbrella PR that fixes the flaky tests surfacing in Test / python-source CI. Two independent flakes are addressed below, plus a probe of the whole suite under load to confirm nothing else is flaky.


1. test_component_view_to_iframe_* (product change: eager iframe)

Root cause. view_to_iframe rendered the <iframe> with loading="lazy", so the browser only fetched the iframe's document once it scrolled close to the viewport. These iframes sit below the fold of the home page and the shared Playwright page is already sitting on /, so navigate_to_page performs no navigation and the container is never scrolled. Under CI's unpinned Chromium the deferred fetch could exceed the 30s wait_for, so the iframe's [data-success=...] element never appeared.

Fix. Drop the forced loading="lazy" so iframes load eagerly (the HTML default); the iframe document is then fetched as part of the parent page's initial load. Lazy behavior remains opt-in via extra_props={"loading": "lazy"} (merged after the defaults). Verified: all 6 iframe documents load immediately after goto(wait_until="load") at 1280×720 with no scrolling.

2. test_form_* (test change: retry empty/form submits)

Root cause. DjangoForm registers a native submit listener that calls preventDefault() and forwards form data over the WebSocket, but the listener is attached in componentDidMount and can momentarily lag the rendered <form> under load. A click landing during that window performs a native form submission: the browser reloads the page, which resets all ReactPy state so the awaited callback never fires. This matches the CI signature — a second Starting client... with no preceding Disconnected!/Reconnecting... (a WebSocket reconnect logs those; a page reload does not). The pre-existing retry guard only covered the later filled re-submit of the event forms, leaving every empty-submit assertion exposed.

Fix. Generalize the existing retry guard into a shared _retry_submit_until(selector, fill=..., submit_selector=...) helper and route every bare "submit, then wait for the error state" assertion through it:

  • test_form_sync_events — empty-submit #error (the reported failure) and the filled re-submit
  • test_form_async_events — same two paths
  • test_form_basic — empty-submit .errorlist
  • test_form_bootstrap — empty-submit .invalid-feedback (uses button[type=submit])
  • test_form_orm_model — empty-submit .errorlist

Re-clicking the submit until the awaited state surfaces makes each interaction deterministic instead of racing the listener mount. Test-only; no product code touched.


Testing

  • ruff check / ruff format --check and pyright src pass (0 errors).
  • All 5 form tests: green solo and across repeated CPU-contention load runs.
  • Full test_components.py under CPU-contention load: 74 passed (exit 0), several times over.
  • CI on the iframe-fix commit (f41bda5) was fully green (Test + CodeQL).

@Archmonger
Archmonger marked this pull request as ready for review September 22, 2026 18:08
@Archmonger
Archmonger requested a review from a team as a code owner September 22, 2026 18:08
@Archmonger
Archmonger force-pushed the fix-flaky-iframe-component-test branch from 4363d95 to 5a10bba Compare September 22, 2026 20:45
`view_to_iframe` previously forced `loading="lazy"` on the rendered iframe.
The browser only fetches a lazy iframe's document once it is scrolled close to
the viewport. On the home page these iframes sit well below the fold, and the
test harness performs no navigation (and no scroll) when it is already on "/",
so a deferred lazy iframe is never fetched and a consumer waiting on the
iframe's contents times out (observed intermittently on slow CI runners, e.g.
`test_component_view_to_iframe_sync_func` on windows-latest).

An eager iframe (the HTML default, no `loading` attribute) is a sub-resource
the browser fetches as part of the parent page's initial load, so its document
is guaranteed to be present as soon as the page loads. This makes loading
deterministic instead of depending on the browser's load-timing heuristics,
which is the real root cause of the flake.

Lazy loading remains available as an opt-in for users who prefer it: pass
`extra_props={"loading": "lazy"}`.
@Archmonger
Archmonger force-pushed the fix-flaky-iframe-component-test branch from 5a10bba to e6c8471 Compare September 22, 2026 20:53
Archmonger and others added 3 commits September 22, 2026 14:09
`test_form_sync_events` intermittently times out waiting for
`#error[data-value='true']` after submitting the empty form. The
`DjangoForm` client component registers a native `submit` listener that
calls `preventDefault()` and forwards the form data over the WebSocket,
but the listener is attached in `componentDidMount` and can momentarily
lag the rendered `<form>` under load. A click landing during that window
performs a native form submission: the browser reloads the page (a second
`Starting client...` with no preceding `Disconnected!`, matching the CI
signature), which resets all ReactPy state so `on_error` never fires.

The existing retry guard only covered the later *filled* re-submit. Extend
it into a shared `_retry_submit_until(selector, fill=...)` helper and use
it for the empty-submit path too (and, symmetrically, for the async-event
form which shares the same interaction). Re-clicking the submit until the
awaited callback surfaces makes the interaction deterministic instead of
racing the mount. Test-only change; no product code is touched.
@Archmonger Archmonger changed the title Fix flaky test_component_view_to_iframe_* tests Fix flaky view_to_iframe and django_form submit tests Sep 23, 2026
The reviewer flagged that `test_form_basic`, `test_form_bootstrap`, and
`test_form_orm_model` use the identical bare `submit -> wait_for(error)`
pattern as `test_form_sync_events`, so they share the same latent exposure
to a native form submission (which reloads the page and resets state)
when the `DjangoForm` `componentDidMount` submit listener has not yet
attached under load.

Extend the shared `_retry_submit_until` guard to those three sites as
well. Added a `submit_selector` parameter because the bootstrap form uses
`button[type=submit]` instead of `input[type=submit]`.

Test-only; no product code touched. Verified: `hatch fmt` clean, full
`test_components.py` green under CPU-contention load (74 passed), and the
5-test form family green across repeated load runs.
@Archmonger
Archmonger merged commit 789c885 into main Sep 23, 2026
23 checks passed
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.

1 participant