End-to-end UI tests for validating parity between the legacy Django/React platform and the new Phoenix/Elixir implementation during the strangling fig migration.
CivicTechExchange is undergoing a platform rewrite from Django/React to Phoenix/Elixir using the strangler fig pattern. This test suite exists to:
- Ensure feature parity between old and new implementations
- Catch UI regressions during incremental migration
- Provide confidence when routing traffic between systems
- Document expected behavior through executable specifications
- Independent: This test suite lives in its own repository, separate from both the Django/React and Phoenix/Elixir codebases
- Portable: Can be run locally on any developer machine
- Language: Written in JavaScript/TypeScript
- Framework: Playwright for cross-browser E2E testing
- Node.js 18+
- npm or yarn
# Clone the repository
git clone <repository-url>
cd civic-tech-exchange-ui-tests
# Install dependencies
npm install
# Install Playwright browsers (first time only)
npx playwright installCopy the example environment file and configure URLs:
cp .env.example .envEdit .env to point to your running applications:
# Run all tests (default config)
npm test
# Run with Playwright UI (interactive mode)
npm run test:ui
# Run with browser visible
npm run test:headed
# Run in debug mode
npm run test:debugnpm run test:chromium
npm run test:firefox
npm run test:webkit# View default report
npm run report
# View legacy-specific report
npm run report:legacy
# View phoenix-specific report
npm run report:phoenixcivic-tech-exchange-ui-tests/
├── config/
│ ├── legacy.config.ts # Config for Django/React app
│ └── phoenix.config.ts # Config for Phoenix/Elixir app
├── tests/
│ ├── fixtures/
│ │ └── test-data.ts # Shared test data
│ ├── helpers/
│ │ └── page-helpers.ts # Common page interactions
│ └── example.spec.ts # Example test file
├── playwright.config.ts # Base Playwright config
├── tsconfig.json
├── package.json
└── README.md
Tests are written using Playwright Test. Each test should:
- Work identically against both legacy and new implementations
- Use flexible selectors that don't rely on implementation details
- Focus on user-visible behavior, not internal structure
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test('should allow users to log in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel(/email/i).fill('user@example.com');
await page.getByLabel(/password/i).fill('password123');
await page.getByRole('button', { name: /sign in|log in/i }).click();
// Verify login succeeded - works for both implementations
await expect(page.getByRole('button', { name: /log out|profile/i })).toBeVisible();
});
});- Use role-based selectors:
getByRole(),getByLabel(),getByText() - Avoid implementation-specific selectors: Don't use CSS classes or React/Phoenix-specific attributes
- Use regex for flexibility:
/sign in|log in/imatches both "Sign In" and "Log In" - Wait for network idle: Use
waitForLoadState('networkidle')for async operations - Abstract differences: Put implementation-specific logic in helper functions
In CI, run tests against both implementations in parallel:
jobs:
test-legacy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:legacy
test-phoenix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:phoenixThis is expected during migration! Document the difference and decide:
- Is this a bug in the new implementation? Fix it.
- Is this intentional new behavior? Update the test.
- Is this a known gap? Mark the test as skipped for that config.
- Add explicit waits:
await page.waitForLoadState('networkidle') - Use
toBeVisible()before interacting with elements - Increase timeout for slow operations
- Use test retries:
test.describe.configure({ retries: 2 })
Run in specific browser to debug:
npx playwright test --project=webkit --debug- Write tests that pass against the legacy implementation first
- Verify they also pass against Phoenix when that feature is migrated
- Use flexible selectors that work for both
- Document any intentional behavioral differences
MIT