This document describes the detailed execution flows for integration tests. For component overview, see Architecture.
Documentation index: See README.md for complete documentation navigation.
Integration tests follow two main execution paths depending on token validity:
When complete_auth_data.json contains valid tokens:
sequenceDiagram
participant Test
participant Injector as CredentialInjector
participant Storage as FlutterSecureStorage
participant App as MovieStar App
participant Solid as Solidpod Package
participant POD as POD Server
Test->>Injector: injectFullAuth()
Injector->>Injector: loadCompleteAuthData()
Note over Injector: Tokens valid (< 1 hour old)
Injector->>Storage: write('_solid_auth_data', authDataJson)
Storage-->>Injector: OK
Injector-->>Test: Success
Test->>App: Launch app
App->>Solid: Initialize
Solid->>Storage: read('_solid_auth_data')
Storage-->>Solid: Auth data JSON
Solid->>Solid: Parse auth data
Solid->>Solid: Load RSA private key
Note over App,Solid: App recognizes as authenticated
Test->>App: Tap "Favorites"
App->>Solid: Get favorites from POD
Solid->>Solid: Generate DPoP proof
Solid->>POD: GET /favorites<br/>Auth: DPoP token<br/>DPoP: proof
POD->>POD: Verify DPoP signature
POD-->>Solid: Favorites data
Solid-->>App: Display favorites
App-->>Test: Assertion passes
Phase 1: Auth Injection (< 1 second)
complete_auth_data.json from fixtures directory_solid_auth_data (contract with solidpod package)Phase 2: App Launch (2-5 seconds)
app.main() to start MovieStar applicationPhase 3: Test Assertions
| Step | Duration |
|---|---|
| Load auth data | < 100ms |
| Check expiry | < 10ms |
| Write to storage | < 500ms |
| App launch | 2-5s |
| Total | ~3-6s |
When tokens are expired and autoRegenerateOnFailure: true:
sequenceDiagram
participant Test
participant Injector as CredentialInjector
participant Automator as PodAuthAutomator
participant Browser as Puppeteer
participant POD as POD Server
participant Storage as FlutterSecureStorage
Test->>Injector: injectFullAuth(autoRegenerateOnFailure: true)
Injector->>Injector: loadCompleteAuthData()
Note over Injector: Tokens expired (> 1 hour old)
Injector->>Injector: needsRegeneration = true
Injector->>Automator: authenticate()
Automator->>Automator: loadCredentials()
Automator->>Browser: Launch headless Chrome
Automator->>POD: Register OAuth client
POD-->>Automator: client_id
Automator->>Browser: Navigate to auth URL
Automator->>Browser: Fill credentials
Browser->>POD: Submit login
POD->>Browser: Consent screen
Automator->>Browser: Click "Yes"
Browser-->>Automator: Callback with auth code
Automator->>Automator: Generate RSA keypair
Automator->>POD: Exchange code for tokens
POD-->>Automator: Fresh DPoP tokens
Automator->>Automator: Build complete auth data
Automator-->>Injector: Return auth data + tokens
Injector->>Injector: Save to complete_auth_data.json
Injector->>Storage: write('_solid_auth_data', authDataJson)
Storage-->>Injector: OK
Injector-->>Test: Success (with fresh tokens)
Note over Test: Test continues normally
Phase 1: Token Expiry Detection
complete_auth_data.jsonexpires_at timestamp from auth responsePhase 2: Browser Automation (15-20 seconds)
PodAuthAutomator.authenticate()test_credentials.jsonPhase 3: Token Exchange
Phase 4: Persistence
complete_auth_data.jsonPhase 5: Test Continues
| Step | Duration |
|---|---|
| Detect expiry | < 100ms |
| Launch browser | 2-3s |
| OAuth flow | 10-15s |
| RSA generation | 1-2s |
| Token exchange | 1-2s |
| Save to disk | < 500ms |
| Total | ~15-25s |
Tests must wait for app to fully initialize:
app.main();
await tester.pumpAndSettle(const Duration(seconds: 5));
await Future.delayed(delay); // 2s for styling to load
await tester.pump(interact); // Visual inspection if needed
Why each delay:
pumpAndSettle() - Wait for animations and microtasksFuture.delayed(delay) - Allow theming/styling to applytester.pump(interact) - Optional visual review (0s in qtest)FlutterSecureStorage writes are async but awaited:
await CredentialInjector.injectFullAuth();
// Auth data guaranteed written before app launch
app.main();
If not awaited, app might launch before auth data is written, causing test to fail with “not logged in” state.
CredentialInjector uses 1-minute buffer for expiry checks:
final expiryTime =
DateTime.fromMillisecondsSinceEpoch(expiresAt * 1000);
final bufferTime = DateTime.now().add(const Duration(minutes: 1));
if (expiryTime.isBefore(bufferTime)) {
// Treat as expired, regenerate
}
Why buffer: Prevents race condition where tokens expire during test execution.
Symptom:
OpenIdException(invalid_grant): grant request is invalid
Cause: OAuth tokens expired mid-test (> 1 hour old)
Handled by:
CredentialInjector._isTokenExpired() checks timestamp before
injectionautoRegenerateOnFailure: true, triggers
regenerationManual recovery:
dart run integration_test/tools/generate_auth_data.dart
Symptom:
TimeoutException: Waiting for login form timed out
Cause: POD server down, network issues, or incorrect selectors
Handled by:
PodAuthAutomator returns AuthResult with success: falseRecovery:
test_credentials.json--no-headless to debug selectorsSymptom:
Exception: Test credentials file not found
Cause: test_credentials.json doesn’t exist in fixtures directory
Handled by:
Recovery:
Create credentials file:
mkdir -p integration_test/fixtures
cat > integration_test/fixtures/test_credentials.json <<EOF
{
"email": "test@example.com",
"password": "your-password",
"securityKey": "1234",
"issuer": "https://pods.example.com/"
}
EOF
Run before executing multiple tests:
dart run integration_test/tools/generate_auth_data.dart
make qtest # All tests use pre-generated tokens
Avoids regeneration overhead for each test file.
For CI/CD, pre-generate fresh tokens and disable auto-regeneration:
- name: Generate auth data
run: dart run integration_test/tools/generate_auth_data.dart
- name: Run tests
run: |
flutter test integration_test/test1.dart \
-d linux --dart-define=AUTO_REGENERATE=false
Ensures consistent CI execution time (no random regeneration delays).
Not currently implemented: Puppeteer browser instance caching
Potential optimization: Launch browser once, reuse for multiple auth flows
Complexity: Browser state management, session cleanup between runs
1. Test Credentials (test_credentials.json)
└─> Git-ignored, contains real passwords
└─> Read by PodAuthAutomator
2. OAuth Flow
└─> Browser automation with real POD server
└─> Returns authorization code
3. Token Exchange
└─> Authorization code + RSA keys
└─> Returns access_token, id_token
4. Complete Auth Data (complete_auth_data.json)
└─> Git-ignored, contains tokens + private keys
└─> Written to disk for reuse
5. FlutterSecureStorage
└─> Platform-encrypted storage
└─> App reads on launch
6. Cleanup
└─> Tests clear storage after execution
└─> Files remain on disk (manual deletion)