@@ -145,9 +145,11 @@ test.describe('Task Approval API Contract', () => {
145145 * Test that 422 validation errors from task approval are properly handled.
146146 *
147147 * This test mocks a 422 response to verify the frontend handles it gracefully.
148+ * NOTE: Since earlier tests may have transitioned the project to 'active' phase,
149+ * we must mock the status endpoint to return 'planning' phase so TaskReview renders.
148150 */
149151 test ( 'should handle 422 validation error gracefully' , async ( { page } ) => {
150- // Mock a 422 validation error response
152+ // Mock a 422 validation error response for the approval API
151153 await page . route ( '**/api/projects/*/tasks/approve' , async ( route : Route ) => {
152154 await route . fulfill ( {
153155 status : 422 ,
@@ -164,6 +166,48 @@ test.describe('Task Approval API Contract', () => {
164166 } ) ;
165167 } ) ;
166168
169+ // Mock project status to return 'planning' phase so TaskReview component renders
170+ // (Earlier tests may have transitioned the project to 'active' phase)
171+ await page . route ( '**/api/projects/*/status' , async ( route : Route ) => {
172+ await route . fulfill ( {
173+ status : 200 ,
174+ contentType : 'application/json' ,
175+ body : JSON . stringify ( {
176+ id : parseInt ( PROJECT_ID ) ,
177+ name : 'e2e-planning-project' ,
178+ description : 'Test project' ,
179+ status : 'planning' ,
180+ phase : 'planning' , // Critical: Must be 'planning' for TaskReview to render
181+ workflow_step : 1 ,
182+ progress : { completed_tasks : 0 , total_tasks : 6 , percentage : 0 }
183+ } )
184+ } ) ;
185+ } ) ;
186+
187+ // Mock issues endpoint to return test issues with tasks (required for TaskReview)
188+ await page . route ( '**/api/projects/*/issues*' , async ( route : Route ) => {
189+ await route . fulfill ( {
190+ status : 200 ,
191+ contentType : 'application/json' ,
192+ body : JSON . stringify ( {
193+ issues : [
194+ {
195+ id : '1' ,
196+ issue_number : '1.1' ,
197+ title : 'Test Issue 1' ,
198+ status : 'pending' ,
199+ tasks : [
200+ { id : '1' , task_number : '1.1.1' , title : 'Test Task 1' , description : 'Description' } ,
201+ { id : '2' , task_number : '1.1.2' , title : 'Test Task 2' , description : 'Description' }
202+ ]
203+ }
204+ ] ,
205+ total_issues : 1 ,
206+ total_tasks : 2
207+ } )
208+ } ) ;
209+ } ) ;
210+
167211 // Navigate to project
168212 await page . goto ( `${ FRONTEND_URL } /projects/${ PROJECT_ID } ` ) ;
169213 await page . waitForLoadState ( 'networkidle' ) ;
@@ -173,7 +217,7 @@ test.describe('Task Approval API Contract', () => {
173217 await expect ( tasksTab ) . toBeVisible ( { timeout : 10000 } ) ;
174218 await tasksTab . click ( ) ;
175219
176- // Look for approve button - MUST be visible
220+ // Look for approve button - MUST be visible (TaskReview renders because phase is mocked to 'planning')
177221 const approveButton = page . getByRole ( 'button' , { name : / a p p r o v e / i } ) ;
178222 await expect ( approveButton ) . toBeVisible ( { timeout : 5000 } ) ;
179223
@@ -190,14 +234,10 @@ test.describe('Task Approval API Contract', () => {
190234 expect ( response . status ( ) ) . toBe ( 422 ) ;
191235
192236 // Verify UI shows error message (not a blank screen)
193- // Use explicit assertion - FAIL if no error UI appears
194- const errorIndicators = page . locator ( '[class*="destructive"], [class*="error"], [role="alert"]' ) ;
195- const errorText = page . getByText ( / f a i l e d | e r r o r | t r y a g a i n / i) ;
196-
197- // At least one error indicator must be visible
198- await expect (
199- errorIndicators . first ( ) . or ( errorText . first ( ) )
200- ) . toBeVisible ( { timeout : 5000 } ) ;
237+ // Look for the specific error message from TaskReview component
238+ // Note: Exclude Next.js route announcer which also has role="alert"
239+ const errorMessage = page . getByText ( 'Failed to approve tasks' ) ;
240+ await expect ( errorMessage ) . toBeVisible ( { timeout : 5000 } ) ;
201241 } ) ;
202242
203243} ) ;
0 commit comments