This guide explains how to adapt the MovieStar integration testing approach for different POD providers. For general adaptation, see Adapting for Your App.
Documentation index: See README.md for complete documentation navigation.
All Solid POD providers must implement the Solid-OIDC specification, which uses:
The core OAuth + DPoP + Puppeteer approach works with any compliant Solid POD provider. Only provider-specific UI selectors and configuration details need adjustment.
| Provider | Status | Notes |
|---|---|---|
| Community Solid Server (CSS) | Tested | Used in MovieStar |
| Node Solid Server (NSS) | Compatible | Solid-OIDC support |
| Enterprise Solid Server (ESS) | Compatible | Commercial offering |
| Custom implementations | Unknown | Must implement Solid-OIDC |
Used by: MovieStar integration tests
Login flow:
Token expiry: 3600 seconds (1 hour)
Refresh tokens: Supported but not always returned
Configuration:
{
"email": "test@example.com",
"password": "your-password",
"securityKey": "1234",
"issuer": "https://pods.dev.solidcommunity.au/"
}
Puppeteer selectors (current):
// Email field
await page.waitForSelector('input[name="email"]');
// Password field
await page.waitForSelector('input[name="password"]');
// Security key field
await page.waitForSelector('input[id="securityKey"]');
// Consent button
await page.$('button:has-text("Yes")');
Login flow:
Key differences from CSS:
username field instead of emailapprove instead of yesModify for NSS:
// In pod_auth_automator.dart
final usernameInput = await page.waitForSelector(
'input[name="username"]' // Changed from 'email'
);
final consentButton = await page.waitForSelector(
'button[name="approve"]' // Changed from 'yes'
);
Configuration:
{
"username": "testuser",
"password": "your-password",
"issuer": "https://your-nss-server.example.com/"
}
Login flow:
Key considerations:
Requires:
Example adaptation:
// Navigate to SSO login page
await page.goto(authUrl);
// Wait for SSO provider redirect
await page.waitForNavigation();
// Fill SSO credentials
await page.type('#sso-username', credentials['username']);
await page.type('#sso-password', credentials['password']);
// Submit SSO form
await page.click('button[type="submit"]');
// Handle consent screen (if separate)
await page.waitForSelector('.consent-form');
await page.click('button[name="consent"]');
Run browser automation in non-headless mode to inspect elements:
dart run integration_test/tools/generate_auth_data.dart --no-headless
Use Chrome DevTools to find selectors:
id, name, or class attributes#id, [name="value"], .classBy ID:
await page.waitForSelector('#username');
By name attribute:
await page.waitForSelector('input[name="email"]');
By class:
await page.waitForSelector('.login-button');
By text content:
await page.$('button:has-text("Login")');
By type and attribute:
await page.waitForSelector('input[type="password"]');
File: integration_test/helpers/pod_auth_automator.dart
Section: _fillLoginForm() method
Future<void> _fillLoginForm(pw.Page page) async {
// Update these selectors for your POD provider
final emailInput = await page.waitForSelector(
'input[name="email"]' // ← Change this
);
await emailInput.type(credentials['email']);
final passwordInput = await page.waitForSelector(
'input[name="password"]' // ← Change this
);
await passwordInput.type(credentials['password']);
// Security key may not exist for all providers
if (credentials.containsKey('securityKey')) {
final securityKeyInput = await page.$(
'input[id="securityKey"]' // ← Change this
);
if (securityKeyInput != null) {
await securityKeyInput.type(credentials['securityKey']);
}
}
// Submit button selector
final submitButton = await page.waitForSelector(
'button[type="submit"]' // ← Change this
);
await submitButton.click();
}
Symptom: Puppeteer timeout waiting for login form elements
Cause: Selector doesn’t match your provider’s HTML
Solution:
--no-headless to see browserpod_auth_automator.dartSymptom: Puppeteer can’t find consent button
Cause: Different consent screen implementation
Solution:
Try alternative button selectors:
// Option 1: By text
await page.$('button:has-text("Consent")');
// Option 2: By value
await page.$('button[value="consent"]');
// Option 3: By name
await page.$('input[name="authorize"]');
// Option 4: By class
await page.$('.consent-button');
Symptom: 400/401 error during client registration
Cause: Provider requires additional metadata fields
Solution:
Update client metadata in oauth_helpers.dart:
final clientMetadata = {
'client_name': 'YourApp E2E Test Client',
'redirect_uris': [redirectUri],
'grant_types': ['authorization_code', 'refresh_token'],
'response_types': ['code'],
'token_endpoint_auth_method': 'none',
'scope': 'openid profile offline_access', // May be required
'application_type': 'native', // Or 'web'
};
Symptom: solidpod package can’t parse tokens
Cause: Provider returns non-standard token structure
Solution:
Log token response for debugging:
print('Token response: ${jsonEncode(tokenResponse)}');
Verify it contains required fields:
access_token (string)id_token (string)token_type (must be “DPoP”)expires_in or expires_at (integer)| Feature | CSS | NSS | ESS |
|---|---|---|---|
| Login field | username | Varies | |
| 2FA support | Security key | No | Varies |
| Consent screen | “Yes” button | “Approve” | Custom |
| Token expiry | 3600s | Varies | Configurable |
| Refresh tokens | Sometimes | Yes | Yes |
| DPoP required | Yes | Yes | Yes |
Run manual extraction to test selectors:
flutter run integration_test/tools/generate_auth_data.dart -d linux
If login succeeds, selectors are correct.
Run automated extraction:
dart run integration_test/tools/generate_auth_data.dart
Should complete in 15-20 seconds without errors.
Check generated file structure:
cat integration_test/fixtures/complete_auth_data.json | jq .
Should contain web_id, rsa_info, auth_response.
Test with your app:
flutter test integration_test/workflows/your_test.dart \
-d linux --dart-define=INTERACT=0