Skip to content

Fix/202: e2e logout relogin onboarding#305

Merged
graycyrus merged 2 commits intotinyhumansai:mainfrom
YellowSnnowmann:fix/202-e2e-logout-relogin-onboarding
Apr 3, 2026
Merged

Fix/202: e2e logout relogin onboarding#305
graycyrus merged 2 commits intotinyhumansai:mainfrom
YellowSnnowmann:fix/202-e2e-logout-relogin-onboarding

Conversation

@YellowSnnowmann
Copy link
Copy Markdown
Contributor

@YellowSnnowmann YellowSnnowmann commented Apr 3, 2026

To be merged after : #303

Summary

  • Adds a new E2E spec (logout-relogin-onboarding.spec.ts) covering the full logout → re-login onboarding overlay lifecycle, including a stale-state regression assertion.
  • Extracts logoutViaSettings, waitForLoggedOutState, and three overlay lifecycle helpers to shared-flows.ts so logout and overlay assertions are reusable across specs.
  • Adds telegramMeDelayMs behavior to the mock server so tests can simulate a slow profile fetch and assert the overlay does not appear prematurely.

Problem

No E2E test covered what happens after a user logs out and logs back in. Persistent flags like userLoadTimedOut written by a previous session can survive across the logout boundary. If those flags are still set when the new session boots, the onboarding overlay either fires too early (before the user profile loads) or not at all — both are regressions that went undetected.

The logout flow was also duplicated inline in auth-access-control.spec.ts with no shared abstraction for other specs to use.

Solution

New helpers in shared-flows.ts:

Helper Purpose
isOnboardingOverlayVisible() Single-shot public wrapper around the private onboardingOverlayLikelyVisible — for point-in-time assertions
waitForOnboardingOverlayVisible(timeout) Polls until overlay appears or timeout — for "overlay must appear after delay" assertions
waitForOnboardingOverlayHidden(timeout) Polls until overlay disappears — for "overlay must dismiss" assertions
waitForLoggedOutState(timeout) Polls for any welcome-screen marker (Welcome / Sign in / Login / Get Started)
logoutViaSettings(logPrefix) Full logout flow: navigate to Settings → click Log out → handle confirmation dialog → assert logged-out state

New spec logout-relogin-onboarding.spec.ts — single test case with four checkpoints:

  1. Fresh login completes onboarding and reaches Home.
  2. logoutViaSettings clears the session; persist:auth in localStorage is verified to have an empty token and empty per-user onboarding maps — no stale state.
  3. Re-login with telegramMeDelayMs=4500 delays the profile response. Immediately after auth bootstrap the overlay must not be visible (proves no stale userLoadTimedOut from the previous session fired early).
  4. After the fresh-session timeout elapses the overlay does appear with clean Welcome + Skip markers, and a /telegram/me call is recorded.

Mock server changes (mock-api-core.mjs):

  • getDelayMs(key) / sleep(ms) utilities for configurable per-endpoint delays.
  • telegramMeDelayMs wired into the GET /telegram/me handler.
  • Long if-conditions and json() call sites reformatted to stay within line length (no logic changes).

Submission Checklist

  • Unit tests — N/A: change is E2E harness and mock server only.
  • E2E / integration — New spec exercises the full logout → re-login → overlay flow end-to-end including localStorage state assertions.
  • Doc comments — New exported helpers have JSDoc describing their polling behaviour and when to use each.
  • Inline comments — Key assertions in the spec are commented with the regression they guard against.

Impact

  • E2E and mock server only. No product code or Rust core changed.
  • logoutViaSettings consolidates logout logic that was previously duplicated in auth-access-control.spec.ts — future specs can use it directly.
  • Mock telegramMeDelayMs is available to any spec that needs to simulate slow profile fetches.

Merge order note

Merge fix/200 (performFullLogin rewrite) before this PR.

Both PRs insert code immediately after onboardingOverlayLikelyVisible() in shared-flows.ts.
When this PR is rebased/merged after fix/200 lands, a one-hunk conflict will appear.

