Samples

To run a sample, edit a file with the sample content, and run Hurl:

$ vi sample.hurl

GET https://example.org

$ hurl sample.hurl

By default, Hurl behaves like curl and outputs the last HTTP response’s entry. To have a test oriented output, you can use --test option:

$ hurl --test sample.hurl

You can check Hurl tests suite for more samples.

Getting Data

A simple GET:

GET https://example.org

Doc

HTTP Headers

A simple GET with headers:

GET https://example.org/news
User-Agent: Mozilla/5.0 
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Doc

Query Params

GET https://example.org/news
[QueryStringParams]
order: newest
search: something to search
count: 100

Or:

GET https://example.org/news?order=newest&search=something%20to%20search&count=100

Doc

Basic Authentication

GET https://example.org/protected
[BasicAuth]
bob: secret

Doc

This is equivalent to construct the request with a Authorization header:

# Authorization header value can be computed with `echo -n 'bob:secret' | base64`
GET https://example.org/protected
Authorization: Basic Ym9iOnNlY3JldA==

Basic authentication allows per request authentication. If you want to add basic authentication to all the requests of a Hurl file you could use -u/--user option.

Sending Data

Sending HTML Form Data

POST https://example.org/contact
[FormParams]
default: false
token: {{token}}
email: john.doe@rookie.org
number: 33611223344

Doc

Sending Multipart Form Data

POST https://example.org/upload
[MultipartFormData]
field1: value1
field2: file,example.txt;
# One can specify the file content type:
field3: file,example.zip; application/zip

Doc

Multipart forms can also be sent with a multiline string body:

POST https://example.org/upload
Content-Type: multipart/form-data; boundary="boundary"
```
--boundary
Content-Disposition: form-data; name="key1"

value1
--boundary
Content-Disposition: form-data; name="upload1"; filename="data.txt"
Content-Type: text/plain

Hello World!
--boundary
Content-Disposition: form-data; name="upload2"; filename="data.html"
Content-Type: text/html

<div>Hello <b>World</b>!</div>
--boundary--
```

In that case, files have to be inlined in the Hurl file.

Doc

Posting a JSON Body

With an inline JSON:

POST https://example.org/api/tests
{
    "id": "456",
    "evaluate": true
}

Doc

With a local file:

POST https://example.org/api/tests
Content-Type: application/json
file,data.json;

Doc

Templating a JSON Body

PUT https://example.org/api/hits
Content-Type: application/json
{
    "key0": "{{a_string}}",
    "key1": {{a_bool}},
    "key2": {{a_null}},
    "key3": {{a_number}}
}

Variables can be initialized via command line:

$ hurl --variable a_string=apple \
       --variable a_bool=true \
       --variable a_null=null \
       --variable a_number=42 \
       test.hurl

Resulting in a PUT request with the following JSON body:

{
    "key0": "apple",
    "key1": true,
    "key2": null,
    "key3": 42
}

Doc

Templating a XML Body

Using templates with XML body is not currently supported in Hurl. You can use templates in XML multiline string body with variables to send a variable XML body:

POST https://example.org/echo/post/xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<Request>
    <Login>{{login}}</Login>
    <Password>{{password}}</Password>
