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 { MaterialCommunityIcons, Entypo } from '@expo/vector-icons';
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 { useRouter } from 'expo-router';
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
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_PASSWORD=
# Product under test. run.sh appends a timestamp so re-runs don't collide with
# the backend's unique-name check.
# 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)

View file

@ -6,13 +6,17 @@ Maestro flows that drive the **admin-ui** Expo app on an Android emulator.
```
tests/e2e/
config.yaml # workspace config (entry flows = *.yaml at the root)
add-product.yaml # first flow: create a product with 2 SKUs (one flash)
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
subflows/
login.yaml # reusable login
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
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`
run.sh # sources .env and runs `maestro test`
run.sh # loads .env and runs `maestro test`
.env.example # copy to .env and fill in
```
@ -33,11 +37,14 @@ cp tests/e2e/.env.example tests/e2e/.env
## Run
```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 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
@ -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.
7. Submits and asserts `Product created successfully!`.
All in-app interactions use `testID` selectors (`id:`). The only exception is the
OS photo picker, which is system UI and is targeted by its platform IDs in
`subflows/pick-image.yaml`.
## What `add-slot-with-product.yaml` does
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
- 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
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).
- Tests create real data on the connected backend.

View file

@ -28,7 +28,12 @@ tags:
permissions:
storage: allow
# --- Login ---
# --- Login (only when the login screen is showing) ---
- runFlow:
when:
visible:
id: login-name-input
commands:
- runFlow: subflows/login.yaml
# --- 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:
- '*.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}"
: "${STORE_ID:?STORE_ID is required}"
# Unique product name so repeat runs don't trip the backend's duplicate check.
PRODUCT_NAME="${PRODUCT_NAME} $(date +%s)"
# 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.

View file

@ -3,9 +3,10 @@ env:
STAFF_NAME: ${STAFF_NAME || ""}
STAFF_PASSWORD: ${STAFF_PASSWORD || ""}
---
# Logs into the admin app. Reused by other flows via `runFlow: subflows/login.yaml`.
- assertVisible:
id: login-name-input
# 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}
@ -15,6 +16,6 @@ env:
- hideKeyboard
- tapOn:
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:
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'