Skip to content

Commit c41fb72

Browse files
Merge pull request #366 from Police-Data-Accessibility-Project/dev
feat: dev to main, map and sidebar updates, e2e testing
2 parents 7e5cf7a + b4fb542 commit c41fb72

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3673
-973
lines changed

.github/pull_request_template.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
- Adds this
2020
- Removes that
2121

22+
## Testing
23+
24+
- [ ] End-to-end tests are up to date with these changes
25+
2226
## Acceptance Criteria
2327

2428
<!-- What are the steps to test the changes? -->

.github/scripts/set-e2e-env.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
# Set E2E environment variables based on event type and inputs
4+
5+
if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then
6+
echo "E2E_TESTING_URL=$TEST_URL" >> $GITHUB_ENV
7+
8+
# Use provided API URL if it exists, otherwise use conditional logic
9+
if [[ -n "$API_URL" ]]; then
10+
echo "VITE_API_URL=$API_URL" >> $GITHUB_ENV
11+
elif [[ "$TEST_URL" == *"pdap.io"* ]]; then
12+
echo "VITE_API_URL=https://data-sources.pdap.io/api" >> $GITHUB_ENV
13+
echo "VITE_API_KEY=$E2E_API_KEY_PROD" >> $GITHUB_ENV
14+
echo "E2E_TESTING_ENV=production" >> $GITHUB_ENV
15+
else
16+
echo "VITE_API_URL=https://data-sources.pdap.dev/api" >> $GITHUB_ENV
17+
echo "VITE_API_KEY=$E2E_API_KEY_DEV" >> $GITHUB_ENV
18+
fi
19+
else
20+
# It's a PR. Run against prod if merging to main, dev otherwise
21+
if [[ $GITHUB_BASE_REF == "main" ]]; then
22+
echo "VITE_API_URL=https://data-sources.pdap.io/api" >> $GITHUB_ENV
23+
echo "VITE_API_KEY=$E2E_API_KEY_PROD" >> $GITHUB_ENV
24+
echo "E2E_TESTING_ENV=production" >> $GITHUB_ENV
25+
else
26+
echo "VITE_API_URL=https://data-sources.pdap.dev/api" >> $GITHUB_ENV
27+
echo "VITE_API_KEY=$E2E_API_KEY_DEV" >> $GITHUB_ENV
28+
fi
29+
fi

