11import { expect , test } from '@playwright/test' ;
22import TestData from '../fixtures/testData' ;
3- import { waitForBreathingExerciseToStart } from '../fixtures/testHelpers' ;
3+ import { waitForBreathingExerciseToStart , waitForExerciseTimer } from '../fixtures/testHelpers' ;
44
55test . describe ( 'Breathing Exercise' , ( ) => {
66 test . beforeEach ( async ( { page } ) => {
@@ -22,7 +22,7 @@ test.describe('Breathing Exercise', () => {
2222 await expect ( page . locator ( 'h2' ) . filter ( { hasText : / b r e a t h i n g e x e r c i s e / i } ) ) . toBeVisible ( ) ;
2323 await expect ( page . locator ( 'p' ) . filter ( { hasText : / i n h a l e .* f o r .* 4 .* s e c o n d s .* h o l d .* f o r .* 7 .* s e c o n d s .* a n d .* e x h a l e .* f o r .* 8 .* s e c o n d s / i } ) ) . toBeVisible ( ) ;
2424
25- const soundToggle = page . locator ( 'svg[class*="lucide-volume"]' ) ;
25+ const soundToggle = page . locator ( TestData . selectors . soundToggle ) ;
2626 await expect ( soundToggle ) . toBeVisible ( ) ;
2727 } ) ;
2828
@@ -35,8 +35,7 @@ test.describe('Breathing Exercise', () => {
3535 console . log ( 'H2 elements found:' , h2Elements ) ;
3636
3737 // The exercise should show a timer when running
38- // Look for any h2 that contains time format (MM:SS or M:SS)
39- const timerElement = page . locator ( 'h2' ) . filter ( { hasText : / \d + : \d + / } ) ;
38+ const timerElement = page . locator ( TestData . selectors . timer ) ;
4039 const isTimerVisible = await timerElement . isVisible ( ) ;
4140
4241 if ( ! isTimerVisible ) {
@@ -52,17 +51,17 @@ test.describe('Breathing Exercise', () => {
5251 // If timer is visible, check for controls
5352 await expect ( timerElement ) . toBeVisible ( ) ;
5453
55- const pausePlayButton = page . locator ( 'button svg[class*="lucide-pause"], button svg[class*="lucide-play"]' ) ;
54+ const pausePlayButton = page . locator ( TestData . selectors . pauseButton ) . or ( page . locator ( TestData . selectors . playButton ) ) ;
5655 await expect ( pausePlayButton ) . toBeVisible ( ) ;
57- await expect ( page . locator ( 'p' ) . filter ( { hasText : / t h r o u g h y o u r n o s e | h o l d y o u r b r e a t h | t h r o u g h y o u r m o u t h | i n h a l e | e x h a l e / i } ) ) . toBeVisible ( ) ;
56+ await expect ( page . locator ( TestData . selectors . instructionText ) ) . toBeVisible ( ) ;
5857 }
5958 } ) ;
6059
6160 test ( 'should have back navigation button' , async ( { page } ) => {
6261 // Wait for the page to load first
6362 await waitForBreathingExerciseToStart ( page ) ;
6463
65- const backButton = page . locator ( 'svg[class*="lucide-arrow-left"]' ) ;
64+ const backButton = page . locator ( TestData . selectors . backButton ) ;
6665 await expect ( backButton ) . toBeVisible ( ) ;
6766 } ) ;
6867 } ) ;
@@ -71,54 +70,58 @@ test.describe('Breathing Exercise', () => {
7170 test ( 'should toggle pause/play when pause button is clicked' , async ( { page } ) => {
7271 await waitForBreathingExerciseToStart ( page ) ;
7372
74- await page . waitForFunction (
75- ( ) => {
76- const h1Elements = Array . from ( document . querySelectorAll ( 'h1' ) ) ;
77- const timerH2 = Array . from ( document . querySelectorAll ( 'h2' ) ) . find (
78- el => / \d + : \d + / . test ( el . textContent || '' )
79- ) ;
80- return h1Elements . length === 0 && timerH2 !== undefined ;
81- } ,
82- { timeout : 15000 }
83- ) ;
84-
85- const timerElement = page . locator ( 'h2' ) . filter ( { hasText : / \d + : \d + / } ) ;
86- await expect ( timerElement ) . toBeVisible ( ) ;
87-
88- const pauseButton = page . locator ( 'button' ) . filter ( { has : page . locator ( 'svg[class*="lucide-pause"]' ) } ) ;
89- await expect ( pauseButton ) . toBeVisible ( ) ;
90- await pauseButton . click ( ) ;
91-
92- const playButton = page . locator ( 'button' ) . filter ( { has : page . locator ( 'svg[class*="lucide-play"]' ) } ) ;
93- await expect ( playButton ) . toBeVisible ( ) ;
94- await playButton . click ( ) ;
95- await expect ( pauseButton ) . toBeVisible ( ) ;
73+ // Wait for timer to appear (exercise has started, intro phase is over)
74+ // This accounts for the ~13 second intro phase in CI
75+ await waitForExerciseTimer ( page , 25000 ) ;
76+
77+ // Wait for pause button to be visible
78+ const pauseButton = page . locator ( TestData . selectors . pauseButton ) ;
79+ await expect ( pauseButton ) . toBeVisible ( { timeout : 15000 } ) ;
80+
81+ // Click pause button and wait for play button to appear
82+ // Use force: true because button is constantly animating (never "stable")
83+ await pauseButton . click ( { force : true } ) ;
84+
85+ // Wait for play button to appear after pause
86+ const playButton = page . locator ( TestData . selectors . playButton ) ;
87+ await expect ( playButton ) . toBeVisible ( { timeout : 15000 } ) ;
88+
89+ // Click play button
90+ // Use force: true because button is constantly animating (never "stable")
91+ await playButton . click ( { force : true } ) ;
92+
93+ // Wait for pause button to reappear
94+ await expect ( pauseButton ) . toBeVisible ( { timeout : 15000 } ) ;
9695 } ) ;
9796
9897 test ( 'should navigate back when back button is clicked' , async ( { page } ) => {
9998 await waitForBreathingExerciseToStart ( page ) ;
10099
101- const backButton = page . locator ( 'svg[class*="lucide-arrow-left"]' ) ;
100+ const backButton = page . locator ( TestData . selectors . backButton ) ;
102101 await backButton . click ( ) ;
103102 await expect ( page ) . toHaveURL ( '/' ) ;
104103 } ) ;
105104
106105 test ( 'should toggle sound when sound button is clicked' , async ( { page } ) => {
107106 await waitForBreathingExerciseToStart ( page ) ;
108107
109- const soundButton = page . locator ( 'div.mt-8.cursor-pointer' ) ;
110- const initialVolume = await page . locator ( 'svg[class*="lucide-volume2"]' ) . isVisible ( ) ;
108+ const soundToggle = page . locator ( TestData . selectors . soundToggle ) ;
109+ await expect ( soundToggle ) . toBeVisible ( { timeout : 15000 } ) ;
110+
111+ // Check initial state - look for Volume2 icon (sound enabled) or VolumeX (sound disabled)
112+ const initialSoundEnabled = await page . locator ( 'svg[class*="lucide-volume2"]' ) . isVisible ( ) ;
111113
112- await soundButton . click ( ) ;
114+ await soundToggle . click ( ) ;
113115
114- if ( initialVolume ) {
115- await expect ( page . locator ( 'svg[class*="lucide-volume2"]' ) ) . not . toBeVisible ( ) ;
116+ // Wait for state to change
117+ if ( initialSoundEnabled ) {
118+ await expect ( page . locator ( 'svg[class*="lucide-volume2"]' ) ) . not . toBeVisible ( { timeout : 5000 } ) ;
116119 } else {
117- await expect ( page . locator ( 'svg[class*="lucide-volume2"]' ) ) . toBeVisible ( ) ;
120+ await expect ( page . locator ( 'svg[class*="lucide-volume2"]' ) ) . toBeVisible ( { timeout : 5000 } ) ;
118121 }
119122
120- const newVolume = await page . locator ( 'svg[class*="lucide-volume2"]' ) . isVisible ( ) ;
121- expect ( newVolume ) . not . toBe ( initialVolume ) ;
123+ const newSoundEnabled = await page . locator ( 'svg[class*="lucide-volume2"]' ) . isVisible ( ) ;
124+ expect ( newSoundEnabled ) . not . toBe ( initialSoundEnabled ) ;
122125 } ) ;
123126 } ) ;
124127
@@ -127,8 +130,8 @@ test.describe('Breathing Exercise', () => {
127130 await waitForBreathingExerciseToStart ( page ) ;
128131
129132 // Check if timer is already visible (exercise started)
130- const timer = page . locator ( 'h2' ) . filter ( { hasText : / \d + : \d + / } ) ;
131- const isTimerVisible = await timer . isVisible ( ) ;
133+ const timer = page . locator ( TestData . selectors . timer ) ;
134+ const isTimerVisible = await timer . isVisible ( { timeout : 5000 } ) . catch ( ( ) => false ) ;
132135
133136 if ( ! isTimerVisible ) {
134137 // Exercise is in intro mode, skip this test
@@ -150,12 +153,12 @@ test.describe('Breathing Exercise', () => {
150153 await expect ( introInstructions ) . toBeVisible ( ) ;
151154
152155 // Check if exercise is running (has timer)
153- const timer = page . locator ( 'h2' ) . filter ( { hasText : / \d + : \d + / } ) ;
154- const isTimerVisible = await timer . isVisible ( ) ;
156+ const timer = page . locator ( TestData . selectors . timer ) ;
157+ const isTimerVisible = await timer . isVisible ( { timeout : 5000 } ) . catch ( ( ) => false ) ;
155158
156159 if ( isTimerVisible ) {
157160 // Exercise is running, check for dynamic instructions
158- const instructions = page . locator ( 'p' ) . filter ( { hasText : / i n h a l e | e x h a l e | h o l d / i } ) ;
161+ const instructions = page . locator ( TestData . selectors . instructionText ) ;
159162 await expect ( instructions ) . toBeVisible ( ) ;
160163
161164 const instructionText = await instructions . textContent ( ) ;
@@ -170,8 +173,8 @@ test.describe('Breathing Exercise', () => {
170173
171174 await page . setViewportSize ( TestData . viewports . mobile ) ;
172175 await expect ( page . locator ( 'h2' ) . filter ( { hasText : / b r e a t h i n g e x e r c i s e / i } ) ) . toBeVisible ( ) ;
173- await expect ( page . locator ( 'svg[class*="lucide-arrow-left"]' ) ) . toBeVisible ( ) ;
174- await expect ( page . locator ( 'svg[class*="lucide-volume"]' ) ) . toBeVisible ( ) ;
176+ await expect ( page . locator ( TestData . selectors . backButton ) ) . toBeVisible ( ) ;
177+ await expect ( page . locator ( TestData . selectors . soundToggle ) ) . toBeVisible ( ) ;
175178 } ) ;
176179
177180 test ( 'should maintain functionality on tablet' , async ( { page } ) => {
0 commit comments