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..0f7c5eb7 --- /dev/null +++ b/.maestro/flows/settings/10_sign_out_confirm.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: + id: settings_sign_out_confirm_dialog + 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/11_sign_out_cancel.yaml b/.maestro/flows/settings/11_sign_out_cancel.yaml new file mode 100644 index 00000000..08a1c6e6 --- /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: + id: settings_sign_out_confirm_dialog + 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_dialog.yaml b/.maestro/flows/settings/12_delete_account_dialog.yaml new file mode 100644 index 00000000..4bea8ef2 --- /dev/null +++ b/.maestro/flows/settings/12_delete_account_dialog.yaml @@ -0,0 +1,37 @@ +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 + direction: DOWN + timeout: 30000 +- tapOn: + id: settings_delete_account_button +- extendedWaitUntil: + visible: + id: settings_delete_confirm_text_field + timeout: 10000 +- tapOn: + id: settings_delete_confirm_text_field +- 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 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..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) @@ -816,9 +820,10 @@ struct SettingsContentView: View { } .foregroundStyle(Color(hex: "#F04438")) } + .accessibilityIdentifier("settings_delete_account_button") } } - + struct ResetAppStateView: View { let labelText: String @@ -846,6 +851,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 +939,8 @@ struct SettingsContentView: View { .foregroundStyle(.grayScale10) ) } - + .accessibilityIdentifier("settings_sign_out_button") + } } }