This guide helps you adapt MovieStar’s POD authentication testing approach for your own Solid POD application.
For provider-specific details: See Provider Compatibility
For CI/CD setup: See CI/CD Integration
Documentation index: See README.md for complete documentation navigation.
Setup Phase:
Testing Phase:
CI/CD Phase:
These files can be copied directly to your project with minimal changes:
integration_test/helpers/
├── credential_injector.dart # Reusable
└── pod_auth_automator.dart # Reusable
integration_test/tools/
└── generate_auth_data.dart # Reusable
integration_test/utils/
└── delays.dart # Reusable
What these provide:
integration_test/workflows/
└── your_app_test.dart # Write your own tests
integration_test/fixtures/
├── test_credentials.json # Your POD credentials
└── complete_auth_data.json # Auto-generated
Add to your pubspec.yaml:
dev_dependencies:
integration_test:
sdk: flutter
puppeteer: ^3.19.0 # Browser automation for OAuth
pointycastle: ^3.9.1 # RSA key generation for DPoP
flutter_secure_storage: ^9.0.0 # Secure credential storage
Location: Multiple files reference the POD provider
MovieStar default:
'https://pods.dev.solidcommunity.au'
Change to:
Files to update:
// integration_test/fixtures/test_credentials.json
{
"issuer": "https://your-pod-provider.com",
"podUrl": "https://your-pod-provider.com/username/",
"webId": "https://your-pod-provider.com/username/profile/card#me"
}
Making it configurable:
// Add to pod_auth_automator.dart
static String getPodProvider() {
return const String.fromEnvironment(
'POD_PROVIDER',
defaultValue: 'https://pods.dev.solidcommunity.au',
);
}
Location: integration_test/helpers/pod_auth_automator.dart
MovieStar default:
final redirectUri = 'http://localhost:44007/';
Change to:
http://localhost:<port>/ for desktop appsExample:
// Your app uses port 45000
final redirectUri = 'http://localhost:45000/';
Note: Update in both:
pod_auth_automator.dart - Puppeteer automationLocation: integration_test/helpers/pod_auth_automator.dart
MovieStar default:
'client_name': 'MovieStar E2E Test Client',
Change to:
'client_name': 'YourApp E2E Test Client',
This name appears in POD server logs and consent screens.
Location: integration_test/fixtures/test_credentials.json
Create with your POD account:
cat > integration_test/fixtures/test_credentials.json <<'EOF'
{
"email": "your-test-account",
"password": "your-test-password",
"securityKey": "your-2fa-key",
"webId": "https://your-pod-provider.com/test/profile/card#me",
"podUrl": "https://your-pod-provider.com/test/",
"issuer": "https://your-pod-provider.com"
}
EOF
Security: Use a dedicated test POD account, not your personal POD.
/// E2E test for YourApp POD operations.
///
/// Copyright (C) 2025, Your Organization.
library;
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_app/main.dart' as app;
import '../helpers/credential_injector.dart';
import '../utils/delays.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('YourApp POD Tests', () {
setUpAll() async {
// Inject auth before app launch
await CredentialInjector.injectFullAuth(
autoRegenerateOnFailure: true,
);
});
tearDownAll() async {
// Clean up credentials
await CredentialInjector.clearCredentials();
});
testWidgets('app recognizes authenticated state', (tester) async {
app.main();
await tester.pumpAndSettle(const Duration(seconds: 5));
await Future.delayed(delay);
await tester.pump(interact);
// Your app-specific assertions
expect(find.text('Logged In'), findsOneWidget);
});
});
}
For apps that only read public data:
testWidgets('loads public profile from POD', (tester) async {
app.main();
await tester.pumpAndSettle();
// Navigate to profile screen
await tester.tap(find.text('Profile'));
await tester.pumpAndSettle();
// Verify data loaded from POD
expect(find.text('Name: Test User'), findsOneWidget);
});
For apps that modify POD data:
testWidgets('saves preferences to POD', (tester) async {
app.main();
await tester.pumpAndSettle();
// Modify a setting
await tester.tap(find.byKey(const Key('dark_mode_toggle')));
await tester.pumpAndSettle();
// Save to POD
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
// Verify saved
expect(find.text('Saved to POD'), findsOneWidget);
});
For apps that work with multiple PODs:
testWidgets('shares data between PODs', (tester) async {
await CredentialInjector.injectFullAuth();
app.main();
await tester.pumpAndSettle();
// Share data with another POD
await tester.tap(find.text('Share'));
await tester.enterText(
find.byKey(const Key('recipient_webid')),
'https://pod-b.example.com/user/profile/card#me'
);
await tester.tap(find.text('Send'));
await tester.pumpAndSettle();
expect(find.text('Shared successfully'), findsOneWidget);
});
Copy the reusable components to your project:
# Copy helpers
cp -r integration_test/helpers your-project/integration_test/
# Copy tools
cp -r integration_test/tools your-project/integration_test/
# Copy utils
cp -r integration_test/utils your-project/integration_test/
# Optional: Copy docs for reference
cp -r integration_test/docs your-project/integration_test/
pod_auth_automator.darttest_credentials.json with your POD accountGenerate auth data to verify setup:
dart run integration_test/tools/generate_auth_data.dart
Should complete in 15-20 seconds and create
complete_auth_data.json.
Create your first test in integration_test/workflows/:
touch integration_test/workflows/your_app_test.dart
Use the template above and add your app-specific assertions.
# Quick test
flutter test integration_test/workflows/your_app_test.dart \
-d linux --dart-define=INTERACT=0
# Interactive test (for debugging)
flutter test integration_test/workflows/your_app_test.dart \
-d linux --dart-define=INTERACT=5
Questions about adaptation:
adaptation-question labelContributing improvements:
To adapt for your app:
The core OAuth + DPoP + Puppeteer approach is fully reusable. Only application-specific test assertions and POD provider configuration need customization.