.github/workflows/e2e.yaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: E2E Tests
2+
on:
3+
pull_request:
4+
branches: [dev, main, feature/**]
5+
workflow_dispatch:
6+
inputs:
7+
test_url:
8+
description: 'Application URL to run E2E tests against (defaults to local, in order to test changes that are present in the PR)'
9+
required: true
10+
type: string
11+
default: 'http://localhost:8888'
12+
api_url:
13+
description: 'API URL to use (optional)'
14+
required: false
15+
type: string
16+
17+
jobs:
18+
e2e:
19+
timeout-minutes: 60
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 20.19.0
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Install Playwright Browsers
31+
run: npx playwright install --with-deps
32+
33+
- name: Set API URL and Test Credentials
34+
env:
35+
E2E_API_KEY_PROD: ${{ secrets.E2E_API_KEY_PROD }}
36+
E2E_API_KEY_DEV: ${{ secrets.E2E_API_KEY_DEV }}
37+
GITHUB_EVENT_NAME: ${{ github.event_name }}
38+
TEST_URL: ${{ github.event.inputs.test_url }}
39+
API_URL: ${{ github.event.inputs.api_url }}
40+
41+
run: bash .github/scripts/set-e2e-env.sh
42+
43+
- name: Run Playwright tests
44+
env:
45+
E2E_PASSWORD_AUTH_EMAIL_PROD: ${{ secrets.E2E_PASSWORD_AUTH_EMAIL_PROD }}
46+
E2E_PASSWORD_AUTH_PASSWORD_PROD: ${{ secrets.E2E_PASSWORD_AUTH_PASSWORD_PROD }}
47+
E2E_PASSWORD_AUTH_EMAIL_TEST: ${{ secrets.E2E_PASSWORD_AUTH_EMAIL_TEST }}
48+
E2E_PASSWORD_AUTH_PASSWORD_TEST: ${{ secrets.E2E_PASSWORD_AUTH_PASSWORD_TEST }}
49+
E2E_PW_RESET_EMAIL_PROD: ${{ secrets.E2E_PW_RESET_EMAIL_PROD }}
50+
E2E_PW_RESET_EMAIL_TEST: ${{ secrets.E2E_PW_RESET_EMAIL_TEST }}
51+
E2E_SIGNUP_EMAIL_PROD: ${{ secrets.E2E_SIGNUP_EMAIL_PROD }}
52+
E2E_SIGNUP_EMAIL_TEST: ${{ secrets.E2E_SIGNUP_EMAIL_TEST }}
53+
MAILGUN_KEY: ${{ secrets.MAILGUN_KEY }}
54+
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
55+
56+
57+
run: npx playwright test
58+
59+
- uses: actions/upload-artifact@v4
60+
if: always()
61+
with:
62+
name: playwright-report
63+
path: playwright-report/
64+
retention-days: 30
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: E2E Production Monitoring
2+
on:
3+
schedule:
4+
- cron: '0 * * * *' # Every hour
5+
workflow_dispatch:
6+
7+
jobs:
8+
e2e-prod:
9+
timeout-minutes: 30
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: 20.19.0
16+
17+
- name: Install dependencies
18+
run: npm ci
19+
20+
- name: Install Playwright Browsers
21+
run: npx playwright install --with-deps
22+
23+
- name: Run E2E tests against production
24+
env:
25+
VITE_API_URL: https://data-sources.pdap.io/api
26+
VITE_API_KEY: ${{ secrets.E2E_API_KEY_PROD }}
27+
E2E_TESTING_ENV: production
28+
E2E_TESTING_URL: https://pdap.io
29+
E2E_PASSWORD_AUTH_EMAIL_PROD: ${{ secrets.E2E_PASSWORD_AUTH_EMAIL_PROD }}
30+
E2E_PASSWORD_AUTH_PASSWORD_PROD: ${{ secrets.E2E_PASSWORD_AUTH_PASSWORD_PROD }}
31+
E2E_PW_RESET_EMAIL_PROD: ${{ secrets.E2E_PW_RESET_EMAIL_PROD }}
32+
E2E_SIGNUP_EMAIL_PROD: ${{ secrets.E2E_SIGNUP_EMAIL_PROD }}
33+
MAILGUN_KEY: ${{ secrets.MAILGUN_KEY }}
34+
MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }}
35+
run: npx playwright test
36+
37+
- uses: actions/upload-artifact@v4
38+
if: always()
39+
with:
40+
name: playwright-report-prod
41+
path: playwright-report/
42+
retention-days: 7

.github/workflows/pull.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
with:
6767
timezoneLinux: "America/New_York"
6868
- name: Run tests
69-
run: npm run test:ci
69+
run: npm run test:unit:ci
7070

7171
build:
7272
name: Build

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,9 @@ yarn-debug.log*
1212
yarn-error.log*
1313
pnpm-debug.log*
1414

15-
# TS files generated by router
15+
# TS files generated by router (not needed until/unless we port this app to TS)
1616
typed-router.d.ts
17+
18+
# Playwright
19+
test-results
20+
playwright-report

CONTRIBUTING.md

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ If you have questions as you're working, you can reach out to PDAP staff on [Dis
1313
Please raise a PR against the `dev` branch (unless otherwise specified), only after you have done the following:
1414

1515
- Verified that your changes resolve the problem or enhancement described in the issue.
16-
- Ensured that all existing unit and e2e tests pass. Or, if your changes make some part of the affected tests obsolete, please update the tests accordingly.
16+
- Ensured that all existing unit and E2E tests pass. Or, if your changes make some part of the affected tests obsolete, please update the tests accordingly.
17+
- Updated E2E tests if your changes affect user-facing functionality.
1718
- Ensured that the linter runs without errors.
1819
- Thoroughly completed the PR request template
1920

@@ -61,9 +62,56 @@ There are two exceptions to the routing rules that are both defined by our confi
6162

6263
Currently, there are unit tests for the `src/util` and `src/component` directories, with relatively paltry coverage. The goal is to increase the level of coverage without being obsessive about it. Chasing 100% coverage has quickly diminishing returns. We want to cover core functionality, but let's not muck around too much with edge cases.
6364

64-
#### E2E testing
65+
## End-to-End Testing
6566

66-
E2e testing ~~is~~ will be done using playwright.
67+
This project uses [Playwright](https://playwright.dev/) for end-to-end testing. Tests are located in the `e2e/` directory.
68+
69+
### Setup
70+
71+
1. Install Playwright browsers:
72+
```shell
73+
npx playwright install
74+
```
75+
76+
2. Set up environment variables for testing:
77+
```shell
78+
# Test credentials (get from PDAP staff)
79+
80+
E2E_PASSWORD_AUTH_PASSWORD_TEST=pw
81+
82+
83+
84+
# Mailgun for email testing
85+
MAILGUN_KEY=key
86+
MAILGUN_DOMAIN=domain
87+
```
88+
89+
### Running Tests
90+
91+
```shell
92+
# Run all E2E tests
93+
npm run test:e2e
94+
95+
# Run with UI mode for debugging
96+
npm run test:e2e:ui
97+
98+
# Run specific test file
99+
npx playwright test e2e/auth/sign-in.spec.js
100+
```
101+
102+
### Writing Tests
103+
104+
1. Use the `TEST_IDS` object from `e2e/fixtures/test-ids.js` for element selectors
105+
2. Import test users from `e2e/fixtures/users.js`
106+
3. Follow the existing test patterns for consistency
107+
4. Add new test IDs to both the fixture and corresponding page components
108+
5. Always add new tests if routes are being updated.
109+
110+
### CI/CD
111+
112+
E2E tests run automatically on:
113+
- Pull requests (against dev environment for prs to dev or feature/** branches, against prod on prs to main)
114+
- Hourly monitoring (production health checks)
67115

68116
<!-- ### Resources
69117

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ npm i && npm run dev
3131

3232
### NPM Scripts
3333

34-
| Script | What it does |
35-
| -------------- | --------------------------------------------------------------------------------- |
36-
| `build` | Builds the application in production mode |
37-
| `serve` | Serves the built assets. Requires `build` to be run first |
38-
| `lint` | Lints the codebase with `eslint` and `prettier` |
39-
| `lint:fix` | Lints the codebase with `eslint` and `prettier` and fixes all auto-fixable issues |
40-
| `test` | Runs unit tests with debug output |
41-
| `test:ci` | Runs unit tests quietly |
42-
| `test:changed` | Runs unit tests on changed files only |
43-
| `coverage` | Runs unit tests and outputs coverage report |
44-
34+
| Script | What it does |
35+
| ------------------- | --------------------------------------------------------------------------------- |
36+
| `build` | Builds the application in production mode |
37+
| `serve` | Serves the built assets. Requires `build` to be run first |
38+
| `lint` | Lints the codebase with `eslint` and `prettier` |
39+
| `lint:fix` | Lints the codebase with `eslint` and `prettier` and fixes all auto-fixable issues |
40+
| `test:unit` | Runs unit tests with debug output |
41+
| `test:ci:unit` | Runs unit tests quietly |
42+
| `test:unit:changed` | Runs unit tests on changed files only |
43+
| `test:e2e` | Runs end-to-end tests with Playwright |
44+
| `test:e2e:ui` | Runs end-to-end tests with Playwright UI mode |
4545

4646

4747
### Contributing

e2e/auth/change-password.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const { expect } = require('@playwright/test');
2+
const { test } = require('../fixtures/base');
3+
const { PASSWORD_AUTH } = require('../fixtures/users');
4+
const { TEST_IDS } = require('../fixtures/test-ids');
5+
require('../msw-setup.js');
6+
7+
test.describe('Change password flow', () => {
8+
/** Skipping for now, because user password state (which does not match our validation schema) is reset nightly :( */
9+
test.skip('should change password successfully', async ({ page }) => {
10+
// Step 1: Sign in first
11+
await page.goto('/sign-in');
12+
await page.waitForLoadState('networkidle');
13+
14+
await page.fill(
15+
`input[data-test="${TEST_IDS.email_input}"]`,
16+
PASSWORD_AUTH.email
17+
);
18+
await page.fill(
19+
`input[data-test="${TEST_IDS.password_input}"]`,
20+
PASSWORD_AUTH.password
21+
);
22+
await page.click(`[data-test="${TEST_IDS.sign_in_submit}"]`);
23+
24+
// Wait for successful sign in
25+
await page.waitForLoadState('networkidle');
26+
await expect(page).toHaveURL(/\/(profile)?$/);
27+
28+
// Step 2: Navigate to change password page
29+
await page.goto('/change-password');
30+
await page.waitForLoadState('networkidle');
31+
32+
// Step 3: Fill out change password form
33+
await page.fill(
34+
`input[data-test="${TEST_IDS.current_password_input}"]`,
35+
PASSWORD_AUTH.password
36+
);
37+
await page.fill(
38+
`input[data-test="${TEST_IDS.new_password_input}"]`,
39+
PASSWORD_AUTH.password
40+
); // Same password
41+
await page.fill(
42+
`input[data-test="${TEST_IDS.confirm_password_input}"]`,
43+
PASSWORD_AUTH.password
44+
);
45+
46+
// Step 4: Submit form
47+
await page.click(`[data-test="${TEST_IDS.change_password_submit}"]`);
48+
49+
// Step 5: Verify success (should redirect to profile or show success message)
50+
await page.waitForLoadState('networkidle');
51+
52+
await page.waitForLoadState('networkidle');
53+
await expect(page).toHaveURL('/profile', { timeout: 30000 });
54+
});
55+
});