</Request>
```

Doc

Using GraphQL Query

A simple GraphQL query:

POST https://example.org/starwars/graphql
```graphql
{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
```

A GraphQL query with variables:

POST https://example.org/starwars/graphql
```graphql
query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

variables {
  "episode": "JEDI",
  "withFriends": false
}
```

GraphQL queries can also use Hurl templates.

Doc

Testing Response

Testing Response Headers

Use implicit response asserts to test header values:

GET https://example.org/index.html
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT

Doc

Or use explicit response asserts with predicates:

GET https://example.org
HTTP 302
[Asserts]
header "Location" contains "www.example.net"

Doc

Testing REST APIs

Asserting JSON body response (node values, collection count etc...) with JSONPath:

GET https://example.org/order
screencapability: low
HTTP 200
[Asserts]
jsonpath "$.validated" == true
jsonpath "$.userInfo.firstName" == "Franck"
jsonpath "$.userInfo.lastName" == "Herbert"
jsonpath "$.hasDevice" == false
jsonpath "$.links" count == 12
jsonpath "$.state" != null
jsonpath "$.order" matches "^order-\\d{8}$"
jsonpath "$.order" matches /^order-\d{8}$/     # Alternative syntax with regex literal

Doc

Testing status code:

GET https://example.org/order/435
HTTP 200

Doc

GET https://example.org/order/435
# Testing status code is in a 200-300 range
HTTP *
[Asserts]
status >= 200
status < 300

Doc

Testing HTML Response

GET https://example.org
HTTP 200
Content-Type: text/html; charset=UTF-8
[Asserts]
xpath "string(/html/head/title)" contains "Example" # Check title
xpath "count(//p)" == 2  # Check the number of p
xpath "//p" count == 2  # Similar assert for p
xpath "boolean(count(//h2))" == false  # Check there is no h2  
xpath "//h2" not exists  # Similar assert for h2
xpath "string(//div[1])" matches /Hello.*/

Doc

GET https://example.org/home
HTTP 200
[Asserts]
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Expires]" contains "Wed, 13 Jan 2021"
cookie "JSESSIONID[Secure]" exists
cookie "JSESSIONID[HttpOnly]" exists
cookie "JSESSIONID[SameSite]" == "Lax"

Doc

Testing Bytes Content

Check the SHA-256 response body hash:

GET https://example.org/data.tar.gz
HTTP 200
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;

Doc

SSL Certificate

Check the properties of a SSL certificate:

GET https://example.org
HTTP 200
[Asserts]
certificate "Subject" == "CN=example.org"
certificate "Issuer" == "C=US, O=Let's Encrypt, CN=R3"
certificate "Expire-Date" daysAfterNow > 15
certificate "Serial-Number" matches /[\da-f]+/

Doc

Others

HTTP Version

Testing HTTP version (1.0, 1.1, 2 or 3):

GET https://example.org/order/435
HTTP/3 200

Doc

Polling and Retry

Retry request on any errors (asserts, captures, status code, runtime etc...):

# Create a new job
POST https://api.example.org/jobs
HTTP 201
[Captures]
job_id: jsonpath "$.id"
[Asserts]
jsonpath "$.state" == "RUNNING"


# Pull job status until it is completed
GET https://api.example.org/jobs/{{job_id}}
[Options]
retry: 10   # maximum number of retry, -1 for unlimited
HTTP 200
[Asserts]
jsonpath "$.state" == "COMPLETED"

Doc

Delaying Requests

Add delay for every request, or a particular requests:

# Delaying this request by 5s
GET https://example.org/turtle
[Options]
delay: 5000
HTTP 200

# No delay!
GET https://example.org/turtle
HTTP 200

Doc

Skipping Requests

# a, b, d are runner, c is skipped
GET https://example.org/a

GET https://example.org/b
[Options]
skip: true

GET https://example.org/c

GET https://example.org/d

Doc

Testing Endpoint Performance

GET https://sample.org/helloworld
HTTP *
[Asserts]
duration < 1000   # Check that response time is less than one second

Doc

Using SOAP APIs

POST https://example.org/InStock
Content-Type: application/soap+xml; charset=utf-8
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="https://example.org">
  <soap:Header></soap:Header>
  <soap:Body>
    <m:GetStockPrice>
      <m:StockName>GOOG</m:StockName>
    </m:GetStockPrice>
  </soap:Body>
</soap:Envelope>
HTTP 200

Doc

Capturing and Using a CSRF Token

GET https://example.org
HTTP 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"


POST https://example.org/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP 302

Doc

Checking Byte Order Mark (BOM) in Response Body

GET https://example.org/data.bin
HTTP 200
[Asserts]
bytes startsWith hex,efbbbf;

Doc

AWS Signature Version 4 Requests

Generate signed API requests with AWS Signature Version 4, as used by several cloud providers.

POST https://sts.eu-central-1.amazonaws.com/
[Options]
aws-sigv4: aws:amz:eu-central-1:sts
[FormParams]
Action: GetCallerIdentity
Version: 2011-06-15

The Access Key is given per --user.

Doc