Skip to content

Commit 7a68420

Browse files
authored
add Chrome/Firefox interop tests (#1576)
* add interop tests adds interoperability tests between Chrome unstable and Firefox Nightly. These tests are designed to run nightly as github actions. They do not rely on a signaling server, instead use the mocha-based test that is controlling the individual selenium webdriver instances to act as a signaling channel which exchanges offers and answers with candidates. This pattern is described in the testbed repository https://github.com/fippo/testbed from 2016, this is a "more modern" take on the same subject. To run the tests locally, * npm install --no-save chromedriver geckodriver * npm run mocha test/interop/connection
1 parent 5990ba4 commit 7a68420

File tree

6 files changed

+333
-39
lines changed

6 files changed

+333
-39
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
on:
2+
schedule:
3+
- cron: "30 5 * * *"
4+
push:
5+
jobs:
6+
interop:
7+
runs-on: ubuntu-latest
8+
timeout-minutes: 5
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
browserA: [chrome, firefox]
13+
browserB: [firefox, chrome]
14+
bver: ['unstable']
15+
steps:
16+
- uses: actions/checkout@v2
17+
- uses: actions/setup-node@v3
18+
- run: npm install
19+
- run: BROWSER=${{matrix.browserA}} BVER=${{matrix.bver}} ./node_modules/travis-multirunner/setup.sh
20+
- run: BROWSER=${{matrix.browserB}} BVER=${{matrix.bver}} ./node_modules/travis-multirunner/setup.sh
21+
- run: Xvfb :99 &
22+
- run: BROWSER_A=${{matrix.browserA}} BROWSER_B=${{matrix.browserB}} BVER=${{matrix.bver}} DISPLAY=:99.0 npm run mocha test/interop/connection.js

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"scripts": {
2222
"start": "http-server . -c-1",
2323
"test": "npm run eslint && npm run stylelint",
24-
"eslint": "eslint 'src/content/**/*.js'",
24+
"eslint": "eslint 'test/**.js' 'src/content/**/*.js'",
2525
"mocha": "mocha --timeout 5000 'src/content/**/test.js'",
2626
"stylelint": "stylelint 'src/**/*.css'"
2727
},

test/interop/connection.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
8+
const {buildDriver} = require('../webdriver');
9+
const {PeerConnection, MediaDevices} = require('../webrtcclient');
10+
const steps = require('../steps');
11+
12+
const browserA = process.env.BROWSER_A || 'chrome';
13+
const browserB = process.env.BROWSER_B || 'chrome';
14+
15+
describe(`basic interop test ${browserA} => ${browserB}`, function() {
16+
this.retries(3); // retry up to three times.
17+
let drivers;
18+
let clients;
19+
before(async () => {
20+
const options = {
21+
version: process.env.BVER || 'stable',
22+
browserLogging: true,
23+
}
24+
drivers = [
25+
buildDriver(browserA, options),
26+
buildDriver(browserB, options),
27+
];
28+
clients = drivers.map(driver => {
29+
return {
30+
connection: new PeerConnection(driver),
31+
mediaDevices: new MediaDevices(driver),
32+
};
33+
});
34+
});
35+
after(async () => {
36+
await drivers.map(driver => driver.close());
37+
});
38+
39+
it('establishes a connection', async () => {
40+
await Promise.all(drivers); // timeouts in before(Each)?
41+
await steps.step(drivers, (d) => d.get('https://webrtc.github.io/samples/emptypage.html'), 'Empty page loaded');
42+
await steps.step(clients, (client) => client.connection.create(), 'Created RTCPeerConnection');
43+
await steps.step(clients, async (client) => {
44+
const stream = await client.mediaDevices.getUserMedia({audio: true, video: true});
45+
return Promise.all(stream.getTracks().map(async track => {
46+
return client.connection.addTrack(track, stream);
47+
}));
48+
}, 'Acquired and added audio/video stream');
49+
const offerWithCandidates = await clients[0].connection.setLocalDescription();
50+
await clients[1].connection.setRemoteDescription(offerWithCandidates);
51+
const answerWithCandidates = await clients[1].connection.setLocalDescription();
52+
await clients[0].connection.setRemoteDescription(answerWithCandidates);
53+
54+
await steps.step(drivers, (d) => steps.waitNVideosExist(d, 1), 'Video elements exist');
55+
await steps.step(drivers, steps.waitAllVideosHaveEnoughData, 'Video elements have enough data');
56+
}).timeout(30000);
57+
}).timeout(90000);;

test/steps.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
8+
const TIMEOUT = 10000;
9+
10+
function step(drivers, cb, logMessage) {
11+
return Promise.all(drivers.map(driver => {
12+
return cb(driver);
13+
})).then(() => {
14+
if (logMessage) {
15+
console.log(logMessage);
16+
}
17+
});
18+
}
19+
function waitNVideosExist(driver, n) {
20+
return driver.wait(() => {
21+
return driver.executeScript(n => document.querySelectorAll('video').length === n, n);
22+
}, TIMEOUT);
23+
}
24+
25+
function waitAllVideosHaveEnoughData(driver) {
26+
return driver.wait(() => {
27+
return driver.executeScript(() => {
28+
const videos = document.querySelectorAll('video');
29+
let ready = 0;
30+
for (let i = 0; i < videos.length; i++) {
31+
if (videos[i].readyState >= videos[i].HAVE_ENOUGH_DATA) {
32+
ready++;
33+
}
34+
}
35+
return ready === videos.length;
36+
});
37+
}, TIMEOUT);
38+
}
39+
40+
module.exports = {
41+
step,
42+
waitNVideosExist,
43+
waitAllVideosHaveEnoughData,
44+
};

test/webdriver.js

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,101 @@
1+
/*
2+
* Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
18
const os = require('os');
2-
const fs = require('fs');
39

410
const webdriver = require('selenium-webdriver');
511
const chrome = require('selenium-webdriver/chrome');
612
const firefox = require('selenium-webdriver/firefox');
713
const safari = require('selenium-webdriver/safari');
814

9-
// setup path for webdriver binaries
1015
if (os.platform() === 'win32') {
11-
process.env.PATH += ';C:\\Program Files (x86)\\Microsoft Web Driver\\';
12-
// FIXME: not sure why node_modules\.bin\ is not enough
13-
process.env.PATH += ';' + process.cwd() +
14-
'\\node_modules\\chromedriver\\lib\\chromedriver\\';
15-
process.env.PATH += ';' + process.cwd() +
16-
'\\node_modules\\geckodriver';
16+
process.env.PATH += ';' + process.cwd() + '\\node_modules\\chromedriver\\lib\\chromedriver\\';
17+
process.env.PATH += ';' + process.cwd() + '\\node_modules\\geckodriver';
1718
} else {
1819
process.env.PATH += ':node_modules/.bin';
1920
}
2021

2122
function buildDriver(browser = process.env.BROWSER || 'chrome', options = {bver: process.env.BVER}) {
22-
// Firefox options.
23-
let firefoxPath;
24-
if (options.firefoxpath) {
25-
firefoxPath = options.firefoxpath;
26-
} else if (os.platform() == 'linux' && options.bver) {
27-
firefoxPath = 'browsers/bin/firefox-' + options.bver;
28-
} else {
29-
firefoxPath = firefox.Channel.RELEASE;
30-
}
31-
32-
const firefoxOptions = new firefox.Options()
33-
.setPreference('media.navigator.streams.fake', true)
34-
.setPreference('media.navigator.permission.disabled', true)
35-
.setPreference('xpinstall.signatures.required', false)
36-
.setPreference('media.peerconnection.dtls.version.min', 771)
37-
.setBinary(firefoxPath);
38-
3923
// Chrome options.
40-
let chromeOptions = new chrome.Options()
41-
.addArguments('allow-file-access-from-files')
24+
const chromeOptions = new chrome.Options()
25+
.addArguments('allow-insecure-localhost')
4226
.addArguments('use-fake-device-for-media-stream')
43-
.addArguments('use-fake-ui-for-media-stream')
44-
.addArguments('disable-translate')
45-
.addArguments('no-process-singleton-dialog')
46-
.addArguments('mute-audio');
47-
// ensure chrome.runtime is visible.
48-
chromeOptions.excludeSwitches('test-type');
27+
.addArguments('allow-file-access-from-files');
28+
if (options.chromeFlags) {
29+
options.chromeFlags.forEach((flag) => chromeOptions.addArguments(flag));
30+
}
4931

5032
if (options.chromepath) {
5133
chromeOptions.setChromeBinaryPath(options.chromepath);
52-
} else if (os.platform() === 'linux' && options.bver) {
53-
chromeOptions.setChromeBinaryPath('browsers/bin/chrome-' + options.bver);
34+
} else if (os.platform() === 'linux' && options.version) {
35+
chromeOptions.setChromeBinaryPath('browsers/bin/chrome-' + options.version);
36+
}
37+
38+
if (!options.devices || options.headless) {
39+
// GUM doesn't work in headless mode so we need this. See
40+
// https://bugs.chromium.org/p/chromium/issues/detail?id=776649
41+
chromeOptions.addArguments('use-fake-ui-for-media-stream');
42+
} else {
43+
// see https://bugs.chromium.org/p/chromium/issues/detail?id=459532#c22
44+
const domain = 'https://' + (options.devices.domain || 'localhost') + ':' + (options.devices.port || 443) + ',*';
45+
const exceptions = {
46+
media_stream_mic: {},
47+
media_stream_camera: {},
48+
};
49+
50+
exceptions.media_stream_mic[domain] = {
51+
last_used: Date.now(),
52+
setting: options.devices.audio ? 1 : 2 // 0: ask, 1: allow, 2: denied
53+
};
54+
exceptions.media_stream_camera[domain] = {
55+
last_used: Date.now(),
56+
setting: options.devices.video ? 1 : 2
57+
};
58+
59+
chromeOptions.setUserPreferences({
60+
profile: {
61+
content_settings: {
62+
exceptions: exceptions
63+
}
64+
}
65+
});
5466
}
5567

5668
const safariOptions = new safari.Options();
5769
safariOptions.setTechnologyPreview(options.bver === 'unstable');
5870

71+
// Firefox options.
72+
const firefoxOptions = new firefox.Options();
73+
let firefoxPath = firefox.Channel.RELEASE;
74+
if (options.firefoxpath) {
75+
firefoxPath = options.firefoxpath;
76+
} else if (os.platform() == 'linux' && options.version) {
77+
firefoxPath = 'browsers/bin/firefox-' + options.version;
78+
}
79+
if (options.headless) {
80+
firefoxOptions.addArguments('-headless');
81+
}
82+
firefoxOptions.setBinary(firefoxPath);
83+
firefoxOptions.setPreference('media.navigator.streams.fake', true);
84+
firefoxOptions.setPreference('media.navigator.permission.disabled', true);
85+
5986
const driver = new webdriver.Builder()
60-
.setFirefoxOptions(firefoxOptions)
6187
.setChromeOptions(chromeOptions)
6288
.setSafariOptions(safariOptions)
63-
.forBrowser(browser);
64-
driver.getCapabilities().set('acceptInsecureCerts', true);
89+
.setFirefoxOptions(firefoxOptions)
90+
.forBrowser(browser)
91+
.setChromeService(
92+
new chrome.ServiceBuilder().addArguments('--disable-build-check')
93+
);
6594

95+
if (browser === 'firefox') {
96+
driver.getCapabilities().set('marionette', true);
97+
driver.getCapabilities().set('acceptInsecureCerts', true);
98+
}
6699
return driver.build();
67100
}
68101

0 commit comments

Comments
 (0)