Fix flaky view_to_iframe and django_form submit tests - #302
Merged
Merged
Conversation
Archmonger
marked this pull request as ready for review
September 22, 2026 18:08
Archmonger
force-pushed
the
fix-flaky-iframe-component-test
branch
from
September 22, 2026 20:45
4363d95 to
5a10bba
Compare
`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
force-pushed
the
fix-flaky-iframe-component-test
branch
from
September 22, 2026 20:53
5a10bba to
e6c8471
Compare
`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.
test_component_view_to_iframe_* testsview_to_iframe and django_form submit tests
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Umbrella PR that fixes the flaky tests surfacing in
Test / python-sourceCI. 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_iframerendered the<iframe>withloading="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/, sonavigate_to_pageperforms no navigation and the container is never scrolled. Under CI's unpinned Chromium the deferred fetch could exceed the 30swait_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 viaextra_props={"loading": "lazy"}(merged after the defaults). Verified: all 6 iframe documents load immediately aftergoto(wait_until="load")at 1280×720 with no scrolling.2.
test_form_*(test change: retry empty/form submits)Root cause.
DjangoFormregisters a nativesubmitlistener that callspreventDefault()and forwards form data over the WebSocket, but the listener is attached incomponentDidMountand 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 secondStarting client...with no precedingDisconnected!/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-submittest_form_async_events— same two pathstest_form_basic— empty-submit.errorlisttest_form_bootstrap— empty-submit.invalid-feedback(usesbutton[type=submit])test_form_orm_model— empty-submit.errorlistRe-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 --checkandpyright srcpass (0 errors).test_components.pyunder CPU-contention load: 74 passed (exit 0), several times over.f41bda5) was fully green (Test+CodeQL).