This commit is contained in:
shafi54 2026-09-16 21:48:06 +05:30
parent a40725213f
commit 192126cf9e
24 changed files with 385 additions and 166 deletions

View file

@ -1,11 +1,19 @@
import { QueryClientProvider } from "@tanstack/react-query";
import queryClient from "../utils/queryClient";
import { Stack } from "expo-router";
import { LogBox } from "react-native";
import { StaffAuthProvider } from "@/components/context/staff-auth-context";
import { trpc, trpcClient } from "@/src/trpc-client";
import { SafeAreaView } from "react-native-safe-area-context";
import { RefreshProvider } from '../../../packages/ui/src/lib/refresh-context';
// Silence dev warnings/errors in LogBox. The "Open debugger to view warnings"
// toast overlays the bottom of the UI and intercepts taps (e.g. the Add Slot
// FAB), which breaks E2E flows.
if (__DEV__) {
LogBox.ignoreAllLogs(true);
}
export default function Layout() {
return (
<QueryClientProvider client={queryClient}>

View file

@ -290,6 +290,7 @@ function Login() {
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-5`}>
<MyTextInput
testID="user-login-mobile"
topLabel="Mobile Number"
placeholder="Enter your mobile number"
value={value}
@ -446,6 +447,7 @@ function Login() {
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-4`}>
<MyTextInput
testID="user-login-identifier"
topLabel="Username"
placeholder="Enter your email or mobile"
value={value}
@ -473,6 +475,7 @@ function Login() {
render={({ field: { onChange, onBlur, value } }) => (
<View style={tw`mb-4`}>
<MyTextInput
testID="user-login-password"
topLabel="Password"
placeholder="Enter your password"
value={value}
@ -528,6 +531,7 @@ function Login() {
<View style={tw`mt-4`}>
<MyButton
testID="user-login-submit"
onPress={handleSubmit(onSubmit)}
fillColor="brand600"
textColor="white1"
@ -549,6 +553,7 @@ function Login() {
{step === 'mobile' && (
<MyTouchableOpacity
testID="user-login-other-ways"
onPress={() => {
setPasswordBackTo('mobile');
setValue('identifier', watch('mobile') || '');

View file

@ -696,6 +696,7 @@ export default function Dashboard() {
style={searchBarContainerStyle}
>
<SearchBar
testID="home-search"
value={""}
onChangeText={() => { }}
onPress={handleSearchPress}

View file

@ -128,6 +128,7 @@ export default function SearchResults() {
<View style={tw`pt-4 pb-2 px-4`}>
<SearchBar
ref={searchInputRef}
testID="search-input"
value={inputQuery}
onChangeText={setInputQuery}
onSubmitEditing={handleSearch}

View file

@ -8,7 +8,7 @@ import { Stack } from "expo-router";
import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
import { Appearance, StatusBar, View } from "react-native";
import { Appearance, StatusBar, View, LogBox } from "react-native";
import { QueryClientProvider } from "@tanstack/react-query";
import { theme , MyStatusBar } from "common-ui";
import queryClient from "@/utils/queryClient";
@ -31,6 +31,13 @@ import React from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import NotifChecker from "@/services/notif-service/notif-checker";
// Silence dev warnings/errors in LogBox. The "Open debugger to view warnings"
// toast overlays the bottom of the UI and intercepts taps (e.g. tab bar /
// bottom-sheet buttons), which breaks E2E flows.
if (__DEV__) {
LogBox.ignoreAllLogs(true);
}
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({

View file

@ -160,9 +160,10 @@ const CheckoutAddressSelector: React.FC<AddressSelectorProps> = ({
decelerationRate="fast"
snapToInterval={CARD_WIDTH}
>
{sortedAddresses.map((address) => (
{sortedAddresses.map((address, index) => (
<TouchableOpacity
key={address.id}
testID={`address-option-${index}`}
onPress={() => {
onAddressSelect(address.id);
resetScrollToLeft();

View file

@ -329,7 +329,7 @@ const PaymentAndOrderComponent: React.FC<PaymentAndOrderProps> = ({
{/* Bottom Action Bar */}
<View style={tw`bg-white p-5 rounded-2xl shadow-sm mb-4 border border-gray-100`}>
{!selectedAddress && (
<View style={tw`bg-red-50 p-3 rounded-lg mb-3 flex-row items-center`}>
<View testID="select-address-error" style={tw`bg-red-50 p-3 rounded-lg mb-3 flex-row items-center`}>
<MaterialIcons name="error-outline" size={16} color="#EF4444" style={tw`mr-2`} />
<MyText style={tw`text-red-600 text-sm font-medium`}>
Please select a delivery address to place order

View file

@ -162,7 +162,7 @@ export default function AddToCartDialog() {
{availableSlots.map((slot: any) => (
<MyTouchableOpacity
key={slot.id}
testID="slot-option"
testID={`slot-${dayjs(slot.deliveryTime).format('YYYY-MM-DD-HH-mm')}`}
style={tw`flex-row items-start mb-4 bg-gray-50 p-4 rounded-xl border border-gray-100 ${selectedSlotId === slot.id ? 'border-brand500' : 'border-gray-100'
}`}
onPress={() => {

View file

@ -2382,6 +2382,155 @@ again; callers invoke it only when the login screen is showing.
commands:
- runFlow: <login path>
[2026-09-16 19:41:02] Disable LogBox in dev builds (admin-ui + user-ui).
The dev LogBox toast ("Open debugger to view warnings.") overlays the bottom of
the UI and intercepts taps (e.g. the Add Slot FAB), which breaks E2E flows.
=== apps/admin-ui/app/_layout.tsx ===
- import: + LogBox from 'react-native'
- module top: `if (__DEV__) { LogBox.ignoreAllLogs(true) }`
=== apps/user-ui/app/_layout.tsx ===
- import: + LogBox from 'react-native'
- module top: `if (__DEV__) { LogBox.ignoreAllLogs(true) }`
[2026-09-16 19:46:50] Add a standalone image-upload dev flow + image remove testID.
Standalone flow to iterate on the image picker without running the whole product flow.
=== packages/ui/src/components/ImageUploaderNeo.tsx ===
- per-image remove button: + testID={testID ? `${testID}-remove` : undefined}
=== tests/e2e/dev/upload-image.yaml (NEW) ===
- addMedia -> launchApp -> conditional login -> add-product-menu-item ->
scroll to variant-0-image-add -> tap -> runFlow subflows/pick-image.yaml ->
assert variant-0-image-add-remove visible
- lives in dev/ so the root suite glob does not pick it up
Run with: bash tests/e2e/run.sh dev/upload-image.yaml
[2026-09-16 19:50:12] Speed up the image-picker subflow.
`pick-image.yaml` was a chain of ~7 `optional: true` taps; each miss waited the
full element timeout, so a single image took ~35-45s.
=== tests/e2e/subflows/pick-image.yaml ===
- now: extendedWaitUntil for the thumbnail, tap it, extendedWaitUntil for
button_add, tap it, then assert app-root
- ids use a regex matching either MediaProvider package:
`com\.(google\.)?android\.providers\.media\.module:id/(icon_thumbnail|button_add)`
- dropped the DocumentsUI / permission-dialog optional fallbacks and the final
guarded fallback block (no longer needed; keeps it fast)
- if the picker doesn't open, it now fails at the first wait instead of timing
out ~7 times
[2026-09-16 19:53:11] Remove the temporary image-upload dev flow.
=== tests/e2e/dev/upload-image.yaml ===
- deleted (served its purpose; picker flow is now fast and verified)
[2026-09-16 20:04:01] Re-add the user-app order flow (rebuilt).
Runs after add-product + add-slot-with-product: opens the user app, logs in,
search the created product, adds it to the cart in the created slot, checks out
and places a COD order.
=== tests/e2e/user-order.yaml (NEW) ===
- launchApp(in.freshyo.app) clearState -> optional FirstUser "Next"
- openLink freshyo://login (x2) -> wait user-login-mobile -> conditional
runFlow subflows/login-user.yaml
- wait home-search -> tap home-search -> search-input-input -> inputText
${PRODUCT_NAME} -> pressKey enter -> tap "Add to Cart"
- wait slot-option -> tap slot-option -> tap add-to-cart-confirm
- openLink freshyo://home/cart -> scrollUntilVisible checkout-button -> tap
- extendedWaitUntil notVisible "Please select a delivery address" (address
auto-select) -> scrollUntilVisible place-order-button -> tap
- assertVisible "Order Placed Successfully!"
=== tests/e2e/subflows/login-user.yaml (NEW) ===
- mobile -> "other ways to login" -> password -> Login
=== tests/e2e/config.yaml ===
- executionOrder.flowsOrder += user-order
=== tests/e2e/run.sh ===
- + -e USER_APP_ID / USER_MOBILE / USER_PASSWORD
[2026-09-16 20:09:14] user-order.yaml: login only if checkout demands it.
No more `openLink freshyo://login`: launchApp (clearState) -> optional FirstUser
"Next" -> home-search -> search product -> "Add to Cart" -> slot-option ->
confirm -> deep link freshyo://home/cart -> checkout-button. Then wait for
`user-login-mobile|place-order-button`; run subflows/login-user.yaml only when
the login screen is showing (auth gate). Address auto-select wait and Place
Order unchanged.
[2026-09-16 20:15:02] user-order.yaml: post-login lands on cart, re-enter checkout.
Login returns to the cart page (not checkout), so after the conditional
subflows/login-user.yaml there is now a second conditional runFlow: when
`checkout-button` is visible (cart page), scroll to it and tap it to return to
checkout. No-op when already on checkout. YAML validates (2 docs).
[2026-09-16 20:21:33] user-order.yaml: fix post-login recovery (button below fold).
The separate `when: visible: checkout-button` conditional never fired because
Maestro visibility needs the element on-screen. Folded the recovery into the
login branch instead: after subflows/login-user.yaml, wait for
`${PRODUCT_NAME}` (cart loaded), then scrollUntilVisible checkout-button DOWN
and tap it. No visibility gate on the button itself. YAML validates (2 docs).
[2026-09-16 20:28:44] user-order: tap first address card when none auto-selected.
Checkout auto-select only picks the address flagged isDefault
(CheckoutAddressSelector.tsx:65-72); with none marked default the "Please
select a delivery address" banner persisted and Place Order stayed disabled.
Added testID `address-option-{index}` to the address cards and a conditional
step in user-order.yaml: when the banner is visible, wait for address-option-0
and tap it, then continue to Place Order.
[2026-09-16 20:35:10] user-order: read the address error at the bottom first.
The "select address" banner sits at the bottom next to Place Order, so a
`when: visible:` check from the top of the page never fires. Reordered:
wait for address-option-0 (cards loaded at top) -> scroll DOWN to
place-order-button -> only then check the banner; if visible, scroll back UP
to address-option-0, tap it, and scroll back DOWN before Place Order.
[2026-09-16 20:41:02] user-order: testID for the select-address error banner.
Added testID `select-address-error` to the "Please select a delivery address"
banner in PaymentAndOrderComponent.tsx; user-order.yaml now matches on that id
instead of text for both the `when: visible:` check and the `notVisible` wait.
[2026-09-16 20:56:12] E2E: deterministic slot via SLOT_DATE/HOUR/MIN + go-to-cart nav.
.env (+ .env.example) gains SLOT_DATE (today, YYYY-MM-DD), SLOT_HOUR, SLOT_MIN
(delivery wall-time, 24h zero-padded, device-local); run.sh requires them,
forwards them, and computes SLOT_FREEZE_HOUR/MIN = delivery - 10 min in bash.
set-slot-times.yaml rewritten to absolute times (date pickers just confirm
today; time pickers use text-input mode with env values; no evalScript).
AddToCartDialog slot options get testID `slot-YYYY-MM-DD-HH-mm` (replaces
generic `slot-option`). user-order.yaml taps `slot-${SLOT_DATE}-${SLOT_HOUR}-
${SLOT_MIN}` and reaches the cart via the floating `go-to-cart` bar instead of
the `freshyo://home/cart` deep link. README layout lines updated.
[2026-09-16 21:02:33] user-order: normalize post-login landing via relaunch.
Login may land on the cart page or the home page, so detecting the landing is
fragile. After subflows/login-user.yaml the flow now relaunches the app
without clearState (session + cart persist; initial route is always home),
then goes home -> go-to-cart -> cart -> checkout-button like the main path.
[2026-09-16 21:06:12] user-order: handle home landing inline, no relaunch.
Reverted the post-login relaunch. After subflows/login-user.yaml the flow now
assumes the home landing: wait for home-search, tap the floating go-to-cart
bar, then scroll to checkout-button and re-enter checkout.
[2026-09-16 21:12:40] E2E: stripped narration comments from flows, run.sh, .env.

4
opencode.jsonc Normal file
View file

@ -0,0 +1,4 @@
// {
// "$schema": "https://opencode.ai/config.json",
// "permission": "allow"
// }

View file

@ -48,6 +48,7 @@ const ImageUploaderNeo: React.FC<ImageUploaderNeoProps & { testID?: string }> =
style={tw`w-full aspect-square rounded`}
/>
<MyTouchableOpacity
testID={testID ? `${testID}-remove` : undefined}
onPress={() =>
onImageRemove({
url: image.imgUrl,

View file

@ -16,6 +16,7 @@ interface SearchBarProps {
containerStyle?: ViewStyle;
onPress?: () => void;
activeOpacity?: number;
testID?: string;
}
const SearchBar = forwardRef<any, SearchBarProps>(({
@ -28,6 +29,7 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
containerStyle,
onPress,
activeOpacity = 0.8,
testID,
}, ref) => {
const Wrapper = onPress ? MyTouchableOpacity : View;
@ -35,8 +37,10 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
style: [tw`flex-row items-center bg-white rounded-2xl px-4 h-12 mb-6 shadow-md`, { overflow: 'visible' }, containerStyle],
onPress,
activeOpacity,
testID,
} : {
style: [tw`flex-row items-center bg-white rounded-2xl px-4 h-12 mb-6 shadow-md`, { overflow: 'visible' }, containerStyle],
testID,
};
return (
@ -44,6 +48,7 @@ const SearchBar = forwardRef<any, SearchBarProps>(({
<MaterialIcons name="search" size={24} color={theme.colors.brand500} />
<MyTextInput
ref={ref}
testID={testID ? `${testID}-input` : undefined}
style={[tw`flex-1 text-base text-gray-800 bg-transparent h-8`, { borderWidth: 0 }]}
placeholder={placeholder}
placeholderTextColor="#9CA3AF"

View file

@ -1,29 +1,15 @@
# Copy this file to `tests/e2e/.env` and fill in real values.
# `tests/e2e/run.sh` loads it. `.env` is gitignored.
# Quote values that contain spaces (they are fine either way with run.sh).
# App under test
APP_ID=in.freshyo.adminui
# Admin login (must exist on the target backend)
STAFF_NAME=
STAFF_PASSWORD=
# Product under test. Used verbatim: add-product.yaml creates it with this exact
# name and add-slot-with-product.yaml searches for it. The backend rejects
# duplicate names, so change it (or delete the product) between suite runs.
PRODUCT_NAME="E2E Test Product"
# Id of the store to pick in the Store dropdown (see the Stores screen / DB)
STORE_ID=1
# Variant 1 (regular SKU)
SKU1_QTY="500 g"
SKU1_PRICE=199
SKU1_MRP=249
# Variant 2 (flash SKU)
SKU2_QTY="1 kg"
SKU2_PRICE=349
SKU2_MRP=399
SKU2_FLASH_PRICE=299
SLOT_DATE=
SLOT_HOUR=
SLOT_MIN=

View file

@ -8,11 +8,11 @@ Maestro flows that drive the **admin-ui** Expo app on an Android emulator.
tests/e2e/
config.yaml # workspace config (entry flows + execution order)
add-product.yaml # create a product with 2 SKUs (one flash)
add-slot-with-product.yaml # create a slot (delivery +60m, freeze +50m) holding that product
add-slot-with-product.yaml # create a slot (SLOT_DATE + SLOT_HOUR:SLOT_MIN) holding that product
subflows/
login.yaml # reusable login (callers invoke it conditionally)
pick-image.yaml # OS photo-picker adapter (Android)
set-slot-times.yaml # delivery +60m / freeze +50m via native pickers
set-slot-times.yaml # absolute delivery time + freeze (-10m) via native pickers
dev/
add-empty-slot.yaml # standalone: create a slot with no products (validate pickers)
assets/test-product.png # seeded into the emulator gallery via `addMedia`

View file

@ -1,7 +1,5 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
# Values are supplied via `-e` by run.sh. The inline `||` defaults let the
# flow run standalone against a dev build without run.sh.
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
@ -18,17 +16,18 @@ tags:
- products
- smoke
---
# Seed the emulator gallery so the OS picker has an image to select.
- addMedia:
- './assets/test-product.png'
# Fresh state (logs out) + grant gallery access.
- launchApp:
clearState: true
permissions:
storage: allow
# --- Login (only when the login screen is showing) ---
- extendedWaitUntil:
visible:
id: login-name-input
timeout: 180000
- runFlow:
when:
visible:
@ -36,13 +35,11 @@ tags:
commands:
- runFlow: subflows/login.yaml
# --- Navigate to Add Product ---
- tapOn:
id: add-product-menu-item
- assertVisible:
id: product-name-input
# --- Product level ---
- tapOn:
id: product-name-input
- inputText: ${PRODUCT_NAME}
@ -66,7 +63,6 @@ tags:
- tapOn:
id: product-type-dropdown-option-item
# --- Variant 1 (regular) ---
- scrollUntilVisible:
element:
id: variant-0-attr-0-value
@ -102,7 +98,6 @@ tags:
id: variant-0-image-add
- runFlow: subflows/pick-image.yaml
# --- Add variant 2 (flash) ---
- scrollUntilVisible:
element:
id: add-variant-button
@ -137,7 +132,6 @@ tags:
- inputText: ${SKU2_MRP}
- hideKeyboard
# Mark variant 2 as flash and set its flash price.
- scrollUntilVisible:
element:
id: variant-1-flash
@ -162,7 +156,6 @@ tags:
id: variant-1-image-add
- runFlow: subflows/pick-image.yaml
# --- Submit ---
- scrollUntilVisible:
element:
id: create-product-button

View file

@ -3,16 +3,19 @@ env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
# Must be the exact name created by add-product.yaml.
PRODUCT_NAME: ${PRODUCT_NAME || "E2E Test Product"}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- slots
- smoke
---
# Restart the app so it returns to its initial route (dashboard when already
# logged in from add-product.yaml, login otherwise).
- launchApp
# Login only when the login screen is showing (already authenticated in-suite).
- extendedWaitUntil:
visible:
id: 'login-name-input|add-product-menu-item'
timeout: 180000
- runFlow:
when:
visible:
@ -20,24 +23,14 @@ tags:
commands:
- runFlow: subflows/login.yaml
# --- Open the Slots list from the dashboard, then the create-slot screen ---
- scrollUntilVisible:
element:
id: delivery-slots-menu-item
direction: DOWN
- tapOn:
id: delivery-slots-menu-item
- assertVisible:
id: add-slot-fab
- tapOn:
id: add-slot-fab
- assertVisible:
- openLink: freshyoadmin://dashboard/slots/add
- extendedWaitUntil:
visible:
id: delivery-date-picker
timeout: 30000
# --- Delivery (+60m) and Freeze (+50m) date/times ---
- runFlow: subflows/set-slot-times.yaml
# --- Add the product to the slot (search the exact name, select its SKUs) ---
- scrollUntilVisible:
element:
id: slot-products-selector
@ -57,7 +50,6 @@ tags:
- tapOn:
id: slot-products-selector-done
# --- Submit ---
- scrollUntilVisible:
element:
id: create-slot-button

View file

@ -1,13 +1,10 @@
# Maestro workspace config for the root E2E suite.
# Only YAML files in this folder are entry flows; `subflows/` is invoked
# explicitly via runFlow and is intentionally not matched here.
flows:
- '*.yaml'
- '!config.yaml'
# add-product must run first so add-slot-with-product can find the product.
executionOrder:
continueOnFailure: false
flowsOrder:
- add-product
- add-slot-with-product
- user-order

View file

@ -3,16 +3,17 @@ env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- dev
---
# Standalone flow: creates a slot (delivery +60m, freeze +50m) with NO products.
# Handy for validating the native date/time pickers in isolation.
#
# Run with:
# bash tests/e2e/run.sh dev/add-empty-slot.yaml
- launchApp
# Login only when the login screen is showing.
- extendedWaitUntil:
visible:
id: 'login-name-input|add-product-menu-item'
timeout: 180000
- runFlow:
when:
visible:
@ -20,7 +21,6 @@ tags:
commands:
- runFlow: ../subflows/login.yaml
# Open the Slots list from the dashboard, then the create-slot screen.
- scrollUntilVisible:
element:
id: delivery-slots-menu-item

View file

@ -1,13 +1,4 @@
#!/usr/bin/env bash
#
# Runs the Maestro E2E suite against a running Android emulator.
#
# Values come from tests/e2e/.env (copy .env.example first). Maestro's CLI does
# not load .env files itself, so we read it here and forward each value with `-e`.
#
# Usage:
# bash tests/e2e/run.sh # run every entry flow in tests/e2e/
# bash tests/e2e/run.sh add-product.yaml
set -euo pipefail
cd "$(dirname "$0")"
@ -16,10 +7,8 @@ if [ ! -f .env ]; then
exit 1
fi
# Load .env line-by-line instead of `source`-ing it, so values that contain
# spaces (e.g. `SKU1_QTY=500 g`) work without needing quotes.
while IFS= read -r line || [ -n "$line" ]; do
line="${line%$'\r'}" # tolerate CRLF
line="${line%$'\r'}"
case "$line" in
'' | '#'*) continue ;;
esac
@ -31,16 +20,21 @@ done < ./.env
: "${STAFF_PASSWORD:?STAFF_PASSWORD is required}"
: "${PRODUCT_NAME:?PRODUCT_NAME is required}"
: "${STORE_ID:?STORE_ID is required}"
: "${SLOT_DATE:?SLOT_DATE is required (YYYY-MM-DD, must be today)}"
: "${SLOT_HOUR:?SLOT_HOUR is required (delivery hour, 24h zero-padded)}"
: "${SLOT_MIN:?SLOT_MIN is required (delivery minute, zero-padded)}"
# PRODUCT_NAME is used verbatim (the slot flow searches for the exact name).
# The backend rejects duplicate product names, so change it between suite runs.
export PRODUCT_NAME
# Default to the whole suite when no path/tags are passed.
if [ "$#" -eq 0 ]; then
set -- .
fi
freeze_total=$((10#$SLOT_HOUR * 60 + 10#$SLOT_MIN - 10))
SLOT_FREEZE_HOUR=$(printf '%02d' $((freeze_total / 60)))
SLOT_FREEZE_MIN=$(printf '%02d' $((freeze_total % 60)))
export SLOT_FREEZE_HOUR SLOT_FREEZE_MIN
maestro test \
-e APP_ID="$APP_ID" \
-e STAFF_NAME="$STAFF_NAME" \
@ -54,4 +48,12 @@ maestro test \
-e SKU2_PRICE="${SKU2_PRICE:-}" \
-e SKU2_MRP="${SKU2_MRP:-}" \
-e SKU2_FLASH_PRICE="${SKU2_FLASH_PRICE:-}" \
-e SLOT_DATE="$SLOT_DATE" \
-e SLOT_HOUR="$SLOT_HOUR" \
-e SLOT_MIN="$SLOT_MIN" \
-e SLOT_FREEZE_HOUR="$SLOT_FREEZE_HOUR" \
-e SLOT_FREEZE_MIN="$SLOT_FREEZE_MIN" \
-e USER_APP_ID="${USER_APP_ID:-in.freshyo.app}" \
-e USER_MOBILE="${USER_MOBILE:-}" \
-e USER_PASSWORD="${USER_PASSWORD:-}" \
"$@"

View file

@ -0,0 +1,17 @@
appId: ${USER_APP_ID}
env:
USER_MOBILE: ${USER_MOBILE || ""}
USER_PASSWORD: ${USER_PASSWORD || ""}
---
- tapOn:
id: user-login-mobile
- inputText: ${USER_MOBILE}
- pressKey: enter
- tapOn:
id: user-login-other-ways
- tapOn:
id: user-login-password
- inputText: ${USER_PASSWORD}
- pressKey: enter
- tapOn:
id: user-login-submit

View file

@ -3,10 +3,6 @@ env:
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
---
# Logs into the admin app. Callers invoke this conditionally, only when the
# login screen is showing (see the `when: visible id: login-name-input` wrapper
# used by the entry flows), so it can also be run standalone from a logged-out
# state.
- tapOn:
id: login-name-input
- inputText: ${STAFF_NAME}
@ -16,6 +12,5 @@ env:
- hideKeyboard
- tapOn:
id: login-button
# Dashboard quick actions render once the drawer shell is up.
- assertVisible:
id: add-product-menu-item

View file

@ -1,78 +1,25 @@
appId: ${APP_ID || "in.freshyo.adminui"}
---
# Android system picker adapter.
#
# The picker is OS-level UI in a separate window, so it cannot be targeted with
# app testIDs. Selectors here are system ids/text, and every step is `optional`
# so different OS versions fall through until one matches.
#
# Confirmed from AOSP MediaProvider (activity_photo_picker.xml):
# thumbnail : @id/icon_thumbnail
# confirm : @id/button_add (Button, text "Add", bottom-right)
# The provider package is `com.google.android.providers.media.module` on Google
# Play images and `com.android.providers.media.module` on AOSP images.
- runFlow:
when:
platform: Android
commands:
# Runtime permission prompt (older Android / first launch).
- tapOn:
text: 'Allow'
optional: true
# Select the first thumbnail: Android 13/14 Photo Picker, then the older
# DocumentsUI gallery.
- tapOn:
id: 'com.google.android.providers.media.module:id/icon_thumbnail'
index: 0
optional: true
- tapOn:
id: 'com.android.providers.media.module:id/icon_thumbnail'
index: 0
optional: true
- tapOn:
id: 'com.google.android.documentsui:id/thumbnail'
index: 0
optional: true
- tapOn:
id: 'com.android.documentsui:id/thumbnail'
index: 0
optional: true
- waitForAnimationToEnd
# Confirm the selection by its exact id.
- tapOn:
id: 'com.google.android.providers.media.module:id/button_add'
optional: true
- tapOn:
id: 'com.android.providers.media.module:id/button_add'
optional: true
- tapOn:
id: 'com.google.android.documentsui:id/button_add'
optional: true
# Give the picker a moment to close.
- extendedWaitUntil:
visible:
id: app-root
timeout: 3000
optional: true
id: 'com\.(google\.)?android\.providers\.media\.module:id/icon_thumbnail'
timeout: 15000
# Fallbacks run ONLY while the picker is still open (app root not visible),
# so they can never tap in-app controls like "Add Variant".
- runFlow:
when:
notVisible:
id: app-root
commands:
- tapOn:
text: '.*(Add|Done|Select|Open|OK).*'
optional: true
- tapOn:
point: '90%,92%'
id: 'com\.(google\.)?android\.providers\.media\.module:id/icon_thumbnail'
index: 0
- extendedWaitUntil:
visible:
id: 'com\.(google\.)?android\.providers\.media\.module:id/button_add'
timeout: 10000
- tapOn:
id: 'com\.(google\.)?android\.providers\.media\.module:id/button_add'
# The picker must be closed before the rest of the flow can continue.
- extendedWaitUntil:
visible:
id: app-root

View file

@ -1,15 +1,5 @@
appId: ${APP_ID || "in.freshyo.adminui"}
---
# Sets the slot's delivery (+60 min) and freeze (+50 min) date/times using the
# native Android pickers. Assumes the create-slot screen is already open.
- evalScript: ${output.delivery = new Date(Date.now() + 60 * 60 * 1000)}
- evalScript: ${output.freeze = new Date(Date.now() + 50 * 60 * 1000)}
- evalScript: ${output.deliveryHour = ('0' + output.delivery.getHours()).slice(-2)}
- evalScript: ${output.deliveryMinute = ('0' + output.delivery.getMinutes()).slice(-2)}
- evalScript: ${output.freezeHour = ('0' + output.freeze.getHours()).slice(-2)}
- evalScript: ${output.freezeMinute = ('0' + output.freeze.getMinutes()).slice(-2)}
# --- Delivery: date (today) + time (+60) ---
- tapOn:
id: delivery-date-picker
- tapOn:
@ -23,15 +13,14 @@ appId: ${APP_ID || "in.freshyo.adminui"}
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${output.deliveryHour}
- inputText: ${SLOT_HOUR}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${output.deliveryMinute}
- inputText: ${SLOT_MIN}
- tapOn:
id: 'android:id/button1'
# --- Freeze: date (today) + time (+50) ---
- tapOn:
id: freeze-date-picker
- tapOn:
@ -45,10 +34,10 @@ appId: ${APP_ID || "in.freshyo.adminui"}
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${output.freezeHour}
- inputText: ${SLOT_FREEZE_HOUR}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${output.freezeMinute}
- inputText: ${SLOT_FREEZE_MIN}
- tapOn:
id: 'android:id/button1'

119
tests/e2e/user-order.yaml Normal file
View file

@ -0,0 +1,119 @@
appId: ${USER_APP_ID}
env:
USER_APP_ID: ${USER_APP_ID || "in.freshyo.app"}
USER_MOBILE: ${USER_MOBILE || ""}
USER_PASSWORD: ${USER_PASSWORD || ""}
PRODUCT_NAME: ${PRODUCT_NAME || "E2E Test Product"}
SLOT_DATE: ${SLOT_DATE || ""}
SLOT_HOUR: ${SLOT_HOUR || ""}
SLOT_MIN: ${SLOT_MIN || ""}
tags:
- user
- smoke
---
- launchApp:
clearState: true
- extendedWaitUntil:
visible:
text: 'Next|Home'
timeout: 60000
- tapOn:
text: 'Next'
optional: true
- waitForAnimationToEnd
- extendedWaitUntil:
visible:
id: home-search
timeout: 30000
- tapOn:
id: home-search
- tapOn:
id: search-input-input
- inputText: ${PRODUCT_NAME}
- pressKey: enter
- tapOn: 'Add to Cart'
- extendedWaitUntil:
visible:
id: 'slot-${SLOT_DATE}-${SLOT_HOUR}-${SLOT_MIN}'
timeout: 20000
- tapOn:
id: 'slot-${SLOT_DATE}-${SLOT_HOUR}-${SLOT_MIN}'
- tapOn:
id: add-to-cart-confirm
- extendedWaitUntil:
visible:
id: go-to-cart
timeout: 20000
- tapOn:
id: go-to-cart
- scrollUntilVisible:
element:
id: checkout-button
direction: DOWN
- tapOn:
id: checkout-button
- extendedWaitUntil:
visible:
id: 'user-login-mobile|place-order-button'
timeout: 30000
- runFlow:
when:
visible:
id: user-login-mobile
commands:
- runFlow: subflows/login-user.yaml
- extendedWaitUntil:
visible:
id: home-search
timeout: 60000
- extendedWaitUntil:
visible:
id: go-to-cart
timeout: 20000
- tapOn:
id: go-to-cart
- scrollUntilVisible:
element:
id: checkout-button
direction: DOWN
- tapOn:
id: checkout-button
- extendedWaitUntil:
visible:
id: address-option-0
timeout: 30000
- scrollUntilVisible:
element:
id: place-order-button
direction: DOWN
- runFlow:
when:
visible:
id: select-address-error
commands:
- scrollUntilVisible:
element:
id: address-option-0
direction: UP
- tapOn:
id: address-option-0
- scrollUntilVisible:
element:
id: place-order-button
direction: DOWN
- extendedWaitUntil:
notVisible:
id: select-address-error
timeout: 15000
- tapOn:
id: place-order-button
- assertVisible: 'Order Placed Successfully!'