|
17 | 17 | |
18 | 18 | This test validates: |
19 | 19 | 1. Mic on → state becomes "listening" |
20 | | - 2. Audio chunks are sent with timestamps in payload.voice.timestamp |
21 | | - 3. Verify ~100ms interval between chunk timestamps |
22 | | - 4. Verify chunk structure (type, name, from.role, payload.voice) |
| 20 | + 2. Audio chunks are sent via postActivity (fire-and-forget) |
| 21 | + 3. Verify chunk structure (type, name, payload.voice) |
| 22 | + 4. Verify ~100ms interval between chunk timestamps |
| 23 | + |
| 24 | + Note: Since voice activities use postVoiceActivity (fire-and-forget), |
| 25 | + we intercept postActivity to capture outgoing chunks for verification. |
23 | 26 | --> |
24 | 27 | <script type="module"> |
25 | 28 | import { setupMockMediaDevices } from '/assets/esm/speechToSpeech/mockMediaDevices.js'; |
|
37 | 40 | } = window; |
38 | 41 |
|
39 | 42 | const { directLine, store } = testHelpers.createDirectLineEmulator(); |
| 43 | + |
| 44 | + // Intercept postActivity to capture outgoing voice chunks |
| 45 | + const capturedChunks = []; |
| 46 | + const originalPostActivity = directLine.postActivity.bind(directLine); |
| 47 | + directLine.postActivity = (activity) => { |
| 48 | + if (activity.name === 'stream.chunk' && activity.payload?.voice) { |
| 49 | + capturedChunks.push({ |
| 50 | + ...activity, |
| 51 | + capturedAt: Date.now() |
| 52 | + }); |
| 53 | + } |
| 54 | + return originalPostActivity(activity); |
| 55 | + }; |
40 | 56 |
|
41 | 57 | render( |
42 | 58 | <FluentThemeProvider variant="fluent"> |
|
77 | 93 | // ===== STEP 2: Wait for multiple chunks ===== |
78 | 94 | // Default chunk interval is 100ms, wait for at least 5 chunks for better interval calculation |
79 | 95 | await pageConditions.became( |
80 | | - 'Multiple audio chunks sent', |
81 | | - () => { |
82 | | - const voiceActivities = store.getState().voiceActivities; |
83 | | - const userChunks = voiceActivities?.filter(a => |
84 | | - a.name === 'stream.chunk' && a.from?.role === 'user' |
85 | | - ); |
86 | | - return userChunks?.length >= 5; |
87 | | - }, |
| 96 | + 'Multiple audio chunks sent via postActivity', |
| 97 | + () => capturedChunks.length >= 5, |
88 | 98 | 2000 |
89 | 99 | ); |
90 | 100 |
|
|
97 | 107 | 1000 |
98 | 108 | ); |
99 | 109 |
|
100 | | - // ===== STEP 4: Analyze chunks from voiceActivities ===== |
101 | | - const userChunks = store.getState().voiceActivities.filter(a => |
102 | | - a.name === 'stream.chunk' && a.from?.role === 'user' |
103 | | - ); |
104 | | - |
105 | | - expect(userChunks.length).toBeGreaterThanOrEqual(5); |
| 110 | + // ===== STEP 4: Verify captured chunks ===== |
| 111 | + expect(capturedChunks.length).toBeGreaterThanOrEqual(5); |
106 | 112 |
|
107 | 113 | // ===== STEP 5: Verify chunk structure ===== |
108 | | - const sampleChunk = userChunks[0]; |
| 114 | + const sampleChunk = capturedChunks[0]; |
109 | 115 |
|
110 | 116 | expect(sampleChunk.type).toBe('event'); |
111 | 117 | expect(sampleChunk.name).toBe('stream.chunk'); |
112 | | - expect(sampleChunk.from.role).toBe('user'); |
113 | 118 | expect(sampleChunk.payload).toBeTruthy(); |
114 | 119 | expect(sampleChunk.payload.voice).toBeTruthy(); |
115 | 120 | expect(sampleChunk.payload.voice.content).toBeTruthy(); |
116 | | - expect(sampleChunk.payload.voice.timestamp).toBeTruthy(); |
117 | 121 |
|
118 | | - // ===== STEP 6: Verify interval using timestamps ===== |
119 | | - const timestamps = userChunks.map(c => new Date(c.payload.voice.timestamp).getTime()).sort((a, b) => a - b); |
| 122 | + // ===== STEP 6: Verify interval using capturedAt timestamps ===== |
| 123 | + const timestamps = capturedChunks.map(c => c.capturedAt).sort((a, b) => a - b); |
120 | 124 |
|
121 | 125 | // Calculate intervals between consecutive chunks |
122 | 126 | const intervals = []; |
|
127 | 131 | // Calculate average interval |
128 | 132 | const avgInterval = intervals.reduce((sum, i) => sum + i, 0) / intervals.length; |
129 | 133 |
|
130 | | - // Verify average interval is approximately 100ms (allow 80-120ms range for test stability) |
131 | | - expect(avgInterval).toBeGreaterThanOrEqual(80); |
132 | | - expect(avgInterval).toBeLessThanOrEqual(120); |
| 134 | + // Verify average interval is approximately 100ms (allow 50-150ms range for test stability) |
| 135 | + expect(avgInterval).toBeGreaterThanOrEqual(50); |
| 136 | + expect(avgInterval).toBeLessThanOrEqual(150); |
133 | 137 | }); |
134 | 138 | </script> |
135 | 139 | </body> |
|
0 commit comments