Ctrl K
ComunidadGuías…

QA_SCENARIOS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# QA Scenarios — Cross-Platform SDK Test Catalog

> Reference catalog of test scenarios across the Android, iOS, and web SDKs.
> Each cluster lists the recurring failure modes observed in the field
> (anonymized), the recommended test layer, and the current automated
> coverage status. Use this as the input to test-planning decisions and as
> a cross-platform parity reference.

## Test layers

| Layer | Where | Cost per test | Catches |
|-------|-------|---------------|---------|
| L1 — Hermetic unit | JVM / Swift unit test, fakes for backend | seconds | Pure logic: reducers, parsers, state machines, formatters |
| L2 — Component UI | Compose UI / SwiftUI / Vitest + RTL | seconds–minutes | Single-component rendering + interaction |
| L3 — E2E synthetic | Maestro / Playwright against seeded test app | minutes | Multi-screen flows, real network, cross-platform parity |
| L4 — Manual | Tester on a real device | hours | UX feel, IME quirks, real-device-only bugs |

General rule: **push every test as far down the pyramid as it will go.** A
pure reducer behaviour that can be expressed as L1 should never live as L2
or L3. L3 is reserved for what L1 + L2 genuinely cannot reach — real
network, real OS surfaces, cross-platform parity, second device.

## Scenario clusters

### A. Multi-room state machine

**Failure modes:**
- After opening Room A successfully, switching to Room B leaves the UI on
  "Connecting" indefinitely.
- After re-login, only the most recently opened room loads cleanly;
  previously opened rooms then stick on "Connecting".
- Some rooms permanently spin on history fetch while others in the same
  session load fine.
- Room header shows "0 users" despite the room having members.
- Empty-state placeholder ("Start a conversation") renders in place of a
  previously-working room after a server-side data shape change.

**Why high priority:** all symptoms point at the same underlying class —
per-room state not isolated from session-level state, and presence /
history responses not surviving room switches cleanly. Fix-once,
test-everywhere territory.

**Recommended tests:**
- L1 `RoomStoreTest`: open A → open B → switch back to A. Assert per-room
  connection state survives the switch, current-room pointer transfers, no
  cross-contamination of pending presence callbacks.
- L1 `XMPPPresenceCacheTest`: presence-response markers persist across room
  changes; no stale `joining=true` flag leaks after switch.
- L1 `RoomMembershipTest`: member count derived from roster matches
  `members.size`, not a stale field — catches the "0 users when populated"
  failure mode.
- L2 Compose / SwiftUI `ChatRoomViewTest`: prop change to room JID re-keys
  the tree cleanly. Scroll state, draft, and unread reset without holding
  refs to the previous room.
- L3 Maestro: open A → open B → back to A flow, repeated 3× under one
  session; assert no "Connecting" loop on the third re-entry.

**Coverage status:** None hermetic today. Highest-priority cluster.

### B. Send + media orchestration

**Failure modes:**
- Text + image sent in the same compose action → image arrives, text
  silently dropped.
- File upload completes but doesn't commit as a chat message until the
  user performs another action (sends a second message, leaves room,
  etc.).
- Send orchestrator emits inconsistent statuses ("sending" vs immediate
  error) for messages that ultimately deliver successfully.
- Combined-send variant per platform: web drops text, native drops or
  stalls file — same root cause, different presentation.

**Recommended tests:**
- L1 `MessageSendOrchestratorTest`: `send(body, mediaUri)`. Assert
  orchestrator emits exactly 2 outgoing messages (or 1 combined OOB, per
  the locked contract), in order, neither swallowed. Current SDK contract
  treats two separate messages as acceptable.
- L1 `MessageSendOrchestratorTest`: `send(mediaUri only)`. Assert 1
  outgoing OOB stanza, no spurious empty-body stanza alongside.
- L1 `MessageSendOrchestratorTest`: `send(body only)`. Assert 1 outgoing
  body stanza, no orphan media artifact.
