Skip to content

Fix two flaky tests: subdeck flashcard-selector button and reminder dialog#1301

Draft
RisingOrange wants to merge 3 commits into
mainfrom
worktree-fix-flaky-tests
Draft

Fix two flaky tests: subdeck flashcard-selector button and reminder dialog#1301
RisingOrange wants to merge 3 commits into
mainfrom
worktree-fix-flaky-tests

Conversation

@RisingOrange
Copy link
Copy Markdown
Collaborator

Related issues

N/A — flakes spotted on the NRT-749 / 751 / 763 / 764 CI runs. Findings written up in .claude/worktrees/NRT-749-qa-followup/notes/nrt_flaky_tests.md.

Proposed changes

Two unrelated CI flakes, fixed in one PR off main so they don't get tied to any in-flight feature branch.

1. tests/addon/test_integration.pytest_flashcard_selector_button_exists_for_subdeck_of_deck_with_note_embeddings

Replace fixed qtbot.wait(500) + one-shot evalWithCallback DOM check with qtbot.wait_until polling. The flashcard-selector button is injected asynchronously after set_current_deck fires overview_did_refresh, and the subdeck path is a touch slower than the parent-deck path because it walks ancestors to find the AnkiHub deck. A fixed 500ms wait is unreliable under CI load.

2. tests/addon/test_unit.pyTestShowNextDueDateReminderDialog

Add a class-scoped autouse fixture that clears _reminder_dialog_state.queue / .dialog and drains pending QTimer.singleShot callbacks before each test's @patch decorators activate.

Root cause: real-dialog tests connect dialog.finishedQTimer.singleShot(0, _show_next_due_date_reminder_dialog). A stray late timer was firing inside this unit test's mock window and recording a SubdeckDueDateReminderDialog(parent=<real AnkiQt>) call on the mocked dialog class, making assert_called_once_with(parent=mock_aqt.mw) fail with the real aqt.mw as the parent. Clearing the queue + draining processEvents() before mocks bind means any stray timer hits the empty-queue early-return without touching the mocks.

Not in this PR: a single sighting of tests/addon/performance/test_subdeck_operations.py::TestFlattenDeck::test_basic flaked once on NRT-751 — not investigated; revisit if it recurs.

How to reproduce

These are CI flakes; locally they pass consistently. To verify the fixes:

xvfb-run -a uv run pytest tests/addon/test_unit.py::TestShowNextDueDateReminderDialog -n 0
xvfb-run -a uv run pytest "tests/addon/test_integration.py::TestFlashCardSelector::test_flashcard_selector_button_exists_for_subdeck_of_deck_with_note_embeddings" -m sequential -n 0

Both pass; the subdeck integration test runs in ~1.3s.

Screenshots and videos

N/A — test-only change.

Further comments

Intentionally scoped: only the two flakes I could root-cause. The flatten-deck performance test gets a fresh look if it shows up again.

…ialog

- test_flashcard_selector_button_exists_for_subdeck_of_deck_with_note_embeddings:
  replace fixed qtbot.wait(500) + one-shot DOM check with qtbot.wait_until
  polling. The button is injected asynchronously after overview_did_refresh
  and the fixed wait is unreliable under CI load.

- TestShowNextDueDateReminderDialog: add class-scoped autouse fixture that
  clears _reminder_dialog_state and drains pending QTimer.singleShot
  callbacks before each test's @patch decorators activate. Real-dialog tests
  connect dialog.finished -> singleShot(0, _show_next_due_date_reminder_dialog),
  and a stray late timer was firing inside this test's mock window and
  recording a SubdeckDueDateReminderDialog(parent=<real AnkiQt>) call on the
  mocked dialog class, making the assert_called_once_with(parent=mock_aqt.mw)
  check fail.
Codex pointed out that TestShowSubdeckDueDateReminders also touches
_reminder_dialog_state and leaves the queue populated (it mocks out
_show_next_due_date_reminder_dialog, so the queue never drains). A late
real QTimer.singleShot firing afterwards would then act on real aqt with
that residue.

Pull the setup/teardown out of TestShowNextDueDateReminderDialog into a
module-level fixture and apply it to both reminder-dialog test classes via
pytest.mark.usefixtures. Also add the symmetric post-yield teardown reset
so each test cleans up after itself.
The inline closure in the subdeck flake fix was a copy-pastable shape; lift
it to a module-level helper so future "wait until DOM element exists" tests
have something to reach for.

Tried adopting it in test_flashcard_selector_send_note_suspension_states_message,
but that test depends on the original qtbot.wait(500) to let the webview's
_domDone flip true before any evalWithCallback runs. Polling immediately
queues an eval that sits there until the wait_callback times out at 5s.
Left that test on its qtbot.wait(500) — it isn't flaky and isn't worth
destabilising for the refactor.
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