Skip to content

Commit b39a636

Browse files
Add test case
1 parent 4e50f29 commit b39a636

File tree

4 files changed

+263
-1
lines changed

4 files changed

+263
-1
lines changed

injected/integration-test/pages.spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,15 @@ test.describe('Test integration pages', () => {
127127
);
128128
});
129129

130+
test('enumerateDevices API functionality', async ({ page }, testInfo) => {
131+
await testPage(
132+
page,
133+
testInfo,
134+
'webcompat/pages/enumerate-devices-api-test.html',
135+
'./integration-test/test-pages/webcompat/config/enumerate-devices-api.json',
136+
);
137+
});
138+
130139
test('minSupportedVersion (string)', async ({ page }, testInfo) => {
131140
await testPage(
132141
page,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"readme": "This config is used to test the enumerateDevices API proxy functionality.",
3+
"version": 1,
4+
"unprotectedTemporary": [],
5+
"features": {
6+
"webCompat": {
7+
"state": "enabled",
8+
"exceptions": [],
9+
"settings": {
10+
"enumerateDevices": "enabled"
11+
}
12+
}
13+
}
14+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width">
6+
<title>enumerateDevices API Test</title>
7+
<link rel="stylesheet" href="../../shared/style.css">
8+
</head>
9+
<body>
10+
<script src="../../shared/utils.js"></script>
11+
<p><a href="../index.html">[Webcompat API Tests]</a></p>
12+
13+
<p>This page tests the enumerateDevices API proxy functionality</p>
14+
15+
<script>
16+
// Mock device data for testing
17+
const mockDevices = [
18+
{
19+
deviceId: 'audioinput-1',
20+
kind: 'audioinput',
21+
label: 'Test Microphone',
22+
groupId: 'group-1'
23+
},
24+
{
25+
deviceId: 'audiooutput-1',
26+
kind: 'audiooutput',
27+
label: 'Test Speaker',
28+
groupId: 'group-1'
29+
},
30+
{
31+
deviceId: 'videoinput-1',
32+
kind: 'videoinput',
33+
label: 'Test Camera',
34+
groupId: 'group-2'
35+
}
36+
];
37+
38+
// Helper function to create device objects with proper prototype
39+
function createDeviceObject(deviceData) {
40+
const device = Object.create(
41+
deviceData.kind === 'videoinput' || deviceData.kind === 'audioinput'
42+
? InputDeviceInfo.prototype
43+
: MediaDeviceInfo.prototype
44+
);
45+
46+
Object.defineProperties(device, {
47+
deviceId: { value: deviceData.deviceId, writable: false, enumerable: true },
48+
kind: { value: deviceData.kind, writable: false, enumerable: true },
49+
label: { value: deviceData.label, writable: false, enumerable: true },
50+
groupId: { value: deviceData.groupId, writable: false, enumerable: true }
51+
});
52+
53+
// Add toJSON method to avoid "Illegal invocation" errors
54+
device.toJSON = function() {
55+
return {
56+
deviceId: this.deviceId,
57+
kind: this.kind,
58+
label: this.label,
59+
groupId: this.groupId
60+
};
61+
};
62+
63+
return device;
64+
}
65+
66+
// Mock navigator.mediaDevices.enumerateDevices
67+
const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;
68+
navigator.mediaDevices.enumerateDevices = async function() {
69+
return mockDevices.map(createDeviceObject);
70+
};
71+
72+
test('Native enumerateDevices behavior', async () => {
73+
const results = [];
74+
75+
try {
76+
const devices = await navigator.mediaDevices.enumerateDevices();
77+
78+
results.push({
79+
name: 'Returns array of devices',
80+
result: Array.isArray(devices),
81+
expected: true
82+
});
83+
84+
results.push({
85+
name: 'Returns correct number of devices',
86+
result: devices.length,
87+
expected: 3
88+
});
89+
90+
results.push({
91+
name: 'Devices have correct structure',
92+
result: devices.every(device =>
93+
device.deviceId &&
94+
device.kind &&
95+
device.label &&
96+
device.groupId
97+
),
98+
expected: true
99+
});
100+
101+
results.push({
102+
name: 'Devices are MediaDeviceInfo instances',
103+
result: devices.every(device => device instanceof MediaDeviceInfo),
104+
expected: true
105+
});
106+
107+
results.push({
108+
name: 'Input devices are InputDeviceInfo instances',
109+
result: devices.filter(d => d.kind === 'audioinput' || d.kind === 'videoinput')
110+
.every(device => device instanceof InputDeviceInfo),
111+
expected: true
112+
});
113+
114+
results.push({
115+
name: 'toJSON method works without errors',
116+
result: (() => {
117+
try {
118+
devices.forEach(device => device.toJSON());
119+
return true;
120+
} catch (e) {
121+
return false;
122+
}
123+
})(),
124+
expected: true
125+
});
126+
127+
results.push({
128+
name: 'Device properties are read-only',
129+
result: (() => {
130+
try {
131+
devices[0].deviceId = 'modified';
132+
return false;
133+
} catch (e) {
134+
return true;
135+
}
136+
})(),
137+
expected: true
138+
});
139+
140+
results.push({
141+
name: 'Device IDs are unique',
142+
result: new Set(devices.map(d => d.deviceId)).size === devices.length,
143+
expected: true
144+
});
145+
146+
results.push({
147+
name: 'Group IDs are valid',
148+
result: devices.every(device => typeof device.groupId === 'string' && device.groupId.length > 0),
149+
expected: true
150+
});
151+
152+
} catch (error) {
153+
results.push({
154+
name: 'No errors thrown',
155+
result: false,
156+
expected: true
157+
});
158+
}
159+
160+
return results;
161+
});
162+
163+
test('Feature-enabled enumerateDevices behavior', async () => {
164+
const results = [];
165+
166+
// This test would run when the feature is enabled
167+
// The actual behavior should be the same as native since we're just proxying
168+
try {
169+
const devices = await navigator.mediaDevices.enumerateDevices();
170+
171+
results.push({
172+
name: 'Feature-enabled returns same number of devices',
173+
result: devices.length,
174+
expected: 3
175+
});
176+
177+
results.push({
178+
name: 'Feature-enabled devices maintain structure',
179+
result: devices.every(device =>
180+
device.deviceId &&
181+
device.kind &&
182+
device.label &&
183+
device.groupId
184+
),
185+
expected: true
186+
});
187+
188+
results.push({
189+
name: 'Feature-enabled devices are proper instances',
190+
result: devices.every(device => device instanceof MediaDeviceInfo),
191+
expected: true
192+
});
193+
194+
} catch (error) {
195+
results.push({
196+
name: 'Feature-enabled no errors thrown',
197+
result: false,
198+
expected: true
199+
});
200+
}
201+
202+
return results;
203+
});
204+
205+
test('Error handling', async () => {
206+
const results = [];
207+
208+
// Test with a broken enumerateDevices
209+
const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;
210+
navigator.mediaDevices.enumerateDevices = async function() {
211+
throw new Error('Permission denied');
212+
};
213+
214+
try {
215+
await navigator.mediaDevices.enumerateDevices();
216+
results.push({
217+
name: 'Error should be thrown',
218+
result: false,
219+
expected: true
220+
});
221+
} catch (error) {
222+
results.push({
223+
name: 'Error is properly thrown',
224+
result: error.message === 'Permission denied',
225+
expected: true
226+
});
227+
}
228+
229+
// Restore original
230+
navigator.mediaDevices.enumerateDevices = originalEnumerateDevices;
231+
232+
return results;
233+
});
234+
235+
// eslint-disable-next-line no-undef
236+
renderResults();
237+
</script>
238+
</body>
239+
</html>

injected/src/features/web-compat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class WebCompat extends ContentFeature {
130130
if (this.getFeatureSettingEnabled('disableDeviceEnumeration') || this.getFeatureSettingEnabled('disableDeviceEnumerationFrames')) {
131131
this.preventDeviceEnumeration();
132132
}
133-
if (this.getFeatureSettingEnabled('deviceEnumeration')) {
133+
if (this.getFeatureSettingEnabled('enumerateDevices')) {
134134
this.deviceEnumerationFix();
135135
}
136136
}

0 commit comments

Comments
 (0)