- L2 Compose / SwiftUI `ChatInputTest` extension: attach preview + typed
  text → tap send → assert both render as expected bubbles.

**Coverage status:** Partial. `PendingMediaSendQueueTest` covers the
queue; no orchestrator-contract test exists yet.

### C. Connection lifecycle

**Failure modes:**
- XMPP socket stays null until eventual reopening — incoming messages
  backfill only when the user resumes the app or sends a second message.
- Pending media uploads commit only when the user triggers a subsequent
  action, not when the network is actually restored.
- Same user logged into two sessions (e.g., real device + simulator) sees
  their own messages duplicated, with a noticeable backfill delay.

**Recommended tests:**
- L1 `XMPPConnectionStateMachineTest`: reconnect transitions don't
  double-flush pending queues; socket-null guard doesn't silently swallow
  outgoing sends.
- L1 `MessageRetryQueueTest`: queue commits on a connection-restored
  event, not on an unrelated user action.
- L3 Maestro: backgrounding flow — send msg → home button → resume after
  10s → assert msg delivered without manual second-send nudge.
- L4 Manual: dual-session same-user test (XMPP resource conflict
  territory), since the duplicate-self-message bug only surfaces with a
  real second client.

**Coverage status:** None hermetic.

### D. Send-status correctness & duplication

**Failure modes:**
- Bulk-send under degraded network → some messages briefly show error →
  seconds later they deliver successfully → original error bubbles remain
  visible alongside the delivered duplicate.
- Status flips between "sending" and "error" inconsistently for messages
  that eventually deliver fine.
- App crashes during sustained high-frequency send on certain Android API
  levels.

**Recommended tests:**
- L1 `MessageStoreTest`: `pending → sendFailed → server-echo` arrives.
  Assert echo merges into the original bubble; no stale failed bubble
  remains in the list.
- L1 `MessageStoreTest`: explicit `retry()` vs auto-retry — both paths
  must reconcile against incoming echo, neither leaves a duplicate.
- L1 `MessageStoreTest`: bulk add of 10 messages where 3 are marked
  `sendFailed=true` first, then all 10 echo back from server → final list
  size is 10, no orphan failed bubbles.
- L2 Compose / SwiftUI: drive a 10-message rapid-send with simulated 30%
  failure rate; assert final visible bubble count is 10, not 10 + 3 stale
  failures.
- L4 Manual: sustained-send + intentional network blip + crash-resistance
  test across the Android API matrix.

**Coverage status:** Partial. Bidirectional `pending ↔ echo` ID matching
covered. Failure-then-recovery NOT covered.

### E. Unread counter

**Failure modes:**
- Own messages appear as unread after re-login (cursor reset bug — own
  messages from MAM history should never count as unread).
- Unread badges don't render on the room-list when no chat room is
  currently focused.
- Edge cases acknowledged as still being investigated: re-entry resets,
  multi-room concurrent receive, edits, deletes.

**Recommended tests:**
- L1 `UnreadObserverTest`: MAM replay of own messages → cursor sets to
  last own message → unread count = 0.
- L1: room focus reset clears unread badge on entry; room-switch
  transfers focus correctly without bleeding unread between rooms.
- L1: deleted-while-unread doesn't decrement past zero; deleting an
  already-read message doesn't change count.
- L1: edits-while-unread don't double-count (edit is not a new unread).
- L1: badge formatter — 0 → empty, 1-99 → exact, 100+ → "99+".
- L2: badge component shows count when prop > 0, hides when 0.

**Coverage status:** Partial. `UnreadObserverTest` exists. Edge cases
above mostly uncovered.

### F. History + render parity

**Failure modes:**
- After re-login, first room opened shows no history (silent load
  failure).
- File icon missing on file messages loaded from MAM history; the same
  file-type message rendered from a live receive shows the icon
  correctly.
- Some rooms permanently loading history while others in the same session
  load fine.

