maestro cases

This commit is contained in:
2026-03-02 19:18:35 +05:30
parent 07a0a29106
commit c0a69707e6
46 changed files with 1108 additions and 89 deletions

77
.github/workflows/maestro-e2e.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
# Maestro E2E Tests
# Runs on: manual trigger, or when maestro flows change
# Requires secrets: TEST_STAFF_PHONE, TEST_STAFF_OTP, TEST_CLIENT_EMAIL, TEST_CLIENT_PASSWORD
# Optional: TEST_CLIENT_COMPANY, TEST_STAFF_SIGNUP_PHONE
name: Maestro E2E
on:
workflow_dispatch:
pull_request:
paths:
- 'apps/mobile/**/maestro/**'
- '.github/workflows/maestro-e2e.yml'
push:
branches:
- main
paths:
- 'apps/mobile/**/maestro/**'
- '.github/workflows/maestro-e2e.yml'
jobs:
maestro-e2e:
name: 🎭 Maestro E2E
runs-on: macos-latest
timeout-minutes: 45
steps:
- name: 📥 Checkout
uses: actions/checkout@v4
- name: 🦋 Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.x'
channel: 'stable'
cache: true
- name: 🔧 Install Firebase CLI
run: npm install -g firebase-tools
- name: 📦 Get dependencies
run: make mobile-install
- name: 🔨 Build Staff APK
run: make mobile-staff-build PLATFORM=apk MODE=debug
- name: 🔨 Build Client APK
run: make mobile-client-build PLATFORM=apk MODE=debug
- name: 📲 Start emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
target: default
arch: x86_64
profile: Nexus 6
script: |
# Install Maestro
curl -Ls "https://get.maestro.mobile.dev" | bash
export PATH="$HOME/.maestro/bin:$PATH"
maestro --version
# Install APKs
adb install -r apps/mobile/apps/staff/build/app/outputs/flutter-apk/app-debug.apk
adb install -r apps/mobile/apps/client/build/app/outputs/flutter-apk/app-debug.apk
# Run auth flows (Staff + Client)
maestro test --shard-split=1 \
apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
apps/mobile/apps/client/maestro/auth/sign_in.yaml \
apps/mobile/apps/client/maestro/auth/sign_up.yaml \
-e TEST_STAFF_PHONE="${{ secrets.TEST_STAFF_PHONE }}" \
-e TEST_STAFF_OTP="${{ secrets.TEST_STAFF_OTP }}" \
-e TEST_STAFF_SIGNUP_PHONE="${{ secrets.TEST_STAFF_SIGNUP_PHONE }}" \
-e TEST_CLIENT_EMAIL="${{ secrets.TEST_CLIENT_EMAIL }}" \
-e TEST_CLIENT_PASSWORD="${{ secrets.TEST_CLIENT_PASSWORD }}" \
-e TEST_CLIENT_COMPANY="${{ secrets.TEST_CLIENT_COMPANY }}"

View File

@@ -10,6 +10,18 @@ maestro/
auth/ auth/
sign_in.yaml sign_in.yaml
sign_up.yaml sign_up.yaml
navigation/
home.yaml
orders.yaml
billing.yaml
coverage.yaml
reports.yaml
orders/
view_orders.yaml
completed_no_edit_icon.yaml # #492
create_order_entry.yaml
settings/
settings_page.yaml
``` ```
## Credentials (env, never hardcoded) ## Credentials (env, never hardcoded)
@@ -19,6 +31,8 @@ maestro/
| sign_in | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` | | sign_in | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` |
| sign_up | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` | | sign_up | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` |
**Sign-in:** testclient@gmail.com / testclient!
## Run ## Run
```bash ```bash

View File

@@ -0,0 +1,23 @@
# Client App — Sign in with wrong password (negative test)
# Uses valid email, invalid password; expects error and stays on Sign In
# Run: maestro test .../auth/sign_in_invalid_password.yaml -e TEST_CLIENT_EMAIL=testclient@gmail.com -e TEST_CLIENT_INVALID_PASSWORD=wrongpass
appId: com.krowwithus.client
env:
EMAIL: ${TEST_CLIENT_EMAIL}
PASSWORD: ${TEST_CLIENT_INVALID_PASSWORD}
---
- launchApp
- assertVisible: "Sign In"
- tapOn: "Sign In"
- assertVisible: "Email"
- tapOn:
id: sign_in_email
- inputText: ${EMAIL}
- tapOn:
id: sign_in_password
- inputText: ${PASSWORD}
- tapOn: "Sign In"
- extendedWaitUntil:
visible: "Invalid"
timeout: 5000
- assertVisible: "Sign In"

View File

@@ -0,0 +1,13 @@
# Client App — Sign out flow (tap Log Out, confirm in dialog)
# Run: maestro test auth/sign_in.yaml auth/sign_out.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Settings"
- extendedWaitUntil:
visible: "Log Out"
timeout: 5000
- tapOn: "Log Out"
- assertVisible: "Are you sure you want to log out?"
- tapOn: "Log Out"
- assertVisible: "Sign In"

View File

@@ -0,0 +1,7 @@
# Client App — Billing tab navigation
# Run: maestro test auth/sign_in.yaml navigation/billing.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Billing"
- assertVisible: "Billing"

View File

@@ -0,0 +1,7 @@
# Client App — Coverage tab navigation
# Run: maestro test auth/sign_in.yaml navigation/coverage.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Coverage"
- assertVisible: "Coverage"

View File

@@ -0,0 +1,6 @@
# Client App — Home tab (default after sign-in)
# Run: maestro test auth/sign_in.yaml navigation/home.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- assertVisible: "Orders"

View File

@@ -0,0 +1,7 @@
# Client App — Orders tab navigation
# Run: maestro test auth/sign_in.yaml navigation/orders.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Orders"
- assertVisible: "Orders"

View File

@@ -0,0 +1,7 @@
# Client App — Reports tab navigation
# Run: maestro test auth/sign_in.yaml navigation/reports.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Reports"
- assertVisible: "Reports"

View File

@@ -0,0 +1,8 @@
# Client App — Completed tab: edit icon hidden for past/completed orders (#492)
# Run: maestro test auth/sign_in.yaml orders/completed_no_edit_icon.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Orders"
- tapOn: "Completed"
- assertVisible: "Completed"

View File

@@ -0,0 +1,9 @@
# Client App — Create order flow entry (tap Post, see order type selection)
# Run: maestro test auth/sign_in.yaml orders/create_order_entry.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Orders"
- assertVisible: "Post"
- tapOn: "Post"
- assertVisible: "Rapid"

View File

@@ -0,0 +1,9 @@
# Client App — View Orders page with filter tabs
# Run: maestro test auth/sign_in.yaml orders/view_orders.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Orders"
- assertVisible: "Up Next"
- assertVisible: "Active"
- assertVisible: "Completed"

View File

