From 8ad720da5db5999ecda787575dc45621d18a5498 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 11 Oct 2026 20:50:14 +0200 Subject: [PATCH] feat(auth): migrate public authentication presentation --- docs/redesign/phase-7/COMMITS.md | 4 +- docs/redesign/phase-7/p7-01/README.md | 90 + .../p7-01/evidence/public-auth-results.json | 6360 +++++++++++++++++ .../phase-7/p7-01/evidence/static-checks.json | 104 + .../auth-tests/auth-recovery.spec.ts | 140 + .../e2e/phase7-authentication.spec.ts | 224 + .../playwright.auth-recovery.config.ts | 66 + job-tracker-ui/scripts/localSMTPfixture.mjs | 125 + .../scripts/localSMTPfixture.test.mjs | 96 + job-tracker-ui/src/layout/AuthPageFrame.tsx | 27 + job-tracker-ui/src/login-page.test.tsx | 2 +- .../src/microsoft-legacy-relink-page.test.tsx | 24 +- .../src/public-auth-presentation.test.tsx | 241 + .../src/views/ForgotPasswordPage.tsx | 47 +- job-tracker-ui/src/views/LoginPage.tsx | 72 +- .../src/views/MicrosoftLegacyRelinkPage.tsx | 20 +- .../src/views/ResetPasswordPage.tsx | 47 +- job-tracker-ui/src/views/VerifyEmailPage.tsx | 33 +- 18 files changed, 7595 insertions(+), 127 deletions(-) create mode 100644 docs/redesign/phase-7/p7-01/README.md create mode 100644 docs/redesign/phase-7/p7-01/evidence/public-auth-results.json create mode 100644 docs/redesign/phase-7/p7-01/evidence/static-checks.json create mode 100644 job-tracker-ui/auth-tests/auth-recovery.spec.ts create mode 100644 job-tracker-ui/e2e/phase7-authentication.spec.ts create mode 100644 job-tracker-ui/playwright.auth-recovery.config.ts create mode 100644 job-tracker-ui/scripts/localSMTPfixture.mjs create mode 100644 job-tracker-ui/scripts/localSMTPfixture.test.mjs create mode 100644 job-tracker-ui/src/layout/AuthPageFrame.tsx create mode 100644 job-tracker-ui/src/public-auth-presentation.test.tsx diff --git a/docs/redesign/phase-7/COMMITS.md b/docs/redesign/phase-7/COMMITS.md index 462c0c2..bd5429f 100644 --- a/docs/redesign/phase-7/COMMITS.md +++ b/docs/redesign/phase-7/COMMITS.md @@ -2,11 +2,11 @@ Every completed checkpoint or coherent repair is committed and pushed normally to `codex/ui-redesign-phase4`, with immediate direct remote=HEAD verification. Production main remains `27657f31131cd95d42f0e1290853ec9c9b3959a4`; no force push, main push or automatic deployment. -Entry is the completed Phase6 successor receipt `b5319981ad3d1bd635daff861453d23c0de5320b`. Its [historical ledger](../phase-6/COMMITS.md) retains P6-05 `4669fb42e604c4728e8c420846ba78f298a8d90c` and original runtime provenance. Application source is unchanged from P6-03 `bd47214bdcd74336788dca0f294b8ddb9403cac1`; measured export `2LDblja_XnSBWSuY6ys_V`. +Entry is the completed Phase6 successor receipt `b5319981ad3d1bd635daff861453d23c0de5320b`. Its [historical ledger](../phase-6/COMMITS.md) retains P6-05 `4669fb42e604c4728e8c420846ba78f298a8d90c` and original runtime provenance. At P7-00 entry, application source was unchanged from P6-03 `bd47214bdcd74336788dca0f294b8ddb9403cac1`; inherited measured export `2LDblja_XnSBWSuY6ys_V`. Later migration rows record their own changed source/build identity. | Logical unit | Commit | Validation / push receipt | |---|---|---| -| P7-00 inventory, preservation/debt and unchanged-source baseline | PENDING — coordinator records actual hash after normal push | [P7-00](p7-00/README.md), [fresh finite baseline](p7-00/evidence/baseline.json); inherited runtime explicitly separated. P7-01 page changes wait for this push/remote verification | +| P7-00 inventory, preservation/debt and unchanged-source baseline | `27c54146ad6f9059295d169b533db7b1b6689210` | [P7-00](p7-00/README.md), [fresh finite baseline](p7-00/evidence/baseline.json) and [static checks](p7-00/evidence/static-checks.json); inherited runtime explicitly separated. Normal development push verified2026-10-11 14:44:45UTC: localHEAD=origin=live development27c5414; local/origin/live main unchanged27657f3; clean worktree. P7-01 entry dependency satisfied | Hashes and remote receipts are appended by a successor to avoid self-referential commit hashes. Each row records actual commands/results, source scope, remaining conditions, normal push and immediate direct remote identity; partial or blocked evidence is recorded without inventing success. Subsequent receipt metadata is a separately reviewable documentation unit, with its own hash reported after push rather than an endless receipt chain. diff --git a/docs/redesign/phase-7/p7-01/README.md b/docs/redesign/phase-7/p7-01/README.md new file mode 100644 index 0000000..2a18be0 --- /dev/null +++ b/docs/redesign/phase-7/p7-01/README.md @@ -0,0 +1,90 @@ +# P7-01 — Authentication and account-security presentation + +**Status: public authentication sub-slice CONDITIONAL GO; account-security implementation/acceptance PENDING.** This is the bounded execution/acceptance plan from the [approved handoff](../../phase-6/p6-05/PHASE7_HANDOFF.md). P7-00 `27c54146ad6f9059295d169b533db7b1b6689210` is pushed and direct development/localHEAD/origin equality verified at2026-10-11 14:44:45UTC; main unchanged and worktree clean at entry. Its [baseline](../p7-00/evidence/baseline.json) and [ledger](../COMMITS.md) satisfy entry. No new routes, identity/provider protocols or capability activation are included. Public source/results below are measured before their commit; the parent records its push receipt separately. This sub-slice does not complete P7-01. + +## Baseline and bounded commit scope + +The inherited accepted runtime baseline is backend1,033, frontend521/85, ordinary browser159, sidecar49 and separate existing security/HTTP/fidelity lanes. P7-00 performed fresh identity/routes/matrix/static checks only. New affected runtime evidence begins here; every count delta must identify actual added/removed cases and avoid adding focused subsets to full totals. + +| Coherent commit boundary within P7-01 | Existing source owners | Allowed presentation scope / exclusions | +|---|---|---| +| Public authentication | [Login/Register](../../../../job-tracker-ui/src/views/LoginPage.tsx), [Forgot](../../../../job-tracker-ui/src/views/ForgotPasswordPage.tsx), [Reset](../../../../job-tracker-ui/src/views/ResetPasswordPage.tsx), [Verify/email-change](../../../../job-tracker-ui/src/views/VerifyEmailPage.tsx), [Microsoft relink](../../../../job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx) | Reuse existing shared page/forms/state/error patterns; coherent headings/names/labels/focus, responsive layout and translated feedback. Preserve safe from/query/hash/state, remember-me, registration/verification/token/Turnstile/2FA and provider exchange semantics | +| Account identity/security | [Profile](../../../../job-tracker-ui/src/views/ProfilePage.tsx), [AuthStatus](../../../../job-tracker-ui/src/components/AuthStatusCard.tsx), [2FA](../../../../job-tracker-ui/src/components/TwoFactorSettingsCard.tsx), [Sessions](../../../../job-tracker-ui/src/components/SessionsSettingsCard.tsx) | Shared theme/section hierarchy, honest status/error/retry, dialog-local failure, locale dates and long metadata reflow. Preserve identity-only profile payload/local preference ownership, avatar/crop/remove, email confirmation/password, recovery/trusted-device/current-session/revoke semantics | +| Shared provider consumers affected by either boundary | [Google](../../../../job-tracker-ui/src/components/GoogleAuthCard.tsx), [Microsoft](../../../../job-tracker-ui/src/components/MicrosoftAuthCard.tsx) | Real consumers are LoginPage sign-in presentation and ProfilePage account-link presentation. Prove both after shared changes; preserve link password, popup origin/state, cancellation/no-exchange, safe return and unlink guards. Mailbox grants remain separate | + +The exact checkpoint remains P7-01; these are reviewable commit boundaries, not additional phases. Account changes follow the public-auth commit to avoid conflicting shared-provider edits. Keep existing save/fetch/security owners; no dead-code cleanup or backend concurrency/provider redesign hidden in presentation. + +## Read-only source findings to reproduce before repair + +| Observation at entry | Bounded correction candidate / required proof | +|---|---| +| TwoFactorSettingsCard status GET catches failure into null; null then selects Enable2FA action as though disabled | Distinct pending/known/failed status and deliberate retry; no misleading enabled/disabled assertion or setup action while unknown. Request counts, known enabled/disabled branch and failure→retry readback tests | +| Sessions revoke-others failure sets the global error rendered outside the still-open confirmation dialog | Durable alert inside that dialog, retained confirmation/retry/cancel and focus; no background-isolation workaround or automatic resend/revoke | +| AuthStatus signed-in sentence is a literal English sentence; account/security/provider dates use host-default locale | Existing i18n interpolation/natural EN/NB wording and explicit UI-locale date formatting; test actual two locales with fixed timestamps and shared provider presentations | +| Account child card headings default from typography variant; shell already owns page h1 and Profile has explicit contextual headings | Use existing section primitives and explicit contextual levels/names; prove loaded full hierarchy and preserve field/action names. Do not add a second h1/main | +| Profile load/avatar/remove/save/password errors still use raw response/message stringification, while email-change paths use shared helper | Reuse getApiErrorMessage with translated caller fallback and structured server explanations where reproduced; retained draft/local state and deliberate successful recovery. Do not change payload/save semantics | +| Trusted-device/session metadata uses absolute secondary-action list layout | Verify long device names/dates at320/390 and keyboard/zoom before any reflow repair; source layout alone is not measured overflow evidence | + +Installed MUI Dialog generates aria-labelledby and DialogTitle inherits its ID. Missing explicit ID props alone do not establish an unnamed-dialog defect; actual named-dialog/description/focus/error tests determine whether a repair is required. Preserve recovery confirmation before dismissing codes, password/code clearing and no automatic sensitive action on entry. + +## Required automated and native evidence + +| Scope | Existing anchors / additions needed | +|---|---| +| Local auth and safe navigation | [Login tests](../../../../job-tracker-ui/src/login-page.test.tsx), [Verify tests](../../../../job-tracker-ui/src/verify-email-page.test.tsx), [native auth return/expiry](../../../../job-tracker-ui/e2e/analytics-auth-repair.spec.ts). Exercise login/register/reset/verify/relink loaded/error/expired/used-token states, remember-me, recovery/rate-limit and unsafe/nested return rejection without route/protocol changes | +| Account identity versus Career | [Profile tests](../../../../job-tracker-ui/src/profile-page.test.tsx), [native account isolation](../../../../job-tracker-ui/e2e/account-isolation.spec.ts). Add identity-only save payload/readback, no canonical Career/CV/sibling write, local-only vs external read-only rules, pending email request/cancel/password failure/retry and owner-switch disposal | +| 2FA status/confirm/recovery/devices | [2FA tests](../../../../job-tracker-ui/src/two-factor-settings.test.tsx) currently cover enable→recovery and wrong-password disable. Add unknown/error/retry, known status, recovery acknowledgment/clear/cancel, trusted-device error/revoke and actual named modal trap/return; no real authenticator qualification claim | +| Sessions and sign-out | Add focused real Sessions/AuthStatus tests for load failure/retry, remote vs current revoke, canceled revoke-others/no-write, failed confirm within modal and deliberate retry/readback. Preserve clearAuthClientState/private-tree disposal with [session-boundary tests](../../../../job-tracker-ui/src/session-boundary.test.ts) | +| Shared providers | Current Login tests exercise real Google/Microsoft components under mocked external exchange; Profile tests mock Google. Add real account-link/unlink presentation/negative-path coverage so Login success cannot stand in for Profile compatibility. No actual provider call without separate authority | +| Responsive/localised/accessibility | EN/NB, light/dark,320/360–390/768/1024/1280/1440/wide representative states, long labels/device names, actual relevant zoom, keyboard/focus/errors/loading/retry and dialog state. Capture DOM names, root overflow, screenshot and axe contexts; use current theme/page/i18n owners | + +Use owned synthetic fixtures and actual mutation/no-write/readback assertions. Run appropriate affected unit/native tests, app/browser TypeScript, lint/build and finite docs/secret/whitespace checks. A broader full runtime gate is required at P7-12; extra runs here need a new change/failure/unresolved concern. Preserve ordinary P2-D01, existing auth/tenant/AI/frozen/output guards and original PDF oracles. + +## Acceptance and remaining gates + +- Public/auth and account commits have bounded diffs, real consumer regression, exact commands/results/count deltas, source identity and retained intermediate failures. Commit/push each completed unit and record immediate direct remote=HEAD in the ledger. +- Existing32 routes/aliases/search/history/state remain; no weakened authentication/authorization, canonical mutation, duplicate provider exchange/save owner or silent sensitive-action semantics change. +- EN/NB/theme/responsive names/headings/error/retry/focus behavior is verified in actual representative native states, with axe incomplete/violation contexts retained rather than disabled. +- Applicable E01–E06/E09 and shared E07/E08 plus S03 boundaries remain in the [57 matrix](../PRESERVATION.md). Finite scoped repairs do not upgrade whole groups without missing mandatory evidence. +- Actual mail/OAuth/Turnstile/authenticator/device/reader results and M01–M12 remain explicit when unavailable. M13/P5-13 remains deferred do-not-execute. Keep all [debt](../DEBT.md), production NO-GO and main unchanged; no Phase8 customization. + +## Public authentication implementation and acceptance + +The [public evidence manifest](evidence/public-auth-results.json) records the fresh final commands, source/export identity, all 61 affected unit assertions, 22 native assertions, two isolated SMTP workflows, 22 all-enabled axe attachments, exact geometry, image hashes and retained intermediate attempts. Public UI runtime files are copied to the immutable `.artifacts/phase7/p7-01/public-final/` snapshot before account runs reuse the runner lanes; affected-unit/backend artifacts retain their unique original paths. Only manifest file references were rebased: raw reports retain original absolute execution/attachment paths internally, and commands, times, results and measured SHA256 values are unchanged. Portable PNG references resolve to the snapshot. The six public source files use UTF-8/LF normalization so their hashes remain stable when Git stages LF. The manifest is written with LF; no commit hash is inferred from its own contents. + +The five existing view owners now use [AuthPageFrame](../../../../job-tracker-ui/src/layout/AuthPageFrame.tsx), the existing PageHeader/theme spacing and a bounded 520px form width. Each public page owns one named main landmark/H1. Recovery forms have durable page feedback, labelled local validation, focus on the failed field, retained input and explicit resubmission. Verification/email-change loading and success have named dynamic status; invalid/missing token pages retain a usable recovery action. Long action labels wrap at narrow widths. Existing route, command, token, Turnstile, remember-me, provider and safe-return owners remain in place. + +Visual review of the first passing export found registration describing its password as “Current password.” Two ordinary password-intent unit cases reproduced the mismatch; registration now uses the existing “New password”/“Nytt passord” key while preserving `new-password` autocomplete. The final export and native/recovery runs below include that repair. No new translation key or hard-coded product string was required. + +| Fresh bounded evidence | Observed final result | Scope / count explanation | +|---|---|---| +| Affected public Jest files | **61/61**, four suites, zero failure/pending | Existing Login33 and Verify4 retained; Microsoft relink1→2; new public-presentation22. Compared with the inherited affected38 cases, +23 cases/+1 suite. This is not a fresh full frontend run | +| Ordinary public native pack | **22/22**, zero skip/flaky/unexpected/retry | Fifteen finite presentation samples, two keyboard recovery/prefill/validation workflows, three real invalid-token HTTP400 workflows and two failed-login→explicit-success/return-context workflows. Fresh2026-10-11 18:40:40–18:41:10UTC | +| Isolated actual Identity + loopback SMTP pack | **2/2**, zero skip/flaky/unexpected/retry | Missing-token/no-write plus real registration202, delivered verification link204, invalid link400, password-reset delivery/reset204/replay400, old password/session rejection and protected return. Fresh18:41:10–18:41:23UTC; separate from the22 ordinary cases | +| Loopback SMTP fixture helper | **6/6**, zero fail/cancel/skip/todo | Fragmented commands/dot stuffing, envelope reset, oversize rejection, quoted-printable UTF-8, folded base64 and unsupported-MIME handling. Separate tooling cases | +| Backend auth/security subset | **87/87**, zero failed/not-executed | Auth/session/local identity/context/2FA/email-ownership/authorization contracts at unchanged backend source; a subset of inherited1,033, not an additive total | +| App and browser TypeScript, lint, production frontend build | **All exit0** | Final sequence18:40:04–18:40:39UTC; lint retains zero-warning policy. Browser config contents and command/log hashes are portable in the manifest | + +Authoritative final build ID is `rd_SQeVLwVwER9VMCc6XL`. Canonical public six-file source digest is `b330b233508ad5202a1f622ade4464103f7a986ee53648c8f16047f2a1d32586`; the manifest records each file and its precise ordered digest algorithm. Static export74 files have measured raw-file digest `dff031a52740a09359715a31456c9cec70f5aae6f9932c018af078215e973d67`. These identify the tested public working tree, not a newly deployed or remotely pushed identity. P7-00 remains the recorded entry commit until a successor push receipt is recorded in the ledger. + +### Responsive, localisation and visual evidence + +The native pack actually covers EN/NB and light/dark across320,360,390,768,1024,1280,1440 and1920px; the 1280×320 registration sample exercises short landscape height. It includes ready, registration-disabled, missing-link, local-validation, actual HTTP400 and actual HTTP401 states. Each of22 scans records one H1, named main and exact visible-control geometry; root/main overflow is0 throughout and visible controls remain within viewport tolerance. Public forms have no density control, so Comfortable/Compact is explicitly not applicable here. This is a finite sampling plan, not every route×theme×language×breakpoint combination, physical orientation or browser zoom acceptance. + +The parent opened the **eight exact final presentation-matrix PNGs** with `view_image`: login NB/dark320; registration NB/light390 and NB/dark1280×320; forgot EN/dark360; reset EN/light320; verify NB/dark390; email-change EN/dark1024; Microsoft relink EN/dark1440. Final registration images visibly say “Nytt passord.” The reviewed samples have calm shared framing and reachable/wrapped labels/actions, with no new observed app-chrome overflow. The manifest records those eight exact raw PNG hashes. Other workflow screenshots were captured and hashed; individual visual inspection is not claimed for them. + +The22 all-enabled axe scans contain **0 violation nodes and31 incomplete nodes**, all `color-contrast`. Every retained check says the background could not be determined because another element overlaps it. Contexts include outlined empty/filled form inputs and two primary-button nodes in failed-login states. Exact targets, HTML, check IDs/messages/data, context and geometry are retained in the manifest; complete raw attachments remain embedded in the hashed native report. Reflow and zero reported violations do not prove contrast. **M11** must qualify contextual contrast/forced colors; M09 must qualify spoken names/errors/state and possible duplicate route/error announcements. Browser zoom, human screen reader and physical/software-keyboard qualification remain unverified. No axe tag/rule was suppressed to obtain these results. + +Frontend-owned copy uses the existing EN/NB keys. Actual invalid-token responses include existing server-authored English “Invalid or expired link.”/“Invalid email or token.” in NB samples; this is retained as a localisation qualification, not presented as translated server feedback. Actual external OAuth, Turnstile and production email delivery remain unqualified; the SMTP run uses the real application mail/Identity path against an owned local capture server, not an external service. + +### Retained failures and preservation + +The first SMTP attempt passed0/2 because strict alert selectors also matched the established route announcer. The second passed1/2 because the fixture expected direct login where the protected-route handoff actually opens the existing Landing page. Scoped-main alert assertions and following that actual Landing handoff fixed the test assumptions; no product change was made for these failures. The third passed2/2 before the label repair, and the fresh final run passed2/2 after it. Their reports/logs/command records and whole output folders are retained under `.artifacts/phase7/p7-01/public-final/attempts/`; the manifest resolves archived attachment paths to their retained folders rather than later output files. + +Intermediate public unit attempts are also retained, including the59-case run with one failure, the later59/59 run, and the password-label diagnostic RED with1 pass/1 failure/20 unselected pending cases. The final61-case affected run has zero pending/failure; diagnostic `testNamePattern` selection is not used for final acceptance. The first passing22-case public native run remains historical evidence and did not excuse the visually identified label defect. + +The finite public checks preserve E01–E06/E09 and E07/E08/S03 boundaries, exact safe return/query/hash handling, real token failure/replay and old-session rejection. Backend routes/protocols and established link/exchange guards are retained. All **57 groups remain56 PARTIAL + C11 DEFERRED**, and all32 routes remain retained with their existing qualifications. No whole-group upgrade or fresh full frontend/backend/browser/fidelity acceptance is inferred from these subsets. Ordinary P2-D01 remains required by the inherited baseline and future integrated gate; it was not rerun in this public pack. + +Public sub-slice acceptance is **CONDITIONAL GO** for its completed development commit, with M01–M12, server-error language/reader/contrast/provider qualifications and inherited debt carried. **Production remains NO-GO**, main unchanged; M13/P5-13 remains deferred do-not-execute. Commit/push/remote receipt and finite hygiene checks are recorded by the parent in the ledger before account product work proceeds. + +Account/security acceptance is **PENDING**: five new account-presentation RED cases and12 real-provider cases are prepared (eight existing provider behaviors pass; four provider presentation cases fail), plus six meaningful native account cases not yet executed. The parent owns those product repairs and the later fresh affected build/native evidence. This public result neither completes P7-01 nor changes the approved checkpoint order. diff --git a/docs/redesign/phase-7/p7-01/evidence/public-auth-results.json b/docs/redesign/phase-7/p7-01/evidence/public-auth-results.json new file mode 100644 index 0000000..6362c9b --- /dev/null +++ b/docs/redesign/phase-7/p7-01/evidence/public-auth-results.json @@ -0,0 +1,6360 @@ +{ + "schemaVersion": 1, + "checkpoint": "P7-01", + "slice": "Public authentication presentation", + "collectedUtc": "2026-10-11T18:46:34.391008+00:00", + "acceptance": "CONDITIONAL_GO_PUBLIC_SUBSLICE", + "wholeCheckpointAcceptance": "PENDING_ACCOUNT_IMPLEMENTATION_AND_ACCEPTANCE", + "production": "NO-GO; main unchanged; no deployment", + "phase7Readiness": "Conditional development authorization; mandatory manual/production/withheld-AI gates retained", + "identity": { + "developmentBranch": "codex/ui-redesign-phase4", + "entryCommit": "27c54146ad6f9059295d169b533db7b1b6689210", + "currentHeadObserved": "27c54146ad6f9059295d169b533db7b1b6689210", + "qualification": "Measured working-tree public source, no eventual commit self-reference or inferred remote receipt", + "publicSourceFiles": [ + { + "path": "job-tracker-ui/src/views/LoginPage.tsx", + "sha256": "3120b4e29d6ebf980e92742b68f12d45ec0f0ecbda12ba81a1f3c1ac24a716de", + "bytes": 13880, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + { + "path": "job-tracker-ui/src/views/ForgotPasswordPage.tsx", + "sha256": "662cd57f46fce43aa8c61d8ea8629ab76c5df34a3a9fc1a822fe64fb726206ad", + "bytes": 3601, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + { + "path": "job-tracker-ui/src/views/ResetPasswordPage.tsx", + "sha256": "33cb1f86b6b9f3254b5287e20f912c9f13236626d1ee8f9e5e40e88b535fbf51", + "bytes": 4750, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + { + "path": "job-tracker-ui/src/views/VerifyEmailPage.tsx", + "sha256": "90274cf2885ad80fbd16073e2750a8a3a53061c5fc35d877441e8efc1515e2d0", + "bytes": 2619, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + { + "path": "job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx", + "sha256": "88cfd80f0f743e20889a665261111a40e977c6a8ebdf0715aba255c01e1fef16", + "bytes": 2990, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + { + "path": "job-tracker-ui/src/layout/AuthPageFrame.tsx", + "sha256": "cdd4b0f238d1c5dffdb13fd0f0dd0ea08f89238ccd83858e10eed58a0a8911a7", + "bytes": 1021, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + } + ], + "publicSourceSha256Lf": "b330b233508ad5202a1f622ade4464103f7a986ee53648c8f16047f2a1d32586", + "publicSourceAlgorithm": "SHA256 of ordered path + TAB + canonical-LF file SHA256 + LF records", + "buildId": "rd_SQeVLwVwER9VMCc6XL", + "buildIdRaw": { + "path": ".artifacts/phase7/p7-01/public-final/BUILD_ID", + "sha256": "3b9a7dfe22d00b2ea2b367484f524ba84ad775388e54714d70bb5813ef00c595", + "bytes": 21, + "hashPolicy": "Exact measured raw bytes" + }, + "staticExportFileCount": 74, + "staticExportSha256": "dff031a52740a09359715a31456c9cec70f5aae6f9932c018af078215e973d67", + "staticExportAlgorithm": "SHA256 of sorted repository-relative path + TAB + raw exported file SHA256 + LF records", + "finalFreezeConfirmedByParent": true + }, + "environment": { + "osObserved": "Windows-11-10.0.26200-SP0", + "localOnly": true, + "nativeBrowser": "Chromium", + "appliedRunnerEnvironment": { + "NEXT_PUBLIC_API_BASE_URL": "http://localhost:5302/api", + "NEXT_TELEMETRY_DISABLED": "1", + "DOTNET_ROOT": "C:/Users/Connor/.dotnet", + "DOTNET_HOST_PATH": "C:/Users/Connor/.dotnet/dotnet.exe", + "PLAYWRIGHT_STATIC_EXPORT": "true", + "PLAYWRIGHT_PREBUILT_EXPORT": "true", + "PLAYWRIGHT_REUSE_SERVERS": "false", + "Workers__SavedJobSearchAlertsEnabled": "false", + "CV_FIDELITY_OUTPUT": ".artifacts/phase7/current/ui/fidelity", + "CV_SETTINGS_FIDELITY_OUTPUT": ".artifacts/phase7/current/ui/settings-fidelity" + }, + "runnerRaw": { + "path": ".artifacts/phase7/current/run-ui.py", + "sha256": "95c479462812f91be97a07865764b76e7cd4e251edb0cf871ae945147fbb606d", + "bytes": 3737, + "hashPolicy": "Exact measured raw bytes" + }, + "browserTypeScriptConfig": { + "raw": { + "path": ".artifacts/phase7/e2e-tsconfig.json", + "sha256": "55fa6fffaed1e76a32657edfffcc501cd8bd87330bda2c3a3944f8f89d4019e2", + "bytes": 572, + "hashPolicy": "Exact measured raw bytes" + }, + "contents": "{\n \"extends\": \"../../job-tracker-ui/tsconfig.json\",\n \"compilerOptions\": {\n \"incremental\": false,\n \"types\": [\"node\", \"jest\"],\n \"typeRoots\": [\"../../job-tracker-ui/node_modules/@types\"]\n },\n \"include\": [\n \"../../job-tracker-ui/e2e/**/*.ts\",\n \"../../job-tracker-ui/ai-tests/**/*.ts\",\n \"../../job-tracker-ui/auth-tests/**/*.ts\",\n \"../../job-tracker-ui/playwright.*.config.ts\",\n \"../../job-tracker-ui/src/mui-theme.d.ts\",\n \"../../docs/redesign/phase-5/p5-15/p5-15-01/diagnostics/**/*.ts\"\n ],\n \"exclude\": [\"../../job-tracker-ui/node_modules\"]\n}\n" + }, + "SMTPConfiguration": { + "path": "job-tracker-ui/playwright.auth-recovery.config.ts", + "sha256": "e1398a2ca5da83ae4fcb5f7486025ad57972d1c8c9e59f5045cc0fc4771ffb46", + "bytes": 2514, + "hashPolicy": "UTF-8 text with CRLF normalized to LF" + }, + "SMTPQualification": "Fresh synthetic SQLite/keys, loopback SMTP capture, actual app Identity tokens/mail/reset/session revocation; production mail/OAuth/Turnstile disabled, no real provider call" + }, + "commands": { + "typecheck": { + "record": { + "name": "typecheck", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "run", + "typecheck" + ], + "startedUtc": "2026-10-11T18:40:04.381270+00:00", + "finishedUtc": "2026-10-11T18:40:13.702467+00:00", + "exitCode": 0 + }, + "recordRaw": { + "path": ".artifacts/phase7/p7-01/public-final/typecheck-command.json", + "sha256": "ec09944c3564073f9fc5053cb04b709238d9b3517d932f87ef6bfd87769985f3", + "bytes": 247, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/typecheck.log", + "sha256": "89b0ec97b537eca6560c13ca2dfea9e3a97dd1bb35580c4ff39d2462db25e080", + "bytes": 148, + "hashPolicy": "Exact measured raw bytes" + } + }, + "browser-typecheck": { + "record": { + "name": "browser-typecheck", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "exec", + "tsc", + "--", + "--project", + "../.artifacts/phase7/e2e-tsconfig.json", + "--noEmit" + ], + "startedUtc": "2026-10-11T18:40:13.704966+00:00", + "finishedUtc": "2026-10-11T18:40:17.223050+00:00", + "exitCode": 0 + }, + "recordRaw": { + "path": ".artifacts/phase7/p7-01/public-final/browser-typecheck-command.json", + "sha256": "f521fa7034e2a4a13271c2290d64aa5ab6cf70ec3069193b0dbe0581d362f71d", + "bytes": 343, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/browser-typecheck.log", + "sha256": "d2993bc68ad9a63f711c8f5473155e2ba00fdcd461220cc3849185f2f9ad93e6", + "bytes": 78, + "hashPolicy": "Exact measured raw bytes" + } + }, + "lint": { + "record": { + "name": "lint", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "run", + "lint" + ], + "startedUtc": "2026-10-11T18:40:17.225515+00:00", + "finishedUtc": "2026-10-11T18:40:33.244235+00:00", + "exitCode": 0 + }, + "recordRaw": { + "path": ".artifacts/phase7/p7-01/public-final/lint-command.json", + "sha256": "9942a5aabf7a93ba5150d8fb705c3641c6187892a1db213a31086c000959e9d1", + "bytes": 237, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/lint.log", + "sha256": "b040dbeca9b4adb3f6f4c25ac49de0d37e921375b2d9b57e87b542a10f599913", + "bytes": 136, + "hashPolicy": "Exact measured raw bytes" + } + }, + "build": { + "record": { + "name": "build", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "run", + "build" + ], + "startedUtc": "2026-10-11T18:40:33.246731+00:00", + "finishedUtc": "2026-10-11T18:40:39.963279+00:00", + "exitCode": 0 + }, + "recordRaw": { + "path": ".artifacts/phase7/p7-01/public-final/build-command.json", + "sha256": "e3fcd7fee126f082d52489ce5ce70f42cacf9b542a49228957e25ac979cf38a4", + "bytes": 239, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/build.log", + "sha256": "ace43e7ab44772d78e3802b2b305d89fbd69febe25843ee41610de9671b3f030", + "bytes": 670, + "hashPolicy": "Exact measured raw bytes" + } + }, + "smtp-fixture": { + "record": { + "name": "smtp-fixture", + "command": [ + "rtk", + "proxy", + "node", + "--test", + "scripts/localSMTPfixture.test.mjs" + ], + "startedUtc": "2026-10-11T18:38:47.568488+00:00", + "finishedUtc": "2026-10-11T18:38:47.841554+00:00", + "exitCode": 0 + }, + "recordRaw": { + "path": ".artifacts/phase7/p7-01/public-final/smtp-fixture-command.json", + "sha256": "155a2822339d2fa288781cd3ba82973c3ac9b9a81ac689f64d735cf59ee4578f", + "bytes": 274, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/smtp-fixture.log", + "sha256": "21ab3459c6263f849416bb3ec1284146164b8b218f2b2c16a7cb94b2877e23ec", + "bytes": 712, + "hashPolicy": "Exact measured raw bytes" + } + } + }, + "focusedUnit": { + "raw": { + "path": ".artifacts/phase7/current/unit/public-password-label-green-20261011T183907Z.json", + "sha256": "19eb766e24079444a713e1ebe0b398289b2160223c86b393d45aaab1a906258e", + "bytes": 26508, + "hashPolicy": "Exact measured raw bytes" + }, + "startedUnixMs": 1791743948118, + "passed": 61, + "failed": 0, + "pending": 0, + "total": 61, + "suites": 4, + "cases": [ + { + "name": "public auth presentation names the password field for login intent and retains its autocomplete contract", + "status": "passed" + }, + { + "name": "public auth presentation names the password field for register intent and retains its autocomplete contract", + "status": "passed" + }, + { + "name": "public auth presentation provides one named main landmark and H1 for password request", + "status": "passed" + }, + { + "name": "public auth presentation provides one named main landmark and H1 for password reset", + "status": "passed" + }, + { + "name": "public auth presentation provides one named main landmark and H1 for email verification", + "status": "passed" + }, + { + "name": "public auth presentation provides one named main landmark and H1 for email change", + "status": "passed" + }, + { + "name": "public auth presentation provides one named main landmark and H1 for Microsoft recovery", + "status": "passed" + }, + { + "name": "public auth presentation announces the pending confirmation without an unnamed progressbar: false", + "status": "passed" + }, + { + "name": "public auth presentation announces the pending confirmation without an unnamed progressbar: true", + "status": "passed" + }, + { + "name": "public auth presentation retains the entered reset email and a durable error in the page when the command fails", + "status": "passed" + }, + { + "name": "public auth presentation retains both new-password fields and the decoded recovery token after a rejected reset", + "status": "passed" + }, + { + "name": "public auth presentation keeps a rejected local sign-in beside the retained fields without navigating", + "status": "passed" + }, + { + "name": "public auth presentation keeps the reset-request action name while announcing an in-flight command", + "status": "passed" + }, + { + "name": "public auth presentation announces an in-flight local sign-in while preserving its action name", + "status": "passed" + }, + { + "name": "public auth presentation announces a pending password save without changing its payload or successful destination", + "status": "passed" + }, + { + "name": "public auth presentation focuses the first invalid local-login field and associates its explanation", + "status": "passed" + }, + { + "name": "public auth presentation focuses the invalid new password without losing confirmation input", + "status": "passed" + }, + { + "name": "public auth presentation focuses and explains the invalid reset-request email without sending a command", + "status": "passed" + }, + { + "name": "public auth presentation replaces previous reset-request success when a later request fails", + "status": "passed" + }, + { + "name": "public auth presentation does not assert optional authentication while login configuration is unresolved", + "status": "passed" + }, + { + "name": "public auth presentation does not assert optional authentication while register configuration is unresolved", + "status": "passed" + }, + { + "name": "public auth presentation offers a read-only configuration retry while retaining entered sign-in fields", + "status": "passed" + }, + { + "name": "LoginPage provides a main landmark and level-one heading for login", + "status": "passed" + }, + { + "name": "LoginPage provides a main landmark and level-one heading for register", + "status": "passed" + }, + { + "name": "LoginPage renders a dedicated registration screen and explains when registration is disabled", + "status": "passed" + }, + { + "name": "LoginPage renders one conventional sign-in card with enabled provider alternatives", + "status": "passed" + }, + { + "name": "LoginPage accepts a username and reports invalid credentials without changing routes", + "status": "passed" + }, + { + "name": "LoginPage handles Microsoft cancellation without exchanging or navigating", + "status": "passed" + }, + { + "name": "LoginPage completes the Microsoft provider return through the hardened exchange endpoint", + "status": "passed" + }, + { + "name": "LoginPage completes a Google credential return without account-linking copy", + "status": "passed" + }, + { + "name": "LoginPage posts remember-me preference without storing an auth token in browser storage", + "status": "passed" + }, + { + "name": "LoginPage does not complete login when registration requires email verification", + "status": "passed" + }, + { + "name": "LoginPage rejects an unsafe post-login redirect path", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"https://evil.example/analytics\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"//evil.example/analytics\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/\\t/evil.example\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/%2f%2fevil.example\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/%252f%252fevil.example\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/%5cevil.example\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/analytics?months=%ZZ\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/jobs/../login\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/%6cogin\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/register\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/analytics%0a\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/analytics?months=6\\n\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/LOGIN\"", + "status": "passed" + }, + { + "name": "LoginPage uses the safe fallback for the untrusted return target \"/%4cOGIN\"", + "status": "passed" + }, + { + "name": "LoginPage preserves encoded internal query and fragment without decoding the navigation target", + "status": "passed" + }, + { + "name": "LoginPage restores valid owned-workspace navigation context separately from the sign-in destination", + "status": "passed" + }, + { + "name": "LoginPage does not replay an unsafe nested return context after authentication", + "status": "passed" + }, + { + "name": "LoginPage abandons prior return context on the established account/session disposal entry", + "status": "passed" + }, + { + "name": "LoginPage opens the separate forgot-password page with the typed email prefilled", + "status": "passed" + }, + { + "name": "LoginPage shows the 2FA code step when login requires two-factor, then proceeds like a normal login on success", + "status": "passed" + }, + { + "name": "LoginPage shows a clear message when the 2FA challenge is rate-limited", + "status": "passed" + }, + { + "name": "LoginPage offers a resend-verification action when login reports the account is not verified", + "status": "passed" + }, + { + "name": "VerifyEmailPage confirms the account and shows success when the link is valid", + "status": "passed" + }, + { + "name": "VerifyEmailPage shows an error when the link is invalid or expired", + "status": "passed" + }, + { + "name": "VerifyEmailPage shows an error without calling the API when the link is missing userId/token", + "status": "passed" + }, + { + "name": "VerifyEmailPage confirms a pending email change and requires a fresh sign-in", + "status": "passed" + }, + { + "name": "legacy Microsoft recovery requires the emailed proof and a fresh matching Microsoft token", + "status": "passed" + }, + { + "name": "pending Microsoft recovery keeps a named action and announces progress before the real confirmation boundary", + "status": "passed" + } + ] + }, + "nativePublic": { + "command": { + "name": "auth", + "command": [ + "rtk", + "proxy", + "node", + "node_modules/@playwright/test/cli.js", + "test", + "e2e/phase7-authentication.spec.ts", + "--grep", + "public", + "--retries=0", + "--reporter=list,json", + "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\auth-results" + ], + "startedUtc": "2026-10-11T18:40:39.968796+00:00", + "finishedUtc": "2026-10-11T18:41:10.212431+00:00", + "exitCode": 0, + "stats": { + "startTime": "2026-10-11T18:40:40.482Z", + "duration": 29658.595, + "expected": 22, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + } + }, + "commandRaw": { + "path": ".artifacts/phase7/p7-01/public-final/auth-command.json", + "sha256": "2f9818509196a29ff4c94d39763eb941efa957cef52ffc079a0a585b888f6dc5", + "bytes": 667, + "hashPolicy": "Exact measured raw bytes" + }, + "reportRaw": { + "path": ".artifacts/phase7/p7-01/public-final/auth.json", + "sha256": "56125fb7953ac8c36a6d42c1e1a8e45e061f7e34276f091cea6f5bb23625d65a", + "bytes": 3782886, + "hashPolicy": "Exact measured raw bytes" + }, + "logRaw": { + "path": ".artifacts/phase7/p7-01/public-final/auth.log", + "sha256": "8b8e79bba89ab30125a65a7d97f2f660d4ee5addaa2f17bfd0a19ce1fdc0d8c4", + "bytes": 4061, + "hashPolicy": "Exact measured raw bytes" + }, + "stats": { + "startTime": "2026-10-11T18:40:40.482Z", + "duration": 29658.595, + "expected": 22, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "[P7-01 public/auth] /login nb/dark 320x740: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1035 + }, + { + "title": "[P7-01 public/auth] /login en/light 1280x900: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 921 + }, + { + "title": "[P7-01 public/auth] /register nb/light 390x844: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 977 + }, + { + "title": "[P7-01 public/auth] /register en/dark 1440x900: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 937 + }, + { + "title": "[P7-01 public/auth] /forgot-password en/dark 360x740: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 909 + }, + { + "title": "[P7-01 public/auth] /forgot-password nb/light 1024x768: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 851 + }, + { + "title": "[P7-01 public/auth] /reset-password en/light 320x740: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 916 + }, + { + "title": "[P7-01 public/auth] /reset-password nb/dark 768x1024: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 932 + }, + { + "title": "[P7-01 public/auth] /verify-email en/light 1920x1080: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 862 + }, + { + "title": "[P7-01 public/auth] /verify-email nb/dark 390x844: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 848 + }, + { + "title": "[P7-01 public/auth] /confirm-email-change en/dark 1024x768: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 863 + }, + { + "title": "[P7-01 public/auth] /confirm-email-change nb/light 768x1024: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 843 + }, + { + "title": "[P7-01 public/auth] /microsoft-legacy-relink nb/light 1280x900: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 855 + }, + { + "title": "[P7-01 public/auth] /microsoft-legacy-relink en/dark 1440x900: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 886 + }, + { + "title": "[P7-01 public/auth] /register nb/dark 1280x320: landmarks, labels and reflow", + "file": "phase7-authentication.spec.ts", + "line": 85, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 963 + }, + { + "title": "[P7-01 public/auth] en keyboard recovery entry preserves the prefill, validates locally and returns focus to sign-in", + "file": "phase7-authentication.spec.ts", + "line": 114, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1072 + }, + { + "title": "[P7-01 public/auth] nb keyboard recovery entry preserves the prefill, validates locally and returns focus to sign-in", + "file": "phase7-authentication.spec.ts", + "line": 114, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1032 + }, + { + "title": "[P7-01 public/auth] real /auth/verify-email invalid-token rejection retains a usable page", + "file": "phase7-authentication.spec.ts", + "line": 149, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1000 + }, + { + "title": "[P7-01 public/auth] real /auth/email-change/confirm invalid-token rejection retains a usable page", + "file": "phase7-authentication.spec.ts", + "line": 149, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 931 + }, + { + "title": "[P7-01 public/auth] real /auth/reset-password invalid-token rejection retains a usable page", + "file": "phase7-authentication.spec.ts", + "line": 149, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1068 + }, + { + "title": "[P7-01 public/auth] en rejected sign-in retains credentials and the exact protected return route", + "file": "phase7-authentication.spec.ts", + "line": 186, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1977 + }, + { + "title": "[P7-01 public/auth] nb rejected sign-in retains credentials and the exact protected return route", + "file": "phase7-authentication.spec.ts", + "line": 186, + "expectedStatus": "passed", + "project": "chromium", + "resultStatuses": [ + "passed" + ], + "retries": [ + 0 + ], + "durationMs": 1709 + } + ], + "axeScans": [ + { + "test": "[P7-01 public/auth] /login nb/dark 320x740: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "36105550b3f71a94ce0d042881679513669ee70f1112d2f42607bf384c829606", + "bytes": 155615 + }, + "context": { + "path": "/login", + "title": "signInTitle", + "language": "nb", + "mode": "dark", + "width": 320, + "height": 740, + "screenshot": true, + "state": "ready", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 320, + "viewportHeight": 740, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 126.25, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "text", + "text": "", + "left": 43, + "right": 277, + "width": 234, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 43, + "right": 277, + "width": 234, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "checkbox", + "text": "", + "left": 22, + "right": 64, + "width": 42, + "height": 42 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Glemt passord?", + "left": 193.5625, + "right": 287, + "width": 93.4375, + "height": 42 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Logg inn", + "left": 198.65625, + "right": 287, + "width": 88.34375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Fortsett med Google", + "left": 33, + "right": 287, + "width": 254, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Fortsett med Microsoft", + "left": 33, + "right": 287, + "width": 254, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 320, + "windowHeight": 740, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 2 + }, + { + "test": "[P7-01 public/auth] /login en/light 1280x900: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "836104700e4d84242ae71dea62d9fcff26b8049ebd7f70ac016713b4e95b2d6f", + "bytes": 156060 + }, + "context": { + "path": "/login", + "title": "signInTitle", + "language": "en", + "mode": "light", + "width": 1280, + "height": 900, + "state": "ready", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1280, + "viewportHeight": 900, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 222.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "text", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "checkbox", + "text": "", + "left": 390, + "right": 432, + "width": 42, + "height": 42 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Forgot password?", + "left": 771.3125, + "right": 879, + "width": 107.6875, + "height": 30.75 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Sign in", + "left": 803.015625, + "right": 879, + "width": 75.984375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Continue with Google", + "left": 401, + "right": 879, + "width": 478, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Continue with Microsoft", + "left": 401, + "right": 879, + "width": 478, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1280, + "windowHeight": 900, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 2 + }, + { + "test": "[P7-01 public/auth] /register nb/light 390x844: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "fe0c297344c630d13cb2cefcded76ac977454835f26c16268df44d7cfb2e142f", + "bytes": 153600 + }, + "context": { + "path": "/register", + "title": "createAccount", + "language": "nb", + "mode": "light", + "width": 390, + "height": 844, + "screenshot": true, + "state": "registration-disabled", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 390, + "viewportHeight": 844, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 221.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 43, + "right": 347, + "width": 304, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 43, + "right": 347, + "width": 304, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 43, + "right": 347, + "width": 304, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 33, + "right": 166.4375, + "width": 133.4375, + "height": 30.75 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Opprett konto", + "left": 232.828125, + "right": 357, + "width": 124.171875, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 390, + "windowHeight": 844, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_6_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 3 + }, + { + "test": "[P7-01 public/auth] /register en/dark 1440x900: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "7aff8d9d2271e4a64181c11eb3f92ea898412c8f6121b36d769a74b621cb440b", + "bytes": 153528 + }, + "context": { + "path": "/register", + "title": "createAccount", + "language": "en", + "mode": "dark", + "width": 1440, + "height": 900, + "state": "registration-disabled", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1440, + "viewportHeight": 900, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 262.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 491, + "right": 949, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 491, + "right": 949, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 491, + "right": 949, + "width": 458, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 481, + "right": 565.609375, + "width": 84.609375, + "height": 30.75 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Create account", + "left": 829.90625, + "right": 959, + "width": 129.09375, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1440, + "windowHeight": 900, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_6_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 3 + }, + { + "test": "[P7-01 public/auth] /forgot-password en/dark 360x740: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "2ac1cb084f099227edbd57888888e3935411c2766928e8672d51236819a1b4df", + "bytes": 112126 + }, + "context": { + "path": "/forgot-password", + "title": "forgotPasswordTitle", + "language": "en", + "mode": "dark", + "width": 360, + "height": 740, + "screenshot": true, + "state": "ready", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 360, + "viewportHeight": 740, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 269.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 43, + "right": 317, + "width": 274, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 67.703125, + "right": 187.296875, + "width": 119.59375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Send reset link", + "left": 195.296875, + "right": 327, + "width": 131.703125, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 360, + "windowHeight": 740, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 1 + }, + { + "test": "[P7-01 public/auth] /forgot-password nb/light 1024x768: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "529e99aff04dae255fa88ec690bda41a13e4af1d7a947f4e4261e85df04fa4f8", + "bytes": 112217 + }, + "context": { + "path": "/forgot-password", + "title": "forgotPasswordTitle", + "language": "nb", + "mode": "light", + "width": 1024, + "height": 768, + "state": "ready", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1024, + "viewportHeight": 768, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 281.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 283, + "right": 741, + "width": 458, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 397.625, + "right": 567.09375, + "width": 169.46875, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Send nullstillingslenke", + "left": 575.09375, + "right": 751, + "width": 175.90625, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1024, + "windowHeight": 768, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 1 + }, + { + "test": "[P7-01 public/auth] /reset-password en/light 320x740: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "98de569213c6d526446c96f6edb6bb8a5cea899191d6d3112b51fb7af5cbffa7", + "bytes": 153735 + }, + "context": { + "path": "/reset-password", + "title": "resetPasswordTitle", + "language": "en", + "mode": "light", + "width": 320, + "height": 740, + "screenshot": true, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 320, + "viewportHeight": 740, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 154.5, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 43, + "right": 277, + "width": 234, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 43, + "right": 277, + "width": 234, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 43, + "right": 277, + "width": 234, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 167.40625, + "right": 287, + "width": 119.59375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Update password", + "left": 145.15625, + "right": 287, + "width": 141.84375, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 320, + "windowHeight": 740, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_6_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 3 + }, + { + "test": "[P7-01 public/auth] /reset-password nb/dark 768x1024: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "22ff81c149b4357dbb0265f770ae61f33de9c86179026afe43d125bcaa1bef31", + "bytes": 153215 + }, + "context": { + "path": "/reset-password", + "title": "resetPasswordTitle", + "language": "nb", + "mode": "dark", + "width": 768, + "height": 1024, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 768, + "viewportHeight": 1024, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 324.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 155, + "right": 613, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 155, + "right": 613, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 155, + "right": 613, + "width": 458, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 299.875, + "right": 469.34375, + "width": 169.46875, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Oppdater passord", + "left": 477.34375, + "right": 623, + "width": 145.65625, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 768, + "windowHeight": 1024, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_6_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 3 + }, + { + "test": "[P7-01 public/auth] /verify-email en/light 1920x1080: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "46f1f839097cbac05ae348e4d656f3ad202e6359ac9097e65006bd6936f74a65", + "bytes": 96524 + }, + "context": { + "path": "/verify-email", + "title": "verifyEmailTitle", + "language": "en", + "mode": "light", + "width": 1920, + "height": 1080, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1920, + "viewportHeight": 1080, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 457.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 1079.40625, + "right": 1199, + "width": 119.59375, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1920, + "windowHeight": 1080, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /verify-email nb/dark 390x844: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "f7d3670933d975eed13353494d62419cf74509a50afdb4e19e7d62db2c87ff73", + "bytes": 96590 + }, + "context": { + "path": "/verify-email", + "title": "verifyEmailTitle", + "language": "nb", + "mode": "dark", + "width": 390, + "height": 844, + "screenshot": true, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 390, + "viewportHeight": 844, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 341.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 187.53125, + "right": 357, + "width": 169.46875, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 390, + "windowHeight": 844, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /confirm-email-change en/dark 1024x768: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "d12ddfb5a5721f62a8b02b359b0cb6571ab796c580c5d03e365a13462ce8bfd4", + "bytes": 96625 + }, + "context": { + "path": "/confirm-email-change", + "title": "confirmEmailChangeTitle", + "language": "en", + "mode": "dark", + "width": 1024, + "height": 768, + "screenshot": true, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1024, + "viewportHeight": 768, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 301.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 631.40625, + "right": 751, + "width": 119.59375, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1024, + "windowHeight": 768, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /confirm-email-change nb/light 768x1024: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "01198fc809e90fe3a3ba868560da0ea451c4316d99a1687b98789b24ddaf24b3", + "bytes": 96713 + }, + "context": { + "path": "/confirm-email-change", + "title": "confirmEmailChangeTitle", + "language": "nb", + "mode": "light", + "width": 768, + "height": 1024, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 768, + "viewportHeight": 1024, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 429.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 453.53125, + "right": 623, + "width": 169.46875, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 768, + "windowHeight": 1024, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /microsoft-legacy-relink nb/light 1280x900: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "46bd516a1264629e6c41691869ce9d5aa9e48d36baaf36e394259ceda5f4d11a", + "bytes": 104409 + }, + "context": { + "path": "/microsoft-legacy-relink", + "title": "microsoftLegacyRelinkTitle", + "language": "nb", + "mode": "light", + "width": 1280, + "height": 900, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1280, + "viewportHeight": 900, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 334.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 538.46875, + "right": 691.9375, + "width": 153.46875, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Fortsett med Microsoft", + "left": 699.9375, + "right": 879, + "width": 179.0625, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1280, + "windowHeight": 900, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /microsoft-legacy-relink en/dark 1440x900: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "660bd988651b7056ed6aab306be0dcfeacaba8156003a2fde00fc7d4f65c36c6", + "bytes": 104331 + }, + "context": { + "path": "/microsoft-legacy-relink", + "title": "microsoftLegacyRelinkTitle", + "language": "en", + "mode": "dark", + "width": 1440, + "height": 900, + "screenshot": true, + "state": "missing-link", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1440, + "viewportHeight": 900, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 341.75, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 658.6875, + "right": 762.28125, + "width": 103.59375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Continue with Microsoft", + "left": 770.28125, + "right": 959, + "width": 188.71875, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1440, + "windowHeight": 900, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [], + "violationsNodes": 0, + "incompleteNodes": 0 + }, + { + "test": "[P7-01 public/auth] /register nb/dark 1280x320: landmarks, labels and reflow", + "attachment": "public-auth-presentation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "3d413f1ca998d2fa836a3ac88ece276fb7ae01ef1a1a341a9e038ebc86b37d3c", + "bytes": 153580 + }, + "context": { + "path": "/register", + "title": "createAccount", + "language": "nb", + "mode": "dark", + "width": 1280, + "height": 320, + "screenshot": true, + "state": "registration-disabled", + "density": "not-applicable", + "browserZoom": "not-measured" + }, + "geometry": { + "viewportWidth": 1280, + "viewportHeight": 320, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 45, + "mainName": "_r_3_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "INPUT", + "type": "password", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Tilbake til innlogging", + "left": 401, + "right": 534.4375, + "width": 133.4375, + "height": 30.75 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Opprett konto", + "left": 754.828125, + "right": 879, + "width": 124.171875, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1280, + "windowHeight": 320, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_4_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_5_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_6_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + } + ] + } + ], + "violationsNodes": 0, + "incompleteNodes": 3 + }, + { + "test": "[P7-01 public/auth] en keyboard recovery entry preserves the prefill, validates locally and returns focus to sign-in", + "attachment": "public-auth-reset-request-validation-all-enabled-axe", + "raw": { + "storage": "Base64 attachment in raw report", + "sha256": "b20e7c0dec003fd31dd14bc5ebee4f1e033d1c31bb6ff4cde1b429c247259de0", + "bytes": 115730 + }, + "context": { + "language": "en", + "mode": "light", + "width": 1280, + "height": 900, + "route": "/forgot-password", + "state": "client-validation", + "requests": [] + }, + "geometry": { + "viewportWidth": 1280, + "viewportHeight": 900, + "pageOverflow": 0, + "mainOverflow": 0, + "headingTop": 347.75, + "mainName": "_r_c_", + "h1Count": 1, + "controls": [ + { + "tag": "INPUT", + "type": "email", + "text": "", + "left": 411, + "right": 869, + "width": 458, + "height": 38.125 + }, + { + "tag": "BUTTON", + "type": "button", + "text": "Back to login", + "left": 619.703125, + "right": 739.296875, + "width": 119.59375, + "height": 36.5 + }, + { + "tag": "BUTTON", + "type": "submit", + "text": "Send reset link", + "left": 747.296875, + "right": 879, + "width": 131.703125, + "height": 36.5 + } + ] + }, + "engine": { + "name": "axe-core", + "version": "4.13.0" + }, + "environment": { + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.7922.34 Safari/537.36", + "windowWidth": 1280, + "windowHeight": 900, + "orientationAngle": 0, + "orientationType": "landscape-primary" + }, + "toolOptions": { + "reporter": "v1" + }, + "violations": [], + "incomplete": [ + { + "id": "color-contrast", + "impact": "serious", + "tags": [ + "cat.color", + "wcag2aa", + "wcag143", + "TTv5", + "TT13.c", + "EN-301-549", + "EN-9.1.4.3", + "ACT", + "RGAAv4", + "RGAA-3.2.1" + ], + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/color-contrast?application=playwright", + "nodes": [ + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": "", + "target": [ + "#_r_d_" + ], + "failureSummary": "Fix any of the following:\n Element's background color could not be determined because it is overlapped by another element" + }, + { + "any": [ + { + "id": "color-contrast", + "data": { + "contrastRatio": 0, + "fontSize": "10.5pt (14px)", + "fontWeight": "normal", + "messageKey": "bgOverlap", + "expectedContrastRatio": "4.5:1" + }, + "relatedNodes": [], + "impact": "serious", + "message": "Element's background color could not be determined because it is overlapped by another element" + } + ], + "all": [], + "none": [], + "impact": "serious", + "html": " - + {loading ? {t("passwordResetRequestSending")} : null} - - + ); } diff --git a/job-tracker-ui/src/views/LoginPage.tsx b/job-tracker-ui/src/views/LoginPage.tsx index d67f22e..b1c3b55 100644 --- a/job-tracker-ui/src/views/LoginPage.tsx +++ b/job-tracker-ui/src/views/LoginPage.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; -import { Alert, Box, Button, Checkbox, Divider, FormControlLabel, Paper, TextField, Typography } from "@mui/material"; +import { Alert, Box, Button, Checkbox, Divider, FormControlLabel, TextField, Typography } from "@mui/material"; import { useLocation, useNavigate } from "react-router-dom"; @@ -12,6 +12,7 @@ import TwoFactorChallenge from "../components/TwoFactorChallenge"; import TurnstileWidget from "../components/TurnstileWidget"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; +import AuthPageFrame from "../layout/AuthPageFrame"; type AuthConfig = { requireAuth: boolean; @@ -31,6 +32,8 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo const location = useLocation() as any; const [cfg, setCfg] = useState(null); + const [configLoading, setConfigLoading] = useState(true); + const [configFailed, setConfigFailed] = useState(false); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); @@ -44,6 +47,10 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo const [fieldErrors, setFieldErrors] = useState<{ email?: string; password?: string; confirmPassword?: string }>({}); const [registerMode, setRegisterMode] = useState(initialMode === "register"); const [turnstileToken, setTurnstileToken] = useState(""); + const [submitError, setSubmitError] = useState(null); + const emailRef = useRef(null); + const passwordRef = useRef(null); + const confirmPasswordRef = useRef(null); const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -56,13 +63,18 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo state: { from: contextPath, ...(Number.isSafeInteger(fromState.focusJobId) && fromState.focusJobId > 0 ? { focusJobId: fromState.focusJobId } : {}) }, } : { replace: true }; - useEffect(() => { - api + const loadAuthConfig = useCallback(() => { + setConfigLoading(true); + setConfigFailed(false); + return api .get("/auth/config") .then((r) => setCfg(r.data)) - .catch(() => setCfg(null)); + .catch(() => { setCfg(null); setConfigFailed(true); }) + .finally(() => setConfigLoading(false)); }, []); + useEffect(() => { void loadAuthConfig(); }, [loadAuthConfig]); + async function completeLogin() { setAuthPersistencePreference(rememberMe ? "local" : "session"); await api.get("/auth/me"); @@ -83,12 +95,16 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo } setFieldErrors(errors); + if (errors.email) emailRef.current?.focus(); + else if (errors.password) passwordRef.current?.focus(); + else if (errors.confirmPassword) confirmPasswordRef.current?.focus(); return Object.keys(errors).length === 0; } async function submit(mode: "login" | "register") { if (!validate(mode)) return; setLoading(true); + setSubmitError(null); setEmailNotVerified(false); setVerificationResent(false); try { @@ -109,7 +125,7 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo if (mode === "login" && e?.response?.data?.error === "email_not_verified") { setEmailNotVerified(true); } else { - toast(getApiErrorMessage(e, t("loginFailed")), "error"); + setSubmitError(getApiErrorMessage(e, t("loginFailed"))); } } finally { setLoading(false); @@ -118,12 +134,13 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo async function resendVerification() { setResendingVerification(true); + setSubmitError(null); try { await api.post("/auth/resend-verification-email", { email }); setVerificationResent(true); toast(t("verificationEmailResent"), "success"); } catch (e: any) { - toast(getApiErrorMessage(e, t("verifyEmailFailed")), "error"); + setSubmitError(getApiErrorMessage(e, t("verifyEmailFailed"))); } finally { setResendingVerification(false); } @@ -132,26 +149,11 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo const allowReg = cfg?.allowRegistration ?? false; return ( - - - - {registerMode ? t("createAccount") : t("signInTitle")} - - - {cfg?.requireAuth ? t("authRequired") : t("authOptional")} - - + + {configLoading ? {t("loading")} : null} + {configFailed ? void loadAuthConfig()}>{t("retry")}}> + {t("appErrorBody")} + : null} {new URLSearchParams(location.search).has("sessionChanged") ? {t("sessionChangedCleared")} : null} {pendingToken ? ( @@ -167,6 +169,7 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo onSubmit={(e) => { e.preventDefault(); void submit(registerMode ? "register" : "login"); }} sx={{ display: "flex", flexDirection: "column", gap: 1.5 }} > + {submitError ? {submitError} : null} {registerMode && cfg && !allowReg ? {t("registrationUnavailable")} : null} {cfg?.requireEmailVerification && emailNotVerified && ( { setEmail(e.target.value); setFieldErrors((f) => ({ ...f, email: undefined })); }} autoComplete={registerMode ? "email" : "username"} autoFocus + inputRef={emailRef} error={Boolean(fieldErrors.email)} helperText={fieldErrors.email} fullWidth /> { setPassword(e.target.value); setFieldErrors((f) => ({ ...f, password: undefined, confirmPassword: undefined })); }} autoComplete={registerMode ? "new-password" : "current-password"} + inputRef={passwordRef} type="password" error={Boolean(fieldErrors.password)} helperText={fieldErrors.password || (registerMode ? t("passwordTooShort") : undefined)} @@ -207,6 +212,7 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo value={confirmPassword} onChange={(e) => { setConfirmPassword(e.target.value); setFieldErrors((f) => ({ ...f, confirmPassword: undefined })); }} autoComplete="new-password" + inputRef={confirmPasswordRef} type="password" error={Boolean(fieldErrors.confirmPassword)} helperText={fieldErrors.confirmPassword} @@ -251,16 +257,17 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo size="small" disableRipple disabled={loading} - onClick={() => { if (initialMode === "register") navigate("/login"); else setRegisterMode((v) => !v); setFieldErrors({}); }} - sx={{ px: 0, minWidth: 0, fontWeight: 700 }} + onClick={() => { if (initialMode === "register") navigate("/login"); else setRegisterMode((v) => !v); setFieldErrors({}); setSubmitError(null); }} + sx={{ px: 0, minWidth: 0, fontWeight: 700, maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" }} > {registerMode ? t("backToLogin") : t("createAccount")} )} - + {loading || resendingVerification ? {t("loading")} : null} {!registerMode && (cfg?.googleEnabled || cfg?.microsoftEnabled) ? ( @@ -274,7 +281,6 @@ export default function LoginPage({ initialMode = "login" }: { initialMode?: "lo ) : null} )} - - + ); } diff --git a/job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx b/job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx index 71a2671..49518de 100644 --- a/job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx +++ b/job-tracker-ui/src/views/MicrosoftLegacyRelinkPage.tsx @@ -1,11 +1,12 @@ import React, { useState } from "react"; -import { Alert, Box, Button, CircularProgress, Paper, Typography } from "@mui/material"; +import { Alert, Box, Button, CircularProgress, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { api, getApiErrorMessage } from "../api"; import { getMicrosoftMsalInstance } from "../components/MicrosoftAuthCard"; import { useI18n } from "../i18n/I18nProvider"; +import AuthPageFrame from "../layout/AuthPageFrame"; export default function MicrosoftLegacyRelinkPage() { const { t } = useI18n(); @@ -45,22 +46,19 @@ export default function MicrosoftLegacyRelinkPage() { } return ( - - - {t("microsoftLegacyRelinkTitle")} - {t("microsoftLegacyRelinkBody")} + {missing ? {t("microsoftLegacyRelinkMissing")} : null} {error ? {error} : null} - {success ? {t("microsoftLegacyRelinkSuccess")} : null} - + {success ? {t("microsoftLegacyRelinkSuccess")} : null} + button": { maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" } }}> {!success ? ( - ) : null} - - + {working ? {t("loading")} : null} + ); } diff --git a/job-tracker-ui/src/views/ResetPasswordPage.tsx b/job-tracker-ui/src/views/ResetPasswordPage.tsx index e0b29bd..f34f498 100644 --- a/job-tracker-ui/src/views/ResetPasswordPage.tsx +++ b/job-tracker-ui/src/views/ResetPasswordPage.tsx @@ -1,12 +1,13 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; -import { Alert, Box, Button, Paper, TextField, Typography } from "@mui/material"; +import { Alert, Box, Button, TextField, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { api, getApiErrorMessage } from "../api"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; +import AuthPageFrame from "../layout/AuthPageFrame"; export default function ResetPasswordPage() { const { toast } = useToast(); @@ -19,6 +20,9 @@ export default function ResetPasswordPage() { const [confirmPassword, setConfirmPassword] = useState(""); const [loading, setLoading] = useState(false); const [fieldErrors, setFieldErrors] = useState<{ newPassword?: string; confirmPassword?: string }>({}); + const [submitError, setSubmitError] = useState(null); + const passwordRef = useRef(null); + const confirmPasswordRef = useRef(null); useEffect(() => { const params = new URLSearchParams(window.location.search); @@ -29,25 +33,7 @@ export default function ResetPasswordPage() { const missingResetInfo = !email || !token; return ( - - - - {t("resetPasswordTitle")} - - - {t("resetPasswordBody")} - - + { @@ -60,8 +46,12 @@ export default function ResetPasswordPage() { if (!newPassword || newPassword.length < 8) errors.newPassword = t("passwordTooShort"); if (confirmPassword !== newPassword) errors.confirmPassword = t("passwordsDoNotMatch"); setFieldErrors(errors); - if (Object.keys(errors).length > 0) return; + if (Object.keys(errors).length > 0) { + (errors.newPassword ? passwordRef : confirmPasswordRef).current?.focus(); + return; + } + setSubmitError(null); setLoading(true); api .post("/auth/reset-password", { email, token, newPassword }) @@ -70,13 +60,14 @@ export default function ResetPasswordPage() { navigate("/login", { replace: true }); }) .catch((e2: any) => { - toast(getApiErrorMessage(e2, t("resetFailed")), "error"); + setSubmitError(getApiErrorMessage(e2, t("resetFailed"))); }) .finally(() => setLoading(false)); }} sx={{ display: "flex", flexDirection: "column", gap: 1.5 }} > {missingResetInfo ? {t("missingResetLinkInfo")} : null} + {submitError ? {submitError} : null} setEmail(e.target.value)} disabled={!missingResetInfo} fullWidth /> { setNewPassword(e.target.value); setFieldErrors((f) => ({ ...f, newPassword: undefined, confirmPassword: undefined })); }} autoComplete="new-password" + inputRef={passwordRef} error={Boolean(fieldErrors.newPassword)} helperText={fieldErrors.newPassword || t("passwordTooShort")} fullWidth @@ -94,21 +86,22 @@ export default function ResetPasswordPage() { value={confirmPassword} onChange={(e) => { setConfirmPassword(e.target.value); setFieldErrors((f) => ({ ...f, confirmPassword: undefined })); }} autoComplete="new-password" + inputRef={confirmPasswordRef} error={Boolean(fieldErrors.confirmPassword)} helperText={fieldErrors.confirmPassword} fullWidth /> - + button": { maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" } }}> - + {loading ? {t("saving")} : null} - - + ); } diff --git a/job-tracker-ui/src/views/VerifyEmailPage.tsx b/job-tracker-ui/src/views/VerifyEmailPage.tsx index 015507e..9e55730 100644 --- a/job-tracker-ui/src/views/VerifyEmailPage.tsx +++ b/job-tracker-ui/src/views/VerifyEmailPage.tsx @@ -1,11 +1,12 @@ import React, { useEffect, useState } from "react"; -import { Alert, Box, Button, CircularProgress, Paper, Typography } from "@mui/material"; +import { Alert, Box, Button, CircularProgress, Typography } from "@mui/material"; import { useNavigate } from "react-router-dom"; import { api, getApiErrorMessage } from "../api"; import { useI18n } from "../i18n/I18nProvider"; +import AuthPageFrame from "../layout/AuthPageFrame"; type Status = "verifying" | "success" | "error"; @@ -38,39 +39,23 @@ export default function VerifyEmailPage({ emailChange = false }: { emailChange?: }, [emailChange, t]); return ( - - - - {t(emailChange ? "confirmEmailChangeTitle" : "verifyEmailTitle")} - - - + + {status === "verifying" && ( - - + + )} - {status === "success" && {t(emailChange ? "confirmEmailChangeSuccess" : "verifyEmailSuccess")}} + {status === "success" && {t(emailChange ? "confirmEmailChangeSuccess" : "verifyEmailSuccess")}} {status === "error" && {errorMessage}} - + button": { maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" } }}> - - + ); }