Testing HTTP

HTTP-specific configuration

TLS/SSL

By default, Artillery will reject SSL certificates that it's unable to validate, e.g. self-signed certificates. You may see errors such as UNABLE_TO_GET_ISSUER_CERT_LOCALLY, UNABLE_TO_VERIFY_LEAF_SIGNATURE, CERT_UNTRUSTED or one of the other validation error codes when that happens.

You can disable certificate validation with either of the following two options:

  1. Pass -k (or --insecure) flag to artillery run or artillery quick
  2. Set rejectUnauthorized: false in config.tls:
config:
  target: "https://myapp.staging:3002"
  tls:
    rejectUnauthorized: false
scenarios:
  - ...

Note: This option can be useful for testing in a development / staging environment, but should never be used when testing a production system.

Request timeout

If a response takes longer than 120 seconds Artillery will abort the request and report an ETIMEDOUT error.

To increase or decrease the default timeout set config.http.timeout to a number (in seconds).

config:
  target: "http://my.app"
  http:
    # Responses have to be sent within 10 seconds or the request will be aborted
    timeout: 10

Fixed connection pool

By default Artillery will open a new connection for each new virtual user. To open and re-use a fixed number of connections instead, set config.http.pool to a number:

config:
  target: "http://my.app"
  http:
    pool: 10 # All HTTP requests from all virtual users will be sent over the same 10 connections

This can be useful to emulate the conditions when the target would normally be behind a load-balancer and would have a fixed number of connections established at any given time.

Max sockets per virtual user

By default Artillery creates one TCP connection per virtual user. To allow for multiple sockets per virtual user (to mimic the behavior of a web browser for example), the config.http.maxSockets option may be set.

Note: this setting is per virtual user, not for the total number of sockets. To limit the total number of sockets, use the pool setting.

Flow actions

GET / POST / PUT / PATCH / DELETE requests

An HTTP request object may have the following attributes:

Example:

config:
  target: "https://example.com"
  phases:
    - duration: 10
      arrivalRate: 1
scenarios:
  - flow:
      - get:
          url: "/"
      - post:
          url: "/resource"
          json:
            hello: "world"

Logging

Debug messages can be logged with the log action:

config:
  target: "https://example.com"
  phases:
    - duration: 10
      arrivalRate: 1
scenarios:
  - flow:
      - log: "New virtual user running"
      - get:
          url: "/"
      - post:
          url: "/resource"
          json:
            hello: "world"

The string argument to log may include variables:

- log: "Current environment is set to: {{ $environment }}"

Setting Headers

Arbitrary headers may be sent with:

- get:
    url: "/test"
    headers:
      X-My-Header: "123"

Compressed Responses (gzip)

Set gzip to true to have Artillery add an Accept-Encoding header to the request, and decode compressed responses (e.g. encoded with gzip).

- post:
    url: "/test"
    json:
      foo: bar
    gzip: true

Redirects

Artillery follows redirects by default. To stop Artillery from following redirects, set followRedirect property on a request to false.

- get:
    url: "/test"
    followRedirect: false

Forms

URL-encoded forms (application/x-www-form-urlencoded)

Use the form attribute to send an URL-encoded form.

- post:
    url: "/upload"
    form:
      name: "Homer Simpson"
      favorite_food: "donuts"

Multipart forms (multipart/form-data)

Use the formData attribute to send a multipart/form-data form (forms containing files, non-ASCII data, and binary data).

- post:
    url: "/upload"
    formData:
      name: "Homer Simpson"
      favorite_food: "donuts"

To attach binary data a custom JS function can be used.

First-class file upload support is also provided by Artillery Pro for teams requiring extensive file uploading functionality in their tests.

Extracting and reusing parts of a response (request chaining)

You can parse responses and reuse those values in subsequent requests.

Syntax

To tell Artillery to parse a response, add a capture attribute to any request spec like so:

- get:
    url: "/"
    capture:
      json: "$.id"
      as: "id"

The capture element must always have an as attribute which names the value for use in subsequent requests, and one of:

Optionally, a transform attribute may be specified too, which should be a snippet of JS code (as a string) transforming the value after it has been extracted from the response:

- get:
    url: "/journeys"
    capture:
      xpath: "(//Journey)[1]/JourneyId/text()"
      transform: "this.JourneyId.toUpperCase()"
      as: "JourneyId"

Where this refers to the context of the virtual user running the scenario, i.e. an object containing all currently defined variables, including the one that has just been extracted from the response.

Capturing multiple values

Multiple values can be captured with an array of capture specs, e.g.:

- get:
    url: "/journeys"
    capture:
      - xpath: "(//Journey)[1]/JourneyId/text()"
        transform: "this.JourneyId.toUpperCase()"
        as: "JourneyId"
      - header: "x-my-custom-header"
        as: "headerValue"

Examples

In the following example, we POST to /pets to create a new resource, capture part of the response (the id of the new resource) and store it in the variable id. We then use that value in the subsequent request to load the resource and to check to see if the resource we get back looks right.

  - post:
      url: "/pets"
      json:
        name: "Mali"
        species: "dog"
      capture:
        json: "$.id"
        as: "id"
  - get:
      url: "/pets/{{ id }}"
      match:
        json: "$.name"
        value: "{{ name }}"

In the following example, we grab a matching a element at random and then use the value of its href attribute in another request:

- get:
    url: "/some/page"
    capture:
      - selector: "a[class^=productLink]"
        index: "random"
        attr: "href"
        as: "productUrl"
- get:
    url: "{{ productUrl }"