e2e/auth/password-reset.spec.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const { expect } = require('@playwright/test');
2+
const { test } = require('../fixtures/base');
3+
const { TEST_IDS } = require('../fixtures/test-ids');
4+
const { MailgunHelper } = require('../helpers/mailgun');
5+
const { TEST_RESET_EMAIL } = require('e2e/fixtures/users');
6+
require('../msw-setup.js');
7+
8+
// Test email for password reset
9+
const TEST_EMAIL = TEST_RESET_EMAIL;
10+
11+
test.describe('Password reset flow', () => {
12+
let mailgun;
13+
14+
test.beforeAll(async () => {
15+
mailgun = new MailgunHelper();
16+
});
17+
18+
test('should complete password reset flow', async ({ page }) => {
19+
// Step 1: Go to request password reset page
20+
await page.goto('/request-reset-password');
21+
await page.waitForLoadState('networkidle');
22+
23+
// Step 2: Fill out reset request form
24+
await page.fill(`input[data-test="${TEST_IDS.email_input}"]`, TEST_EMAIL);
25+
26+
// Step 3: Submit form
27+
await page.click(`[data-test="${TEST_IDS.form_submit}"]`);
28+
29+
// Step 4: Verify we're on the success page
30+
// await expect(page.locator('h1')).toContainText(
31+
// 'Request a link to reset your password'
32+
// );
33+
await expect(
34+
page.locator(`[data-test="${TEST_IDS.success_subheading}"]`)
35+
).toContainText('We sent you an email with a link to reset your password');
36+
37+
// Step 5: Wait for and retrieve password reset email
38+
console.log('Waiting for password reset email...');
39+
const email = await mailgun.getLatestEmailFor(TEST_EMAIL);
40+
41+
// Step 6: Extract reset URL from email
42+
const resetUrl = mailgun.extractPasswordResetUrl(
43+
email.body.html || email.body.text
44+
);
45+
console.log(`Reset URL: ${resetUrl}`);
46+
47+
// Step 7: Navigate to reset URL
48+
await page.goto(resetUrl);
49+
await page.waitForLoadState('networkidle');
50+
51+
// Step 8: Fill out new password form
52+
await page.fill(
53+
`input[data-test="${TEST_IDS.password_input}"]`,
54+
'NewPassword123!'
55+
);
56+
await page.fill(
57+
`input[data-test="${TEST_IDS.confirm_password_input}"]`,
58+
'NewPassword123!'
59+
);
60+
61+
// Step 9: Submit new password
62+
await page.click(`[data-test="${TEST_IDS.form_submit}"]`);
63+
64+
// Step 10: Verify password was reset successfully
65+
await expect(page).toHaveURL(/\/profile/);
66+
});
67+
});

0 commit comments

Comments
 (0)