Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions .maestro/README-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,22 @@ The pre-existing-room prerequisite intentionally avoids creating unsolicited pee
- The overlay and native `AndroidView(MapView)` fill the same Compose map slot. Target bounds use its actual `onSizeChanged` size, with no status-bar or screen-size correction. Targets are exposed only when the actual viewport starts at `(0,0)` and exactly matches that size. Nonzero origins, size mismatches, null projections, and partly off-map bitmap bounds produce no target; no offset conversion is guessed. Text-only labels do not provide a fabricated bitmap target. `Label.isShow` is checked but is not treated as post-collision visibility proof. SDK label creation, visibility configuration, and projection do not prove hit testing; the actual callback and destination assertions remain mandatory.
- Camera movement start clears targets. Redraw, actual camera movement end, viewport change, and native map-size change refresh projections. Map start/error, removal of observed labels, or disposal clears targets. No ranks, layers, camera destinations, or collision behavior are changed. The same debug/armed boundary gates member/search row descriptions; a missing target fails rather than selecting a public row.
- Permission denial and allowed-permission/null-location are different production branches. The shared denial helper accepts only the exact `permission_deny_button` and `permission_deny_and_dont_ask_again_button` variants, then asserts that the foreground-allow button is absent. It neither grants permission nor presses Back when no dialog exists. This suite asserts the real permission-denial guidance and absence of room entry. It does not claim null-location coverage. Register/search/list/map navigation uses explicit destinations, and room/profile navigation uses Back before accessing the previously hidden bottom tabs. Saved map browsing position remains available when location permission is denied.
- On a denied-permission restart, the flow requires an actual OS denial button and taps it unconditionally. It asserts that the permission controls are gone before waiting for map readiness and requiring the destination controls. Map or navigation readiness cannot satisfy the permission wait. A missing prompt fails instead of skipping denial.

## Safe map diagnostics
## Safe map and place-load diagnostics