Cookies

Cookies are remembered and re-used by individual virtual users. Custom cookies can be specified with cookie attribute in individual requests.

  - get:
      url: "/pets/"
      cookie:
        saved: "tapir,sloth"

Pausing execution with think

To pause the virtual user for N seconds, use a think action:

  - post:
      url: "/pets"
      json:
        name: "Mali"
        species: "dog"
      capture:
        json: "$.id"
        as: "id"
  #
  # wait for 5 seconds:
  #
  - think: 5
  - get:
      url: "/pets/{{ id }}"
      match:
        json: "$.name"
        value: "{{ name }}"

Conditional Requests

An ifTrue attribute may be used to only execute a request in a flow if a condition is met. ifTrue may take two forms:

  1. Set to a name of a scenario variable. If the variable is set, the request will be executed.
  2. Set to a simple conditional expression, which may refer to any of the scenario variables and use one or more of:
    1. A numeric operation with +, -, *, /, % (modulo), or ^ (power)
    2. A comparison with ==, <, >, <=, >=
    3. Boolean operation with or, and, or not

Examples

Make a GET request only if the "keyword" scenario variable is set to a value:

- get:
    url: "/search?keyword={{ keyword }}"
    ifTrue: "keyword"

Make a GET request only if the pageNumber scenario variable is < 10:

- get:
    url: "/pages/{{ pageNumber }}"
    ifTrue: "pageNumber < 10"

Looping through a number of requests

You can use the loop construct to loop through a number of requests in a scenario. For example, each virtual user will send 100 GET requests to / with this scenario:

  config:
    # ... config here ...
  scenarios:
    - flow:
        - loop:
            - get:
                url: "/"
          count: 100

If count is omitted, the loop will run indefinitely.

loop is an array - any number of requests can be specified. Variables, cookie and response parsing will work as expected.

The current step of the loop is available inside a loop through the $loopCount variable (for example going from 1 too 100 in the example above).

Looping through an array

Looping through an array can be done by setting the over property to a literal array or the name of the variable containing an array of values.

In the following example 3 requests would be made, one for each product ID:

- loop:
    - get:
         url: "/products/{{ $loopElement }}"
   over:
     - id123
     - id456
     - id789

The product IDs could also be defined in the config section and used with a loop:

config:
  target: "https://my.app.local"
  phases:
    - duration: 600
      arrivalRate: 10
  variables:
    productIds:
      - ["id1", "id2", "id3"]
      - ["id4", "id5", "id6"]
scenarios:
  - flow:
      - loop:
          - get:
              url: "/products/{{ $loopElement }}"
        over: productIds

In this example each virtual user will make 3 requests to:

(Experimental) Looping with custom logic

Let's say we want to poll an endpoint until it returns a JSON response with the top-level status attribute set to "ready":

- loop:
    - think: 5
    - get:
        url: "/some/endpoint"
        capture:
          - json: $.status
            as: "status"
  whileTrue: "myFunction"
function myFunction(context, next) {
  const continueLooping = context.vars.status !== 'ready';
  return next(continueLooping); // call back with true to loop again
}

NOTE: whileTrue true takes precendence over count and over attributes if either of those is specified.

Advanced: writing custom logic in Javascript

The HTTP engine has support for "hooks", which allow for custom JS functions to be called at certain points during the execution of a scenario.

JS functions may also be run at any point in a scenario with the function action.

Loading custom JS code

To tell Artillery to load your custom code, set config.processor to path to your JS file:

config:
  target: "https://my.app.dev"
  phases:
    -
      duration: 300
      arrivalRate: 1
  processor: "./my-functions.js"
scenarios:
  - # ... scenarios definitions here ...

The JS file is expected to be a standard Node.js module:

  //
  // my-functions.js
  //
  module.exports = {
    setJSONBody: setJSONBody,
    logHeaders: logHeaders
  }

  function setJSONBody(requestParams, context, ee, next) {
    return next(); // MUST be called for the scenario to continue
  }

  function logHeaders(requestParams, response, context, ee, next) {
    console.log(response.headers);
    return next(); // MUST be called for the scenario to continue
  }

Specifying a function to run

beforeRequest and afterResponse hooks can be set in a request spec like this:

  # ... a request in a scenario definition:
  - post:
      url: "/some/route"
      beforeRequest: "setJSONBody"
      afterResponse: "logHeaders"

This tells Artillery to run the setJSONBody function before the request is made, and to run the logHeaders function after the response has been received.

Specifying multiple functions

An array of function names can be specified too, in which case the functions will be run one after another.

Setting scenario-level hooks

Similarly, a scenario definition can have a beforeRequest/afterResponse attribute, which will make the functions specified run for every request in the scenario.

Function steps in a flow

A function may be run at any point in a scenario with a function action:

scenarios:
  - flow:
    # Call setupSomeData
    - function: "setupSomeData"
    - get:
        url: "/some/url?q={{ query }}"

Functions invoked with function action have full access to the virtual user's context:

function setupSomeData(context, events, done) {
  context.vars['query'] = 'foo'; // set the "query" variable for the virtual user
  return done();)
}

Function signatures

beforeRequest

A function invoked in a beforeRequest hook should have the following signature:

function myBeforeRequestHandler(requestParams, context, ee, next) {
}

Where:

afterResponse

A function invoked in an afterResponse hook should have the following signature:

function myAfterResponseHandler(requestParams, response, context, ee, next) {
}

Where:

Functions invoked with a function action

function myFunction(context, ee, next) {
}

Where: