VOLTS

VoIP Open Linear Tester Suite

Functional tests for VoIP systems based on voip_patrol, sipp, sox, chromaprint, opensips, and docker.

Overview

The system is designed to run simple call scenarios, that you usually do with your desk phones. Scenarios are run one by one from scenarios folder in alphabetical order, which could be considered a limitation, but also allows you to reuse the same accounts in a different set of tests. This stands for Linear in the name 😉

So, call some destination(s) with one (or more) device(s) and control call arrival on another phone(s). But wait, there is more:

System Architecture

The suite consists of 8 parts, running sequentially:

  1. Preparation - Transform templates to real scenarios using Jinja2 template engine with jinja2_time extension
  2. WebSocket-TLS Proxy - Start proxy to provide WSS transport for voip_patrol scenarios
  3. Database (Pre) - Run database scripts to put test data
  4. VoIP Testing - Run voip_patrol or sipp scenario
  5. Database (Post) - Remove test data from databases
  6. Media Check - Analyze obtained media files if necessary
  7. Proxy Teardown - Stop WebSocket-TLS proxy
  8. Report Generation - Analyze results and print them in desired format

Steps 3-6 run sequentially against scenario files prepared in step 1, one at a time. Again, it’s for Linear.

Getting Started

Building

Suite is designed to run locally from your Linux PC or Mac. Docker should be installed.

Notes on using podman: VOLTS can run using podman-docker package. One obstacle by default - the volumes permissions inside a container. To address this issue please refer to this article.

To build, just run:

./build.sh

Script will build 6 docker images and tag them accordingly.

In case if voip_patrol or sipp is updated, you need to rebuild these containers:

./build.sh -r

Running

After building, run all scenarios:

./run.sh

Simple, isn’t it? This will run all scenarios found in scenarios folder one by one.

To run a single scenario:

./run.sh <scenario_name>
# or
./run.sh scenarios/<scenario_name>

To get a set of tests running using tag keyword:

./run.sh tag=set1,set2

Command Options

Option Description
-h, --help Show help message
-l, --log-level N Set log level (0=silent, 1=normal, 2=verbose, 3=debug)
-r, --report TYPE Set report type (table|json|table_full|json_full)
-t, --timeout N Set maximum single test time in seconds
-v, --verbose Enable verbose output (equivalent to -l 2)
-d, --debug Enable debug output (equivalent to -l 3)
--tls-port N Set OpenSIPS TLS port
--wss-port N Set OpenSIPS WSS port
--heps-port N Set HEP source port
--hepd-port N Set HEP destination port

Special Commands

Command Description
stop Stop tests and delete all containers
sngrep Launch SIP packet capture tool
dbclean Clean up test data from databases

After running the suite you can always find voip_patrol results in tmp/output folder.

Environment Variables

You can also configure behavior via environment variables (command-line options take precedence):

Variable Description
REPORT_TYPE Report type: table, json, table_full, json_full. Overridden by -r/--report
LOG_LEVEL Log level (0-3). Overridden by -l/--log-level
MAX_SINGLE_TEST_TIME Maximum time for a single test in seconds. Overridden by -t/--timeout
OPENSIPS_TLS_PORT OpenSIPS TLS port (default: 6051). Overridden by --tls-port
OPENSIPS_WSS_PORT OpenSIPS WSS port (default: 9443). Overridden by --wss-port
OPENSIPS_HEPS_PORT HEP source port (default: 8887). Overridden by --heps-port
OPENSIPS_HEPD_PORT HEP destination port (default: 8888). Overridden by --hepd-port

Configuration

Scenarios

VOLTS scenarios are combined voip_patrol/sipp, database, and media_check scenarios, templatized with Jinja2 style. This is done to avoid repeating passwords, usernames, domains, etc.

Due to using jinja2-time extension, it’s possible to use dynamic time/date values in your scenarios, for example testing some time-based rules on your PBX.

Global Config

Values for templates are taken from scenarios/config.yaml. Variables from global section transform to c. (for config) and from accounts to a. in templates for shorter notation.

There is a special name scenario_name that transforms to a scenario file name stripped .xml extension.

All settings from global section are inherited to the accounts section automatically unless defined there explicitly.