The collector preserves map-start evidence even when a flow later fails after SDK readiness. In [Maestro run `39012430640`](https://github.com/projects200/android/actions/runs/39012430640), all three matching flows passed `matching_map_ready`; their later failures are separate from map initialization. Readiness alone does not prove marker hit testing or chat navigation.

Each flow writes `<flow>/matching-map-diagnostics.json` before fixture cleanup or the final app-data clear, including Maestro assertion, timeout, interruption, and runner failures. The optional shared `collect_diagnostics` hook does not replace `after_flow` verification. A collection failure adds only `feature-diagnostics-failed`; the original UI failure remains, and fixture cleanup still runs. Failed restoration still preserves private recovery state instead of clearing it. Other features without this hook keep their existing lifecycle.

The artifact schema is `{"schemaVersion":1,"status":"ok","finalState":"failed","events":[{"sequence":1,"attempt":1,"state":"loading"},{"sequence":2,"attempt":1,"state":"failed","category":"initialization","sdkErrorCode":1001}],"labelClicks":[]}`. `sequence` is the original evidence-array position, including gaps for private non-map events; `attempt` is the sequence of that MapView's actual start. Re-entry/recreation starts a new attempt. Map states are `loading`, `ready`, and `failed`; a failed attempt cannot later become ready. The existing `matching_map_loading` and `matching_map_ready` selectors retain their meaning, and SDK failure exposes `matching_map_failed` only inside the existing debug/armed boundary. Unarmed/release screens do not expose E2E tags or member IDs; failures show only the existing generic error text.
The artifact schema is `{"schemaVersion":1,"status":"ok","finalState":"failed","events":[{"sequence":1,"attempt":1,"state":"loading"},{"sequence":2,"attempt":1,"state":"failed","category":"initialization","sdkErrorCode":1001}],"labelClicks":[],"placeLoads":[]}`. `sequence` is the original evidence-array position, including gaps for private non-map events; `attempt` is the sequence of that MapView's actual start. Re-entry/recreation starts a new attempt. Map states are `loading`, `ready`, and `failed`; a failed attempt cannot later become ready. The existing `matching_map_loading` and `matching_map_ready` selectors retain their meaning, and SDK failure exposes `matching_map_failed` only inside the existing debug/armed boundary. Unarmed/release screens do not expose E2E tags or member IDs; failures show only the existing generic error text.

`labelClicks` records actual SDK label callbacks as `{"sequence":3,"attempt":1,"kind":"cluster"}`. The fixed kinds are `cluster`, `place`, and `unknown`; they contain no tags, coordinates, or identities. A callback must belong to the current ready attempt. Dispatch uses the rendered label's own cluster/place tag, not a new coordinate comparison. `place` means the actual callback selected a primary-place label; an empty array does not establish a peer-marker callback. These diagnostics do not replace the exact peer/location row or room-ownership assertions.

`placeLoads` records actual list-usecase boundaries, not map readiness. A start is `{"sequence":4,"request":4,"state":"started","elapsedMs":0}`. A terminal event has the same `request`, its own original `sequence`, monotonic elapsed milliseconds, and state `success`, `error`, `cancelled`, or `exception`. Only `error` and `exception` include `category`: `network`, `mapping`, `auth`, `server`, or `unknown`. Known result codes and direct IOException types determine these categories; no raw code, error text, exception details, place count, or identity is exported.

Overlapping list requests retain separate start sequences. Each request permits at most one terminal event. A start without a terminal means no terminal was recorded: the request may still be pending, or terminal evidence may have failed to write. It does not establish a network failure or success. Diagnostic write failures do not replace the usecase result; cancellation and thrown exceptions retain their existing propagation. Release and unarmed execution do not create request timers or records. These events never change `finalState`, list readiness, toast behavior, or timeouts.

Only the SDK's public integer `MapAuthException.getErrorCode()` and supported direct network exception types are classified:

| Category | Public evidence / limitation |
Expand All @@ -66,7 +71,7 @@ Only the SDK's public integer `MapAuthException.getErrorCode()` and supported di
| `request` / `quota` | 400 / 429 respectively. |
| `unknown` | -1, an unrecognized signed 32-bit SDK code, or another callback exception with no SDK code. No message/cause-based guesses. |

`finalState` is the latest attempt's observed state; label clicks do not change it. Valid evidence without map events yields `not_observed`; loading or no callback is not an authentication diagnosis. Missing/unreadable evidence, invalid schema/types/order, mismatched run identity, and a file over 1 MiB yield fixed `status` values `read_failed`, `invalid_evidence`, `stale_evidence`, and `size_limit` respectively, with `finalState:"unknown"` and empty `events`/`labelClicks`. At most 1000 source events are accepted. Booleans are not integer codes, attempts, or sequence numbers. Unknown fields/categories/kinds and inconsistent code/category pairs fail closed.
`finalState` is the latest map attempt's observed state; label clicks and place loads do not change it. Valid evidence without map events yields `not_observed`; loading or no callback is not an authentication diagnosis. Missing/unreadable evidence, invalid schema/types/order, mismatched run identity, and a file over 1 MiB yield fixed `status` values `read_failed`, `invalid_evidence`, `stale_evidence`, and `size_limit` respectively, with `finalState:"unknown"` and empty `events`/`labelClicks`/`placeLoads`. At most 1000 source events are accepted. Booleans are not integer codes, attempts, request IDs, durations, or sequence numbers. Unknown fields/categories/kinds and inconsistent code/category pairs fail closed.

The collector uses bounded app-private `run-as` reads, checks the current run ID, and builds new allowlisted JSON objects. It never exports the original root/events, member/place/room identifiers, coordinates, URLs, tokens, exception messages/causes/stacks/classes, or logcat. Raw evidence remains app-private; the artifact is mode 0600 and contains no run ID. Existing logcat exclusion and raw Maestro temporary-file cleanup remain unchanged. Inspect this artifact alongside the existing sanitized flow artifacts. Successful collection alone does not prove Kakao registration, SDK readiness, or a successful device run.

Expand All @@ -82,14 +87,14 @@ After a complete restoration check, feature cleanup retains its private snapshot

No local build, test, formatter, linter, adb, Python, or Maestro command was executed for this patch. Preserved tests cover ownership, debug/armed evidence, SDK code classification, terminal failed readiness, delayed deletion/load state, stale reverse-geocoding responses, private append order, map recreation, 0600 permissions, and the 1000-event limit. The local `test_run_tests.py` transport regressions remain intact: reviewed failure categories, confidentiality, exact operation/feature/status scope, malformed reasons, and private-result retention. Existing regressions cover consumed-search-result replay, actual-label dispatch without coordinate matching, and safe label-click projection. New `MatchingMapTargetTest` cases exercise bitmap-centered bounds, absent/outside projections, clipped targets, invalid sizes, and unsupported viewport origins/sizes. They do not claim collision visibility or SDK input delivery. Existing Maestro validation/member-to-chat flows retain actual marker, exact peer/room, and denial-path assertions; their execution after this change remains pending.

The latest analyzed [Maestro run `39020625550`, attempt 2](https://github.com/projects200/android/actions/runs/39020625550/attempts/2) passed all 12 common flows and `matching-place-crud`. Its report contains 15 flows, two failures, and no skips. Validation and member-to-chat still failed. This patch changes neither GPU selection nor signing.
The latest analyzed [Maestro run `39114978487`, attempt 2](https://github.com/projects200/android/actions/runs/39114978487/attempts/2) executed all 23 flows: 21 passed and two matching flows failed. `smoke-tabs`, all five timer flows, and `matching-place-crud` passed. This patch changes neither GPU selection nor signing.

The received evidence separates the two remaining failures:

- **Place CRUD:** the full flow now passes, including rename and deletion. The flow, persisted ID/address assertions, and complete baseline verification remain unchanged.
- **Validation:** the first `permission_deny_button` dialog was denied successfully. After Add opened the search screen, Android presented `permission_deny_and_dont_ask_again_button`. The old helper skipped that button, and its foreground-allow absence assertion failed. Both condition and tap selectors now recognize the two exact denial variants. All helper callers retain their denied-path assertions; device verification of the new selector remains pending.
- **Member to chat:** SDK readiness and the old peer/location label-creation pattern passed. The externally configured point produced a real SDK `kind:"place"` callback and `places_loaded`, not `rv_members`. The tag dispatcher selected the actual primary-place route correctly. The artifacts did not distinguish overlap from bad calibration. The flow now locates a passive target at the actual SDK-created peer cluster's projected center, both after normal recentering and after denied-permission camera restoration. It still requires the genuine SDK peer route. This observation change is not a claim that the prior failure now passes.
- **Validation:** the initial place-list request still showed `places_loading` after its 20-second wait. The artifact contains a ready map but no list-request outcome. No app crash was reported. This patch adds request-boundary diagnostics; it does not claim a list-load fix or lengthen the timeout.
- **Member to chat:** the real marker, peer profile, room entry, and return to map succeeded. After restart with location denied, a new OS permission dialog covered the bottom navigation. The flow waited for a navigation node, then tried to tap it before handling the dialog. Both denied-restart flows now handle the prompt before map interaction and require actual map readiness afterward.

Marker targeting no longer requires an external percentage point. The fixture still derives location from the authorized peer's actual server place and preserves all peer/catalog/bounds/room prerequisites. Missing or unsupported runtime projections fail closed. Private screenshots and actual callback/destination evidence remain necessary to diagnose any collision or touch-delivery failure; do not publish coordinates, identities, or private snapshots.

Required remote verification includes matching unit tests, `:app:testE2eUnitTest`, instrumentation evidence tests, Python transport/feature-hook tests, and authenticated `--suite matching`. New runtime evidence must name all three flows, prove persisted ID/address and room-peer checks, and show cleanup after a mid-flow failure. Recovery verification includes a failure after feature cleanup and a retry with the same request: snapshot/evidence must remain unchanged until final successful host clear, and baseline data must remain intact. Record the selected suite/device API: combined `ci` used API 34, while the matching descriptor declares API 30. Native/Compose size agreement, actual SDK tapping, camera-dependent target refresh, saved-camera restoration after denial, real catalog stability, and release/unarmed accessibility remain runtime checks. Missing prerequisites must fail explicitly; they must not cause skipped assertions or fabricated fixtures.
Required remote verification includes matching unit tests, `:app:testE2eUnitTest`, instrumentation evidence tests, Python transport/feature-hook tests, and authenticated `--suite matching`. The new diagnostics tests cover fixed error categories, release/unarmed gating, actual private file records and permissions, unfinished/interleaved requests, and malformed/stale/oversized public evidence. They have not been run locally. New runtime evidence must name all three flows, prove persisted ID/address and room-peer checks, and show cleanup after a mid-flow failure. Recovery verification includes a failure after feature cleanup and a retry with the same request: snapshot/evidence must remain unchanged until final successful host clear, and baseline data must remain intact. Record the selected suite/device API: combined `ci` used API 34, while the matching descriptor declares API 30. Native/Compose size agreement, actual SDK tapping, camera-dependent target refresh, saved-camera restoration after denial, real catalog stability, and release/unarmed accessibility remain runtime checks. Missing prerequisites must fail explicitly; they must not cause skipped assertions or fabricated fixtures.
16 changes: 12 additions & 4 deletions .maestro/flows/matching-member-to-chat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,27 @@ tags:
all: allow
notifications: allow
location: deny
# SDK 초기화 뒤 OS 권한 창이 늦게 뜰 수 있으므로 실제 거부 버튼을 확인한 뒤에만 진행합니다.
- extendedWaitUntil:
visible:
id: matching_nav_graph
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
timeout: 60000
- tapOn:
id: matching_nav_graph
- runFlow: ../subflows/matching-deny-location-prompt.yaml
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
- assertNotVisible:
id: ".*:id/permission_allow_foreground_only_button"
- assertNotVisible:
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
- runFlow: ../subflows/matching-dismiss-guides.yaml
- extendedWaitUntil:
visible:
id: matching_map_ready
timeout: 30000
- runFlow: ../subflows/matching-deny-location-prompt.yaml
- assertVisible:
id: matching_map_ready
- assertVisible:
id: matching_nav_graph
- assertVisible: "운동 장소 목록"
# 위치 권한을 거부해도 저장한 지도 카메라는 보존됩니다. 같은 peer·장소 마커를 다시 검증합니다.
- extendedWaitUntil:
visible: ${MATCHING_PEER_MARKER_PATTERN}
Expand Down
16 changes: 12 additions & 4 deletions .maestro/flows/matching-place-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,27 @@ tags:
all: allow
notifications: allow
location: deny
# SDK 초기화 뒤 OS 권한 창이 늦게 뜰 수 있으므로 실제 거부 버튼을 확인한 뒤에만 진행합니다.
- extendedWaitUntil:
visible:
id: matching_nav_graph
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
timeout: 60000
- tapOn:
id: matching_nav_graph
- runFlow: ../subflows/matching-deny-location-prompt.yaml
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
- assertNotVisible:
id: ".*:id/permission_allow_foreground_only_button"
- assertNotVisible:
id: ".*:id/permission_deny(?:_and_dont_ask_again)?_button"
- runFlow: ../subflows/matching-dismiss-guides.yaml
- extendedWaitUntil:
visible:
id: matching_map_ready
timeout: 30000
- runFlow: ../subflows/matching-deny-location-prompt.yaml
- assertVisible:
id: matching_map_ready
- assertVisible:
id: matching_nav_graph
- assertVisible: "운동 장소 목록"
- tapOn: "운동 장소 목록"
- extendedWaitUntil:
visible:
Expand Down
Loading
Loading