Conflict location: app/test/e2e/helpers/shared-flows.ts, right after the closing } of onboardingOverlayLikelyVisible().

Resolution — keep both blocks, in this order:

// 1. Public overlay lifecycle helpers (this PR — fix/202)
export async function isOnboardingOverlayVisible() { ... }
export async function waitForOnboardingOverlayVisible(...) { ... }
export async function waitForOnboardingOverlayHidden(...) { ... }

// 2. Private mnemonic step handler (fix/200)
async function completeMnemonicStep(...) { ... }

// 3. walkOnboarding (existing, modified by fix/200)
export async function walkOnboarding(...) { ... }

Related

Summary by CodeRabbit

  • Tests
    • Added end-to-end tests for onboarding overlay behavior during logout and re-login cycles
    • Introduced test helpers for managing onboarding visibility and authentication state transitions
    • Enhanced test infrastructure with configurable request delays for simulating various scenarios

YellowSnnowmann and others added 2 commits April 3, 2026 16:25
…ers (tinyhumansai#202)

- Add isOnboardingOverlayVisible, waitForOnboardingOverlayVisible,
  waitForOnboardingOverlayHidden to shared-flows for precise overlay
  lifecycle assertions across specs.
- Add logoutViaSettings helper: navigates to Settings, clicks Log out,
  handles optional confirmation dialog, asserts logged-out state —
  consolidating logout logic that was duplicated per-spec.
- Add waitForLoggedOutState helper returning the first welcome-screen
  marker found (Welcome / Sign in / Login / Get Started).
- New spec logout-relogin-onboarding.spec.ts verifies:
    1. Fresh login completes onboarding and reaches Home.
    2. Logout clears persisted auth/onboarding state (token + all
       per-user onboarding maps reset to {}).
    3. Re-login with a delayed /telegram/me response does NOT show
       the overlay prematurely (proves no stale userLoadTimedOut leak).
    4. Once the fresh-session timeout elapses the overlay appears with
       clean Welcome + Skip markers and a /telegram/me call is made.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ests (tinyhumansai#202)

- Add getDelayMs/sleep utilities to mock-api-core.mjs so individual
  endpoints can honour a configurable delay set via __admin/behavior.
- Wire telegramMeDelayMs into the GET /telegram/me handler so the
  logout-relogin spec can simulate a slow profile fetch and assert that
  the onboarding overlay does not fire before the timeout threshold.
- Reformat long if-conditions and json() call sites to stay within line
  length limits (no logic changes).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

New E2E test helpers detect onboarding overlay visibility with polling waiter functions and implement logout-via-settings workflow with state verification. A test spec validates logout→relogin onboarding behavior by monitoring browser storage and network requests. Mock API gains optional delay support for Telegram profile fetch simulation.

Changes

Cohort / File(s) Summary
E2E Onboarding & Logout Helpers
app/test/e2e/helpers/shared-flows.ts
Added five new exported async functions: isOnboardingOverlayVisible() for direct overlay detection; waitForOnboardingOverlayVisible() and waitForOnboardingOverlayHidden() with polling loops and timeout handling; waitForLoggedOutState() polling for welcome/login screen strings; logoutViaSettings() navigating to Settings, clicking logout, optionally confirming via dialog, and verifying logged-out state with accessibility-tree error context on failure.
Logout-Relogin Onboarding Test
app/test/e2e/specs/logout-relogin-onboarding.spec.ts
New E2E test suite validating onboarding overlay across logout and re-login. Boots app with mock server, performs authenticated login, logs out via settings and verifies persisted auth cleared. Configures delayed Telegram profile fetch, re-authenticates via deep link, asserts network requests (token consume POST and profile GET), polls persisted auth until token restored, checks onboarding overlay timing (not immediately visible, appears within timeout), and verifies onboarding UI text presence.
Mock API Delay Support & Formatting
scripts/mock-api-core.mjs
Added getDelayMs() and sleep() helpers. Implemented async delay on GET /telegram/me handler when mockBehavior.telegramMeDelayMs is a finite positive number. Reformatted JSON responses and nested conditional routes for readability without changing endpoint behavior or computed values.

Sequence Diagram(s)

sequenceDiagram
    actor Test as E2E Test
    participant App as App/Browser
    participant Storage as localStorage
    participant Settings as Settings UI
    participant API as Mock API
    participant Overlay as Onboarding Overlay

    Test->>App: Boot app & login (mocked server)
    App->>Storage: Store auth token + onboarding state
    
    Test->>Settings: logoutViaSettings()
    Settings->>App: Navigate to Settings
    App->>Settings: Display Settings UI
    Settings->>App: Click logout action
    Settings->>Settings: Confirm logout dialog
    Settings->>Storage: Clear persisted auth
    
    Test->>Storage: Poll for auth cleared (token absent)
    Storage-->>Test: Confirm auth cleared
    
    Test->>App: Trigger re-login (deep link)
    App->>API: POST /telegram/login-tokens/consume
    API-->>App: Token consumed
    App->>API: GET /telegram/me (with delay)
    API-->>App: Profile data
    App->>Storage: Store new auth token
    
    Test->>Storage: Poll for new token & onboarding state
    Storage-->>Test: Confirm token present, onboarding empty
    
    Test->>Overlay: Check onboarding NOT visible (immediately)
    Overlay-->>Test: Confirmed hidden
    
    Test->>Overlay: waitForOnboardingOverlayVisible()
    Overlay-->>Test: Overlay appears
    
    Test->>App: Verify onboarding UI (Welcome, Skip)
    App-->>Test: Assertion passed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • senamakel

Poem

🐰 A dash, a click, a logout quest,
E2E flows put to the test,
Onboarding overlays wait and hide,
Re-login brings them back with pride!
Polling loops and state so clean,

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding E2E test coverage and helpers for the logout-relogin-onboarding flow, which is the primary focus of all file modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
app/test/e2e/specs/logout-relogin-onboarding.spec.ts (2)

171-173: Double-parsing of already-decoded values.

getPersistedAuthSnapshot() already decodes the persisted values via its internal decode function. Calling parsePersistedValue() again on lines 171-173 is redundant — if the values are already objects, parsePersistedValue will return them unchanged, so this is harmless but unnecessary.

♻️ Remove redundant parsePersistedValue calls
-    expect(parsePersistedValue(secondSessionState.isOnboardedByUser)).toEqual({});
-    expect(parsePersistedValue(secondSessionState.onboardingTasksByUser)).toEqual({});
-    expect(parsePersistedValue(secondSessionState.hasIncompleteOnboardingByUser)).toEqual({});
+    expect(secondSessionState.isOnboardedByUser).toEqual({});
+    expect(secondSessionState.onboardingTasksByUser).toEqual({});
+    expect(secondSessionState.hasIncompleteOnboardingByUser).toEqual({});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/test/e2e/specs/logout-relogin-onboarding.spec.ts` around lines 171 - 173,
The three assertions redundantly call parsePersistedValue on values already
decoded by getPersistedAuthSnapshot; remove the extra parsing and assert the
decoded fields directly (e.g., check secondSessionState.isOnboardedByUser,
secondSessionState.onboardingTasksByUser, and
secondSessionState.hasIncompleteOnboardingByUser are equal to {}), keeping the
same expect(...).toEqual({}) semantics and leaving getPersistedAuthSnapshot and
parsePersistedValue used elsewhere unchanged.

38-46: Duplicate decode logic — parsePersistedValue is defined but the same logic is inlined in getPersistedAuthSnapshot.

The decode arrow function inside getPersistedAuthSnapshot (lines 55-61) duplicates parsePersistedValue (lines 38-46). Consider removing the inlined version and reusing parsePersistedValue.

However, note that parsePersistedValue is defined outside the browser.execute() scope, so it cannot be called from within that callback. If you want to reuse it, the decode must happen after the execute returns.

♻️ Optional: Refactor to avoid duplication
 async function getPersistedAuthSnapshot() {
-  return browser.execute(() => {
+  const raw = await browser.execute(() => {
     const raw = window.localStorage.getItem('persist:auth');
     if (!raw) return null;
-
-    try {
-      const parsed = JSON.parse(raw);
-      const decode = value => {
-        if (typeof value !== 'string') return value;
-        try {
-          return JSON.parse(value);
-        } catch {
-          return value.replace(/^"|"$/g, '');
-        }
-      };
-
-      return {
-        token: decode(parsed.token),
-        isOnboardedByUser: decode(parsed.isOnboardedByUser),
-        onboardingTasksByUser: decode(parsed.onboardingTasksByUser),
-        hasIncompleteOnboardingByUser: decode(parsed.hasIncompleteOnboardingByUser),
-        isAnalyticsEnabledByUser: decode(parsed.isAnalyticsEnabledByUser),
-      };
-    } catch {
-      return null;
-    }
+    return raw;
   });
+  if (!raw) return null;
+
+  try {
+    const parsed = JSON.parse(raw);
+    return {
+      token: parsePersistedValue(parsed.token),
+      isOnboardedByUser: parsePersistedValue(parsed.isOnboardedByUser),
+      onboardingTasksByUser: parsePersistedValue(parsed.onboardingTasksByUser),
+      hasIncompleteOnboardingByUser: parsePersistedValue(parsed.hasIncompleteOnboardingByUser),
+      isAnalyticsEnabledByUser: parsePersistedValue(parsed.isAnalyticsEnabledByUser),
+    };
+  } catch {
+    return null;
+  }
 }

Also applies to: 55-61

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/test/e2e/specs/logout-relogin-onboarding.spec.ts` around lines 38 - 46,
parsePersistedValue duplicates the inline decode inside getPersistedAuthSnapshot
(the decode arrow in the browser.execute callback); remove the inlined decode
from the execute callback and instead return the raw string values from
browser.execute, then after the execute returns map over those results and call
parsePersistedValue to decode them (since parsePersistedValue is defined outside
the browser.execute scope and cannot be called inside the browser context);
update getPersistedAuthSnapshot to perform decoding post-execute using
parsePersistedValue and delete the duplicated decode logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/test/e2e/specs/logout-relogin-onboarding.spec.ts`:
- Around line 171-173: The three assertions redundantly call parsePersistedValue
on values already decoded by getPersistedAuthSnapshot; remove the extra parsing
and assert the decoded fields directly (e.g., check
secondSessionState.isOnboardedByUser, secondSessionState.onboardingTasksByUser,
and secondSessionState.hasIncompleteOnboardingByUser are equal to {}), keeping
the same expect(...).toEqual({}) semantics and leaving getPersistedAuthSnapshot
and parsePersistedValue used elsewhere unchanged.
- Around line 38-46: parsePersistedValue duplicates the inline decode inside
getPersistedAuthSnapshot (the decode arrow in the browser.execute callback);
remove the inlined decode from the execute callback and instead return the raw
string values from browser.execute, then after the execute returns map over
those results and call parsePersistedValue to decode them (since
parsePersistedValue is defined outside the browser.execute scope and cannot be
called inside the browser context); update getPersistedAuthSnapshot to perform
decoding post-execute using parsePersistedValue and delete the duplicated decode
logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d8365ae-81b3-4ed6-89de-123187cfaa93

📥 Commits

Reviewing files that changed from the base of the PR and between 898fe13 and bff4519.

📒 Files selected for processing (3)
  • app/test/e2e/helpers/shared-flows.ts
  • app/test/e2e/specs/logout-relogin-onboarding.spec.ts
  • scripts/mock-api-core.mjs

@graycyrus graycyrus merged commit cc55323 into tinyhumansai:main Apr 3, 2026
9 checks passed
@YellowSnnowmann YellowSnnowmann linked an issue Apr 3, 2026 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] E2E: Test logout and re-login onboarding overlay behavior

2 participants