Skip to content

Commit 867bd7d

Browse files
authored
Merge pull request #229 from OpenPathfinder/ulises/2019-e2e
2 parents 5f02b88 + 98b7119 commit 867bd7d

File tree

13 files changed

+518
-22
lines changed

13 files changed

+518
-22
lines changed

.dockerignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,6 @@ dist
133133
# CUSTOM
134134
IGNORE/
135135
output
136-
.vscode
136+
.vscode
137+
test-results/
138+
playwright-report

.github/workflows/e2e-tests.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
e2e-tests:
13+
name: Playwright Tests
14+
runs-on: ubuntu-latest
15+
16+
services:
17+
postgres:
18+
image: postgres:17.2
19+
env:
20+
POSTGRES_DB: dashboard
21+
POSTGRES_USER: visionBoard
22+
POSTGRES_PASSWORD: password
23+
ports:
24+
- 5432:5432
25+
options: >-
26+
--health-cmd="pg_isready -U visionBoard"
27+
--health-interval=10s
28+
--health-timeout=5s
29+
--health-retries=5
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v3
34+
35+
- name: Set up Node.js
36+
uses: actions/setup-node@v3
37+
with:
38+
node-version: '22'
39+
cache: 'npm'
40+
41+
- name: Install dependencies
42+
run: npm ci
43+
44+
- name: Install Playwright
45+
run: npx playwright install
46+
47+
- name: Install Playwright system dependencies
48+
run: npx playwright install-deps
49+
50+
- name: Set up database
51+
run: |
52+
npm run db:migrate
53+
npm run db:seed
54+
55+
- name: Run Playwright tests
56+
run: |
57+
# Run the tests with list reporter to avoid hanging on failures
58+
# Playwright will automatically start and manage the server as configured in playwright.config.js
59+
npm run test:e2e-ci
60+
61+
- name: Upload test results
62+
if: always()
63+
uses: actions/upload-artifact@v4
64+
with:
65+
name: playwright-report
66+
path: playwright-report/
67+
retention-days: 30

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,6 @@ dist
133133
# CUSTOM
134134
IGNORE/
135135
output
136-
.vscode
136+
.vscode
137+
test-results/
138+
playwright-report

e2e/global-setup.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// @ts-check
2+
const knexInit = require('knex')
3+
const { getConfig } = require('../src/config')
4+
const { resetDatabase, initializeStore } = require('../__utils__')
5+
const { sampleGithubOrg, sampleGithubRepository } = require('../__fixtures__')
6+
7+
const { dbSettings } = getConfig('test')
8+
9+
/**
10+
* Global setup for Playwright tests
11+
* This is run once before all tests
12+
* It sets up the database with test fixtures
13+
*/
14+
async function globalSetup () {
15+
console.log('Setting up E2E test fixtures...')
16+
17+
// Initialize database connection
18+
const knex = knexInit(dbSettings)
19+
20+
try {
21+
// Get store functions
22+
const { addProject, addGithubOrganization, upsertGithubRepository } = initializeStore(knex)
23+
24+
// Reset database for a clean slate
25+
await resetDatabase(knex)
26+
27+
// Add a test project
28+
const testProject = await addProject({
29+
name: 'E2E Test Project'
30+
})
31+
32+
// Add a GitHub organization for the test project
33+
const testOrg = await addGithubOrganization({
34+
login: sampleGithubOrg.login,
35+
html_url: sampleGithubOrg.html_url,
36+
project_id: testProject.id,
37+
node_id: sampleGithubOrg.node_id
38+
})
39+
40+
// Add a GitHub repository for the test organization
41+
await upsertGithubRepository({
42+
name: sampleGithubRepository.name,
43+
full_name: sampleGithubRepository.full_name,
44+
html_url: sampleGithubRepository.html_url,
45+
url: sampleGithubRepository.url,
46+
node_id: sampleGithubRepository.node_id,
47+
stargazers_count: sampleGithubRepository.stargazers_count || 10,
48+
forks_count: sampleGithubRepository.forks_count || 5,
49+
subscribers_count: sampleGithubRepository.subscribers_count || 3,
50+
open_issues_count: sampleGithubRepository.open_issues_count || 2,
51+
git_url: sampleGithubRepository.git_url,
52+
ssh_url: sampleGithubRepository.ssh_url,
53+
clone_url: sampleGithubRepository.clone_url,
54+
visibility: sampleGithubRepository.visibility,
55+
default_branch: sampleGithubRepository.default_branch
56+
}, testOrg.id)
57+
58+
console.log('E2E test fixtures created successfully')
59+
} catch (error) {
60+
console.error('Error setting up E2E test fixtures:', error)
61+
throw error
62+
} finally {
63+
// Close database connection
64+
await knex.destroy()
65+
}
66+
}
67+
68+
module.exports = globalSetup

