Skip to main content

End-to-End Testing

This document outlines the end-to-end (E2E) testing strategy for the BookWish platform, covering cross-platform testing for mobile, web, and API integration.

Overview

End-to-end tests verify complete user flows across the entire BookWish ecosystem:

  • Flutter mobile app (iOS/Android)
  • Next.js store websites
  • Backend API
  • Third-party integrations (Stripe, Square, Firebase)

Current Status

E2E testing is not yet implemented for the BookWish platform. This document serves as a specification for future implementation.

Testing Framework Options

For Flutter Mobile App

# pubspec.yaml
dev_dependencies:
integration_test:
sdk: flutter

Advantages:

  • Native Flutter support
  • Runs on real devices and emulators
  • No additional setup required
  • Direct access to Flutter widgets

Example:

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:bookwish/main.dart' as app;

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('BookWish E2E Tests', () {
testWidgets('Complete book purchase flow', (tester) async {
app.main();
await tester.pumpAndSettle();

// Sign in
await tester.tap(find.text('Sign In'));
await tester.pumpAndSettle();

await tester.enterText(find.byKey(Key('email')), 'test@example.com');
await tester.enterText(find.byKey(Key('password')), 'password123');
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();

// Search for book
await tester.tap(find.byIcon(Icons.search));
await tester.pumpAndSettle();

await tester.enterText(find.byType(TextField), 'Harry Potter');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();

// Select book
await tester.tap(find.byType(BookCard).first);
await tester.pumpAndSettle();

// Add to cart
await tester.tap(find.text('Add to Cart'));
await tester.pumpAndSettle();

// Verify cart
expect(find.text('1 item'), findsOneWidget);
});
});
}

Option 2: Detox (Alternative)

For React Native-style E2E testing on Flutter:

{
"detox": {
"configurations": {
"ios.sim.debug": {
"device": "iPhone 14",
"app": "ios/build/Build/Products/Debug-iphonesimulator/BookWish.app"
}
}
}
}

For Next.js Store Websites

// tests/e2e/store-flow.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Store Website E2E', () => {
test('User can browse and add book to wishlist', async ({ page }) => {
// Navigate to store
await page.goto('https://bookwish.shop/mycoolbookstore');

// Verify store loads
await expect(page.locator('h1')).toContainText('My Cool Bookstore');

// Browse books
await page.click('text=Browse Books');
await expect(page).toHaveURL(/.*\/books/);

// Search for book
await page.fill('input[name="search"]', 'fiction');
await page.click('button:has-text("Search")');
await page.waitForLoadState('networkidle');

// Click first book
await page.click('.book-card >> nth=0');

// Add to wishlist (requires auth)
await page.click('text=Add to BookWish');

// Should redirect to login if not authenticated
await expect(page).toHaveURL(/.*\/login/);
});

test('Authenticated user can add to wishlist', async ({ page }) => {
// Login first
await page.goto('https://bookwish.io/login');
await page.fill('input[type="email"]', 'test@example.com');
await page.fill('input[type="password"]', 'password');
await page.click('button:has-text("Sign In")');

// Navigate to store
await page.goto('https://bookwish.shop/mycoolbookstore/books/123');

// Add to wishlist
await page.click('text=Add to BookWish');

// Select wishlist
await page.click('text=Reading List');

// Verify success
await expect(page.locator('text=Added to wishlist')).toBeVisible();
});
});

Option 2: Cypress (Alternative)

// cypress/e2e/store-flow.cy.ts
describe('Store E2E Tests', () => {
it('should display store homepage', () => {
cy.visit('/mycoolbookstore');
cy.contains('h1', 'My Cool Bookstore').should('be.visible');
cy.get('.featured-books').should('have.length.at.least', 1);
});

it('should search and filter books', () => {
cy.visit('/mycoolbookstore/books');
cy.get('input[name="search"]').type('science fiction');
cy.get('button[type="submit"]').click();
cy.url().should('include', 'search=science+fiction');
cy.get('.book-card').should('have.length.at.least', 1);
});
});

Critical User Flows to Test

1. Mobile App (Flutter)

Authentication Flow

testWidgets('User can sign up and sign in', (tester) async {
// Sign up
await tester.tap(find.text('Create Account'));
await tester.enterText(find.byKey(Key('email')), 'newuser@test.com');
await tester.enterText(find.byKey(Key('password')), 'SecurePass123');
await tester.tap(find.text('Sign Up'));
await tester.pumpAndSettle();

expect(find.text('Welcome'), findsOneWidget);

// Sign out
await tester.tap(find.byIcon(Icons.person));
await tester.tap(find.text('Sign Out'));
await tester.pumpAndSettle();

// Sign in
await tester.tap(find.text('Sign In'));
await tester.enterText(find.byKey(Key('email')), 'newuser@test.com');
await tester.enterText(find.byKey(Key('password')), 'SecurePass123');
await tester.tap(find.text('Login'));
await tester.pumpAndSettle();

expect(find.text('Welcome back'), findsOneWidget);
});

