This commit is contained in:
shafi54 2026-09-16 00:36:02 +05:30
parent e560f51fe7
commit a40725213f
11 changed files with 331 additions and 21 deletions

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons'; import { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
import { View, TouchableOpacity, FlatList, Alert, ActivityIndicator } from 'react-native'; import { View, TouchableOpacity, FlatList, Alert, ActivityIndicator } from 'react-native';
import { AppContainer, MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui'; import { MyText, tw, MyFlatList , BottomDialog, MyTouchableOpacity, Checkbox } from 'common-ui';
import { trpc } from '@/src/trpc-client'; import { trpc } from '@/src/trpc-client';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import dayjs from 'dayjs'; import dayjs from 'dayjs';

View file

@ -2280,6 +2280,113 @@ Confirmed from AOSP MediaProvider res/layout/activity_photo_picker.xml:
guard so they can only run while the picker is still open (previously the text guard so they can only run while the picker is still open (previously the text
regex could have tapped "Add Variant" in the app after the picker closed) regex could have tapped "Add Variant" in the app after the picker closed)
[2026-09-16 00:05:33] Add slot E2E flow (create slot with relative times holding the product) + verbatim product name.
Product name is now used verbatim from .env (no timestamp suffix) so the slot flow can
search for the exact product created by add-product.yaml.
=== apps/admin-ui/app/(drawer)/dashboard/slots/index.tsx ===
- import: removed unused `AppContainer` from the common-ui import
(the list already renders <View> + <MyFlatList>, so no nested scroll)
=== tests/e2e/run.sh ===
- removed `PRODUCT_NAME="${PRODUCT_NAME} $(date +%s)"` (and its export) — name used verbatim
=== tests/e2e/.env.example ===
- PRODUCT_NAME comment updated: exact name, must be unique per run
=== tests/e2e/config.yaml ===
- added executionOrder (continueOnFailure: false; flowsOrder: add-product, add-slot-with-product)
=== tests/e2e/add-slot-with-product.yaml (NEW) ===
- login -> add-slot-fab
- evalScript computes delivery(+60m) and freeze(+50m) HH/MM
- delivery/freeze date via <x>-date-picker -> android:id/button1
- delivery/freeze time via <x>-time-picker -> android:id/toggle_mode (optional)
-> android:id/input_hour / input_minute (eraseText + inputText) -> android:id/button1
- products via slot-products-selector -> slot-products-selector-search -> inputText ${PRODUCT_NAME}
-> slot-products-selector-option-.* (index 0,1) -> slot-products-selector-done
- create-slot-button -> assert "Slot created successfully!" -> OK
=== tests/e2e/README.md ===
- documented the slot flow, suite ordering, and the verbatim-name caveat
[2026-09-16 00:08:22] Extract slot time-picking into a subflow + add a standalone empty-slot flow.
Purpose: validate the native date/time picker interaction in isolation, without
creating a product first.
=== tests/e2e/subflows/set-slot-times.yaml (NEW) ===
- computes delivery(+60m)/freeze(+50m) HH/MM via evalScript and drives the native
pickers (delivery/freeze date-picker -> android:id/button1; time-picker ->
android:id/toggle_mode (optional) -> input_hour / input_minute -> button1)
extracted verbatim from add-slot-with-product.yaml
=== tests/e2e/add-slot-with-product.yaml ===
- replaced the inline evalScript + delivery/freeze picker blocks with
`runFlow: subflows/set-slot-times.yaml`
=== tests/e2e/dev/add-empty-slot.yaml (NEW) ===
- standalone flow: login -> add-slot-fab -> set-slot-times -> create-slot-button
-> assert "Slot created successfully!" (no products)
- lives in dev/ so the root `*.yaml` suite glob does not pick it up
[2026-09-16 00:11:19] Fix slot flows: navigate to the Slots list before the FAB.
Symptom: `Element not found: Id matching regex: add-slot-fab`. The FAB is on the
Slots list screen, but the flows tapped it right after login (which lands on the
dashboard).
=== tests/e2e/add-slot-with-product.yaml ===
- added: scrollUntilVisible delivery-slots-menu-item -> tapOn delivery-slots-menu-item
-> assertVisible add-slot-fab, before tapping add-slot-fab
=== tests/e2e/dev/add-empty-slot.yaml ===
- same navigation added after login
[2026-09-16 00:21:09] Make login subflow idempotent; relaunch app between suite flows.
Symptom: after add-product succeeded (already authenticated, not on the login
screen), add-slot-with-product failed at `Assertion is false: id: login-name-input
is visible`.
=== tests/e2e/subflows/login.yaml ===
- wrapped the login steps in `runFlow: when: visible id: login-name-input`, so the
subflow logs in only if the login screen is showing (reusable standalone or after
another already-authenticated flow)
- removed the trailing `assertVisible add-product-menu-item` (callers wait for their
own first element)
=== tests/e2e/add-slot-with-product.yaml ===
- added `launchApp` before login so the app restarts at its initial route
(dashboard when already logged in, login otherwise)
=== tests/e2e/dev/add-empty-slot.yaml ===
- added `launchApp` before login for the same reason
[2026-09-16 00:22:43] Keep login.yaml as a real flow; make the invocation conditional.
Rework of the previous entry: login.yaml is a normal (unconditional) login flow
again; callers invoke it only when the login screen is showing.
=== tests/e2e/subflows/login.yaml ===
- restored unconditional login steps + trailing `assertVisible add-product-menu-item`
(no inner `when`)
=== tests/e2e/add-product.yaml, add-slot-with-product.yaml, dev/add-empty-slot.yaml ===
- replaced `runFlow: <login path>` with a conditional inline wrapper:
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: <login path>

View file

@ -9,8 +9,9 @@ APP_ID=in.freshyo.adminui
STAFF_NAME= STAFF_NAME=
STAFF_PASSWORD= STAFF_PASSWORD=
# Product under test. run.sh appends a timestamp so re-runs don't collide with # Product under test. Used verbatim: add-product.yaml creates it with this exact
# the backend's unique-name check. # 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" PRODUCT_NAME="E2E Test Product"
# Id of the store to pick in the Store dropdown (see the Stores screen / DB) # Id of the store to pick in the Store dropdown (see the Stores screen / DB)

View file

@ -6,13 +6,17 @@ Maestro flows that drive the **admin-ui** Expo app on an Android emulator.
``` ```
tests/e2e/ tests/e2e/
config.yaml # workspace config (entry flows = *.yaml at the root) config.yaml # workspace config (entry flows + execution order)
add-product.yaml # first flow: create a product with 2 SKUs (one flash) 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
subflows/ subflows/
login.yaml # reusable login login.yaml # reusable login (callers invoke it conditionally)
pick-image.yaml # OS photo-picker adapter (Android) pick-image.yaml # OS photo-picker adapter (Android)
set-slot-times.yaml # delivery +60m / freeze +50m 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` assets/test-product.png # seeded into the emulator gallery via `addMedia`
run.sh # sources .env and runs `maestro test` run.sh # loads .env and runs `maestro test`
.env.example # copy to .env and fill in .env.example # copy to .env and fill in
``` ```
@ -33,11 +37,14 @@ cp tests/e2e/.env.example tests/e2e/.env
## Run ## Run
```bash ```bash
bash tests/e2e/run.sh # every entry flow in tests/e2e/ bash tests/e2e/run.sh # every entry flow (ordered: add-product, then add-slot-with-product)
bash tests/e2e/run.sh add-product.yaml bash tests/e2e/run.sh add-product.yaml
bash tests/e2e/run.sh dev/add-empty-slot.yaml # standalone: slot only, no product
``` ```
`run.sh` appends a timestamp to `PRODUCT_NAME` (the backend rejects duplicate names). `PRODUCT_NAME` is used **verbatim**`add-product.yaml` creates it with that exact name
and `add-slot-with-product.yaml` searches for it. The backend rejects duplicate names, so
change `PRODUCT_NAME` (or delete the product) between suite runs.
## What `add-product.yaml` does ## What `add-product.yaml` does
@ -49,14 +56,26 @@ bash tests/e2e/run.sh add-product.yaml
6. Adds Variant 2, fills quantity/prices, toggles **Flash Available**, sets flash price, attaches an image. 6. Adds Variant 2, fills quantity/prices, toggles **Flash Available**, sets flash price, attaches an image.
7. Submits and asserts `Product created successfully!`. 7. Submits and asserts `Product created successfully!`.
All in-app interactions use `testID` selectors (`id:`). The only exception is the ## What `add-slot-with-product.yaml` does
OS photo picker, which is system UI and is targeted by its platform IDs in
`subflows/pick-image.yaml`. 1. `evalScript` computes delivery = now + 60 min and freeze = now + 50 min.
2. Logs in and taps the `add-slot-fab`.
3. Sets the delivery/freeze date (today) and time via the native Android pickers
(`android:id/button1`, `toggle_mode`, `input_hour`, `input_minute`).
4. Opens `slot-products-selector`, searches the exact `PRODUCT_NAME`, selects the
two SKU options and taps Done.
5. Submits and asserts `Slot created successfully!`.
All in-app interactions use `testID` selectors (`id:`). The exceptions are system UI —
the OS photo picker (`subflows/pick-image.yaml`) and the native date/time pickers.
## Notes / gotchas ## Notes / gotchas
- The image picker flow is the most version-sensitive part; thumbs are targeted by - The image picker flow is the most version-sensitive part; thumbs are targeted by
the Android 13/14 Photo Picker id with a DocumentsUI fallback, and the confirm the Android 13/14 Photo Picker id with a DocumentsUI fallback, and the confirm
button by text regex `Add|Done|Select`. button by its `button_add` id.
- Native time-picker ids (`android:id/toggle_mode`, `input_hour`, `input_minute`,
`button1`) depend on the OS version. The date dialog defaults to today, so a run
within ~60 min of midnight would put delivery on the next day.
- On a reused emulator, `addMedia` images accumulate in the gallery (harmless). - On a reused emulator, `addMedia` images accumulate in the gallery (harmless).
- Tests create real data on the connected backend. - Tests create real data on the connected backend.

View file

@ -28,7 +28,12 @@ tags:
permissions: permissions:
storage: allow storage: allow
# --- Login --- # --- Login (only when the login screen is showing) ---
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: subflows/login.yaml - runFlow: subflows/login.yaml
# --- Navigate to Add Product --- # --- Navigate to Add Product ---

View file

@ -0,0 +1,69 @@
appId: ${APP_ID || "in.freshyo.adminui"}
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"}
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).
- runFlow:
when:
visible:
id: login-name-input
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:
id: delivery-date-picker
# --- 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
direction: DOWN
- tapOn:
id: slot-products-selector
- tapOn:
id: slot-products-selector-search
- inputText: ${PRODUCT_NAME}
- hideKeyboard
- tapOn:
id: 'slot-products-selector-option-.*'
index: 0
- tapOn:
id: 'slot-products-selector-option-.*'
index: 1
- tapOn:
id: slot-products-selector-done
# --- Submit ---
- scrollUntilVisible:
element:
id: create-slot-button
direction: DOWN
- tapOn:
id: create-slot-button
- assertVisible: 'Slot created successfully!'
- tapOn: 'OK'