There is also an env variable that exposes system environment variables to templates. Use it when you need to pass runtime values without modifying config.yaml:

{{ env.MY_VAR }}                        <!-- empty string if not set -->
{{ env.MY_VAR | default('fallback') }}  <!-- explicit fallback value -->
{{ env.DOMAIN | default(c.domain) }}    <!-- fall back to config.yaml value -->

Example config.yaml:

global:
  domain:     '<SOME_DOMAIN>'
  transport:  'tls'
  srtp:       'dtls,sdes,force'
  play_file:  '/voice_ref_files/8000_12s.wav'
databases:
  'sipproxydb':
    type:       'mysql'
    user:       'sipproxydbrw'
    password:   'sipproxydbrwpass'
    base:       'sipproxydb'
    host:       'mysipproxydb.local'
accounts:
  '88881':
    username:       '88881'
    auth_username:  '88881-1'
    password:       'SuperSecretPass1'
  '88882':
    username:       '88882'
    auth_username:  '88882-67345'
    password:       'SuperSecretPass2'

VoIP Patrol Configuration

Basic Registration Example

<config>
    <section type="voip_patrol">
        <actions>
            <action type="register" label="Register {{ a.88881.label }}"
                transport="{{ a.88881.transport }}"
                account="{{ a.88881.label }}"
                username="{{ a.88881.username }}"
                auth_username="{{ a.88881.auth_username }}"
                password="{{ a.88881.password }}"
                registrar="{{ c.domain }}"
                realm="{{ a.88881.domain }}"
                expected_cause_code="200"
            />
            <action type="wait" complete="true" ms="2000"/>
        </actions>
    </section>
</config>

WebSocket Transport Notice

As voip_patrol itself doesn’t support WebSocket transport, OpenSIPS is used as a TLS-WSS SIP proxy. Using it is simple: specify transport="wss" in your voip_patrol section.

You can debug proxy traffic using sngrep:

./run.sh sngrep
# or
docker exec -it volts_opensips sngrep -L udp:127.0.0.1:8888
# or using a local sngrep installation
sudo sngrep -L udp:127.0.0.1:8888 port 8888

Where 8888 is the OPENSIPS_HEPD_PORT (configurable with --hepd-port option).

Note: you need to use the turn section of your voip_patrol configuration to ensure media passes through. See the WSS example below.

SIPP Configuration

You can add existing SIPP scenarios mainly unchanged. They run with:

sipp <target> -sf <scenario.xml> -m 1 -mp <random_port> -i <container_ip>

Example SIPP OPTIONS test:

<config tag="sipp">
    <section type="sipp">
        <actions>
            <action transport="{{ c.transport }}" target="{{ c.domain }}">
                <scenario name="Options">
                    <send>
                        <![CDATA[
                OPTIONS sip:check_server_health@{{ c.domain }} SIP/2.0
                Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
                Max-Forwards: 70
                To: <sip:check_server_health@{{ c.domain }}>
                From: sipp <sip:check_server_health@[local_ip]:[local_port]>;tag=[call_number]
                Call-ID: [call_id]
                CSeq: 1 OPTIONS
                Contact: <sip:check_server_health@[local_ip]:[local_port]>
                Accept: application/sdp
                Content-Length: 0
                ]]>
                    </send>
                    <recv response="200" timeout="200"/>
                </scenario>
            </action>
        </actions>
    </section>
</config>

SIPP Attributes

Attribute Description
transport Transport SIPP will use: udp (default), tcp, or tls
socket_mode single (default) or multi sockets
target Target address (port 5061 appended for TLS if not specified)
call_rate Call rate in calls per second (default: 10)
max_calls Maximum number of calls (default: 1)
max_concurrent_calls Maximum simultaneous calls (default: 10)
total_timeout Test timeout in seconds (default: 600)

Database

Database configuration is done in XML, section database. There are 2 stages:

Stage Description
pre Launched before running voip_patrol. Usually to put accounts data, routing, etc.
post Running after voip_patrol. For cleanup data inserted in pre stage.

Table Operations

Attribute Description
name Table name
type Operation: insert, replace, or delete
continue_on_error Ignore errors and continue (optional)
cleanup_after_test Auto-cleanup on post stage for insert operations (optional)

