Now that we have many requests in our test file, let’s review some tips on how to debug the executed HTTP exchanges.
We can run our test with -v/--verbose
option. In this mode, each entry is displayed with debugging
information like request HTTP headers, response HTTP headers, cookie storage, duration etc...
$ hurl --verbose --no-output basic.hurl
* Options:
* fail fast: true
* follow redirect: false
* insecure: false
* max redirect: 50
* retry: 0
* ------------------------------------------------------------------------------
* Executing entry 1
*
* Cookie store:
*
* Request:
* GET http://localhost:3000
*
* Request can be run with the following curl command:
* curl 'http://localhost:3000'
*
> GET / HTTP/1.1
> Host: localhost:3000
> Accept: */*
> User-Agent: hurl/4.0.0
>
* Response: (received 9564 bytes in 11 ms)
*
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 9564
< Set-Cookie: x-session-id=s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ; Path=/; HttpOnly; SameSite=Strict
< Date: Wed, 26 Jul 2023 13:16:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
*
* ------------------------------------------------------------------------------
* Executing entry 2
*
* Cookie store:
* #HttpOnly_localhost FALSE / FALSE 0 x-session-id s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ
*
* Request:
* GET http://localhost:3000/not-found
*
* Request can be run with the following curl command:
* curl --cookie 'x-session-id=s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ' 'http://localhost:3000/not-found'
*
> GET /not-found HTTP/1.1
> Host: localhost:3000
> Accept: */*
> Cookie: x-session-id=s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ
> User-Agent: hurl/4.0.0
>
* Response: (received 2217 bytes in 3 ms)
*
< HTTP/1.1 404 Not Found
< Content-Type: text/html; charset=utf-8
< Content-Length: 2217
< Date: Wed, 26 Jul 2023 13:16:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
...
Lines beginning with *
are debug info, lines that begin with >
are HTTP request headers and lines that begin with
<
are HTTP response headers.
In each run request, we can also see a curl command line to replay this particular request:
...
* Request can be run with the following curl command:
* curl --cookie 'x-session-id=s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ' 'http://localhost:3000/not-found'
...
In verbose mode, HTTP request and response bodies are not displayed in the debug logs. If you need to inspect the
request or response body, you can display more logs with --very-verbose
option:
$ hurl --very-verbose --no-output basic.hurl
* Options:
* fail fast: true
* follow redirect: false
* insecure: false
* max redirect: 50
* retry: 0
* ------------------------------------------------------------------------------
* Executing entry 1
*
* Cookie store:
*
* Request:
* GET http://localhost:3000
*
* Request can be run with the following curl command:
* curl 'http://localhost:3000'
*
** WARNING: failed to open cookie file ""
** Trying 127.0.0.1:3000...
** Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> Accept: */*
> User-Agent: hurl/4.0.0
>
* Request body:
*
** Added cookie x-session-id="s%3A_l88C6GKbPeC5YuDLraWARY32NB3bP-l.T%2BViEW%2BqMrmLZDqwzDxtEbdtW67lCKt0jGvvlfqls%2FI" for domain localhost, path /, expire 0
** Connection #0 to host localhost left intact
* Response: (received 9564 bytes in 9 ms)
*
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 9564
< Set-Cookie: x-session-id=s%3A_l88C6GKbPeC5YuDLraWARY32NB3bP-l.T%2BViEW%2BqMrmLZDqwzDxtEbdtW67lCKt0jGvvlfqls%2FI; Path=/; HttpOnly; SameSite=Strict
< Date: Wed, 26 Jul 2023 13:19:45 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Response body:
* <!doctype html>
* <html lang="en">
* <head>
* <meta charset="UTF-8" />
* <title>Movies Box</title>
* <link rel="icon" type="image/png" href="/img/favicon.png" />
* <link rel="stylesheet" href="/css/style.css" />
* </head>
* <body>
...
* </body>
* </html>
*
* Timings:
* begin: 2023-07-26 13:19:45.378037 UTC
* end: 2023-07-26 13:19:45.387332 UTC
* namelookup: 4182 µs
* connect: 4798 µs
* app_connect: 0 µs
* pre_transfer: 4912 µs
* start_transfer: 9126 µs
* total: 9171 µs
*
* ------------------------------------------------------------------------------
* Executing entry 2
*
* Cookie store:
* #HttpOnly_localhost FALSE / FALSE 0 x-session-id s%3A_l88C6GKbPeC5YuDLraWARY32NB3bP-l.T%2BViEW%2BqMrmLZDqwzDxtEbdtW67lCKt0jGvvlfqls%2FI
*
* Request:
* GET http://localhost:3000/not-found
*
* Request can be run with the following curl command:
* curl --cookie 'x-session-id=s%3A_l88C6GKbPeC5YuDLraWARY32NB3bP-l.T%2BViEW%2BqMrmLZDqwzDxtEbdtW67lCKt0jGvvlfqls%2FI' 'http://localhost:3000/not-found'
*
** Found bundle for host: 0x60000340c930 [serially]
** Can not multiplex, even if we wanted to
** Re-using existing connection #0 with host localhost
> GET /not-found HTTP/1.1
> Host: localhost:3000
> Accept: */*
> Cookie: x-session-id=s%3A_l88C6GKbPeC5YuDLraWARY32NB3bP-l.T%2BViEW%2BqMrmLZDqwzDxtEbdtW67lCKt0jGvvlfqls%2FI
> User-Agent: hurl/4.0.0
>
* Request body:
*
** Connection #0 to host localhost left intact
* Response: (received 2217 bytes in 5 ms)
*
< HTTP/1.1 404 Not Found
< Content-Type: text/html; charset=utf-8
< Content-Length: 2217
< Date: Wed, 26 Jul 2023 13:19:45 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Response body:
* <!doctype html>
* <html lang="en">
...
* <h3>Not Found</h3>
* <h4>404</h4>
...
* </html>
*
* Timings:
* begin: 2023-07-26 13:19:45.390823 UTC
* end: 2023-07-26 13:19:45.395983 UTC
* namelookup: 44 µs
* connect: 0 µs
* app_connect: 0 µs
* pre_transfer: 126 µs
* start_transfer: 5100 µs
* total: 5124 µs
*
...
--very-verbose
output is much more verbose; with body request and response, libcurl
logs and response timings
are displayed.
If you have a lot of entries (request / response pairs) in your Hurl file, using --verbose
or --very-verbose
can produce a lot of logs and can be difficult to analyse. Instead of passing options to the command line, you can
use an [Options]
section that will activate logs only for the specified entry:
# Checking our home page:
# ...
# Check that we have a 404 response for broken links:
# ...
# Check our health API:
# ...
# Check search API:
GET http://localhost:3000/api/search
[Options]
verbose: true
[QueryStringParams]
q: 1982
sort: name
HTTP 200
# ...
And run it without --verbose
option:
$ hurl --no-output basic.hurl
* ------------------------------------------------------------------------------
* Executing entry 4
*
* Entry options:
* verbose: true
*
* Cookie store:
* #HttpOnly_localhost FALSE / FALSE 0 x-session-id s%3Aq_5wf1l2wBQ_96y6kpLeR0J4zLJF34EZ.n%2Bu1UJPqK0Ih2tz3Dd6w2kXAuufueT6HQDekBPtHhbc
*
* Request:
* GET http://localhost:3000/api/search
* [QueryStringParams]
* q: 1982
* sort: name
*
* Request can be run with the following curl command:
* curl --cookie 'x-session-id=s%3Aq_5wf1l2wBQ_96y6kpLeR0J4zLJF34EZ.n%2Bu1UJPqK0Ih2tz3Dd6w2kXAuufueT6HQDekBPtHhbc' 'http://localhost:3000/api/search?q=1982&sort=name'
*
> GET /api/search?q=1982&sort=name HTTP/1.1
> Host: localhost:3000
> Accept: */*
> Cookie: x-session-id=s%3Aq_5wf1l2wBQ_96y6kpLeR0J4zLJF34EZ.n%2Bu1UJPqK0Ih2tz3Dd6w2kXAuufueT6HQDekBPtHhbc
> User-Agent: hurl/4.0.0
>
* Response: (received 1447 bytes in 0 ms)
*
< HTTP/1.1 200 OK
< Cache-control: no-store
< Content-Type: application/json; charset=utf-8
< Content-Length: 1447
< Date: Wed, 26 Jul 2023 13:29:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
*
When you’ve asserts errors, the analysis can be difficult because you don’t have a lot of information apart of the expected values:
$ hurl --test basic.hurl
basic.hurl: Running [1/1]
error: Assert failure
--> basic.hurl:47:0
|
47 | jsonpath "$[0].name" == "Robocop"
| actual: string <Blade Runner>
| expected: string <Robocop>
|
basic.hurl: Failure (4 request(s) in 16 ms)
--------------------------------------------------------------------------------
Executed files: 1
Succeeded files: 0 (0.0%)
Failed files: 1 (100.0%)
Duration: 17 ms
With --error-format
option, you can opt in for a longer error description in case of error assert. On any error,
we’ll get the response headers and body. This is useful to see the expected values, especially in CI/CD
context when you’ve to analyse past executed tests.
$ hurl --error-format long --test basic.hurl
basic.hurl: Running [1/1]
HTTP/1.1 200
Cache-control: no-store
Content-Type: application/json; charset=utf-8
Content-Length: 1447
Date: Wed, 26 Jul 2023 14:14:00 GMT
Connection: keep-alive
Keep-Alive: timeout=5
[{"name":"Blade Runner","url":"/movies/blade-runner","director":"Ridley Scott","release_date":"1982-06-25","actors":["Harrison Ford","Rutger Hauer","Sean Young","Edward James Olmos"],"artwork":"/img/blade-runner-800x1200.webp","artwork_128":"/img/blade-runner-128x192.webp"},{"name":"Conan the Barbarian","url":"/movies/conan-the-barbarian","director":"John Milius","release_date":"1982-05-14","actors":["Arnold Schwarzenegger","James Earl Jones","Sandahl Bergman","Ben Davidson","Cassandra Gaviola","Gerry Lopez","Mako","Valerie Quennessen","William Smith","Max von Sydow"],"artwork":"/img/conan-the-barbarian-800x1200.webp","artwork_128":"/img/conan-the-barbarian-128x192.webp"},{"name":"The Dark Crystal","url":"/movies/the-dark-crystal","director":"Jim Henson","release_date":"1982-12-17","actors":["Stephen Garlick","Lisa Maxwell","Billie Whitelaw","Percy Edwards"],"artwork":"/img/the-dark-crystal-800x1200.webp","artwork_128":"/img/the-dark-crystal-128x192.webp"},{"name":"The Thing","url":"/movies/the-thing","director":"John Carpenter","release_date":"1982-06-25","actors":["Kurt Russell"],"artwork":"/img/the-thing-800x1200.webp","artwork_128":"/img/the-thing-128x192.webp"},{"name":"Tron","url":"/movies/tron","director":"Steven Lisberger","release_date":"1982-07-09","actors":["Jeff Bridges","Bruce Boxleitner","David Warner","Cindy Morgan","Barnard Hughes"],"artwork":"/img/tron-800x1200.webp","artwork_128":"/img/tron-128x192.webp"}]
error: Assert failure
--> basic.hurl:47:0
|
47 | jsonpath "$[0].name" == "Robocop"
| actual: string <Blade Runner>
| expected: string <Robocop>
|
basic.hurl: Failure (4 request(s) in 23 ms)
We can run the whole Hurl file request by request, with the --interactive
option:
$ hurl --verbose --interactive basic.hurl
* Options:
* fail fast: true
* insecure: false
* follow redirect: false
* max redirect: 50
Interactive mode:
Next request:
GET http://localhost:8080
Press Q (Quit) or C (Continue)
* ------------------------------------------------------------------------------
* Executing entry 1
*
* Cookie store:
*
* Request:
* GET http://localhost:3000
*
* Request can be run with the following curl command:
* curl 'http://localhost:3000'
*
> GET / HTTP/1.1
> Host: localhost:3000
> Accept: */*
> User-Agent: hurl/4.0.0
>
* Response: (received 9564 bytes in 11 ms)
*
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 9564
< Set-Cookie: x-session-id=s%3AEE3wsnrgUPSyAkgJZGa3jMWk7xmOtv4E.kXQpkmNBXnFOqmeSssqXnecF4qqv1D7bKu3rpbEJxmQ; Path=/; HttpOnly; SameSite=Strict
< Date: Wed, 26 Jul 2023 13:16:39 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
*
Interactive mode:
Next request:
GET http://localhost:8080/not-found
Press Q (Quit) or C (Continue)
We can also run our file to only output HTTP headers, with -i/--include
option.
In this mode, headers of the last entry are displayed:
$ hurl -i basic.hurl
HTTP/1.1 200
Cache-control: no-store
Content-Type: application/json; charset=utf-8
Content-Length: 1447
Date: Wed, 26 Jul 2023 14:58:27 GMT
Connection: keep-alive
Keep-Alive: timeout=5
[{"name":"Blade Runner","url":"/movies/blade-runner","director":"Ridley Scott","release_date":"1982-06-25",...
If you want to inspect any entry other than the last one, you can run your test to a
given entry with the --to-entry
option, starting at index 1:
$ hurl -i --to-entry 2 basic.hurl
HTTP/1.1 404
Content-Type: text/html; charset=utf-8
Content-Length: 2217
Date: Wed, 26 Jul 2023 14:59:57 GMT
Connection: keep-alive
Keep-Alive: timeout=5
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title></title>
...
</body>
</html>
Finally, you can use a proxy between Hurl and your server to inspect requests and responses.
For instance, with mitmproxy:
First, launch mitmproxy, it will listen to connections on 8888 port
$ mitmweb -p 8888 --web-port 8889 --web-open-browser
Web server listening at http://127.0.0.1:8889/
Proxy server listening at http://*:8888
Then, run Hurl with -x/--proxy
option
$ hurl --proxy localhost:8888 basic.hurl
The web interface of mitmproxy allows you to inspect, intercept any requests run by Hurl, and see the returned response to Hurl.