summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html')
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html838
1 files changed, 838 insertions, 0 deletions
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
new file mode 100644
index 0000000000..739675eab3
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
@@ -0,0 +1,838 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1337525",
+ title: "webRtc Stats composition and sanity"
+ });
+const statsExpectedByType = {
+ "inbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "packetsReceived",
+ "packetsLost",
+ "bytesReceived",
+ "jitter",
+ ],
+ optional: [
+ "remoteId",
+ "nackCount",
+ ],
+ localVideoOnly: [
+ "discardedPackets",
+ "framerateStdDev",
+ "framerateMean",
+ "bitrateMean",
+ "bitrateStdDev",
+ "firCount",
+ "pliCount",
+ "framesDecoded",
+ ],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "codecId",
+ "packetsDiscarded",
+ "associateStatsId",
+ "sliCount",
+ "packetsRepaired",
+ "fractionLost",
+ "burstPacketsLost",
+ "burstLossCount",
+ "burstDiscardCount",
+ "gapDiscardRate",
+ "gapLossRate",
+ "qpSum", // Not yet implemented for inbound media, see bug 1519590
+ ],
+ deprecated: [
+ "mozRtt",
+ "isRemote",
+ ],
+ },
+ "outbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "packetsSent",
+ "bytesSent",
+ "remoteId",
+ ],
+ optional: [
+ "nackCount",
+ ],
+ localVideoOnly: [
+ "droppedFrames",
+ "bitrateMean",
+ "bitrateStdDev",
+ "framerateMean",
+ "framerateStdDev",
+ "framesEncoded",
+ "firCount",
+ "pliCount",
+ "qpSum",
+ ],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "codecId",
+ "sliCount",
+ "targetBitrate",
+ ],
+ deprecated: [
+ "isRemote",
+ ],
+ },
+ "remote-inbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "packetsLost",
+ "jitter",
+ "localId",
+ ],
+ optional: [
+ "roundTripTime",
+ "nackCount",
+ "packetsReceived",
+ "bytesReceived",
+ ],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "codecId",
+ "packetsDiscarded",
+ "associateStatsId",
+ "sliCount",
+ "packetsRepaired",
+ "fractionLost",
+ "burstPacketsLost",
+ "burstLossCount",
+ "burstDiscardCount",
+ "gapDiscardRate",
+ "gapLossRate",
+ ],
+ deprecated: [
+ "mozRtt",
+ "isRemote",
+ ],
+ },
+ "remote-outbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "packetsSent",
+ "bytesSent",
+ "localId",
+ "remoteTimestamp",
+ ],
+ optional: [
+ "nackCount",
+ ],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "codecId",
+ "sliCount",
+ "targetBitrate",
+ ],
+ deprecated: [
+ "isRemote",
+ ],
+ },
+ "csrc": { skip: true },
+ "codec": { skip: true },
+ "peer-connection": { skip: true },
+ "data-channel": { skip: true },
+ "track": { skip: true },
+ "transport": { skip: true },
+ "candidate-pair": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "transportId",
+ "localCandidateId",
+ "remoteCandidateId",
+ "state",
+ "priority",
+ "nominated",
+ "writable",
+ "readable",
+ "bytesSent",
+ "bytesReceived",
+ "lastPacketSentTimestamp",
+ "lastPacketReceivedTimestamp",
+ ],
+ optional: [
+ "selected",
+ ],
+ unimplemented: [
+ "totalRoundTripTime",
+ "currentRoundTripTime",
+ "availableOutgoingBitrate",
+ "availableIncomingBitrate",
+ "requestsReceived",
+ "requestsSent",
+ "responsesReceived",
+ "responsesSent",
+ "retransmissionsReceived",
+ "retransmissionsSent",
+ "consentRequestsSent",
+ ],
+ deprecated: [],
+ },
+ "local-candidate": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "address",
+ "protocol",
+ "port",
+ "candidateType",
+ "priority",
+ ],
+ optional: [
+ "relayProtocol",
+ "proxied",
+ ],
+ unimplemented: [
+ "networkType",
+ "url",
+ "transportId"
+ ],
+ deprecated: [
+ "candidateId",
+ "portNumber",
+ "ipAddress",
+ "componentId",
+ "mozLocalTransport",
+ "transport",
+ ],
+ },
+ "remote-candidate": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "address",
+ "protocol",
+ "port",
+ "candidateType",
+ "priority",
+ ],
+ optional: [
+ "relayProtocol",
+ "proxied",
+ ],
+ unimplemented: [
+ "networkType",
+ "url",
+ "transportId",
+ ],
+ deprecated: [
+ "candidateId",
+ "portNumber",
+ "ipAddress",
+ "componentId",
+ "mozLocalTransport",
+ "transport",
+ ],
+ },
+ "certificate": { skip: true },
+};
+
+["in", "out"].forEach(pre => {
+ let s = statsExpectedByType[pre + "bound-rtp"];
+ s.optional = [...s.optional, ...s.localVideoOnly];
+});
+
+//
+// Checks that the fields in a report conform to the expectations in
+// statExpectedByType
+//
+var checkExpectedFields = report => report.forEach(stat => {
+ let expectations = statsExpectedByType[stat.type];
+ ok(expectations, "Stats type " + stat.type + " was expected");
+ // If the type is not expected or if it is flagged for skipping continue to
+ // the next
+ if (!expectations || expectations.skip) {
+ return;
+ }
+ // Check that all required fields exist
+ expectations.expected.forEach(field => {
+ ok(field in stat, "Expected stat field " + stat.type + "." + field
+ + " exists");
+ });
+ // Check that each field is either expected or optional
+ let allowed = [...expectations.expected, ...expectations.optional];
+ Object.keys(stat).forEach(field => {
+ ok(allowed.includes(field), "Stat field " + stat.type + "." + field
+ + ` is allowed. ${JSON.stringify(stat)}`);
+ });
+
+ //
+ // Ensure that unimplemented fields are not implemented
+ // note: if a field is implemented it should be moved to expected or
+ // optional.
+ //
+ expectations.unimplemented.forEach(field => {
+ ok(!Object.keys(stat).includes(field), "Unimplemented field " + stat.type
+ + "." + field + " does not exist.");
+ });
+
+ //
+ // Ensure that all deprecated fields are not present
+ //
+ expectations.deprecated.forEach(field => {
+ ok(!Object.keys(stat).includes(field), "Deprecated field " + stat.type
+ + "." + field + " does not exist.");
+ });
+});
+
+var pedanticChecks = report => {
+ // Check that report is only-maplike
+ [...report.keys()].forEach(key => is(report[key], undefined,
+ `Report is not dictionary like, it lacks a property for key ${key}`));
+ report.forEach((statObj, mapKey) => {
+ info(`"${mapKey} = ${JSON.stringify(statObj, null, 2)}`);
+ });
+ // eslint-disable-next-line complexity
+ report.forEach((statObj, mapKey) => {
+ let tested = {};
+ // Record what fields get tested.
+ // To access a field foo without marking it as tested use stat.inner.foo
+ let stat = new Proxy(statObj, {
+ get(stat, key) {
+ if (key == "inner") return stat;
+ tested[key] = true;
+ return stat[key];
+ }
+ });
+
+ let expectations = statsExpectedByType[stat.type];
+
+ if (expectations.skip) {
+ return;
+ }
+
+ // All stats share the following attributes inherited from RTCStats
+ is(stat.id, mapKey, stat.type + ".id is the same as the report key.");
+
+ // timestamp
+ ok(stat.timestamp >= 0, stat.type + ".timestamp is not less than 0");
+ // If the timebase for the timestamp is not properly set the timestamp will
+ // appear relative to the year 1970; Bug 1495446
+ const date = new Date(stat.timestamp);
+ ok(date.getFullYear() > 1970,
+ `${stat.type}.timestamp is relative to current time, date=${date}`);
+ //
+ // RTCStreamStats attributes with common behavior
+ //
+ // inbound-rtp, outbound-rtp, remote-inbound-rtp, remote-outbound-rtp
+ // inherit from RTCStreamStats
+ if (["inbound-rtp", "outbound-rtp",
+ "remote-inbound-rtp", "remote-outbound-rtp"].includes(stat.type)) {
+ const isRemote = stat.type.startsWith("remote-");
+ //
+ // Common RTCStreamStats fields
+ //
+
+ // SSRC
+ ok(stat.ssrc, stat.type + ".ssrc has a value");
+
+ // kind
+ ok(["audio", "video"].includes(stat.kind),
+ stat.type + ".kind is 'audio' or 'video'");
+
+ // mediaType, renamed to kind but remains for backward compability.
+ ok(["audio", "video"].includes(stat.mediaType),
+ stat.type + ".mediaType is 'audio' or 'video'");
+
+ ok(stat.kind == stat.mediaType, "kind equals legacy mediaType");
+
+ if (isRemote) {
+ // local id
+ if (stat.localId) {
+ ok(report.has(stat.localId),
+ `localId ${stat.localId} exists in report.`);
+ is(report.get(stat.localId).ssrc, stat.ssrc,
+ "remote ssrc and local ssrc match.");
+ is(report.get(stat.localId).remoteId, stat.id,
+ "local object has remote object as it's own remote object.");
+ }
+ } else {
+ // remote id
+ if (stat.remoteId) {
+ ok(report.has(stat.remoteId),
+ `remoteId ${stat.remoteId} exists in report.`);
+ is(report.get(stat.remoteId).ssrc, stat.ssrc,
+ "remote ssrc and local ssrc match.");
+ is(report.get(stat.remoteId).localId, stat.id,
+ "remote object has local object as it's own local object.");
+ }
+ }
+
+ // nackCount
+ if (stat.nackCount) {
+ ok(stat.nackCount >= 0, stat.type + ".nackCount is sane.");
+ }
+
+ if (!isRemote && stat.inner.kind == "video") {
+ // firCount
+ ok(stat.firCount >= 0 && stat.firCount < 100,
+ stat.type + ".firCount is a sane number for a short test. value="
+ + stat.firCount);
+
+ // pliCount
+ ok(stat.pliCount >= 0 && stat.pliCount < 200,
+ stat.type + ".pliCount is a sane number for a short test. value="
+ + stat.pliCount);
+ }
+ }
+
+ if (stat.type == "inbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // packetsReceived
+ ok(stat.packetsReceived >= 0
+ && stat.packetsReceived < 10 ** 5,
+ stat.type + ".packetsReceived is a sane number for a short test. value="
+ + stat.packetsReceived);
+
+ // bytesReceived
+ ok(stat.bytesReceived >= 0
+ && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess
+ stat.type + ".bytesReceived is a sane number for a short test. value="
+ + stat.bytesReceived);
+
+ // packetsLost
+ ok(stat.packetsLost < 100,
+ stat.type + ".packetsLost is a sane number for a short test. value="
+ + stat.packetsLost);
+
+ // This should be much lower for audio, TODO: Bug 1330575
+ let expectedJitter = stat.kind == "video" ? 0.5 : 1;
+ // jitter
+ ok(stat.jitter < expectedJitter,
+ stat.type + ".jitter is sane number for a local only test. value="
+ + stat.jitter);
+
+ // packetsDiscarded
+ // special exception for, TODO: Bug 1335967
+ // if (!stat.inner.isRemote && stat.discardedPackets !== undefined) {
+ // ok(stat.packetsDiscarded < 100, stat.type
+ // + ".packetsDiscarded is a sane number for a short test. value="
+ // + stat.packetsDiscarded);
+ // }
+ // if (stat.packetsDiscarded !== undefined) {
+ // ok(!stat.inner.isRemote,
+ // stat.type + ".packetsDiscarded is only set when isRemote is "
+ // + "false");
+ // }
+
+ //
+ // Optional fields
+ //
+
+ //
+ // Local video only stats
+ //
+ if (stat.inner.kind != "video") {
+ expectations.localVideoOnly.forEach(field => {
+ ok(stat[field] === undefined, `${stat.type} does not have field ${field}`
+ + ` when kind is not 'video'`);
+ });
+ } else {
+ expectations.localVideoOnly.forEach(field => {
+ ok(stat.inner[field] !== undefined, stat.type + " has field " + field
+ + " when kind is video");
+ });
+ // discardedPackets
+ ok(stat.discardedPackets < 100, stat.type
+ + ".discardedPackets is a sane number for a short test. value="
+ + stat.discardedPackets);
+ // framesDecoded
+ ok(stat.framesDecoded > 0 && stat.framesDecoded < 1000000, stat.type
+ + ".framesDecoded is a sane number for a short test. value="
+ + stat.framesDecoded);
+ // bitrateMean
+ // special exception, TODO: Bug 1341533
+ if (stat.bitrateMean !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25,
+ // stat.type + ".bitrateMean is sane. value="
+ // + stat.bitrateMean);
+ }
+
+ // bitrateStdDev
+ // special exception, TODO Bug 1341533
+ if (stat.bitrateStdDev !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25,
+ // stat.type + ".bitrateStdDev is sane. value="
+ // + stat.bitrateStdDev);
+ }
+
+ // framerateMean
+ // special exception, TODO: Bug 1341533
+ if (stat.framerateMean !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.framerateMean >= 0 && stat.framerateMean < 120,
+ // stat.type + ".framerateMean is sane. value="
+ // + stat.framerateMean);
+ }
+
+ // framerateStdDev
+ // special exception, TODO: Bug 1341533
+ if (stat.framerateStdDev !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120,
+ // stat.type + ".framerateStdDev is sane. value="
+ // + stat.framerateStdDev);
+ }
+
+ }
+ } else if (stat.type == "remote-inbound-rtp") {
+ // roundTripTime
+ ok(stat.roundTripTime >= 0, stat.type + ".roundTripTime is sane with" +
+ "value of:" + stat.roundTripTime);
+ //
+ // Required fields
+ //
+
+ // packetsLost
+ ok(stat.packetsLost < 100,
+ stat.type + ".packetsLost is a sane number for a short test. value="
+ + stat.packetsLost);
+
+ // jitter
+ ok(stat.jitter >= 0,
+ stat.type + ".jitter is sane number. value="
+ + stat.jitter);
+
+ //
+ // Optional fields
+ //
+
+ // packetsReceived
+ if (stat.packetsReceived) {
+ ok(stat.packetsReceived >= 0
+ && stat.packetsReceived < 10 ** 5,
+ stat.type + ".packetsReceived is a sane number for a short test. value="
+ + stat.packetsReceived);
+ }
+
+ // bytesReceived
+ if (stat.bytesReceived) {
+ ok(stat.bytesReceived >= 0
+ && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess
+ stat.type + ".bytesReceived is a sane number for a short test. value="
+ + stat.bytesReceived);
+ }
+
+ } else if (stat.type == "outbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // packetsSent
+ ok(stat.packetsSent > 0 && stat.packetsSent < 10000,
+ stat.type + ".packetsSent is a sane number for a short test. value="
+ + stat.packetsSent);
+
+ // bytesSent
+ ok(stat.bytesSent, stat.type + ".bytesSent has a value."
+ + " Value not expected to be sane, bug 1339104. value="
+ + stat.bytesSent);
+
+ //
+ // Optional fields
+ //
+
+ //
+ // Local video only stats
+ //
+ if (stat.inner.kind != "video") {
+ expectations.localVideoOnly.forEach(field => {
+ ok(stat[field] === undefined, stat.type + " does not have field "
+ + field + " when kind is not 'video'");
+ });
+ } else {
+ expectations.localVideoOnly.forEach(field => {
+ ok(stat.inner[field] !== undefined, stat.type + " has field " + field
+ + " when kind is video and isRemote is false");
+ });
+
+ // bitrateMean
+ if (stat.bitrateMean !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.bitrateMean >= 0 && stat.bitrateMean < 2 ** 25,
+ // stat.type + ".bitrateMean is sane. value="
+ // + stat.bitrateMean);
+ }
+
+ // bitrateStdDev
+ if (stat.bitrateStdDev !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.bitrateStdDev >= 0 && stat.bitrateStdDev < 2 ** 25,
+ // stat.type + ".bitrateStdDev is sane. value="
+ // + stat.bitrateStdDev);
+ }
+
+ // framerateMean
+ if (stat.framerateMean !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.framerateMean >= 0 && stat.framerateMean < 120,
+ // stat.type + ".framerateMean is sane. value="
+ // + stat.framerateMean);
+ }
+
+ // framerateStdDev
+ if (stat.framerateStdDev !== undefined) {
+ // TODO: uncomment when Bug 1341533 lands
+ // ok(stat.framerateStdDev >= 0 && stat.framerateStdDev < 120,
+ // stat.type + ".framerateStdDev is sane. value="
+ // + stat.framerateStdDev);
+ }
+
+ // droppedFrames
+ ok(stat.droppedFrames >= 0 && stat.droppedFrames < 100000,
+ stat.type + ".droppedFrames is a sane number. value="
+ + stat.droppedFrames);
+
+ // framesEncoded
+ ok(stat.framesEncoded >= 0 && stat.framesEncoded < 100000, stat.type
+ + ".framesEncoded is a sane number for a short test. value="
+ + stat.framesEncoded);
+
+ // qpSum
+ // techinically optional but should be supported for all of our codecs (on the encode side, see bug 1519590)
+ ok(stat.qpSum >= 0,
+ `${stat.type} qpSum is a sane number. value=${stat.qpSum}`);
+ }
+ } else if (stat.type == "remote-outbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // packetsSent
+ ok(stat.packetsSent > 0 && stat.packetsSent < 10000,
+ stat.type + ".packetsSent is a sane number for a short test. value="
+ + stat.packetsSent);
+
+ // bytesSent
+ ok(stat.bytesSent, stat.type + ".bytesSent has a value."
+ + " Value not expected to be sane, bug 1339104. value="
+ + stat.bytesSent);
+
+ ok(stat.remoteTimestamp !== undefined, `${stat.type}.remoteTimestamp `
+ + ` is not undefined`);
+
+ } else if (stat.type == "candidate-pair") {
+ info("candidate-pair is: " + JSON.stringify(stat));
+ //
+ // Required fields
+ //
+
+ // transportId
+ ok(stat.transportId,
+ stat.type + ".transportId has a value. value="
+ + stat.transportId);
+
+ // localCandidateId
+ ok(stat.localCandidateId,
+ stat.type + ".localCandidateId has a value. value="
+ + stat.localCandidateId);
+
+ // remoteCandidateId
+ ok(stat.remoteCandidateId,
+ stat.type + ".remoteCandidateId has a value. value="
+ + stat.remoteCandidateId);
+
+ // priority
+ ok(stat.priority,
+ stat.type + ".priority has a value. value="
+ + stat.priority);
+
+ // readable
+ ok(stat.readable,
+ stat.type + ".readable is true. value="
+ + stat.readable);
+
+ // writable
+ ok(stat.writable,
+ stat.type + ".writable is true. value="
+ + stat.writable);
+
+ // state
+ if (stat.state == "succeeded" &&
+ stat.selected !== undefined &&
+ stat.selected) {
+ info("candidate-pair state is succeeded and selected is true");
+ // nominated
+ ok(stat.nominated,
+ stat.type + ".nominated is true. value="
+ + stat.nominated);
+
+ // bytesSent
+ ok(stat.bytesSent > 5000,
+ stat.type + ".bytesSent is a sane number (>5,000) for a short test. value="
+ + stat.bytesSent);
+
+ // bytesReceived
+ ok(stat.bytesReceived > 5000,
+ stat.type + ".bytesReceived is a sane number (>5,000) for a short test. value="
+ + stat.bytesReceived);
+
+ // lastPacketSentTimestamp
+ ok(stat.lastPacketSentTimestamp,
+ stat.type + ".lastPacketSentTimestamp has a value. value="
+ + stat.lastPacketSentTimestamp);
+
+ // lastPacketReceivedTimestamp
+ ok(stat.lastPacketReceivedTimestamp,
+ stat.type + ".lastPacketReceivedTimestamp has a value. value="
+ + stat.lastPacketReceivedTimestamp);
+
+ } else {
+ info("candidate-pair is _not_ both state == succeeded and selected");
+ // nominated
+ ok(stat.nominated !== undefined,
+ stat.type + ".nominated exists. value="
+ + stat.nominated);
+ ok(stat.bytesSent !== undefined,
+ stat.type + ".bytesSent exists. value="
+ + stat.bytesSent);
+ ok(stat.bytesReceived !== undefined,
+ stat.type + ".bytesReceived exists. value="
+ + stat.bytesReceived);
+ ok(stat.lastPacketSentTimestamp !== undefined,
+ stat.type + ".lastPacketSentTimestamp exists. value="
+ + stat.lastPacketSentTimestamp);
+ ok(stat.lastPacketReceivedTimestamp !== undefined,
+ stat.type + ".lastPacketReceivedTimestamp exists. value="
+ + stat.lastPacketReceivedTimestamp);
+ }
+
+
+ //
+ // Optional fields
+ //
+ // selected
+ ok(stat.selected === undefined ||
+ ((stat.state == "succeeded" && stat.selected) ||
+ !stat.selected),
+ stat.type + ".selected is undefined, true when state is succeeded, "
+ + "or false. value="
+ + stat.selected);
+
+ } else if (stat.type == "local-candidate" || stat.type == "remote-candidate") {
+ info(`candidate is ${JSON.stringify(stat)}`);
+
+ // address
+ ok(stat.address, `${stat.type} has address. value=${stat.address}`);
+
+ // protocol
+ ok(stat.protocol, `${stat.type} has protocol. value=${stat.protocol}`);
+
+ // port
+ ok(stat.port >= 0, `${stat.type} has port >= 0. value=${stat.port}`);
+ ok(stat.port <= 65535, `${stat.type} has port <= 65535. value=${stat.port}`);
+
+ // candidateType
+ ok(stat.candidateType, `${stat.type} has candidateType. value=${stat.candidateType}`);
+
+ // priority
+ ok(stat.priority > 0 && stat.priority < (2**32 - 1),
+ `${stat.type} has priority between 1 and 2^32 - 1 inc. ` +
+ `value=${stat.priority}`);
+
+ // relayProtocol
+ if (stat.type == "local-candidate" && stat.candidateType == "relay") {
+ ok(stat.relayProtocol,
+ `relay ${stat.type} has relayProtocol. value=${stat.relayProtocol}`);
+ } else {
+ is(stat.relayProtocol, undefined,
+ `relayProtocol is undefined for candidates that are not relay and ` +
+ `local. value=${stat.relayProtocol}`);
+ }
+
+ // proxied
+ if (stat.proxied) {
+ ok(stat.proxied == "proxied" || stat.proxied == "non-proxied",
+ `${stat.type} has proxied. value=${stat.proxied}`);
+ }
+ }
+
+ //
+ // Ensure everything was tested
+ //
+ [...expectations.expected, ...expectations.optional].forEach(field => {
+ ok(Object.keys(tested).includes(field), stat.type + "." + field
+ + " was tested.");
+ });
+ });
+}
+
+var PC_LOCAL_TEST_LOCAL_STATS = test => {
+ return test.pcLocal.waitForSyncedRtcp().then(stats => {
+ checkExpectedFields(stats);
+ pedanticChecks(stats);
+ });
+}
+
+var PC_REMOTE_TEST_REMOTE_STATS = test => {
+ return test.pcRemote.waitForSyncedRtcp().then(stats => {
+ checkExpectedFields(stats);
+ pedanticChecks(stats);
+ });
+}
+
+runNetworkTest(async function (options) {
+ // We don't know how to get QP value when using Android system codecs.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_STATS]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_STATS]);
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ test.run();
+});
+</script>
+</pre>
+</body>
+</html>