E2E Testing Quick Start Guide
Quick reference for setting up and running E2E tests for the FishingLog platform.
Prerequisites
- Docker (for TestContainers/PostgreSQL)
- Node.js 20+ (for frontend tests)
- .NET 9.0 SDK (for backend tests)
- AWS CLI (for Cognito test users)
- Stripe CLI (for webhook testing)
Backend E2E Tests
Setup
- Create test project:
dotnet new xunit -n FishingLog.API.Tests -o FishingLog.API.Tests
cd FishingLog.API.Tests
dotnet add reference ../FishingLog.API/FishingLog.API.csproj
- Add required packages:
dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Testcontainers.PostgreSql
dotnet add package Moq
dotnet add package FluentAssertions
- Create test infrastructure:
// TestWebApplicationFactory.cs
public class TestWebApplicationFactory : WebApplicationFactory<Program>
{
private readonly PostgreSQLContainer _postgres = new PostgreSQLBuilder()
.WithImage("postgres:16")
.WithDatabase("fishinglog_test")
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
_postgres.Start();
builder.UseEnvironment("Test");
builder.ConfigureServices(services =>
{
// Replace database connection
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null) services.Remove(descriptor);
services.AddDbContext<AppDbContext>(options =>
{
options.UseNpgsql(_postgres.GetConnectionString());
});
});
}
protected override void Dispose(bool disposing)
{
_postgres?.Dispose();
base.Dispose(disposing);
}
}
Running Tests
cd FishingLog.API.Tests
dotnet test
Example Test
public class AuthControllerTests : IClassFixture<TestWebApplicationFactory>
{
private readonly HttpClient _client;
public AuthControllerTests(TestWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Login_WithValidCredentials_ReturnsTokens()
{
// Arrange
var request = new { username = "test@example.com", password = "Test123!" };
// Act
var response = await _client.PostAsJsonAsync("/api/auth/login", request);
// Assert
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<LoginResponse>();
Assert.NotNull(result.IdToken);
}
}
Web App E2E Tests (Playwright)
Setup
- Install Playwright:
cd web-app
npm install -D @playwright/test
npx playwright install
- Create
playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e/tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.API_URL || 'http://localhost:5000',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
- Create auth helper:
// e2e/helpers/auth.ts
import { Page } from '@playwright/test';
export async function loginAsTestUser(page: Page) {
await page.goto('/login');
await page.fill('[name="username"]', process.env.TEST_USERNAME || 'test@example.com');
await page.fill('[name="password"]', process.env.TEST_PASSWORD || 'Test123!');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
}
Running Tests
cd web-app
npm run test:e2e # Run tests
npm run test:e2e:ui # Run with UI
npm run test:e2e:debug # Debug mode
Example Test
// e2e/tests/auth.spec.ts
import { test, expect } from '@playwright/test';
import { loginAsTestUser } from '../helpers/auth';
test.describe('Authentication', () => {
test('user can login and see dashboard', async ({ page }) => {
await loginAsTestUser(page);
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
});
Mobile App E2E Tests (Detox)
Setup
- Install Detox:
cd mobile-app
npm install -D detox
npm install -D jest-circus
- Create
.detoxrc.js:
module.exports = {
testRunner: {
args: ['--maxWorkers=1'],
jest: {
setupTimeout: 120000,
},
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/FishingLog.app',
build: 'xcodebuild -workspace ios/FishingLog.xcworkspace -scheme FishingLog -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
},
devices: {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 14',
},
},
},
configurations: {
'ios.sim.debug': {
device: 'simulator',
app: 'ios.debug',
},
},
};
- Create auth helper:
// e2e/helpers/auth.ts
export async function loginAsTestUser() {
await element(by.id('username-input')).typeText('test@example.com');
await element(by.id('password-input')).typeText('Test123!');
await element(by.id('login-button')).tap();
await waitFor(element(by.id('home-screen'))).toBeVisible().withTimeout(5000);
}
Running Tests
cd mobile-app
npm run build:ios # Build iOS app
npm run test:e2e:ios # Run iOS tests
Example Test
// e2e/tests/auth.e2e.ts
import { loginAsTestUser } from '../helpers/auth';
describe('Authentication', () => {
beforeAll(async () => {
await device.launchApp();
});
it('user can login and see home screen', async () => {
await loginAsTestUser();
await expect(element(by.id('home-screen'))).toBeVisible();
});
});
Test Data Setup
Create Test Users in Cognito
# Admin test user
aws cognito-idp admin-create-user \
--user-pool-id us-east-2_TZtGx1T3X \
--username admin-test@fishinglog.test \
--user-attributes Name=email,Value=admin-test@fishinglog.test \
--temporary-password TempPass123! \
--message-action SUPPRESS
# Set permanent password
aws cognito-idp admin-set-user-password \
--user-pool-id us-east-2_TZtGx1T3X \
--username admin-test@fishinglog.test \
--password TestPassword123! \
--permanent
# Regular test user
aws cognito-idp admin-create-user \
--user-pool-id us-east-2_TZtGx1T3X \
--username user-test@fishinglog.test \
--user-attributes Name=email,Value=user-test@fishinglog.test \
--temporary-password TempPass123! \
--message-action SUPPRESS
aws cognito-idp admin-set-user-password \
--user-pool-id us-east-2_TZtGx1T3X \
--username user-test@fishinglog.test \
--password TestPassword123! \
--permanent
Environment Variables
Create .env.test files:
Backend:
ASPNETCORE_ENVIRONMENT=Test
ConnectionStrings__DefaultConnection=Host=localhost;Database=fishinglog_test;...
Stripe__SecretKey=sk_test_...
Stripe__PublishableKey=pk_test_...
AWS__Cognito__UserPoolId=us-east-2_TZtGx1T3X
AWS__Cognito__ClientId=6i1opt39o2n5h5ihq471l1ev07
Frontend:
NEXT_PUBLIC_API_URL=http://localhost:5000
NEXT_PUBLIC_COGNITO_USER_POOL_ID=us-east-2_TZtGx1T3X
NEXT_PUBLIC_COGNITO_CLIENT_ID=6i1opt39o2n5h5ihq471l1ev07
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
TEST_USERNAME=user-test@fishinglog.test
TEST_PASSWORD=TestPassword123!
Stripe Testing
Test Cards
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - 3D Secure:
4000 0027 6000 3184
Webhook Testing
# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to local server
stripe listen --forward-to http://localhost:5000/api/stripe/webhook
CI/CD Integration
GitHub Actions
See e2e-testing-strategy.md for complete CI/CD setup examples.
Running Tests Locally Before Push
# Backend
cd FishingLog.API.Tests && dotnet test
# Web App
cd web-app && npm run test:e2e
# Mobile App
cd mobile-app && npm run test:e2e:ios
Troubleshooting
Backend Tests
Issue: Database connection fails
- Solution: Ensure Docker is running and TestContainers can start PostgreSQL
Issue: Authentication fails
- Solution: Check Cognito test user exists and credentials are correct
Web App Tests
Issue: Tests timeout
- Solution: Increase timeout in
playwright.config.ts
Issue: Element not found
- Solution: Use
data-testidattributes for stable selectors
Mobile App Tests
Issue: Simulator not starting
- Solution: Ensure Xcode command line tools are installed
Issue: Build fails
- Solution: Run
cd ios && pod installfirst
Next Steps
- Set up test projects using this guide
- Write tests for critical paths (auth, payments)
- Integrate with CI/CD
- Expand test coverage gradually
See E2E Testing Strategy for comprehensive strategy and best practices.