Example with Database:

<config>
    <section type="database">
        <actions>
            <action database="sippproxydb" stage="pre">
                <table name="subscriber" type="insert" cleanup_after_test="true">
                    <field name="username" value="{{ a.88881.username }}"/>
                    <field name="domain" value="{{ c.domain }}"/>
                    <field name="password" value="{{ a.88881.password }}"/>
                </table>
            </action>
        </actions>
    </section>
    <section type="voip_patrol">
        <!-- VoIP Patrol configuration here -->
    </section>
</config>

Media Check

Analyze call recordings with various media tools.

Media Check Attributes

Attribute Description
type Media check type: sox, sox_st, or fpcalc
file Path to file (must use /output/ prefix)
delete_after Delete file after check: yes/no/keep_failed (default)
print_debug Print debug info: yes/no (default)
sox_filter Used if type is sox/sox_st. Semicolon-separated expressions for SoX validation
fingerprint Used if type is fpcalc. Raw fingerprint from fpcalc tool
length Used if type is fpcalc. Expected length in seconds. Supports <min>-<max> format
likeness Used if type is fpcalc. Minimum similarity score (default: 0.9)

SoX Media Check

SoX collects parameters using sox --i <file>, sox <file> -n stat, and sox <file> -n stats.

Filter example:

sox_filter="length s -ge 10; length s -le 11; crest factor -lt 10"

This checks that:

Uses bash-style comparison operators (-eq, -lt, -gt, -le, -ge, -ne) instead of <, > symbols.

You can also use sox_st as a type to trim silence at the start and end of the recording before analysis — this allows slightly better file fingerprinting.

Chromaprint Media Check

The fpcalc type calculates the “likeness” (similarity) of a recorded audio file against a provided reference fingerprint using the fpcalc utility. More info about fingerprinting can be found here.

Note: make sure files have at least 3-4 seconds of audio. 10-15 seconds is recommended.

Likeness score reference:

Likeness score Meaning
0.95 – 1.00 Near identical (codec change, minor distortion)
0.85 – 0.95 Same content, more significant degradation
0.70 – 0.85 Possibly related, uncertain
< 0.70 Likely different content

Example with Media Check:

<config>
    <section type="voip_patrol">
        <actions>
            <!-- Call configuration with record="/output/{{ scenario_name }}.wav" -->
        </actions>
    </section>
    <section type="media_check">
        <actions>
            <action type="sox"
                sox_filter="length s -ge 10; length s -le 11"
                file="/output/{{ scenario_name }}.wav"
            />
        </actions>
    </section>
</config>

Examples

Basic Tests

Simple Registration Test

<config>
    <section type="voip_patrol">
        <actions>
            <action type="register" label="Register {{ a.88881.label }}"
                transport="{{ a.88881.transport }}"
                <!-- Account parameter is more used in receiving calls on this account later -->
                account="{{ a.88881.label }}"
                <!-- username would be a part of AOR - <sip:username@realm> -->
                username="{{ a.88881.username }}"
                <!-- auth_username would be used in WWW-Authorize procedure -->
                auth_username="{{ a.88881.auth_username }}"
                password="{{ a.88881.password }}"
                registrar="{{ c.domain }}"
                realm="{{ a.88881.domain }}"
                <!-- We are expecting to get 200 code here, so REGISTER is successful -->
                expected_cause_code="200"
            />
            <!-- Just wait 2 sec for all timeouts -->
            <action type="wait" complete="true" ms="2000"/>
        </actions>
    </section>
</config>

Basic Call Scenario

