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