Skip to content

Commit 1a90b20

Browse files
test case updated
1 parent 9370012 commit 1a90b20

5 files changed

Lines changed: 50 additions & 53 deletions

File tree

__tests__/html2/speechToSpeech/csp.recording.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,10 @@
101101
);
102102
expect(blobViolations.length).toBe(0);
103103

104-
// THEN: Verify audio chunks are being captured (AudioWorklet is functioning)
104+
// THEN: Verify recording state is active (AudioWorklet is functioning)
105105
await pageConditions.became(
106-
'Audio chunks are being sent to voiceActivities store',
107-
() => store.getState().voiceActivities && store.getState().voiceActivities.length > 0,
106+
'Voice state is listening (recording active)',
107+
() => store.getState().voice?.recording === true && store.getState().voice?.speechState === 'listening',
108108
1000
109109
);
110110

__tests__/html2/speechToSpeech/dtmf.input.html

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434
} = window;
3535

3636
const { directLine, store } = testHelpers.createDirectLineEmulator();
37+
38+
// Intercept postActivity to capture outgoing DTMF events
39+
const capturedDtmfEvents = [];
40+
const originalPostActivity = directLine.postActivity.bind(directLine);
41+
directLine.postActivity = (activity) => {
42+
if (activity.name === 'dtmf' && activity.payload?.dtmf) {
43+
capturedDtmfEvents.push(activity);
44+
}
45+
return originalPostActivity(activity);
46+
};
3747

3848
render(
3949
<FluentThemeProvider variant="fluent">
@@ -113,20 +123,14 @@
113123
await host.click(key3);
114124
await host.click(key4);
115125

116-
// ===== STEP 5: Verify DTMF activities in voice state =====
126+
// ===== STEP 5: Verify DTMF events were sent via postActivity =====
117127
await pageConditions.became(
118-
'DTMF events received',
119-
() => {
120-
const voiceActivities = store.getState().voiceActivities;
121-
const dtmfEvents = voiceActivities?.filter(a => a.name === 'dtmf');
122-
return dtmfEvents?.length >= 4;
123-
},
128+
'DTMF events sent via postActivity',
129+
() => capturedDtmfEvents.length >= 4,
124130
1000
125131
);
126132

127-
const voiceActivities = store.getState().voiceActivities;
128-
const dtmfActivities = voiceActivities?.filter(a => a.name === 'dtmf');
129-
expect(dtmfActivities.length).toBe(4);
133+
expect(capturedDtmfEvents.length).toBe(4);
130134

131135
await host.click(keypadButton);
132136

__tests__/html2/speechToSpeech/happy.path.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@
6161
1000
6262
);
6363

64-
// THEN: Verify voice activities (audio chunks) are in voiceActivities store
64+
// THEN: Verify voice state is recording/listening
6565
await pageConditions.became(
66-
'Audio chunks are being sent to voiceActivities store',
67-
() => store.getState().voiceActivities && store.getState().voiceActivities.length > 0,
66+
'Voice state is listening',
67+
() => store.getState().voice?.speechState === 'listening',
6868
1000
6969
);
7070

__tests__/html2/speechToSpeech/outgoing.audio.interval.html

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
1818
This test validates:
1919
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.
2326
-->
2427
<script type="module">
2528
import { setupMockMediaDevices } from '/assets/esm/speechToSpeech/mockMediaDevices.js';
@@ -37,6 +40,19 @@
3740
} = window;
3841

3942
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+
};
4056

4157
render(
4258
<FluentThemeProvider variant="fluent">
@@ -77,14 +93,8 @@
7793
// ===== STEP 2: Wait for multiple chunks =====
7894
// Default chunk interval is 100ms, wait for at least 5 chunks for better interval calculation
7995
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,
8898
2000
8999
);
90100

@@ -97,26 +107,20 @@
97107
1000
98108
);
99109

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);
106112

107113
// ===== STEP 5: Verify chunk structure =====
108-
const sampleChunk = userChunks[0];
114+
const sampleChunk = capturedChunks[0];
109115

110116
expect(sampleChunk.type).toBe('event');
111117
expect(sampleChunk.name).toBe('stream.chunk');
112-
expect(sampleChunk.from.role).toBe('user');
113118
expect(sampleChunk.payload).toBeTruthy();
114119
expect(sampleChunk.payload.voice).toBeTruthy();
115120
expect(sampleChunk.payload.voice.content).toBeTruthy();
116-
expect(sampleChunk.payload.voice.timestamp).toBeTruthy();
117121

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);
120124

121125
// Calculate intervals between consecutive chunks
122126
const intervals = [];
@@ -127,9 +131,9 @@
127131
// Calculate average interval
128132
const avgInterval = intervals.reduce((sum, i) => sum + i, 0) / intervals.length;
129133

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);
133137
});
134138
</script>
135139
</body>

packages/test/page-object/src/globals/testHelpers/createDirectLineEmulator.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,7 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill
173173
1000
174174
));
175175
},
176-
emulateIncomingVoiceActivity: async (activity, { skipWait } = {}) => {
177-
activity = updateIn(activity, ['from', 'role'], role => role || 'bot');
178-
activity = updateIn(activity, ['id'], id => id || uniqueId());
176+
emulateIncomingVoiceActivity: activity => {
179177
activity = updateIn(activity, ['timestamp'], timestamp =>
180178
typeof timestamp === 'number'
181179
? new Date(now + timestamp).toISOString()
@@ -185,16 +183,7 @@ export default function createDirectLineEmulator({ autoConnect = true, ponyfill
185183
);
186184
activity = updateIn(activity, ['type'], type => type || 'event');
187185

188-
const { id } = activity;
189-
190186
activityDeferredObservable.next(activity);
191-
192-
skipWait ||
193-
(await became(
194-
'incoming voice activity appears in the voiceActivities store',
195-
() => store.getState().voiceActivities?.find(activity => activity.id === id),
196-
1000
197-
));
198187
},
199188
emulateOutgoingActivity: (activity, options) => {
200189
if (typeof activity === 'string') {

0 commit comments

Comments
 (0)