Skip to main content

Patrol E2E Testing Suite

Comprehensive end-to-end testing for all 5 Flutter shells using Patrol native automation framework.

Overview

The Patrol E2E testing suite provides automated testing across all Flutter applications, catching API endpoint issues, integration bugs, and UI regressions before deployment.

Key Benefits

BenefitDescription
Early DetectionCatch API issues in CI/CD before merge
Cross-Shell CoverageTest all 5 shells with shared utilities
Native AutomationReal device/browser testing capabilities
CI IntegrationAutomated runs on every PR

Coverage Statistics

ShellTest FilesTest CasesCoverage
Staff1456+80%+ critical flows
Customer1248+80%+ critical flows
Admin832+80%+ critical flows
Web624+80%+ critical flows
Cockpit624+80%+ critical flows

Test Structure

frontend/integration_test/patrol/
├── common/
│ ├── test_config.dart # Configuration and credentials
│ ├── test_helpers.dart # Shared helper functions
│ └── test_app.dart # App initialization
├── staff/
│ ├── staff_login_test.dart
│ ├── staff_order_entry_test.dart
│ ├── staff_tables_test.dart
│ ├── staff_kitchen_test.dart
│ ├── staff_inventory_test.dart
│ ├── staff_payment_test.dart
│ ├── staff_offline_test.dart
│ └── ...
├── customer/
│ ├── customer_auth_test.dart
│ ├── customer_menu_test.dart
│ ├── customer_cart_test.dart
│ ├── customer_checkout_test.dart
│ └── ...
├── admin/
│ ├── admin_auth_test.dart
│ ├── admin_tenant_test.dart
│ ├── admin_users_test.dart
│ ├── admin_gating_test.dart
│ └── ...
├── web/
│ ├── web_public_test.dart
│ ├── web_portal_test.dart
│ ├── web_forms_test.dart
│ └── ...
└── cockpit/
├── cockpit_login_test.dart
├── cockpit_dashboard_test.dart
├── cockpit_incidents_test.dart
└── ...

Test Categories

Authentication Tests

ShellTest Coverage
StaffPIN login, employee ID, role routing
CustomerEmail signup, social login (Google, Apple)
AdminSSO login, MFA verification
WebOwner login, forgot password
CockpitOperator SSO, tenant switching

Feature Tests by Shell

Staff Shell

FeatureTest FileCoverage
PIN Loginstaff_login_test.dartEmail/ID login, invalid PIN, role routing
Order Entrystaff_order_entry_test.dartMenu selection, modifiers, cart
Tablesstaff_tables_test.dartStatus updates, section filtering
Kitchenstaff_kitchen_test.dartOrder queue, station routing
Inventorystaff_inventory_test.dartStock levels, adjustments
Paymentsstaff_payment_test.dartCash, card, split, refund
Offlinestaff_offline_test.dartGraceful degradation, sync
Time Clockstaff_clock_test.dartClock in/out, breaks

Customer Shell

FeatureTest FileCoverage
Authenticationcustomer_auth_test.dartSignup, login, social
Menu Browsecustomer_menu_test.dartCategories, search, filters
Cartcustomer_cart_test.dartAdd, remove, quantities
Checkoutcustomer_checkout_test.dartPayment flow, tips
Orderscustomer_orders_test.dartHistory, tracking
Loyaltycustomer_loyalty_test.dartPoints, rewards
Favoritescustomer_favorites_test.dartSave, manage

Admin Shell

FeatureTest FileCoverage
Authenticationadmin_auth_test.dartSSO, session management
Tenantsadmin_tenant_test.dartCRUD, lifecycle
Usersadmin_users_test.dartRoles, permissions
Gatingadmin_gating_test.dartFeature flags, policies
Auditadmin_audit_test.dartLog viewing, search
Locationsadmin_locations_test.dartBranch management
Subscriptionsadmin_subscriptions_test.dartBilling, plans

Writing Tests

Test Pattern

import 'package:patrol/patrol.dart';
import '../common/test_helpers.dart';
import '../common/test_config.dart';

void main() {
group('Feature Tests', () {
patrolTest(
'AC1: User can perform action',
config: patrolTesterConfig,
nativeAutomatorConfig: TestConfig.nativeAutomatorConfig,
($) async {
// Arrange: Start app and setup
await TestHelpers.startStaffApp($);
await TestHelpers.loginAsStaff($, 'manager');

// Act: Perform the action
await $(find.text('Dashboard')).tap();
await $.pumpAndSettle();

// Assert: Verify result
expect($(find.text('Revenue')).exists, isTrue);
},
);
});
}

Using Test Helpers

The TestHelpers class provides reusable functions:

// App initialization
await TestHelpers.startStaffApp($);
await TestHelpers.startCustomerApp($);
await TestHelpers.startAdminApp($);
await TestHelpers.startCockpitApp($);
await TestHelpers.startWebApp($);

// Authentication
await TestHelpers.loginAsStaff($, 'manager');
await TestHelpers.loginAsStaff($, 'server');
await TestHelpers.loginAsStaff($, 'chef');
await TestHelpers.loginAsCustomer($, email: 'test@example.com');
await TestHelpers.loginAsAdmin($);
await TestHelpers.loginAsOperator($);