@@ -0,0 +1,14 @@
# Client App — Edit Profile page (Settings > Edit Profile)
# Run: maestro test auth/sign_in.yaml settings/edit_profile.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Settings"
- extendedWaitUntil:
visible: "Edit Profile"
timeout: 5000
- tapOn: "Edit Profile"
- extendedWaitUntil:
visible: "Save Changes"
timeout: 10000
- assertVisible: "Edit Profile"

View File

@@ -0,0 +1,7 @@
# Client App — Settings page (reached via settings icon on Home header)
# Run: maestro test auth/sign_in.yaml settings/settings_page.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Settings"
- assertVisible: "Edit Profile"

View File

@@ -75,7 +75,8 @@ android {
buildTypes { buildTypes {
debug { debug {
signingConfig = signingConfigs.getByName("release") // Use default debug signing for local dev (no keystore required)
signingConfig = signingConfigs.getByName("debug")
} }
release { release {
signingConfig = signingConfigs.getByName("release") signingConfig = signingConfigs.getByName("release")

View File

@@ -10,6 +10,25 @@ maestro/
auth/ auth/
sign_in.yaml sign_in.yaml
sign_up.yaml sign_up.yaml
navigation/
home.yaml
shifts.yaml
profile.yaml
payments.yaml
clock_in.yaml
profile/
personal_info.yaml
documents_list.yaml
certificates_list.yaml
compliance/
document_upload_banner.yaml # #550
certificate_upload_banner.yaml # #551
attire_upload_banner.yaml # #552
shifts/
find_shifts.yaml
incomplete_profile_banner.yaml # #549 (requires incomplete-profile user)
home/
benefits.yaml # #524
``` ```
## Prerequisites ## Prerequisites
@@ -24,6 +43,8 @@ maestro/
| sign_in | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` | | sign_in | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` |
| sign_up | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` | | sign_up | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` |
**Sign-in:** +1 555-555-1234 (env: 5555551234) / 123123
## Run ## Run
```bash ```bash

View File

@@ -15,8 +15,9 @@ env:
- tapOn: - tapOn:
id: staff_phone_input id: staff_phone_input
- inputText: ${PHONE} - inputText: ${PHONE}
- hideKeyboard
- tapOn: "Send Code" - tapOn: "Send Code"
# OTP screen: Continue button visible until we finish typing # OTP screen: Continue visible when ready for OTP entry
- assertVisible: "Continue" - assertVisible: "Continue"
- tapOn: - tapOn:
id: staff_otp_input id: staff_otp_input

View File

@@ -0,0 +1,25 @@
# Staff App — Sign in with wrong OTP (negative test)
# Uses valid test phone, invalid OTP; expects error and stays on OTP screen
# Run: maestro test .../auth/sign_in_invalid_otp.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_INVALID_OTP=000000
appId: com.krowwithus.staff
env:
PHONE: ${TEST_STAFF_PHONE}
OTP: ${TEST_STAFF_INVALID_OTP}
---
- launchApp
- assertVisible: "Log In"
- tapOn: "Log In"
- assertVisible: "Send Code"
- tapOn:
id: staff_phone_input
- inputText: ${PHONE}
- hideKeyboard
- tapOn: "Send Code"
- assertVisible: "Continue"
- tapOn:
id: staff_otp_input
- inputText: ${OTP}
- extendedWaitUntil:
visible: "Invalid"
timeout: 5000
- assertVisible: "Continue"

View File

@@ -0,0 +1,15 @@
# Staff App — Sign out flow
# Run: maestro test auth/sign_in.yaml auth/sign_out.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 2000
- scrollUntilVisible:
element: "Sign Out"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Sign Out"
- assertVisible: "Log In"

View File

@@ -15,8 +15,9 @@ env:
- tapOn: - tapOn:
id: staff_phone_input id: staff_phone_input
- inputText: ${PHONE} - inputText: ${PHONE}
- hideKeyboard
- tapOn: "Send Code" - tapOn: "Send Code"
# OTP auto-submits when 6th digit entered # OTP screen: Continue visible when ready for OTP entry
- assertVisible: "Continue" - assertVisible: "Continue"
- tapOn: - tapOn:
id: staff_otp_input id: staff_otp_input

View File

@@ -0,0 +1,19 @@
# Staff App — Attire upload file restriction banner (#552)
# Run: maestro test .../compliance/attire_upload_banner.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
env:
PHONE: ${TEST_STAFF_PHONE}
OTP: ${TEST_STAFF_OTP}
# Run after sign_in: maestro test auth/sign_in.yaml compliance/attire_upload_banner.yaml -e ...
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Attire"
- assertVisible: "Attire"
- tapOn: "Attire"
- tapOn: "Shirt"
- assertVisible: "Only JPEG, JPG, and PNG"

View File

@@ -0,0 +1,19 @@
# Staff App — Certificate upload file restriction banner (#551)
# Run: maestro test .../compliance/certificate_upload_banner.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
env:
PHONE: ${TEST_STAFF_PHONE}
OTP: ${TEST_STAFF_OTP}
# Run after sign_in: maestro test auth/sign_in.yaml compliance/certificate_upload_banner.yaml -e ...
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Certificates"
- assertVisible: "Certificates"
- tapOn: "Certificates"
- tapOn: "Add Another Certificate"
- assertVisible: "Only PDF files are accepted"

View File

@@ -0,0 +1,24 @@
# Staff App — Document upload file restriction banner (#550)
# Run: maestro test .../compliance/document_upload_banner.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
env:
PHONE: ${TEST_STAFF_PHONE}
OTP: ${TEST_STAFF_OTP}
# Run after sign_in: maestro test auth/sign_in.yaml compliance/document_upload_banner.yaml -e ...
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Documents"
- assertVisible: "Documents"
- tapOn: "Documents"
- extendedWaitUntil:
visible:
id: "staff_document_upload"
timeout: 20000
- tapOn:
id: "staff_document_upload"
- assertVisible: "Only PDF files are accepted"

View File

@@ -0,0 +1,6 @@
# Staff App — Benefits section on Home (#524)
# Run: maestro test auth/sign_in.yaml home/benefits.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Home"

View File

@@ -0,0 +1,8 @@
# Staff App — Clock In tab navigation
# Run: maestro test auth/sign_in.yaml navigation/clock_in.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Clock In"
- tapOn: "Clock In"
- assertVisible: "Clock In"

View File

@@ -0,0 +1,6 @@
# Staff App — Home tab (default after sign-in)
# Run: maestro test auth/sign_in.yaml navigation/home.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Find Shifts"

View File

@@ -0,0 +1,8 @@
# Staff App — Payments tab navigation
# Run: maestro test auth/sign_in.yaml navigation/payments.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Payments"
- tapOn: "Payments"
- assertVisible: "Payments"

View File

@@ -0,0 +1,8 @@
# Staff App — Profile tab navigation (post sign-in)
# Run: maestro test auth/sign_in.yaml navigation/profile.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- assertVisible: "Personal Info"

View File

@@ -0,0 +1,8 @@
# Staff App — Shifts tab navigation (post sign-in)
# Run: maestro test auth/sign_in.yaml navigation/shifts.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Shifts"
- tapOn: "Shifts"
- assertVisible: "Find Shifts"

View File

@@ -0,0 +1,18 @@
# Staff App — Bank Account section (Profile > Finance > Bank Account)
# Run: maestro test auth/sign_in.yaml profile/bank_account.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Bank Account"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Bank Account"
- extendedWaitUntil:
visible: "Bank Account"
timeout: 10000
- assertVisible: "Bank Account"

View File

@@ -0,0 +1,10 @@
# Staff App — Certificates list page
# Run: maestro test auth/sign_in.yaml profile/certificates_list.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- assertVisible: "Certificates"
- tapOn: "Certificates"
- assertVisible: "Certificates"

View File

@@ -0,0 +1,10 @@
# Staff App — Documents list page
# Run: maestro test auth/sign_in.yaml profile/documents_list.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- assertVisible: "Documents"
- tapOn: "Documents"
- assertVisible: "Documents"

View File

@@ -0,0 +1,18 @@
# Staff App — Emergency Contact section (Profile > Onboarding > Emergency Contact)
# Run: maestro test auth/sign_in.yaml profile/emergency_contact.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Emergency Contact"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Emergency Contact"
- extendedWaitUntil:
visible: "Save & Continue"
timeout: 10000
- assertVisible: "Emergency Contact"

View File

@@ -0,0 +1,18 @@
# Staff App — FAQs section (Profile > Support > FAQs)
# Run: maestro test auth/sign_in.yaml profile/faqs.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "FAQs"
visibilityPercentage: 50
timeout: 10000
- tapOn: "FAQs"
- extendedWaitUntil:
visible: "FAQs"
timeout: 10000
- assertVisible: "FAQs"

View File

@@ -0,0 +1,10 @@
# Staff App — Personal Info page
# Run: maestro test auth/sign_in.yaml profile/personal_info.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- assertVisible: "Personal Info"
- tapOn: "Personal Info"
- assertVisible: "Full Name"

View File

@@ -0,0 +1,18 @@
# Staff App — Privacy & Security section (Profile > Support > Privacy & Security)
# Run: maestro test auth/sign_in.yaml profile/privacy_security.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Privacy & Security"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Privacy & Security"
- extendedWaitUntil:
visible: "Privacy & Security"
timeout: 10000
- assertVisible: "Privacy Policy"

View File

@@ -0,0 +1,18 @@
# Staff App — Timecard section (Profile > Finance > Timecard)
# Run: maestro test auth/sign_in.yaml profile/time_card.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Timecard"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Timecard"
- extendedWaitUntil:
visible: "Hours Worked"
timeout: 10000
- assertVisible: "Timecard"

View File

@@ -0,0 +1,10 @@
# Staff App — Find Shifts tab
# Run: maestro test auth/sign_in.yaml shifts/find_shifts.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Shifts"
- tapOn: "Shifts"
- assertVisible: "Find Shifts"
- tapOn: "Find Shifts"
- assertVisible: "Find Shifts"

View File

@@ -0,0 +1,12 @@
# Staff App — Incomplete profile banner in Find Shifts (#549)
# Requires staff user with INCOMPLETE profile
# Run: maestro test auth/sign_in.yaml shifts/incomplete_profile_banner.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Shifts"
- tapOn: "Shifts"
- assertVisible: "Find Shifts"
- tapOn: "Find Shifts"
- assertVisible: "Your account isn't complete yet."
- assertVisible: "Complete your account now"

View File

@@ -36,8 +36,22 @@ class _OtpInputFieldState extends State<OtpInputField> {
); );
final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode()); final List<FocusNode> _focusNodes = List.generate(6, (_) => FocusNode());
/// Hidden field for E2E: Maestro inputText sends full OTP in one call;
/// the 6 visible boxes have maxLength:1 and would truncate.
late final TextEditingController _hiddenController;
late final FocusNode _hiddenFocusNode;
@override
void initState() {
super.initState();
_hiddenController = TextEditingController();
_hiddenFocusNode = FocusNode();
}
@override @override
void dispose() { void dispose() {
_hiddenController.dispose();
_hiddenFocusNode.dispose();
for (final TextEditingController controller in _controllers) { for (final TextEditingController controller in _controllers) {
controller.dispose(); controller.dispose();
} }
@@ -47,6 +61,22 @@ class _OtpInputFieldState extends State<OtpInputField> {
super.dispose(); super.dispose();
} }
/// Distributes full OTP from hidden field to the 6 visible boxes and notifies Bloc.
void _syncFromHidden(BuildContext context, String value) {
final String raw = value.replaceAll(RegExp(r'\D'), '');
final String digits = raw.length > 6 ? raw.substring(0, 6) : raw;
for (int i = 0; i < 6; i++) {
final String digit = i < digits.length ? digits[i] : '';
if (_controllers[i].text != digit) {
_controllers[i].text = digit;
}
}
BlocProvider.of<AuthBloc>(context).add(AuthOtpUpdated(digits));
if (digits.length == 6) {
widget.onCompleted(digits);
}
}
/// Helper getter to compute the current OTP code from all controllers. /// Helper getter to compute the current OTP code from all controllers.
String get _otpCode => _controllers.map((TextEditingController c) => c.text).join(); String get _otpCode => _controllers.map((TextEditingController c) => c.text).join();
@@ -70,57 +100,79 @@ class _OtpInputFieldState extends State<OtpInputField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const double boxWidth = 45;
const double boxHeight = 56;
return Column( return Column(
children: <Widget>[ children: <Widget>[
Row( SizedBox(
mainAxisAlignment: MainAxisAlignment.spaceBetween, width: 300,
children: List.generate(6, (int index) { height: boxHeight,
final TextField field = TextField( child: Stack(
controller: _controllers[index], children: <Widget>[
focusNode: _focusNodes[index], Row(
keyboardType: TextInputType.number, mainAxisAlignment: MainAxisAlignment.spaceBetween,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly], children: List.generate(6, (int index) {
textAlign: TextAlign.center, final TextField field = TextField(
maxLength: 1, controller: _controllers[index],
style: UiTypography.headline3m, focusNode: _focusNodes[index],
decoration: InputDecoration( keyboardType: TextInputType.number,
counterText: '', inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
border: OutlineInputBorder( textAlign: TextAlign.center,
borderSide: BorderSide( maxLength: 1,
color: widget.error.isNotEmpty style: UiTypography.headline3m,
? UiColors.textError decoration: InputDecoration(
: (_controllers[index].text.isNotEmpty counterText: '',
? UiColors.primary border: OutlineInputBorder(
: UiColors.border), borderSide: BorderSide(
width: 2, color: widget.error.isNotEmpty
), ? UiColors.textError
), : (_controllers[index].text.isNotEmpty
enabledBorder: OutlineInputBorder( ? UiColors.primary
borderRadius: UiConstants.radiusMd, : UiColors.border),
borderSide: BorderSide( width: 2,
color: widget.error.isNotEmpty ),
? UiColors.textError ),
: (_controllers[index].text.isNotEmpty enabledBorder: OutlineInputBorder(
? UiColors.primary borderRadius: UiConstants.radiusMd,
: UiColors.border), borderSide: BorderSide(
width: 2, color: widget.error.isNotEmpty
? UiColors.textError
: (_controllers[index].text.isNotEmpty
? UiColors.primary
: UiColors.border),
width: 2,
),
),
),
onChanged: (String value) =>
_onChanged(context: context, index: index, value: value),
);
return SizedBox(width: boxWidth, height: boxHeight, child: field);
}),
),
Positioned.fill(
child: Semantics(
identifier: 'staff_otp_input',
container: false,
child: Opacity(
opacity: 0.01,
child: TextField(
controller: _hiddenController,
focusNode: _hiddenFocusNode,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
maxLength: 6,
onChanged: (String value) =>
_syncFromHidden(context, value),
), ),
), ),
), ),
onChanged: (String value) => ),
_onChanged(context: context, index: index, value: value), ],
); ),
return SizedBox(
width: 45,
height: 56,
child: index == 0
? Semantics(
identifier: 'staff_otp_input',
child: field,
)
: field,
);
}),
), ),
if (widget.error.isNotEmpty) if (widget.error.isNotEmpty)
Padding( Padding(

View File

@@ -84,6 +84,7 @@ class _PhoneInputFormFieldState extends State<PhoneInputFormField> {
Expanded( Expanded(
child: Semantics( child: Semantics(
identifier: 'staff_phone_input', identifier: 'staff_phone_input',
container: false, // Merge with TextField so tap/input reach the actual field
child: TextField( child: TextField(
controller: _controller, controller: _controller,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,

View File

@@ -152,9 +152,14 @@ class DocumentCard extends StatelessWidget {
Widget _buildActionButton(DocumentStatus status) { Widget _buildActionButton(DocumentStatus status) {
final bool isVerified = status == DocumentStatus.verified; final bool isVerified = status == DocumentStatus.verified;
return InkWell( return Semantics(
onTap: onTap, identifier: 'staff_document_upload',
borderRadius: UiConstants.radiusSm, label: isVerified
? t.staff_documents.card.view
: t.staff_documents.card.upload,
child: InkWell(
onTap: onTap,
borderRadius: UiConstants.radiusSm,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row( child: Row(
@@ -174,6 +179,7 @@ class DocumentCard extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
} }

View File

@@ -47,9 +47,6 @@ type Order @table(name: "orders", key: ["id"]) {
teamHubId: UUID! teamHubId: UUID!
teamHub: TeamHub! @ref(fields: "teamHubId", references: "id") teamHub: TeamHub! @ref(fields: "teamHubId", references: "id")
hubManagerId: UUID
hubManager: TeamMember @ref(fields: "hubManagerId", references: "id")
date: Timestamp date: Timestamp
startDate: Timestamp #for recurring and permanent startDate: Timestamp #for recurring and permanent

View File

@@ -8,10 +8,17 @@ Credentials are injected via env variables — **never hardcoded** in YAML.
|------|---------------| |------|---------------|
| **Client sign-in** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` | | **Client sign-in** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` |
| **Client sign-up** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` | | **Client sign-up** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` |
| **Client sign_in_invalid_password** | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_INVALID_PASSWORD` (default: wrongpass) |
| **Staff sign-in** | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` | | **Staff sign-in** | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` |
| **Staff sign-up** | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` | | **Staff sign-up** | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` |
| **Staff sign_in_invalid_otp** | `TEST_STAFF_PHONE`, `TEST_STAFF_INVALID_OTP` (default: 000000) |
**Example values (login):** legendary@krowd.com / Demo2026! (client), 5557654321 / 123456 (staff) **Sign-in credentials:**
| App | Email/Phone | Password/OTP |
|-----|-------------|--------------|
| **Client** | testclient@gmail.com | testclient! |
| **Staff** | +1 555-555-1234 (use `5555551234` in env) | 123123 |
--- ---
@@ -29,7 +36,7 @@ curl -Ls "https://get.maestro.mobile.dev" | bash
### 2. Add Firebase test phone (Staff app) ### 2. Add Firebase test phone (Staff app)
Firebase Console → **Authentication****Sign-in method****Phone****Phone numbers for testing**: Firebase Console → **Authentication****Sign-in method****Phone****Phone numbers for testing**:
- Add **+1 5557654321** with verification code **123456** - Add **+1 555-555-1234** with verification code **123123**
### 3. Build and install apps ### 3. Build and install apps
@@ -47,33 +54,57 @@ adb install apps/mobile/apps/staff/build/app/outputs/flutter-apk/app-debug.apk
```bash ```bash
# Client login credentials # Client login credentials
export TEST_CLIENT_EMAIL=legendary@krowd.com export TEST_CLIENT_EMAIL=testclient@gmail.com
export TEST_CLIENT_PASSWORD=Demo2026! export TEST_CLIENT_PASSWORD=testclient!
export TEST_CLIENT_COMPANY="KROW Demo" export TEST_CLIENT_COMPANY="Test Company"
# Staff login credentials # Staff login credentials
export TEST_STAFF_PHONE=5557654321 export TEST_STAFF_PHONE=5555551234
export TEST_STAFF_OTP=123456 export TEST_STAFF_OTP=123123
export TEST_STAFF_SIGNUP_PHONE=5555550000 # use a new number for signup export TEST_STAFF_SIGNUP_PHONE=5555550000 # use a new number for signup
# Run full suite # Run full suite
make test-e2e make test-e2e
# Or run per app # Run CLIENT only (auth flows)
make test-e2e-client make test-e2e-client
# Run CLIENT extended (auth + navigation + orders + settings)
make test-e2e-client-extended
# Run STAFF only (auth flows)
make test-e2e-staff make test-e2e-staff
# Run STAFF extended (auth + navigation + profile + compliance + shifts + benefits)
make test-e2e-staff-extended
``` ```
### 5. Run flows directly (without Make) ### 5. Run Client only (Windows PowerShell)
```bash ```powershell
maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml \ $env:TEST_CLIENT_EMAIL = "testclient@gmail.com"
-e TEST_CLIENT_EMAIL=legendary@krowd.com \ $env:TEST_CLIENT_PASSWORD = "testclient!"
-e TEST_CLIENT_PASSWORD=Demo2026! $env:TEST_CLIENT_COMPANY = "Test Company"
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml \ # Auth only
-e TEST_STAFF_PHONE=5557654321 \ make test-e2e-client
-e TEST_STAFF_OTP=123456
# Extended (auth + navigation + orders + settings)
make test-e2e-client-extended
```
### 6. Run Staff only (Windows PowerShell)
```powershell
$env:TEST_STAFF_PHONE = "5555551234"
$env:TEST_STAFF_OTP = "123123"
$env:TEST_STAFF_SIGNUP_PHONE = "5555550000"
# Auth only
make test-e2e-staff
# Extended (auth + navigation + profile + compliance + shifts + benefits)
make test-e2e-staff-extended
``` ```
--- ---
@@ -81,12 +112,117 @@ maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
## Folder structure ## Folder structure
``` ```
apps/mobile/apps/client/maestro/auth/ apps/mobile/apps/client/maestro/
sign_in.yaml auth/
sign_up.yaml sign_in.yaml
apps/mobile/apps/staff/maestro/auth/ sign_up.yaml
sign_in.yaml sign_out.yaml
sign_up.yaml sign_in_invalid_password.yaml
navigation/
home.yaml
orders.yaml
billing.yaml
coverage.yaml
reports.yaml
orders/
view_orders.yaml
completed_no_edit_icon.yaml # #492
create_order_entry.yaml
settings/
settings_page.yaml
edit_profile.yaml
apps/mobile/apps/staff/maestro/
auth/
sign_in.yaml
sign_up.yaml
sign_out.yaml
sign_in_invalid_otp.yaml
navigation/
home.yaml
shifts.yaml
profile.yaml
payments.yaml
clock_in.yaml
profile/
personal_info.yaml
documents_list.yaml
certificates_list.yaml
time_card.yaml
bank_account.yaml
faqs.yaml
privacy_security.yaml
emergency_contact.yaml
compliance/
document_upload_banner.yaml # #550
certificate_upload_banner.yaml # #551
attire_upload_banner.yaml # #552
shifts/
find_shifts.yaml
incomplete_profile_banner.yaml # #549 (requires incomplete-profile user)
home/
benefits.yaml # #524
```
### Direct Maestro commands
To run flows like `sign_in` with explicit paths and env vars:
```bash
# Single flow
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
# Multiple flows (use --shard-split=1; if tcp:7001 persists, run as 2 commands)
maestro test --shard-split=1 apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
# Workaround: run sign_in first, then the feature flow (avoids tcp:7001 between flows)
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
maestro test apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
```
### Make targets
| Target | Description |
|--------|-------------|
| `make test-e2e` | Auth flows (sign_in, sign_up for both apps) |
| `make test-e2e-client` | Client auth flows |
| `make test-e2e-client-extended` | Client full suite (auth + nav + orders + settings) |
| `make test-e2e-client-auth` | Client auth (sign_in, sign_up) |
| `make test-e2e-client-navigation` | Client navigation (sign_in + home, orders, billing, coverage, reports) |
| `make test-e2e-client-orders` | Client orders (sign_in + view_orders, completed_no_edit_icon, create_order_entry) |
| `make test-e2e-client-settings` | Client settings (sign_in + settings_page, edit_profile) |
| `make test-e2e-client-sign-out` | Client sign out flow |
| `make test-e2e-staff` | Staff auth flows |
| `make test-e2e-staff-extended` | Staff full suite |
| `make test-e2e-staff-auth` | Staff auth (sign_in, sign_up) |
| `make test-e2e-staff-navigation` | Staff navigation (sign_in + home, shifts, profile, payments, clock_in) |
| `make test-e2e-staff-profile` | Staff profile (sign_in + personal_info, documents_list, certificates_list) |
| `make test-e2e-staff-profile-extended` | Staff profile + time_card, bank_account |
| `make test-e2e-staff-sign-out` | Staff sign out flow |
| `make test-e2e-staff-shifts` | Staff shifts (sign_in + find_shifts, incomplete_profile_banner) |
| `make test-e2e-staff-compliance` | Staff compliance (sign_in + document/certificate/attire upload banners) |
| `make test-e2e-staff-home` | Staff home (sign_in + benefits) |
| `make test-e2e-extended` | Full suite (both apps, auth + all feature flows) |
---
## Troubleshooting
**`tcp:7001: closed` or `device offline` when running flows**
1. Restart ADB before running: `adb kill-server && adb start-server`
2. Try `--shard-split=1` when running multiple flows: `maestro test --shard-split=1 flow1.yaml flow2.yaml -e ...`
3. If the error persists between flows, run each flow as a **separate command**. The app stays logged in between runs (Firebase Auth persists):
```powershell
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
maestro test apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
```
**Client and Staff flows use sign_in-first pattern**
All flows assume `auth/sign_in.yaml` runs first (via Make targets). Flows use `launchApp` and expect the app to be already logged in. Example:
```bash
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml -e TEST_STAFF_PHONE=5555551234 -e TEST_STAFF_OTP=123123
``` ```
--- ---
@@ -94,7 +230,21 @@ apps/mobile/apps/staff/maestro/auth/
## Checklist ## Checklist
- [ ] Maestro CLI installed - [ ] Maestro CLI installed
- [ ] Firebase test phone +1 5557654321 / 123456 added - [ ] Firebase test phone +1 555-555-1234 / 123123 added
- [ ] Client & Staff apps built and installed - [ ] Client & Staff apps built and installed
- [ ] Env vars exported - [ ] Env vars exported
- [ ] `make test-e2e` run from project root - [ ] `make test-e2e-client` or `make test-e2e-staff` run from project root
---
## GitHub Actions
A `.github/workflows/maestro-e2e.yml` workflow runs Maestro E2E on:
- Manual trigger (`workflow_dispatch`)
- PR/push when maestro flows change
**Required secrets** (Repository Settings → Secrets):
- `TEST_STAFF_PHONE`, `TEST_STAFF_OTP`, `TEST_STAFF_SIGNUP_PHONE`
- `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY`
The workflow builds both APKs, starts an Android emulator, installs the apps, and runs auth flows.

View File

@@ -0,0 +1,138 @@
# Manual End-to-End (E2E) Test Report
**Project:** KROW Workforce Mobile Applications (Staff & Client)
**Milestone:** M4
**Document Version:** 1.0
**Date:** March 2, 2026
**Status:** DRAFT *(Pending Execution)*
---
## Table of Contents
1. [Executive Summary](#1-executive-summary)
2. [Test Scope: M4 Features Completed](#2-test-scope-m4-features-completed)
3. [Test Environment & Accounts](#3-test-environment--accounts)
4. [Test Execution Results](#4-test-execution-results)
5. [Defect Summary](#5-defect-summary)
6. [Sign-off & Approvals](#6-sign-off--approvals)
---
## 1. Executive Summary
This document serves as the formal record of manual End-to-End (E2E) testing conducted for all features completed in Milestone 4 (M4) across the KROW Workforce mobile applications. The objective of this testing phase is to thoroughly test all new functionalities manually, ensuring they meet the acceptance criteria and function seamlessly across both iOS and Android platforms prior to client delivery.
---
## 2. Test Scope: M4 Features Completed
The scope of this test cycle includes the core features implemented during the M4 development phase, as outlined in the underlying M4 documentation. Below is the detailed breakdown of the functionality to be validated.
### 2.1 Staff Mobile Application Features
| Feature Name | Description |
| :--- | :--- |
| **Show Google Maps Location in Shift Details** | Navigate to the shift details page and verify the shift location renders correctly on Google Maps. |
| **Show Shift Requirements in Shift Details** | Verify that requirements (such as required attire) are visible to the worker before accepting a shift. |
| **Implement Attire Screen** | Verify the new attire screen lists "Must Have" and "Nice to Have" items. Ensure the image upload flow for attires works and images link to the worker profile. |
| **Implement FAQ Screen** | Verify the FAQ screen is accessible from the appropriate navigation menu and contents display correctly. |
| **Privacy and Security Screen** | Verify the privacy and security screen exists and contains profile visibility options, Terms of Service (TOS), and Privacy Policy. |
| **Restrict Navigation When Profile is Incomplete** | For incomplete profiles, verify that navigation is severely restricted (showing only **Profile** and **Home** screens). |
| **Preferred Location Edit** | Verify workers can navigate to a separate page to edit their preferred working locations. |
| **Maintain Auth Session** | Restart the app after login and verify the worker remains authenticated without being prompted to log in again. |
| **Enable iOS Deployment** | Ensure the iOS build compiles, deploys, and behaves on par with the Android build without platform-specific issues. |
### 2.2 Client Mobile Application Features
| Feature Name | Description |
| :--- | :--- |
| **Hide Edit Icon for Past/Completed Orders** | Verify the edit icon is hidden when viewing orders that are already in the past or have a "Completed" status. |
| **Implement Rapid Order Creation** | Test using voice/text input to rapidly describe a same-day order and verify the one-time order creation screen is populated correctly. |
| **Implement Recurring Order** | Test the complete UI flow to successfully create and submit a recurring order. |
| **Implement Permanent Order** | Test the complete UI flow to successfully create and submit a permanent order. |
| **Update Reorder Modal** | Verify the reorder modal correctly maps fields across one-time, recurring, and permanent order types. |
| **Complete Reports Interface with AI Insights** | Verify the main reports UI shows correct data and generates the 3 required AI insights without using placeholder UI. |
| **Daily Ops Report** | Verify the "Daily Ops" report renders real backend data. |
| **Spend Report** | Verify the "Spend" report renders real backend data. |
| **Coverage Report** | Verify the "Coverage" report renders real backend data. |
| **No-Show Report** | Verify the "No-Show" report renders real backend data. |
| **Performance Report** | Verify the "Performance" report renders real backend data. |
| **Display Hub Details Interface** | Verify there is a dedicated UI page to view Hub Details. |
| **Enable Hub Editing** | Verify Hub Details can be edited successfully on a separate, dedicated view. |
| **Maintain Auth Session** | Restart the app after login and verify the client remains authenticated without being prompted to log in again. |
| **Enable iOS Deployment** | Ensure the iOS build compiles, deploys, and behaves on par with the Android build without platform-specific issues. |
---
## 3. Test Environment & Accounts
### 3.1 Devices under Test
*Testing should be performed on the latest stable releases unless otherwise specified. Test across iOS and Android.*
- **Android Target:** `[Insert Device Model & OS Version]`
- **iOS Target:** `[Insert Device Model & OS Version]`
### 3.2 Key Test Accounts
To thoroughly evaluate the M4 acceptance criteria, the following test data and accounts are required:
**Incomplete Profile Staff Account**
*(Used specifically to validate the "Restrict Navigation When Profile Is Incomplete" feature)*
- **Email / Phone:** `[Fill Out Before Testing]`
- **Password / OTP:** `[Fill Out Before Testing]`
- **Configuration:** This account must bypass completing onboarding profile steps to trigger the mandated restriction constraint on the mobile application interface. If needed, use a backend script/dataconnect command to strip the required profile parameters before testing.
**Standard Client Tester Account**
- **Email / Phone:** `[Fill Out Before Testing]`
- **Password / OTP:** `[Fill Out Before Testing]`
---
## 4. Test Execution Results
*Instructions for QA: Mark status as 'Pass', 'Fail', or 'Blocked'. Device & OS Version should be specified if different from above. Add a ticket link if a defect is found.*
### 4.1 Staff App Execution
| Feature | Pass / Fail | Device & OS Version | Defect / Issue Link | Tester Notes & Edge Cases |
| :--- | :--- | :--- | :--- | :--- |
| Google Maps Location | `[ ]` | | | |
| Shift Requirements | `[ ]` | | | |
| Attire Screen & Upload | `[ ]` | | | |
| FAQ Screen | `[ ]` | | | |
| Privacy and Security | `[ ]` | | | |
| Incomplete Profile Restrictions | `[ ]` | | | |
| Preferred Location Edit | `[ ]` | | | |
| Maintain Auth Session | `[ ]` | | | |
| Enable iOS Deployment | `[ ]` | | | |
### 4.2 Client App Execution
| Feature | Pass / Fail | Device & OS Version | Defect / Issue Link | Tester Notes & Edge Cases |
| :--- | :--- | :--- | :--- | :--- |
| Hide Edit Icon (Past Orders) | `[ ]` | | | |
| Rapid Order Creation | `[ ]` | | | |
| Recurring Order | `[ ]` | | | |
| Permanent Order | `[ ]` | | | |
| Update Reorder Modal | `[ ]` | | | |
| Reports w/ AI Insights | `[ ]` | | | |
| Daily Ops Report | `[ ]` | | | |
| Spend Report | `[ ]` | | | |
| Coverage Report | `[ ]` | | | |
| No-Show Report | `[ ]` | | | |
| Performance Report | `[ ]` | | | |
| Display Hub Details | `[ ]` | | | |
| Enable Hub Editing | `[ ]` | | | |
| Maintain Auth Session | `[ ]` | | | |
| Enable iOS Deployment | `[ ]` | | | |
---
## 5. Defect Summary
All bugs identified during E2E testing must map back to an issue raised in the repository's tracker. Ensure all failed/blocked tests above have a corresponding row here.
| Defect ID / Link | Severity | Feature | Description | Status |
| :--- | :--- | :--- | :--- | :--- |
| `[#XXX](url)` | `[High/Med/Low]` | `[Feature]` | `[Brief description of the bug]` | `[Open]` |
| `[#XXX](url)` | `[High/Med/Low]` | `[Feature]` | `[Brief description of the bug]` | `[Open]` |
*(Add rows as necessary during test execution)*
---
## 6. Sign-off & Approvals
By signing below, the internal testing team confirms that the E2E manual testing for M4 has been executed according to the scope defined in this document, that both the Client and Staff apps have been evaluated, and that all discovered defects have been formally documented.

View File

@@ -1,6 +1,6 @@
# --- Mobile App Development --- # --- Mobile App Development ---
.PHONY: mobile-install mobile-info mobile-analyze mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart test-e2e test-e2e-setup test-e2e-client test-e2e-staff .PHONY: mobile-install mobile-info mobile-analyze mobile-client-dev-android mobile-staff-dev-android mobile-client-build mobile-staff-build mobile-hot-reload mobile-hot-restart test-e2e test-e2e-setup test-e2e-client test-e2e-staff test-e2e-staff-auth test-e2e-staff-navigation test-e2e-staff-profile test-e2e-staff-profile-extended test-e2e-staff-shifts test-e2e-staff-compliance test-e2e-staff-home test-e2e-staff-sign-out test-e2e-client-auth test-e2e-client-navigation test-e2e-client-orders test-e2e-client-settings test-e2e-client-sign-out
MOBILE_DIR := apps/mobile MOBILE_DIR := apps/mobile
@@ -72,29 +72,184 @@ mobile-staff-build: dataconnect-generate-sdk
# --- E2E (Maestro) --- # --- E2E (Maestro) ---
# Set env before running: TEST_CLIENT_EMAIL, TEST_CLIENT_PASSWORD, TEST_CLIENT_COMPANY, TEST_STAFF_PHONE, TEST_STAFF_OTP, TEST_STAFF_SIGNUP_PHONE # Set env before running: TEST_CLIENT_EMAIL, TEST_CLIENT_PASSWORD, TEST_CLIENT_COMPANY, TEST_STAFF_PHONE, TEST_STAFF_OTP, TEST_STAFF_SIGNUP_PHONE
# Example: export TEST_CLIENT_EMAIL=legendary@krowd.com TEST_CLIENT_PASSWORD=Demo2026! # Client: testclient@gmail.com / testclient!
# Example: export TEST_STAFF_PHONE=5557654321 TEST_STAFF_OTP=123456 # Staff: TEST_STAFF_PHONE=5555551234 TEST_STAFF_OTP=123123 (+1 555-555-1234 in Firebase)
test-e2e-setup: test-e2e-setup:
@echo "--> Checking Maestro CLI..." @echo "--> Checking Maestro CLI..."
@maestro --version @maestro --version
@echo "--> Maestro OK. Ensure apps are built & installed (see docs/research/maestro-test-run-instructions.md)" @echo "--> Maestro OK. Ensure apps are built & installed (see docs/research/maestro-test-run-instructions.md)"
# Maestro runs flows in parallel by default; --shard-split=1 forces sequential (avoids tcp:7001 closed)
# Note: -s/--shards is deprecated, use --shard-split instead
MAESTRO_SHARDS ?= --shard-split=1
test-e2e: test-e2e-setup test-e2e: test-e2e-setup
@echo "--> Running full E2E suite (Client + Staff auth flows)..." @echo "--> Running full E2E suite (Client + Staff auth + negative auth flows)..."
@maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \ @maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml apps/mobile/apps/client/maestro/auth/sign_in_invalid_password.yaml \
apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \ apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml apps/mobile/apps/staff/maestro/auth/sign_in_invalid_otp.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \ -e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" -e TEST_CLIENT_INVALID_PASSWORD="$${TEST_CLIENT_INVALID_PASSWORD:-wrongpass}" \
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}" -e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" \ -e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}" -e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" \
-e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" -e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" -e TEST_STAFF_INVALID_OTP="$${TEST_STAFF_INVALID_OTP:-000000}" -e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
test-e2e-client: test-e2e-setup test-e2e-client: test-e2e-setup
@echo "--> Running Client E2E (sign_in, sign_up)..." @echo "--> Running Client E2E (sign_in, sign_up)..."
@maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \ @maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \ -e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}" -e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}"
test-e2e-staff: test-e2e-setup test-e2e-staff: test-e2e-setup
@echo "--> Running Staff E2E (sign_in, sign_up)..." @echo "--> Running Staff E2E (sign_in, sign_up)..."
@maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \ @maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" \ -e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" \
-e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}" -e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
# Client E2E by folder (run folder-by-folder)
test-e2e-client-auth: test-e2e-setup
@echo "--> Running Client E2E auth flows (sign_in, sign_up, sign_in_invalid_password)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml apps/mobile/apps/client/maestro/auth/sign_in_invalid_password.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" -e TEST_CLIENT_INVALID_PASSWORD="$${TEST_CLIENT_INVALID_PASSWORD:-wrongpass}" \
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}"
test-e2e-client-navigation: test-e2e-setup
@echo "--> Running Client E2E navigation (sign_in first, then nav flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml \
apps/mobile/apps/client/maestro/navigation/home.yaml \
apps/mobile/apps/client/maestro/navigation/orders.yaml \
apps/mobile/apps/client/maestro/navigation/billing.yaml \
apps/mobile/apps/client/maestro/navigation/coverage.yaml \
apps/mobile/apps/client/maestro/navigation/reports.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}"
test-e2e-client-orders: test-e2e-setup
@echo "--> Running Client E2E orders (sign_in first, then order flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml \
apps/mobile/apps/client/maestro/orders/view_orders.yaml \
apps/mobile/apps/client/maestro/orders/completed_no_edit_icon.yaml \
apps/mobile/apps/client/maestro/orders/create_order_entry.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}"
test-e2e-client-settings: test-e2e-setup
@echo "--> Running Client E2E settings (sign_in first, then settings + edit_profile)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml \
apps/mobile/apps/client/maestro/settings/settings_page.yaml \
apps/mobile/apps/client/maestro/settings/edit_profile.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}"
test-e2e-client-sign-out: test-e2e-setup
@echo "--> Running Client E2E sign out..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml \
apps/mobile/apps/client/maestro/auth/sign_out.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}"
# Client extended (auth + navigation + orders + settings)
test-e2e-client-extended: test-e2e-setup
@echo "--> Running Client E2E extended suite..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \
apps/mobile/apps/client/maestro/navigation/home.yaml apps/mobile/apps/client/maestro/navigation/orders.yaml \
apps/mobile/apps/client/maestro/navigation/billing.yaml apps/mobile/apps/client/maestro/navigation/coverage.yaml \
apps/mobile/apps/client/maestro/navigation/reports.yaml apps/mobile/apps/client/maestro/orders/view_orders.yaml \
apps/mobile/apps/client/maestro/orders/completed_no_edit_icon.yaml apps/mobile/apps/client/maestro/orders/create_order_entry.yaml \
apps/mobile/apps/client/maestro/settings/settings_page.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}"
# Staff E2E by folder (run folder-by-folder)
test-e2e-staff-auth: test-e2e-setup
@echo "--> Running Staff E2E auth flows (sign_in, sign_up, sign_in_invalid_otp)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml apps/mobile/apps/staff/maestro/auth/sign_in_invalid_otp.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" -e TEST_STAFF_INVALID_OTP="$${TEST_STAFF_INVALID_OTP:-000000}" \
-e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
test-e2e-staff-navigation: test-e2e-setup
@echo "--> Running Staff E2E navigation (sign_in first, then navigation flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/navigation/home.yaml \
apps/mobile/apps/staff/maestro/navigation/shifts.yaml \
apps/mobile/apps/staff/maestro/navigation/profile.yaml \
apps/mobile/apps/staff/maestro/navigation/payments.yaml \
apps/mobile/apps/staff/maestro/navigation/clock_in.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-profile: test-e2e-setup
@echo "--> Running Staff E2E profile (sign_in first, then profile flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/profile/personal_info.yaml \
apps/mobile/apps/staff/maestro/profile/documents_list.yaml \
apps/mobile/apps/staff/maestro/profile/certificates_list.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-profile-extended: test-e2e-setup
@echo "--> Running Staff E2E profile extended (personal_info, documents, certificates, timecard, bank_account, faqs, privacy, emergency_contact)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/profile/personal_info.yaml \
apps/mobile/apps/staff/maestro/profile/documents_list.yaml \
apps/mobile/apps/staff/maestro/profile/certificates_list.yaml \
apps/mobile/apps/staff/maestro/profile/time_card.yaml \
apps/mobile/apps/staff/maestro/profile/bank_account.yaml \
apps/mobile/apps/staff/maestro/profile/faqs.yaml \
apps/mobile/apps/staff/maestro/profile/privacy_security.yaml \
apps/mobile/apps/staff/maestro/profile/emergency_contact.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-sign-out: test-e2e-setup
@echo "--> Running Staff E2E sign out..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/auth/sign_out.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-shifts: test-e2e-setup
@echo "--> Running Staff E2E shifts (sign_in first, then shifts flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/shifts/find_shifts.yaml \
apps/mobile/apps/staff/maestro/shifts/incomplete_profile_banner.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-compliance: test-e2e-setup
@echo "--> Running Staff E2E compliance (sign_in first, then compliance flows)..."
@maestro test $(MAESTRO_SHARDS) \
apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml \
apps/mobile/apps/staff/maestro/compliance/certificate_upload_banner.yaml \
apps/mobile/apps/staff/maestro/compliance/attire_upload_banner.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
test-e2e-staff-home: test-e2e-setup
@echo "--> Running Staff E2E home (sign_in first, then home flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
apps/mobile/apps/staff/maestro/home/benefits.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}"
# Staff extended (auth + navigation + profile + compliance + shifts + benefits)
test-e2e-staff-extended: test-e2e-setup
@echo "--> Running Staff E2E extended suite..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
apps/mobile/apps/staff/maestro/navigation/home.yaml apps/mobile/apps/staff/maestro/navigation/shifts.yaml \
apps/mobile/apps/staff/maestro/navigation/profile.yaml apps/mobile/apps/staff/maestro/navigation/payments.yaml \
apps/mobile/apps/staff/maestro/navigation/clock_in.yaml apps/mobile/apps/staff/maestro/profile/personal_info.yaml \
apps/mobile/apps/staff/maestro/profile/documents_list.yaml apps/mobile/apps/staff/maestro/profile/certificates_list.yaml \
apps/mobile/apps/staff/maestro/shifts/find_shifts.yaml apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml \
apps/mobile/apps/staff/maestro/compliance/certificate_upload_banner.yaml apps/mobile/apps/staff/maestro/compliance/attire_upload_banner.yaml \
apps/mobile/apps/staff/maestro/shifts/incomplete_profile_banner.yaml apps/mobile/apps/staff/maestro/home/benefits.yaml \
-e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" -e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" \
-e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"
# Extended E2E: auth + navigation + compliance + feature flows (both apps)
test-e2e-extended: test-e2e-setup
@echo "--> Running extended E2E suite (auth, navigation, compliance, feature flows)..."
@maestro test $(MAESTRO_SHARDS) apps/mobile/apps/client/maestro/auth/sign_in.yaml apps/mobile/apps/client/maestro/auth/sign_up.yaml \
apps/mobile/apps/client/maestro/navigation/home.yaml apps/mobile/apps/client/maestro/navigation/orders.yaml \
apps/mobile/apps/client/maestro/navigation/billing.yaml apps/mobile/apps/client/maestro/navigation/coverage.yaml \
apps/mobile/apps/client/maestro/navigation/reports.yaml apps/mobile/apps/client/maestro/orders/view_orders.yaml \
apps/mobile/apps/client/maestro/orders/completed_no_edit_icon.yaml apps/mobile/apps/client/maestro/orders/create_order_entry.yaml \
apps/mobile/apps/client/maestro/settings/settings_page.yaml \
apps/mobile/apps/staff/maestro/auth/sign_in.yaml apps/mobile/apps/staff/maestro/auth/sign_up.yaml \
apps/mobile/apps/staff/maestro/navigation/home.yaml apps/mobile/apps/staff/maestro/navigation/shifts.yaml \
apps/mobile/apps/staff/maestro/navigation/profile.yaml apps/mobile/apps/staff/maestro/navigation/payments.yaml \
apps/mobile/apps/staff/maestro/navigation/clock_in.yaml apps/mobile/apps/staff/maestro/profile/personal_info.yaml \
apps/mobile/apps/staff/maestro/profile/documents_list.yaml apps/mobile/apps/staff/maestro/profile/certificates_list.yaml \
apps/mobile/apps/staff/maestro/shifts/find_shifts.yaml apps/mobile/apps/staff/maestro/compliance/document_upload_banner.yaml \
apps/mobile/apps/staff/maestro/compliance/certificate_upload_banner.yaml apps/mobile/apps/staff/maestro/compliance/attire_upload_banner.yaml \
apps/mobile/apps/staff/maestro/shifts/incomplete_profile_banner.yaml apps/mobile/apps/staff/maestro/home/benefits.yaml \
-e TEST_CLIENT_EMAIL="$${TEST_CLIENT_EMAIL}" -e TEST_CLIENT_PASSWORD="$${TEST_CLIENT_PASSWORD}" \
-e TEST_CLIENT_COMPANY="$${TEST_CLIENT_COMPANY}" -e TEST_STAFF_PHONE="$${TEST_STAFF_PHONE}" \
-e TEST_STAFF_OTP="$${TEST_STAFF_OTP}" -e TEST_STAFF_SIGNUP_PHONE="$${TEST_STAFF_SIGNUP_PHONE}"