Skip to content

Commit 38ee2c3

Browse files
test: add browser e2e coverage for web ui
1 parent d9418ce commit 38ee2c3

7 files changed

Lines changed: 403 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,23 @@ jobs:
5959
- name: Install dependencies
6060
run: npm ci
6161

62+
- name: Install Playwright browser
63+
run: npx playwright install --with-deps chromium
64+
6265
- name: Run end-to-end tests
6366
run: npm run test:e2e
6467

68+
- name: Upload Playwright artifacts
69+
if: failure()
70+
uses: actions/upload-artifact@v7
71+
with:
72+
name: playwright-artifacts
73+
path: |
74+
playwright-report/
75+
test-results/
76+
if-no-files-found: ignore
77+
retention-days: 7
78+
6579
lint:
6680
name: Lint
6781
runs-on: ubuntu-latest

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ dist/
99

1010
# Generated bootcamp output (keep examples)
1111
bootcamp-*/
12+
.bootcamp-output/
13+
14+
# Browser E2E artifacts
15+
playwright-report/
16+
test-results/
1217

1318
# OS files
1419
.DS_Store

package-lock.json

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@
136136
"lint": "eslint \"src/**/*.{ts,tsx}\"",
137137
"typecheck": "tsc -p tsconfig.build.json --noEmit",
138138
"test": "vitest run",
139-
"test:e2e": "vitest run --config vitest.e2e.config.ts",
139+
"test:e2e": "npm run test:e2e:cli && npm run test:e2e:web",
140+
"test:e2e:cli": "vitest run --config vitest.e2e.config.ts",
141+
"test:e2e:web": "playwright test",
140142
"test:watch": "vitest",
141143
"test:coverage": "vitest run --coverage",
142144
"prepublishOnly": "npm run build && npm test"
@@ -160,6 +162,7 @@
160162
},
161163
"devDependencies": {
162164
"@eslint/js": "^10.0.1",
165+
"@playwright/test": "^1.58.2",
163166
"@types/express": "^5.0.6",
164167
"@types/helmet": "^0.0.48",
165168
"@types/node": "^25.2.2",

playwright.config.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
export default defineConfig({
4+
testDir: "./test/playwright",
5+
fullyParallel: false,
6+
timeout: 120_000,
7+
expect: {
8+
timeout: 20_000,
9+
},
10+
outputDir: "test-results/playwright",
11+
reporter: process.env.CI
12+
? [
13+
["line"],
14+
["html", { open: "never", outputFolder: "playwright-report" }],
15+
]
16+
: [["list"]],
17+
retries: process.env.CI ? 1 : 0,
18+
use: {
19+
trace: "retain-on-failure",
20+
screenshot: "only-on-failure",
21+
video: "off",
22+
headless: true,
23+
},
24+
projects: [
25+
{
26+
name: "chromium",
27+
use: {
28+
...devices["Desktop Chrome"],
29+
},
30+
},
31+
],
32+
});

src/web/templates.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ export function getIndexHtml(): string {
1818
padding: 2rem;
1919
}
2020
.container { max-width: 900px; margin: 0 auto; }
21+
.sr-only {
22+
position: absolute;
23+
width: 1px;
24+
height: 1px;
25+
padding: 0;
26+
margin: -1px;
27+
overflow: hidden;
28+
clip: rect(0, 0, 0, 0);
29+
white-space: nowrap;
30+
border: 0;
31+
}
2132
h1 {
2233
font-size: 2.5rem;
2334
margin-bottom: 0.5rem;
@@ -147,25 +158,33 @@ export function getIndexHtml(): string {
147158
<h1>Repo Bootcamp</h1>
148159
<p class="subtitle">Generate onboarding documentation for any GitHub repository</p>
149160
150-
<div class="input-group">
161+
<form class="input-group" id="analyzeForm">
162+
<label class="sr-only" for="repoUrl">Repository URL</label>
151163
<input type="text" id="repoUrl" placeholder="https://github.com/owner/repo" />
152-
<button id="analyzeBtn" onclick="analyze()">Analyze</button>
153-
</div>
164+
<button type="submit" id="analyzeBtn">Analyze</button>
165+
</form>
154166
155-
<div class="progress" id="progress" style="display: none;"></div>
167+
<div class="progress" id="progress" style="display: none;" aria-live="polite"></div>
156168
157-
<div class="results" id="results">
169+
<div class="results" id="results" aria-live="polite">
158170
<div class="stats" id="stats"></div>
159171
<h2 style="margin-bottom: 1rem;">Generated Files</h2>
160172
<div class="files" id="files"></div>
161173
</div>
162174
</div>
163175
164-
<div class="modal" id="modal" onclick="if(event.target===this)closeModal()">
176+
<div
177+
class="modal"
178+
id="modal"
179+
role="dialog"
180+
aria-modal="true"
181+
aria-labelledby="modalTitle"
182+
onclick="if(event.target===this)closeModal()"
183+
>
165184
<div class="modal-content">
166185
<div class="modal-header">
167186
<h2 id="modalTitle"></h2>
168-
<button class="close" onclick="closeModal()">&times;</button>
187+
<button class="close" type="button" aria-label="Close file preview" onclick="closeModal()">&times;</button>
169188
</div>
170189
<pre id="modalContent"></pre>
171190
</div>
@@ -338,6 +357,11 @@ export function getIndexHtml(): string {
338357
btn.textContent = 'Analyze';
339358
}
340359
360+
document.getElementById('analyzeForm').addEventListener('submit', (event) => {
361+
event.preventDefault();
362+
void analyze();
363+
});
364+
341365
document.addEventListener('keydown', (e) => {
342366
if (e.key === 'Escape') closeModal();
343367
});

0 commit comments

Comments
 (0)