# Voice JavaScript SDK: Twilio.PreflightTest

A `Twilio.PreflightTest` object represents a test call to Twilio which provides information to help troubleshoot call-related issues when using the [Voice JavaScript SDK](/docs/voice/sdks/javascript).

You will never instantiate a `PreflightTest` instance directly, but it's returned when you call [Device.runPreflight(token, options)](/docs/voice/sdks/javascript/twiliodevice#devicerunpreflighttoken-options).

**Example:**

```javascript
import { Device } from '@twilio/voice-sdk';

const preflightTest = Device.runPreflight(token, options);

preflightTest.on('completed', (report) => {
  console.log(report);
});

preflightTest.on('failed', (error) => {
  console.log(error);
});
```

## Running PreflightTest

You will need an Access Token to initiate the test call. The [Access Token](/docs/iam/access-tokens) will be associated with a TwiML application.

You likely already have a TwiML app with a Voice Request URL configured to send an `HTTP POST` request to an endpoint on your project's backend.

For the best test results, the endpoint with which you've configured your TwiML App should be able to record audio from a microphone and play it back to the browser.

Your backend application can be modified to handle this behavior, but you can also set up a new TwiML Application configured with TwiML bins specifically for `PreflightTest`s. See the [TwiML Apps for PreflightTest section](/docs/voice/sdks/javascript/twiliopreflighttest#twiml-apps-for-preflighttest) below for more information.

## Method Reference

### preflightTest.stop()

Calling this method from the `PreflightTest` object will stop the existing test and will raise a `failed` event with an error code `31008` indicating that the call has been cancelled.

## Event Reference

### completedEvent

Raised when `PreflightTest.status` has transitioned to `PreflightTest.Status.Completed`. During this time, the `report` is available and ready to be inspected. This will not trigger if a fatal error is encountered during the test.

Passes the `report` object to the `completed` event handler.

```javascript
preflightTest.on('completed', handler(report));
```

See the Report section below to view an example of this report.

### connectedEvent

Raised when `PreflightTest.status` has transitioned to `PreflightTest.Status.Connected`. This indicates that the connection to Twilio has been established.

### failedEvent

Raised when `PreflightTest.status` has transitioned to `PreflightTest.Status.Failed`. This happens when establishing a connection to Twilio has failed or when a test call has encountered a fatal error. This is also raised if `PreflightTest.stop` is called while the test is in progress. The error emitted from this event is coming from [Device.on('error')](/docs/voice/sdks/javascript/twiliodevice#error-event) and uses the same error format.

Passes a `TwilioError` object or a `DOMError` object to the `failed` event handler.

```javascript
preflightTest.on('failed', handler(error));
```

### sampleEvent

Raised when the test Call gets a WebRTC sample object. The event is published every second.

Passes the [RTCSample](https://twilio.github.io/twilio-voice.js/interfaces/voice.rtcsample.html) object to the `sample` event handler.

```javascript
preflightTest.on('sample', handler(sample));
```

### warningEvent

Raised whenever the test Call encounters a warning.

Passes the name of the warning (`string`) and the [Warning](https://twilio.github.io/twilio-voice.js/interfaces/voice.preflighttest.warning.html) object to the `warning` event handler.

```javascript
preflightTest.on('warning', handler(name, warning));
```

## Accessor Reference

### preflightTest.callSid

The Call SID for the test call. This is set when the test `Device` instance has finished connecting to Twilio.

### preflightTest.endTime

A timestamp in milliseconds of when the test ended. This is set when the test has completed and raised the completed event.

### preflightTest.latestSample

The latest WebRTC sample collected. This is set whenever the Call emits a `sample`.

Returns an [RTCSample](https://twilio.github.io/twilio-voice.js/interfaces/voice.rtcsample.html) object or `undefined`.

### preflightTest.report

The report for this test. This is set when the test has completed and raised the `completed` event.

See the Report section below for more information on the report object.

### preflightTest.startTime

A timestamp in milliseconds of when the test started. This is set right after calling `Device.runPreflight(token, options)`.

### preflightTest.status

The status of the test. Below are the possible values for this property.

| Value      | Description                                                                     |
| ---------- | ------------------------------------------------------------------------------- |
| Completed  | The connection to Twilio has been disconnected and the test call has completed. |
| Connected  | The connection to Twilio has been established.                                  |
| Connecting | Connecting to Twilio has started.                                               |
| Failed     | The test has stopped and failed.                                                |

## Report

Below is an example report returned to the `completed` event handler or accessed via `preflightTest.report`.

```javascript
{
    "callSid": "CAa6a7a187a9cba2714d6fdccf472cc7b1",
    "edge": "ashburn",
    "iceCandidateStats": [...],
    "networkTiming": {
        "signaling": {
            "start": 1628271850137,
            "end": 1628271850958,
            "duration": 821
        },
        "dtls": {
            "start": 1628271851032,
            "end": 1628271851192,
            "duration": 160
        },
        "ice": {
            "start": 1628271850956,
            "end": 1628271851032,
            "duration": 76
        },
        "peerConnection": {
            "start": 1628271850965,
            "end": 1628271851193,
            "duration": 228
        }
    },
    "samples": [
        {
            "audioInputLevel": 10999,
            "audioOutputLevel": 1725,
            "bytesReceived": 2880,
            "bytesSent": 6080,
            "codecName": "PCMU",
            "jitter": 0,
            "mos": null,
            "packetsLost": 0,
            "packetsLostFraction": 0,
            "packetsReceived": 18,
            "packetsSent": 38,
            "rtt": 66,
            "timestamp": 1628271851961.724,
            "totals": {
                "bytesReceived": 2880,
                "bytesSent": 6080,
                "packetsLost": 0,
                "packetsLostFraction": 0,
                "packetsReceived": 18,
                "packetsSent": 38
            }
        }
    ],
    "selectedEdge": "roaming",
    "stats": {
        "jitter": {
            "average": 1.2273,
            "max": 3,
            "min": 0
        },
        "mos": {
            "average": 4.4,
            "max": 4.406226172226452,
            "min": 4.390523592170177
        },
        "rtt": {
            "average": 69.045,
            "max": 84,
            "min": 59
        }
    },
    "testTiming": {
        "start": 1628271849623,
        "end": 1628271874932,
        "duration": 25309
    },
    "totals": {
        "bytesReceived": 109280,
        "bytesSent": 182080,
        "packetsLost": 0,
        "packetsLostFraction": 0,
        "packetsReceived": 683,
        "packetsSent": 1138
    },
    "warnings": [
        {
            "name": "low-bytes-received",
            "description": "Received an RTCWarning. See .rtcWarning for the RTCWarning",
            "rtcWarning": {
                "values": [
                    0,
                    0,
                    0
                ],
                "samples": [
                    {
                        "audioInputLevel": 11718,
                        "audioOutputLevel": 0,
                        "bytesReceived": 0,
                        "bytesSent": 8000,
                        "codecName": "PCMU",
                        "jitter": 1,
                        "mos": 4.404255907879351,
                        "packetsLost": 0,
                        "packetsLostFraction": 0,
                        "packetsReceived": 0,
                        "packetsSent": 50,
                        "rtt": 61,
                        "timestamp": 1628271856961.176,
                        "totals": {
                            "bytesReceived": 29920,
                            "bytesSent": 46080,
                            "packetsLost": 0,
                            "packetsLostFraction": 0,
                            "packetsReceived": 187,
                            "packetsSent": 288
                        }
                    },
                    {
                        "audioInputLevel": 11261,
                        "audioOutputLevel": 0,
                        "bytesReceived": 0,
                        "bytesSent": 8000,
                        "codecName": "PCMU",
                        "jitter": 1,
                        "mos": 4.404255907879351,
                        "packetsLost": 0,
                        "packetsLostFraction": 0,
                        "packetsReceived": 0,
                        "packetsSent": 50,
                        "rtt": 61,
                        "timestamp": 1628271857960.764,
                        "totals": {
                            "bytesReceived": 29920,
                            "bytesSent": 54080,
                            "packetsLost": 0,
                            "packetsLostFraction": 0,
                            "packetsReceived": 187,
                            "packetsSent": 338
                        }
                    },
                ],
                "name": "bytesReceived",
                "threshold": {
                    "name": "min",
                    "value": 1
                }
            }
        }
    ],
    "selectedIceCandidatePairStats": {
        "localCandidate": {
            "id": "RTCIceCandidate_XXXXXXXX",
            "timestamp": 1628271851961.724,
            "type": "local-candidate",
            "transportId": "RTCTransport_0_1",
            "isRemote": false,
            "networkType": "wifi",
            "ip": "xx.xx.x.xx",
            "address": "xx.xx.x.xx",
            "port": 44444,
            "protocol": "udp",
            "candidateType": "prflx",
            "priority": 1853759231
        },
        "remoteCandidate": {
            "id": "RTCIceCandidate_XXXXXXXX",
            "timestamp": 1628271851961.724,
            "type": "remote-candidate",
            "transportId": "RTCTransport_0_1",
            "isRemote": true,
            "ip": "xx.xxx.xxx.xxx",
            "address": "xx.xxx.xxx.xxx",
            "port": 11111,
            "protocol": "udp",
            "candidateType": "host",
            "priority": 2130706431
        }
    },
    "isTurnRequired": false,
    "callQuality": "excellent"
}
```

**Report Properties**

**Property**: **callSid**

Description:

The Call SID for the test call.

**Property**: **edge**

Description:

The Edge location that the test call was connected to

**Property**: **iceCandidateStats**

Description:

An array of WebRTC stats for the ICE candidates gathered when connecting to media. Each item is an [RTCIceCandidateStats](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidateStats) object which provides information related to an ICE candidate.

**Property**: **networkTiming**

Description:

Measurements for establishing DTLS connection. Properties:

* dtls: This is measured from RTCDtlsTransport `connecting` to `connected` state.

  * See the documentation for [RTCDtlsTransport state](https://developer.mozilla.org/en-US/docs/Web/API/RTCDtlsTransport/state) for more information.
* `ice`: Measurements for establishing ICE connection.

  * This is measured from ICE connection `checking` to `connected` state. See the documentation for [RTCPeerConnection.iceConnectionState](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState) for more information.
* `peerConnection`: Measurements for establishing a Peer Connection

  * This is measured from PeerConnection connecting to connected state. See the documentation for [RTCPeerConnection.connectionState](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState) for more information.
* `signaling`: Measurements for establishing Signaling connection.

  * This is measured from initiating a connection using `device.connect()` up to when `RTCPeerConnection.signalingState` transitions to `stable` state. See the documentation for [RTCPeerConnection.signalingState](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/signalingState) for more information.

**Property**: **samples**

Description:

WebRTC samples collected during the test. See the object format on the [RTCSample Interface reference page](https://twilio.github.io/twilio-voice.js/interfaces/voice.rtcsample.html).

**Property**: **selectedEdge**

Description:

The edge passed to `Device.runPreflight`

**Property**: **stats**

Description:

RTC-related stats that are extracted from WebRTC samples. This information includes maximum, minimum, and average values calculated for each statistic.

**Property**: **testTiming**

Description:

Timing measurement related to the test. Includes millisecond timestamps and duration.

**Property**: **totals**

Description:

Calculated totals in RTC statistics samples.

**Property**: **warnings**

Description:

PreflightTest.Warnings detected during the test

**Property**: **selectedIceCandidatePairStats**

Description:

A WebRTC stats object for the ICE candidate pair used to connect to media, if candidates were selected. Each item is an [RTCIceCandidatePairStats](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats) object which provides information related to an ICE candidate.

**Property**: **isTurnRequired**

Description:

Whether a TURN server is required to connect to media. This is dependent on the selected ICE candidates, and will be `true` if either is of type "relay", `false` if both are of another type, or `undefined` if there are no selected ICE candidates. See [PreflightTest.Options.iceServers](https://twilio.github.io/twilio-voice.js/interfaces/voice.preflighttest.options.html#iceservers) for more details.

**Property**: **callQuality**

Description:

The quality of the call, determined by the MOS (Mean Opinion Score) of the audio stream. Possible values include:

* Excellent - If the average `mos` is over 4.2
* Great - If the average `mos` is between 4.1 and 4.2 both inclusive
* Good - If the average `mos` is between 3.7 and 4.0 both inclusive
* Fair - If the average `mos` is between 3.1 and 3.6 both inclusive
* Degraded - If the average `mos` is 3.0 or below

## TwiML Apps for PreflightTest

Please see our [example application](https://github.com/twilio/rtc-diagnostics-react-app) if you don't want to set up the required TwiML Apps manually.

`Twilio.Device.runPreflight(token, options)` requires a [Twilio Access Token](/docs/iam/access-tokens) to initiate the test call. This access token will be passed directly to the [Device's](/docs/voice/sdks/javascript/twiliodevice) constructor and will be used to connect to a TwiML app that you associated with your [Twilio Access Token](/docs/iam/access-tokens). In order to get better results, the TwiML app should be able to record audio from a microphone and play it back to the browser. Below are example TwiML Apps that you can use and some setup instructions.

### Record and Play

If `PreflightTest.Options.fakeMicInput` is set to `false`, `Device.runPreflight(token, options)` API requires a token with a TwiML app that can record an audio from a microphone and the ability to play the recorded audio back to the browser. In order to achieve this, we need two TwiML endpoints: one to capture and record the audio, and another one to play the recorded audio.

In this example, we will use TwiML Bins for our TwiML app. Start by going to the [TwiML Bin](https://www.twilio.com/console/twiml-bins) page in the Twilio Console.

**Playback TwiML Bin**

Create a new TwiML Bin with the plus button on that screen and use "Playback" as the friendly name.

Then use the following TwiML under the TwiML section.

```xml
<?xml version="1.0" encoding="UTF-8"?>

<Response>
  <Say>You said:</Say>
  <Play loop="1">{{RecordingUrl}}</Play>
  <Say>Now waiting for a few seconds to gather audio performance metrics.</Say>
  <Pause length="3"/>
  <Say>Hanging up now.</Say>
</Response>
```

**Record TwiML Bin**

Using the [TwiML Bin](https://www.twilio.com/console/twiml-bins) page, let's create another TwiML Bin by clicking the plus button on that screen and use "Record" as the friendly name.

Then replace the **action URL** in the following template with your "Playback" TwiML Bin's URL that you created previously:

```xml
<?xml version="1.0" encoding="UTF-8"?>

<Response>
  <Say>Record a message in 3, 2, 1</Say>
  <Record maxLength="5" action="https://my-record-twimlBin-url"></Record>
  <Say>Did not detect a message to record</Say>
</Response>
```

**Creating the TwiML Application**

Now that we have created our TwiML Bins, let's create our TwiML app by going to the [TwiML Apps](https://www.twilio.com/console/voice/twiml/apps) page.

1. Click the **plus button** on that screen
2. Enter a friendly name for the application like "PreflightTest".
3. Under **Voice request URL**, enter your **Record TwiML Bin's URL** that you created in the previous section, and then click the **Create** button.
4. On that same page, open the TwiML app that you just created by clicking on it and make note of the **SID**.

You can now use this TwiML app to generate your [Access Token](/docs/iam/access-tokens) when calling `Device.runPreflight(token, options)`.

### Echo

If `PreflightTest.Options.fakeMicInput` is set to `true`, `Device.runPreflight(token, options)` API requires an [Access Token](/docs/iam/access-tokens) with a TwiML app that can capture and play audio.

Following the previous steps, create a TwiML Bin using the following TwiML and create a new TwiML Application pointed to that TwiML Bin's URL.

```xml
<?xml version="1.0" encoding="UTF-8"?>

<Response>
  <Echo/>
</Response>
```
