Skip to content

Commit 4d84a59

Browse files
committed
Create comprehensive E2E tests using Page Object Model pattern
1 parent 29a03f7 commit 4d84a59

File tree

3 files changed

+496
-0
lines changed

3 files changed

+496
-0
lines changed

public/fixtures/invalid-sample.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
id,name,description,date,price
2+
1,Event with invalid price,Description for event 1,2023-01-01,abc
3+
2,,Event without name,2023-02-15,25.99
4+
3,Event with missing field,2023-03-10
5+
4,Event with invalid date,Invalid date,99.99
6+
,Event with missing ID,Description,2023-05-20,50

tests/e2e/csv-validator.spec.ts

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
import { test, expect, type Page, type Route } from '@playwright/test';
2+
import { CSVValidatorPage } from './page-objects/csv-validator.page';
3+
import path from 'path';
4+
5+
/**
6+
* E2E Tests for CSV Data Validator application
7+
*
8+
* Tests follow the application flow described in systemPatterns.md:
9+
* 1. Schema Selection
10+
* 2. Schema Documentation
11+
* 3. CSV Upload & Parsing
12+
* 4. Validation
13+
* 5. Results Display
14+
* 6. Error Highlighting
15+
*/
16+
test.describe('CSV Data Validator Application', () => {
17+
let csvValidatorPage: CSVValidatorPage;
18+
19+
// Setup: Run before each test
20+
test.beforeEach(async ({ page }) => {
21+
// Set up page object
22+
csvValidatorPage = new CSVValidatorPage(page);
23+
24+
// Set up API mocks for consistent testing
25+
await setupApiMocks(page);
26+
27+
// Navigate to application
28+
await csvValidatorPage.goto();
29+
});
30+
31+
/**
32+
* Helper function to set up API route mocking
33+
*/
34+
async function setupApiMocks(page: Page) {
35+
// Enable request logging for debugging
36+
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
37+
38+
// Mock the schemas list endpoint
39+
await page.route('**/api/list-schemas', async (route: Route) => {
40+
console.log('Mocking /api/list-schemas');
41+
await route.fulfill({
42+
status: 200,
43+
contentType: 'application/json',
44+
body: JSON.stringify({
45+
schemas: ['event.json', 'place.json']
46+
})
47+
});
48+
});
49+
50+
// Mock individual schema content endpoints
51+
await page.route('**/api/get-schema/**', async (route: Route) => {
52+
const url = route.request().url();
53+
console.log(`Mocking schema request: ${url}`);
54+
55+
// Create mock schema content based on the requested schema
56+
const schemaName = url.includes('event') ? 'event' : 'place';
57+
const mockSchema = {
58+
$schema: 'http://json-schema.org/draft-07/schema#',
59+
title: `${schemaName.charAt(0).toUpperCase() + schemaName.slice(1)} Schema`,
60+
type: 'object',
61+
required: ['id', 'name'],
62+
properties: {
63+
id: {
64+
type: 'string',
65+
description: 'Unique identifier'
66+
},
67+
name: {
68+
type: 'string',
69+
description: 'Name of the item'
70+
},
71+
description: {
72+
type: 'string',
73+
description: 'Detailed description'
74+
},
75+
date: {
76+
type: 'string',
77+
format: 'date',
78+
description: 'Date in YYYY-MM-DD format'
79+
},
80+
price: {
81+
type: 'number',
82+
description: 'Price value (numeric only)'
83+
}
84+
}
85+
};
86+
87+
await route.fulfill({
88+
status: 200,
89+
contentType: 'application/json',
90+
body: JSON.stringify({
91+
content: mockSchema
92+
})
93+
});
94+
});
95+
96+
// Mock schema documentation generation
97+
await page.route('**/api/generate-schema-doc', async (route: Route) => {
98+
console.log('Mocking /api/generate-schema-doc');
99+
100+
// Extract the schema from the request body
101+
const requestData = route.request().postDataJSON();
102+
const schemaTitle = requestData?.title || 'Schema';
103+
104+
const mockMarkdown = `
105+
# ${schemaTitle}
106+
107+
A schema for validating data.
108+
109+
## Properties
110+
111+
| Property | Type | Description | Required |
112+
|----------|------|-------------|----------|
113+
| id | string | Unique identifier | Yes |
114+
| name | string | Name of the item | Yes |
115+
| description | string | Detailed description | No |
116+
| date | string | Date in YYYY-MM-DD format | No |
117+
| price | number | Price value (numeric only) | No |
118+
`;
119+
120+
await route.fulfill({
121+
status: 200,
122+
contentType: 'application/json',
123+
body: JSON.stringify({
124+
markdown: mockMarkdown
125+
})
126+
});
127+
});
128+
}
129+
130+
/**
131+
* Test: Application loads with expected UI elements
132+
*/
133+
test('should load with all required UI elements', async () => {
134+
// Verify page title
135+
await expect(csvValidatorPage.heading).toBeVisible();
136+
137+
// Verify schema dropdown exists
138+
await expect(csvValidatorPage.schemaDropdownTrigger).toBeVisible();
139+
140+
// Verify upload CSV button exists
141+
await expect(csvValidatorPage.uploadCsvButton).toBeVisible();
142+
143+
// Verify validate button exists but is disabled
144+
await expect(csvValidatorPage.validateButton).toBeVisible();
145+
await expect(csvValidatorPage.validateButton).toBeDisabled();
146+
147+
// Verify results panel exists but shows no results yet
148+
await expect(csvValidatorPage.resultsPanel).toBeVisible();
149+
await csvValidatorPage.resultsContain('Upload CSV and click Validate');
150+
});
151+
152+
/**
153+
* Test 1: Schema dropdown population and selection
154+
*/
155+
test('should populate and allow selection from schema dropdown', async () => {
156+
// Wait for schemas to load in dropdown
157+
await expect(csvValidatorPage.schemaDropdownTrigger).toBeEnabled();
158+
159+
// Open dropdown and verify schemas are listed
160+
await csvValidatorPage.schemaDropdownTrigger.click();
161+
await expect(csvValidatorPage.schemaDropdownOptions.filter({ hasText: 'event' })).toBeVisible();
162+
await expect(csvValidatorPage.schemaDropdownOptions.filter({ hasText: 'place' })).toBeVisible();
163+
164+
// Select a schema
165+
await csvValidatorPage.schemaDropdownOptions.filter({ hasText: 'event' }).click();
166+
167+
// Verify selection is reflected in the dropdown text
168+
await expect(csvValidatorPage.schemaDropdownTrigger).toContainText('event');
169+
170+
// Verify validate button is still disabled (no CSV uploaded yet)
171+
await expect(csvValidatorPage.validateButton).toBeDisabled();
172+
});
173+
174+
/**
175+
* Test 2: Schema documentation panel toggle and content
176+
*/
177+
test('should toggle schema documentation panel and display content', async () => {
178+
// First select a schema
179+
await csvValidatorPage.selectSchema('event');
180+
181+
// Verify doc panel is initially hidden
182+
const isInitiallyVisible = await csvValidatorPage.isSchemaDocPanelVisible();
183+
expect(isInitiallyVisible).toBeFalsy();
184+
185+
// Toggle panel and verify it becomes visible
186+
const isNowVisible = await csvValidatorPage.toggleSchemaDocPanel();
187+
expect(isNowVisible).toBeTruthy();
188+
189+
// Verify documentation content is loaded
190+
const docPanel = csvValidatorPage.schemaDocPanel;
191+
await expect(docPanel.getByText('Event Schema')).toBeVisible();
192+
await expect(docPanel.getByText('Properties')).toBeVisible();
193+
await expect(docPanel.getByText('id')).toBeVisible();
194+
await expect(docPanel.getByText('name')).toBeVisible();
195+
196+
// Toggle panel again and verify it's hidden
197+
const isStillVisible = await csvValidatorPage.toggleSchemaDocPanel();
198+
expect(isStillVisible).toBeFalsy();
199+
});
200+
201+
/**
202+
* Test 3: CSV file upload functionality
203+
*/
204+
test('should upload and display CSV file content', async () => {
205+
// First select a schema
206+
await csvValidatorPage.selectSchema('event');
207+
208+
// Get the path to our sample CSV file
209+
const sampleCsvPath = path.join(process.cwd(), 'public/fixtures/sample.csv');
210+
211+
// Upload the CSV
212+
await csvValidatorPage.uploadCsv(sampleCsvPath);
213+
214+
// Verify CSV content is displayed in the editor
215+
// Since we can't directly check monaco editor content, we'll check if validate button becomes enabled
216+
await expect(csvValidatorPage.validateButton).toBeEnabled();
217+
218+
// Check for visual cues that upload succeeded
219+
await expect(csvValidatorPage.page.getByText('sample.csv')).toBeVisible();
220+
});
221+
222+
/**
223+
* Test 4: Validation button activation
224+
*/
225+
test('should enable validation button only after schema selection and CSV upload', async () => {
226+
// Initially, validate button should be disabled
227+
await expect(csvValidatorPage.validateButton).toBeDisabled();
228+
229+
// Select a schema, button should still be disabled
230+
await csvValidatorPage.selectSchema('event');
231+
await expect(csvValidatorPage.validateButton).toBeDisabled();
232+
233+
// Upload CSV, button should become enabled
234+
const sampleCsvPath = path.join(process.cwd(), 'public/fixtures/sample.csv');
235+
await csvValidatorPage.uploadCsv(sampleCsvPath);
236+
237+
// Now validate button should be enabled
238+
await expect(csvValidatorPage.validateButton).toBeEnabled();
239+
});
240+
241+
/**
242+
* Test 5: Validation results display for valid CSV
243+
*/
244+
test('should validate and display success for valid CSV', async () => {
245+
// Set up test with schema and valid CSV
246+
await csvValidatorPage.selectSchema('event');
247+
const sampleCsvPath = path.join(process.cwd(), 'public/fixtures/sample.csv');
248+
await csvValidatorPage.uploadCsv(sampleCsvPath);
249+
250+
// Trigger validation
251+
await csvValidatorPage.validate();
252+
253+
// Check for success message
254+
const status = await csvValidatorPage.getValidationStatus();
255+
expect(status).toBe('valid');
256+
257+
// Results panel should show "Success: Data is valid!"
258+
await csvValidatorPage.resultsContain('Success: Data is valid!');
259+
});
260+
261+
/**
262+
* Test 6: Validation results display for invalid CSV
263+
*/
264+
test('should validate and display errors for invalid CSV', async () => {
265+
// Set up test with schema and invalid CSV
266+
await csvValidatorPage.selectSchema('event');
267+
const invalidCsvPath = path.join(process.cwd(), 'public/fixtures/invalid-sample.csv');
268+
await csvValidatorPage.uploadCsv(invalidCsvPath);
269+
270+
// Trigger validation
271+
await csvValidatorPage.validate();
272+
273+
// Check for error status
274+
const status = await csvValidatorPage.getValidationStatus();
275+
expect(status).toBe('invalid');
276+
277+
// Results panel should show "Invalid data"
278+
await csvValidatorPage.resultsContain('Invalid data');
279+
280+
// Should show specific error messages
281+
await csvValidatorPage.resultsContain('Row 1'); // Error in row 1
282+
await csvValidatorPage.resultsContain('Row 2'); // Error in row 2
283+
await csvValidatorPage.resultsContain('Row 3'); // Error in row 3
284+
});
285+
286+
/**
287+
* Test 7 (Optional): Error highlighting when clicking on errors
288+
*/
289+
test('should highlight CSV line when clicking on error', async () => {
290+
// Set up test with schema and invalid CSV
291+
await csvValidatorPage.selectSchema('event');
292+
const invalidCsvPath = path.join(process.cwd(), 'public/fixtures/invalid-sample.csv');
293+
await csvValidatorPage.uploadCsv(invalidCsvPath);
294+
295+
// Trigger validation
296+
await csvValidatorPage.validate();
297+
298+
// Verify we have errors
299+
const status = await csvValidatorPage.getValidationStatus();
300+
expect(status).toBe('invalid');
301+
302+
// Click on a specific error (e.g., Row 1)
303+
await csvValidatorPage.clickResultItem(1);
304+
305+
// We can't directly check if line highlighting happened in Monaco editor,
306+
// but we can verify the accordion expanded, which is part of the interaction
307+
// This is handled in the clickResultItem method
308+
});
309+
});

0 commit comments

Comments
 (0)