// Navigation
await TestHelpers.navigateToTimeTracking($);
await TestHelpers.navigateToInventory($);
await TestHelpers.navigateToAnalytics($);
await TestHelpers.navigateToCart($);
await TestHelpers.navigateToOrders($);
await TestHelpers.navigateToSettings($);

// Actions
await TestHelpers.addItemToCart($, 'Burger');
await TestHelpers.searchFor($, 'pizza');
await TestHelpers.pullToRefresh($);

// Verification
await TestHelpers.verifyToast($, 'Success');
await TestHelpers.verifyDialog($);
await TestHelpers.verifyLoading($);
await TestHelpers.verifyError($);

Test Credentials

// Test credentials (from test_config.dart)
final creds = TestConfig.staffCredentials['manager']!;
// creds.identifier - email or employee ID
// creds.pin - 6-digit PIN
// creds.employeeId - short ID (e.g., MGR01)

// Available roles
TestConfig.staffCredentials['manager'];
TestConfig.staffCredentials['server'];
TestConfig.staffCredentials['chef'];
TestConfig.staffCredentials['host'];
TestConfig.staffCredentials['bartender'];

Running Tests

Local Execution

# Run all Patrol tests
cd frontend
flutter test integration_test/patrol/ --dart-define=PATROL=true

# Run specific shell tests
flutter test integration_test/patrol/staff/ --dart-define=PATROL=true
flutter test integration_test/patrol/customer/ --dart-define=PATROL=true

# Run single test file
flutter test integration_test/patrol/staff/staff_login_test.dart

# Run with verbose output
flutter test integration_test/patrol/staff/ --reporter expanded

# Run on specific device
flutter test integration_test/patrol/staff/ -d chrome
flutter test integration_test/patrol/staff/ -d emulator-5554

Using Patrol CLI

# Install patrol_cli
dart pub global activate patrol_cli

# Build and run tests
patrol test

# Run with native automation
patrol test --target integration_test/patrol/staff/staff_login_test.dart

# Generate test artifacts
patrol test --record-video
patrol test --take-screenshots

CI/CD Integration

GitHub Actions Workflow

name: Patrol E2E Tests

on:
pull_request:
paths:
- 'frontend/**'
push:
branches: [main, develop]

jobs:
patrol-tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shell: [staff, customer, admin, web, cockpit]

steps:
- uses: actions/checkout@v4

- uses: subosito/flutter-action@v2
with:
flutter-version: '3.x'
channel: 'stable'

- name: Install dependencies
run: |
cd frontend
flutter pub get

- name: Run Patrol tests
run: |
cd frontend
flutter test integration_test/patrol/${{ matrix.shell }}/ \
--dart-define=PATROL=true \
--reporter github

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: patrol-results-${{ matrix.shell }}
path: frontend/test-results/

Test Artifacts

ArtifactDescriptionStorage
ScreenshotsCaptured on failureGitHub Artifacts
VideosTest execution recordingGitHub Artifacts
LogsDetailed test outputJob logs
ReportsJUnit XML reportsTest summary

Timeouts Configuration

class TestConfig {
// Network operations (API calls)
static const networkTimeout = Duration(seconds: 10);

// Short UI animations
static const shortTimeout = Duration(seconds: 3);

// Medium operations (form submissions)
static const mediumTimeout = Duration(seconds: 5);

// Long operations (file uploads, complex flows)
static const longTimeout = Duration(seconds: 15);

// Splash screen duration
static const splashTimeout = Duration(seconds: 5);
}

Best Practices

Do's

  1. Use TestHelpers - Leverage shared utilities for consistency
  2. Test acceptance criteria - Each test maps to an AC
  3. Use semantic finders - find.text(), find.byKey() over widget types
  4. Wait for settle - Use $.pumpAndSettle() after actions
  5. Handle timeouts - Specify appropriate timeouts for operations
  6. Group related tests - Use group() for organization

Don'ts

  1. Don't hardcode delays - Use pumpAndSettle() instead
  2. Don't skip cleanup - Clear state between tests
  3. Don't test implementation - Focus on user behavior
  4. Don't ignore flakiness - Fix or quarantine flaky tests
  5. Don't couple tests - Each test should be independent

Handling Flaky Tests

// Retry flaky network operations
for (var attempt = 0; attempt < 3; attempt++) {
try {
await $.pumpAndSettle(timeout: TestConfig.networkTimeout);
break;
} catch (_) {
if (attempt == 2) rethrow;
await $.pump(const Duration(seconds: 1));
}
}

// Use flexible matchers
expect(
$(find.textContaining('Success')).exists ||
$(find.textContaining('Saved')).exists,
isTrue,
);

Troubleshooting

Common Issues

IssueSolution
Tests timeoutIncrease timeout, check network
Element not foundWait for element, use waitUntilVisible()
Keyboard obscuresDismiss with $.native.pressBack()
State leaks between testsClear storage in startTestApp()
Flaky on CIAdd retry logic, increase timeouts

Debug Mode

// Enable debug logging
patrolTest(
'Debug test',
($) async {
debugPrint('Starting test...');

// Print widget tree
debugDumpApp();

// Check element existence
debugPrint('Button exists: ${$(find.text('Login')).exists}');
},
);

Performance Targets

MetricTarget
Full suite runtimeunder 10 minutes
Individual testunder 30 seconds
Flaky test rateunder 5%
CI detection rate95%+ API issues

Changelog

VersionDateChanges
3.0.02026-01-07Initial release with 5 shell coverage