<config>
    <section type="voip_patrol">
        <actions>
            <!-- As we're using call functionality here - define the list of codecs -->
            <action type="codec" disable="all"/>
            <action type="codec" enable="pcma" priority="250"/>
            <action type="codec" enable="pcmu" priority="249"/>
            
            <action type="register" label="Register {{ a.88881.label }}"
                transport="{{ a.88881.transport }}"
                account="{{ a.88881.label }}"
                username="{{ a.88881.username }}"
                auth_username="{{ a.88881.auth_username }}"
                password="{{ a.88881.password }}"
                registrar="{{ c.domain }}"
                realm="{{ c.domain }}"
                expected_cause_code="200"
                <!-- Make sure we are using SRTP on a call received. This is done here as accounts are created before accept(answer) action -->
                srtp="{{ a.88881.srtp }}"
            />
            <action type="wait" complete="true" ms="2000"/>
            
            <action type="accept" label="Receive call on {{ a.88881.label }}"
                <!-- This is not a load test - so only 1 call is expected -->
                call_count="1"
                <!-- Make sure we have received a call on a previously registered account -->
                match_account="{{ a.88881.label }}"
                <!-- Hangup in 10 seconds after answer -->
                hangup="10"
                <!-- Send back "200 OK" -->
                code="200" reason="OK"
                transport="{{ a.88881.transport }}"
                <!-- Make sure we are using SRTP -->
                srtp="{{ a.88881.srtp }}"
                <!-- Play a file back to gather RTCP stats in the report -->
                play="{{ c.play_file }}"
            />
            
            <action type="call" label="Call {{ a.90001.label }} -> {{ a.88881.label }}"
                transport="tls"
                <!-- We are waiting for an answer -->
                expected_cause_code="200"
                caller="{{ a.90001.label }}@{{ c.domain }}"
                callee="{{ a.88881.label }}@{{ c.domain }}"
                from="sip:{{ a.90001.label }}@{{ c.domain }}"
                to_uri="{{ a.88881.label }}@{{ c.domain }}"
                max_duration="20" hangup="10"
                <!-- We are specifying all auth data here for INVITE -->
                auth_username="{{ a.90001.username }}"
                password="{{ a.90001.password }}"
                realm="{{ c.domain }}"
                rtp_stats="true"
                max_ring_duration="15"
                srtp="{{ a.90001.srtp }}"
                play="{{ c.play_file }}"
            />
            <action type="wait" complete="true" ms="30000"/>
        </actions>
    </section>
</config>

Advanced Scenarios

Call with Database Integration

<!-- Register with 90012 and receive a call from 90011 -->
<config>
    <section type="database">
        <actions>
        <!-- add subscribers to sip proxy -->
            <!-- "sippproxydb" here is referring to an entity in "databases" from config.yaml. -->
            <action database="sippproxydb" stage="pre">
                <table name="subscriber" type="insert" cleanup_after_test="true">
                    <field name="username" value="{{ a.90011.username }}"/>
                    <field name="domain" value="{{ c.domain }}"/>
                    <field name="ha1" value="{{ a.90011.ha1 }}"/>
                    <!-- here password due to ha1 is useless, so we can put some data based on jinja2_time.TimeExtension -->
                    <field name="password" value="{% now 'local' %}"/>
                </table>
                <table name="subscriber" type="insert" cleanup_after_test="true">
                    <field name="username" value="{{ a.90012.username }}"/>
                    <field name="domain" value="{{ c.domain }}"/>
                    <field name="ha1" value="{{ a.90012.ha1 }}"/>
                    <field name="password" value="{% now 'local' + 'days=1', '%D' %}"/>
                </table>
            </action>
        </actions>
    </section>
    <section type="voip_patrol">
        <!-- VoIP testing actions -->
    </section>
</config>

WSS Call with Media Check

<!-- Call echo service and make sure receive an answer with media -->
<config>
    <section type="voip_patrol">
        <actions>
            <action type="codec" disable="all"/>
            <action type="codec" enable="pcma" priority="250"/>
            <action type="codec" enable="pcmu" priority="249"/>
            <action type="turn" enabled="true" server="{{ c.stun_address }}" stun_only="true"/>
            <action type="wait" ms="5000"/>
            
            <action type="call" label="Call to 11111 (echo) WSS"
                transport="wss"
                <!-- We are expecting answer here -->
                expected_cause_code="200"
                caller="{{ a.88881.label }}@{{ c.domain }}"
                callee="11111@{{ a.88881.domain_wss }}"
                from="sip:{{ a.88881.label }}@{{ c.domain }}"
                to_uri="11111@{{ c.domain }}"
                max_duration="20" hangup="10"
                auth_username="{{ a.88881.username }}"
                password="{{ a.88881.password }}"
                realm="{{ c.domain }}"
                play="{{ c.play_file }}"
                rtp_stats="true"
                srtp="{{ a.88881.dtls }}"
                <!-- We need to record file on answer. To analyze it below now it MUST be with "/output/" path prefix -->
                record="/output/{{ scenario_name }}.wav"
            />
            <action type="wait" complete="true" ms="30000"/>
        </actions>
    </section>
    <section type="media_check">
        <actions>
            <action type="sox"
                <!-- We are testing that the outcome of the recorded file is between 9 and 11 seconds and checking amplitude -->
                sox_filter="length s -ge 9; length s -le 11; maximum amplitude -ge 0.9"
                <!-- File name is the same as in the "record" attribute in the "call" action above. Now it MUST be with "/output/" path prefix -->
                file="/output/{{ scenario_name }}.wav"
                delete_after="yes"
            />
        </actions>
    </section>
