Compare commits

...

30 Commits

Author SHA1 Message Date
39263a4af5 chore: fix 273+ analysis issues and repair corrupted core files 2026-03-20 21:05:23 +05:30
d3159bc2ae Merge branch 'origin/dev' into feature/session-persistence-new 2026-03-20 12:44:25 +05:30
Achintha Isuru
0735e3513c Merge pull request #673 from Oloodi/595-sred-research-implement-cross-platform-nfc-clocking-interface-fe
feat: Integrate staff order booking, UTC timestamp handling, and shift UI improvements
2026-03-20 00:01:35 -04:00
Achintha Isuru
b10ef57d37 feat: Add hourly rate field to order position arguments and update related blocs 2026-03-19 23:59:00 -04:00
Achintha Isuru
1e4c8982a5 feat: Add additional fields to OrderItem and update cost calculation in ViewOrderCard 2026-03-19 23:55:57 -04:00
Achintha Isuru
4cd83a9281 feat: Implement UTC conversion for order date and time serialization in order use cases 2026-03-19 23:34:29 -04:00
Achintha Isuru
207831eb3e feat: Adjust layout in ShiftCardBody to improve icon and title alignment 2026-03-19 21:15:52 -04:00
Achintha Isuru
0ff2949c1e feat: Refactor ShiftCard components to include client name and improve layout consistency 2026-03-19 21:13:35 -04:00
Achintha Isuru
591b5d7b88 feat: Enhance CancelledShift and PendingAssignment models with additional fields for client and pay details 2026-03-19 16:44:54 -04:00
Achintha Isuru
a544b051cc feat: Update shift card styles and remove cancellation reason display 2026-03-19 16:40:07 -04:00
230942e776 dev merge 2026-03-19 15:25:37 +05:30
e671827dc4 Merge dev into feature branch 2026-03-19 13:16:04 +05:30
67df7957c6 Merge branch 'dev' of https://github.com/Oloodi/krow-workforce into feature/session-persistence-new 2026-03-19 10:27:42 +05:30
aef4491f97 Merge 592-migrate-frontend-applications-to-v2-backend-and-database into feature/session-persistence-new 2026-03-18 12:51:23 +05:30
ca927fd05e Merge remote-tracking branch 'origin/dev' into feature/session-persistence-new 2026-03-18 12:28:39 +05:30
9a44eed2a6 Merge remote-tracking branch 'origin/dev' into 592-migrate-frontend-applications-to-v2-backend-and-database 2026-03-18 11:53:58 +05:30
2b498607d1 Merge branch 'dev' into feature/session-persistence-new 2026-03-17 18:40:22 +05:30
68b0055cfe comprehensive cases 2026-03-17 15:24:36 +05:30
e3d8d30b1b Merge branch 'dev' into feature/session-persistence-new 2026-03-11 10:56:48 +05:30
9642eb0268 ci: resolve merge conflicts and enforce manual trigger policy across all workflows 2026-03-11 10:55:34 +05:30
41a222ea11 fix 2026-03-11 10:46:37 +05:30
b9574eb96a #636 2026-03-10 14:59:31 +05:30
52bb1d1af4 maestro improvise 2026-03-09 22:10:40 +05:30
91cedf54c9 testcases 2026-03-06 17:19:12 +05:30
b43e28e09d Implement Maestro E2E Tests for Mobile Happy Paths & Document #572 2026-03-05 17:50:46 +05:30
9c07bd7e0e new testcases 2026-03-04 19:24:13 +05:30
277eff3da0 Merge dev into feature/session-persistence-new 2026-03-04 10:19:28 +05:30
7ce837b49a maestra new cases 2026-03-03 17:07:41 +05:30
e7e11771b3 Merge branch 'dev' of https://github.com/Oloodi/krow-workforce into feature/session-persistence-new 2026-03-03 11:07:15 +05:30
c0a69707e6 maestro cases 2026-03-02 19:18:35 +05:30
217 changed files with 6925 additions and 1026 deletions

View File

@@ -1,6 +1,7 @@
name: Backend Foundation
on:
workflow_dispatch: # Manual trigger only — auto-triggers disabled (free plan)
workflow_dispatch:
jobs:

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

@@ -0,0 +1,67 @@
# 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: # Manual trigger only — auto-triggers disabled (free plan)
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

@@ -1,6 +1,7 @@
name: Mobile CI
on:
workflow_dispatch: # Manual trigger only — auto-triggers disabled (free plan)
workflow_dispatch:
jobs:
@@ -236,4 +237,3 @@ jobs:
else
echo "✅ Pipeline PASSED"
fi

View File

@@ -1,7 +1,7 @@
name: Web Quality
on:
workflow_dispatch:
workflow_dispatch: # Manual trigger only — auto-triggers disabled (free plan)
jobs:
web-quality:

View File