**Recommended tests:**
- L1 `MessageCacheTest`: seed 30 messages → re-instantiate store → assert
  30 visible without round-trip (verifies fast-load contract).
- L1 `MessageCacheTest`: per-room cap at 100 — seed 150, assert eviction
  retains the most-recent 100.
- L1 `MessageRenderTest`: `Message(type=file)` rendered from "live
  receive" path vs "MAM replay" path → assert visual output identical
  (icon, filename, click target). Catches the historical-icon-missing
  class of bug.
- L2 Compose / SwiftUI: file-message bubble snapshot — regardless of
  origin path, icon and metadata must render.

**Coverage status:** None for cache contract. Visual parity gap (history
vs live receive) not covered.

### G. Media upload edges

**Failure modes:**
- Camera-captured photos can't be sent on Android (different URI scheme
  path than gallery picker, likely diverges in the upload pipeline).
- Large files (>2 MB) fail with 413 in some setups but succeed in others
  — reference server limit is 200 MB, so failures suggest client- or
  proxy-side size limits not aligned with backend.
- Displayed file size differs between bubble preview and file picker —
  two formatters drifting.
- Sent vs received file bubbles styled differently (sent inset as card,
  received inlined) — looks like a regression rather than intent.

**Recommended tests:**
- L1 `MediaSendTest`: file URI from camera → upload pipeline path matches
  gallery URI path; no special-case lost.
- L1 `MediaSizeFormatterTest`: single source of truth between preview
  formatter and bubble formatter — same byte count → same human string.
- L2 Compose / SwiftUI: sent vs received file bubble snapshot parity —
  same `Message` data, both branches must produce visually equivalent
  bubbles, or document the intentional difference.

**Coverage status:** None.

### H. Configuration safety

**Failure modes:**
- SDK has historically shipped with hardcoded fallback URLs that would
  silently route to default servers if configuration was missing —
  flagged as a privacy/security risk by enterprise consumers. Subsequent
  decision: SDK must fail explicitly on missing config, never silently
  fall back.

**Recommended tests:**
- L1 `ChatConfigurationTest`: SDK init **throws** on missing API host /
  XMPP host / app ID — no fallback to any default value, no silent
  degraded mode.
- Build-time check: source regex scan for known-default URL patterns in
  compiled SDK output.

**Coverage status:** Partial. `ChatConfigurationTest` exists; explicit
"no-fallback, fail-loud" assertion missing.

### I. UI affordances

**Failure modes:**
- Back arrow visible even when the rooms feature is disabled by config;
  tapping is a no-op.
- Scroll-to-bottom after send doesn't reach the bottommost message; user
  has to tap the scroll-down chip after every send.
- URL bubbles tinted / dimmed inconsistently with other text content.
- Header shows "Chat room name" placeholder and "0 users" even when the
  room is populated — partial render bug.

**Recommended tests:**
- L2: each affordance against the config matrix — `roomsDisabled = true`
  must hide the back arrow; `singleRoomMode = true` must hide the room
  list.
- L2: post-send scroll position — assert lazy column / list view scrolls
  to last item index after the send action.
- L2 / screenshot test: bubble style matrix (text, URL, image, file,
  deleted, pending, failed) — preferably with Paparazzi / Roborazzi for
  Android and reference-image snapshots for iOS once a baseline exists.
- L4 Manual: IME / keyboard layout, since Compose UI assertions on IME
  state are flaky.

**Coverage status:** Component-level tests exist for several affordances.
Config-matrix coverage is thin.

### J. App lifecycle

**Failure modes:**
- App crashes during message send on specific Android API levels.
- Process death + restore behavior unverified for draft text, unread
  counts, scroll position.

**Recommended tests:**
- L1 `SavedInstanceStateTest`: draft text and scroll position survive a
  config change (rotation, font scale).
- L2 Maestro: backgrounding → resume after 30s → assert no crash, state
  intact, unread badge correct.
- L4 Manual: device-specific Android API matrix (10 / 11 / 12 / 13 / 14
  / 15) for crash patterns under sustained load.

**Coverage status:** None.

### K. Auth + token state

**Failure modes:**
- 401 Unauthorized on `/users/client` after a JWT format change between
  major SDK versions.
- Token re-dispatch missing → chat component fails to re-init when the
  embedding app refreshes its token prop.

**Recommended tests:**
- L1 `JwtFormatTest`: parser handles both legacy and current token shapes
  cleanly during a deprecation window.
- L1 `LoginWrapperTest`: token-prop change triggers full re-init (close
  existing sockets, clear stores, re-auth, re-join).
- L2 Vitest (web): `<LoginWrapper>` re-renders on token swap.

**Coverage status:** Partial on web. Native unrepresented.

### L. Cross-platform parity

**Failure modes:**
- Message delivery instant browser → Android, ~500 ms Android → browser —
  same test, asymmetric latency.
- Combined send (text + media) drops different parts on different
  platforms — same orchestration bug presenting differently.
- Same multi-room state bug surfaces with different symptoms on web vs
  native.

**Recommended tests:**
- L1: shared test-vectors file (JSON) covering wire-level stanza shapes,
  consumed by parsers on both Android and iOS — guarantees parse symmetry
  for every documented stanza type.
- L3 Maestro + Playwright shared seeded fixture: send from platform A,
  assert receipt on platform B within N seconds, repeat for each platform
  pair.

**Coverage status:** None. Highest infrastructure cost (needs the seeded
test app instance, below).

## Seeded test app instance — recommended infrastructure

Several clusters above (A, C, F, L especially) gain massive testability if
runs target a pre-seeded test app instance with known data rather than
generating data live every run.

Suggested shape:
- A dedicated QA app ID (e.g. `ethora_qa_<env>`)
- 4 deterministic test users (`qa-a`, `qa-b`, `qa-c`, `qa-d`)
- 3 deterministic rooms:
  - empty room (baseline render)
  - active room (50 messages: mix of text, media, edited, deleted)
  - long-history room (500 messages, deterministic content)
- A reset / reseed script (drops + reseeds in ~10s via existing admin
  APIs)
- A CI job that wakes the QA instance before each E2E suite run

After this exists, every cross-platform parity test becomes cheap and
deterministic. Estimated build cost: ~1 day.

## Recommended priority — next 30 days

1. **L1 cluster targeting recurring field bugs** (cheap, hermetic, no
   infra): A multi-room state, D failure-recovery, E unread MAM, F cache
   contract, B send-orchestrator. Target: 12–15 new tests.
2. **L2 Compose / SwiftUI extensions on emulator / simulator** (week 2):
   config-matrix UI affordances (I), combined-send (B).
3. **Seeded test app infrastructure** (week 3): unblocks L3.
4. **L3 cross-platform parity** (after seeded app): Maestro + Playwright
   on the top five flows.

## How to extend this catalog

- Add new clusters when a field bug class doesn't fit cleanly into A–L.
- Within a cluster, add concrete failure modes (one bullet each) before
  adding tests — they are the evidence base for why the test matters.
- Anonymize: no customer names, individual engineer names, JIRA ticket
  IDs, or environment-specific URLs. Generic descriptions only.
- Mark coverage status honestly — "Partial" means *some* tests exist;
  list the specific test names if it helps future readers.

## Per-SDK testing entry points

- Android SDK: `sdk-android/README.md` — Compose UI test scaffold + JVM
  unit-test layer.
- iOS SDK: `sdk-swift/README.md` — XCTest scaffold + accessibility
  identifiers for cross-platform parity.
- Web (chat component): `sdk-reactjs/README.md` — Vitest + RTL setup,
  shared `data-testid` IDs.
- Sample apps: `sample-android/README.md`, `sample-swift/README.md` —
  Maestro E2E flow libraries.