From 6531f9c279f74f6e84cb01510df960ad0e63c54e Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 23 Jan 2026 22:49:41 -0600 Subject: [PATCH 1/9] Add more registrations test stability --- registrations/test/integration/admin_test.exs | 8 ++++---- registrations/test/integration/invitations_test.exs | 3 ++- registrations/test/support/pages/details.ex | 9 +++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/registrations/test/integration/admin_test.exs b/registrations/test/integration/admin_test.exs index e30f2dbc..0b4f86b6 100644 --- a/registrations/test/integration/admin_test.exs +++ b/registrations/test/integration/admin_test.exs @@ -214,6 +214,7 @@ defmodule Registrations.Integration.UnmnemonicDevices.Admin do alias Registrations.Pages.Login alias Registrations.Pages.Nav alias Wallaby.Query + require WaitForIt test "admin can create and update settings", %{session: session} do insert(:octavia, admin: true) @@ -235,10 +236,9 @@ defmodule Registrations.Integration.UnmnemonicDevices.Admin do click(session, Query.css("button[type=submit]")) - assert current_path(session) == "/settings" - - assert has?(session, Query.css("#settings_begun:checked")) - assert text(session, Query.css(".alert-info")) == "Settings updated successfully." + WaitForIt.wait!(current_path(session) == "/settings") + Nav.assert_info_text(session, "Settings updated successfully.") + WaitForIt.wait!(has?(session, Query.css("#settings_begun:checked"))) end test "non-admins cannot access the user list or messages", %{session: session} do diff --git a/registrations/test/integration/invitations_test.exs b/registrations/test/integration/invitations_test.exs index 919551e2..49e80214 100644 --- a/registrations/test/integration/invitations_test.exs +++ b/registrations/test/integration/invitations_test.exs @@ -29,7 +29,8 @@ defmodule Registrations.Integration.Invitations do Details.InviteButton.click(session) - refute Details.InviteButton.present?(session) + Nav.assert_info_text(session, "Invitation sent") + Details.InviteButton.assert_absent(session) wait_for_emails([invitation_email]) assert invitation_email.to == [{"", "bedap@example.com"}] diff --git a/registrations/test/support/pages/details.ex b/registrations/test/support/pages/details.ex index be24edf3..d946847c 100644 --- a/registrations/test/support/pages/details.ex +++ b/registrations/test/support/pages/details.ex @@ -149,6 +149,7 @@ defmodule Registrations.Pages.Details do @moduledoc false alias Wallaby.Browser alias Wallaby.Query + require WaitForIt def present?(session) do Browser.has?(session, Query.css("[data-test-invite]")) @@ -157,6 +158,14 @@ defmodule Registrations.Pages.Details do def click(session) do Browser.click(session, Query.css("[data-test-invite]")) end + + def assert_absent(session, message \\ nil) do + WaitForIt.wait!(!present?(session)) + + if present?(session) do + raise(message || "Expected invite button to be absent") + end + end end defmodule Attending do From 021a82bd6d7c247ed98feb32186b1d5bffa6d55d Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 23 Jan 2026 23:17:11 -0600 Subject: [PATCH 2/9] Add empty commit From c0071e3e39d0835e0c52b7c367d0d21cf01ebd14 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 23 Jan 2026 23:27:14 -0600 Subject: [PATCH 3/9] Add empty commit From 84fc0a1f3309fa23fc25219d8bc49e21a2753a64 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 23 Jan 2026 23:41:57 -0600 Subject: [PATCH 4/9] Add empty commit From f13f33efc2d2604addbc0dc8ca5287c93613fe4d Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 24 Jan 2026 10:08:20 -0600 Subject: [PATCH 5/9] Add another test waiter --- registrations/test/support/pages/nav.ex | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/registrations/test/support/pages/nav.ex b/registrations/test/support/pages/nav.ex index 7f447744..36c5506b 100644 --- a/registrations/test/support/pages/nav.ex +++ b/registrations/test/support/pages/nav.ex @@ -88,7 +88,17 @@ defmodule Registrations.Pages.Nav do def click(session) do WaitForIt.wait!(Browser.has?(session, Query.css(@selector)), timeout: @logout_timeout) - Browser.click(session, Query.css(@selector)) + WaitForIt.wait!( + try do + Browser.click(session, Query.css(@selector)) + true + rescue + Wallaby.StaleReferenceError -> false + Wallaby.QueryError -> false + RuntimeError -> false + end, + timeout: @logout_timeout + ) WaitForIt.wait!( not Browser.has?(session, Query.css(@selector)) or Browser.has?(session, Query.css("a.login")), From a322af0d3586785e8068e68fc2cf75438e36f083 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 24 Jan 2026 10:08:33 -0600 Subject: [PATCH 6/9] Change CI to run duplicates --- .github/workflows/ci-registrations.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci-registrations.yml b/.github/workflows/ci-registrations.yml index 10b0fcf2..272b833e 100644 --- a/.github/workflows/ci-registrations.yml +++ b/.github/workflows/ci-registrations.yml @@ -10,6 +10,11 @@ on: jobs: mix-test: + name: Mix test (run ${{ matrix.repeat }}) + strategy: + fail-fast: false + matrix: + repeat: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] runs-on: ubuntu-latest services: db: From 210c15ef074ea0bbe90e614a33cf034954f1e89d Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 24 Jan 2026 10:29:04 -0600 Subject: [PATCH 7/9] Add more stability fixes --- registrations/test/support/pages/details.ex | 86 +++++++++++++++------ registrations/test/support/pages/nav.ex | 9 +++ 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/registrations/test/support/pages/details.ex b/registrations/test/support/pages/details.ex index d946847c..0042ab62 100644 --- a/registrations/test/support/pages/details.ex +++ b/registrations/test/support/pages/details.ex @@ -16,15 +16,11 @@ defmodule Registrations.Pages.Details do end def proposers(session, opts \\ []) do - session - |> rows_with_wait("[data-test-proposers]", opts) - |> Enum.map(&email_and_text_row(&1)) + map_rows_with_wait(session, "[data-test-proposers]", opts, &email_and_text_row/1) end def mutuals(session, opts \\ []) do - session - |> rows_with_wait("[data-test-mutuals]", opts) - |> Enum.map(fn row -> + map_rows_with_wait(session, "[data-test-mutuals]", opts, fn row -> proposed_team_name_element = Browser.find(row, Query.css(".proposed-team-name")) risk_aversion_element = Browser.find(row, Query.css(".risk-aversion")) @@ -46,21 +42,20 @@ defmodule Registrations.Pages.Details do end def proposals_by_mutuals(session, opts \\ []) do - session - |> rows_with_wait("[data-test-proposals-by-mutuals]", opts) - |> Enum.map(&email_and_text_row(&1)) + map_rows_with_wait( + session, + "[data-test-proposals-by-mutuals]", + opts, + &email_and_text_row/1 + ) end def invalids(session, opts \\ []) do - session - |> rows_with_wait("[data-test-invalids]", opts) - |> Enum.map(&email_and_text_row(&1)) + map_rows_with_wait(session, "[data-test-invalids]", opts, &email_and_text_row/1) end def proposees(session, opts \\ []) do - session - |> rows_with_wait("[data-test-proposees]", opts) - |> Enum.map(&email_and_text_row(&1)) + map_rows_with_wait(session, "[data-test-proposees]", opts, &email_and_text_row/1) end def fill_team_emails(session, team_emails) do @@ -199,20 +194,38 @@ defmodule Registrations.Pages.Details do end def assert_present(session, message \\ nil) do - WaitForIt.wait!(present?(session)) + WaitForIt.wait!(safe_present?(session) == {:ok, true}) - if not present?(session) do + if safe_present?(session) != {:ok, true} do raise(message || "Expected attending error to be present") end end def assert_absent(session, message \\ nil) do - WaitForIt.wait!(!present?(session)) + WaitForIt.wait!(safe_present?(session) == {:ok, false}) - if present?(session) do + if safe_present?(session) != {:ok, false} do raise(message || "Expected attending error to be absent") end end + + defp safe_present?(session) do + try do + {:ok, present?(session)} + rescue + Wallaby.StaleReferenceError -> :error + Wallaby.QueryError -> :error + RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + end + end + + defp handle_runtime_dom_error(%RuntimeError{message: message} = error, stacktrace) do + if String.contains?(message, "does not belong to the document") do + :error + else + reraise error, stacktrace + end + end end end @@ -246,14 +259,15 @@ defmodule Registrations.Pages.Details do Browser.click(session, Query.css("#submit")) end - defp rows_with_wait(session, selector, opts) do + defp map_rows_with_wait(session, selector, opts, mapper) do expected = Keyword.get(opts, :count) - if expected do - WaitForIt.wait!(length(Browser.all(session, Query.css(selector))) == expected) - end + WaitForIt.wait!(match?({:ok, _}, safe_map_rows(session, selector, expected, mapper))) - Browser.all(session, Query.css(selector)) + case safe_map_rows(session, selector, expected, mapper) do + {:ok, rows} -> rows + :retry -> map_rows_with_wait(session, selector, opts, mapper) + end end defp email_and_text_row(row) do @@ -273,4 +287,28 @@ defmodule Registrations.Pages.Details do |> Enum.member?(class_name) end + defp safe_map_rows(session, selector, expected, mapper) do + try do + rows = Browser.all(session, Query.css(selector)) + + if expected && length(rows) != expected do + :retry + else + {:ok, Enum.map(rows, mapper)} + end + rescue + Wallaby.StaleReferenceError -> :retry + Wallaby.QueryError -> :retry + RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + end + end + + defp handle_runtime_dom_error(%RuntimeError{message: message} = error, stacktrace) do + if String.contains?(message, "does not belong to the document") do + :retry + else + reraise error, stacktrace + end + end + end diff --git a/registrations/test/support/pages/nav.ex b/registrations/test/support/pages/nav.ex index 36c5506b..4c8732f2 100644 --- a/registrations/test/support/pages/nav.ex +++ b/registrations/test/support/pages/nav.ex @@ -208,6 +208,15 @@ defmodule Registrations.Pages.Nav do rescue Wallaby.StaleReferenceError -> :error Wallaby.QueryError -> :error + RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + end + end + + defp handle_runtime_dom_error(%RuntimeError{message: message} = error, stacktrace) do + if String.contains?(message, "does not belong to the document") do + :error + else + reraise error, stacktrace end end From 4f6e8a13c3574a4d3210723b6c6ffc7f2cdd44c9 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 24 Jan 2026 10:38:24 -0600 Subject: [PATCH 8/9] Fix build errors --- registrations/test/support/pages/details.ex | 4 ++-- registrations/test/support/pages/nav.ex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registrations/test/support/pages/details.ex b/registrations/test/support/pages/details.ex index 0042ab62..a9d3fb7e 100644 --- a/registrations/test/support/pages/details.ex +++ b/registrations/test/support/pages/details.ex @@ -215,7 +215,7 @@ defmodule Registrations.Pages.Details do rescue Wallaby.StaleReferenceError -> :error Wallaby.QueryError -> :error - RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + error in RuntimeError -> handle_runtime_dom_error(error, __STACKTRACE__) end end @@ -299,7 +299,7 @@ defmodule Registrations.Pages.Details do rescue Wallaby.StaleReferenceError -> :retry Wallaby.QueryError -> :retry - RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + error in RuntimeError -> handle_runtime_dom_error(error, __STACKTRACE__) end end diff --git a/registrations/test/support/pages/nav.ex b/registrations/test/support/pages/nav.ex index 4c8732f2..64890718 100644 --- a/registrations/test/support/pages/nav.ex +++ b/registrations/test/support/pages/nav.ex @@ -208,7 +208,7 @@ defmodule Registrations.Pages.Nav do rescue Wallaby.StaleReferenceError -> :error Wallaby.QueryError -> :error - RuntimeError = error -> handle_runtime_dom_error(error, __STACKTRACE__) + error in RuntimeError -> handle_runtime_dom_error(error, __STACKTRACE__) end end From 574a3e9dd99fcc4183c578afc454a220e5f77040 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Sat, 24 Jan 2026 10:55:16 -0600 Subject: [PATCH 9/9] Add more stability changes ugh --- registrations/test/support/pages/nav.ex | 26 +++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/registrations/test/support/pages/nav.ex b/registrations/test/support/pages/nav.ex index 64890718..84be1c51 100644 --- a/registrations/test/support/pages/nav.ex +++ b/registrations/test/support/pages/nav.ex @@ -87,7 +87,7 @@ defmodule Registrations.Pages.Nav do end def click(session) do - WaitForIt.wait!(Browser.has?(session, Query.css(@selector)), timeout: @logout_timeout) + WaitForIt.wait!(safe_has?(session, @selector), timeout: @logout_timeout) WaitForIt.wait!( try do Browser.click(session, Query.css(@selector)) @@ -95,16 +95,34 @@ defmodule Registrations.Pages.Nav do rescue Wallaby.StaleReferenceError -> false Wallaby.QueryError -> false - RuntimeError -> false + error in RuntimeError -> handle_runtime_dom_error(error, __STACKTRACE__) end, timeout: @logout_timeout ) WaitForIt.wait!( - not Browser.has?(session, Query.css(@selector)) or - Browser.has?(session, Query.css("a.login")), + not safe_has?(session, @selector) or + safe_has?(session, "a.login"), timeout: @logout_timeout ) end + + defp safe_has?(session, selector) do + try do + Browser.has?(session, Query.css(selector)) + rescue + Wallaby.StaleReferenceError -> false + Wallaby.QueryError -> false + error in RuntimeError -> handle_runtime_dom_error(error, __STACKTRACE__) + end + end + + defp handle_runtime_dom_error(%RuntimeError{message: message} = error, stacktrace) do + if String.contains?(message, "does not belong to the document") do + false + else + reraise error, stacktrace + end + end end defmodule LoginLink do