@@ -25,7 +25,7 @@ void main() async {
);
// Register global BLoC observer for centralized error logging
Bloc.observer = CoreBlocObserver(
Bloc.observer = const CoreBlocObserver(
logEvents: true,
logStateChanges: false, // Set to true for verbose debugging
);

View File

@@ -1,7 +1,7 @@
# Maestro Integration Tests — Client App
Auth flows for the KROW Client app.
See [docs/research/flutter-testing-tools.md](/docs/research/flutter-testing-tools.md) and [maestro-test-run-instructions.md](/docs/research/maestro-test-run-instructions.md).
Auth flows and E2E happy paths for the KROW Client app.
See [docs/research/flutter-testing-tools.md](/docs/research/flutter-testing-tools.md), [maestro-test-run-instructions.md](/docs/research/maestro-test-run-instructions.md), and [docs/testing/maestro-e2e-happy-paths.md](/docs/testing/maestro-e2e-happy-paths.md) (#572).
## Structure
@@ -10,6 +10,43 @@ maestro/
auth/
sign_in.yaml
sign_up.yaml
sign_out.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
create_order_rapid.yaml
rapid_to_one_time_draft_submit_e2e.yaml
create_order_one_time_e2e.yaml
edit_active_order_e2e.yaml
edit_active_order_verify_updated_e2e.yaml
hubs/
create_hub_e2e.yaml
manage_hubs_from_settings.yaml
edit_hub_e2e.yaml
delete_hub_e2e.yaml
billing/
billing_overview.yaml
invoice_details_smoke.yaml
invoice_approval_e2e.yaml
reports/
reports_dashboard.yaml
spend_report_export_smoke.yaml
home/
home_dashboard_widgets.yaml
tab_bar_roundtrip.yaml
settings/
settings_page.yaml
edit_profile.yaml
edit_profile_save_e2e.yaml
logout_flow.yaml
```
## Credentials (env, never hardcoded)
@@ -19,11 +56,22 @@ maestro/
| sign_in | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD` |
| sign_up | `TEST_CLIENT_EMAIL`, `TEST_CLIENT_PASSWORD`, `TEST_CLIENT_COMPANY` |
**Sign-in:** testclient@gmail.com / testclient!
## Run
```bash
# Via Makefile (export vars first)
make test-e2e-client
make test-e2e-client # Auth only
make test-e2e-client-extended # Auth + nav + orders + settings
make test-e2e-client-happy-path # Auth + hubs + create order E2E + billing + reports + logout (#572)
make test-e2e-client-smoke # Deterministic smoke suite
make test-e2e-client-hubs-e2e # Hubs manage + edit + delete
make test-e2e-client-billing-smoke # Billing overview + invoice details/empty state
make test-e2e-client-reports-smoke # Reports dashboard + export placeholder
make test-e2e-client-settings-e2e # Settings + edit profile save + logout
make test-e2e-client-orders-smoke # Orders smoke (RAPID → One-Time draft)
make test-e2e-client-orders-data # Orders data-dependent (edit active order)
# Direct
maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml \

View File

@@ -0,0 +1,56 @@
# Client App — E2E: Session Persistence Across Relaunch
# Purpose:
# - Log in via sign_in.yaml
# - Stop the app
# - Relaunch and verify user is still logged in (bypass login screen)
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/auth/session_persistence.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
# We rely on sign_in.yaml being run before this to establish a session.
- launchApp
- extendedWaitUntil:
visible:
id: "client_nav_home"
timeout: 60000
- stopApp
- launchApp:
clearState: false
- extendedWaitUntil:
visible:
id: "client_nav_home"
timeout: 30000
# Cleanup: Log out
- tapOn:
id: "header_settings_icon"
optional: true
- tapOn:
point: "92%,10%"
optional: true
- extendedWaitUntil:
visible: "Log Out"
timeout: 15000
- tapOn: "Log Out"
- tapOn:
text: "Log Out"
optional: true
- extendedWaitUntil:
visible:
id: "client_landing_sign_in"
timeout: 30000
- assertVisible:
id: "client_landing_sign_in"

View File

@@ -0,0 +1,47 @@
# Client App — Sign In flow
appId: com.krowwithus.client
env:
EMAIL: ${TEST_CLIENT_EMAIL}
PASSWORD: ${TEST_CLIENT_PASSWORD}
---
- launchApp:
clearState: true
- extendedWaitUntil:
visible:
id: "client_landing_sign_in"
timeout: 60000
- tapOn:
id: "client_landing_sign_in"
- extendedWaitUntil:
visible:
id: "sign_in_email"
timeout: 20000
- tapOn:
id: "sign_in_email"
- inputText: ${EMAIL}
- stopApp: # Small trick to hide keyboard on some emulators
optional: true
- launchApp: # Resume where we were
clearState: false
- tapOn:
id: "sign_in_password"
- inputText: ${PASSWORD}
- hideKeyboard
- tapOn:
id: "sign_in_submit_button"
optional: true
- tapOn:
text: "(?i)Sign In"
- extendedWaitUntil:
visible:
id: "client_nav_home"
timeout: 45000
- assertVisible:
id: "client_nav_home"

View File

@@ -0,0 +1,33 @@
# 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
- extendedWaitUntil:
visible:
id: "client_nav_home"
timeout: 30000
# Open Settings
- tapOn:
id: "header_settings_icon"
optional: true
- tapOn:
point: "92%,10%"
optional: true
- extendedWaitUntil:
visible: "Log Out"
timeout: 20000
- tapOn: "Log Out"
- assertVisible: "Are you sure you want to log out?"
- tapOn: "Log Out"
- extendedWaitUntil:
visible:
id: "client_landing_sign_in"
timeout: 30000

View File

@@ -8,10 +8,17 @@ env:
PASSWORD: ${TEST_CLIENT_PASSWORD}
COMPANY: ${TEST_CLIENT_COMPANY}
---
- launchApp
- assertVisible: "Create Account"
# Always start from a clean auth state so the Get Started
# screen (with "Create Account") is shown even if already signed in.
- launchApp:
clearState: true
- extendedWaitUntil:
visible: "Create Account"
timeout: 10000
- tapOn: "Create Account"
- assertVisible: "Company"
- assertVisible: "(?i)Company Name"
- tapOn:
id: sign_up_company
- inputText: ${COMPANY}
@@ -26,3 +33,6 @@ env:
- inputText: ${PASSWORD}
- tapOn: "Create Account"
- assertVisible: "Home"

View File

@@ -0,0 +1,26 @@
# 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

@@ -1,22 +0,0 @@
# Client App — Sign In flow
# Credentials via env: TEST_CLIENT_EMAIL, TEST_CLIENT_PASSWORD
# Run: maestro test apps/mobile/apps/client/maestro/auth/sign_in.yaml -e TEST_CLIENT_EMAIL=... -e TEST_CLIENT_PASSWORD=...
# Or: export MAESTRO_TEST_CLIENT_EMAIL / MAESTRO_TEST_CLIENT_PASSWORD (Maestro auto-reads MAESTRO_*)
appId: com.krowwithus.client
env:
EMAIL: ${TEST_CLIENT_EMAIL}
PASSWORD: ${TEST_CLIENT_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"
- assertVisible: "Home"

View File

@@ -0,0 +1,50 @@
# Client App — Billing: Empty state smoke
# Purpose:
# - Opens Billing tab
# - Verifies the screen loads correctly with no invoices
# - Checks that "Current Period" header is always visible
# - Verifies empty-state copy appears when no invoices exist
# - Deterministic: always passes whether or not invoices exist
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/billing/billing_empty_state.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Billing"
- extendedWaitUntil:
visible: "(?i)Current Period"
timeout: 20000
# Entry assertion — billing is loaded
- assertVisible: "(?i)Current Period"
# Awaiting Approval section
- assertVisible:
text: "(?i)Awaiting Approval"
optional: true
# Case A: invoices exist → invoice amounts/dates are visible
- assertVisible:
text: "(?i).*(Invoice Ready|Review & Approve|Approved).*"
optional: true
# Case B: no invoices → empty-state message visible
- assertVisible:
text: "(?i).*(No invoices|nothing|all clear|no pending).*"
optional: true
# Exit assertion — still in Billing context
- assertVisible: "(?i)Current Period"

View File

@@ -0,0 +1,20 @@
# Client App — Billing overview
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Billing"
timeout: 30000
- tapOn: "(?i)Billing"
- extendedWaitUntil:
visible: "(?i)Current Period"
timeout: 20000
- assertVisible: "(?i)Current Period"

View File

@@ -0,0 +1,62 @@
# Client App — E2E: Comprehensive Invoice Approval
# Purpose:
# - Navigates to Billing -> Awaiting Approval.
# - Captures the Invoice ID or date/amount (conceptually).
# - Approves the invoice.
# - Verifies success message.
# - Navigates to the "Approved" tab.
# - Verifies the invoice is now listed in the history.
#
# Run:
# maestro test \
11: # apps/mobile/apps/client/maestro/auth/happy_path/sign_in.yaml \
12: # apps/mobile/apps/client/maestro/billing/happy_path/comprehensive_billing_flow.yaml \
13: # -e TEST_CLIENT_EMAIL=... \
14: # -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- tapOn: "Home"
- tapOn: "Billing"
- extendedWaitUntil:
visible: "Awaiting Approval"
timeout: 10000
# 1. Enter the approval flow
- tapOn: "Awaiting Approval"
- extendedWaitUntil:
visible: "Review & Approve"
timeout: 10000
# 2. Approve the first invoice
- tapOn: "Review & Approve"
- extendedWaitUntil:
visible: "Approve"
timeout: 10000
- tapOn: "Approve"
# 3. Verify success message
- extendedWaitUntil:
visible: "Invoice approved and payment initiated"
timeout: 15000
# 4. Deep Verification: check the 'Approved' tab
- tapOn: "Approved"
- extendedWaitUntil:
visible: "Invoice History"
timeout: 10000
# Ensure we see a 'View Receipt' or 'Approved' status instead of 'Review'
- assertVisible: "View Receipt"
- assertNotVisible: "Review & Approve"
# Back to Home
- tapOn: "Home"

View File

@@ -0,0 +1,68 @@
# Client App — E2E: Invoice Approval Flow
# Flow:
# - Home → Billing Tab
# - Navigate to "Awaiting Approval" (Pending Invoices)
# - Review the first pending invoice
# - Click Approve & verify success
#
# Prerequisite:
# User must have at least one invoice in the "Awaiting Approval" state (pending validation/timesheets).
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/billing/invoice_approval_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- assertVisible: "Home"
- tapOn: "Home"
- waitForAnimationToEnd:
timeout: 3000
- tapOn: "Billing"
- extendedWaitUntil:
visible: "Awaiting Approval"
timeout: 10000
# Open the Pending Invoices List
- tapOn: "Awaiting Approval"
- extendedWaitUntil:
visible: "Review & Approve"
timeout: 10000
# Tap the first invoice waiting for approval
- tapOn: "Review & Approve"
- extendedWaitUntil:
visible: "Approve"
timeout: 10000
# Tap the primary approve action in CompletionReviewActions
- tapOn: "Approve"
# Validate it returns automatically and shows the success snackbar banner
- extendedWaitUntil:
visible: "Invoice approved and payment initiated"
timeout: 15000
# Post-Action State Verification:
# After approval, we confirm we are back on the 'Invoices' screen and the count has updated (or the item is gone)
- extendedWaitUntil:
visible: "Awaiting Approval"
timeout: 10000
# Optionally, verify the 'Review & Approve' button for that specific invoice is gone.
- assertNotVisible:
text: "Review & Approve"
optional: true

View File

@@ -0,0 +1,50 @@
# Client App — Billing: open invoice details (smoke)
# Purpose:
# - Validates Billing tab loads
# - If invoices exist, opens an invoice and verifies invoice actions (e.g. Download PDF)
# - If no invoices exist, verifies the empty-state copy is shown
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/billing/invoice_details_smoke.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Billing"
- extendedWaitUntil:
visible: "Current Period"
timeout: 10000
# If there are invoices ready, open one and verify details actions (optional)
- tapOn:
text: "Invoice Ready"
optional: true
- extendedWaitUntil:
visible: "Download Invoice PDF"
timeout: 10000
optional: true
- assertVisible:
text: "Download Invoice PDF"
optional: true
# Otherwise, validate deterministic empty states (still a valid smoke outcome)
- assertVisible:
text: "No invoices ready yet"
optional: true
- assertVisible:
text: "No Invoices for the selected period"
optional: true
# Always end by asserting we are still in Billing context
- assertVisible: "Current Period"

View File

@@ -0,0 +1,35 @@
# Client App — Home dashboard widgets & quick actions
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
- extendedWaitUntil:
visible: "(?i)Create Order.*Schedule.*"
timeout: 30000
- assertVisible: "(?i)RAPID.*Urgent.*"
- assertVisible: "(?i)Create Order.*Schedule.*"
- tapOn: "(?i)Create Order.*Schedule.*"
- extendedWaitUntil:
visible: "(?i)Create Order"
timeout: 15000
- assertVisible: "(?i)ORDER TYPE"
- assertVisible: "(?i)RAPID.*URGENT.*"
- assertVisible: "(?i)One-Time.*Single Event.*"

View File

@@ -0,0 +1,43 @@
# Client App — Tab bar roundtrip smoke (Home ↔ Orders/Billing/Coverage/Reports)
# Goal: ensure tab navigation works and returns to Home without relying on dynamic data.
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/home/tab_bar_roundtrip.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
# Start from Home (stabilizes header + tab bar)
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Orders
- tapOn: "Orders"
- assertVisible: "Orders"
# Billing
- tapOn: "Billing"
- assertVisible: "Billing"
# Coverage
- tapOn: "Coverage"
- assertVisible: "Coverage"
# Reports
- tapOn: "Reports"
- assertVisible: "Reports"
# Back to Home
- tapOn: "Home"
- assertVisible: "Welcome back"

View File

@@ -0,0 +1,61 @@
# Client App — Hubs: Empty state smoke
# Purpose:
# - Navigates to Settings → Clock-In Hubs
# - Verifies the hubs list loads correctly
# - If no hubs exist, verifies an empty-state message is shown
# - If hubs exist, verifies the list renders correctly (passes either way)
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/hubs/hub_empty_state.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "(?i)Clock-In Hubs"
timeout: 10000
- tapOn: "(?i)Clock-In Hubs"
- extendedWaitUntil:
visible: "(?i).*(Hubs|Manage clock-in locations).*"
timeout: 15000
# Entry assertion — hubs page loaded
- assertVisible: "(?i).*(Hubs|Manage clock-in locations).*"
# "Add Hub" button should always be present
- assertVisible: "(?i)Add Hub"
# Case A: hubs exist — list is visible
- assertVisible:
text: "(?i).*(Hub|Location|Address).*"
optional: true
# Case B: no hubs — empty state copy should be shown
- assertVisible:
text: "(?i).*(No hubs|No locations|Add your first|get started|no clock-in).*"
optional: true
# Exit assertion — still on the Hubs screen
- assertVisible: "(?i)Add Hub"

View File

@@ -0,0 +1,74 @@
# Client App — E2E: Create Hub and verify it appears in list
# Flow:
# - Home → Settings (gear) → Clock-In Hubs
# - Add Hub → fill form → Create Hub
# - Assert success message and newly created hub card is visible
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/hubs/create_hub_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Clock-In Hubs"
timeout: 10000
- tapOn: "Clock-In Hubs"
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 15000
- tapOn: "Add Hub"
- extendedWaitUntil:
visible: "Add New Hub"
timeout: 10000
# Fill required fields
- tapOn: "Hub Name *"
- inputText: "E2E Hub Automation"
- hideKeyboard
# Address field uses an autocomplete widget; focus it via a safe coordinate
# within the form, then type an address.
- tapOn:
point: "50%,60%"
- inputText: "345 Park Avenue, New York, NY"
- hideKeyboard
- tapOn:
point: "50%,88%"
# For now we assert that tapping Create returns us to the Hubs list
# (header still visible), which exercises the full Add Hub form flow.
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 20000
# Post-Action State Verification:
# Verify the newly created hub name is in the list
- scrollUntilVisible:
element: "E2E Hub Automation"
visibilityPercentage: 50
timeout: 10000
- assertVisible: "E2E Hub Automation"

View File

@@ -0,0 +1,84 @@
# Client App — E2E: Delete Hub (create → details → delete)
# Purpose:
# - Creates a hub
# - Opens Hub Details and deletes it
# - Verifies deletion confirmation and success
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/hubs/delete_hub_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Clock-In Hubs"
timeout: 10000
- tapOn: "Clock-In Hubs"
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 15000
# Create a hub for this test run (deterministic)
- tapOn: "Add Hub"
- extendedWaitUntil:
visible: "Add New Hub"
timeout: 10000
- tapOn: "Hub Name *"
- inputText: "E2E Hub Delete"
- hideKeyboard
- tapOn:
point: "50%,60%"
- inputText: "345 Park Avenue, New York, NY"
- hideKeyboard
- tapOn:
point: "50%,88%"
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 20000
- scrollUntilVisible:
element: "E2E Hub Delete"
visibilityPercentage: 50
timeout: 15000
- tapOn: "E2E Hub Delete"
- extendedWaitUntil:
visible: "E2E Hub Delete"
timeout: 10000
# Delete action is the destructive bottom button on details page
- assertVisible: "Delete"
- tapOn: "Delete"
- extendedWaitUntil:
visible: "Confirm Hub Deletion"
timeout: 10000
- tapOn: "Delete"
- extendedWaitUntil:
visible: "Hub deleted successfully"
timeout: 15000

View File

@@ -0,0 +1,90 @@
# Client App — E2E: Edit Hub (create → details → edit)
# Purpose:
# - Ensures hubs list is reachable from Settings
# - Creates a hub (if needed for the test run)
# - Opens Hub Details, edits hub name, verifies success
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/hubs/edit_hub_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Clock-In Hubs"
timeout: 10000
- tapOn: "Clock-In Hubs"
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 15000
# Create a hub for this test run (deterministic)
- tapOn: "Add Hub"
- extendedWaitUntil:
visible: "Add New Hub"
timeout: 10000
- tapOn: "Hub Name *"
- inputText: "E2E Hub Edit"
- hideKeyboard
# Address field uses an autocomplete widget; focus it via a safe coordinate
- tapOn:
point: "50%,60%"
- inputText: "345 Park Avenue, New York, NY"
- hideKeyboard
- tapOn:
point: "50%,88%"
- extendedWaitUntil:
visible: "Hubs\nManage clock-in locations"
timeout: 20000
- scrollUntilVisible:
element: "E2E Hub Edit"
visibilityPercentage: 50
timeout: 15000
- tapOn: "E2E Hub Edit"
# Hub details page uses the hub name as the app bar title
- extendedWaitUntil:
visible: "E2E Hub Edit"
timeout: 10000
- assertVisible: "Edit Hub"
- tapOn: "Edit Hub"
- extendedWaitUntil:
visible: "Edit Hub"
timeout: 10000
# Append suffix to avoid needing to clear the field
- tapOn: "Hub Name *"
- inputText: " Updated"
- hideKeyboard
- tapOn: "Save Changes"
- extendedWaitUntil:
visible: "Hub updated successfully"
timeout: 15000

View File

@@ -0,0 +1,54 @@
# Client App — E2E: Hub Management Lifecycle
# Purpose:
# - Create a new Hub.
# - Find and Edit the Hub.
# - Delete the Hub.
# - Verify it is gone from the list.
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- tapOn: "Home"
- tapOn: "Settings" # Assuming Hubs are managed via Settings or a Hubs tab
# 1. Navigate to Hubs
- tapOn: "Manage Hubs"
# 2. Create Hub
- tapOn: "Add New Hub" # Or the '+' button
- tapOn: "HUB NAME"
- inputText: "Maestro Test Hub"
- tapOn: "ADDRESS"
- inputText: "123 Test Street, NYC"
- hideKeyboard
- tapOn: "Save Hub"
- extendedWaitUntil:
visible: "Hub created successfully"
timeout: 10000
# 3. Edit Hub
- tapOn: "Maestro Test Hub"
- tapOn: "HUB NAME"
- inputText: "Updated Maestro Hub"
- hideKeyboard
- tapOn: "Save Hub"
- extendedWaitUntil:
visible: "Hub updated successfully"
timeout: 10000
# 4. Delete Hub
- tapOn: "Updated Maestro Hub"
- tapOn: "Delete Hub"
- tapOn: "DELETE" # Confirmation
- extendedWaitUntil:
visible: "Hub deleted"
timeout: 10000
# 5. Verify gone
- assertNotVisible: "Updated Maestro Hub"

View File

@@ -0,0 +1,49 @@
# Client App — Manage Hubs via Settings quick link
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
# Open Settings via stable semantic ID
- tapOn:
id: "client_home_settings"
- extendedWaitUntil:
visible: "(?i).*Quick Links.*"
timeout: 20000
- assertVisible: "(?i)Profile"
- assertVisible: "(?i)Clock-In Hubs"
- tapOn: "(?i)Clock-In Hubs"
- extendedWaitUntil:
visible: "(?i).*Hubs.*"
timeout: 15000
- assertVisible: "(?i).*Add Hub.*"
- tapOn: "(?i)Add Hub"
- extendedWaitUntil:
visible: "(?i)Add New Hub"
timeout: 10000
- tapOn: "(?i)Create Hub"
- extendedWaitUntil:
visible: "(?i).*required.*"
timeout: 5000

View File

@@ -0,0 +1,20 @@
# Client App — Billing tab navigation
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Billing"
timeout: 30000
- tapOn: "(?i)Billing"
- extendedWaitUntil:
visible: "(?i).*(Current Period|Billing).*"
timeout: 20000
- assertVisible: "(?i).*(Current Period|Billing).*"

View File

@@ -0,0 +1,20 @@
# Client App — Coverage tab navigation
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Coverage"
timeout: 30000
- tapOn: "(?i)Coverage"
- extendedWaitUntil:
visible: "(?i).*(Today.*Status|Daily Coverage|Unfilled|Checked In).*"
timeout: 20000
- assertVisible: "(?i).*(Today.*Status|Daily Coverage|Unfilled|Checked In).*"

View File

@@ -0,0 +1,20 @@
# Client App — Home tab navigation
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Create Order|RAPID).*"
timeout: 20000
- assertVisible: "(?i).*(Welcome back|Create Order|RAPID).*"

View File

@@ -0,0 +1,20 @@
# Client App — Orders tab navigation
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 30000
- tapOn: "(?i)Orders"
- extendedWaitUntil:
visible: "(?i).*(Up Next|Post an Order).*"
timeout: 20000
- assertVisible: "(?i).*(Up Next|Post an Order).*"

View File

@@ -0,0 +1,20 @@
# Client App — Reports tab navigation
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Reports"
timeout: 30000
- tapOn: "(?i)Reports"
- extendedWaitUntil:
visible: "(?i)Workforce Control Tower"
timeout: 20000
- assertVisible: "(?i)Workforce Control Tower"

View File

@@ -0,0 +1,85 @@
# DEBUG: Staff Search Only
# Tests ONLY the Staff shift search flow in isolation.
# Pass a known order name to verify the Staff app can find it.
#
# Usage:
# maestro test staff_search_only.yaml -e TEST_STAFF_PHONE="5557654321" -e TEST_STAFF_OTP="123456" -e TEST_ORDER_NAME="E2E-XXXXX"
appId: com.krowwithus.staff
---
- launchApp:
clearState: false
# Wait for app to fully render
- waitForAnimationToEnd:
timeout: 15000
# Sign in if needed
- runFlow:
when:
visible: "Log In"
file: ../../../../staff/maestro/auth/happy_path/sign_in.yaml
env:
TEST_STAFF_PHONE: ${TEST_STAFF_PHONE}
TEST_STAFF_OTP: ${TEST_STAFF_OTP}
# Navigate to Shifts
- tapOn:
id: "nav_shifts"
# Pull to refresh (force reload from backend)
- swipe:
start: 50%, 30%
end: 50%, 80%
- waitForAnimationToEnd:
timeout: 8000
# 3. Aggressive Retry Loop: Search, if not found, refresh and try again.
# This handles slow backend indexing by periodically pulling fresh data.
- repeat:
times: 5
commands:
- runFlow:
when:
notVisible:
id: "shft_card_logo_placeholder"
index: 0
commands:
# 1. Clear current search
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" # Strategic clear if "" fails
- inputText: ""
- tapOn: "Shifts" # Dismiss keyboard
# 2. Pull-to-refresh
- swipe:
start: 50%, 60%
end: 50%, 90%
- waitForAnimationToEnd:
timeout: 15000
# 3. Search again
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "${TEST_ORDER_NAME}\n"
- tapOn: "Shifts" # Dismiss keyboard
- waitForAnimationToEnd:
timeout: 5000
# Final check with a long timeout
- extendedWaitUntil:
visible:
id: "shft_card_logo_placeholder"
index: 0
timeout: 30000
- assertVisible:
id: "shft_card_logo_placeholder"
index: 0

View File

@@ -0,0 +1,26 @@
# Client App — Completed tab: edit icon hidden for past/completed orders (#492)
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 30000
- tapOn: "(?i)Orders"
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 15000
- extendedWaitUntil:
visible: "(?i)Completed.*"
timeout: 10000
- tapOn: "(?i)Completed.*"
- assertVisible: "(?i)Completed.*"

View File

@@ -0,0 +1,60 @@
# Client App — Orders: Empty state smoke
# Purpose:
# - Opens the Orders tab
# - Verifies filter tabs (Up Next, Active, Completed) are always present
# - If no orders exist in a tab, verifies empty-state copy is shown (not a crash)
# - Taps each tab and asserts either a list or empty-state is visible
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/orders_empty_state.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Orders"
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 15000
# Verify filter tabs are always present
- assertVisible: "(?i)Up Next.*"
- assertVisible: "(?i)Active.*"
- assertVisible: "(?i)Completed.*"
# --- Up Next tab ---
- tapOn: "(?i)Up Next.*"
- waitForAnimationToEnd:
timeout: 2000
- assertVisible:
text: "(?i).*(No orders|No upcoming|Nothing here|order).*"
optional: true
# --- Active tab ---
- tapOn: "(?i)Active.*"
- waitForAnimationToEnd:
timeout: 2000
- assertVisible:
text: "(?i).*(No active|No orders|Nothing here|order).*"
optional: true
# --- Completed tab ---
- tapOn: "(?i)Completed.*"
- waitForAnimationToEnd:
timeout: 2000
- assertVisible:
text: "(?i).*(No completed|No orders|Nothing here|order).*"
optional: true
# Exit assertion — we are still on the Orders screen
- assertVisible: "(?i).*(Up Next|Active|Completed).*"

View File

@@ -0,0 +1,118 @@
# Client App — E2E: Comprehensive Order Lifecycle (Create -> Verify in List -> View Details)
# Purpose:
# - Generates a unique order name via JS.
# - Creates a One-Time order.
# - Verifies the success confirmation.
# - Navigates to the Orders list.
# - Uses scrollUntilVisible to find the unique order.
# - Taps the order and verifies details on the summary page.
#
# Run:
# maestro test \
11: # apps/mobile/apps/client/maestro/auth/happy_path/sign_in.yaml \
12: # apps/mobile/apps/client/maestro/orders/happy_path/comprehensive_order_lifecycle.yaml \
13: # -e TEST_CLIENT_EMAIL=... \
14: # -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
# 1. Generate unique order name
- runScript: ../../scripts/generate_order_name.js
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# 2. Start Create Order Flow
- tapOn: "Create Order\\nSchedule shifts"
- extendedWaitUntil:
visible: "One-Time\\nSingle Event or Shift Request"
timeout: 10000
- tapOn: "One-Time\\nSingle Event or Shift Request"
- extendedWaitUntil:
visible: "One-Time Order"
timeout: 10000
# 3. Fill Order Details
- tapOn: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
- inputText: ${output.orderName}
- hideKeyboard
# Select Role
- tapOn: ".*(Select Role|SELECT ROLE).*"
- tapOn: ".*\\$.*" # Just tap the first role from the dropdown
# Set Start Time (Required for valid form)
- scrollUntilVisible:
element: "--:--"
direction: DOWN
timeout: 5000
optional: true
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
# Set End Time (Required for valid form)
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
# Scroll if needed to see the Create Order button
- scrollUntilVisible:
element: "(?i)Create Order"
direction: DOWN
timeout: 5000
- tapOn: "(?i)Create Order"
# 4. Verify Success Confirmation
- extendedWaitUntil:
visible: "(?i)Order Created.*"
timeout: 30000
- assertVisible: "(?i)Order Created.*"
- assertVisible: ${output.orderName}
# 5. Navigate to Orders List and Verify Persistence
- tapOn: "Back to Orders"
- extendedWaitUntil:
visible: "Orders"
timeout: 15000
# Select the "Up Next" or "Active" tab if not already selected (assuming default is correct)
- tapOn: "Up Next"
# Scroll until we find our unique order name
- scrollUntilVisible:
element: ${output.orderName}
direction: DOWN
timeout: 20000
- assertVisible: ${output.orderName}
# 6. View Details and Final Verification
- tapOn: ${output.orderName}
- extendedWaitUntil:
visible: "Order Summary"
timeout: 10000
- assertVisible: ${output.orderName}
- assertVisible: "POSTED" # or whichever status it initialises to
- assertVisible: "Role" # Check if role label is visible
# Optionally, go back to Home
- tapOn: "Home"

View File

@@ -0,0 +1,92 @@
# Client App — E2E: Create One-Time Order and verify success
# Flow:
# - Home → Create Order → One-Time
# - Fill required fields (Event Name, Role)
# - Create Order
# - Verify success message and return to orders
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/create_order_one_time_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- tapOn: "Create Order\\nSchedule shifts"
- extendedWaitUntil:
visible: "One-Time\\nSingle Event or Shift Request"
timeout: 10000
- tapOn: "One-Time\\nSingle Event or Shift Request"
- extendedWaitUntil:
visible: "One-Time Order"
timeout: 10000
# Wait for form or empty state data to load from API
- extendedWaitUntil:
visible: ".*(Create your order|No Vendors Available).*"
timeout: 15000
- assertNotVisible: "No Vendors Available"
- tapOn: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
- inputText: "Test E2E Event"
- hideKeyboard
# Wait for Vendor and Hub to auto-populate the defaults from the API.
# We just need to give it a second.
- extendedWaitUntil:
visible: ".*(Select Role|SELECT ROLE).*"
timeout: 10000
# Select Role (Required for valid form)
- tapOn: ".*(Select Role|SELECT ROLE).*"
- tapOn: ".*\\$.*" # Tap the first role from the dropdown
# Set Start Time (Required for valid form)
- scrollUntilVisible:
element: "--:--"
direction: DOWN
timeout: 5000
optional: true
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
# Set End Time (Required for valid form)
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
- scrollUntilVisible:
element: "(?i)Create Order"
direction: DOWN
timeout: 5000
optional: true
# Wait for Create Order button to be enabled (isValid=true handles this by making it clickable)
- tapOn: "(?i)Create Order"
# Success screen shows "Order received." or similar success title/message
- extendedWaitUntil:
visible: "Test E2E Event" # or success message, assuming it goes back to Orders or shows Success Screen
timeout: 45000

View File

@@ -0,0 +1,180 @@
# Multi-App E2E: Client Creates Order -> Staff Sees Shift
# Purpose:
# - Historically verifies that an order created in the Client app
# is instantly visible as a shift in the Staff app.
#
# Run:
# maestro test cross_app_order_verification.yaml \
11: # -e TEST_CLIENT_EMAIL=... \
12: # -e TEST_CLIENT_PASSWORD=... \
13: # -e TEST_STAFF_PHONE=... \
14: # -e TEST_STAFF_OTP=...
appId: com.krowwithus.client
---
# --- PHASE 1: CLIENT CREATES ORDER ---
- launchApp:
clearState: false
- runScript: ../../scripts/generate_order_name.js
- tapOn:
text: "(?i)Home"
optional: true
- extendedWaitUntil:
visible: "(?i)Welcome back"
timeout: 30000
- tapOn: "Create Order\nSchedule shifts"
- tapOn: "One-Time\nSingle Event or Shift Request"
- extendedWaitUntil:
visible: "One-Time Order"
timeout: 10000
# 3. Fill Order Details
- tapOn: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
- inputText: ${output.orderName}
- hideKeyboard
# Select Role
- tapOn: ".*(Select Role|SELECT ROLE).*"
- tapOn: ".*\\$.*"
# Set Start Time (Required for valid form)
- scrollUntilVisible:
element: "--:--"
direction: DOWN
timeout: 5000
optional: true
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
# Set End Time (Required for valid form)
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
- scrollUntilVisible:
element: "(?i)Create Order"
direction: DOWN
- tapOn: "(?i)Create Order"
# 4. Verify Success Confirmation
- extendedWaitUntil:
visible: "(?i)Order Created.*"
timeout: 30000
- stopApp: com.krowwithus.client
# --- PHASE 2: STAFF VERIFIES SHIFT ---
- appId: com.krowwithus.staff
- launchApp:
clearState: false
# If not logged in, we'd need a sign_in flow here, but we assume state is kept
# or we run this after a staff sign-in test.
# For a standalone test, we should include the login steps.
# (Optional login steps if app starts at landing)
- runFlow:
when:
visible: "Join the Workforce"
file: ../../../../staff/maestro/auth/happy_path/sign_in.yaml
env:
PHONE: ${TEST_STAFF_PHONE}
OTP: ${TEST_STAFF_OTP}
- extendedWaitUntil:
visible: "Shifts"
timeout: 20000
- launchApp:
appId: com.krowwithus.staff
clearState: false
# Wait for Staff app to fully render AND give backend time to index the new order
- waitForAnimationToEnd:
timeout: 30000
# 1. Navigate to Shifts tab
- tapOn:
id: "nav_shifts"
# 2. Refresh #1: Swipe to force backend reload, THEN search
- swipe:
start: 50%, 30%
end: 50%, 80%
- waitForAnimationToEnd:
timeout: 8000
# Wait for the search field to appear (visible when Find Shifts tab is active)
- extendedWaitUntil:
visible: "(?i)Search jobs.*"
timeout: 15000
# 3. Aggressive Retry Loop: Search, if not found, refresh and try again.
- repeat:
times: 5
commands:
- runFlow:
when:
notVisible:
id: "shft_card_logo_placeholder"
index: 0
commands:
# 1. Clear current search
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
- inputText: ""
- tapOn: "Shifts" # Dismiss keyboard
# 2. Pull-to-refresh
- swipe:
start: 50%, 60%
end: 50%, 90%
- waitForAnimationToEnd:
timeout: 15000
# 3. Enter search query again
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "${output.orderName}\n"
- tapOn: "Shifts" # Dismiss keyboard
- waitForAnimationToEnd:
timeout: 5000
# 4. Final wait — plenty of time for slow backends
- extendedWaitUntil:
visible:
id: "shft_card_logo_placeholder"
index: 0
timeout: 60000
- assertVisible:
id: "shft_card_logo_placeholder"
index: 0
- tapOn:
id: "shft_card_logo_placeholder"
index: 0
# Wait for Details page with a very generous timeout for slow backend syncing
- extendedWaitUntil:
visible: "LOCATION"
timeout: 45000
- assertVisible: "${output.orderName}"
- assertVisible: "(?i)(Apply|Accept|Clock|Confirm).*"

View File

@@ -0,0 +1,71 @@
# Client App — E2E: Edit Active Order (One-Time)
# Flow:
# - Home → Expanded Active Order Card
# - Tap Edit icon to open OrderEditSheet
# - Change position count to +1
# - Continue to Review
# - Confirm Save
#
# Prerequisite:
# User must have at least one active, uncompleted order.
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/edit_active_order_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- assertVisible: "Home"
- tapOn: "Home"
- waitForAnimationToEnd:
timeout: 3000
# Wait for active orders to load
- scrollUntilVisible:
element: "OPEN" # A badge indicating an open order
visibilityPercentage: 50
timeout: 10000
# Tap the edit icon (using an id or the generic icon if no ID is present, we scroll to find the Edit Sheet trigger)
# Since we can't select by icon natively, we rely on the card layout having a tapped edit button
- tapOn:
id: "edit_order_button"
# Fallback if no ID is set, tap near the top right of the order
point: "85%, 25%"
- extendedWaitUntil:
visible: "Edit One-Time Order"
timeout: 10000
# Scroll to the position count control
- scrollUntilVisible:
element: "WORKERS"
visibilityPercentage: 50
timeout: 10000
# Increase worker count
- tapOn: "+"
# Proceed to review
- tapOn: "Review Positions"
- extendedWaitUntil:
visible: "Positions Breakdown"
timeout: 10000
# Ensure the count reflects the change before confirming
- tapOn: "Confirm & Save"
# Verify it saved and the modal closed
- extendedWaitUntil:
notVisible: "Confirm & Save"
timeout: 15000

View File

@@ -0,0 +1,70 @@
# Client App — E2E: Edit Active Order and verify update confirmation
# Purpose:
# - Opens an active order edit sheet
# - Adjusts workers count (+1)
# - Confirms save
# - Verifies the "Order Updated!" confirmation UI appears
#
# Prerequisite:
# User must have at least one active, uncompleted order.
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/edit_active_order_verify_updated_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- assertVisible: "Home"
- tapOn: "Home"
- waitForAnimationToEnd:
timeout: 3000
# Wait for active orders to load and ensure we have an OPEN order
- scrollUntilVisible:
element: "OPEN"
visibilityPercentage: 50
timeout: 15000
# Tap edit on the active order card (prefer ID; fallback to a safe point)
- tapOn:
id: "edit_order_button"
point: "85%,25%"
- extendedWaitUntil:
visible: "Edit One-Time Order"
timeout: 15000
- scrollUntilVisible:
element: "WORKERS"
visibilityPercentage: 50
timeout: 15000
- tapOn: "+"
- tapOn: "Review Positions"
- extendedWaitUntil:
visible: "Positions Breakdown"
timeout: 15000
- tapOn: "Confirm & Save"
# Verify the in-app confirmation appears
- extendedWaitUntil:
visible: "Order Updated!"
timeout: 20000
- assertVisible: "Your shift has been updated successfully."
- tapOn: "Back to Orders"
- extendedWaitUntil:
visible: "Orders"
timeout: 15000

View File

@@ -0,0 +1,272 @@
# Multi-App E2E: Full Order-to-Payment Lifecycle
# Roles: Client, Staff
# Flow: Create -> Apply -> Clock In (Geofence) -> Clock Out -> Approve Payment
appId: com.krowwithus.client # Starts with Client profile
---
# === PERSONA: CLIENT ===
- launchApp:
clearState: false
# 1. Generate unique identifier for this session
- runScript: ../../scripts/generate_order_name.js
# 2. Create the Order
- waitForAnimationToEnd:
timeout: 10000
- tapOn:
text: "(?i)Home"
optional: true
- extendedWaitUntil:
visible: "(?i)Welcome back"
timeout: 30000
- tapOn: "Create Order\\nSchedule shifts"
- extendedWaitUntil:
visible: "One-Time\\nSingle Event or Shift Request"
timeout: 10000
- tapOn: "One-Time\\nSingle Event or Shift Request"
- extendedWaitUntil:
visible: "One-Time Order"
timeout: 15000
- extendedWaitUntil:
visible: ".*(Create your order|No Vendors Available).*"
timeout: 15000
# Safety check: ensure the account has vendors setup!
- assertNotVisible: "No Vendors Available"
- tapOn: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
- inputText: "${output.orderName}"
- hideKeyboard
# Select a Hub (assuming first one is auto-selected or we tap)
- tapOn:
text: ".*(HUB|Hub).*"
optional: true
# Wait for Vendor and Hub to auto-populate the defaults from the API.
- extendedWaitUntil:
visible: ".*(Select Role|SELECT ROLE).*"
timeout: 10000
# Select Role (Required for valid form)
- tapOn: ".*(Select Role|SELECT ROLE).*"
- tapOn: ".*\\$.*" # Tap the first role from the dropdown (matches 'Role - $Cost')
# Set Start Time (Required for valid form)
- scrollUntilVisible:
element: "--:--"
direction: DOWN
timeout: 5000
optional: true
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
# Set End Time (Required for valid form)
- tapOn: "--:--"
- extendedWaitUntil:
visible: "(?i)ok"
timeout: 5000
- tapOn: "(?i)ok"
- scrollUntilVisible:
element: "(?i)Create Order"
direction: DOWN
timeout: 5000
optional: true
- tapOn: "(?i)Create Order"
- extendedWaitUntil:
visible: "(?i)Order Created.*"
timeout: 30000
# Cool-down to let backend index the new order
- waitForAnimationToEnd:
timeout: 35000
- stopApp
# === PERSONA: STAFF ===
- launchApp:
appId: com.krowwithus.staff
clearState: false
# Wait for Staff app to fully render (also serves as additional backend indexing time)
- waitForAnimationToEnd:
timeout: 15000
# Sign in if needed
- runFlow:
when:
visible: "Log In"
file: ../../../../staff/maestro/auth/happy_path/sign_in.yaml
env:
TEST_STAFF_PHONE: ${TEST_STAFF_PHONE}
TEST_STAFF_OTP: ${TEST_STAFF_OTP}
# 1. Navigate to Shifts tab
- tapOn:
id: "nav_shifts"
# 2. Refresh #1: Swipe to force backend reload, THEN search
- swipe:
start: 50%, 30%
end: 50%, 80%
- waitForAnimationToEnd:
timeout: 8000
# Wait for the search field to appear (visible when Find Shifts tab is active)
- extendedWaitUntil:
visible: "(?i)Search jobs.*"
timeout: 15000
# 3. Aggressive Retry Loop: Search, if not found, refresh and try again.
- repeat:
times: 5
commands:
- runFlow:
when:
notVisible:
id: "shft_card_logo_placeholder"
index: 0 # Index 0 for Logo (ID is unique to cards)
commands:
# 1. Clear current search
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"
- inputText: ""
- tapOn: "Shifts" # Dismiss keyboard
# 2. Pull-to-refresh
- swipe:
start: 50%, 60%
end: 50%, 90%
- waitForAnimationToEnd:
timeout: 15000
# 3. Enter search query again
- tapOn:
id: "find_shifts_search_input"
- waitForAnimationToEnd:
timeout: 2000
- inputText: "${output.orderName}\n"
- tapOn: "Shifts" # Dismiss keyboard
- waitForAnimationToEnd:
timeout: 5000
# 4. Final wait — plenty of time for slow backends
- extendedWaitUntil:
visible:
id: "shft_card_logo_placeholder"
index: 0
timeout: 60000
- tapOn:
id: "shft_card_logo_placeholder"
index: 0
# Wait for Details page with a very generous timeout for slow backend syncing
- extendedWaitUntil:
visible: "LOCATION"
timeout: 45000
# 2. Book the shift (Instant Book flow)
- extendedWaitUntil:
visible: "(?i)(Apply|Accept|Clock|Confirm).*"
timeout: 20000
- tapOn: "(?i)(Apply|Accept|Clock|Confirm).*"
# Handle confirmation dialog if it appears
- runFlow:
when:
visible: "(?i)Book Shift"
file: ../../../../staff/maestro/shifts/confirm_booking_dialog.yaml
- extendedWaitUntil:
visible: "Shift successfully booked!"
timeout: 20000
- extendedWaitUntil:
visible: "(?i)Welcome back"
timeout: 20000
# Tap Home tab to reset navigation context
- tapOn:
id: "nav_home"
# Tap Clock In tab
- tapOn:
id: "nav_clock_in"
# Wait for Clock In screen to load
- extendedWaitUntil:
visible: "(?i)Clock In to your Shift"
timeout: 15000
# Set location to the Venue (NYC Grand Hotel as per ClockInCubit)
- setLocation:
latitude: 40.7128
longitude: -74.0060
- extendedWaitUntil:
visible: "Swipe to Check In"
timeout: 15000
- swipe:
direction: RIGHT
element: "Swipe to Check In"
- extendedWaitUntil:
visible: "Check In!"
timeout: 15000
# 4. Clock Out (immediately for test purposes)
- swipe:
direction: RIGHT
element: "Swipe to Check Out"
- extendedWaitUntil:
visible: "Check Out!" # Assuming similar success UI
timeout: 10000
optional: true
- stopApp
# === PERSONA: CLIENT ===
- launchApp:
appId: com.krowwithus.client
clearState: false
# 1. Verify and Approve Billing
- tapOn: "Billing"
- tapOn: "Awaiting Approval"
# Find the specific approved shift's related invoice
# For the test, we assume the top one is our most recent completion
- extendedWaitUntil:
visible: "Review & Approve"
timeout: 10000
- tapOn: "Review & Approve"
- tapOn: "Approve"
- extendedWaitUntil:
visible: "Invoice approved"
timeout: 15000
# Test complete - invoice approved successfully
- assertVisible: "Invoice approved"

View File

@@ -0,0 +1,50 @@
# Client App — E2E: Rapid order → parsed One-Time draft
# This is a true end-to-end in-app flow:
# - Navigate to Create Order → Rapid
# - Enter a message
# - Submit ("Send Message") which triggers parse usecase
# - Verify navigation into One-Time order draft screen
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/rapid_to_one_time_draft_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- extendedWaitUntil:
visible: "Create Order\nSchedule shifts"
timeout: 20000
- tapOn: "Create Order\nSchedule shifts"
- extendedWaitUntil:
visible: "RAPID\nURGENT same-day Coverage"
timeout: 10000
- tapOn: "RAPID\nURGENT same-day Coverage"
- extendedWaitUntil:
visible: "RAPID Order"
timeout: 10000
# Use one of the predefined example messages so the BLoC
# has a realistic input without manual typing.
- tapOn: ".*Need 2 cooks ASAP.*"
# For now we only require that the Send Message action
# remains visible and tappable on the Rapid Order screen.
- assertVisible: "Send Message"

View File

@@ -0,0 +1,52 @@
# Client App — E2E: Rapid order → parsed One-Time draft (submit)
# Purpose:
# - Validates the full RAPID happy path including submitting a message and
# landing on the One-Time order draft screen.
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/rapid_to_one_time_draft_submit_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- extendedWaitUntil:
visible: "Create Order\nSchedule shifts"
timeout: 20000
- tapOn: "Create Order\nSchedule shifts"
- extendedWaitUntil:
visible: "RAPID\nURGENT same-day Coverage"
timeout: 10000
- tapOn: "RAPID\nURGENT same-day Coverage"
- extendedWaitUntil:
visible: "RAPID Order"
timeout: 10000
# Use a predefined example message so the Rapid flow has valid input.
- tapOn: ".*Need 2 cooks ASAP.*"
- assertVisible: "Send Message"
- tapOn: "Send Message"
# Parsed result should navigate into a One-Time order draft screen.
# This depends on backend parsing; allow extra time for slower networks.
- extendedWaitUntil:
visible: "ORDER NAME"
timeout: 60000
- assertVisible: "Select Role"

View File

@@ -0,0 +1,53 @@
# Client App — E2E: Reorder Flow
# Purpose:
# - Navigates Home → Reorder Section.
# - Taps "Reorder" on a recent shift.
# - Verifies the One-Time Order form is correctly populated.
# - Submits the reordered shift.
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- tapOn: "Home"
# 1. Generate a new name for the reorder to avoid confusion
- runScript: ../../scripts/generate_order_name.js
# 2. Find the "Reorder" section (translates to "Recently Completed" or similar)
# We search for the Reorder button text
- scrollUntilVisible:
element: "REORDER"
direction: DOWN
timeout: 10000
- tapOn: "REORDER"
# 3. Verification: We should be on the One-Time Order creation page
- extendedWaitUntil:
visible: "One-Time Order"
timeout: 10000
# 4. Verification: Some fields should be pre-filled (e.g. Hub)
# We can't easily check exact pre-fill values without knowing the history,
# but we can verify we are in the flow and the form is loaded.
- extendedWaitUntil:
visible: ".*(Create your order|No Vendors Available).*"
timeout: 15000
- assertNotVisible: "No Vendors Available"
- assertVisible: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
# 5. Overwrite name and Submit
- tapOn: ".*(ORDER NAME|EVENT NAME|Order[ Nn]ame|Event[ Nn]ame).*"
- inputText: "${output.orderName}"
- hideKeyboard
- tapOn: "(?i)Create Order"
# 6. Success check
- extendedWaitUntil:
visible: "(?i)Order Created.*"
timeout: 15000

View File

@@ -0,0 +1,26 @@
# Client App — View Orders page with filter tabs
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 30000
- tapOn: "(?i)Orders"
- extendedWaitUntil:
visible: "(?i)Orders"
timeout: 15000
- extendedWaitUntil:
visible: "(?i)Up Next.*"
timeout: 10000
- assertVisible: "(?i)Up Next.*"
- assertVisible: "(?i)Active.*"
- assertVisible: "(?i)Completed.*"

View File

@@ -0,0 +1,63 @@
# Client App — Orders: Validation errors (negative path)
# Purpose:
# - Opens Create Order → One-Time form
# - Attempts to submit WITHOUT filling required fields
# - Verifies inline validation error messages appear
# - Verifies the form does NOT navigate away (stays on create order screen)
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/create_order_validation_errors.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
# Navigate to Create Order
- extendedWaitUntil:
visible: "(?i)Create Order.*Schedule.*"
timeout: 30000
- tapOn: "(?i)Create Order.*Schedule.*"
- extendedWaitUntil:
visible: "(?i)ORDER TYPE"
timeout: 10000
# Select One-Time order type
- tapOn: "(?i)One-Time.*Single Event.*"
- extendedWaitUntil:
visible: "(?i)One-Time Order"
timeout: 10000
# Do NOT fill any fields — attempt to submit immediately
- tapOn: "(?i)Create Order"
- waitForAnimationToEnd:
timeout: 2000
# Validation: form should NOT advance — still on One-Time Order screen
- assertVisible: "(?i)One-Time Order"
# Validation: at least one inline error or disabled-state indicator should appear
- assertVisible:
text: "(?i).*(required|must|invalid|please fill|error).*"
optional: true
# Verify ORDER NAME field is still visible (form did not navigate away)
- assertVisible: "(?i)ORDER NAME"

View File

@@ -0,0 +1,30 @@
# Client App — Create order flow entry
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
- extendedWaitUntil:
visible: "(?i)Create Order.*Schedule.*"
timeout: 20000
- tapOn: "(?i)Create Order.*Schedule.*"
- extendedWaitUntil:
visible: "(?i)ORDER TYPE"
timeout: 10000
- assertVisible: "(?i)RAPID.*URGENT.*"

View File

@@ -0,0 +1,38 @@
# Client App — Create One-Time Order flow
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
- extendedWaitUntil:
visible: "(?i)Create Order.*Schedule.*"
timeout: 30000
- tapOn: "(?i)Create Order.*Schedule.*"
- extendedWaitUntil:
visible: "(?i)One-Time.*Single Event.*"
timeout: 10000
- tapOn: "(?i)One-Time.*Single Event.*"
- extendedWaitUntil:
visible: "(?i)One-Time Order"
timeout: 15000
- assertVisible: "(?i)Create Your Order"
- assertVisible: "(?i).*(SELECT VENDOR|Date|HUB|Positions).*"
- assertVisible: "(?i)Create Order"

View File

@@ -0,0 +1,39 @@
# Client App — Create Permanent Order (placeholder/WIP screen)
# Validates that Permanent entry is present and navigates to the expected WIP placeholder.
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/create_order_permanent_placeholder.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
# Open Create Order from Home quick action (reliable entry point)
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- extendedWaitUntil:
visible: "Create Order\nSchedule shifts"
timeout: 20000
- tapOn: "Create Order\nSchedule shifts"
# Select Permanent order type
- extendedWaitUntil:
visible: "Permanent\nLong-Term Staffing Placement"
timeout: 10000
- tapOn: "Permanent\nLong-Term Staffing Placement"
# Validate Permanent Order screen header (WIP flow)
- extendedWaitUntil:
visible: "Permanent Order"
timeout: 10000
- assertVisible: "Permanent Order"

View File

@@ -0,0 +1,39 @@
# Client App — Create Rapid Order flow (UI smoke)
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
- extendedWaitUntil:
visible: "(?i)Create Order.*Schedule.*"
timeout: 30000
- tapOn: "(?i)Create Order.*Schedule.*"
- extendedWaitUntil:
visible: "(?i)ORDER TYPE"
timeout: 10000
- assertVisible: "(?i)RAPID.*URGENT.*"
- tapOn: "(?i)RAPID.*URGENT.*"
- extendedWaitUntil:
visible: "(?i)RAPID Order"
timeout: 15000
- assertVisible: "(?i)Emergency staffing in minutes"
- assertVisible: "(?i).*(Send Message|Speak).*"

View File

@@ -0,0 +1,39 @@
# Client App — Create Recurring Order (placeholder/WIP screen)
# Validates that Recurring entry is present and navigates to the expected WIP placeholder.
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/create_order_recurring_placeholder.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
# Open Create Order from Home quick action (reliable entry point)
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- extendedWaitUntil:
visible: "Create Order\nSchedule shifts"
timeout: 20000
- tapOn: "Create Order\nSchedule shifts"
# Select Recurring order type
- extendedWaitUntil:
visible: "Recurring\nOngoing Weekly / Monthly Coverage"
timeout: 10000
- tapOn: "Recurring\nOngoing Weekly / Monthly Coverage"
# Validate Recurring Order screen header (WIP flow)
- extendedWaitUntil:
visible: "Recurring Order"
timeout: 10000
- assertVisible: "Recurring Order"

View File

@@ -0,0 +1,36 @@
# Client App — Order Type selection screen (comprehensive smoke)
# Asserts all order types are present with correct accessible labels.
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/orders/order_type_selection_smoke.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- extendedWaitUntil:
visible: "Create Order\nSchedule shifts"
timeout: 20000
- tapOn: "Create Order\nSchedule shifts"
- extendedWaitUntil:
visible: "ORDER TYPE"
timeout: 10000
- assertVisible: "RAPID\nURGENT same-day Coverage"
- assertVisible: "One-Time\nSingle Event or Shift Request"
- assertVisible: "Recurring\nOngoing Weekly / Monthly Coverage"
- assertVisible: "Permanent\nLong-Term Staffing Placement"

View File

@@ -0,0 +1,62 @@
# Client App — E2E: Reports Insights Verification
# Purpose:
# - Navigates to Reports.
# - Opens Spend Report.
# - Verifies that charts and summary cards are loaded.
# - Opens No-Show Report.
# - Verifies the empty/data states are handled.
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- tapOn: "Home"
- tapOn: "Reports"
# 1. Verify Spend Report
- extendedWaitUntil:
visible: "Spend Report"
timeout: 10000
- tapOn: "Spend Report"
# Verify specialized summary cards are visible
- extendedWaitUntil:
visible: "TOTAL SPEND"
timeout: 10000
- assertVisible: "AVG DAILY COST"
# Swipe to see historical chart content if needed
- swipe:
direction: DOWN
element: "TOTAL SPEND"
- assertVisible: "SPEND BY INDUSTRY"
# Go back
- tapOn:
id: "back_button" # Or the arrow icon
optional: true
- tapOn:
point: "8% 8%" # Fallback to top-left if ID missing
optional: true
# 2. Verify No-Show Report
- extendedWaitUntil:
visible: "No-Show Report"
timeout: 10000
- tapOn: "No-Show Report"
- extendedWaitUntil:
visible: "(?i).*(No-Show|Report).*"
timeout: 10000
# Smoke check for data availability
- assertVisible:
text: "No show incidents"
optional: true
- tapOn: "Home"

View File

@@ -0,0 +1,20 @@
# Client App — Reports dashboard
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Reports"
timeout: 30000
- tapOn: "(?i)Reports"
- extendedWaitUntil:
visible: "(?i)Workforce Control Tower"
timeout: 20000
- assertVisible: "(?i)Workforce Control Tower"

View File

@@ -0,0 +1,53 @@
# Client App — Reports: No-Show Report smoke
# Purpose:
# - Opens Reports dashboard (Workforce Control Tower)
# - Navigates to the No-Show Reports section
# - Verifies the no-show report screen or relevant UI elements are visible
# - Also verifies empty-state copy if no no-show records exist
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/reports/no_show_report_smoke.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Reports"
- extendedWaitUntil:
visible: "(?i)Workforce Control Tower"
timeout: 20000
- assertVisible: "(?i)Workforce Control Tower"
# Scroll to find No-Show report entry
- scrollUntilVisible:
element: "(?i).*(No.Show|No Show|Absence).*"
visibilityPercentage: 50
timeout: 15000
optional: true
# Tap No-Show report (if visible)
- tapOn:
text: "(?i).*(No.Show|No Show|Absence).*"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# Either the report detail loads or we see a placeholder/empty state
- assertVisible:
text: "(?i).*(No.Show|No Show|Absence|No records|Nothing|Report).*"
optional: true
# Exit assertion — still in Reports context (no crash)
- assertVisible: "(?i).*(Workforce Control Tower|Reports|No.Show).*"

View File

@@ -0,0 +1,46 @@
# Client App — Reports: Spend Report export (smoke)
# Purpose:
# - Opens Reports dashboard
# - Opens Spend Report
# - Triggers Export action and verifies placeholder export message
#
# Note: Export is currently placeholder-driven in UI strings.
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/reports/spend_report_export_smoke.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Reports"
- extendedWaitUntil:
visible: "Workforce Control Tower"
timeout: 10000
- scrollUntilVisible:
element: "Quick Reports"
visibilityPercentage: 50
timeout: 15000
- assertVisible: "Quick Reports"
- tapOn: "Spend Report"
- extendedWaitUntil:
visible: "Spend Report"
timeout: 10000
# Trigger export (may be a placeholder message)
- tapOn: "Export"
- extendedWaitUntil:
visible: "Exporting Spend Report \\(Placeholder\\)"
timeout: 10000

View File

@@ -0,0 +1 @@
output.orderName = "E2E-" + Math.random().toString(36).substring(2, 7).toUpperCase();

View File

@@ -0,0 +1,29 @@
# Client App — Edit Profile (navigates via Settings)
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 15000
- tapOn:
id: "client_home_settings"
- extendedWaitUntil:
visible: "(?i).*Profile.*"
timeout: 20000
- assertVisible: "(?i)Profile"
- assertVisible: "(?i)Quick Links"
- assertVisible: "(?i).*(Log Out|Logout).*"

View File

@@ -0,0 +1,80 @@
# Client App — E2E: Edit Profile save (with re-open verification)
# Purpose:
# - Navigates Settings → Profile → Edit Profile
# - Saves a small change and verifies success message
# - Re-opens Edit Profile to validate the change is visible (basic persistence check)
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/settings/edit_profile_save_e2e.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Profile"
timeout: 10000
- tapOn: "Profile"
- extendedWaitUntil:
visible: "Edit Profile"
timeout: 10000
- tapOn: "Edit Profile"
- extendedWaitUntil:
visible: "Edit Profile"
timeout: 10000
- assertVisible: "FIRST NAME"
- assertVisible: "LAST NAME"
- assertVisible: "PHONE NUMBER"
- assertVisible: "Save Changes"
# Append a suffix to first name (avoids needing to clear the field)
- tapOn: "FIRST NAME"
- inputText: " QA"
- hideKeyboard
- tapOn: "Save Changes"
- extendedWaitUntil:
visible: "Profile updated successfully"
timeout: 15000
# Re-open Edit Profile and confirm the suffix is visible somewhere in the form.
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Profile"
timeout: 10000
- tapOn: "Profile"
- extendedWaitUntil:
visible: "Edit Profile"
timeout: 10000
- tapOn: "Edit Profile"
- extendedWaitUntil:
visible: "Edit Profile"
timeout: 10000
- assertVisible: "QA"

View File

@@ -0,0 +1,44 @@
# Client App — Settings logout flow
# Navigates to Settings via gear icon and logs out.
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/settings/logout_flow.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "Quick Links"
timeout: 10000
- assertVisible: "Log Out"
- tapOn: "Log Out"
# Confirm dialog (button label is localized as "Log Out")
- extendedWaitUntil:
visible: "Cancel"
timeout: 10000
- tapOn: "Log Out"
# Post-logout should return to auth entry (e.g. Create Account screen).
- extendedWaitUntil:
visible: "Create Account"
timeout: 20000

View File

@@ -0,0 +1,31 @@
# Client App — Settings page
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- extendedWaitUntil:
visible: "(?i)Home"
timeout: 30000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 15000
# Open Settings via header gear icon using stable ID
- tapOn:
id: "client_home_settings"
- extendedWaitUntil:
visible: "(?i).*Quick Links.*"
timeout: 20000
- assertVisible: "(?i)Profile"
- assertVisible: "(?i)Clock-In Hubs"
- assertVisible: "(?i)Billing & Payments"
- assertVisible: "(?i).*(Log Out|Logout).*"

View File

@@ -0,0 +1,80 @@
# Client App — Settings: Edit Profile validation errors (negative path)
# Purpose:
# - Navigates to Settings → Edit Profile
# - Clears a required field (e.g. First Name) and attempts to save
# - Verifies that an inline validation error is shown
# - Verifies the form does NOT navigate away (no false success)
#
# Run:
# maestro test \
# apps/mobile/apps/client/maestro/auth/sign_in.yaml \
# apps/mobile/apps/client/maestro/settings/edit_profile_validation.yaml \
# -e TEST_CLIENT_EMAIL=... \
# -e TEST_CLIENT_PASSWORD=...
appId: com.krowwithus.client
---
- launchApp:
clearState: false
- extendedWaitUntil:
visible: "(?i).*(Home|Orders|Coverage|Billing|Reports).*"
timeout: 45000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 20000
# Open Settings via header gear icon (top-right)
- tapOn:
point: "92%,10%"
- extendedWaitUntil:
visible: "(?i)Profile"
timeout: 10000
- tapOn: "(?i)Profile"
- extendedWaitUntil:
visible: "(?i)Edit Profile"
timeout: 10000
- tapOn: "(?i)Edit Profile"
- extendedWaitUntil:
visible: "(?i)Edit Profile"
timeout: 10000
# Verify required fields are present
- assertVisible: "(?i)FIRST NAME"
- assertVisible: "(?i)LAST NAME"
- assertVisible: "(?i)Save Changes"
# Clear the FIRST NAME field by selecting all and deleting
- tapOn: "(?i)FIRST NAME"
- repeat:
times: 30
commands:
- pressKey: Backspace
- hideKeyboard
# Attempt to save with empty first name
- tapOn: "(?i)Save Changes"
- waitForAnimationToEnd:
timeout: 2000
# Negative assertion: success message must NOT appear
- assertNotVisible:
text: "(?i)Profile updated successfully"
optional: true
# Positive assertion: validation error appears OR form stays put
- assertVisible: "(?i)Edit Profile"
# Inline error should be visible for the empty required field
- assertVisible:
text: "(?i).*(required|cannot be empty|must not be|invalid|enter your).*"
optional: true

View File

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

View File

@@ -75,6 +75,11 @@ public final class GeneratedPluginRegistrant {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin shared_preferences_android, io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin", e);
}
try {
flutterEngine.getPlugins().add(new fman.ge.smart_auth.SmartAuthPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin smart_auth, fman.ge.smart_auth.SmartAuthPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.urllauncher.UrlLauncherPlugin());
} catch (Exception e) {

View File

@@ -66,6 +66,12 @@
@import shared_preferences_foundation;
#endif
#if __has_include(<smart_auth/SmartAuthPlugin.h>)
#import <smart_auth/SmartAuthPlugin.h>
#else
@import smart_auth;
#endif
#if __has_include(<url_launcher_ios/URLLauncherPlugin.h>)
#import <url_launcher_ios/URLLauncherPlugin.h>
#else
@@ -91,6 +97,7 @@
[FPPPackageInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPPackageInfoPlusPlugin"]];
[RecordIosPlugin registerWithRegistrar:[registry registrarForPlugin:@"RecordIosPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
[SmartAuthPlugin registerWithRegistrar:[registry registrarForPlugin:@"SmartAuthPlugin"]];
[URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]];
[WorkmanagerPlugin registerWithRegistrar:[registry registrarForPlugin:@"WorkmanagerPlugin"]];
}

View File

@@ -23,7 +23,7 @@ void main() async {
await const BackgroundTaskService().initialize(backgroundGeofenceDispatcher);
// Register global BLoC observer for centralized error logging
Bloc.observer = CoreBlocObserver(
Bloc.observer = const CoreBlocObserver(
logEvents: true,
logStateChanges: false, // Set to true for verbose debugging
);

View File

@@ -8,6 +8,7 @@
#include <file_selector_linux/file_selector_plugin.h>
#include <record_linux/record_linux_plugin.h>
#include <smart_auth/smart_auth_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
@@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar);
g_autoptr(FlPluginRegistrar) smart_auth_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin");
smart_auth_plugin_register_with_registrar(smart_auth_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
record_linux
smart_auth
url_launcher_linux
)

View File

@@ -14,6 +14,7 @@ import geolocator_apple
import package_info_plus
import record_macos
import shared_preferences_foundation
import smart_auth
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
@@ -26,5 +27,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -1,7 +1,7 @@
# Maestro Integration Tests — Staff App
Auth flows for the KROW Staff app.
See [docs/research/flutter-testing-tools.md](/docs/research/flutter-testing-tools.md) and [maestro-test-run-instructions.md](/docs/research/maestro-test-run-instructions.md).
Auth flows and E2E happy paths for the KROW Staff app.
See [docs/research/flutter-testing-tools.md](/docs/research/flutter-testing-tools.md), [maestro-test-run-instructions.md](/docs/research/maestro-test-run-instructions.md), and [docs/testing/maestro-e2e-happy-paths.md](/docs/testing/maestro-e2e-happy-paths.md) (#572).
## Structure
@@ -10,6 +10,49 @@ 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
availability.yaml
profile/
personal_info.yaml
documents_list.yaml
certificates_list.yaml
time_card.yaml
time_card_detail_smoke.yaml
bank_account.yaml
bank_account_fields_smoke.yaml
tax_forms.yaml
tax_forms_smoke.yaml
faqs.yaml
privacy_security.yaml
emergency_contact.yaml
attire.yaml
attire_validation_e2e.yaml
compliance/
document_upload_banner.yaml # #550
certificate_upload_banner.yaml # #551
attire_upload_banner.yaml # #552
document_upload_e2e.yaml
certificate_upload_e2e.yaml
shifts/
find_shifts.yaml
find_shifts_apply_smoke.yaml
clock_in_e2e.yaml
clock_out_e2e.yaml
incomplete_profile_banner.yaml # #549 (requires incomplete-profile user)
availability/
set_availability_e2e.yaml
payments/
payments_view_e2e.yaml
payment_history_smoke.yaml
home/
benefits.yaml # #524
```
## Prerequisites
@@ -24,11 +67,20 @@ maestro/
| sign_in | `TEST_STAFF_PHONE`, `TEST_STAFF_OTP` |
| sign_up | `TEST_STAFF_SIGNUP_PHONE`, `TEST_STAFF_OTP` |
**Sign-in:** +1 555-555-1234 (env: 5555551234) / 123123
## Run
```bash
# Via Makefile (export vars first)
make test-e2e-staff
make test-e2e-staff # Auth only
make test-e2e-staff-extended # Auth + nav + profile + compliance + shifts + benefits
make test-e2e-staff-happy-path # Auth + clock in/out + availability + document upload + payments + sign out (#572)
make test-e2e-staff-smoke # Deterministic smoke suite
make test-e2e-staff-profile-smoke # Profile smoke (timecard/bank/tax/attire validation)
make test-e2e-staff-payments-smoke # Payments smoke (earnings history)
make test-e2e-staff-shifts-smoke # Shifts smoke (find shifts; optionally apply)
make test-e2e-staff-compliance-e2e # Compliance E2E (document + certificate uploads)
# Direct
maestro test apps/mobile/apps/staff/maestro/auth/sign_in.yaml \

View File

@@ -0,0 +1,53 @@
# Staff App — E2E: Session Persistence Across Relaunch
# Purpose:
# - Log in via sign_in.yaml
# - Stop the app
# - Relaunch and verify user is still logged in (bypass login screen)
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/auth/session_persistence.yaml \
# -e TEST_STAFF_PHONE=... \
# -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
# We rely on sign_in.yaml being run before this to establish a session.
- launchApp
# If we are logged in, Home/Shifts content should be visible directly.
- extendedWaitUntil:
visible:
text: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 15000
# Perform a full stop to clear memory (not just backgrounding)
- stopApp
# Relaunch - should NOT show the login screen
- launchApp
- extendedWaitUntil:
visible:
text: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 15000
# Verification: Sign out to ensure clean state for next test
- tapOn: "(?i)Profile"
- scrollUntilVisible:
element: "(?i).*(Log Out|Sign Out).*"
visibilityPercentage: 50
timeout: 10000
- tapOn:
text: "(?i).*(Log Out|Sign Out).*"
# Confirm Sign Out
- tapOn:
text: "(?i).*(Yes|Confirm).*(Log Out|Sign Out).*"
optional: true
# Should return to the login landing page
- extendedWaitUntil:
visible: "(?i)Log In"
timeout: 10000
- assertVisible: "(?i)Log In"

View File

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

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:
id: staff_phone_input
- inputText: ${PHONE}
- hideKeyboard
- tapOn: "Send Code"
# OTP auto-submits when 6th digit entered
# OTP screen: Continue visible when ready for OTP entry
- assertVisible: "Continue"
- tapOn:
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,37 @@
# Staff App — E2E: Set Availability
# Flow:
# - Home → Profile → Availability
# - Try setting week days and saving
# - Uses the "Quick Set Availability" buttons for Weekdays
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/availability/set_availability_e2e.yaml \
# -e TEST_STAFF_PHONE=... \
# -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- assertVisible: "Profile"
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- tapOn: "Availability"
- extendedWaitUntil:
visible: "My Availability"
timeout: 10000
# Try the built-in quick actions
# "Weekdays" sets Mon-Fri to available
- tapOn: "Weekdays"
- tapOn: "Back"
- extendedWaitUntil:
visible: "Availability saved successfully"
timeout: 10000

View File

@@ -0,0 +1,63 @@
%PDF-1.4
%âãÏÓ
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/Resources <<
/Font <<
/F1 4 0 R
>>
>>
/MediaBox [0 0 612 792]
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj
5 0 obj
<<
/Length 44
>>
stream
BT
/F1 24 Tf
100 700 Td
(Test Document) Tj
ET
endstream
endobj
xref
0 6
0000000000 65535 f
0000000015 00000 n
0000000068 00000 n
0000000125 00000 n
0000000259 00000 n
0000000347 00000 n
trailer
<<
/Size 6
/Root 1 0 R
>>
startxref
442
%%EOF

View File

@@ -0,0 +1,84 @@
# Staff App — Compliance: Attire Upload E2E (full flow)
# Purpose:
# - Navigates Profile → Attire
# - Opens the attire upload form
# - Attempts to take/upload an attire photo (uses optional steps for camera flow)
# - Verifies success state or confirmation that attire record is saved
#
# Prerequisite:
# - Staff user with Attire compliance required (not yet uploaded)
# - Device/emulator must support camera or have a pre-configured photo
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/compliance/attire_upload_e2e.yaml \
# -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 20000
- tapOn: "(?i)Profile"
- waitForAnimationToEnd:
timeout: 3000
# Scroll down to find Attire section
- scrollUntilVisible:
element: "(?i)Attire"
visibilityPercentage: 50
timeout: 10000
- tapOn: "(?i)Attire"
- extendedWaitUntil:
visible: "(?i).*(Attire|Uniform|Photo).*"
timeout: 10000
# Entry assertion — Attire screen is open
- assertVisible: "(?i).*(Attire|Uniform|Dress Code).*"
# Attempt to trigger upload / photo action
- tapOn:
text: "(?i).*(Upload|Take Photo|Add Photo|Camera).*"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# If camera permission prompt appears — allow it
- tapOn:
text: "(?i).*(Allow|OK|Permit).*"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# If a photo capture confirmation button appears
- tapOn:
text: "(?i).*(Use Photo|Done|Confirm|Save).*"
optional: true
- waitForAnimationToEnd:
timeout: 3000
# Save attire submission
- tapOn:
text: "(?i).*(Save Attire|Submit|Upload Attire|Save).*"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# Success: either a success snackbar or return to profile
- assertVisible:
text: "(?i).*(Attire uploaded|Attire saved|Photo uploaded|submitted successfully|Success).*"
optional: true
# Exit assertion — still in profile/compliance context (no crash)
- assertVisible: "(?i).*(Attire|Profile|Compliance|Uniform).*"

View File

@@ -0,0 +1,73 @@
# Staff App — E2E: Certificate Upload
# Purpose:
# - Opens Certificates
# - Uploads a PDF certificate and verifies success snackbar
#
# Prerequisite:
# - Push fixture.pdf to emulator/device before running:
# bash apps/mobile/apps/staff/maestro/compliance/push_fixture.sh
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/compliance/certificate_upload_e2e.yaml \
# -e TEST_STAFF_PHONE=... \
# -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Certificates"
visibilityPercentage: 50
timeout: 15000
- tapOn: "Certificates"
- extendedWaitUntil:
visible: "Certificates"
timeout: 15000
# Start upload (button can exist on cards or as an add-more CTA)
- tapOn:
text: "Upload Certificate"
optional: true
- tapOn:
text: "Add Another Certificate"
optional: true
- extendedWaitUntil:
visible: "Upload Certificate"
timeout: 10000
- assertVisible: "Certificate Name"
- tapOn: "Certificate Name"
- inputText: "E2E Food Handler Permit"
- hideKeyboard
- tapOn: "Certificate Issuer"
- inputText: "E2E Department"
- hideKeyboard
- scrollUntilVisible:
element: "Upload File"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Upload File"
- extendedWaitUntil:
visible: "fixture.pdf"
timeout: 10000
- tapOn: "fixture.pdf"
- tapOn: "Save Certificate"
- extendedWaitUntil:
visible: "Certificate successfully uploaded and pending verification"
timeout: 20000

View File

@@ -0,0 +1,104 @@
# Staff App — E2E: Document Upload (State change flow)
# Flow:
# - Home → Profile → Documents → Select a pending document (Upload)
# - Select PDF file (relies on a pushed fixture)
# - Check Attestation -> Submit Document -> Verify success message
appId: com.krowwithus.staff
---
- launchApp
# Wait for splash/loading to finish and home page to be visible
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 30000
- tapOn: "(?i)Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "(?i)Documents"
visibilityPercentage: 50
timeout: 10000
- tapOn: "(?i)Documents"
# Wait for the Documents page title. Using regex with wildcards for maximum flexibility.
- extendedWaitUntil:
visible: "(?i).*Documents.*"
timeout: 20000
optional: true
# If the Documents title isn't found, try looking for the progress card or empty state
- extendedWaitUntil:
visible: "(?i).*(Document Verification|No documents found).*"
timeout: 10000
optional: true
# Tap the first Upload button available (uses staff_document_upload ID in code)
- tapOn:
id: "staff_document_upload"
optional: true
# If ID not found, try text as fallback
- tapOn:
text: "(?i)Upload"
optional: true
- extendedWaitUntil:
visible: "(?i).*Only PDF files are accepted.*"
timeout: 15000
optional: true
# Open native file picker
- tapOn:
id: "native_file_picker" # Optional ID if exists
optional: true
- tapOn:
text: "(?i)Select PDF File"
optional: true
# Wait for file picker content
- extendedWaitUntil:
visible: "fixture.pdf"
timeout: 15000
optional: true
# Select the pushed fixture
- tapOn:
text: "fixture.pdf"
optional: true
# Wait to return after selection
- extendedWaitUntil:
visible: "(?i).*I certify that this document is genuine and valid.*"
timeout: 10000
optional: true
# Check attestation
- tapOn:
text: "(?i).*I certify that this document is genuine and valid.*"
optional: true
- tapOn:
text: "(?i)Submit Document"
optional: true
# Success validation
- extendedWaitUntil:
visible: "(?i)Document uploaded successfully"
timeout: 20000
optional: true
# Post-action validation: Ensure the list reflects the new document
- back # Navigate away from success/view screen
- extendedWaitUntil:
visible: "(?i).*Documents.*"
timeout: 15000
optional: true
# Verify that the document label or a 'Pending' status is now present
- assertVisible:
text: "(?i).*(Pending Review|Verification in progress|Pending).*"
optional: true

View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Push the dummy PDF to the emulator for E2E upload flow testing
SCRIPT_DIR=$(dirname "$0")
FIXTURE_PATH="$SCRIPT_DIR/fixture.pdf"
# Push to the emulator downloads folder
adb push "$FIXTURE_PATH" /sdcard/Download/fixture.pdf
echo "Pushed fixture.pdf to /sdcard/Download/"

View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Compatibility wrapper for older docs/flows that reference push_upload_fixture.sh.
# Preferred script name: push_fixture.sh
SCRIPT_DIR=$(dirname "$0")
bash "$SCRIPT_DIR/push_fixture.sh"

View File

@@ -0,0 +1,15 @@
# 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
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Attire"
- tapOn: "Attire"

View File

@@ -0,0 +1,17 @@
# 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
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Certificates"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Certificates"

View File

@@ -0,0 +1,17 @@
# 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
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Documents"
visibilityPercentage: 50
timeout: 10000
- tapOn: "Documents"

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,63 @@
# Staff App — Home: Incomplete profile banner smoke
# Purpose:
# - Navigates to Home screen
# - Verifies that when a profile is incomplete, a banner/nudge is displayed
# prompting the staff member to complete their profile
# - Deterministic: if profile IS complete, completion state is simply not asserted
#
# Note: Banner visibility depends on profile completion state of the test account.
# The test always passes — it just verifies the banner if present.
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/home/incomplete_profile_banner_smoke.yaml \
# -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 20000
- tapOn: "(?i)Home"
- extendedWaitUntil:
visible: "(?i).*(Welcome back|Home).*"
timeout: 15000
# Entry assertion — home screen is loaded
- assertVisible: "(?i).*(Welcome back|Home).*"
# Case A: incomplete profile banner is visible
- assertVisible:
text: "(?i).*(Complete your profile|Profile incomplete|finish setting up|complete account|missing information).*"
optional: true
# Tap the banner CTA if present (verifies it navigates to profile)
- tapOn:
text: "(?i).*(Complete Now|Complete Profile|Finish Setup|Get Started).*"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# If tapped, verify we landed on Profile
- assertVisible:
text: "(?i).*(Profile|Personal Info|Documents).*"
optional: true
# Navigate back to Home
- back
- waitForAnimationToEnd:
timeout: 2000
# Case B: profile is complete — no banner (still a valid pass)
- assertVisible:
text: "(?i).*(Welcome back|Home|Benefits|Shifts).*"
optional: true
# Exit assertion — still in app context, no crash
- assertVisible: "(?i).*(Home|Profile|Shifts).*"

View File

@@ -0,0 +1,22 @@
# Staff App — Availability navigation
# Run: maestro test auth/sign_in.yaml navigation/availability.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
# Availability is usually reached from Home quick actions; keep this stable.
- tapOn: "Home"
- extendedWaitUntil:
visible: "Welcome back"
timeout: 15000
- scrollUntilVisible:
element: "Availability"
visibilityPercentage: 50
timeout: 15000
- tapOn: "Availability"
- extendedWaitUntil:
visible: "Availability"
timeout: 15000

View File

@@ -0,0 +1,28 @@
# 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
# Wait for the home/tab bar to be available
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 10000
# Try common clock-in labels and IDs
# Note: This tab is hidden if profile is incomplete
- tapOn:
id: "nav_clock_in"
optional: true
- tapOn:
text: "(?i)Clock In"
optional: true
# Check if we landed on the clock-in screen
- extendedWaitUntil:
visible: "(?i)Clock In to your Shift"
timeout: 10000
optional: true
- assertVisible:
text: "(?i).*(Clock In|Shift).*"
optional: true

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: "Welcome back"

View File

@@ -0,0 +1,31 @@
# 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
# Wait for some Home state to be ready
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 15000
# Try common payments/earnings labels and IDs
# Note: This tab is hidden if profile is incomplete
- tapOn:
id: "nav_payments"
optional: true
- tapOn:
text: "(?i)Earnings"
optional: true
- tapOn:
text: "(?i)Payments"
optional: true
# Check if we landed on a payments-related screen
- extendedWaitUntil:
visible: "(?i).*(Earnings|Payments).*"
timeout: 10000
optional: true
- assertVisible:
text: "(?i).*(Earnings|Payments).*"
optional: true

View File

@@ -0,0 +1,11 @@
# 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
- extendedWaitUntil:
visible: "(?i).*(Home|Profile|Log In).*"
timeout: 10000
- assertVisible: "Profile"
- tapOn: "Profile"
- assertVisible: "Personal Info"

View File

@@ -0,0 +1,11 @@
# 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
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back|Log In).*"
timeout: 10000
- assertVisible: "Shifts"
- tapOn: "Shifts"
- assertVisible: "Find Shifts"

View File

@@ -0,0 +1,94 @@
# Staff App — E2E: Earnings & Early Pay View
# Flow:
# - Launch App → Navigate to More/Payments
# - Checks for "Earnings" or "Total Earnings" state loading
# - Navigates across Period tabs (Week/Month/Year)
# - If Early Pay is available, attempts to trigger cash out flow.
appId: com.krowwithus.staff
---
- launchApp
# Waiting for Home page content
- extendedWaitUntil:
visible:
text: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 15000
# Accessing Payments/Earnings from Home layout.
# If there's an explicit Earnings tab.
- tapOn:
id: "nav_payments"
optional: true
# If it's not ID'd, usually "Earnings" or "Pay" exists
- tapOn:
text: "(?i)Earnings"
optional: true
# In PaymentsPage (payments_page.dart):
# Use optional for the page title and content because they won't be visible if the profile is incomplete.
- extendedWaitUntil:
visible: "(?i)Earnings"
timeout: 15000
optional: true
- assertVisible:
text: "(?i)Earnings"
optional: true
- assertVisible:
text: "(?i)Recent Payments"
optional: true
# Test tabs
- tapOn:
text: "(?i)Month"
optional: true
- extendedWaitUntil:
visible: "(?i)This Month"
timeout: 5000
optional: true
- tapOn:
text: "(?i)Year"
optional: true
- extendedWaitUntil:
visible: "(?i)This Year"
timeout: 5000
optional: true
- tapOn:
text: "(?i)Week"
optional: true
- extendedWaitUntil:
visible: "(?i)This Week"
timeout: 5000
optional: true
# Test Early Pay if module is active.
- tapOn:
text: "(?i)Cash Out"
optional: true
# Use scrollUntilVisible with assertions to ensure data integrity
- scrollUntilVisible:
element: "(?i)Recent Payments"
visibilityPercentage: 100
timeout: 10000
optional: true
- assertVisible:
text: "(?i)Recent Payments"
optional: true
# Specifically check for presence of some payment rows to avoid empty screens
- assertVisible:
id: "payment_item_row"
optional: true
# Verification of Back Navigation (Ensures the backstack isn't corrupted)
- back
- extendedWaitUntil:
visible:
text: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 10000

View File

@@ -0,0 +1,71 @@
# Staff App — Payments: Payment detail view smoke
# Purpose:
# - Navigates to the Payments tab
# - If payment history items exist, taps the first one
# - Verifies the detail screen loads with expected UI elements
# (date, amount, shift info, status)
# - If no payment history, verifies empty state is handled gracefully
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/payments/payment_detail_smoke.yaml \
# -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- extendedWaitUntil:
visible: "(?i).*(Home|Shifts|Welcome back).*"
timeout: 20000
# Navigate to Payments
- tapOn:
id: "nav_payments"
optional: true
- tapOn:
text: "(?i).*(Earnings|Payments|Pay).*"
optional: true
- extendedWaitUntil:
visible: "(?i).*(Earnings|Payments|Recent Payments).*"
timeout: 15000
optional: true
# Scroll to payment history section
- scrollUntilVisible:
element: "(?i)Recent Payments"
visibilityPercentage: 50
timeout: 10000
optional: true
# Case A: payments exist — tap first item to open detail view
- tapOn:
id: "payment_item_row"
optional: true
- waitForAnimationToEnd:
timeout: 2000
# Detail screen should show shift/payment specifics
- assertVisible:
text: "(?i).*(Amount|$|Earnings|hours|shift|date|status).*"
optional: true
# Navigate back to payments list
- back
- extendedWaitUntil:
visible: "(?i).*(Earnings|Payments|Recent Payments).*"
timeout: 10000
optional: true
# Case B: no payments — empty state is shown
- assertVisible:
text: "(?i).*(No payments|No earnings|Nothing yet|No records).*"
optional: true
# Exit assertion — back in payments context, no crash
- assertVisible: "(?i).*(Earnings|Payments|Home|Shifts).*"

View File

@@ -0,0 +1,39 @@
# Staff App — Payments: history visible (smoke)
# Purpose:
# - Navigates to Earnings/Payments
# - Verifies Total Earnings loads and Recent Payments section is reachable
#
# Run:
# maestro test \
# apps/mobile/apps/staff/maestro/auth/sign_in.yaml \
# apps/mobile/apps/staff/maestro/payments/payment_history_smoke.yaml \
# -e TEST_STAFF_PHONE=... \
# -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- extendedWaitUntil:
visible: "Shifts"
timeout: 10000
# Try tab/button variants depending on profile
- tapOn:
id: "nav_payments"
optional: true
- tapOn:
text: "Earnings"
optional: true
- extendedWaitUntil:
visible: "Total Earnings"
timeout: 15000
- scrollUntilVisible:
element: "Recent Payments"
visibilityPercentage: 50
timeout: 15000
- assertVisible: "Recent Payments"

View File

@@ -0,0 +1,19 @@
# Staff App — Attire section (Profile > Onboarding > Attire)
# Run: maestro test auth/sign_in.yaml profile/attire.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Attire"
visibilityPercentage: 50
timeout: 15000
- tapOn: "Attire"
- extendedWaitUntil:
visible: "Verify Attire"
timeout: 15000

View File

@@ -0,0 +1,37 @@
# 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: "(?i)Bank Account"
visibilityPercentage: 50
timeout: 10000
- tapOn: "(?i)Bank Account"
- extendedWaitUntil:
visible: "(?i)Bank Account"
timeout: 10000
# The AppBar title should be Bank Account
- assertVisible: "(?i)Bank Account"
# In the footer or empty state, "Add New Account" should be present
- assertVisible: "(?i)Add New Account"
# Optional: AccountCard.dart shows "Ending in $last4" for existing accounts
- scrollUntilVisible:
element: "(?i)Ending in .*"
visibilityPercentage: 50
timeout: 10000
optional: true
- assertVisible:
text: "(?i)Ending in .*"
optional: true

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,20 @@
# Staff App — Experience section (Profile > Onboarding > Experience)
# Run: maestro test auth/sign_in.yaml profile/experience.yaml -e TEST_STAFF_PHONE=... -e TEST_STAFF_OTP=...
appId: com.krowwithus.staff
---
- launchApp
- tapOn: "Profile"
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element: "Experience"
visibilityPercentage: 50
timeout: 15000
- tapOn: "Experience"
# Landing assertion kept flexible; title should exist somewhere on screen
- extendedWaitUntil:
visible: "Experience"
timeout: 15000

Some files were not shown because too many files have changed in this diff Show More