View file

@ -4,3 +4,10 @@
flows: flows:
- '*.yaml' - '*.yaml'
- '!config.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

View file

@ -0,0 +1,47 @@
appId: ${APP_ID || "in.freshyo.adminui"}
env:
APP_ID: ${APP_ID || "in.freshyo.adminui"}
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
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.
- runFlow:
when:
visible:
id: login-name-input
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:
id: delivery-date-picker
- runFlow: ../subflows/set-slot-times.yaml
- scrollUntilVisible:
element:
id: create-slot-button
direction: DOWN
- tapOn:
id: create-slot-button
- assertVisible: 'Slot created successfully!'
- tapOn: 'OK'

View file

@ -32,8 +32,8 @@ done < ./.env
: "${PRODUCT_NAME:?PRODUCT_NAME is required}" : "${PRODUCT_NAME:?PRODUCT_NAME is required}"
: "${STORE_ID:?STORE_ID is required}" : "${STORE_ID:?STORE_ID is required}"
# Unique product name so repeat runs don't trip the backend's duplicate check. # PRODUCT_NAME is used verbatim (the slot flow searches for the exact name).
PRODUCT_NAME="${PRODUCT_NAME} $(date +%s)" # The backend rejects duplicate product names, so change it between suite runs.
export PRODUCT_NAME export PRODUCT_NAME
# Default to the whole suite when no path/tags are passed. # Default to the whole suite when no path/tags are passed.

View file

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

View file

@ -0,0 +1,54 @@
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:
id: 'android:id/button1'
- tapOn:
id: delivery-time-picker
- tapOn:
id: 'android:id/toggle_mode'
optional: true
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${output.deliveryHour}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${output.deliveryMinute}
- tapOn:
id: 'android:id/button1'
# --- Freeze: date (today) + time (+50) ---
- tapOn:
id: freeze-date-picker
- tapOn:
id: 'android:id/button1'
- tapOn:
id: freeze-time-picker
- tapOn:
id: 'android:id/toggle_mode'
optional: true
- tapOn:
id: 'android:id/input_hour'
- eraseText
- inputText: ${output.freezeHour}
- tapOn:
id: 'android:id/input_minute'
- eraseText
- inputText: ${output.freezeMinute}
- tapOn:
id: 'android:id/button1'