Skip to content

feat: submit account flows structurally, not by English caption - #13

Merged
andregoepel merged 1 commit into
mainfrom
feature/culture-agnostic-account-helpers
Aug 23, 2026
Merged

andregoepel merged 1 commit into
mainfrom
feature/culture-agnostic-account-helpers

Conversation

@andregoepel

Copy link
Copy Markdown
Owner

E2ETestBase's account helpers located their submit control by visible
English text, so they could not drive a login or registration page rendered
in another language.

It surfaced writing German E2E coverage for finance-app: a test set
Accept-Language: de, called LoginAsAdminAsync(), and hung until Playwright's
30s timeout waiting for a button named "Log in". finance-app currently works
around it by logging in under English and switching culture afterwards.

Both flows now submit through ClickAccountSubmitAsync
PageExtensions.ClickSubmitAsync, which finds the control by type="submit".

Why the selector is page-wide, and why that is load-bearing

The obvious form is form button[type=submit]. On /Account/Login it matches
nothing.

Login.razor:30-33 renders the submit button outside the form — in the action
bar so it can sit beside the passkey button — wired up by the HTML
form="login-form" attribute, while the form itself comes from the nested
LoginForm.razor:10 (<RadzenTemplateForm … id="login-form">).

Register.razor:49 does have its button inside its form, so checking that
page alone would have produced the wrong answer and a login helper that hangs.

FormScopedSubmitSelector_LoginPageMarkup_MatchesNothing pins this, so a future
"tightening" of the selector fails here rather than in a consumer's CI.

Page-wide is unambiguous anyway: every account page in the identity package
renders exactly one ButtonType.Submit; PasskeySubmit.razor:5 is
type="button"; the sign-in handoff form has no button; the language switcher
renders plain anchors.

Why not a caption parameter

Backward compatible, but self-defeating. The rest of these helpers is already
structural — LoginAsync hard-codes /Account/Login and fills [name='Email']
/ [name='NewPassword'], which only work against this one package's markup
regardless. The submit click was the last caption-based step; an option would
have handed every consumer a knob to keep in sync with a translation file, for
a page with exactly one submit control.

Escape hatch instead of a knob: ClickSubmitAsync takes an optional CSS
scope, and ClickAccountSubmitAsync is virtual.

Compatibility

Source-compatible. LoginAsAdminAsync(), LoginAsync, RegisterAsync,
ClickButtonAsync keep their signatures; nothing removed or made stricter.
Additions only. Release v1.3.0 after merge (current release is v1.2.0).

Tests, and their honest limit

10 new tests pin SubmitControlSelector against AngleSharp-parsed markup
transcribed from the identity pages' structure — detached submit,
type="button" passkey sibling, button-less handoff form, anchor switcher —
with German captions in the fixtures.

What this does not prove: it exercises AngleSharp's CSS engine, not
Playwright's, against transcribed rather than captured HTML. It validates the
selector's shape decisions, not that the real page loads. A real localized login
cannot be rendered here — /Account/Login lives in
AndreGoepel.Marten.Identity.Blazor, which consumes this package, so
referencing it would point the dependency arrow backwards. The live proof stays
in each consuming repo's *.E2ETests, as CLAUDE.md already says.

AngleSharp becomes a direct test-only reference. It was already in the graph
transitively via bUnit at the same 1.7.0, so the lockfile moves are
TransitiveCentralTransitive with no version drift, and
dotnet restore --locked-mode passes.

Follow-up (not in this PR)

finance-app's LocalizationTests can drop its workaround once it takes 1.3.0 —
inverting to UseCultureAsync("de") then LoginAsAdminAsync().
ProvisionAdminAsync runs in its own throwaway context with no locale set, so
ProvisionAdminButtonText = "Create admin" keeps matching. That caption stays
caption-based deliberately: the Setup page belongs to each host app, not to the
identity package.

Verification

  • dotnet build — 0 errors, 0 warnings
  • 42 tests pass (27 E2E incl. the 10 new, 15 bUnit)
  • dotnet csharpier check . clean · dotnet restore --locked-mode clean ·
    dotnet list package --vulnerable --include-transitive clean

Closes #12

E2ETestBase located the login and registration submit controls by their
visible English text, so the helpers could not drive a page rendered in
another language. Writing German E2E coverage for finance-app, a test set
Accept-Language: de, called LoginAsAdminAsync(), and hung until
Playwright's 30s timeout waiting for a button named "Log in".

Both flows now go through ClickAccountSubmitAsync, which finds the
control by type="submit" instead.

The selector is page-wide rather than the tempting form-scoped variant,
and that is load-bearing: Login.razor renders its submit button *outside*
the form, in the action bar beside the passkey control, wired up by the
HTML form="login-form" attribute, while the form itself comes from the
nested LoginForm component. `form button[type=submit]` matches nothing
there. Register.razor does have its button inside the form, so checking
that page alone would have given the wrong answer. A guard test pins the
form-scoped variant matching zero elements on the login shape.

Page-wide is unambiguous: every account page in the identity package
renders exactly one submit control, the passkey button is type="button",
and the language switcher renders anchors.

A caption parameter was the alternative and lost. The rest of these
helpers is already structural — the hard-coded /Account/Login path, the
[name='Email'] field lookups — so an option would only have handed every
consumer a knob to keep in sync with a translation file.

Source-compatible: additions only, no signature changed.

AngleSharp becomes a direct test-only reference. It was already in the
graph transitively via bUnit at the same 1.7.0, so the lockfile moves are
Transitive -> CentralTransitive with no version drift.

Closes #12
@andregoepel
andregoepel merged commit a7a8076 into main Aug 23, 2026
3 checks passed
@andregoepel
andregoepel deleted the feature/culture-agnostic-account-helpers branch August 23, 2026 05:54
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.

E2E account helpers cannot log in under a non-English culture

1 participant