Book Discovery & Purchase

testWidgets('User can discover and purchase book', (tester) async {
// Search for book
await tester.tap(find.byIcon(Icons.search));
await tester.enterText(find.byType(TextField), 'The Great Gatsby');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();

// Select book
await tester.tap(find.byType(BookCard).first);
await tester.pumpAndSettle();

// Add to cart
await tester.tap(find.text('Add to Cart'));
await tester.pumpAndSettle();

// Go to cart
await tester.tap(find.byIcon(Icons.shopping_cart));
await tester.pumpAndSettle();

// Checkout
await tester.tap(find.text('Checkout'));
await tester.pumpAndSettle();

// Fill shipping info
await tester.enterText(find.byKey(Key('address')), '123 Main St');
await tester.enterText(find.byKey(Key('city')), 'New York');
await tester.tap(find.text('Continue'));
await tester.pumpAndSettle();

// Verify payment screen
expect(find.text('Payment'), findsOneWidget);
});

Wishlist Management

testWidgets('User can create and manage wishlists', (tester) async {
// Create wishlist
await tester.tap(find.text('Wish'));
await tester.tap(find.byIcon(Icons.add));
await tester.enterText(find.byKey(Key('wishlist_name')), 'Summer Reading');
await tester.tap(find.text('Create'));
await tester.pumpAndSettle();

// Add book to wishlist
await tester.tap(find.byIcon(Icons.search));
await tester.enterText(find.byType(TextField), 'fiction');
await tester.pumpAndSettle();

await tester.tap(find.byType(BookCard).first);
await tester.tap(find.text('Add to Wishlist'));
await tester.tap(find.text('Summer Reading'));
await tester.pumpAndSettle();

expect(find.text('Added to wishlist'), findsOneWidget);
});

2. Store Website (Next.js)

Store Discovery

test('User can discover store and browse inventory', async ({ page }) => {
await page.goto('https://bookwish.shop');

// Search for store
await page.fill('input[placeholder*="search"]', 'bookstore');
await page.keyboard.press('Enter');

// Click store from results
await page.click('.store-card >> nth=0');

// Verify store page loads
await expect(page.locator('h1')).toBeVisible();

// Browse books
await page.click('text=Browse Books');
await expect(page.locator('.book-card')).toHaveCount.greaterThan(0);
});

Book Detail & Add to Wishlist

test('User can view book details and add to wishlist', async ({ page, context }) => {
// Login to BookWish first
await page.goto('https://bookwish.io/login');
await page.fill('input[type="email"]', process.env.TEST_EMAIL);
await page.fill('input[type="password"]', process.env.TEST_PASSWORD);
await page.click('button[type="submit"]');
await page.waitForNavigation();

// Visit store book page
await page.goto('https://bookwish.shop/mycoolbookstore/books/123');

// Verify book details
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('text=/\\$\\d+\\.\\d{2}/')).toBeVisible();

// Add to wishlist
await page.click('text=Add to BookWish');
await page.click('text=My Wishlist');

// Verify success
await expect(page.locator('text=Added to wishlist')).toBeVisible();
});

3. Cross-Platform Integration

Store to App Flow

test('User can navigate from store website to app', async ({ page }) => {
await page.goto('https://bookwish.shop/mycoolbookstore');

// Click "Get the App" or similar CTA
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('text=Get the App')
]);

// Verify app download page
await expect(popup).toHaveURL(/.*bookwish\.io/);
});

API Integration Testing

Backend API Tests

import { test, expect } from '@playwright/test';

test.describe('API Integration', () => {
test('API returns correct book data', async ({ request }) => {
const response = await request.get(
'https://api.bookwish.io/books/search?q=fiction'
);

expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.books).toBeDefined();
expect(data.books.length).toBeGreaterThan(0);
});

test('Authenticated requests work correctly', async ({ request }) => {
const token = process.env.TEST_AUTH_TOKEN;

const response = await request.get('https://api.bookwish.io/users/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});

expect(response.ok()).toBeTruthy();
const user = await response.json();
expect(user.email).toBeDefined();
});
});

Test Data Management

Test User Setup

