Skip to main content

Introduction

For a product, integration tests are one of the crucial part that improves quality & stability. For a WebRTC based solution like Dyte, having integration tests that can test multi-user call with Audio/Video on is necessary.

For an end user, sharing a camera & mic is easy. For this, browsers expose APIs such as enumerateDevices & getUserMedia on MediaDevices interface, on which user interfaces can be built easily.

Access to camera & mic prompts the users to allow permissions to do so. This works great as long as an end-user is using the product and actively allowing permissions and selecting devices, However this makes it impossible to write integration tests because for integration tests there is no active user and you need to somehow allow permissions programmatically which at the moment of writing this README is not reliably supported in modern tools like Playwright.

Even if we can somehow allow permissions, The next set of questions would be: What would the video & audio feed look like? Can we customize the feed? Can we use the feed to detect delays between a video feed producer and consumer? How do we test multiple devices? How do we test media ejection on the fly? How do we test addition of a new device?

Dyte's Device Emulator is a solution that answers all these questions and provides a easier way to mimic, add, remove devices & their feed.

Installation

To set up device-emulator add the following script tags inside the <head /> tag.

<script>
window.addEventListener('dyte.deviceEmulatorLoaded', () => {
// use device emulator methods here...
});
</script>
<script src="https://cdn.jsdelivr.net/npm/@dytesdk/device-emulator/dist/index.iife.js"></script>

API reference

Add a virtual device

const virtualDeviceID = navigator.mediaDevices.addEmulatedDevice('videoinput');

// get a media track from the virtual device
navigator.mediaDevices
.getUserMedia({
video: {
deviceId: {
exact: virtualDeviceID,
},
},
})
.then((mediaStream) => {
const video = document.querySelector('video');
video.srcObject = mediaStream;
video.onloadedmetadata = () => {
video.play();
};
})
.catch((err) => {
// always check for errors at the end.
console.error(`${err.name}: ${err.message}`);
});

Remove virtual device

navigator.mediaDevices.removeEmulatedDevice(deviceId);

Fail the device

You can use failDevice method to test scenarios where the devices stops working abruptly.

const virtualDeviceID = navigator.mediaDevices.addEmulatedDevice('videoinput');

// Stop the device from working
navigator.mediaDevices.failDevice(virtualDeviceID, true);

navigator.mediaDevices
.getUserMedia({
video: {
deviceId: {
exact: virtualDeviceID,
},
},
})
.then((mediaStream) => {
// This will not work
const video = document.querySelector('video');
video.srcObject = mediaStream;
video.onloadedmetadata = () => {
video.play();
};
})
.catch((err) => {
// catch `NotReadableError` thrown from the device
console.error(`${err.name}: ${err.message}`);
});

Executing the failDevice after getting the tracks will stop the active tracks.

Resume the device

Use failDevice method to make the device work normally

navigator.mediaDevices.failDevice(deviceId, false);

Silence the tracks

Generate tracks that are silent

navigator.mediaDevices.silenceDevice(deviceId, true);

Unmute the tracks from the device

Remove the silence check on the device

navigator.mediaDevices.silenceDevice(deviceId, false);