Announcing Hurl 1.7.0

The Hurl team is happy to announce a new version of Hurl, 1.7.0.

Hurl is a command line tool powered by curl, that runs HTTP requests defined in a simple plain text format:

# Get home:
GET https://example.org

HTTP/1.1 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"

# Do login!
POST https://example.org/login?user=toto&password=1234
X-CSRF-TOKEN: 

HTTP/1.1 302

Hurl can be used to get data like curl, or as an integration testing tool for JSON/XML HTTP apis / HTML content.

So, what’s new in 1.7.0?

Verbose Output Improvement

We’ve improved -v/--verbose option:

  • add more color!
  • add --very-verbose option to output request and response body for each entry of your Hurl file

First, we’ve added more color to the debug output.

In 1.6.1, a verbose output of Hurl looks like:

$ echo 'GET https://google.fr' | hurl --verbose
* fail fast: true
* insecure: false
* follow redirect: false
* max redirect: 50
* ------------------------------------------------------------------------------
* executing entry 1
* 
* Cookie store:
* 
* Request
* GET https://google.fr
* 
* request can be run with the following curl command:
* curl 'https://google.fr'
*
> GET / HTTP/2
> Host: google.fr
> accept: */*
> user-agent: hurl/1.6.1
> 
< HTTP/2 301 
< location: https://www.google.fr/
< content-type: text/html; charset=UTF-8
< date: Thu, 18 Aug 2022 09:55:23 GMT
< expires: Thu, 18 Aug 2022 09:55:23 GMT
< cache-control: private, max-age=2592000
< server: gws
< content-length: 219
< x-xss-protection: 0
< x-frame-options: SAMEORIGIN
< set-cookie: CONSENT=PENDING+677; expires=Sat, 17-Aug-2024 09:55:23 GMT; path=/; domain=.google.fr; Secure
< p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
< 
* 
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.fr/">here</A>.
</BODY></HTML>

In 1.7.0, headers are highlighted and requests and responses are more visible:

$ echo 'GET https://google.fr' | hurl --verbose
* Options:
*     fail fast: true
*     insecure: false
*     follow redirect: false
*     max redirect: 50
* ------------------------------------------------------------------------------
* Executing entry 1
*
* Cookie store:
*
* Request:
* GET https://google.fr
*
* Request can be run with the following curl command:
* curl 'https://google.fr'
*
> GET / HTTP/2
> Host: google.fr
> accept: */*
> user-agent: hurl/1.7.0-snapshot
>
* Response: (received 219 bytes in 111 ms)
*
< HTTP/2 301
< location: https://www.google.fr/
< content-type: text/html; charset=UTF-8
< date: Thu, 18 Aug 2022 09:56:40 GMT
< expires: Thu, 18 Aug 2022 09:56:40 GMT
< cache-control: private, max-age=2592000
< server: gws
< content-length: 219
< x-xss-protection: 0
< x-frame-options: SAMEORIGIN
< set-cookie: CONSENT=PENDING+308; expires=Sat, 17-Aug-2024 09:56:40 GMT; path=/; domain=.google.fr; Secure
< p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
<
*
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.fr/">here</A>.
</BODY></HTML>

Error on asserts are also colored now:

error: Assert status code
  --> /tmp/test.hurl:2:8
   |
 2 | HTTP/* 200
   |        ^^^ actual value is <301>
   |

Colors can be forced with --color, or deactivated with --no-color, and Hurl supports now NO_COLOR environnement variables (see http://no-color.org).

Secondly, intermediary request and response bodies can be outputted in debug with --very-verbose option. By default, the last HTTP body response is outputted on standard output (like curl). With --verbose option, request and response headers are also displayed on standard error, and with --very-verbose option, request and response bodies are finally also displayed on standard error. If your Hurl file has a lot of entries, debug logs can be pretty large, but you can mitigate it with the brand new [Options] section that we’re going to present now.

Use Level Request Options

Options such as --location, --verbose, --insecure can be used at the command line and applied to every request of an Hurl file. An [Options] section can be used to apply option to only one request (without passing options to the command line), while other requests are unaffected.

GET https://example.org
# An options section, each option is optional and applied only to this request...
[Options]
cacert: /etc/cert.pem   # a custom certificate file
compressed: true        # request a compressed response
insecure: true          # allows insecure SSL connections and transfers
location: true          # follow redirection for this request
max-redirs: 10          # maximum number of redirections
verbose: true           # allow verbose output
very-verbose: true      # allow more verbose output

So, given this Hurl file

GET https://google.fr
HTTP/* 301

GET https://google.fr
[Options]
location: true
HTTP/* 200

GET https://google.fr
HTTP/* 301

The second entry will follow location (and so we can test the status code to be 200 instead of 301).

You can use it to logs a specific entry:

# ... previous entries

GET https://api.example.org
[Options]
very-verbose: true

HTTP/* 200


# ... next entries

And only the debug logs of the specific entry will be displayed on standard error.

Using Hurl in Node.js

Started since 1.6.1, Hurl is available on npm, and can be easily integrated in various JavaScript projects. Hurl on npm is a thin JavaScript wrapper around the native binary.

To install it, just run:

$ npm install --save-dev @orangeopensource/hurl

And then edit your package.json:

{
  "name": "sample-app",
  "scripts": {
    "test": "hurl --test --glob test/*.hurl",
    ...
  },
  ...

Now you can run your integration tests with Hurl:

$ npm test
test/bar.hurl: Running [1/3]
test/bar.hurl: Success (5 request(s) in 136 ms)
test/baz.hurl: Running [2/3]
error: Assert failure
  --> test/baz.hurl:6:0
   |
 6 | xpath "string(//title)" == "Something"
   |   actual:   string <301 Moved>
   |   expected: string <Something>
   |

test/baz.hurl: Failure (4 request(s) in 62 ms)
test/foo.hurl: Running [3/3]
test/foo.hurl: Success (10 request(s) in 527 ms)
--------------------------------------------------------------------------------
Executed files:  3
Succeeded files: 2 (66.7%)
Failed files:    1 (33.3%)
Duration:        766 ms

Support for XML namespace in XPath

JSON and XML are first class citizens in Hurl with JSONPath assert and XPath assert. For XPath, we now support asserts with namespaces:

<?xml version="1.0"?>
<!-- both namespace prefixes are available throughout -->
<bk:book xmlns:bk='urn:loc.gov:books'
         xmlns:isbn='urn:ISBN:0-395-36341-6'>
    <bk:title>Cheaper by the Dozen</bk:title>
    <isbn:number>1568491379</isbn:number>
</bk:book>

Can be tested with the following Hurl file:

GET http://localhost:8000/assert-xpath

HTTP/1.0 200
[Asserts]

xpath "string(//bk:book/bk:title)" == "Cheaper by the Dozen"
xpath "string(//*[name()='bk:book']/*[name()='bk:title'])" == "Cheaper by the Dozen"
xpath "string(//*[local-name()='book']/*[local-name()='title'])" == "Cheaper by the Dozen"

xpath "string(//bk:book/isbn:number)" == "1568491379"
xpath "string(//*[name()='bk:book']/*[name()='isbn:number'])" == "1568491379"
xpath "string(//*[local-name()='book']/*[local-name()='number'])" == "1568491379"

For convenience, the first default namespace can be used with _.

This sample:

<?xml version="1.0"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" 
              "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    circle:hover {fill-opacity:0.9;}
  </style>
  <g style="fill-opacity:0.7;">
    <circle cx="6.5cm" cy="2cm" r="100" style="fill:red; stroke:black; stroke-width:0.1cm" transform="translate(0,50)" />
    <circle cx="6.5cm" cy="2cm" r="100" style="fill:blue; stroke:black; stroke-width:0.1cm" transform="translate(70,150)" />
    <circle cx="6.5cm" cy="2cm" r="100" style="fill:green; stroke:black; stroke-width:0.1cm" transform="translate(-70,150)"/>
  </g>
</svg>

Can be tested with the following Hurl file:

GET http://localhost:8000/assert-xpath-svg

HTTP/1.0 200
[Asserts]
xpath "//_:svg/_:g/_:circle" count == 3
xpath "//*[local-name()='svg']/*[local-name()='g']/*[local-name()='circle']" count ==  3
xpath "//*[name()='svg']/*[name()='g']/*[name()='circle']" count == 3

Others...

Under the hood, we’ve improved our code and Hurl should be quicker than ever. We’ve completely rewritten our grammar to be more correct and support Hurl future evolutions.

There are other changes and bug fixes in the Hurl 1.7.0 release: check out the release note!

And, finally, a big thanks to all our contributors!