// test-setup.ts
export const TEST_USERS = {
regular: {
email: 'test.user@bookwish.test',
password: 'TestPassword123!',
tier: 'free'
},
premium: {
email: 'premium.user@bookwish.test',
password: 'TestPassword123!',
tier: 'premium'
},
bookstore: {
email: 'bookstore.owner@bookwish.test',
password: 'TestPassword123!',
tier: 'bookstore'
}
};

export async function createTestUser(userData) {
// Create user via API
const response = await fetch('https://api.bookwish.io/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
return response.json();
}

export async function cleanupTestUsers() {
// Delete test users after tests
for (const user of Object.values(TEST_USERS)) {
await fetch(`https://api.bookwish.io/users/${user.email}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${ADMIN_TOKEN}` }
});
}
}

Test Database

import { PrismaClient } from '@prisma/client';

export const testDb = new PrismaClient({
datasources: {
db: {
url: process.env.TEST_DATABASE_URL
}
}
});

export async function seedTestData() {
// Create test books
await testDb.book.createMany({
data: [
{ isbn13: '9781234567890', title: 'Test Book 1', authors: ['Author 1'] },
{ isbn13: '9780987654321', title: 'Test Book 2', authors: ['Author 2'] },
]
});

// Create test store
await testDb.store.create({
data: {
name: 'Test Bookstore',
slug: 'test-bookstore',
websiteEnabled: true
}
});
}

export async function cleanupTestData() {
await testDb.book.deleteMany({
where: { isbn13: { startsWith: '978' } }
});
await testDb.store.deleteMany({
where: { slug: 'test-bookstore' }
});
}

Running E2E Tests

Flutter Integration Tests

# Run on connected device
flutter test integration_test/

# Run on specific device
flutter test integration_test/ -d <device_id>

# Run on iOS simulator
flutter test integration_test/ -d "iPhone 14"

# Run on Android emulator
flutter test integration_test/ -d emulator-5554

Playwright Tests

# Install browsers
npx playwright install

# Run all tests
npx playwright test

# Run specific test file
npx playwright test tests/e2e/store-flow.spec.ts

# Run in headed mode (see browser)
npx playwright test --headed

# Run with UI mode
npx playwright test --ui

# Generate report
npx playwright show-report

Playwright Configuration

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'https://bookwish.shop',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},

projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
{ name: 'Mobile Safari', use: { ...devices['iPhone 12'] } },
],

webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});

CI/CD Integration

GitHub Actions for E2E Tests

name: E2E Tests

on: [push, pull_request]

jobs:
flutter-e2e:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
- run: flutter pub get
- run: flutter test integration_test/
working-directory: ./app

playwright-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
working-directory: ./stores
- run: npx playwright install --with-deps
- run: npx playwright test
working-directory: ./stores
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: stores/playwright-report/

Best Practices

1. Test Isolation

  • Each test should be independent
  • Clean up test data after each test
  • Use unique test data identifiers

2. Realistic Scenarios

  • Test complete user journeys
  • Include error scenarios
  • Test edge cases

3. Stable Selectors

// ✅ Good - stable selectors
await page.click('[data-testid="add-to-cart"]');
await page.click('button:has-text("Checkout")');

// ❌ Bad - fragile selectors
await page.click('.btn-primary.mt-4.px-6');
await page.click('div > div > button:nth-child(3)');

4. Wait Strategies

// Wait for network to be idle
await page.waitForLoadState('networkidle');

// Wait for specific element
await page.waitForSelector('[data-testid="book-list"]');

// Wait for API response
await page.waitForResponse(response =>
response.url().includes('/api/books')
);

5. Error Handling

test('handles network errors gracefully', async ({ page }) => {
// Simulate offline
await page.route('**/*', route => route.abort());

await page.goto('/books');

// Verify error message shown
await expect(page.locator('text=Network error')).toBeVisible();
});

Future Implementation Plan

Phase 1: Foundation

  1. Set up Playwright for store websites
  2. Create basic smoke tests
  3. Implement test data management

Phase 2: Core Flows

  1. Authentication flows
  2. Book search and discovery
  3. Wishlist management
  4. Cart and checkout (mock payments)

Phase 3: Flutter Integration

  1. Set up Flutter integration tests
  2. Implement critical user flows
  3. Cross-platform authentication

Phase 4: Advanced

  1. Visual regression testing
  2. Performance monitoring
  3. Load testing
  4. Mobile-specific flows (barcode scanning, camera)

Monitoring & Reporting

Test Metrics to Track

  • Test execution time
  • Pass/fail rates
  • Flaky test identification
  • Coverage of critical paths
  • Browser/device compatibility

Reporting Tools

  • Playwright HTML Reporter
  • Allure Reports
  • TestRail integration
  • Slack notifications for failures