From dec096024255b62fb69964413baa54ec1d5bc319 Mon Sep 17 00:00:00 2001 From: justanotheratom Date: Mon, 9 Mar 2026 13:15:16 +0530 Subject: [PATCH 1/4] Feature: expand Maestro test suite from 35 to 63 flows Add 28 new Maestro E2E flows covering product detail interactions, lists/favorites navigation, settings danger zone actions, food notes editing, home screen content, family management, and chat. All 63 flows pass on simulator. - 4 new UITestScenario cases (productDetailFavorited, productDetailStale, recentScansEmpty, settingsGuest) with corresponding fixtures - 4 new launch subflows for the new scenarios - 6 new accessibility IDs across ProductDetailView, IngredientsAlertCard, HomeView, ManageFamilyView, and ListsTab - 28 new test flows across product, lists, settings, food_notes, home, family, chat, and scan domains Co-Authored-By: Claude Opus 4.6 --- .maestro/flows/chat/02_predefined_reply.yaml | 25 +++++ .maestro/flows/chat/03_chat_skip.yaml | 21 ++++ .maestro/flows/family/02_members_visible.yaml | 14 +++ .../family/03_pending_member_status.yaml | 12 +++ .../food_notes/03_open_intolerances_edit.yaml | 27 ++++++ .../food_notes/04_open_diet_types_edit.yaml | 25 +++++ .../food_notes/05_open_lifestyle_edit.yaml | 25 +++++ .../food_notes/06_food_notes_chat_button.yaml | 14 +++ .../flows/home/05_home_content_cards.yaml | 17 ++++ .../flows/home/06_home_stats_visible.yaml | 15 +++ .maestro/flows/home/07_home_greeting.yaml | 10 ++ .../flows/home/08_home_family_avatars.yaml | 17 ++++ .../flows/lists/03_favorites_tap_item.yaml | 16 ++++ .../flows/lists/04_recent_scans_tap_item.yaml | 16 ++++ .../05_recent_scans_filter_favorites.yaml | 20 ++++ .../lists/06_recent_scans_empty_state.yaml | 14 +++ .../lists/07_recent_scans_all_items.yaml | 14 +++ .../flows/product/02_toggle_favorite.yaml | 14 +++ .maestro/flows/product/03_unfavorite.yaml | 12 +++ .../flows/product/04_ingredients_visible.yaml | 15 +++ .../flows/product/05_ingredient_alert.yaml | 21 ++++ .../flows/product/06_reanalyze_stale.yaml | 12 +++ .../flows/scan/05_scan_to_product_detail.yaml | 25 +++++ .../flows/settings/10_sign_out_confirm.yaml | 17 ++++ .../flows/settings/11_sign_out_cancel.yaml | 27 ++++++ .../settings/12_delete_account_flow.yaml | 20 ++++ .../settings/13_delete_account_cancel.yaml | 23 +++++ .../flows/settings/14_guest_reset_app.yaml | 15 +++ .../launch/product_detail_favorited.yaml | 8 ++ .../subflows/launch/product_detail_stale.yaml | 8 ++ .../subflows/launch/recent_scans_empty.yaml | 8 ++ .maestro/subflows/launch/settings_guest.yaml | 8 ++ .../Components/FilterSegmentedControl.swift | 1 + IngrediCheck/Testing/UITestHarness.swift | 95 +++++++++++++++++++ .../Views/Family/ManageFamilyView.swift | 2 + IngrediCheck/Views/HomeView.swift | 2 + .../ProductDetails/IngredientsAlertCard.swift | 1 + .../ProductDetails/ProductDetailView.swift | 1 + IngrediCheck/Views/Tabs/ListsTab.swift | 1 + IngrediCheck/Views/Tabs/SettingsSheet.swift | 7 +- 40 files changed, 643 insertions(+), 2 deletions(-) create mode 100644 .maestro/flows/chat/02_predefined_reply.yaml create mode 100644 .maestro/flows/chat/03_chat_skip.yaml create mode 100644 .maestro/flows/family/02_members_visible.yaml create mode 100644 .maestro/flows/family/03_pending_member_status.yaml create mode 100644 .maestro/flows/food_notes/03_open_intolerances_edit.yaml create mode 100644 .maestro/flows/food_notes/04_open_diet_types_edit.yaml create mode 100644 .maestro/flows/food_notes/05_open_lifestyle_edit.yaml create mode 100644 .maestro/flows/food_notes/06_food_notes_chat_button.yaml create mode 100644 .maestro/flows/home/05_home_content_cards.yaml create mode 100644 .maestro/flows/home/06_home_stats_visible.yaml create mode 100644 .maestro/flows/home/07_home_greeting.yaml create mode 100644 .maestro/flows/home/08_home_family_avatars.yaml create mode 100644 .maestro/flows/lists/03_favorites_tap_item.yaml create mode 100644 .maestro/flows/lists/04_recent_scans_tap_item.yaml create mode 100644 .maestro/flows/lists/05_recent_scans_filter_favorites.yaml create mode 100644 .maestro/flows/lists/06_recent_scans_empty_state.yaml create mode 100644 .maestro/flows/lists/07_recent_scans_all_items.yaml create mode 100644 .maestro/flows/product/02_toggle_favorite.yaml create mode 100644 .maestro/flows/product/03_unfavorite.yaml create mode 100644 .maestro/flows/product/04_ingredients_visible.yaml create mode 100644 .maestro/flows/product/05_ingredient_alert.yaml create mode 100644 .maestro/flows/product/06_reanalyze_stale.yaml create mode 100644 .maestro/flows/scan/05_scan_to_product_detail.yaml create mode 100644 .maestro/flows/settings/10_sign_out_confirm.yaml create mode 100644 .maestro/flows/settings/11_sign_out_cancel.yaml create mode 100644 .maestro/flows/settings/12_delete_account_flow.yaml create mode 100644 .maestro/flows/settings/13_delete_account_cancel.yaml create mode 100644 .maestro/flows/settings/14_guest_reset_app.yaml create mode 100644 .maestro/subflows/launch/product_detail_favorited.yaml create mode 100644 .maestro/subflows/launch/product_detail_stale.yaml create mode 100644 .maestro/subflows/launch/recent_scans_empty.yaml create mode 100644 .maestro/subflows/launch/settings_guest.yaml diff --git a/.maestro/flows/chat/02_predefined_reply.yaml b/.maestro/flows/chat/02_predefined_reply.yaml new file mode 100644 index 00000000..6579179c --- /dev/null +++ b/.maestro/flows/chat/02_predefined_reply.yaml @@ -0,0 +1,25 @@ +appId: llc.fungee.ingredicheck +tags: [chat] +--- +- runFlow: ../../subflows/launch/chat.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- tapOn: + text: Open chat +- extendedWaitUntil: + visible: + id: chat_view + timeout: 30000 +- tapOn: + id: chat_input +- inputText: what should i avoid? +- tapOn: + id: chat_send_button +- extendedWaitUntil: + visible: + text: Avoid peanuts, dairy, and lactose based on the seeded simulator profile\. + timeout: 30000 +- assertVisible: + text: Avoid peanuts, dairy, and lactose based on the seeded simulator profile\. diff --git a/.maestro/flows/chat/03_chat_skip.yaml b/.maestro/flows/chat/03_chat_skip.yaml new file mode 100644 index 00000000..1ad6008e --- /dev/null +++ b/.maestro/flows/chat/03_chat_skip.yaml @@ -0,0 +1,21 @@ +appId: llc.fungee.ingredicheck +tags: [chat] +--- +- runFlow: ../../subflows/launch/chat.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- tapOn: + text: Open chat +- extendedWaitUntil: + visible: + id: chat_view + timeout: 30000 +- swipe: + direction: DOWN + duration: 500 +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 diff --git a/.maestro/flows/family/02_members_visible.yaml b/.maestro/flows/family/02_members_visible.yaml new file mode 100644 index 00000000..172a4c97 --- /dev/null +++ b/.maestro/flows/family/02_members_visible.yaml @@ -0,0 +1,14 @@ +appId: llc.fungee.ingredicheck +tags: [family] +--- +- runFlow: ../../subflows/launch/manage_family.yaml +- extendedWaitUntil: + visible: + id: manage_family_view + timeout: 30000 +- assertVisible: + text: Aarav +- assertVisible: + text: Riya +- assertVisible: + text: Kabir diff --git a/.maestro/flows/family/03_pending_member_status.yaml b/.maestro/flows/family/03_pending_member_status.yaml new file mode 100644 index 00000000..8dd20b50 --- /dev/null +++ b/.maestro/flows/family/03_pending_member_status.yaml @@ -0,0 +1,12 @@ +appId: llc.fungee.ingredicheck +tags: [family, invite] +--- +- runFlow: ../../subflows/launch/manage_family.yaml +- extendedWaitUntil: + visible: + id: manage_family_view + timeout: 30000 +- assertVisible: + text: Kabir +- assertVisible: + id: manage_family_invite_button_33333333-3333-3333-3333-333333333333 diff --git a/.maestro/flows/food_notes/03_open_intolerances_edit.yaml b/.maestro/flows/food_notes/03_open_intolerances_edit.yaml new file mode 100644 index 00000000..da415a84 --- /dev/null +++ b/.maestro/flows/food_notes/03_open_intolerances_edit.yaml @@ -0,0 +1,27 @@ +appId: llc.fungee.ingredicheck +tags: [food-notes] +--- +- runFlow: ../../subflows/launch/food_notes.yaml +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 +- scrollUntilVisible: + element: + id: food_notes_edit_intolerances + direction: DOWN + timeout: 30000 +- tapOn: + id: food_notes_edit_intolerances +- extendedWaitUntil: + visible: + id: food_notes_edit_sheet + timeout: 30000 +- assertVisible: + text: Lactose +- tapOn: + text: Done +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 diff --git a/.maestro/flows/food_notes/04_open_diet_types_edit.yaml b/.maestro/flows/food_notes/04_open_diet_types_edit.yaml new file mode 100644 index 00000000..59d8da54 --- /dev/null +++ b/.maestro/flows/food_notes/04_open_diet_types_edit.yaml @@ -0,0 +1,25 @@ +appId: llc.fungee.ingredicheck +tags: [food-notes] +--- +- runFlow: ../../subflows/launch/food_notes.yaml +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 +- scrollUntilVisible: + element: + id: food_notes_edit_avoid + direction: DOWN + timeout: 30000 +- tapOn: + id: food_notes_edit_avoid +- extendedWaitUntil: + visible: + id: food_notes_edit_sheet + timeout: 30000 +- tapOn: + text: Done +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 diff --git a/.maestro/flows/food_notes/05_open_lifestyle_edit.yaml b/.maestro/flows/food_notes/05_open_lifestyle_edit.yaml new file mode 100644 index 00000000..ab8eda2f --- /dev/null +++ b/.maestro/flows/food_notes/05_open_lifestyle_edit.yaml @@ -0,0 +1,25 @@ +appId: llc.fungee.ingredicheck +tags: [food-notes] +--- +- runFlow: ../../subflows/launch/food_notes.yaml +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 +- scrollUntilVisible: + element: + id: food_notes_edit_life_style + direction: DOWN + timeout: 30000 +- tapOn: + id: food_notes_edit_life_style +- extendedWaitUntil: + visible: + id: food_notes_edit_sheet + timeout: 30000 +- tapOn: + text: Done +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 diff --git a/.maestro/flows/food_notes/06_food_notes_chat_button.yaml b/.maestro/flows/food_notes/06_food_notes_chat_button.yaml new file mode 100644 index 00000000..0a632948 --- /dev/null +++ b/.maestro/flows/food_notes/06_food_notes_chat_button.yaml @@ -0,0 +1,14 @@ +appId: llc.fungee.ingredicheck +tags: [food-notes, chat] +--- +- runFlow: ../../subflows/launch/food_notes.yaml +- extendedWaitUntil: + visible: + id: unified_canvas_view + timeout: 30000 +- tapOn: + id: food_notes_notes_chat_button +- extendedWaitUntil: + visible: + id: chat_view + timeout: 30000 diff --git a/.maestro/flows/home/05_home_content_cards.yaml b/.maestro/flows/home/05_home_content_cards.yaml new file mode 100644 index 00000000..2a2e23d3 --- /dev/null +++ b/.maestro/flows/home/05_home_content_cards.yaml @@ -0,0 +1,17 @@ +appId: llc.fungee.ingredicheck +tags: [home, content] +--- +- runFlow: ../../subflows/launch/home.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- assertVisible: + id: home_food_notes_card +- scrollUntilVisible: + element: + id: home_recent_scans_section + direction: DOWN + timeout: 30000 +- assertVisible: + id: home_recent_scans_section diff --git a/.maestro/flows/home/06_home_stats_visible.yaml b/.maestro/flows/home/06_home_stats_visible.yaml new file mode 100644 index 00000000..f8712403 --- /dev/null +++ b/.maestro/flows/home/06_home_stats_visible.yaml @@ -0,0 +1,15 @@ +appId: llc.fungee.ingredicheck +tags: [home, stats] +--- +- runFlow: ../../subflows/launch/home.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- scrollUntilVisible: + element: + text: "34" + direction: DOWN + timeout: 30000 +- assertVisible: + text: "34" diff --git a/.maestro/flows/home/07_home_greeting.yaml b/.maestro/flows/home/07_home_greeting.yaml new file mode 100644 index 00000000..ebe7b733 --- /dev/null +++ b/.maestro/flows/home/07_home_greeting.yaml @@ -0,0 +1,10 @@ +appId: llc.fungee.ingredicheck +tags: [home, greeting] +--- +- runFlow: ../../subflows/launch/home.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- assertVisible: + text: Aarav diff --git a/.maestro/flows/home/08_home_family_avatars.yaml b/.maestro/flows/home/08_home_family_avatars.yaml new file mode 100644 index 00000000..33e4cdda --- /dev/null +++ b/.maestro/flows/home/08_home_family_avatars.yaml @@ -0,0 +1,17 @@ +appId: llc.fungee.ingredicheck +tags: [home, family] +--- +- runFlow: ../../subflows/launch/home.yaml +- extendedWaitUntil: + visible: + id: home_view + timeout: 30000 +- assertVisible: + text: Aarav +- scrollUntilVisible: + element: + text: Your IngrediFam + direction: DOWN + timeout: 30000 +- assertVisible: + text: Your IngrediFam diff --git a/.maestro/flows/lists/03_favorites_tap_item.yaml b/.maestro/flows/lists/03_favorites_tap_item.yaml new file mode 100644 index 00000000..c99fe39a --- /dev/null +++ b/.maestro/flows/lists/03_favorites_tap_item.yaml @@ -0,0 +1,16 @@ +appId: llc.fungee.ingredicheck +tags: [lists, favorites] +--- +- runFlow: ../../subflows/launch/favorites.yaml +- extendedWaitUntil: + visible: + id: favorites_page_view + timeout: 30000 +- tapOn: + text: Oreo Original +- extendedWaitUntil: + visible: + text: Ingredients + timeout: 30000 +- assertVisible: + text: Oreo Original diff --git a/.maestro/flows/lists/04_recent_scans_tap_item.yaml b/.maestro/flows/lists/04_recent_scans_tap_item.yaml new file mode 100644 index 00000000..db6187d2 --- /dev/null +++ b/.maestro/flows/lists/04_recent_scans_tap_item.yaml @@ -0,0 +1,16 @@ +appId: llc.fungee.ingredicheck +tags: [lists, recent-scans] +--- +- runFlow: ../../subflows/launch/recent_scans.yaml +- extendedWaitUntil: + visible: + id: recent_scans_page_view + timeout: 30000 +- tapOn: + text: Diet Coke Soft Drink +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- assertVisible: + text: Diet Coke Soft Drink diff --git a/.maestro/flows/lists/05_recent_scans_filter_favorites.yaml b/.maestro/flows/lists/05_recent_scans_filter_favorites.yaml new file mode 100644 index 00000000..22d3e5b5 --- /dev/null +++ b/.maestro/flows/lists/05_recent_scans_filter_favorites.yaml @@ -0,0 +1,20 @@ +appId: llc.fungee.ingredicheck +tags: [lists, recent-scans, filter] +--- +- runFlow: ../../subflows/launch/recent_scans.yaml +- extendedWaitUntil: + visible: + id: recent_scans_page_view + timeout: 30000 +- assertVisible: + text: Diet Coke Soft Drink +- tapOn: + text: Fav +- extendedWaitUntil: + visible: + text: Oreo Original + timeout: 10000 +- assertVisible: + text: Oreo Original +- assertNotVisible: + text: Diet Coke Soft Drink diff --git a/.maestro/flows/lists/06_recent_scans_empty_state.yaml b/.maestro/flows/lists/06_recent_scans_empty_state.yaml new file mode 100644 index 00000000..78b0cd4d --- /dev/null +++ b/.maestro/flows/lists/06_recent_scans_empty_state.yaml @@ -0,0 +1,14 @@ +appId: llc.fungee.ingredicheck +tags: [lists, recent-scans, empty] +--- +- runFlow: ../../subflows/launch/recent_scans_empty.yaml +- extendedWaitUntil: + visible: + id: recent_scans_page_view + timeout: 30000 +- extendedWaitUntil: + visible: + text: "No Scans !" + timeout: 30000 +- assertVisible: + text: "No Scans !" diff --git a/.maestro/flows/lists/07_recent_scans_all_items.yaml b/.maestro/flows/lists/07_recent_scans_all_items.yaml new file mode 100644 index 00000000..269e9763 --- /dev/null +++ b/.maestro/flows/lists/07_recent_scans_all_items.yaml @@ -0,0 +1,14 @@ +appId: llc.fungee.ingredicheck +tags: [lists, recent-scans] +--- +- runFlow: ../../subflows/launch/recent_scans.yaml +- extendedWaitUntil: + visible: + id: recent_scans_page_view + timeout: 30000 +- assertVisible: + text: Diet Coke Soft Drink +- assertVisible: + text: Oreo Original +- assertVisible: + text: Sea Salt Veggie Chips diff --git a/.maestro/flows/product/02_toggle_favorite.yaml b/.maestro/flows/product/02_toggle_favorite.yaml new file mode 100644 index 00000000..f49e52ac --- /dev/null +++ b/.maestro/flows/product/02_toggle_favorite.yaml @@ -0,0 +1,14 @@ +appId: llc.fungee.ingredicheck +tags: [product, favorite] +--- +- runFlow: ../../subflows/launch/product_detail.yaml +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- assertVisible: + text: Diet Coke Soft Drink +- tapOn: + id: product_detail_favorite_button +- tapOn: + id: product_detail_favorite_button diff --git a/.maestro/flows/product/03_unfavorite.yaml b/.maestro/flows/product/03_unfavorite.yaml new file mode 100644 index 00000000..6c708c4b --- /dev/null +++ b/.maestro/flows/product/03_unfavorite.yaml @@ -0,0 +1,12 @@ +appId: llc.fungee.ingredicheck +tags: [product, favorite] +--- +- runFlow: ../../subflows/launch/product_detail_favorited.yaml +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- assertVisible: + text: Oreo Original +- tapOn: + id: product_detail_favorite_button diff --git a/.maestro/flows/product/04_ingredients_visible.yaml b/.maestro/flows/product/04_ingredients_visible.yaml new file mode 100644 index 00000000..7ff99c4a --- /dev/null +++ b/.maestro/flows/product/04_ingredients_visible.yaml @@ -0,0 +1,15 @@ +appId: llc.fungee.ingredicheck +tags: [product, ingredients] +--- +- runFlow: ../../subflows/launch/product_detail.yaml +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- scrollUntilVisible: + element: + id: product_detail_ingredients_section + direction: DOWN + timeout: 30000 +- assertVisible: + id: product_detail_ingredients_section diff --git a/.maestro/flows/product/05_ingredient_alert.yaml b/.maestro/flows/product/05_ingredient_alert.yaml new file mode 100644 index 00000000..c1dc3565 --- /dev/null +++ b/.maestro/flows/product/05_ingredient_alert.yaml @@ -0,0 +1,21 @@ +appId: llc.fungee.ingredicheck +tags: [product, ingredients, alert] +--- +- runFlow: ../../subflows/launch/product_detail_favorited.yaml +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- scrollUntilVisible: + element: + id: product_detail_ingredient_alert + direction: DOWN + timeout: 30000 +- tapOn: + id: product_detail_ingredient_alert +- extendedWaitUntil: + visible: + text: Wheat Flour + timeout: 10000 +- assertVisible: + text: Wheat Flour diff --git a/.maestro/flows/product/06_reanalyze_stale.yaml b/.maestro/flows/product/06_reanalyze_stale.yaml new file mode 100644 index 00000000..f9c1eb5f --- /dev/null +++ b/.maestro/flows/product/06_reanalyze_stale.yaml @@ -0,0 +1,12 @@ +appId: llc.fungee.ingredicheck +tags: [product, reanalyze] +--- +- runFlow: ../../subflows/launch/product_detail_stale.yaml +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- assertVisible: + id: product_detail_reanalyze_button +- tapOn: + id: product_detail_reanalyze_button diff --git a/.maestro/flows/scan/05_scan_to_product_detail.yaml b/.maestro/flows/scan/05_scan_to_product_detail.yaml new file mode 100644 index 00000000..e179261a --- /dev/null +++ b/.maestro/flows/scan/05_scan_to_product_detail.yaml @@ -0,0 +1,25 @@ +appId: llc.fungee.ingredicheck +tags: [scan, product] +--- +- runFlow: ../../subflows/launch/scan_barcode.yaml +- extendedWaitUntil: + visible: + id: scan_ui_test_inject_barcode_button + timeout: 30000 +- runFlow: + file: ../../subflows/inject_barcode.yaml + env: + PRESET_LABEL: "Coca-Cola 12 oz" +- extendedWaitUntil: + visible: + text: Diet Coke Soft Drink + timeout: 40000 +- tapOn: Diet Coke Soft Drink +- extendedWaitUntil: + visible: + id: product_detail_view + timeout: 30000 +- assertVisible: + text: Diet Coke Soft Drink +- assertVisible: + id: product_detail_favorite_button diff --git a/.maestro/flows/settings/10_sign_out_confirm.yaml b/.maestro/flows/settings/10_sign_out_confirm.yaml new file mode 100644 index 00000000..9e57c620 --- /dev/null +++ b/.maestro/flows/settings/10_sign_out_confirm.yaml @@ -0,0 +1,17 @@ +appId: llc.fungee.ingredicheck +tags: [settings, auth, sign-out] +--- +- runFlow: ../../subflows/launch/settings.yaml +- scrollUntilVisible: + element: + id: settings_sign_out_button + direction: DOWN + timeout: 30000 +- tapOn: + id: settings_sign_out_button +- extendedWaitUntil: + visible: + text: Sign out + timeout: 10000 +- tapOn: + text: Sign out diff --git a/.maestro/flows/settings/11_sign_out_cancel.yaml b/.maestro/flows/settings/11_sign_out_cancel.yaml new file mode 100644 index 00000000..6bfb17ea --- /dev/null +++ b/.maestro/flows/settings/11_sign_out_cancel.yaml @@ -0,0 +1,27 @@ +appId: llc.fungee.ingredicheck +tags: [settings, auth, sign-out] +--- +- runFlow: ../../subflows/launch/settings.yaml +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 30000 +- scrollUntilVisible: + element: + id: settings_sign_out_button + direction: DOWN + timeout: 30000 +- tapOn: + id: settings_sign_out_button +- extendedWaitUntil: + visible: + text: Not now + timeout: 10000 +- tapOn: + text: Not now +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 10000 +- assertVisible: + id: settings_content_view diff --git a/.maestro/flows/settings/12_delete_account_flow.yaml b/.maestro/flows/settings/12_delete_account_flow.yaml new file mode 100644 index 00000000..a7043e0d --- /dev/null +++ b/.maestro/flows/settings/12_delete_account_flow.yaml @@ -0,0 +1,20 @@ +appId: llc.fungee.ingredicheck +tags: [settings, auth, delete-account] +--- +- runFlow: ../../subflows/launch/settings.yaml +- scrollUntilVisible: + element: + id: settings_delete_account_button + direction: DOWN + timeout: 30000 +- tapOn: + id: settings_delete_account_button +- extendedWaitUntil: + visible: + text: "Type \"DELETE\" to confirm" + timeout: 10000 +- tapOn: + text: "Type \"DELETE\" to confirm" +- inputText: DELETE +- tapOn: + text: Confirm diff --git a/.maestro/flows/settings/13_delete_account_cancel.yaml b/.maestro/flows/settings/13_delete_account_cancel.yaml new file mode 100644 index 00000000..c263c44c --- /dev/null +++ b/.maestro/flows/settings/13_delete_account_cancel.yaml @@ -0,0 +1,23 @@ +appId: llc.fungee.ingredicheck +tags: [settings, auth, delete-account] +--- +- runFlow: ../../subflows/launch/settings.yaml +- scrollUntilVisible: + element: + id: settings_delete_account_button + direction: DOWN + timeout: 30000 +- tapOn: + id: settings_delete_account_button +- extendedWaitUntil: + visible: + text: Cancel + timeout: 10000 +- tapOn: + text: Cancel +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 10000 +- assertVisible: + id: settings_content_view diff --git a/.maestro/flows/settings/14_guest_reset_app.yaml b/.maestro/flows/settings/14_guest_reset_app.yaml new file mode 100644 index 00000000..d88afc3d --- /dev/null +++ b/.maestro/flows/settings/14_guest_reset_app.yaml @@ -0,0 +1,15 @@ +appId: llc.fungee.ingredicheck +tags: [settings, guest, reset] +--- +- runFlow: ../../subflows/launch/settings_guest.yaml +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 30000 +- scrollUntilVisible: + element: + id: settings_reset_app_button + direction: DOWN + timeout: 30000 +- assertVisible: + id: settings_reset_app_button diff --git a/.maestro/subflows/launch/product_detail_favorited.yaml b/.maestro/subflows/launch/product_detail_favorited.yaml new file mode 100644 index 00000000..a4f89797 --- /dev/null +++ b/.maestro/subflows/launch/product_detail_favorited.yaml @@ -0,0 +1,8 @@ +appId: llc.fungee.ingredicheck +--- +- launchApp: + stopApp: true + arguments: + "--ui-test-mode": true + "--ui-test-scenario=productDetailFavorited": true +- waitForAnimationToEnd diff --git a/.maestro/subflows/launch/product_detail_stale.yaml b/.maestro/subflows/launch/product_detail_stale.yaml new file mode 100644 index 00000000..7bd83ad2 --- /dev/null +++ b/.maestro/subflows/launch/product_detail_stale.yaml @@ -0,0 +1,8 @@ +appId: llc.fungee.ingredicheck +--- +- launchApp: + stopApp: true + arguments: + "--ui-test-mode": true + "--ui-test-scenario=productDetailStale": true +- waitForAnimationToEnd diff --git a/.maestro/subflows/launch/recent_scans_empty.yaml b/.maestro/subflows/launch/recent_scans_empty.yaml new file mode 100644 index 00000000..f6cf60a5 --- /dev/null +++ b/.maestro/subflows/launch/recent_scans_empty.yaml @@ -0,0 +1,8 @@ +appId: llc.fungee.ingredicheck +--- +- launchApp: + stopApp: true + arguments: + "--ui-test-mode": true + "--ui-test-scenario=recentScansEmpty": true +- waitForAnimationToEnd diff --git a/.maestro/subflows/launch/settings_guest.yaml b/.maestro/subflows/launch/settings_guest.yaml new file mode 100644 index 00000000..bb5a6393 --- /dev/null +++ b/.maestro/subflows/launch/settings_guest.yaml @@ -0,0 +1,8 @@ +appId: llc.fungee.ingredicheck +--- +- launchApp: + stopApp: true + arguments: + "--ui-test-mode": true + "--ui-test-scenario=settingsGuest": true +- waitForAnimationToEnd diff --git a/IngrediCheck/Components/FilterSegmentedControl.swift b/IngrediCheck/Components/FilterSegmentedControl.swift index 6a84ecac..2f79bd0e 100644 --- a/IngrediCheck/Components/FilterSegmentedControl.swift +++ b/IngrediCheck/Components/FilterSegmentedControl.swift @@ -61,6 +61,7 @@ struct FilterSegmentedControl: View { } .padding(2) .frame(width: 105, height: 31) + .accessibilityIdentifier("recent_scans_filter_control") .background( Capsule() .stroke(Color.white) diff --git a/IngrediCheck/Testing/UITestHarness.swift b/IngrediCheck/Testing/UITestHarness.swift index 5b0aacc6..421e8cfd 100644 --- a/IngrediCheck/Testing/UITestHarness.swift +++ b/IngrediCheck/Testing/UITestHarness.swift @@ -53,6 +53,10 @@ enum UITestScenario: String { case permissions case tipJar case support + case productDetailFavorited + case productDetailStale + case recentScansEmpty + case settingsGuest } struct UITestFixture { @@ -626,6 +630,97 @@ private enum UITestFixtures { favorites: shared.favorites, shareMessage: "Share us tapped" ) + case .productDetailFavorited: + let oreoScan = shared.scans[1] // Oreo Original (is_favorited: true) + return homeFixture( + scenario: scenario, + homeRoute: .productDetail(scanId: oreoScan.id, initialScan: oreoScan), + openSettingsSheet: false, + family: shared.family, + foodNotesAll: shared.foodNotesAll, + foodNotesSummary: shared.foodNotesSummary, + scans: shared.scans, + stats: shared.stats, + favorites: shared.favorites + ) + case .productDetailStale: + let scans = shared.scans + let dietCoke = scans[0] + let staleDietCoke = DTO.Scan( + id: dietCoke.id, + scan_type: dietCoke.scan_type, + barcode: dietCoke.barcode, + state: dietCoke.state, + product_info: dietCoke.product_info, + product_info_source: dietCoke.product_info_source, + product_info_vote: dietCoke.product_info_vote, + analysis_result: DTO.ScanAnalysisResult( + id: dietCoke.analysis_result?.id, + overall_analysis: dietCoke.analysis_result?.overall_analysis, + overall_match: dietCoke.analysis_result?.overall_match, + ingredient_analysis: dietCoke.analysis_result?.ingredient_analysis ?? [], + is_stale: true, + vote: dietCoke.analysis_result?.vote + ), + images: dietCoke.images, + latest_guidance: dietCoke.latest_guidance, + created_at: dietCoke.created_at, + last_activity_at: dietCoke.last_activity_at, + error: dietCoke.error, + is_favorited: dietCoke.is_favorited, + analysis_id: dietCoke.analysis_id + ) + var staleScans = scans + staleScans[0] = staleDietCoke + return homeFixture( + scenario: scenario, + homeRoute: .productDetail(scanId: staleDietCoke.id, initialScan: staleDietCoke), + openSettingsSheet: false, + family: shared.family, + foodNotesAll: shared.foodNotesAll, + foodNotesSummary: shared.foodNotesSummary, + scans: staleScans, + stats: shared.stats, + favorites: shared.favorites + ) + case .recentScansEmpty: + return homeFixture( + scenario: scenario, + homeRoute: .recentScansAll, + openSettingsSheet: false, + family: shared.family, + foodNotesAll: shared.foodNotesAll, + foodNotesSummary: shared.foodNotesSummary, + scans: [], + stats: emptyStats(), + favorites: [] + ) + case .settingsGuest: + return UITestFixture( + scenario: scenario, + restoredState: (canvas: .home, sheet: .homeDefault), + requiresSession: true, + launchesWithCompletedOnboarding: true, + marksOnboardingCompleted: true, + authProvider: .guest, + googleOutcome: .success, + appleOutcome: .success, + homeRoute: nil, + openSettingsSheet: true, + startScanningOnAppStart: false, + family: shared.family, + selectedMemberId: shared.family.selfMember.id, + foodNotesAll: shared.foodNotesAll, + foodNotesSummary: shared.foodNotesSummary, + permissions: UITestPermissions(cameraAuthorized: true, notificationsAuthorized: true), + scans: shared.scans, + stats: shared.stats, + favorites: shared.favorites, + chatReplies: chatReplies(), + tipProducts: tipProducts(), + tipPurchaseOutcome: .success(message: "Thanks for supporting IngrediCheck"), + shareMessage: "Share IngrediCheck" + ) } } diff --git a/IngrediCheck/Views/Family/ManageFamilyView.swift b/IngrediCheck/Views/Family/ManageFamilyView.swift index bc4cf53b..9124689a 100644 --- a/IngrediCheck/Views/Family/ManageFamilyView.swift +++ b/IngrediCheck/Views/Family/ManageFamilyView.swift @@ -459,6 +459,7 @@ struct ManageFamilyView: View { } .buttonStyle(.plain) .disabled(isSingleMember) + .accessibilityIdentifier("manage_family_leave_button") .confirmationDialog("Leave Family", isPresented: $showLeaveConfirm) { Button("Leave Family", role: .destructive) { Task { await familyStore.leave() } @@ -543,6 +544,7 @@ private struct FamilyCardView: View { .textInputAutocapitalization(.words) .disableAutocorrection(true) .focused($isEditingFamilyName) + .accessibilityIdentifier("manage_family_name_field") .submitLabel(.done) .onSubmit { isEditingFamilyName = false diff --git a/IngrediCheck/Views/HomeView.swift b/IngrediCheck/Views/HomeView.swift index 73489bb6..1077a9ee 100644 --- a/IngrediCheck/Views/HomeView.swift +++ b/IngrediCheck/Views/HomeView.swift @@ -160,6 +160,7 @@ struct HomeView: View { .frame(maxWidth: .infinity, alignment: .leading) } .frame(height: 196) + .accessibilityIdentifier("home_food_notes_card") // Family + Average scans - use GeometryReader to ensure equal width GeometryReader { geometry in @@ -353,6 +354,7 @@ struct HomeView: View { .foregroundStyle(Color(hex: "#EEEEEE")) ) ) + .accessibilityIdentifier("home_recent_scans_section") } // end else (real content) } .frame(maxWidth: .infinity, alignment: .topLeading) diff --git a/IngrediCheck/Views/ProductDetails/IngredientsAlertCard.swift b/IngrediCheck/Views/ProductDetails/IngredientsAlertCard.swift index 357c068a..37c3661c 100644 --- a/IngrediCheck/Views/ProductDetails/IngredientsAlertCard.swift +++ b/IngrediCheck/Views/ProductDetails/IngredientsAlertCard.swift @@ -48,6 +48,7 @@ struct IngredientsAlertCard: View { isExpanded.toggle() } } + .accessibilityIdentifier("product_detail_ingredient_alert") } private var header: some View { diff --git a/IngrediCheck/Views/ProductDetails/ProductDetailView.swift b/IngrediCheck/Views/ProductDetails/ProductDetailView.swift index 5644e7d8..f588d2e4 100644 --- a/IngrediCheck/Views/ProductDetails/ProductDetailView.swift +++ b/IngrediCheck/Views/ProductDetails/ProductDetailView.swift @@ -419,6 +419,7 @@ struct ProductDetailView: View { } .padding(.horizontal, 20) .padding(.bottom, 40) + .accessibilityIdentifier("product_detail_ingredients_section") } } .redacted(reason: isPlaceholderMode ? .placeholder : []) diff --git a/IngrediCheck/Views/Tabs/ListsTab.swift b/IngrediCheck/Views/Tabs/ListsTab.swift index 5c244564..95d505ba 100644 --- a/IngrediCheck/Views/Tabs/ListsTab.swift +++ b/IngrediCheck/Views/Tabs/ListsTab.swift @@ -157,6 +157,7 @@ import os } ) .frame(maxWidth: .infinity) + .accessibilityIdentifier("recent_scans_empty_state") } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) diff --git a/IngrediCheck/Views/Tabs/SettingsSheet.swift b/IngrediCheck/Views/Tabs/SettingsSheet.swift index 3e3a06a6..876c208e 100644 --- a/IngrediCheck/Views/Tabs/SettingsSheet.swift +++ b/IngrediCheck/Views/Tabs/SettingsSheet.swift @@ -816,9 +816,10 @@ struct SettingsContentView: View { } .foregroundStyle(Color(hex: "#F04438")) } + .accessibilityIdentifier("settings_delete_account_button") } } - + struct ResetAppStateView: View { let labelText: String @@ -846,6 +847,7 @@ struct SettingsContentView: View { } .foregroundStyle(Color(hex: "#F04438")) } + .accessibilityIdentifier("settings_reset_app_button") .confirmationDialog( "This will sign you out and reset the app", isPresented: $confirmationShown, @@ -933,7 +935,8 @@ struct SettingsContentView: View { .foregroundStyle(.grayScale10) ) } - + .accessibilityIdentifier("settings_sign_out_button") + } } } From 049d428947f388e02ed63b9fc1e61eb0e2d73c60 Mon Sep 17 00:00:00 2001 From: justanotheratom Date: Mon, 9 Mar 2026 14:14:05 +0530 Subject: [PATCH 2/4] Fix: address review feedback on settings danger zone flows - Add dedicated accessibility IDs for sign-out confirmation dialog and confirm button (settings_sign_out_confirm_dialog, settings_sign_out_confirm_button) to eliminate ambiguity between the sign-out capsule button and the destructive confirmation action - Add accessibility ID for delete-confirm text field (settings_delete_confirm_text_field) and tap it before typing to ensure the input is focused - Add NOTE comment to delete account flow clarifying it only tests the confirmation dialog UI under the local auth harness, not actual account deletion - Use .accessibilityElement(children: .contain) on the sign-out dialog so child IDs remain discoverable Co-Authored-By: Claude Opus 4.6 --- .maestro/flows/settings/10_sign_out_confirm.yaml | 8 ++++++-- .maestro/flows/settings/11_sign_out_cancel.yaml | 2 +- .maestro/flows/settings/12_delete_account_flow.yaml | 12 ++++++++++-- IngrediCheck/Views/Tabs/SettingsSheet.swift | 4 ++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/.maestro/flows/settings/10_sign_out_confirm.yaml b/.maestro/flows/settings/10_sign_out_confirm.yaml index 9e57c620..e70da85c 100644 --- a/.maestro/flows/settings/10_sign_out_confirm.yaml +++ b/.maestro/flows/settings/10_sign_out_confirm.yaml @@ -2,6 +2,10 @@ appId: llc.fungee.ingredicheck tags: [settings, auth, sign-out] --- - runFlow: ../../subflows/launch/settings.yaml +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 30000 - scrollUntilVisible: element: id: settings_sign_out_button @@ -11,7 +15,7 @@ tags: [settings, auth, sign-out] id: settings_sign_out_button - extendedWaitUntil: visible: - text: Sign out + id: settings_sign_out_confirm_dialog timeout: 10000 - tapOn: - text: Sign out + id: settings_sign_out_confirm_button diff --git a/.maestro/flows/settings/11_sign_out_cancel.yaml b/.maestro/flows/settings/11_sign_out_cancel.yaml index 6bfb17ea..08a1c6e6 100644 --- a/.maestro/flows/settings/11_sign_out_cancel.yaml +++ b/.maestro/flows/settings/11_sign_out_cancel.yaml @@ -15,7 +15,7 @@ tags: [settings, auth, sign-out] id: settings_sign_out_button - extendedWaitUntil: visible: - text: Not now + id: settings_sign_out_confirm_dialog timeout: 10000 - tapOn: text: Not now diff --git a/.maestro/flows/settings/12_delete_account_flow.yaml b/.maestro/flows/settings/12_delete_account_flow.yaml index a7043e0d..4cf8d6f6 100644 --- a/.maestro/flows/settings/12_delete_account_flow.yaml +++ b/.maestro/flows/settings/12_delete_account_flow.yaml @@ -1,7 +1,15 @@ appId: llc.fungee.ingredicheck tags: [settings, auth, delete-account] --- +# NOTE: Under --ui-test-mode the harness uses a local debug session, +# so AuthController.deleteAccount() cannot reach the real backend. +# This flow only verifies the confirmation dialog UI (text field focus, +# Confirm button enablement) — not actual account deletion. - runFlow: ../../subflows/launch/settings.yaml +- extendedWaitUntil: + visible: + id: settings_content_view + timeout: 30000 - scrollUntilVisible: element: id: settings_delete_account_button @@ -11,10 +19,10 @@ tags: [settings, auth, delete-account] id: settings_delete_account_button - extendedWaitUntil: visible: - text: "Type \"DELETE\" to confirm" + id: settings_delete_confirm_text_field timeout: 10000 - tapOn: - text: "Type \"DELETE\" to confirm" + id: settings_delete_confirm_text_field - inputText: DELETE - tapOn: text: Confirm diff --git a/IngrediCheck/Views/Tabs/SettingsSheet.swift b/IngrediCheck/Views/Tabs/SettingsSheet.swift index 876c208e..e4fd48b6 100644 --- a/IngrediCheck/Views/Tabs/SettingsSheet.swift +++ b/IngrediCheck/Views/Tabs/SettingsSheet.swift @@ -454,6 +454,7 @@ struct SettingsContentView: View { .foregroundStyle(Color(hex: "#FF1100")) .frame(maxWidth: .infinity) } + .accessibilityIdentifier("settings_sign_out_confirm_button") Divider() @@ -470,6 +471,8 @@ struct SettingsContentView: View { .frame(width: 270, height: 167) .background(Color.grayScale10) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) + .accessibilityElement(children: .contain) + .accessibilityIdentifier("settings_sign_out_confirm_dialog") } if showDeleteConfirm { @@ -489,6 +492,7 @@ struct SettingsContentView: View { .textInputAutocapitalization(.characters) .disableAutocorrection(true) .font(ManropeFont.medium.size(16)) + .accessibilityIdentifier("settings_delete_confirm_text_field") Rectangle() .fill(Color(hex: "#E3E3E3")) .frame(height: 1) From 3e152d9a446ab74a6911ec8fac7ffa416d945a98 Mon Sep 17 00:00:00 2001 From: justanotheratom Date: Mon, 9 Mar 2026 14:33:56 +0530 Subject: [PATCH 3/4] Fix: add post-confirm assertions to sign-out and delete-account flows Verify sign_in_sheet appears after confirming sign-out and account deletion, proving the actions actually triggered rather than ending blindly after tapOn. Co-Authored-By: Claude Opus 4.6 --- .maestro/flows/settings/10_sign_out_confirm.yaml | 6 ++++++ .maestro/flows/settings/12_delete_account_flow.yaml | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/.maestro/flows/settings/10_sign_out_confirm.yaml b/.maestro/flows/settings/10_sign_out_confirm.yaml index e70da85c..0f7c5eb7 100644 --- a/.maestro/flows/settings/10_sign_out_confirm.yaml +++ b/.maestro/flows/settings/10_sign_out_confirm.yaml @@ -19,3 +19,9 @@ tags: [settings, auth, sign-out] timeout: 10000 - tapOn: id: settings_sign_out_confirm_button +- extendedWaitUntil: + visible: + id: sign_in_sheet + timeout: 15000 +- assertVisible: + id: sign_in_sheet diff --git a/.maestro/flows/settings/12_delete_account_flow.yaml b/.maestro/flows/settings/12_delete_account_flow.yaml index 4cf8d6f6..4bea8ef2 100644 --- a/.maestro/flows/settings/12_delete_account_flow.yaml +++ b/.maestro/flows/settings/12_delete_account_flow.yaml @@ -26,3 +26,12 @@ tags: [settings, auth, delete-account] - inputText: DELETE - tapOn: text: Confirm +# Verify the app signed out — proves typing DELETE enabled the Confirm button +# and the delete action triggered (even though the backend call is a no-op +# under the local auth harness). +- extendedWaitUntil: + visible: + id: sign_in_sheet + timeout: 15000 +- assertVisible: + id: sign_in_sheet From d91b50afb3404c49bc618b2779482db4399cf5d8 Mon Sep 17 00:00:00 2001 From: justanotheratom Date: Mon, 9 Mar 2026 14:39:27 +0530 Subject: [PATCH 4/4] Rename delete-account flow to clarify it tests dialog UI, not backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 12_delete_account_flow.yaml → 12_delete_account_dialog.yaml Co-Authored-By: Claude Opus 4.6 --- ...{12_delete_account_flow.yaml => 12_delete_account_dialog.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .maestro/flows/settings/{12_delete_account_flow.yaml => 12_delete_account_dialog.yaml} (100%) diff --git a/.maestro/flows/settings/12_delete_account_flow.yaml b/.maestro/flows/settings/12_delete_account_dialog.yaml similarity index 100% rename from .maestro/flows/settings/12_delete_account_flow.yaml rename to .maestro/flows/settings/12_delete_account_dialog.yaml