@@ -63,7 +63,7 @@ test.describe('Device Error Recovery (Issue #1190)', () => {
6363 } ) ;
6464
6565 // Error message should be visible
66- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
66+ await expect ( page . locator ( 'text=Camera access denied ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
6767
6868 // Fix A/V button should STILL be accessible so users can attempt to recover
6969 await expect ( page . locator ( '[data-test="fixAV"]' ) ) . toBeVisible ( ) ;
@@ -85,7 +85,7 @@ test.describe('Device Error Recovery (Issue #1190)', () => {
8585 } ) ;
8686 } ) ;
8787
88- await expect ( page . locator ( 'text=Microphone blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
88+ await expect ( page . locator ( 'text=Microphone access denied ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
8989
9090 // Fix A/V button should still be accessible
9191 await expect ( page . locator ( '[data-test="fixAV"]' ) ) . toBeVisible ( ) ;
@@ -109,7 +109,7 @@ test.describe('Device Error Recovery (Issue #1190)', () => {
109109 } ) ;
110110 } ) ;
111111
112- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
112+ await expect ( page . locator ( 'text=Camera in use ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
113113
114114 // Modal close button (X) should be available
115115 await expect ( page . locator ( 'button[aria-label="Close"]' ) ) . toBeVisible ( ) ;
@@ -135,13 +135,13 @@ test.describe('Device Error Recovery (Issue #1190)', () => {
135135 } ) ;
136136 } ) ;
137137
138- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
138+ await expect ( page . locator ( 'text=Camera in use ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
139139
140140 // Dismiss the error via the modal's X close button
141141 await page . locator ( 'button[aria-label="Close"]' ) . click ( ) ;
142142
143143 // Error message should be gone
144- await expect ( page . locator ( 'text=Camera blocked ' ) ) . not . toBeVisible ( ) ;
144+ await expect ( page . locator ( 'text=Camera in use ' ) ) . not . toBeVisible ( ) ;
145145
146146 // Normal call tiles should be restored
147147 await expect ( component . locator ( '[data-test="callTile"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
@@ -173,13 +173,13 @@ test.describe('Device Error Recovery — Permission guidance (Issue #1190)', ()
173173 } ) ;
174174 } ) ;
175175
176- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
176+ await expect ( page . locator ( 'text=Camera access denied ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
177177
178178 // Browser-specific guidance should appear
179179 await expect ( page . locator ( 'text=Please enable it in your browser settings' ) ) . toBeVisible ( ) ;
180180
181- // The generic lock-icon step should NOT appear when dailyErrorType is "permissions"
182- await expect ( page . locator ( "text=Use the lock icon in your browser's address bar to allow camera access " ) ) . not . toBeVisible ( ) ;
181+ // Generic steps should NOT appear when dailyErrorType is "permissions"
182+ await expect ( page . locator ( "text=Close any other app " ) ) . not . toBeVisible ( ) ;
183183 } ) ;
184184
185185 /**
@@ -211,7 +211,7 @@ test.describe('Device Error Recovery — Permission guidance (Issue #1190)', ()
211211 } ) ;
212212 } ) ;
213213
214- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
214+ await expect ( page . locator ( 'text=Camera access denied ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
215215
216216 // The image for the current browser should be rendered and actually loaded
217217 const img = page . locator ( `img[src*="${ expectedImageSubstring } "]` ) ;
@@ -278,7 +278,7 @@ test.describe('Device Error Recovery — Permission guidance (Issue #1190)', ()
278278 } ) ;
279279 } ) ;
280280
281- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
281+ await expect ( page . locator ( 'text=Camera access denied ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
282282
283283 // User grants permissions in browser settings
284284 await page . evaluate ( ( ) => window . simulatePermissionsGranted ( ) ) ;
@@ -306,10 +306,10 @@ test.describe('Device Error Recovery — Permission guidance (Issue #1190)', ()
306306 } ) ;
307307 } ) ;
308308
309- await expect ( page . locator ( 'text=Camera blocked ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
309+ await expect ( page . locator ( 'text=Camera in use ' ) ) . toBeVisible ( { timeout : 8000 } ) ;
310310
311- // For in-use errors, generic steps should be shown
312- await expect ( page . locator ( "text=Use the lock icon in your browser's address bar to allow camera access " ) ) . toBeVisible ( ) ;
311+ // For in-use errors, cause-specific steps should be shown
312+ await expect ( page . locator ( "text=Close any other app " ) ) . toBeVisible ( ) ;
313313
314314 // Browser-specific permission guidance should NOT appear
315315 await expect ( page . locator ( 'text=Please enable it in your browser settings' ) ) . not . toBeVisible ( ) ;
@@ -347,7 +347,7 @@ test.describe('Device Error Recovery — Device picker (Issue #1190)', () => {
347347 } ) ;
348348 } ) ;
349349
350- await expect ( page . locator ( 'text= Camera blocked' ) ) . toBeVisible ( { timeout : 8000 } ) ;
350+ await expect ( page . getByRole ( 'heading' , { name : ' Camera disconnected' } ) ) . toBeVisible ( { timeout : 8000 } ) ;
351351
352352 // Device picker should appear with available cameras
353353 await expect ( page . locator ( '[data-test="devicePickerSelect"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
@@ -359,7 +359,7 @@ test.describe('Device Error Recovery — Device picker (Issue #1190)', () => {
359359 expect ( optionCount ) . toBeGreaterThanOrEqual ( 1 ) ;
360360
361361 // Generic steps should NOT appear when picker is shown
362- await expect ( page . locator ( "text=Use the lock icon in your browser's address bar to allow camera access " ) ) . not . toBeVisible ( ) ;
362+ await expect ( page . locator ( "text=Close any other app " ) ) . not . toBeVisible ( ) ;
363363 } ) ;
364364
365365 /**
@@ -387,7 +387,7 @@ test.describe('Device Error Recovery — Device picker (Issue #1190)', () => {
387387 } ) ;
388388 } ) ;
389389
390- await expect ( page . locator ( 'text= Microphone blocked' ) ) . toBeVisible ( { timeout : 8000 } ) ;
390+ await expect ( page . getByRole ( 'heading' , { name : ' Microphone disconnected' } ) ) . toBeVisible ( { timeout : 8000 } ) ;
391391
392392 // Mic picker should appear
393393 await expect ( page . locator ( '[data-test="devicePickerSelect"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
@@ -423,7 +423,7 @@ test.describe('Device Error Recovery — Device picker (Issue #1190)', () => {
423423 } ) ;
424424 } ) ;
425425
426- await expect ( page . locator ( 'text= Camera blocked' ) ) . toBeVisible ( { timeout : 8000 } ) ;
426+ await expect ( page . getByRole ( 'heading' , { name : ' Camera disconnected' } ) ) . toBeVisible ( { timeout : 8000 } ) ;
427427 await expect ( page . locator ( '[data-test="devicePickerSelect"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
428428
429429 // Dispatch a click directly to bypass Playwright's actionability retry loop
@@ -438,7 +438,72 @@ test.describe('Device Error Recovery — Device picker (Issue #1190)', () => {
438438 expect ( calls [ calls . length - 1 ] . videoDeviceId ) . not . toBeUndefined ( ) ;
439439
440440 // Error overlay should be dismissed and call tiles restored
441- await expect ( page . locator ( 'text= Camera blocked' ) ) . not . toBeVisible ( { timeout : 5000 } ) ;
441+ await expect ( page . getByRole ( 'heading' , { name : ' Camera disconnected' } ) ) . not . toBeVisible ( { timeout : 5000 } ) ;
442442 await expect ( component . locator ( '[data-test="callTile"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
443443 } ) ;
444444} ) ;
445+
446+ test . describe ( 'Device Error Recovery — Error priority (Issue #1190)' , ( ) => {
447+ /**
448+ * DEVRECOV-012: permissions error takes priority over in-use error
449+ *
450+ * When a lower-priority error (in-use) is showing and a higher-priority
451+ * error (permissions) arrives, the modal should update to show the
452+ * permissions error. This ensures users see the most actionable guidance.
453+ */
454+ test ( 'DEVRECOV-012: permissions error overwrites in-use error' , async ( { mount, page } ) => {
455+ const component = await mount ( < VideoCall showSelfView /> , { hooksConfig : connectedConfig } ) ;
456+ await expect ( component ) . toBeVisible ( { timeout : 15000 } ) ;
457+
458+ // Fire in-use error first
459+ await page . evaluate ( ( ) => {
460+ window . mockCallObject . emit ( 'camera-error' , {
461+ error : { type : 'in-use' , message : 'Camera in use' } ,
462+ } ) ;
463+ } ) ;
464+
465+ await expect ( page . locator ( 'text=Camera in use' ) ) . toBeVisible ( { timeout : 8000 } ) ;
466+
467+ // Fire permissions error — should overwrite the in-use error
468+ await page . evaluate ( ( ) => {
469+ window . mockCallObject . emit ( 'camera-error' , {
470+ error : { type : 'permissions' , message : 'Permission denied' } ,
471+ } ) ;
472+ } ) ;
473+
474+ await expect ( page . locator ( 'text=Camera access denied' ) ) . toBeVisible ( { timeout : 5000 } ) ;
475+ await expect ( page . locator ( 'text=Camera in use' ) ) . not . toBeVisible ( ) ;
476+ } ) ;
477+
478+ /**
479+ * DEVRECOV-013: lower-priority error does not overwrite higher-priority error
480+ *
481+ * When a permissions error is showing and an in-use error arrives,
482+ * the modal should keep showing the permissions error.
483+ */
484+ test ( 'DEVRECOV-013: in-use error does not overwrite permissions error' , async ( { mount, page } ) => {
485+ const component = await mount ( < VideoCall showSelfView /> , { hooksConfig : connectedConfig } ) ;
486+ await expect ( component ) . toBeVisible ( { timeout : 15000 } ) ;
487+
488+ // Fire permissions error first
489+ await page . evaluate ( ( ) => {
490+ window . mockCallObject . emit ( 'camera-error' , {
491+ error : { type : 'permissions' , message : 'Permission denied' } ,
492+ } ) ;
493+ } ) ;
494+
495+ await expect ( page . locator ( 'text=Camera access denied' ) ) . toBeVisible ( { timeout : 8000 } ) ;
496+
497+ // Fire in-use error — should NOT overwrite the permissions error
498+ await page . evaluate ( ( ) => {
499+ window . mockCallObject . emit ( 'camera-error' , {
500+ error : { type : 'in-use' , message : 'Camera in use' } ,
501+ } ) ;
502+ } ) ;
503+
504+ // Wait briefly then verify permissions error is still showing
505+ await page . waitForTimeout ( 1000 ) ;
506+ await expect ( page . locator ( 'text=Camera access denied' ) ) . toBeVisible ( ) ;
507+ await expect ( page . locator ( 'text=Camera in use' ) ) . not . toBeVisible ( ) ;
508+ } ) ;
509+ } ) ;
0 commit comments