</config>

Chromaprint Media Check Example

<!-- Call echo service and verify audio fingerprint similarity -->
<config>
    <section type="voip_patrol">
        <actions>
            <action type="codec" disable="all"/>
            <action type="codec" enable="pcma" priority="250"/>
            <action type="codec" enable="pcmu" priority="249"/>
            <action type="call" label="Call to 11111 (echo)"
                transport="{{ a.88881.transport }}"
                expected_cause_code="200"
                caller="{{ a.88881.label }}@{{ c.domain }}"
                callee="11111@{{ a.88881.domain }}"
                from="sip:{{ a.88881.label }}@{{ c.domain }}"
                to_uri="11111@{{ c.domain }}"
                max_duration="20" hangup="10"
                auth_username="{{ a.88881.username }}"
                password="{{ a.88881.password }}"
                realm="{{ c.domain }}"
                play="{{ c.play_file }}"
                rtp_stats="true"
                srtp="{{ a.88881.srtp }}"
                record="/output/{{ scenario_name }}.wav"
            />
            <action type="wait" complete="true" ms="30000"/>
        </actions>
    </section>
    <section type="media_check">
        <actions>
            <action type="fpcalc"
                length="9.5-10.5"
                fingerprint="3089777192, 3089826344, 3089901096, 3089902136, 3111922232, 3120310648, 2577140987, 2577204442"
                file="/output/{{ scenario_name }}.wav"
                likeness="0.85"
            />
        </actions>
    </section>
</config>

SIPP Tests

Load Testing with SIPP

<config>
    <section type="sipp">
        <actions>
            <action transport="{{ c.transport }}"
                    target="{{ c.domain }}"
                    max_calls="1000"
                    call_rate="10"
                    max_concurrent_calls="10"
                    socket_mode="single">
                <!-- sipp {{ c.domain }}:5061 -sf scen.xml -m 1000 -r 10 -l 10 -t ln -->
                <scenario name="UAC REGISTER - UnREGISTER with Auth">
                    <send retrans="500">
                        <![CDATA[
                            REGISTER sip:{{ c.domain }}:[remote_port] SIP/2.0
                            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
                            From: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}>;tag=[pid]VOLTsTag00[call_number]
                            To: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}:[remote_port]>
                            Call-ID: {% now 'utc', '%H%M%S' %}///[call_id]
                            CSeq: 1 REGISTER
                            Contact: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@[local_ip]:[local_port];transport=[transport]>
                            Max-Forwards: 70
                            User-Agent: VOLTS/1.0
                            Expires: 60
                            Content-Length: 0

                        ]]>
                    </send>
                    <recv response="401" auth="true"></recv>
                    <send retrans="500">
                        <![CDATA[
                            REGISTER sip:{{ c.domain }}:[remote_port] SIP/2.0
                            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
                            From: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}>;tag=[pid]VOLTsTag00[call_number]
                            To: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}:[remote_port]>
                            Call-ID: {% now 'utc', '%H%M%S' %}///[call_id]
                            CSeq: 2 REGISTER
                            Contact: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@[local_ip]:[local_port];transport=[transport]>
                            Max-Forwards: 70
                            User-Agent: VOLTS/1.0
                            Expires: 60
                            [authentication username={{ a.88881.username }} password={{ a.88881.password }}]
                            Content-Length: 0

                        ]]>
                    </send>
                    <recv response="200" crlf="true"></recv>
                    
                    <!-- Unregister -->
                    <pause milliseconds="5000"/>
                    <send retrans="500">
                        <![CDATA[
                            REGISTER sip:{{ c.domain }}:[remote_port] SIP/2.0
                            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
                            From: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}>;tag=[pid]VOLTsTag00[call_number]
                            To: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}:[remote_port]>
                            Call-ID: {% now 'utc', '%H%M%S' %}///[call_id]
                            CSeq: 3 REGISTER
                            Contact: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@[local_ip]:[local_port];transport=[transport]>
                            Max-Forwards: 70
                            User-Agent: VOLTS/1.0
                            Expires: 0
                            Content-Length: 0

                        ]]>
                    </send>
                    <recv response="401" auth="true"></recv>
                    <send retrans="500">
                        <![CDATA[
                            REGISTER sip:{{ c.domain }}:[remote_port] SIP/2.0
                            Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
                            From: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}>;tag=[pid]VOLTsTag00[call_number]
                            To: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@{{ c.domain }}:[remote_port]>
                            Call-ID: {% now 'utc', '%H%M%S' %}///[call_id]
                            CSeq: 4 REGISTER
                            Contact: "VOLTS UAC TESTER" <sip:{{ a.88881.label }}@[local_ip]:[local_port];transport=[transport]>
                            Max-Forwards: 70
                            User-Agent: VOLTS/1.0
                            Expires: 0
                            [authentication username={{ a.88881.username }} password={{ a.88881.password }}]
                            Content-Length: 0

                        ]]>
                    </send>
                    <recv response="200"></recv>
                </scenario>
            </action>
        </actions>
    </section>