e2e/global-teardown.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const knexInit = require('knex')
2+
const { getConfig } = require('../src/config')
3+
const { resetDatabase } = require('../__utils__')
4+
5+
const { dbSettings } = getConfig('test')
6+
7+
/**
8+
* Global teardown for Playwright tests
9+
* This is run once after all tests are complete
10+
* It cleans up the database by resetting it
11+
*/
12+
async function globalTeardown () {
13+
console.log('Cleaning up after E2E tests...')
14+
15+
// Initialize database connection
16+
const knex = knexInit(dbSettings)
17+
18+
try {
19+
// Reset database to clean state
20+
await resetDatabase(knex)
21+
console.log('Database cleanup completed successfully')
22+
} catch (error) {
23+
console.error('Error cleaning up after E2E tests:', error)
24+
throw error
25+
} finally {
26+
// Close database connection
27+
await knex.destroy()
28+
}
29+
}
30+
31+
module.exports = globalTeardown

e2e/website.spec.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// @ts-check
2+
const { test, expect } = require('@playwright/test')
3+
4+
/**
5+
* E2E tests for the website router functionality
6+
* This tests the routes defined in src/httpServer/routers/website.js
7+
*/
8+
test.describe('Website Router', () => {
9+
// @TODO: load this from fixtures
10+
const testProjectId = 1
11+
12+
test('should render the index page with projects, checklists and Compliance Checks', async ({ page }) => {
13+
// Navigate to the root path (handled by router.get('/'))
14+
await page.goto('/')
15+
16+
// Check that the page title contains expected text
17+
await expect(page).toHaveTitle(/VisionBoard Reports/)
18+
19+
// Check for the header with the logo
20+
const header = page.locator('header')
21+
await expect(header).toBeVisible()
22+
await expect(header.locator('a')).toContainText('VisionBoard Reports')
23+
24+
// Check for main content container
25+
const mainContent = page.locator('main.container')
26+
await expect(mainContent).toBeVisible()
27+
28+
// Check for the Projects heading
29+
const projectsHeading = page.locator('[data-testid="projects-heading"]')
30+
await expect(projectsHeading).toBeVisible()
31+
32+
// Check for project links in the list
33+
const projectLinks = page.locator('[data-testid="projects-list"] li a')
34+
const count = await projectLinks.count()
35+
36+
// We should have at least one project link
37+
expect(count).toBeGreaterThan(0)
38+
39+
// Check for the Checklists heading
40+
const checklistsHeading = page.locator('[data-testid="checklists-heading"]')
41+
await expect(checklistsHeading).toBeVisible()
42+
43+
// Check for the first table
44+
const checklistsTable = page.locator('[data-testid="checklists-table"]')
45+
await expect(checklistsTable).toBeVisible()
46+
// Check that the table headers exist
47+
const docHeader = checklistsTable.locator('thead tr th:has-text("Documentation")')
48+
const titleHeader = checklistsTable.locator('thead tr th:has-text("Title")')
49+
const descHeader = checklistsTable.locator('thead tr th:has-text("Description")')
50+
const authorHeader = checklistsTable.locator('thead tr th:has-text("Author")')
51+
52+
await expect(docHeader).toBeVisible()
53+
await expect(titleHeader).toBeVisible()
54+
await expect(descHeader).toBeVisible()
55+
await expect(authorHeader).toBeVisible()
56+
57+
// Check for at least one row in the table
58+
const rows = await checklistsTable.locator('tr')
59+
expect(await rows.count()).toBeGreaterThan(0)
60+
61+
// Check for the Compliance Checks heading
62+
const complianceChecksHeading = page.locator('[data-testid="compliance-checks-heading"]')
63+
await expect(complianceChecksHeading).toBeVisible()
64+
65+
// Check for Compliance Checks Description
66+
const complianceChecksDescription = page.locator('[data-testid="compliance-checks-description"]')
67+
await expect(complianceChecksDescription).toBeVisible()
68+
69+
// Check for the second table
70+
const complianceChecksTable = page.locator('[data-testid="compliance-checks-table"]')
71+
await expect(complianceChecksTable).toBeVisible()
72+
// Check that the table headers exist
73+
const docHeader2 = complianceChecksTable.locator('thead tr th:has-text("Documentation")')
74+
const nameHeader = complianceChecksTable.locator('thead tr th:has-text("Name")')
75+
const descHeader2 = complianceChecksTable.locator('thead tr th:has-text("Description")')
76+
77+
await expect(docHeader2).toBeVisible()
78+
await expect(nameHeader).toBeVisible()
79+
await expect(descHeader2).toBeVisible()
80+
81+
// Check for at least one row in the table
82+
const rows2 = await complianceChecksTable.locator('tr')
83+
expect(await rows2.count()).toBeGreaterThan(0)
84+
})
85+
86+
test('should navigate to and render project details page', async ({ page }) => {
87+
// Navigate directly to the project page using our test project ID
88+
await page.goto(`/projects/${testProjectId}`)
89+
90+
// Check that we're on the correct project page URL
91+
await expect(page).toHaveURL(`/projects/${testProjectId}`)
92+
93+
// Check for the project name in the heading
94+
const projectHeading = page.locator('[data-testid="project-heading"]')
95+
await expect(projectHeading).toBeVisible()
96+
await expect(projectHeading).toContainText('Report')
97+
98+
// Check for main content container
99+
const mainContent = page.locator('main.container')
100+
await expect(mainContent).toBeVisible()
101+
102+
// Check for section headings that should be present on project pages
103+
const sectionHeadings = [
104+
'[data-testid="alerts-heading"]',
105+
'[data-testid="results-heading"]',
106+
'[data-testid="tasks-heading"]',
107+
'[data-testid="ossf-scorecard-heading"]',
108+
'[data-testid="github-orgs-heading"]',
109+
'[data-testid="github-repos-heading"]'
110+
]
111+
112+
for (const headingText of sectionHeadings) {
113+
const heading = page.locator(headingText)
114+
await expect(heading).toBeVisible()
115+
}
116+
117+
// Check for a GitHub organization list
118+
const list = page.locator('[data-testid="github-orgs-list"] li')
119+
await expect(list).toBeVisible()
120+
121+
// Check for the GitHub repositories table
122+
const githubReposTable = page.locator('[data-testid="github-repos-table"]')
123+
await expect(githubReposTable).toBeVisible()
124+
// Check that the table headers exist
125+
const repositoryHeader = githubReposTable.locator('thead tr th:has-text("Repository")')
126+
const starsHeader = githubReposTable.locator('thead tr th:has-text("Stars")')
127+
const forksHeader = githubReposTable.locator('thead tr th:has-text("Forks")')
128+
const subscribersHeader = githubReposTable.locator('thead tr th:has-text("Subscribers")')
129+
const issuesHeader = githubReposTable.locator('thead tr th:has-text("Open Issues")')
130+
131+
await expect(repositoryHeader).toBeVisible()
132+
await expect(starsHeader).toBeVisible()
133+
await expect(forksHeader).toBeVisible()
134+
await expect(subscribersHeader).toBeVisible()
135+
await expect(issuesHeader).toBeVisible()
136+
137+
// Check for at least one row in the table
138+
const rows = await githubReposTable.locator('tr')
139+
expect(await rows.count()).toBeGreaterThan(0)
140+
})
141+
142+
test('should handle invalid project IDs correctly', async ({ page }) => {
143+
// Test with non-numeric ID (should render 404 page)
144+
await page.goto('/projects/invalid')
145+
146+
// Check for the Not Found heading
147+
const notFoundHeading = page.locator('[data-testid="not-found-title"]')
148+
await expect(notFoundHeading).toBeVisible()
149+
150+
// Test with non-existent numeric ID
151+
await page.goto('/projects/999999')
152+
153+
// Check for the Not Found heading
154+
const notFoundHeading2 = page.locator('[data-testid="not-found-title"]')
155+
await expect(notFoundHeading2).toBeVisible()
156+
})
157+
158+
test('should have working navigation between pages', async ({ page }) => {
159+
// Start at the index page
160+
await page.goto('/')
161+
162+
// Find project links in the list
163+
const projectLinks = page.locator('ul li a')
164+
165+
// Click the first project link
166+
await projectLinks.first().click()
167+
168+
// Verify we're on a project page by checking for project-specific content
169+
const projectHeading = page.locator('h1:has-text("Report")')
170+
await expect(projectHeading).toBeVisible()
171+
172+
// Verify the project page URL
173+
await expect(page).toHaveURL(`/projects/${testProjectId}`)
174+
175+
// Navigate back to the index page using the header link
176+
const headerLink = page.locator('header a')
177+
await expect(headerLink).toBeVisible()
178+
await headerLink.click()
179+
180+
// Verify we're back at the index page by checking for index-specific content
181+
const welcomeHeading = page.locator('h1:has-text("Welcome")')
182+
await expect(welcomeHeading).toBeVisible()
183+
184+
// Also verify we're at the root URL
185+
await expect(page).toHaveURL('/')
186+
})
187+
})

jest.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export default {
1010
'<rootDir>/src/database/seeds/',
1111
'<rootDir>/src/config/'
1212
],
13+
testPathIgnorePatterns: [
14+
'<rootDir>/node_modules/',
15+
'<rootDir>/e2e/'
16+
],
1317
transformIgnorePatterns: [
1418
'/node_modules/(?!octokit).+\\.js$'
1519
]

0 commit comments

Comments
 (0)