Skip to content

Commit f3c79b8

Browse files
JohnRDOrazioclaude
andcommitted
fix: e2e test improvements and early i18n initialization
- Create screenshots directory before taking screenshots - Use web-first assertions for button visibility checks - Fix isFormReset() to check against data-default attribute - Initialize I18n early in admin.php for API calls before HTML output - Update .gitignore to track screenshots/.gitkeep 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 7a84443 commit f3c79b8

File tree

6 files changed

+42
-21
lines changed

6 files changed

+42
-21
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ credentials.php
1111
e2e/.auth/
1212
playwright-report/
1313
test-results/
14-
e2e/screenshots/
14+
e2e/screenshots/*
15+
!e2e/screenshots/.gitkeep

admin.php

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ function isLocalhost(): bool
9393
// Create PSR-compliant HTTP client
9494
$apiClient = new ApiClient(null, 10, 5, $logger);
9595

96+
// Initialize I18n early so we can use locale for API requests before HTML output
97+
include_once 'includes/I18n.php';
98+
use LiturgicalCalendar\UnitTestInterface\I18n;
99+
$i18n = new I18n();
100+
96101
// Fetch tests data from API
97102
try {
98103
$testsData = $apiClient->fetchJsonWithKey("$baseUrl/tests", 'litcal_tests');
@@ -105,6 +110,20 @@ function isLocalhost(): bool
105110
die('Failed to fetch tests data. Please try again later.');
106111
}
107112

113+
// Fetch events data from API with locale (before HTML output for consistent error pages)
114+
$eventsEndpoint = "$baseUrl/events";
115+
$apiClient->setLocale($i18n->locale);
116+
try {
117+
$eventsData = $apiClient->fetchJsonWithKey($eventsEndpoint, 'litcal_events');
118+
$LitCalAllLitEvents = $eventsData['litcal_events'];
119+
} catch (RuntimeException $e) {
120+
$logger->error('Failed to fetch events data', ['error' => $e->getMessage()]);
121+
if ($debugMode) {
122+
die('Could not fetch data from ' . $eventsEndpoint . ': ' . $e->getMessage());
123+
}
124+
die('Failed to fetch events data. Please try again later.');
125+
}
126+
108127
// Signal that this page has the login modal (for topnavbar.php)
109128
define('HAS_LOGIN_MODAL', true);
110129

@@ -129,7 +148,8 @@ function isLocalhost(): bool
129148
<option value="" selected>--</option>
130149
<?php
131150
foreach ($LitCalTests as $LitCalTest) {
132-
echo "<option value=\"{$LitCalTest['name']}\">{$LitCalTest['name']}</option>";
151+
$escapedName = htmlspecialchars($LitCalTest['name'], ENT_QUOTES, 'UTF-8');
152+
echo "<option value=\"{$escapedName}\">{$escapedName}</option>";
133153
}
134154
?>
135155
</select>
@@ -236,19 +256,6 @@ class="fas fa-flask-vial fa-2x text-black d-inline-block me-4"
236256
</main>
237257
<!-- End of Main Content -->
238258
<?php
239-
// Fetch events data from API with locale
240-
$eventsEndpoint = "$baseUrl/events";
241-
$apiClient->setLocale($i18n->locale);
242-
try {
243-
$eventsData = $apiClient->fetchJsonWithKey($eventsEndpoint, 'litcal_events');
244-
$LitCalAllLitEvents = $eventsData['litcal_events'];
245-
} catch (RuntimeException $e) {
246-
$logger->error('Failed to fetch events data', ['error' => $e->getMessage()]);
247-
if ($debugMode) {
248-
die('Could not fetch data from ' . $eventsEndpoint . ': ' . $e->getMessage());
249-
}
250-
die('Failed to fetch events data. Please try again later.');
251-
}
252259
// Include the test creation modal (hidden by JS when not authenticated)
253260
include_once 'components/NewTestModal.php';
254261
?>

e2e/admin.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { test, expect } from './fixtures';
2+
import { mkdir } from 'fs/promises';
3+
import path from 'path';
24

35
/**
46
* E2E tests for admin.php JWT authentication and test management
@@ -105,8 +107,8 @@ test.describe('Admin Page - Authenticated State', () => {
105107

106108
for (let i = 0; i < buttonCount; i++) {
107109
const btn = protectedButtons.nth(i);
108-
const isHidden = await btn.evaluate(el => el.classList.contains('d-none'));
109-
expect(isHidden).toBe(false);
110+
// Use web-first assertion for consistency
111+
await expect(btn).not.toHaveClass(/\bd-none\b/);
110112
}
111113
});
112114

@@ -212,6 +214,8 @@ test.describe('Admin Page - 401 Response Handling', () => {
212214
});
213215

214216
test.describe('Admin Page - Visual Regression', () => {
217+
const screenshotsDir = path.join(__dirname, 'screenshots');
218+
215219
test('should not have dropdown clipping issues', async ({ adminPage, page }) => {
216220
await adminPage.goToAdmin();
217221
await adminPage.waitForAuth();
@@ -226,7 +230,10 @@ test.describe('Admin Page - Visual Regression', () => {
226230
});
227231
expect(parentOverflow).not.toBe('hidden');
228232

233+
// Ensure screenshots directory exists before taking screenshot
234+
await mkdir(screenshotsDir, { recursive: true });
235+
229236
// Optionally take a screenshot for manual review
230-
await page.screenshot({ path: 'e2e/screenshots/dropdown-no-clipping.png', fullPage: true });
237+
await page.screenshot({ path: path.join(screenshotsDir, 'dropdown-no-clipping.png'), fullPage: true });
231238
});
232239
});

e2e/fixtures.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,12 @@ export class AdminPageHelper {
185185
* Check if the form is in a clean/reset state
186186
*/
187187
async isFormReset(): Promise<boolean> {
188-
const testName = await this.page.locator('#testName').textContent();
188+
const testNameEl = this.page.locator('#testName');
189+
const testName = await testNameEl.textContent();
190+
const testNameDefault = await testNameEl.getAttribute('data-default');
189191
const description = await this.page.locator('#description').inputValue();
190-
return testName === '' && description === '';
192+
// Form is reset when testName shows the default label and description is empty
193+
return testName === testNameDefault && description === '';
191194
}
192195

193196
/**

e2e/screenshots/.gitkeep

Whitespace-only changes.

layout/head.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
use LiturgicalCalendar\UnitTestInterface\I18n;
55

6-
$i18n = new I18n();
6+
// Only create I18n if not already initialized (e.g., by admin.php for early API calls)
7+
if (!isset($i18n)) {
8+
$i18n = new I18n();
9+
}
710
$pageName = basename($_SERVER["SCRIPT_FILENAME"], '.php');
811
?><!DOCTYPE html>
912
<html lang="<?php echo $i18n->locale; ?>">

0 commit comments

Comments
 (0)