</config>

Tagged Test Execution

You can specify a tag on each test scenario:

<config tag='set1'>
    <section type="voip_patrol">
        <!-- Test configuration -->
    </section>
</config>

Multiple tags per scenario are supported:

<config tag='set1,set3'>
    <section type="voip_patrol">
        <!-- Test configuration -->
    </section>
</config>

Run tagged tests:

./run.sh tag=set1
./run.sh tag=set1,set2,test_set3

Results

After running tests, you’ll get a table like this:

+---------------------------------------+-----------------------------------------------------------+------+----------+-------+--------+------------------+
|                              Scenario |                                               VoIP Patrol | SIPP | Database | Media | Status |             Text |
+---------------------------------------+-----------------------------------------------------------+------+----------+-------+--------+------------------+
|                           01-register |                                                      PASS |  N/A |      N/A |   N/A |   PASS |  Scenario passed |
|                                       |                                      Register 88881       |      |          |       |   PASS | Main test passed |
|                          02-call-echo |                                                      PASS |  N/A |      N/A |   N/A |   PASS |  Scenario passed |
|                                       |                                      Call to 11111 (echo) |      |          |       |   PASS | Main test passed |
|            51-call-echo-media-control |                                                      PASS |  N/A |      N/A |  PASS |   PASS |  Scenario passed |
|                                       |                                      Call to 11111 (echo) |      |          |       |   PASS | Main test passed |
| 52-delayed-call-forward-unconditional |                                                      PASS |  N/A |     PASS |   N/A |   PASS |  Scenario passed |
|                                       |                                      Register 90012       |      |          |       |   PASS | Main test passed |
|                                       |                                      Register 90013       |      |          |       |   PASS | Main test passed |
|                                       |                      Receive call on 90012 and not answer |      |          |       |   PASS |    Call canceled |
|                                       |   Call from 90011 to 90012 (delay forward 25 sec) ->90013 |      |          |       |   PASS | Main test passed |
|                                       |                       Receive call on 90013       finally |      |          |       |   PASS | Main test passed |
|                53-server-check-health |                                                       N/A | PASS |      N/A |   N/A |   PASS | SIPP test passed |
+---------------------------------------+-----------------------------------------------------------+------+----------+-------+--------+------------------+

If any scenarios fail, you’ll see:

Scenarios ['49-teams-follow-forward', '50-team-no-answer-forward'] are failed!

This means your system needs tuning or there are issues with the tests. Check the console output and logs in tmp/output folder for detailed information.


For more detailed examples and advanced configurations, refer to the scenarios folder in the repository.