webrtcchromiumfunctional-testingtestcafemediadevices

How to use navigator media devices in Chromium based functional tests? (TestCafe)


TL;DR How can I run functional tests that use navigator.mediaDevices and its methods with TestCafe and Chromium?

I'm trying to write functional tests with TestCafe to test some WebRTC code. I'm using React. I have a custom hook that enumerates the devices:

function useUpdateDeviceList() {
  const dispatch = useDispatch();

  useEffect(() => {
    async function updateDeviceList() {
      const { audioInputs, videoInputs } = await getInputDeviceInfos();

      dispatch(setAudioInputs(audioInputs));
      dispatch(setVideoInputs(videoInputs));
    }

    updateDeviceList();

    console.log('navigator', navigator);

    navigator.mediaDevices.ondevicechange = updateDeviceList;

    return () => {
      navigator.mediaDevices.ondevicechange = null;
    };
  }, [dispatch]);
}

where getInputDeviceInfos calls navigator.mediaDevices.enumerateDevices.

When I run this code in the browser, it works flawlessly. When it gets run in TestCafe's functional tests, it throws.

TypeError: Cannot set property 'ondevicechange' of undefined

A thorough Google search only gave the answer to add the flags --use-fake-ui-for-media-stream and --use-fake-device-for-media-stream to the start command (where the former avoids the need to grant camera/microphone permissions and the ladder feeds a test pattern to getUserMedia() instead of live camera input according to the docs), which I did:

"functional-tests:chrome-desktop":"testcafe 'chrome --use-fake-ui-for-media-stream --use-fake-device-for-media-stream' src/**/*.functional-test.js --app 'yarn dev' --app-init-delay 2000",

Still the same error occurs. The console.log in the hook above shows a navigator object without mediaDevices.

Navigator {vendorSub: "", productSub: "20030107", vendor: "Google Inc.", maxTouchPoints: 0, sendBeacon: ƒ, …}
appCodeName: "Mozilla"
appName: "Netscape"
appVersion: "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
connection: NetworkInformation {onchange: null, effectiveType: "4g", rtt: 100, downlink: 1.55, saveData: false}
cookieEnabled: true
doNotTrack: null
geolocation: Geolocation {}
hardwareConcurrency: 16
language: "en-GB"
languages: (3) ["en-GB", "en-US", "en"]
maxTouchPoints: 0
mediaCapabilities: MediaCapabilities {}
mediaSession: MediaSession {metadata: null, playbackState: "none"}
mimeTypes: MimeTypeArray {0: MimeType, 1: MimeType, application/x-nacl: MimeType, application/x-pnacl: MimeType, length: 2}
onLine: true
permissions: Permissions {}
platform: "MacIntel"
plugins: PluginArray {0: Plugin, Native Client: Plugin, length: 1}
product: "Gecko"
productSub: "20030107"
sendBeacon: ƒ ()
userActivation: UserActivation {hasBeenActive: false, isActive: false}
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"
vendor: "Google Inc."
vendorSub: ""
webkitPersistentStorage: DeprecatedStorageQuota {}
webkitTemporaryStorage: DeprecatedStorageQuota {}
__proto__: Navigator

What do I have to do to get these tests to pass?

PS: In case it matters, here is the simple test that should pass:

import { Selector } from 'testcafe';

const {
  FUNCTIONAL_TESTS_MEETINGS_APP_PROTOCOL: protocol = 'http',
  FUNCTIONAL_TESTS_MEETINGS_APP_HOST: host = 'localhost',
  FUNCTIONAL_TESTS_MEETINGS_APP_PORT: port = 3000,
  FUNCTIONAL_TESTS_MEETINGS_APP_MEETING_ID: meetingId = '327fa7b0-0605-4595-b066-819f201ce593',
} = process.env;

fixture`Meetings page`.page(`${protocol}://${host}:${port}/${meetingId}`);

test.only('page should load and display the correct title', async t => {
  await t.debug();
  const actual = Selector('title').innerText;
  const expected = `Meetings - ${meetingId}`;

  await t.expect(actual).eql(expected);
});

Solution

  • Chrome doesn't allow calling the getUserMedia API from insecure origins. So, you can use either of the following ways:

    1. Specify 'localhost' as the host name by adding this option: --hostname
    testcafe --hostname localhost ...
    
    1. Use a SSL certificate by adding this option: --ssl
    testcafe --ssl pfx=/path/to/cert.pfx ...
    

    For more information, please refer to the following example: Mock Camera/Microphone Access.