Skip to content

Commit 3abb9eb

Browse files
committed
feat: implement Phase 4 - Component Test IDs
Add data-testid attributes to key UI elements for improved test reliability and resilience to UI changes. ProjectSelection.tsx: - data-testid="project-selection" (container) - data-testid="project-row-{id}" and data-project-name (rows) - data-testid="project-checkbox-{id}" (checkboxes) - data-testid="selected-projects-summary/count" - data-testid="continue-to-alert-button" AlertForm.tsx: - data-testid="alert-form" (container) - data-testid="coordinates-display" - data-testid="selected-projects-display" - data-testid="alert-submit-button" - data-testid="alert-error-message" - data-testid="alert-validation-error" Page objects updated to use new selectors instead of fragile CSS class or text matchers.
1 parent e800ed2 commit 3abb9eb

File tree

4 files changed

+61
-18
lines changed

4 files changed

+61
-18
lines changed

src/components/AlertForm.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,10 @@ export const AlertForm = ({
239239
: selectedProjectNames.join(", ");
240240

241241
return (
242-
<div className="min-h-screen p-4 bg-gradient-to-br from-blue-50 to-indigo-100">
242+
<div
243+
className="min-h-screen p-4 bg-gradient-to-br from-blue-50 to-indigo-100"
244+
data-testid="alert-form"
245+
>
243246
<div className="max-w-2xl mx-auto">
244247
<div className="flex items-center gap-4 mb-6">
245248
<Button
@@ -265,7 +268,10 @@ export const AlertForm = ({
265268
<div className="flex items-center justify-between">
266269
<div>
267270
<p className="text-sm text-gray-600">{t("alert.location")}</p>
268-
<p className="font-mono text-sm">
271+
<p
272+
className="font-mono text-sm"
273+
data-testid="coordinates-display"
274+
>
269275
{coordinates.lat}, {coordinates.lng}
270276
</p>
271277
</div>
@@ -280,7 +286,7 @@ export const AlertForm = ({
280286
</Button>
281287
</div>
282288

283-
<div>
289+
<div data-testid="selected-projects-display">
284290
<p className="text-sm text-gray-600">
285291
{t("alert.selectedProjects", {
286292
count: selectedProjects.length,
@@ -348,15 +354,21 @@ export const AlertForm = ({
348354
{t("alert.slugFormatHelp")}
349355
</p>
350356
{alertName && !validateSlug(alertName) && (
351-
<p className="text-sm text-red-600">
357+
<p
358+
className="text-sm text-red-600"
359+
data-testid="alert-validation-error"
360+
>
352361
{t("alert.invalidFormat")}
353362
</p>
354363
)}
355364
</div>
356365

357366
{/* Error Message */}
358367
{errorMessage && (
359-
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
368+
<div
369+
className="bg-red-50 border border-red-200 rounded-lg p-3"
370+
data-testid="alert-error-message"
371+
>
360372
<p className="text-sm text-red-700">{errorMessage}</p>
361373
</div>
362374
)}
@@ -369,6 +381,7 @@ export const AlertForm = ({
369381
submissionState === "loading" || !validateSlug(alertName)
370382
}
371383
variant={getButtonVariant()}
384+
data-testid="alert-submit-button"
372385
>
373386
{getButtonContent()}
374387
</Button>

src/components/ProjectSelection.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ export const ProjectSelection = ({
135135
}
136136

137137
return (
138-
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
138+
<div
139+
className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"
140+
data-testid="project-selection"
141+
>
139142
{/* Mobile-first header */}
140143
<div
141144
className="bg-white/95 backdrop-blur-sm border-b border-gray-200 px-4 py-3"
@@ -181,6 +184,8 @@ export const ProjectSelection = ({
181184
<div
182185
key={project.projectId}
183186
className="flex items-center space-x-3 p-4 border rounded-lg hover:bg-gray-50 transition-colors min-h-[60px]"
187+
data-testid={`project-row-${project.projectId}`}
188+
data-project-name={project.name}
184189
>
185190
<Checkbox
186191
id={project.projectId}
@@ -189,6 +194,7 @@ export const ProjectSelection = ({
189194
handleProjectToggle(project.projectId)
190195
}
191196
className="scale-125"
197+
data-testid={`project-checkbox-${project.projectId}`}
192198
/>
193199
<Label
194200
htmlFor={project.projectId}
@@ -201,8 +207,14 @@ export const ProjectSelection = ({
201207
</div>
202208

203209
{selectedProjects.length > 0 && (
204-
<div className="mt-6 p-4 bg-green-50 rounded-lg">
205-
<p className="text-sm text-green-700 mb-3">
210+
<div
211+
className="mt-6 p-4 bg-green-50 rounded-lg"
212+
data-testid="selected-projects-summary"
213+
>
214+
<p
215+
className="text-sm text-green-700 mb-3"
216+
data-testid="selected-projects-count"
217+
>
206218
{t("projects.selected", {
207219
count: selectedProjects.length,
208220
plural: selectedProjects.length !== 1 ? "s" : "",
@@ -223,6 +235,7 @@ export const ProjectSelection = ({
223235
onClick={handleContinue}
224236
className="w-full mt-6 min-h-12 py-3 text-sm sm:text-base whitespace-normal"
225237
disabled={selectedProjects.length === 0}
238+
data-testid="continue-to-alert-button"
226239
>
227240
{t("projects.continueToAlert", {
228241
count: selectedProjects.length,

tests/pages/AlertFormPage.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export interface AlertFormData {
1212
* Page Object for the Alert Form page
1313
*/
1414
export class AlertFormPage extends BasePage {
15+
// Container
16+
readonly container: Locator;
17+
1518
// Form inputs
1619
readonly startTimeInput: Locator;
1720
readonly endTimeInput: Locator;
@@ -33,25 +36,28 @@ export class AlertFormPage extends BasePage {
3336
constructor(page: Page) {
3437
super(page);
3538

39+
// Container
40+
this.container = page.locator('[data-testid="alert-form"]');
41+
3642
// Form inputs - use ID selectors for reliability
3743
this.startTimeInput = page.locator('#startTime');
3844
this.endTimeInput = page.locator('#endTime');
3945
this.sourceIdInput = page.locator('#sourceId');
4046
this.alertNameInput = page.locator('#alertName');
4147

4248
// Buttons
43-
this.submitButton = page.locator('button[type="submit"]');
49+
this.submitButton = page.locator('[data-testid="alert-submit-button"]');
4450
this.backButton = page.getByRole('button', { name: /back/i });
4551

4652
// Validation error (inline format error for alert name)
47-
this.validationError = page.locator('.text-red-600, .text-destructive');
53+
this.validationError = page.locator('[data-testid="alert-validation-error"]');
4854

4955
// Submission error (red error box)
50-
this.submissionError = page.locator('.bg-red-50');
56+
this.submissionError = page.locator('[data-testid="alert-error-message"]');
5157

5258
// Summary section elements
53-
this.coordinatesDisplay = page.locator('text=/[-]?\\d+\\.\\d+.*,.*[-]?\\d+\\.\\d+/');
54-
this.selectedProjectsDisplay = page.locator('text=/\\d+\\s+project/i');
59+
this.coordinatesDisplay = page.locator('[data-testid="coordinates-display"]');
60+
this.selectedProjectsDisplay = page.locator('[data-testid="selected-projects-display"]');
5561
}
5662

5763
/**
@@ -148,7 +154,7 @@ export class AlertFormPage extends BasePage {
148154
// Wait for button to not be in "creating" state
149155
await this.page.waitForFunction(
150156
() => {
151-
const button = document.querySelector('button[type="submit"]');
157+
const button = document.querySelector('[data-testid="alert-submit-button"]');
152158
const text = button?.textContent?.toLowerCase() ?? '';
153159
return !text.includes('creating');
154160
},
@@ -163,7 +169,7 @@ export class AlertFormPage extends BasePage {
163169
// First wait for success state on button
164170
await this.page.waitForFunction(
165171
() => {
166-
const button = document.querySelector('button[type="submit"]');
172+
const button = document.querySelector('[data-testid="alert-submit-button"]');
167173
const text = button?.textContent?.toLowerCase() ?? '';
168174
return text.includes('success') || text.includes('created');
169175
},

tests/pages/ProjectSelectionPage.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { BasePage } from './BasePage';
66
*/
77
export class ProjectSelectionPage extends BasePage {
88
// Locators
9+
readonly container: Locator;
910
readonly backToMapButton: Locator;
1011
readonly continueButton: Locator;
1112
readonly logoutButton: Locator;
@@ -15,23 +16,33 @@ export class ProjectSelectionPage extends BasePage {
1516
constructor(page: Page) {
1617
super(page);
1718

19+
// Container
20+
this.container = page.locator('[data-testid="project-selection"]');
21+
1822
// Navigation buttons
1923
this.backToMapButton = page.getByRole('button', { name: /back.*map/i });
20-
this.continueButton = page.getByRole('button', { name: /continue.*alert/i });
24+
this.continueButton = page.locator('[data-testid="continue-to-alert-button"]');
2125
this.logoutButton = page.getByRole('button', { name: /logout/i });
2226

2327
// Loading state
2428
this.loadingIndicator = page.locator('.animate-spin');
2529

2630
// Selected projects summary (green box showing count and names)
27-
this.selectedProjectsSummary = page.locator('.bg-green-50');
31+
this.selectedProjectsSummary = page.locator('[data-testid="selected-projects-summary"]');
2832
}
2933

3034
/**
3135
* Get all project checkboxes
3236
*/
3337
getProjectCheckboxes(): Locator {
34-
return this.page.getByRole('checkbox');
38+
return this.page.locator('[data-testid^="project-checkbox-"]');
39+
}
40+
41+
/**
42+
* Get project row by name using data attribute
43+
*/
44+
getProjectRow(projectName: string): Locator {
45+
return this.page.locator(`[data-project-name="${projectName}"]`);
3546
}
3647

3748
/**

0 commit comments

Comments
 (0)