Update docs for Hurl 2.0.0.

This commit is contained in:
jcamiel 2022-12-19 21:30:08 +01:00
parent a9e7b85262
commit 42ef4125fe
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
30 changed files with 1735 additions and 785 deletions

325
README.md
View File

@ -12,21 +12,24 @@
Hurl is a command line tool that runs <b>HTTP requests</b> defined in a simple <b>plain text format</b>.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very
versatile: it can be used for <b>fetching data</b>, <b>testing HTTP</b> sessions and testing <b>XML / JSON APIs</b>.
versatile: it can be used for both <b>fetching data</b> and <b>testing HTTP</b> sessions.
Hurl makes it easy to work with <b>HTML</b> content, <b>REST / SOAP / GraphQL</b> APIs, or any other <b>XML / JSON</b> based APIs.
```hurl
# Get home:
GET https://example.org
HTTP/1.1 200
HTTP 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: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
Chaining multiple requests is easy:
@ -55,7 +58,7 @@ POST https://example.org/api/tests
"evaluate": true
}
HTTP/1.1 200
HTTP 200
[Asserts]
header "X-Frame-Options" == "SAMEORIGIN"
jsonpath "$.status" == "RUNNING" # Check the status code
@ -68,12 +71,27 @@ jsonpath "$.id" matches /\d{4}/ # Check the format of the id
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
```
and even SOAP APIs
<b>GraphQL</b>
~~~hurl
POST https://example.org/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
HTTP 200
~~~
and even <b>SOAP APIs</b>
```hurl
POST https://example.org/InStock
@ -88,8 +106,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</m:GetStockPrice>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
Hurl can also be used to performance test HTTP endpoints:
@ -97,7 +114,7 @@ Hurl can also be used to performance test HTTP endpoints:
```hurl
GET https://example.org/api/v1/pets
HTTP/1.0 200
HTTP 200
[Asserts]
duration < 1000 # Duration in ms
```
@ -107,7 +124,7 @@ And response bytes
```hurl
GET https://example.org/data.tar.gz
HTTP/1.0 200
HTTP 200
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
@ -138,7 +155,7 @@ POST https://hurl.dev/api/feedback
"name": "John Doe",
"feedback": "Hurl is awesome !"
}
HTTP/1.1 200
HTTP 200
```
# Resources
@ -164,7 +181,9 @@ Table of Contents
* [Sending HTML Form Data](#sending-html-form-data)
* [Sending Multipart Form Data](#sending-multipart-form-data)
* [Posting a JSON Body](#posting-a-json-body)
* [Templating a JSON / XML Body](#templating-a-json--xml-body)
* [Templating a JSON Body](#templating-a-json-body)
* [Templating a XML Body](#templating-a-xml-body)
* [Using GraphQL Query](#using-graphql-query)
* [Testing Response](#testing-response)
* [Testing Response Headers](#testing-response-headers)
* [Testing REST APIs](#testing-rest-apis)
@ -172,6 +191,7 @@ Table of Contents
* [Testing Set-Cookie Attributes](#testing-set-cookie-attributes)
* [Testing Bytes Content](#testing-bytes-content)
* [Others](#others)
* [HTTP Version](#http-version)
* [Polling and Retry](#polling-and-retry)
* [Testing Endpoint Performance](#testing-endpoint-performance)
* [Using SOAP APIs](#using-soap-apis)
@ -354,15 +374,11 @@ file,data.json;
[Doc](https://hurl.dev/docs/request.html#file-body)
### Templating a JSON / XML Body
### Templating a JSON Body
Using templates with [JSON body] or [XML body] is not currently supported in Hurl.
Besides, you can use templates in [multiline string body] with variables to send a JSON or XML body:
~~~hurl
```hurl
PUT https://example.org/api/hits
Content-Type: application/json
```
{
"key0": "{{a_string}}",
"key1": {{a_bool}},
@ -370,7 +386,6 @@ Content-Type: application/json
"key3": {{a_number}}
}
```
~~~
Variables can be initialized via command line:
@ -393,8 +408,67 @@ Resulting in a PUT request with the following JSON body:
}
```
[Doc](https://hurl.dev/docs/templates.html)
### 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:
~~~hurl
POST https://example.org/echo/post/xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<Request>
<Login>{{login}}</Login>
<Password>{{password}}</Password>
</Request>
```
~~~
[Doc](https://hurl.dev/docs/request.html#multiline-string-body)
### Using GraphQL Query
A simple GraphQL query:
~~~hurl
POST https://example.org/starwars/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
~~~
A GraphQL query with variables:
~~~hurl
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](https://hurl.dev/docs/request.html#graphql-body)
## Testing Response
### Testing Response Headers
@ -404,7 +478,7 @@ Use implicit response asserts to test header values:
```hurl
GET https://example.org/index.html
HTTP/1.0 200
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
```
@ -417,7 +491,7 @@ Or use explicit response asserts with [predicates]:
```hurl
GET https://example.org
HTTP/1.1 302
HTTP 302
[Asserts]
header "Location" contains "www.example.net"
```
@ -433,7 +507,7 @@ Asserting JSON body response (node values, collection count etc...) with [JSONPa
GET https://example.org/order
screencapability: low
HTTP/1.1 200
HTTP 200
[Asserts]
jsonpath "$.validated" == true
jsonpath "$.userInfo.firstName" == "Franck"
@ -453,7 +527,7 @@ Testing status code:
```hurl
GET https://example.org/order/435
HTTP/1.1 200
HTTP 200
```
[Doc](https://hurl.dev/docs/asserting-response.html#version-status)
@ -462,7 +536,7 @@ HTTP/1.1 200
GET https://example.org/order/435
# Testing status code is in a 200-300 range
HTTP/1.1 *
HTTP *
[Asserts]
status >= 200
status < 300
@ -476,7 +550,7 @@ status < 300
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
Content-Type: text/html; charset=UTF-8
[Asserts]
@ -495,7 +569,7 @@ xpath "string(//div[1])" matches /Hello.*/
```hurl
GET http://myserver.com/home
HTTP/1.0 200
HTTP 200
[Asserts]
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
@ -525,6 +599,17 @@ sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
## Others
### HTTP Version
Testing HTTP version (1.0, 1.1 or 2):
```hurl
GET https://example.org/order/435
HTTP/2 200
```
[Doc](https://hurl.dev/docs/asserting-response.html#version-status)
### Polling and Retry
Retry request on any errors (asserts, captures, status code, runtime etc...):
@ -533,7 +618,7 @@ Retry request on any errors (asserts, captures, status code, runtime etc...):
# Create a new job
POST https://api.example.org/jobs
HTTP/* 201
HTTP 201
[Captures]
job_id: jsonpath "$.id"
[Asserts]
@ -545,7 +630,7 @@ GET https://api.example.org/jobs/{{job_id}}
[Options]
retry: true
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.state" == "COMPLETED"
```
@ -559,7 +644,7 @@ jsonpath "$.state" == "COMPLETED"
```hurl
GET https://sample.org/helloworld
HTTP/* *
HTTP *
[Asserts]
duration < 1000 # Check that response time is less than one second
```
@ -582,7 +667,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
[Doc](https://hurl.dev/docs/request.html#xml-body)
@ -592,14 +677,14 @@ HTTP/1.1 200
```hurl
GET https://example.org
HTTP/* 200
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
HTTP 302
```
[Doc](https://hurl.dev/docs/capturing-response.html#xpath-capture)
@ -609,7 +694,7 @@ HTTP/* 302
```hurl
GET https://example.org/data.bin
HTTP/* 200
HTTP 200
[Asserts]
bytes startsWith hex,efbbbf;
```
@ -631,9 +716,9 @@ hurl - run and test HTTP requests.
## Description
**Hurl** is an HTTP client that performs HTTP requests defined in a simple plain text format.
**Hurl** is a command line tool that runs HTTP requests defined in a simple plain text format.
Hurl is very versatile. It enables chaining HTTP requests, capturing values from HTTP responses, and making assertions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very versatile, it can be used for fetching data and testing HTTP sessions: HTML content, REST / SOAP / GraphQL APIs, or any other XML / JSON based APIs.
```shell
$ hurl session.hurl
@ -690,11 +775,11 @@ GET http:/example.org/endpoint2
A value from an HTTP response can be-reused for successive HTTP requests.
A typical example occurs with csrf tokens.
A typical example occurs with CSRF tokens.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -708,28 +793,28 @@ More information on captures can be found here [https://hurl.dev/docs/capturing-
### Asserts
The HTTP response defined in the Hurl session are used to make asserts.
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
At the minimum, the response includes the asserts on the HTTP version and status code.
At the minimum, response includes assert on the HTTP status code.
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
```
It can also include asserts on the response headers
```hurl
GET http:/google.com
HTTP/1.1 301
Location: http://www.google.com
GET http:/example.org
HTTP 301
Location: http://www.example.org
```
Explicit asserts can be included by combining a query and a predicate
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
[Asserts]
xpath "string(//title)" == "301 Moved"
```
@ -753,56 +838,61 @@ $ hurl --location foo.hurl
will follow redirection for each entry in `foo.hurl`. You can also define an option only for a particular entry with an `[Options]` section. For instance, this Hurl file:
```hurl
GET https://google.com
HTTP/* 301
GET https://example.org
HTTP 301
GET https://google.com
GET https://example.org
[Options]
location: true
HTTP/* 200
HTTP 200
```
will follow a redirection only for the second entry.
Option | Description
--- | ---
<a href="#cacert" id="cacert"><code>--cacert</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br/>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br/>
<a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br/>
<a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br/>
<a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br/><br/>See also [`-m, --max-time`](#max-time) option.<br/>
<a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br/><br/>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br/>The file will be written using the Netscape cookie file format.<br/><br/>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br/>By default, Hurl exits after an assert error in the HTTP response.<br/><br/>Note that this option does not affect the behavior with multiple input Hurl files.<br/><br/>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br/>
<a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br/>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br/>
<a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br/>
<a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br/><br/>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br/>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br/>
<a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br/>
<a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br/>
<a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br/>
<a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br/>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br/>
<a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br/>
<a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br/>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br/>
<a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br/><br/>See also [`--connect-timeout`](#connect-timeout) option.<br/>
<a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br/>
<a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br/>
<a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br/>Override value from Environment variable no_proxy.<br/>
<a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br/>
<a href="#proxy" id="proxy"><code>-x, --proxy [protocol://]host[:port]</code></a> | Use the specified proxy.<br/>
<a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br/><br/>If the FILE report already exists, it will be updated with the new test results.<br/>
<a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br/><br/>If the HTML report already exists, it will be updated with the new test results.<br/>
<a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br/>
<a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br/>
<a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br/>
<a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br/>
<a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br/>Ignore the remaining of the file. It is useful for debugging a session.<br/>
<a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br/>
<a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br/>
<a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br/>
<a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br/><br/>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br/><br/>Note that defining a variable twice produces an error.<br/>
<a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br/>Useful for debugging.<br/><br/>A line starting with '>' means data sent by Hurl.<br/>A line staring with '<' means data received by Hurl.<br/>A line starting with '*' means additional info provided by Hurl.<br/><br/>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br/>
<a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br/><br/>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br/>
<a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br/>
<a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br/>
| Option | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <a href="#cacert" id="cacert"><code>--cacert &lt;FILE&gt;</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br> |
| <a href="#cert" id="cert"><code>-E, --cert &lt;CERTIFICATE[:PASSWORD]&gt;</code></a> | Client certificate file and password.<br><br>See also [`--key`](#key).<br> |
| <a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br> |
| <a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br> |
| <a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br><br>See also [`-m, --max-time`](#max-time).<br> |
| <a href="#connect-to" id="connect-to"><code>--connect-to &lt;HOST1:PORT1:HOST2:PORT2&gt;</code></a> | For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. This option can be used several times in a command line.<br><br>See also [`--resolve`](#resolve).<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br> |
| <a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br> |
| <a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br><br>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br> |
| <a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br> |
| <a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br> |
| <a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br> |
| <a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br> |
| <a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br> |
| <a href="#key" id="key"><code>--key &lt;KEY&gt;</code></a> | Private key file name.<br> |
| <a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br> |
| <a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br><br>See also [`--connect-timeout`](#connect-timeout).<br> |
| <a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br> |
| <a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br> |
| <a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br>Override value from Environment variable no_proxy.<br> |
| <a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br> |
| <a href="#proxy" id="proxy"><code>-x, --proxy &lt;[PROTOCOL://]HOST[:PORT]&gt;</code></a> | Use the specified proxy.<br> |
| <a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br><br>If the FILE report already exists, it will be updated with the new test results.<br> |
| <a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br><br>If the HTML report already exists, it will be updated with the new test results.<br> |
| <a href="#resolve" id="resolve"><code>--resolve &lt;HOST:PORT:ADDR&gt;</code></a> | Provide a custom address for a specific host and port pair. Using this, you can make the Hurl requests(s) use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative provided on the command line.<br> |
| <a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br> |
| <a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br> |
| <a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br> |
| <a href="#ssl-no-revoke" id="ssl-no-revoke"><code>--ssl-no-revoke</code></a> | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.<br> |
| <a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br> |
| <a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br>Ignore the remaining of the file. It is useful for debugging a session.<br> |
| <a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br> |
| <a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br> |
| <a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br> |
| <a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br><br>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br><br>Note that defining a variable twice produces an error.<br> |
| <a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br>Useful for debugging.<br><br>A line starting with '>' means data sent by Hurl.<br>A line staring with '<' means data received by Hurl.<br>A line starting with '*' means additional info provided by Hurl.<br><br>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br> |
| <a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br><br>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br> |
| <a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br> |
| <a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br> |
## Environment
@ -810,23 +900,23 @@ Environment variables can only be specified in lowercase.
Using an environment variable to set the proxy has the same effect as using the [`-x, --proxy`](#proxy) option.
Variable | Description
--- | ---
`http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br/>
`https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br/>
`all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br/>
`no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br/>
`HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br/>
`NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br/>
| Variable | Description |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br> |
| `https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br> |
| `all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br> |
| `no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br> |
| `HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br> |
| `NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br> |
## Exit Codes
Value | Description
--- | ---
`1` | Failed to parse command-line options.<br/>
`2` | Input File Parsing Error.<br/>
`3` | Runtime error (such as failure to connect to host).<br/>
`4` | Assert Error.<br/>
| Value | Description |
|-------|---------------------------------------------------------|
| `1` | Failed to parse command-line options.<br> |
| `2` | Input File Parsing Error.<br> |
| `3` | Runtime error (such as failure to connect to host).<br> |
| `4` | Assert Error.<br> |
## WWW
@ -843,12 +933,12 @@ curl(1) hurlfmt(1)
### Linux
Precompiled binary is available at [hurl-1.8.0-x86_64-linux.tar.gz]:
Precompiled binary is available at [hurl-2.0.0-x86_64-linux.tar.gz]:
```shell
$ INSTALL_DIR=/tmp
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-2.0.0:$PATH
```
#### Debian / Ubuntu
@ -856,8 +946,8 @@ $ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
For Debian / Ubuntu, Hurl can be installed using a binary .deb file provided in each Hurl release.
```shell
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl_1.8.0_amd64.deb
$ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl_2.0.0_amd64.deb
$ sudo apt update && apt install ./hurl_2.0.0_amd64.deb
```
#### Arch Linux / Manjaro
@ -870,7 +960,7 @@ $ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
### macOS
Precompiled binary is available at [hurl-1.8.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-1.8.0-arm64-macos.tar.gz] for ARM CPUS.
Precompiled binary is available at [hurl-2.0.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-2.0.0-arm64-macos.tar.gz] for ARM CPUS.
#### Homebrew
@ -894,11 +984,11 @@ $ sudo pkg install hurl
#### Zip File
Hurl can be installed from a standalone zip file [hurl-1.8.0-win64.zip]. You will need to update your `PATH` variable.
Hurl can be installed from a standalone zip file [hurl-2.0.0-win64.zip]. You will need to update your `PATH` variable.
#### Installer
An installer [hurl-1.8.0-win64-installer.exe] is also available.
An installer [hurl-2.0.0-win64-installer.exe] is also available.
#### Chocolatey
@ -1011,7 +1101,7 @@ Please follow the [contrib on Windows section].
[libcurl]: https://curl.se/libcurl/
[JSON body]: https://hurl.dev/docs/request.html#json-body
[XML body]: https://hurl.dev/docs/request.html#xml-body
[multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
[XML multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
[predicates]: https://hurl.dev/docs/asserting-response.html#predicates
[JSONPath]: https://goessner.net/articles/JsonPath/
[Basic authentication]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme
@ -1022,12 +1112,13 @@ Please follow the [contrib on Windows section].
[curl]: https://curl.se
[entry]: https://hurl.dev/docs/entry.html
[`--test` option]: https://hurl.dev/docs/manual.html#test
[Hurl templates]: https://hurl.dev/docs/templates.html
[GitHub]: https://github.com/Orange-OpenSource/hurl
[hurl-1.8.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64.zip
[hurl-1.8.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64-installer.exe
[hurl-1.8.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-macos.tar.gz
[hurl-1.8.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-arm64-macos.tar.gz
[hurl-1.8.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz
[hurl-2.0.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64.zip
[hurl-2.0.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64-installer.exe
[hurl-2.0.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-macos.tar.gz
[hurl-2.0.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-arm64-macos.tar.gz
[hurl-2.0.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[`hurl-bin` package]: https://aur.archlinux.org/packages/hurl-bin/
[install]: https://www.rust-lang.org/tools/install

View File

@ -45,7 +45,7 @@ def process_table(doc: MarkdownDoc, nodes: List[Node], col_name: str) -> None:
new_nodes = [
Whitespace(content="\n"),
Paragraph(content=f"{col_name} | Description\n --- | --- \n"),
Paragraph(content=f"| {col_name} | Description |\n| --- | --- |\n"),
]
h3s = [n for n in nodes if isinstance(n, Header)]
@ -78,9 +78,9 @@ def process_table(doc: MarkdownDoc, nodes: List[Node], col_name: str) -> None:
paragraphs = doc.slice(first_p, next_node)
paragraphs_contents = [p.content for p in paragraphs if p.content]
description = "".join(paragraphs_contents)
description = description.replace("\n", "<br/>")
description = description.replace("\n", "<br>")
new_node = Paragraph(content=f"{name} | {description}\n")
new_node = Paragraph(content=f"| {name} | {description} |\n")
new_nodes.append(new_node)
# Delete all previous options:

View File

@ -4,15 +4,15 @@ Hurl is a command line tool written in Rust that runs <b>HTTP requests</b> defin
The `@orangeopensource/hurl` package allows JavaScript developers to use Hurl in npm scripts.
Hurl can perform requests, capture values and evaluate queries on headers and body response. Hurl is very
versatile: it can be used for both <b>fetching data</b> and <b>testing HTTP</b> sessions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very
versatile, it can be used for <b>fetching data</b> and <b>testing HTTP</b> sessions: <b>HTML</b> content, <b>REST / SOAP / GraphQL</b> APIs, or any other <b>XML / JSON</b> based APIs.
```hurl
# Get home:
GET https://example.net
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
@ -20,23 +20,23 @@ csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
POST https://example.net/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
Hurl can run HTTP requests but can also be used to <b>test HTTP responses</b>.
Different types of queries and predicates are supported, from [XPath](https://en.wikipedia.org/wiki/XPath) and
[JSONPath](https://goessner.net/articles/JsonPath/) on body response, to assert on status code and response headers.
It is well adapted for <b>REST / JSON apis</b>
It is well adapted for <b>REST / JSON APIs</b>
```hurl
POST https://api.example.net/tests
POST https://example.org/api/tests
{
"id": "4568",
"evaluate": true
}
HTTP/1.1 200
HTTP 200
[Asserts]
header "X-Frame-Options" == "SAMEORIGIN"
jsonpath "$.status" == "RUNNING" # Check the status code
@ -44,16 +44,49 @@ jsonpath "$.tests" count == 25 # Check the number of items
jsonpath "$.id" matches /\d{4}/ # Check the format of the id
```
and <b>HTML content</b>
<b>HTML content</b>
```hurl
GET https://example.net
GET https://example.org
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
```
<b>GraphQL</b>
~~~hurl
POST https://example.org/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
HTTP 200
~~~
and even <b>SOAP APIs</b>
```hurl
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
```
## Installation
```

View File

@ -1,32 +1,60 @@
# Asserting Response
## Version - Status
## Asserts
Asserts are used to test various properties of an HTTP response. Asserts can be implicits (such as version, status,
headers) or explicit within an `[Asserts]` section.
```hurl
GET https://api/example.org/cats
HTTP 200
Content-Type: application/json; charset=utf-8 # Implicit assert on Content-Type Hedaer
[Asserts] # Explicit asserts section
bytes count == 120
header "Content-Type" contains "utf-8"
jsonpath "$.cats" count == 49
jsonpath "$.cats[0].name" == "Felix"
jsonpath "$.cats[0].lives" == 9
```
## Implicit asserts
### Version - Status
Expected protocol version and status code of the HTTP response.
Protocol version is one of `HTTP/1.0`, `HTTP/1.1`, `HTTP/2` or
`HTTP/*`; `HTTP/*` describes any version. Note that there are no status text following the status code.
`HTTP`; `HTTP` describes any version. Note that there are no status text following the status code.
```hurl
GET https://example.org/404.html
HTTP/1.1 404
HTTP 404
```
Wildcard keywords (`HTTP/*`, `*`) can be used to disable tests on protocol version and status:
Wildcard keywords `HTTP` and `*` can be used to disable tests on protocol version and status:
```hurl
GET https://example.org/api/pets
HTTP/1.0 *
HTTP *
# Check that response status code is > 400 and <= 500
[Asserts]
status > 400
status <= 500
```
While `HTTP/1.0`, `HTTP/1.1` and `HTTP/2` explicitly check HTTP version:
## Headers
```hurl
# Check that our server responds with HTTP/2
GET https://example.org/api/pets
HTTP/2 200
```
### Headers
Optional list of the expected HTTP response headers that must be in the received response.
@ -43,7 +71,7 @@ POST https://example.org/login
user: toto
password: 12345678
HTTP/1.1 302
HTTP 302
Location: https://example.org/home
```
@ -71,7 +99,7 @@ You can either test the two header values:
GET https://example.org/index.html
Host: example.net
HTTP/1.0 200
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
```
@ -82,7 +110,7 @@ Or only one:
GET https://example.org/index.html
Host: example.net
HTTP/1.0 200
HTTP 200
Set-Cookie: theme=light
```
@ -91,9 +119,9 @@ if you want to test header value with [predicates] (like `startsWith`, `contains
you can use the explicit [header assert].
## Asserts
## Explicit asserts
Optional list of assertions on the HTTP response. Assertions can describe checks
Optional list of assertions on the HTTP response within an `[Asserts]` section. Assertions can describe checks
on status code, on the received body (or part of it) and on response headers.
Structure of an assert:
@ -132,31 +160,33 @@ is shared with [captures], and can be one of :
- [`variable`](#variable-assert)
- [`duration`](#duration-assert)
Queries, as in captures, can be refined with subqueries. [`count`] subquery can be used
with various predicates to add tests on collections sizes.
Queries are used to extract data from the HTTP response. Queries, in asserts and in captures, can be refined with [filters], like
[`count`][count] to add tests on collections sizes.
### Predicates
Predicates consist of a predicate function, and a predicate value. Predicate functions are:
Predicates consist of a predicate function and a predicate value. Predicate functions are:
- `==` (`equals`): check equality of query and predicate value
- `!=`: check that query and predicate value are different
- `>` (`greaterThan`): check that query number is greater than predicate value
- `>=` (`greaterThanOrEquals`): check that query number is greater than or equal to the predicate value
- `<` (`lessThan`): check that query number is less than that predicate value
- `<=` (`lessThanOrEquals`): check that query number is less than or equal to the predicate value
- `startsWith`: check that query starts with the predicate value (query can return a string or a binary content)
- `endsWith`: check that query ends with the predicate value (query can return a string or a binary content)
- `contains`: check that query contains the predicate value (query can return a string or a binary content)
- `includes`: check that query collections includes the predicate value
- `matches`: check that query string matches the regex pattern described by the predicate value
- `exists`: check that query returns a value
- `isInteger`: check that query returns an integer
- `isFloat`: check that query returns a float
- `isBoolean`: check that query returns a boolean
- `isString`: check that query returns a string
- `isCollection`: check that query returns a collection
| Predicate | Description | Example |
|--------------------|-------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| __`==`__ | Query and predicate value are equals | `jsonpath "$.book" == "Dune"` |
| __`!=`__ | Query and predicate value are different | `jsonpath "$.color" != "red"` |
| __`>`__ | Query number is greater than predicate value | `jsonpath "$.year" > 1978` |
| __`>=`__ | Query number is greater than or equal to the predicate value | `jsonpath "$.year" >= 1978` |
| __`<`__ | Query number is less than that predicate value | `jsonpath "$.year" < 1978` |
| __`<=`__ | Query number is less than or equal to the predicate value | `jsonpath "$.year" <= 1978` |
| __`startsWith`__ | Query starts with the predicate value<br>Value is string or a binary content | `jsonpath "$.movie" startsWith "The"`<br><br>`bytes startsWith hex,efbbbf;` |
| __`endsWith`__ | Query ends with the predicate value<br>Value is string or a binary content | `jsonpath "$.movie" endsWith "Back"`<br><br>`bytes endsWith hex,ab23456;` |
| __`contains`__ | Query contains the predicate value<br>Value is string or a binary content | `jsonpath "$.movie" contains "Empire"`<br><br>`bytes contains hex,beef;` |
| __`includes`__ | Query collections includes the predicate value | `jsonpath "$.nooks" includes "Dune"` |
| __`matches`__ | Part of the query string matches the regex pattern described by the predicate value | `jsonpath "$.release" matches "\\d{4}"`<br><br>`jsonpath "$.release" matches /\d{4}/` |
| __`exists`__ | Query returns a value | `jsonpath "$.book" exists` |
| __`isInteger`__ | Query returns an integer | `jsonpath "$.count" isInteger` |
| __`isFloat`__ | Query returns a float | `jsonpath "$.height" isFloat` |
| __`isBoolean`__ | Query returns a boolean | `jsonpath "$.suceeded" isBoolean` |
| __`isString`__ | Query returns a string | `jsonpath "$.name" isString` |
| __`isCollection`__ | Query returns a collection | `jsonpath "$.books" isCollection` |
Each predicate can be negated by prefixing it with `not` (for instance, `not contains` or `not exists`)
@ -170,7 +200,7 @@ Each predicate can be negated by prefixing it with `not` (for instance, `not con
</div>
A predicate values is typed, and can be a string, a boolean, a number, a bytestream, `null` or a collection. Note that
A predicate value is typed, and can be a string, a boolean, a number, a bytestream, `null` or a collection. Note that
`"true"` is a string, whereas `true` is a boolean.
For instance, to test the presence of a h1 node in an HTML response, the following assert can be used:
@ -178,7 +208,7 @@ For instance, to test the presence of a h1 node in an HTML response, the followi
```hurl
GET https://example.org/home
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "boolean(count(//h1))" == true
xpath "//h1" exists # Equivalent but simpler
@ -201,7 +231,7 @@ The following assert will check the value of the `data-visible` attribute:
```hurl
GET https://example.org/home
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//article/@data-visible)" == "true"
```
@ -209,14 +239,14 @@ xpath "string(//article/@data-visible)" == "true"
In this case, the XPath query `string(//article/@data-visible)` returns a string, so the predicate value must be a
string.
The predicate function `equals` can work with string, number or boolean while `matches`, `startWith` and `contains` work
only on string. If a query returns a number, a `contains` predicate will raise a runner error.
The predicate function `equals` can be used with string, numbers or booleans; `startWith` and `contains` can only
be used with strings and bytes, while `matches` only works on string. If a query returns a number, using a `matches` predicate will cause a runner error.
```hurl
# A really well tested web page...
GET https://example.org/home
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" contains "text/html"
header "Last-Modified" == "Wed, 21 Oct 2015 07:28:00 GMT"
@ -234,22 +264,23 @@ function and value.
```hurl
GET https://example.org
HTTP/1.1 *
HTTP *
[Asserts]
status < 300
```
### Header assert
Check the value of a received HTTP response header. Header assert consists of the keyword `header` followed by a predicate
function and value.
Check the value of a received HTTP response header. Header assert consists of the keyword `header` followed by the value
of the header, a predicate function and a predicate value.
```hurl
GET https://example.org
HTTP/1.1 302
HTTP 302
[Asserts]
header "Location" contains "www.example.net"
header "Last-Modified" matches /\d{2} [a-z-A-Z]{3} \d{4}/
```
If there are multiple headers with the same name, the header assert returns a collection, so `count`, `includes` can be
@ -261,7 +292,7 @@ Let's say we have this request and response:
> GET /hello HTTP/1.1
> Host: example.org
> Accept: */*
> User-Agent: hurl/1.8.0-SNAPSHOT
> User-Agent: hurl/2.0.0-SNAPSHOT
>
* Response: (received 12 bytes in 11 ms)
*
@ -279,7 +310,7 @@ One can use explicit header asserts:
```hurl
GET https://example.org/hello
HTTP/* 200
HTTP 200
[Asserts]
header "Vary" count == 2
header "Vary" includes "User-Agent"
@ -291,7 +322,7 @@ Or implicit header asserts:
```hurl
GET https://example.org/hello
HTTP/* 200
HTTP 200
Vary: User-Agent
Vary: Content-Type
```
@ -306,7 +337,7 @@ GET https://example.org/redirecting
[Options]
location: true
HTTP/* 200
HTTP 200
[Asserts]
url == "https://example.org/redirected"
```
@ -325,7 +356,7 @@ Cookie attributes value can be checked by using the following format:
```hurl
GET http://localhost:8000/cookies/set
HTTP/1.0 200
HTTP 200
# Explicit check of Set-Cookie header value. If the attributes are
# not in this exact order, this assert will fail.
@ -361,13 +392,11 @@ value. The encoding used to decode the body is based on the `charset` value in t
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Asserts]
body contains "<h1>Welcome!</h1>"
```
> Precise the encoding used to decode the text body.
### Bytes assert
Check the value of the received HTTP response body as a bytestream. Body assert
@ -376,9 +405,11 @@ consists of the keyword `bytes` followed by a predicate function and value.
```hurl
GET https://example.org/data.bin
HTTP/* 200
HTTP 200
[Asserts]
bytes startsWith hex,efbbbf;
bytes count == 12424
header "Content-Length" == "12424"
```
### XPath assert
@ -418,9 +449,8 @@ With Hurl, we can write multiple XPath asserts describing the DOM content:
```hurl
GET https://example.org
HTTP/1.1 200
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>
@ -446,7 +476,7 @@ This XML response can be tested with the following Hurl file:
```hurl
GET http://localhost:8000/assert-xpath
HTTP/1.0 200
HTTP 200
[Asserts]
xpath "string(//bk:book/bk:title)" == "Cheaper by the Dozen"
@ -467,7 +497,7 @@ namespaces.
### JSONPath assert
Check the value of a [JSONPath] query on the received HTTP body decoded as a JSON
document. Body assert consists of the keyword `jsonpath` followed by a predicate
document. JSONPath assert consists of the keyword `jsonpath` followed by a predicate
function and value.
Let's say we want to check this JSON response:
@ -501,7 +531,7 @@ With Hurl, we can write multiple JSONPath asserts describing the DOM content:
```hurl
GET http://httpbin.org/json
HTTP/1.1 200
HTTP 200
[Asserts]
jsonpath "$.slideshow.author" == "Yours Truly"
jsonpath "$.slideshow.slides[0].title" contains "Wonder"
@ -520,7 +550,7 @@ the readability:
```hurl
GET https://sample.org/hello
HTTP/1.0 200
HTTP 200
[Asserts]
# Predicate value with matches predicate:
jsonpath "$.date" matches "^\\d{4}-\\d{2}-\\d{2}$"
@ -537,11 +567,19 @@ Check that the HTTP received body, decoded as text, matches a regex pattern.
```hurl
GET https://sample.org/hello
HTTP/1.0 200
HTTP 200
[Asserts]
regex "^\\d{4}-\\d{2}-\\d{2}$" == "2018-12-31"
regex "^(\\d{4}-\\d{2}-\\d{2})$" == "2018-12-31"
# Same assert as previous using regex literals
regex /^(\d{4}-\d{2}-\d{2})$/ == "2018-12-31"
```
The regex pattern must have at least one capture group, otherwise the
assert will fail. The assertion is done on the captured group value. When the regex pattern is a double-quoted string,
metacharacters beginning with a backslash in the pattern (like `\d`, `\s`) must be escaped; literal pattern enclosed by
`/` can also be used to avoid metacharacters escaping.
### SHA-256 assert
Check response body [SHA-256] hash.
@ -549,7 +587,7 @@ Check response body [SHA-256] hash.
```hurl
GET https://example.org/data.tar.gz
HTTP/* *
HTTP 200
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
@ -561,7 +599,7 @@ Check response body [MD5] hash.
```hurl
GET https://example.org/data.tar.gz
HTTP/* *
HTTP 200
[Asserts]
md5 == hex,ed076287532e86365e841e92bfc50d8c;
```
@ -572,7 +610,8 @@ md5 == hex,ed076287532e86365e841e92bfc50d8c;
```hurl
# Test that the XML endpoint return 200 pets
GET https://example.org/api/pets
HTTP/* 200
HTTP 200
[Captures]
pets: xpath "//pets"
[Asserts]
@ -586,37 +625,11 @@ Check the total duration (sending plus receiving time) of the HTTP transaction.
```hurl
GET https://sample.org/helloworld
HTTP/1.0 200
HTTP 200
[Asserts]
duration < 1000 # Check that response time is less than one second
```
## Filters
Optionally, asserts can be refined using filters `count` and `regex`.
### Count filter
```hurl
GET https://pets.org/cats/cutest
HTTP/1.0 200
[Asserts]
jsonpath "$.cats" count == 12
```
### Regex filter
```hurl
GET https://pets.org/cats/cutest
HTTP/1.0 200
# Cat name are structured like this `meow + id`: for instance `meow123456`
[Asserts]
jsonpath "$.cats[0].name" regex /meow(\d+)/ == "123456"
```
## Body
Optional assertion on the received HTTP response body. Body section can be seen
@ -634,7 +647,7 @@ the body byte content to check.
# Get a doggy thing:
GET https://example.org/api/dogs/{{dog-id}}
HTTP/1.1 200
HTTP 200
{
"id": 0,
"name": "Frieda",
@ -645,12 +658,32 @@ HTTP/1.1 200
}
```
JSON response body can be seen as syntactic sugar of [multiline string body] with `json` identifier:
~~~hurl
# Get a doggy thing:
GET https://example.org/api/dogs/{{dog-id}}
HTTP 200
```json
{
"id": 0,
"name": "Frieda",
"picture": "images/scottish-terrier.jpeg",
"age": 3,
"breed": "Scottish Terrier",
"location": "Lisco, Alabama"
}
```
~~~
### XML body
~~~hurl
GET https://example.org/api/catalog
HTTP/1.1 200
HTTP 200
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="bk101">
@ -664,12 +697,34 @@ HTTP/1.1 200
</catalog>
~~~
XML response body can be seen as syntactic sugar of [multiline string body] with `xml` identifier:
~~~hurl
GET https://example.org/api/catalog
HTTP 200
```xml
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications with XML.</description>
</book>
</catalog>
```
~~~
### Multiline string body
~~~hurl
GET https://example.org/models
HTTP/1.1 200
HTTP 200
```
Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
@ -689,41 +744,28 @@ line3
```
~~~
is evaluated as "line1\nline2\nline3\n".
#### Oneline string body
For text based response body that do not contain newlines, one can use oneline string, started and ending with <code>&#96;</code>.
To construct an empty string :
~~~hurl
POST https://example.org/helloworld
~~~
```
```
HTTP 200
`Hello world!`
~~~
or
~~~
``````
~~~
Finally, multiline can be used without any newline:
~~~
```line```
~~~
is evaluated as "line".
### Base64 body
Base64 body assert starts with `base64,` and end with `;`. MIME's Base64 encoding
Base64 response body assert starts with `base64,` and end with `;`. MIME's Base64 encoding
is supported (newlines and white spaces may be present anywhere but are to be
ignored on decoding), and `=` padding characters might be added.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
base64,TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIG
FkaXBpc2NpbmcgZWxpdC4gSW4gbWFsZXN1YWRhLCBuaXNsIHZlbCBkaWN0dW0g
aGVuZHJlcml0LCBlc3QganVzdG8gYmliZW5kdW0gbWV0dXMsIG5lYyBydXRydW
@ -738,7 +780,7 @@ can be used. File body starts with `file,` and ends with `;``
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
file,data.bin;
```
@ -760,9 +802,11 @@ of all file nodes.
[XML]: https://en.wikipedia.org/wiki/XML
[Base64]: https://en.wikipedia.org/wiki/Base64
[`--file-root` option]: /docs/manual.md#file-root
[`count`]: /docs/capturing-response.md#count-subquery
[Javascript-like Regular expression syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
[MD5]: https://en.wikipedia.org/wiki/MD5
[SHA-256]: https://en.wikipedia.org/wiki/SHA-2
[options]: /docs/request.md#options
[`--location` option]: /docs/manual.md#location
[multiline string body]: #multiline-string-body
[filters]: /docs/filters.md
[count]: /docs/filters.md#count

250
docs/assets/cast/hurl.cast Normal file
View File

@ -0,0 +1,250 @@
{"version": 2, "width": 80, "height": 25, "timestamp": 1665239255, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color", "PROMPT": "%F{cyan}$%f "}}
[0.01621, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[0.027176, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[36m$\u001b[39m \u001b[K\u001b[?2004h"]
[0.51818, "o", "v"]
[0.667008, "o", "\bvi"]
[0.906839, "o", "m"]
[0.967164, "o", " "]
[1.297275, "o", "s"]
[1.509441, "o", "t"]
[1.688824, "o", "a"]
[1.824537, "o", "r"]
[2.318819, "o", "w"]
[2.527648, "o", "a"]
[2.708786, "o", "r"]
[2.798336, "o", "s"]
[3.247485, "o", "."]
[3.578633, "o", "h"]
[3.757589, "o", "u"]
[3.846672, "o", "r"]
[4.088367, "o", "l"]
[4.95909, "o", "\u001b[?2004l\r\r\n"]
[5.010246, "o", "\u001b[?1049h\u001b[>4;2m\u001b[?1h\u001b=\u001b[?2004h\u001b[?1004h\u001b[1;25r\u001b[?12h\u001b[?12l\u001b[22;2t\u001b[22;1t"]
[5.010721, "o", "\u001b[27m\u001b[29m\u001b[m\u001b[H\u001b[2J\u001b[?25l\u001b[25;1H\"starwars.hurl\" [New]"]
[5.015574, "o", "\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[3;1H\u001bPzz\u001b\\\u001b[0%m\u001b[6n\u001b[3;1H \u001b[1;1H\u001b[>c"]
[5.015583, "o", "\u001b]10;?\u0007\u001b]11;?\u0007"]
[5.016023, "o", "\u001b[1;1H\u001b[38;5;242m 1 \u001b[m\r\n\u001b[94m~ \u001b[3;1H~ \u001b[4;1H~ \u001b[5;1H~ \u001b[6;1H~ \u001b[7;1H~ \u001b[8;1H~ \u001b[9;1H~ \u001b[10;1H~ \u001b[11;1H~ \u001b[12;1H~ \u001b[13;1H~ "]
[5.016049, "o", " \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[17;1H~ \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ \u001b[21;1H~ \u001b[22;1H~ \u001b[23;1H~ \u001b[m\u001b[24;1H\u001b[38;5;238m\u001b[48;5;117m NORMAL "]
[5.016131, "o", "\u001b[m\u001b[38;5;252m\u001b[48;5;240m starwars.hurl \u001b[m\u001b[38;5;248m\u001b[48;5;238m unix | utf-8 | hurl \u001b[m\u001b[38;5;247m\u001b[48;5;240m 100% \u001b[m\u001b[38;5;238m\u001b[48;5;244m 0:0 \u001b[1;5H\u001b[?25h"]
[5.016144, "o", "\u001b[?12$p"]
[5.980583, "o", "\u001b[m\u001b[24;1H\u001b[38;5;238m\u001b[48;5;119m INSERT \u001b[m\u001b[69C\u001b[38;5;238m\u001b[48;5;244m1\u001b[1;5H\u001b[?25l\u001b[?25h"]
[6.339754, "o", "\u001b[?25l\u001b[mG\u001b[24;24H\u001b[38;5;252m\u001b[48;5;240m| + \u001b[m\u001b[48C\u001b[38;5;238m\u001b[48;5;244m1:2\u001b[1;6H\u001b[?25h"]
[6.426642, "o", "\u001b[?25l\u001b[mE\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[1;7H\u001b[?25h"]
[6.580036, "o", "\u001b[?25l\u001b[m\b\b\u001b[93mGET\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[1;8H\u001b[?25h"]
[6.656322, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[1;9H\u001b[?25h"]
[7.720245, "o", "\u001b[?25l\u001b[mh\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[1;10H\u001b[?25h"]
[7.959788, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[1;11H\u001b[?25h"]
[8.064465, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[1;12H\u001b[?25h"]
[8.350182, "o", "\u001b[?25l\u001b[mp\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[1;13H\u001b[?25h"]
[8.530041, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[1;14H\u001b[?25h"]
[8.82781, "o", "\u001b[?25l\u001b[m:\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[1;15H\u001b[?25h"]
[9.159365, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[1;16H\u001b[?25h"]
[9.29217, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[1;17H\u001b[?25h"]
[9.608683, "o", "\u001b[?25l\u001b[ms\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[1;18H\u001b[?25h"]
[9.772887, "o", "\u001b[?25l\u001b[mw\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[1;19H\u001b[?25h"]
[9.998823, "o", "\u001b[?25l\u001b[ma\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[1;20H\u001b[?25h"]
[10.18035, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[1;21H\u001b[?25h"]
[10.222808, "o", "\u001b[?25l\u001b[mi\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[1;22H\u001b[?25h"]
[10.599361, "o", "\u001b[?25l\u001b[m.\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[1;23H\u001b[?25h"]
[10.809162, "o", "\u001b[?25l\u001b[md\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m20\u001b[1;24H\u001b[?25h"]
[10.959103, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[1;25H\u001b[?25h"]
[11.034188, "o", "\u001b[?25l\u001b[mv\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[1;26H\u001b[?25h"]
[11.52938, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[1;27H\u001b[?25h"]
[11.769769, "o", "\u001b[?25l\u001b[ma\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[1;28H\u001b[?25h"]
[11.979865, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[1;29H\u001b[?25h"]
[11.993141, "o", "\u001b[?25l\u001b[mi\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[1;30H\u001b[?25h"]
[12.444503, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[1;31H\u001b[?25h"]
[12.792815, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[1;32H\u001b[?25h"]
[12.938877, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[1;33H\u001b[?25h"]
[13.149924, "o", "\u001b[?25l\u001b[mo\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m30\u001b[1;34H\u001b[?25h"]
[13.360561, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[1;35H\u001b[?25h"]
[13.509042, "o", "\u001b[?25l\u001b[ml\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[1;36H\u001b[?25h"]
[13.644134, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[1;37H\u001b[?25h"]
[14.230904, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 2 \u001b[m\u001b[2;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m2:1 \u001b[2;5H\u001b[?25h"]
[14.366055, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 3 \u001b[m\u001b[3;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m3\u001b[3;5H\u001b[?25h"]
[16.118006, "o", "\u001b[?25l\u001b[mH\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[3;6H\u001b[?25h"]
[16.361333, "o", "\u001b[?25l\u001b[mT\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[3;7H\u001b[?25h"]
[16.479912, "o", "\u001b[?25l\u001b[mT\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[3;8H\u001b[?25h"]
[16.765441, "o", "\u001b[?25l\u001b[mP\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[3;9H\u001b[?25h"]
[17.089972, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[3;10H\u001b[?25h"]
[17.479949, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m2\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[3;11H\u001b[?25h"]
[17.750065, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m0\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[3;12H\u001b[?25h"]
[17.885149, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m0\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[3;13H\u001b[?25h"]
[18.938288, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 4 \u001b[m\u001b[4;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m4:1 \u001b[4;5H\u001b[?25h"]
[19.930771, "o", "\u001b[?25l\u001b[m[\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[4;6H\u001b[?25h"]
[20.469027, "o", "\u001b[?25l\u001b[mA\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[4;7H\u001b[?25h"]
[20.777164, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[4;8H\u001b[?25h"]
[20.903673, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[4;9H\u001b[?25h"]
[21.160428, "o", "\u001b[?25l\u001b[me\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[4;10H\u001b[?25h"]
[21.368903, "o", "\u001b[?25l\u001b[mr\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[4;11H\u001b[?25h"]
[21.609942, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[4;12H\u001b[?25h"]
[21.775268, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[4;13H\u001b[?25h"]
[22.688437, "o", "\u001b[?25l\u001b[m\u001b[4;5H\u001b[95m[Asserts]\u001b[m\u001b[4;5H\u001b[95m\u001b[46m[\u001b[7C]\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[4;14H\u001b[?25h"]
[22.9887, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 5 \u001b[m\u001b[5;5H\u001b[K\u001b[4;5H\u001b[95m[\u001b[7C]\u001b[m\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m5:1 \u001b[5;5H\u001b[?25h"]
[23.68264, "o", "\u001b[?25l\u001b[mj\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[5;6H\u001b[?25h"]
[23.78524, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[5;7H\u001b[?25h"]
[23.919476, "o", "\u001b[?25l\u001b[mo\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[5;8H\u001b[?25h"]
[23.980456, "o", "\u001b[?25l\u001b[mn\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[5;9H\u001b[?25h"]
[24.189599, "o", "\u001b[?25l\u001b[mp\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[5;10H\u001b[?25h"]
[24.291845, "o", "\u001b[?25l\u001b[ma\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[5;11H\u001b[?25h"]
[24.45999, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[5;12H\u001b[?25h"]
[24.639712, "o", "\u001b[?25l\u001b[mh\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[5;13H\u001b[?25h"]
[24.684401, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[5;14H\u001b[?25h"]
[25.000139, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[5;15H\u001b[?25h"]
[25.51158, "o", "\u001b[?25l\u001b[m\u001b[92m$\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[5;16H\u001b[?25h"]
[25.8998, "o", "\u001b[?25l\u001b[m\u001b[92m.\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[5;17H\u001b[?25h"]
[26.138606, "o", "\u001b[?25l\u001b[m\u001b[92mc\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[5;18H\u001b[?25h"]
[26.318844, "o", "\u001b[?25l\u001b[m\u001b[92mo\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[5;19H\u001b[?25h"]
[26.349408, "o", "\u001b[?25l\u001b[m\u001b[92mu\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[5;20H\u001b[?25h"]
[26.589545, "o", "\u001b[?25l\u001b[m\u001b[92mn\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[5;21H\u001b[?25h"]
[26.666032, "o", "\u001b[?25l\u001b[m\u001b[92mt\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[5;22H\u001b[?25h"]
[26.98015, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[5;23H\u001b[?25h"]
[27.400301, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m20\u001b[5;24H\u001b[?25h"]
[27.940385, "o", "\u001b[?25l\u001b[m=\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[5;25H\u001b[?25h"]
[28.058324, "o", "\u001b[?25l\u001b[m=\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[5;26H\u001b[?25h"]
[28.330285, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[5;27H\u001b[?25h"]
[28.869495, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m8\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[5;28H\u001b[?25h"]
[29.16975, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m2\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[5;29H\u001b[?25h"]
[29.559967, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 6 \u001b[m\u001b[6;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m6:1 \u001b[6;5H\u001b[?25h"]
[29.679857, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 7 \u001b[m\u001b[7;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m7\u001b[7;5H\u001b[?25h"]
[29.890601, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 8 \u001b[m\u001b[8;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m8\u001b[8;5H\u001b[?25h"]
[31.299997, "o", "\u001b[?25l\u001b[mG\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[8;6H\u001b[?25h"]
[31.418839, "o", "\u001b[?25l\u001b[mE\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[8;7H\u001b[?25h"]
[31.538763, "o", "\u001b[?25l\u001b[m\b\b\u001b[93mGET\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[8;8H\u001b[?25h"]
[31.614974, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[8;9H\u001b[?25h"]
[32.680907, "o", "\u001b[?25l\u001b[mh\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[8;10H\u001b[?25h"]
[32.890015, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[8;11H\u001b[?25h"]
[33.008892, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[8;12H\u001b[?25h"]
[33.340299, "o", "\u001b[?25l\u001b[mp\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[8;13H\u001b[?25h"]
[33.487104, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[8;14H\u001b[?25h"]
[33.702452, "o", "\u001b[?25l\u001b[m:\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[8;15H\u001b[?25h"]
[33.984328, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[8;16H\u001b[?25h"]
[34.119828, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[8;17H\u001b[?25h"]
[34.328978, "o", "\u001b[?25l\u001b[ms\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[8;18H\u001b[?25h"]
[34.508459, "o", "\u001b[?25l\u001b[mw\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[8;19H\u001b[?25h"]
[34.690022, "o", "\u001b[?25l\u001b[ma\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[8;20H\u001b[?25h"]
[34.870277, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[8;21H\u001b[?25h"]
[34.94284, "o", "\u001b[?25l\u001b[mi\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[8;22H\u001b[?25h"]
[35.271978, "o", "\u001b[?25l\u001b[m.\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[8;23H\u001b[?25h"]
[35.530057, "o", "\u001b[?25l\u001b[md\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m20\u001b[8;24H\u001b[?25h"]
[35.679772, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[8;25H\u001b[?25h"]
[35.785471, "o", "\u001b[?25l\u001b[mv\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[8;26H\u001b[?25h"]
[36.234008, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[8;27H\u001b[?25h"]
[36.429984, "o", "\u001b[?25l\u001b[ma\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[8;28H\u001b[?25h"]
[36.639217, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[8;29H\u001b[?25h"]
[36.700511, "o", "\u001b[?25l\u001b[mi\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[8;30H\u001b[?25h"]
[37.179882, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[8;31H\u001b[?25h"]
[38.199234, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[8;32H\u001b[?25h"]
[38.320493, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[8;33H\u001b[?25h"]
[38.500165, "o", "\u001b[?25l\u001b[mo\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m30\u001b[8;34H\u001b[?25h"]
[38.737671, "o", "\u001b[?25l\u001b[mp\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[8;35H\u001b[?25h"]
[38.889822, "o", "\u001b[?25l\u001b[ml\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[8;36H\u001b[?25h"]
[39.010086, "o", "\u001b[?25l\u001b[me\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[8;37H\u001b[?25h"]
[39.354628, "o", "\u001b[?25l\u001b[m/\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[8;38H\u001b[?25h"]
[39.669139, "o", "\u001b[?25l\u001b[m1\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[8;39H\u001b[?25h"]
[40.300656, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 9 \u001b[m\u001b[9;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m9:1 \u001b[9;5H\u001b[?25h"]
[40.405191, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 10 \u001b[m\u001b[10;5H\u001b[K\u001b[24;75H\u001b[38;5;238m\u001b[48;5;244m10\u001b[10;5H\u001b[?25h"]
[41.200373, "o", "\u001b[?25l\u001b[mH\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[10;6H\u001b[?25h"]
[41.409986, "o", "\u001b[?25l\u001b[mT\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[10;7H\u001b[?25h"]
[41.543744, "o", "\u001b[?25l\u001b[mT\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[10;8H\u001b[?25h"]
[41.888207, "o", "\u001b[?25l\u001b[mP\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[10;9H\u001b[?25h"]
[42.466984, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[10;10H\u001b[?25h"]
[42.885714, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m2\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[10;11H\u001b[?25h"]
[43.09993, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m0\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[10;12H\u001b[?25h"]
[43.750093, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m0\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[10;13H\u001b[?25h"]
[44.379124, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 11 \u001b[m\u001b[11;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m1:1 \u001b[11;5H\u001b[?25h"]
[45.520172, "o", "\u001b[?25l\u001b[m[\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[11;6H\u001b[?25h"]
[46.331364, "o", "\u001b[?25l\u001b[mA\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[11;7H\u001b[?25h"]
[47.410577, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[11;8H\u001b[?25h"]
[47.54615, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[11;9H\u001b[?25h"]
[47.800418, "o", "\u001b[?25l\u001b[me\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[11;10H\u001b[?25h"]
[47.979125, "o", "\u001b[?25l\u001b[mr\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[11;11H\u001b[?25h"]
[48.188143, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[11;12H\u001b[?25h"]
[48.355861, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[11;13H\u001b[?25h"]
[49.001936, "o", "\u001b[?25l\u001b[m\u001b[11;5H\u001b[95m[Asserts]\u001b[m\u001b[11;5H\u001b[95m\u001b[46m[\u001b[7C]\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[11;14H\u001b[?25h"]
[49.327319, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 12 \u001b[m\u001b[12;5H\u001b[K\u001b[11;5H\u001b[95m[\u001b[7C]\u001b[m\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m2:1 \u001b[12;5H\u001b[?25h"]
[50.02065, "o", "\u001b[?25l\u001b[mj\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m2\u001b[12;6H\u001b[?25h"]
[50.288374, "o", "\u001b[?25l\u001b[ms\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m3\u001b[12;7H\u001b[?25h"]
[50.619446, "o", "\u001b[?25l\u001b[mo\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m4\u001b[12;8H\u001b[?25h"]
[50.646937, "o", "\u001b[?25l\u001b[mn\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m5\u001b[12;9H\u001b[?25h"]
[50.859392, "o", "\u001b[?25l\u001b[mp\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m6\u001b[12;10H\u001b[?25h"]
[50.979905, "o", "\u001b[?25l\u001b[ma\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m7\u001b[12;11H\u001b[?25h"]
[51.160997, "o", "\u001b[?25l\u001b[mt\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m8\u001b[12;12H\u001b[?25h"]
[51.370097, "o", "\u001b[?25l\u001b[mh\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m9\u001b[12;13H\u001b[?25h"]
[51.415244, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m10\u001b[12;14H\u001b[?25h"]
[51.700144, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[12;15H\u001b[?25h"]
[52.360838, "o", "\u001b[?25l\u001b[m\u001b[92m$\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[12;16H\u001b[?25h"]
[52.809737, "o", "\u001b[?25l\u001b[m\u001b[92m.\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[12;17H\u001b[?25h"]
[53.470085, "o", "\u001b[?25l\u001b[m\u001b[92mn\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[12;18H\u001b[?25h"]
[53.560389, "o", "\u001b[?25l\u001b[m\u001b[92ma\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[12;19H\u001b[?25h"]
[53.800169, "o", "\u001b[?25l\u001b[m\u001b[92mm\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[12;20H\u001b[?25h"]
[53.873787, "o", "\u001b[?25l\u001b[m\u001b[92me\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[12;21H\u001b[?25h"]
[54.13006, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[12;22H\u001b[?25h"]
[54.430549, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[12;23H\u001b[?25h"]
[54.999008, "o", "\u001b[?25l\u001b[m=\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m20\u001b[12;24H\u001b[?25h"]
[55.103269, "o", "\u001b[?25l\u001b[m=\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[12;25H\u001b[?25h"]
[55.419937, "o", "\u001b[?25l\u001b[m\u001b[38;5;81m \u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[12;26H\u001b[?25h"]
[55.541378, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[12;27H\u001b[?25h"]
[56.169023, "o", "\u001b[?25l\u001b[m\u001b[92mD\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[12;28H\u001b[?25h"]
[56.530119, "o", "\u001b[?25l\u001b[m\u001b[92ma\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[12;29H\u001b[?25h"]
[56.66352, "o", "\u001b[?25l\u001b[m\u001b[92mr\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m6\u001b[12;30H\u001b[?25h"]
[56.888287, "o", "\u001b[?25l\u001b[m\u001b[92mt\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m7\u001b[12;31H\u001b[?25h"]
[57.217614, "o", "\u001b[?25l\u001b[m\u001b[92mh\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m8\u001b[12;32H\u001b[?25h"]
[57.729172, "o", "\u001b[?25l\u001b[m\u001b[92m \u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m9\u001b[12;33H\u001b[?25h"]
[58.089175, "o", "\u001b[?25l\u001b[m\u001b[92mV\u001b[m\u001b[24;78H\u001b[38;5;238m\u001b[48;5;244m30\u001b[12;34H\u001b[?25h"]
[58.810289, "o", "\u001b[?25l\u001b[m\u001b[92ma\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m1\u001b[12;35H\u001b[?25h"]
[59.050431, "o", "\u001b[?25l\u001b[m\u001b[92md\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m2\u001b[12;36H\u001b[?25h"]
[59.230771, "o", "\u001b[?25l\u001b[m\u001b[92me\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m3\u001b[12;37H\u001b[?25h"]
[59.407561, "o", "\u001b[?25l\u001b[m\u001b[92mr\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m4\u001b[12;38H\u001b[?25h"]
[59.619514, "o", "\u001b[?25l\u001b[m\u001b[92m\"\u001b[m\u001b[24;79H\u001b[38;5;238m\u001b[48;5;244m5\u001b[12;39H\u001b[?25h"]
[60.251408, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 13 \u001b[m\u001b[13;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m3:1 \u001b[13;5H\u001b[?25h"]
[60.400358, "o", "\u001b[?25l\u001b[m\r\n\u001b[38;5;242m 14 \u001b[m\u001b[14;5H\u001b[K\u001b[24;76H\u001b[38;5;238m\u001b[48;5;244m4\u001b[14;5H\u001b[?25h"]
[61.870358, "o", "\u001b[m\u001b[24;1H\u001b[38;5;238m\u001b[48;5;117m NORMAL \u001b[m\u001b[69C\u001b[38;5;238m\u001b[48;5;244m0\u001b[14;5H\u001b[?25l\u001b[m\u001b[25;1H\u001b[K\u001b[25;1H:\u001b[?25h"]
[62.003726, "o", "w"]
[62.199186, "o", "q"]
[62.303996, "o", "\r"]
[62.305851, "o", "\u001b[?25l\u001b[?2004l\u001b[>4;m\"starwars.hurl\""]
[62.327041, "o", " [New] 14L, 176B written"]
[62.331626, "o", "\r\u001b[23;2t\u001b[23;1t\r\r\n\u001b[?1004l\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[>4;m\u001b[?1049l"]
[62.333658, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[62.352147, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[36m$\u001b[39m \u001b[K\u001b[?2004h"]
[63.369186, "o", "h"]
[63.549714, "o", "\bhu"]
[64.062416, "o", "r"]
[64.222636, "o", "l"]
[64.298216, "o", " "]
[64.627363, "o", "-"]
[64.748937, "o", "-"]
[65.106564, "o", "t"]
[65.167739, "o", "e"]
[65.347715, "o", "s"]
[65.438304, "o", "t"]
[65.498885, "o", " "]
[65.963696, "o", "*"]
[66.308602, "o", "."]
[66.608382, "o", "h"]
[66.789524, "o", "u"]
[66.862967, "o", "r"]
[67.012863, "o", "l"]
[67.749707, "o", "\u001b[?2004l\r\r\n"]
[67.760315, "o", "\u001b[1mbasic.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [1/8]\r\n"]
[68.192546, "o", "\u001b[1mbasic.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (4 request(s) in 431 ms)\r\n"]
[68.196455, "o", "\u001b[1mhealth.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [2/8]\r\n"]
[68.458322, "o", "\u001b[1mhealth.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (6 request(s) in 261 ms)\r\n"]
[68.462394, "o", "\u001b[1mlogin.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [3/8]\r\n"]
[68.724943, "o", "\u001b[1mlogin.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (6 request(s) in 261 ms)\r\n"]
[68.728932, "o", "\u001b[1mlogout.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [4/8]\r\n"]
[68.995074, "o", "\u001b[1mlogout.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (6 request(s) in 265 ms)\r\n"]
[69.001866, "o", "\u001b[1mnew-user.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [5/8]\r\n"]
[69.202078, "o", "\u001b[1mnew-user.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (4 request(s) in 200 ms)\r\n"]
[69.206269, "o", "\u001b[1msecurity.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [6/8]\r\n"]
[69.410651, "o", "\u001b[1msecurity.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (4 request(s) in 203 ms)\r\n"]
[69.414707, "o", "\u001b[1mstarwars.hurl\u001b[0m: \u001b[1;36mRunning\u001b[0m [7/8]\r\n"]
[69.744533, "o", "\u001b[1;31merror\u001b[0m: \u001b[1mAssert failure\u001b[0m\r\n \u001b[1;34m-->\u001b[0m starwars.hurl:12:0\r\n \u001b[1;34m|\u001b[0m\r\n\u001b[1;34m12\u001b[0m \u001b[1;34m|\u001b[0m jsonpath \"$.name\" == \"Darth Vader\"\r\n \u001b[1;34m|\u001b[0m \u001b[1;31mactual: string <Luke Skywalker>\u001b[0m\r\n \u001b[1;34m|\u001b[0m \u001b[1;31mexpected: string <Darth Vader>\u001b[0m\r\n \u001b[1;34m|\u001b[0m\r\n\r\n\u001b[1mstarwars.hurl\u001b[0m: \u001b[1;31mFailure\u001b[0m (2 request(s) in 329 ms)\r\n"]
[69.74857, "o", "\u001b[1mstress.hurl\u001b[0m: \u001b[1;36mRunning"]
[69.748646, "o", "\u001b[0m [8/8]\r\n"]
[69.967151, "o", "\u001b[1mstress.hurl\u001b[0m: \u001b[1;32mSuccess\u001b[0m (4 request(s) in 218 ms)\r\n"]
[69.971905, "o", "--------------------------------------------------------------------------------\r\nExecuted files: 8\r\nSucceeded files: 7 (87.5%)\r\nFailed files: 1 (12.5%)\r\nDuration: 2211 ms\r\n\r\n"]
[69.97336, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[69.991528, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[36m$\u001b[39m \u001b[K\u001b[?2004h"]
[90.954253, "o", "\u001b[?2004l\r\r\n"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

@ -2,23 +2,22 @@
## Captures
Captures are optional values captured from the HTTP response, in a named variable. Captures can be the
response status code, part of or the entire the body, and response headers.
Captures are optional values that are __extracted from the HTTP response__ and stored in a named variable.
These captures may be the response status code, part of or the entire the body, and response headers.
Captured variables are available through a run session; each new value of a given variable overrides the last value.
Captured variables can be accessed through a run session; each new value of a given variable overrides the last value.
Captures allow using data from one request in another request, when working with
[CSRF tokens] for instance. Variables can also be initialized at the start of the
session, by passing [variable values], or can be used in [templates].
Captures can be useful for using data from one request in another request, such as when working with [CSRF tokens].
Variables in a Hurl file can be created from captures or [injected into the session].
```hurl
# An example to show how to pass a CSRF token from one request
# to another:
# An example to show how to pass a CSRF token
# from one request to another:
# First GET request to get CSRF token value:
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -27,7 +26,7 @@ csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
POST https://acmecorp.net/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
Structure of a capture:
@ -40,13 +39,15 @@ Structure of a capture:
</div>
</div>
A capture consists of a variable name, followed by `:` and a query. The captures
A capture consists of a variable name, followed by `:` and a query. Captures
section starts with `[Captures]`.
### Query
Query can be of the following type:
Queries are used to extract data from an HTTP response.
A query can be of the following type:
- [`status`](#status-capture)
- [`header`](#header-capture)
@ -60,6 +61,7 @@ Query can be of the following type:
- [`variable`](#variable-capture)
- [`duration`](#duration-capture)
Extracted data can then be further refined using [filters].
### Status capture
@ -69,7 +71,7 @@ keyword `status`.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Captures]
my_status: status
```
@ -85,7 +87,7 @@ POST https://example.org/login
user: toto
password: 12345678
HTTP/1.1 302
HTTP 302
[Captures]
next_url: header "Location"
```
@ -100,7 +102,7 @@ GET https://example.org/redirecting
[Options]
location: true
HTTP/* 200
HTTP 200
[Captures]
landing_url: url
```
@ -114,7 +116,7 @@ and a cookie name.
```hurl
GET https://example.org/cookies/set
HTTP/1.0 200
HTTP 200
[Captures]
session-id: cookie "LSID"
```
@ -126,7 +128,7 @@ Cookie attributes value can also be captured by using the following format:
```hurl
GET https://example.org/cookies/set
HTTP/1.0 200
HTTP 200
[Captures]
value1: cookie "LSID"
value2: cookie "LSID[Value]" # Equivalent to the previous capture
@ -147,7 +149,7 @@ Capture the entire body (decoded as text) from the received HTTP response
```hurl
GET https://example.org/home
HTTP/1.1 200
HTTP 200
[Captures]
my_body: body
```
@ -159,7 +161,7 @@ Capture the entire body (as a raw bytestream) from the received HTTP response
```hurl
GET https://example.org/data.bin
HTTP/1.1 200
HTTP 200
[Captures]
my_data: bytes
```
@ -174,14 +176,14 @@ Currently, only XPath 1.0 expression can be used.
GET https://example.org/home
# Capture the identifier from the dom node <div id="pet0">5646eaf23</div
HTTP/1.1 200
HTTP 200
[Captures]
ped-id: xpath "normalize-space(//div[@id='pet0'])"
# Open the captured page.
GET https://example.org/home/pets/{{pet-id}}
HTTP/1.1 200
HTTP 200
```
XPath captures are not limited to node values (like string, or boolean); any
@ -190,7 +192,8 @@ valid XPath can be captured and asserted with variable asserts.
```hurl
# Test that the XML endpoint return 200 pets
GET https://example.org/api/pets
HTTP/* 200
HTTP 200
[Captures]
pets: xpath "//pets"
[Asserts]
@ -208,7 +211,7 @@ POST https://example.org/api/contact
token: {{token}}
email: toto@rookie.net
HTTP/1.1 200
HTTP 200
[Captures]
contact-id: jsonpath "$['id']"
```
@ -241,7 +244,7 @@ We can capture the following paths:
```hurl
GET https://example.org/captures-json
HTTP/1.0 200
HTTP 200
[Captures]
an_object: jsonpath "$['an_object']"
a_list: jsonpath "$['a_list']"
@ -261,16 +264,17 @@ Capture a regex pattern from the HTTP received body, decoded as text.
```hurl
GET https://example.org/helloworld
HTTP/1.0 200
HTTP 200
[Captures]
id_a: regex "id_a:([0-9]+)!"
id_b: regex "id_b:(\\d+)!"
name: regex "Hello ([a-zA-Z]+)!"
id_a: regex "id_a:([0-9]+)"
id_b: regex "id_b:(\\d+)" # pattern using double quote
id_c: regex /id_c:(\d+)/ # pattern using forward slash
name: regex "Hello ([a-zA-Z]+)"
```
Pattern of the regex query must have at least one capture group, otherwise the
capture will fail. Metacharacters beginning with a backslash in the pattern
(like `\d`, `\s`) must be escaped: `regex "(\\d+)!"` will capture one or more digit.
The regex pattern must have at least one capture group, otherwise the
capture will fail. When the pattern is a double-quoted string, metacharacters beginning with a backslash in the pattern
(like `\d`, `\s`) must be escaped; literal pattern enclosed by `/` can also be used to avoid metacharacters escaping.
### Variable capture
@ -280,10 +284,10 @@ Capture the value of a variable into another.
```hurl
GET https://example.org/helloworld
HTTP/1.0 200
HTTP 200
[Captures]
in: body
name: variable "in" regex "Hello ([a-zA-Z]+)!"
name: variable "in"
```
### Duration capture
@ -293,60 +297,19 @@ Capture the response time of the request in ms.
```hurl
GET https://example.org/helloworld
HTTP/1.0 200
HTTP 200
[Captures]
duration_in_ms: duration
```
## Filters
Optionally, query can be refined using filters `count` and `regex`.
<div class="schema-container u-font-size-0 u-font-size-1-sm u-font-size-3-md">
<div class="schema">
<span class="schema-token schema-color-1">my_var<span class="schema-label">variable</span></span>
<span> : </span>
<span class="schema-token schema-color-2">xpath "string(//h1)"<span class="schema-label">query</span></span>
<span class="schema-token">regex "(\\d+)"<span class="schema-label">filter (optional)</span></span>
</div>
</div>
### Count filter
Returns the count of a collection.
```hurl
GET https://pets.org/cats/cutest
HTTP/1.0 200
[Captures]
cats_size: jsonpath "$.cats" count
```
### Regex filter
```hurl
GET https://pets.org/cats/cutest
HTTP/1.0 200
# Cat name are structured like this `meow + id`: for instance `meow123456`
[Captures]
id: jsonpath "$.cats[0].name" regex "meow(\\d+)"
```
Pattern of the regex filter must have at least one capture group, otherwise the
capture will fail. Metacharacters beginning with a backslash in the pattern
(like `\d`, `\s`) must be escaped: `regex "(\\d+)!"` will capture one or more digit.
[CSRF tokens]: https://en.wikipedia.org/wiki/Cross-site_request_forgery
[variable values]: /docs/manual.md#variable
[templates]: /docs/templates.md
[injected into the session]: /docs/templates.md#injecting-variables
[`Set-Cookie`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
[XPath]: https://en.wikipedia.org/wiki/XPath
[JSONPath]: https://goessner.net/articles/JsonPath/
[XPath captures]: #xpath-capture
[Javascript-like Regular expression syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
[options]: /docs/request.md#options
[`--location` option]: /docs/manual.md#location
[`--location` option]: /docs/manual.md#location
[filters]: /docs/filters.md

View File

@ -13,22 +13,21 @@ to [capture values] to perform subsequent requests, or [add asserts to HTTP resp
# First, test home title.
GET https://acmecorp.net
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
# Get some news, response description is optional
GET https://acmecorp.net/news
# Do a POST request without csrf token and check
# Do a POST request without CSRF token and check
# that status code is Forbidden 403
POST https://acmecorp.net/contact
[FormParams]
default: false
email: john.doe@rookie.org
number: 33611223344
HTTP/1.1 403
HTTP 403
```
## Description
@ -42,24 +41,24 @@ every entry of a given file will follow redirection:
$ hurl --location foo.hurl
```
You can use an [`[Options]` section] to use option only for a specified option. For instance, in this Hurl file:
You can use an [`[Options]` section][options] to use option only for a specified option. For instance, in this Hurl file:
```hurl
GET https://google.fr
HTTP/* 301
HTTP 301
GET https://google.fr
[Options]
location: true
HTTP/* 200
HTTP 200
GET https://google.fr
HTTP/* 301
HTTP 301
```
The second entry will follow location (and so we can test the status code to be 200 instead of 301).
The second entry will follow location (so we can test the status code to be 200 instead of 301).
You can use it to logs a specific entry:
You can use it to log a specific entry:
```hurl
# ... previous entries
@ -67,8 +66,7 @@ You can use it to logs a specific entry:
GET https://api.example.org
[Options]
very-verbose: true
HTTP/* 200
HTTP 200
# ... next entries
```
@ -83,17 +81,15 @@ By default, Hurl doesn't follow redirection. To effectively run a redirection, e
of the redirection, allowing insertion of asserts in each response.
```hurl
# First entry, test the redirection (status code and
# Location header)
# First entry, test the redirection (status code and 'Location' header)
GET https://google.fr
HTTP/1.1 301
HTTP 301
Location: https://www.google.fr/
# Second entry, the 200 OK response
GET https://www.google.fr
HTTP/1.1 200
HTTP 200
```
Alternatively, one can use [`--location`] option to force redirection
@ -103,7 +99,7 @@ redirections can be limited with [`--max-redirs`].
```hurl
# Running hurl --location google.hurl
GET https://google.fr
HTTP/1.1 200
HTTP 200
```
Finally, you can force redirection on a particular request with an [`[Options]` section][options] and the [`--location` option]:
@ -112,7 +108,7 @@ Finally, you can force redirection on a particular request with an [`[Options]`
GET https://google.fr
[Options]
location: true
HTTP/1.1 200
HTTP 200
```
### Retry
@ -129,7 +125,7 @@ For example, in this Hurl file, first we create a new job, then we poll the new
# Create a new job
POST http://api.example.org/jobs
HTTP/* 201
HTTP 201
[Captures]
job_id: jsonpath "$.id"
[Asserts]
@ -141,7 +137,7 @@ GET http://api.example.org/jobs/{{job_id}}
[Options]
retry: true
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.state" == "COMPLETED"
```
@ -154,7 +150,7 @@ jsonpath "$.state" == "COMPLETED"
[`--location`]: /docs/manual.md#location
[`--max-redirs`]: /docs/manual.md#max-redirs
[Options]: /docs/manual.md#options
[options]: /docs/manual.md#options
[options]: /docs/request.md#options
[`--location` option]: /docs/manual.md#location
[headers]: /docs/response.md#headers
[status code]: /docs/response.md#version-status

189
docs/filters.md Normal file
View File

@ -0,0 +1,189 @@
# Filters
## Definition
[Captures] and [asserts] share a common structure: query. A query is used to extract data from an HTTP response; this data
can come from the HTTP response body, the HTTP response headers or from the HTTP meta-informations (like `duration` for instance)...
In this example, the query __`jsonpath "$.books[0].name"`__ is used in a capture to save data and in an assert to test
the HTTP response body.
__Capture__:
<div class="schema-container schema-container u-font-size-2 u-font-size-3-md">
<div class="schema">
<span class="schema-token schema-color-1">name<span class="schema-label">variable</span></span>
<span> : </span>
<span class="schema-token schema-color-2">jsonpath "$.books[0].name"<span class="schema-label">query</span></span>
</div>
</div>
__Assert__:
<div class="schema-container schema-container u-font-size-2 u-font-size-3-md">
<div class="schema">
<span class="schema-token schema-color-2">jsonpath "$.books[0].name"<span class="schema-label">query</span></span>
<span class="schema-token schema-color-3">== "Dune"<span class="schema-label">predicate</span></span>
</div>
</div>
In both case, the query is exactly the same: queries are the core structure of asserts and captures. Sometimes, you want
to process data extracted by queries: that's the purpose of __filters__.
Filters are used to transform value extracted by a query and can be used in asserts and captures to refine data. Filters
__can be chained__, allowing for fine-grained data extraction.
<div class="schema-container schema-container u-font-size-2 u-font-size-3-md">
<div class="schema">
<span class="schema-token schema-color-2">jsonpath "$.name"<span class="schema-label">query</span></span>
<span class="schema-token schema-color-1">split "," nth 0<span class="schema-label">2 filters</span></span>
<span class="schema-token schema-color-3">== "Herbert"<span class="schema-label">predicate</span></span>
</div>
</div>
## Example
```hurl
GET https://example.org/api
HTTP 200
[Captures]
name: jsonpath "$user.id" replace /\d/ "x"
[Asserts]
header "x-servers" split "," count == 2
header "x-servers" split "," nth 0 == "rec1"
header "x-servers" split "," nth 1 == "rec3"
jsonpath "$.books" count == 12
```
## Description
### count
Counts the number of items in a collection.
```hurl
GET https://example.org/api
HTTP 200
[Asserts]
jsonpath "$.books" count == 12
```
### htmlEscape
Converts the characters `&`, `<` and `>` to HTML-safe sequence.
```hurl
GET https://example.org/api
HTTP 200
[Asserts]
jsonpath "$.text" htmlEscape == "a &gt; b"
```
### htmlUnescape
Converts all named and numeric character references (e.g. `&gt;`, `&#62;`, `&#x3e;`) to the corresponding Unicode characters.
```hurl
GET https://example.org/api
HTTP 200
[Asserts]
jsonpath "$.escaped_html[1]" htmlUnescape == "Foo © bar 𝌆"
```
### nth
Returns the element from a collection at a zero-based index.
```hurl
GET https://example.org/api
HTTP 200
[Asserts]
jsonpath "$.books" nth 2 == "Children of Dune"
```
### regex
Extracts regex capture group. Pattern must have at least one capture group.
```hurl
GET https://example.org/foo
HTTP 200
[Captures]
param1: header "header1"
param2: header "header2" regex "Hello (.*)!"
param3: header "header2" regex /Hello (.*)!/
```
### replace
Replaces all occurrences of old string with new string.
```hurl
GET https://example.org/foo
HTTP 200
[Captures]
url: jsonpath "$.url" replace "http://" "https://"
[Asserts]
jsonpath "$.ips" replace ", " "|" == "192.168.2.1|10.0.0.20|10.0.0.10"
```
### split
Splits to a list of strings around occurrences of the specified delimiter.
```hurl
GET https://example.org/foo
HTTP 200
[Asserts]
jsonpath "$.ips" split ", " count == 3
```
### toInt
Converts to integer number.
```hurl
GET https://example.org/foo
HTTP 200
[Asserts]
jsonpath "$.id" toInt == 123
```
### urlDecode
Replaces %xx escapes with their single-character equivalent.
```hurl
GET https://example.org/foo
HTTP 200
[Asserts]
jsonpath "$.encoded_url" urlDecode == "https://mozilla.org/?x=шеллы"
```
### urlEncode
Percent-encodes all the characters which are not included in unreserved chars (see [RFC3986]) with the exception of forward slash (/).
```hurl
GET https://example.org/foo
HTTP 200
[Asserts]
jsonpath "$.url" urlEncode == "https%3A//mozilla.org/%3Fx%3D%D1%88%D0%B5%D0%BB%D0%BB%D1%8B"
```
[Captures]: /docs/capturing-response.md
[asserts]: /docs/asserting-response.md
[RFC3986]: https://www.rfc-editor.org/rfc/rfc3986

View File

@ -10,21 +10,24 @@
Hurl is a command line tool that runs <b>HTTP requests</b> defined in a simple <b>plain text format</b>.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very
versatile: it can be used for <b>fetching data</b>, <b>testing HTTP</b> sessions and testing <b>XML / JSON APIs</b>.
versatile: it can be used for both <b>fetching data</b> and <b>testing HTTP</b> sessions.
Hurl makes it easy to work with <b>HTML</b> content, <b>REST / SOAP / GraphQL</b> APIs, or any other <b>XML / JSON</b> based APIs.
```hurl
# Get home:
GET https://example.org
HTTP/1.1 200
HTTP 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: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
Chaining multiple requests is easy:
@ -53,7 +56,7 @@ POST https://example.org/api/tests
"evaluate": true
}
HTTP/1.1 200
HTTP 200
[Asserts]
header "X-Frame-Options" == "SAMEORIGIN"
jsonpath "$.status" == "RUNNING" # Check the status code
@ -66,12 +69,27 @@ jsonpath "$.id" matches /\d{4}/ # Check the format of the id
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
```
and even SOAP APIs
<b>GraphQL</b>
~~~hurl
POST https://example.org/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
HTTP 200
~~~
and even <b>SOAP APIs</b>
```hurl
POST https://example.org/InStock
@ -86,8 +104,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</m:GetStockPrice>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
Hurl can also be used to performance test HTTP endpoints:
@ -95,7 +112,7 @@ Hurl can also be used to performance test HTTP endpoints:
```hurl
GET https://example.org/api/v1/pets
HTTP/1.0 200
HTTP 200
[Asserts]
duration < 1000 # Duration in ms
```
@ -105,7 +122,7 @@ And response bytes
```hurl
GET https://example.org/data.tar.gz
HTTP/1.0 200
HTTP 200
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
@ -136,7 +153,7 @@ POST https://hurl.dev/api/feedback
"name": "John Doe",
"feedback": "Hurl is awesome !"
}
HTTP/1.1 200
HTTP 200
```
# Resources

View File

@ -20,7 +20,7 @@ a documentation for HTTP based workflows so it can be useful to be very descript
GET https://www.sample.net
x-app: MY_APP # Add a dummy header
HTTP/1.1 302 # Check that we have a redirection
HTTP 302 # Check that we have a redirection
[Asserts]
header "Location" exists
header "Location" contains "login" # Check that we are redirected to the login page
@ -37,8 +37,7 @@ String can include the following special characters:
```hurl
GET https://example.org/api
HTTP/1.1 200
HTTP 200
# The following assert are equivalent:
[Asserts]
jsonpath "$.slideshow.title" == "A beautiful ✈!"
@ -51,7 +50,7 @@ In the following example:
```hurl
GET https://example.org/api
x-token: BEEF \#STEACK # Some comment
HTTP/1.1 200
HTTP 200
```
We're sending a header `x-token` with value `BEEF #STEACK`

View File

@ -4,12 +4,12 @@
### Linux
Precompiled binary is available at [hurl-1.8.0-x86_64-linux.tar.gz]:
Precompiled binary is available at [hurl-2.0.0-x86_64-linux.tar.gz]:
```shell
$ INSTALL_DIR=/tmp
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-2.0.0:$PATH
```
#### Debian / Ubuntu
@ -17,8 +17,8 @@ $ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
For Debian / Ubuntu, Hurl can be installed using a binary .deb file provided in each Hurl release.
```shell
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl_1.8.0_amd64.deb
$ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl_2.0.0_amd64.deb
$ sudo apt update && apt install ./hurl_2.0.0_amd64.deb
```
#### Arch Linux / Manjaro
@ -31,7 +31,7 @@ $ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
### macOS
Precompiled binary is available at [hurl-1.8.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-1.8.0-arm64-macos.tar.gz] for ARM CPUS.
Precompiled binary is available at [hurl-2.0.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-2.0.0-arm64-macos.tar.gz] for ARM CPUS.
#### Homebrew
@ -55,11 +55,11 @@ $ sudo pkg install hurl
#### Zip File
Hurl can be installed from a standalone zip file [hurl-1.8.0-win64.zip]. You will need to update your `PATH` variable.
Hurl can be installed from a standalone zip file [hurl-2.0.0-win64.zip]. You will need to update your `PATH` variable.
#### Installer
An installer [hurl-1.8.0-win64-installer.exe] is also available.
An installer [hurl-2.0.0-win64-installer.exe] is also available.
#### Chocolatey
@ -156,11 +156,11 @@ $ ./target/release/hurl --version
Please follow the [contrib on Windows section].
[GitHub]: https://github.com/Orange-OpenSource/hurl
[hurl-1.8.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64.zip
[hurl-1.8.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64-installer.exe
[hurl-1.8.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-macos.tar.gz
[hurl-1.8.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-arm64-macos.tar.gz
[hurl-1.8.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz
[hurl-2.0.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64.zip
[hurl-2.0.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64-installer.exe
[hurl-2.0.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-macos.tar.gz
[hurl-2.0.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-arm64-macos.tar.gz
[hurl-2.0.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[`hurl-bin` package]: https://aur.archlinux.org/packages/hurl-bin/
[install]: https://www.rust-lang.org/tools/install

View File

@ -12,9 +12,9 @@ hurl - run and test HTTP requests.
## Description
**Hurl** is an HTTP client that performs HTTP requests defined in a simple plain text format.
**Hurl** is a command line tool that runs HTTP requests defined in a simple plain text format.
Hurl is very versatile. It enables chaining HTTP requests, capturing values from HTTP responses, and making assertions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very versatile, it can be used for fetching data and testing HTTP sessions: HTML content, REST / SOAP / GraphQL APIs, or any other XML / JSON based APIs.
```shell
$ hurl session.hurl
@ -71,11 +71,11 @@ GET http:/example.org/endpoint2
A value from an HTTP response can be-reused for successive HTTP requests.
A typical example occurs with csrf tokens.
A typical example occurs with CSRF tokens.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -89,28 +89,28 @@ More information on captures can be found here [https://hurl.dev/docs/capturing-
### Asserts
The HTTP response defined in the Hurl session are used to make asserts.
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
At the minimum, the response includes the asserts on the HTTP version and status code.
At the minimum, response includes assert on the HTTP status code.
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
```
It can also include asserts on the response headers
```hurl
GET http:/google.com
HTTP/1.1 301
Location: http://www.google.com
GET http:/example.org
HTTP 301
Location: http://www.example.org
```
Explicit asserts can be included by combining a query and a predicate
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
[Asserts]
xpath "string(//title)" == "301 Moved"
```
@ -134,56 +134,61 @@ $ hurl --location foo.hurl
will follow redirection for each entry in `foo.hurl`. You can also define an option only for a particular entry with an `[Options]` section. For instance, this Hurl file:
```hurl
GET https://google.com
HTTP/* 301
GET https://example.org
HTTP 301
GET https://google.com
GET https://example.org
[Options]
location: true
HTTP/* 200
HTTP 200
```
will follow a redirection only for the second entry.
Option | Description
--- | ---
<a href="#cacert" id="cacert"><code>--cacert</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br/>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br/>
<a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br/>
<a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br/>
<a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br/><br/>See also [`-m, --max-time`](#max-time) option.<br/>
<a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br/><br/>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br/>The file will be written using the Netscape cookie file format.<br/><br/>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br/>By default, Hurl exits after an assert error in the HTTP response.<br/><br/>Note that this option does not affect the behavior with multiple input Hurl files.<br/><br/>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br/>
<a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br/>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br/>
<a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br/>
<a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br/><br/>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br/>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br/>
<a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br/>
<a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br/>
<a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br/>
<a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br/>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br/>
<a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br/>
<a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br/>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br/>
<a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br/><br/>See also [`--connect-timeout`](#connect-timeout) option.<br/>
<a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br/>
<a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br/>
<a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br/>Override value from Environment variable no_proxy.<br/>
<a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br/>
<a href="#proxy" id="proxy"><code>-x, --proxy [protocol://]host[:port]</code></a> | Use the specified proxy.<br/>
<a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br/><br/>If the FILE report already exists, it will be updated with the new test results.<br/>
<a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br/><br/>If the HTML report already exists, it will be updated with the new test results.<br/>
<a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br/>
<a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br/>
<a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br/>
<a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br/>
<a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br/>Ignore the remaining of the file. It is useful for debugging a session.<br/>
<a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br/>
<a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br/>
<a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br/>
<a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br/><br/>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br/><br/>Note that defining a variable twice produces an error.<br/>
<a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br/>Useful for debugging.<br/><br/>A line starting with '>' means data sent by Hurl.<br/>A line staring with '<' means data received by Hurl.<br/>A line starting with '*' means additional info provided by Hurl.<br/><br/>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br/>
<a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br/><br/>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br/>
<a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br/>
<a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br/>
| Option | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <a href="#cacert" id="cacert"><code>--cacert &lt;FILE&gt;</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br> |
| <a href="#cert" id="cert"><code>-E, --cert &lt;CERTIFICATE[:PASSWORD]&gt;</code></a> | Client certificate file and password.<br><br>See also [`--key`](#key).<br> |
| <a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br> |
| <a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br> |
| <a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br><br>See also [`-m, --max-time`](#max-time).<br> |
| <a href="#connect-to" id="connect-to"><code>--connect-to &lt;HOST1:PORT1:HOST2:PORT2&gt;</code></a> | For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. This option can be used several times in a command line.<br><br>See also [`--resolve`](#resolve).<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br> |
| <a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br> |
| <a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br><br>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br> |
| <a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br> |
| <a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br> |
| <a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br> |
| <a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br> |
| <a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br> |
| <a href="#key" id="key"><code>--key &lt;KEY&gt;</code></a> | Private key file name.<br> |
| <a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br> |
| <a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br><br>See also [`--connect-timeout`](#connect-timeout).<br> |
| <a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br> |
| <a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br> |
| <a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br>Override value from Environment variable no_proxy.<br> |
| <a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br> |
| <a href="#proxy" id="proxy"><code>-x, --proxy &lt;[PROTOCOL://]HOST[:PORT]&gt;</code></a> | Use the specified proxy.<br> |
| <a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br><br>If the FILE report already exists, it will be updated with the new test results.<br> |
| <a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br><br>If the HTML report already exists, it will be updated with the new test results.<br> |
| <a href="#resolve" id="resolve"><code>--resolve &lt;HOST:PORT:ADDR&gt;</code></a> | Provide a custom address for a specific host and port pair. Using this, you can make the Hurl requests(s) use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative provided on the command line.<br> |
| <a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br> |
| <a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br> |
| <a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br> |
| <a href="#ssl-no-revoke" id="ssl-no-revoke"><code>--ssl-no-revoke</code></a> | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.<br> |
| <a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br> |
| <a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br>Ignore the remaining of the file. It is useful for debugging a session.<br> |
| <a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br> |
| <a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br> |
| <a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br> |
| <a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br><br>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br><br>Note that defining a variable twice produces an error.<br> |
| <a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br>Useful for debugging.<br><br>A line starting with '>' means data sent by Hurl.<br>A line staring with '<' means data received by Hurl.<br>A line starting with '*' means additional info provided by Hurl.<br><br>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br> |
| <a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br><br>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br> |
| <a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br> |
| <a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br> |
## Environment
@ -191,23 +196,23 @@ Environment variables can only be specified in lowercase.
Using an environment variable to set the proxy has the same effect as using the [`-x, --proxy`](#proxy) option.
Variable | Description
--- | ---
`http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br/>
`https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br/>
`all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br/>
`no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br/>
`HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br/>
`NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br/>
| Variable | Description |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br> |
| `https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br> |
| `all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br> |
| `no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br> |
| `HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br> |
| `NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br> |
## Exit Codes
Value | Description
--- | ---
`1` | Failed to parse command-line options.<br/>
`2` | Input File Parsing Error.<br/>
`3` | Runtime error (such as failure to connect to host).<br/>
`4` | Assert Error.<br/>
| Value | Description |
|-------|---------------------------------------------------------|
| `1` | Failed to parse command-line options.<br> |
| `2` | Input File Parsing Error.<br> |
| `3` | Runtime error (such as failure to connect to host).<br> |
| `4` | Assert Error.<br> |
## WWW

View File

@ -1,4 +1,4 @@
.TH hurl 1 "07 Nov 2022" "hurl 2.0.0-SNAPSHOT" " Hurl Manual"
.TH hurl 1 "24 Jan 2023" "hurl 2.0.0-SNAPSHOT" " Hurl Manual"
.SH NAME
hurl - run and test HTTP requests.
@ -13,9 +13,9 @@ hurl - run and test HTTP requests.
.SH DESCRIPTION
.B Hurl
is an HTTP client that performs HTTP requests defined in a simple plain text format.
is a command line tool that runs HTTP requests defined in a simple plain text format.
Hurl is very versatile. It enables chaining HTTP requests, capturing values from HTTP responses, and making assertions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very versatile, it can be used for fetching data and testing HTTP sessions: HTML content, REST / SOAP / GraphQL APIs, or any other XML / JSON based APIs.
$ hurl session.hurl
@ -62,10 +62,10 @@ It consists of one or several HTTP requests
A value from an HTTP response can be-reused for successive HTTP requests.
A typical example occurs with csrf tokens.
A typical example occurs with CSRF tokens.
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -78,23 +78,23 @@ More information on captures can be found here \fIhttps://hurl.dev/docs/capturin
.IP "Asserts"
The HTTP response defined in the Hurl session are used to make asserts.
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
At the minimum, the response includes the asserts on the HTTP version and status code.
At the minimum, response includes assert on the HTTP status code.
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
It can also include asserts on the response headers
GET http:/google.com
HTTP/1.1 301
Location: http://www.google.com
GET http:/example.org
HTTP 301
Location: http://www.example.org
Explicit asserts can be included by combining a query and a predicate
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
[Asserts]
xpath "string(//title)" == "301 Moved"
@ -114,21 +114,27 @@ For instance:
will follow redirection for each entry in `foo.hurl`. You can also define an option only for a particular entry with an `[Options]` section. For instance, this Hurl file:
GET https://google.com
HTTP/* 301
GET https://example.org
HTTP 301
GET https://google.com
GET https://example.org
[Options]
location: true
HTTP/* 200
HTTP 200
will follow a redirection only for the second entry.
.IP "--cacert "
.IP "--cacert <FILE> "
Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.
Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.
.IP "-E, --cert <CERTIFICATE[:PASSWORD]> "
Client certificate file and password.
See also \fI--key\fP.
.IP "--color "
Colorize Output.
@ -141,7 +147,13 @@ Request a compressed response using one of the algorithms br, gzip, deflate and
Maximum time in seconds that you allow Hurl's connection to take.
See also \fI-m, --max-time\fP option.
See also \fI-m, --max-time\fP.
.IP "--connect-to <HOST1:PORT1:HOST2:PORT2> "
For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. This option can be used several times in a command line.
See also \fI--resolve\fP.
.IP "-b, --cookie <FILE> "
@ -202,6 +214,10 @@ This is similar to a break point, You can then continue (Press C) or quit (Press
Output each hurl file result to JSON. The format is very closed to HAR format.
.IP "--key <KEY> "
Private key file name.
.IP "--max-redirs <NUM> "
Set maximum number of redirection-followings allowed
@ -211,7 +227,7 @@ By default, the limit is set to 50 redirections. Set this option to -1 to make i
Maximum time in seconds that you allow a request/response to take. This is the standard timeout.
See also \fI--connect-timeout\fP option.
See also \fI--connect-timeout\fP.
.IP "--no-color "
@ -230,7 +246,7 @@ Override value from Environment variable no_proxy.
Write output to FILE instead of stdout.
.IP "-x, --proxy [protocol://]host[:port] "
.IP "-x, --proxy <[PROTOCOL://]HOST[:PORT]> "
Use the specified proxy.
@ -246,6 +262,10 @@ Generate HTML report in DIR.
If the HTML report already exists, it will be updated with the new test results.
.IP "--resolve <HOST:PORT:ADDR> <HOST:PORT:ADDR>"
Provide a custom address for a specific host and port pair. Using this, you can make the Hurl requests(s) use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative provided on the command line.
.IP "--retry "
Retry requests if any error occurs (asserts, captures, runtimes etc...).
@ -258,6 +278,10 @@ Duration in milliseconds between each retry. Default is 1000 ms.
Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.
.IP "--ssl-no-revoke "
(Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.
.IP "--test "
Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.

View File

@ -10,9 +10,9 @@ hurl - run and test HTTP requests.
## DESCRIPTION
**Hurl** is an HTTP client that performs HTTP requests defined in a simple plain text format.
**Hurl** is a command line tool that runs HTTP requests defined in a simple plain text format.
Hurl is very versatile. It enables chaining HTTP requests, capturing values from HTTP responses, and making assertions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very versatile, it can be used for fetching data and testing HTTP sessions: HTML content, REST / SOAP / GraphQL APIs, or any other XML / JSON based APIs.
```shell
$ hurl session.hurl
@ -69,11 +69,11 @@ GET http:/example.org/endpoint2
A value from an HTTP response can be-reused for successive HTTP requests.
A typical example occurs with csrf tokens.
A typical example occurs with CSRF tokens.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -87,28 +87,28 @@ More information on captures can be found here [https://hurl.dev/docs/capturing-
### Asserts
The HTTP response defined in the Hurl session are used to make asserts.
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
At the minimum, the response includes the asserts on the HTTP version and status code.
At the minimum, response includes assert on the HTTP status code.
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
```
It can also include asserts on the response headers
```hurl
GET http:/google.com
HTTP/1.1 301
Location: http://www.google.com
GET http:/example.org
HTTP 301
Location: http://www.example.org
```
Explicit asserts can be included by combining a query and a predicate
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
[Asserts]
xpath "string(//title)" == "301 Moved"
```
@ -132,22 +132,28 @@ $ hurl --location foo.hurl
will follow redirection for each entry in `foo.hurl`. You can also define an option only for a particular entry with an `[Options]` section. For instance, this Hurl file:
```hurl
GET https://google.com
HTTP/* 301
GET https://example.org
HTTP 301
GET https://google.com
GET https://example.org
[Options]
location: true
HTTP/* 200
HTTP 200
```
will follow a redirection only for the second entry.
### --cacert {#cacert}
### --cacert <FILE> {#cacert}
Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.
Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.
### -E, --cert <CERTIFICATE[:PASSWORD]> {#cert}
Client certificate file and password.
See also [`--key`](#key).
### --color {#color}
Colorize Output.
@ -160,7 +166,13 @@ Request a compressed response using one of the algorithms br, gzip, deflate and
Maximum time in seconds that you allow Hurl's connection to take.
See also [`-m, --max-time`](#max-time) option.
See also [`-m, --max-time`](#max-time).
### --connect-to <HOST1:PORT1:HOST2:PORT2> {#connect-to}
For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. This option can be used several times in a command line.
See also [`--resolve`](#resolve).
### -b, --cookie <FILE> {#cookie}
@ -221,6 +233,10 @@ This is similar to a break point, You can then continue (Press C) or quit (Press
Output each hurl file result to JSON. The format is very closed to HAR format.
### --key <KEY> {#key}
Private key file name.
### --max-redirs <NUM> {#max-redirs}
Set maximum number of redirection-followings allowed
@ -230,7 +246,7 @@ By default, the limit is set to 50 redirections. Set this option to -1 to make i
Maximum time in seconds that you allow a request/response to take. This is the standard timeout.
See also [`--connect-timeout`](#connect-timeout) option.
See also [`--connect-timeout`](#connect-timeout).
### --no-color {#no-color}
@ -249,7 +265,7 @@ Override value from Environment variable no_proxy.
Write output to FILE instead of stdout.
### -x, --proxy [protocol://]host[:port] {#proxy}
### -x, --proxy <[PROTOCOL://]HOST[:PORT]> {#proxy}
Use the specified proxy.
@ -265,6 +281,10 @@ Generate HTML report in DIR.
If the HTML report already exists, it will be updated with the new test results.
### --resolve <HOST:PORT:ADDR> {#resolve} <HOST:PORT:ADDR>
Provide a custom address for a specific host and port pair. Using this, you can make the Hurl requests(s) use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative provided on the command line.
### --retry {#retry}
Retry requests if any error occurs (asserts, captures, runtimes etc...).
@ -277,6 +297,10 @@ Duration in milliseconds between each retry. Default is 1000 ms.
Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.
### --ssl-no-revoke {#ssl-no-revoke}
(Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.
### --test {#test}
Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.

View File

@ -1,4 +1,4 @@
.TH hurl 1 "07 Nov 2022" "hurl 2.0.0-SNAPSHOT" " Hurl Manual"
.TH hurl 1 "24 Jan 2023" "hurl 2.0.0-SNAPSHOT" " Hurl Manual"
.SH NAME
hurlfmt - format Hurl files

View File

@ -4,8 +4,8 @@
Request describes an HTTP request: a mandatory [method] and [URL], followed by optional [headers].
Then, [query parameters], [form parameters], [multipart form data], [cookies] and
[basic authentication] can be used to configure the HTTP request.
Then, [query parameters], [form parameters], [multipart form data], [cookies], [basic authentication] and [options]
can be used to configure the HTTP request.
Finally, an optional [body] can be used to configure the HTTP request body.
@ -116,7 +116,7 @@ order: newest
```
The last optional part of a request configuration is the request [body]. Request body must be the last parameter of a request
(after [headers] and request sections). Like headers, [body] have no explicit marker:
(after [headers] and request sections). Like headers, body have no explicit marker:
```hurl
POST https://example.org/api/dogs?id=4567
@ -131,7 +131,7 @@ User-Agent: My User Agent
### Method
Mandatory HTTP request method, one of `GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`,
`TRACE`, `PATCH`.
`TRACE`, `PATCH`, `LINK`, `UNLINK`, `PURGE`, `LOCK`, `UNLOCK`, `PROPFIND`, `VIEW`.
### URL
@ -173,7 +173,7 @@ Connection: keep-alive
> Headers directly follow URL, without any section name, contrary to query parameters, form parameters
> or cookies
Note that header usually don't start with double quotes. If the header value starts with double quotes, the double
Note that a header usually doesn't start with double quotes. If a header value starts with double quotes, double
quotes will be part of the header value:
```hurl
@ -223,7 +223,7 @@ number: 33611223344
```
Form parameters section can be seen as syntactic sugar over body section (values in form parameters section
are not URL encoded.). A [multiline string body] could be used instead of a forms parameters section.
are not URL encoded.). A [oneline string body] could be used instead of a forms parameters section.
~~~hurl
# Run a POST request with form parameters section:
@ -235,9 +235,7 @@ key1: value1
# Run the same POST request with a body section:
POST https://example.org/test
Content-Type: application/x-www-form-urlencoded
```
name=John%20Doe&key1=value1
```
`name=John%20Doe&key1=value1`
~~~
When both [body section] and form parameters section are present, only the body section is taken into account.
@ -342,8 +340,9 @@ Optional HTTP body request.
If the body of the request is a [JSON] string or a [XML] string, the value can be
directly inserted without any modification. For a text based body that is not JSON nor XML,
one can use multiline string that starts with <code>&#96;&#96;&#96;</code> and ends
with <code>&#96;&#96;&#96;</code>.
one can use [multiline string body] that starts with <code>&#96;&#96;&#96;</code> and ends
with <code>&#96;&#96;&#96;</code>. Multiline string body support "language hint" and can be used
to create [GraphQL queries].
For a precise byte control of the request body, [Base64] encoded string, [hexadecimal string]
or [included file] can be used to describe exactly the body byte content.
@ -354,7 +353,7 @@ The body section must be the last section of the request configuration.
#### JSON body
JSON body is used to set a literal JSON as the request body.
JSON request body is used to set a literal JSON as the request body.
```hurl
# Create a new doggy thing with JSON body:
@ -369,11 +368,43 @@ POST https://example.org/api/dogs
}
```
When using JSON body, the content type `application/json` is automatically set.
JSON request body can be [templatized with variables]:
```hurl
# Create a new catty thing with JSON body:
POST https://example.org/api/cats
{
"id": 42,
"lives": {{ lives_count }},
"name": "{{ name }}"
}
```
When using JSON request body, the content type `application/json` is automatically set.
JSON request body can be seen as syntactic sugar of [multiline string body] with `json` identifier:
~~~hurl
# Create a new doggy thing with JSON body:
POST https://example.org/api/dogs
```json
{
"id": 0,
"name": "Frieda",
"picture": "images/scottish-terrier.jpeg",
"age": 3,
"breed": "Scottish Terrier",
"location": "Lisco, Alabama"
}
```
~~~
#### XML body
XML body is used to set a literal XML as the request body.
XML request body is used to set a literal XML as the request body.
~~~hurl
# Create a new soapy thing XML body:
@ -392,9 +423,88 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</soap:Envelope>
~~~
XML request body can be seen as syntactic sugar of [multiline string body] with `xml` identifier:
~~~hurl
# Create a new soapy thing XML body:
POST https://example.org/InStock
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 299
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
```xml
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:m="http://example.net">
<soap:Header></soap:Header>
<soap:Body>
<m:GetStockPrice>
<m:StockName>GOOG</m:StockName>
</m:GetStockPrice>
</soap:Body>
</soap:Envelope>
```
~~~
> Contrary to JSON body, the succint syntax of XML body can not use variables. If you need to use variables in your
> XML body, use a simple [multiline string body] with variables.
#### GraphQL query
GraphQL query uses [multiline string body] with `graphql` identifier:
~~~hurl
POST https://example.org/starwars/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
~~~
GraphQL query body can use [GraphQL variables]:
~~~hurl
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 query, as every multiline string body, can use Hurl variables.
~~~hurl
POST https://example.org/starwars/graphql
```graphql
{
human(id: "{{human_id}}") {
name
height(unit: FOOT)
}
}
```
~~~
> Hurl variables and GraphQL variables can be mixed in the same body.
#### Multiline string body
For text based body that are not JSON nor XML, one can used multiline string, started and ending with
For text based body that are not JSON nor XML, one can use multiline string, started and ending with
<code>&#96;&#96;&#96;</code>.
~~~hurl
@ -420,28 +530,28 @@ line3
is evaluated as "line1\nline2\nline3\n".
Multiline string body can use language identifier, like `json`, `xml` or `graphql`. Depending on the language identifier,
an additional 'Content-Type' request header is sent, and the real body (bytes sent over the wire) can be different from the
raw multiline text.
To construct an empty string:
~~~
```
~~~hurl
POST https://example.org/api/dogs
```json
{
"id": 0,
"name": "Frieda",
}
```
~~~
or
#### Oneline string body
For text based body that do not contain newlines, one can use oneline string, started and ending with <code>&#96;</code>.
~~~hurl
POST https://example.org/helloworld
`Hello world!`
~~~
``````
~~~
Finally, multiline string can be used without any newline:
~~~
```line```
~~~
is evaluated as "line".
#### Base64 body
@ -504,10 +614,15 @@ 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
variable: country=Italy # define variable country
variable: planet=Earth # define variable planet
verbose: true # allow verbose output
very-verbose: true # allow more verbose output
```
> Variable defined in an `[Options]` section are defined also for the next entries. This is
> the exception, all other options are defined only for the current request.
[method]: #method
[URL]: #url
[headers]: #headers
@ -521,6 +636,7 @@ very-verbose: true # allow more verbose output
[query parameters section]: #query-parameters
[HTML form]: https://developer.mozilla.org/en-US/docs/Learn/Forms
[multiline string body]: #multiline-string-body
[oneline string body]: #oneline-string-body
[body section]: #body
[multipart/form-data on MDN]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST
[`--file-root` option]: /docs/manual.md#file-root
@ -536,4 +652,8 @@ very-verbose: true # allow more verbose output
[`--location`]: /docs/manual.md#location
[`--verbose`]: /docs/manual.md#verbose
[`--insecure`]: /docs/manual.md#insecure
[templatized with variables]: /docs/templates.md#templating-body
[GraphQL queries]: #graphql-query
[GraphQL variables]: https://graphql.org/learn/queries/#variables
[options]: #options

View File

@ -15,7 +15,7 @@ in the following entries.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
[Asserts]
xpath "normalize-space(//head/title)" startsWith "Welcome"
@ -28,7 +28,7 @@ xpath "//li" count == 18
<div class="hurl-structure">
<div class="hurl-structure-col-0">
<div class="hurl-part-0">
HTTP/1.1 *
HTTP *
</div>
<div class=" hurl-part-1">
content-length: 206<br>accept-ranges: bytes<br>user-agent: Test

View File

@ -63,17 +63,25 @@ You can use [`--glob` option] to test files that match a given pattern:
$ hurl --test --glob "test/integration/**/*.hurl"
```
## Generating an HTML Report
## Generating Report
Hurl can also generate an HTML report by using the [`--report-html HTML_DIR`] option.
### HTML Report
Hurl can generate an HTML report by using the [`--report-html HTML_DIR`] option.
If the HTML report already exists, the test results will be appended to it.
<img src="/docs/assets/img/hurl-html-report.png" width="320" height="258" alt="Hurl HTML Report">
<img src="/docs/assets/img/hurl-html-report.png" style="max-width:670px;width:100%" alt="Hurl HTML Report">
The input Hurl files (HTML version) are also included and are easily accessed from the main page.
<img src="/docs/assets/img/hurl-html-file.png" width="380" height="206" alt="Hurl HTML file">
<img src="/docs/assets/img/hurl-html-file.png" style="max-width:380px;width:100%" alt="Hurl HTML file">
### JUnit Report
A JUnit report can be produced by using the [`--report-junit FILE`] option.
If the JUnit file already exists, it will be updated with the new test results.
## Use Variables in Tests
@ -89,6 +97,7 @@ You will find a detailed description in the [Injecting Variables] section of the
[`--output /dev/null`]: /docs/manual.md#output
[`--test`]: /docs/manual.md#test
[`--report-html HTML_DIR`]: /docs/manual.md#report-html
[`--report-junit FILE`]: /docs/manual.md#report-junit
[`--test` option]: /docs/manual.md#test
[`--glob` option]: /docs/manual.md#glob
[`--variable` option]: /docs/manual.md#variable

View File

@ -137,15 +137,11 @@ file,data.json;
[Doc](/docs/request.md#file-body)
### Templating a JSON / XML Body
### Templating a JSON Body
Using templates with [JSON body] or [XML body] is not currently supported in Hurl.
Besides, you can use templates in [multiline string body] with variables to send a JSON or XML body:
~~~hurl
```hurl
PUT https://example.org/api/hits
Content-Type: application/json
```
{
"key0": "{{a_string}}",
"key1": {{a_bool}},
@ -153,7 +149,6 @@ Content-Type: application/json
"key3": {{a_number}}
}
```
~~~
Variables can be initialized via command line:
@ -176,8 +171,67 @@ Resulting in a PUT request with the following JSON body:
}
```
[Doc](/docs/templates.md)
### 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:
~~~hurl
POST https://example.org/echo/post/xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<Request>
<Login>{{login}}</Login>
<Password>{{password}}</Password>
</Request>
```
~~~
[Doc](/docs/request.md#multiline-string-body)
### Using GraphQL Query
A simple GraphQL query:
~~~hurl
POST https://example.org/starwars/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
~~~
A GraphQL query with variables:
~~~hurl
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](/docs/request.md#graphql-body)
## Testing Response
### Testing Response Headers
@ -187,7 +241,7 @@ Use implicit response asserts to test header values:
```hurl
GET https://example.org/index.html
HTTP/1.0 200
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
```
@ -200,7 +254,7 @@ Or use explicit response asserts with [predicates]:
```hurl
GET https://example.org
HTTP/1.1 302
HTTP 302
[Asserts]
header "Location" contains "www.example.net"
```
@ -216,7 +270,7 @@ Asserting JSON body response (node values, collection count etc...) with [JSONPa
GET https://example.org/order
screencapability: low
HTTP/1.1 200
HTTP 200
[Asserts]
jsonpath "$.validated" == true
jsonpath "$.userInfo.firstName" == "Franck"
@ -236,7 +290,7 @@ Testing status code:
```hurl
GET https://example.org/order/435
HTTP/1.1 200
HTTP 200
```
[Doc](/docs/asserting-response.md#version-status)
@ -245,7 +299,7 @@ HTTP/1.1 200
GET https://example.org/order/435
# Testing status code is in a 200-300 range
HTTP/1.1 *
HTTP *
[Asserts]
status >= 200
status < 300
@ -259,7 +313,7 @@ status < 300
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
Content-Type: text/html; charset=UTF-8
[Asserts]
@ -278,7 +332,7 @@ xpath "string(//div[1])" matches /Hello.*/
```hurl
GET http://myserver.com/home
HTTP/1.0 200
HTTP 200
[Asserts]
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
@ -308,6 +362,17 @@ sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
## Others
### HTTP Version
Testing HTTP version (1.0, 1.1 or 2):
```hurl
GET https://example.org/order/435
HTTP/2 200
```
[Doc](/docs/asserting-response.md#version-status)
### Polling and Retry
Retry request on any errors (asserts, captures, status code, runtime etc...):
@ -316,7 +381,7 @@ Retry request on any errors (asserts, captures, status code, runtime etc...):
# Create a new job
POST https://api.example.org/jobs
HTTP/* 201
HTTP 201
[Captures]
job_id: jsonpath "$.id"
[Asserts]
@ -328,7 +393,7 @@ GET https://api.example.org/jobs/{{job_id}}
[Options]
retry: true
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.state" == "COMPLETED"
```
@ -342,7 +407,7 @@ jsonpath "$.state" == "COMPLETED"
```hurl
GET https://sample.org/helloworld
HTTP/* *
HTTP *
[Asserts]
duration < 1000 # Check that response time is less than one second
```
@ -365,7 +430,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
[Doc](/docs/request.md#xml-body)
@ -375,14 +440,14 @@ HTTP/1.1 200
```hurl
GET https://example.org
HTTP/* 200
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
HTTP 302
```
[Doc](/docs/capturing-response.md#xpath-capture)
@ -392,7 +457,7 @@ HTTP/* 302
```hurl
GET https://example.org/data.bin
HTTP/* 200
HTTP 200
[Asserts]
bytes startsWith hex,efbbbf;
```
@ -402,7 +467,7 @@ bytes startsWith hex,efbbbf;
[JSON body]: /docs/request.md#json-body
[XML body]: /docs/request.md#xml-body
[multiline string body]: /docs/request.md#multiline-string-body
[XML multiline string body]: /docs/request.md#multiline-string-body
[predicates]: /docs/asserting-response.md#predicates
[JSONPath]: https://goessner.net/articles/JsonPath/
[Basic authentication]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme
@ -413,3 +478,4 @@ bytes startsWith hex,efbbbf;
[curl]: https://curl.se
[entry]: /docs/entry.md
[`--test` option]: /docs/manual.md#test
[Hurl templates]: /docs/templates.md

View File

@ -8,7 +8,7 @@ value from an HTTP response in the next entries, you can capture this value in a
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
@ -16,7 +16,7 @@ csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
POST https://acmecorp.net/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
In this example, we capture the value of the [CSRF token] from the body of the first response, and inject it
@ -24,12 +24,15 @@ as a header in the next POST request.
```hurl
GET https://example.org/api/index
HTTP/* 200
HTTP 200
[Captures]
index: body
GET https://example.org/api/status
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.errors[{{index}}].id" == "error"
```
@ -45,7 +48,8 @@ templates can be rendered differently. Let's say we have captured an integer val
```hurl
GET https://sample/counter
HTTP/* 200
HTTP 200
[Captures]
count: jsonpath "$.results[0]"
```
@ -54,7 +58,8 @@ The following entry:
```hurl
GET https://sample/counter/{{count}}
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.id" == "{{count}}"
```
@ -62,8 +67,9 @@ jsonpath "$.id" == "{{count}}"
will be rendered at runtime to:
```hurl
GET https://sample/counter/458
HTTP/* 200
GET https://sample/counter/458
HTTP 200
[Asserts]
jsonpath "$.id" == "458"
```
@ -74,7 +80,8 @@ On the other hand, the following assert:
```hurl
GET https://sample/counter/{{count}}
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.index" == {{count}}
```
@ -83,7 +90,8 @@ will be rendered at runtime to:
```hurl
GET https://sample/counter/458
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.index" == 458
```
@ -105,15 +113,16 @@ Variables can also be injected in a Hurl file:
- by using [`--variable` option]
- by using [`--variables-file` option]
- by defining environment variables, for instance `HURL_foo=bar`
- by defining variables in an [`[Options]` section][options]
Lets' see how to inject variables, given this `test.hurl`:
```hurl
GET https://{{host}}/{{id}}/status
HTTP/1.1 304
HTTP 304
GET https://{{host}}/health
HTTP/1.1 200
HTTP 200
```
### `variable` option
@ -142,14 +151,28 @@ id=1234
### Environment variable
Finally, we can use environment variables in the form of `HURL_name=value`:
We can use environment variables in the form of `HURL_name=value`:
```shell
$ export HURL_host=example.net
$ export HURL_id=1234
$ hurl test.hurl
```
```
### Options sections
We can define variables in `[Options]` section. Variables defined in a section are available for the next requests.
```hurl
GET https://{{host}}/{{id}}/status
[Options]
variable: host=example.net
variable: id=1234
HTTP 304
GET https://{{host}}/health
HTTP 200
```
## Templating Body
@ -194,3 +217,4 @@ Resulting in a PUT request with the following JSON body:
[JSON body]: /docs/request.md#json-body
[XML body]: /docs/request.md#xml-body
[multiline string body]: /docs/request.md#multiline-string-body
[options]: /docs/request.md#options

View File

@ -6,19 +6,13 @@ Our basic Hurl file is now:
# Our first Hurl file, just checking
# that our server is up and running.
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
```
Currently, we're just checking that our home page is responding with a `200 OK` HTTP status code.
But we also want to check the _content_ of our home page, to ensure that everything is ok. To check the response
of an HTTP request with Hurl, we have to _describe_ tests that the response content must pass.
> We're already implicitly asserting the response with the line\
> `HTTP/1.1 200`\
> On one hand, we are checking that the HTTP protocol version is 1.1; on the other hand, we are checking
> that the HTTP status response code is 200.
To do so, we're going to use [asserts].
As our endpoint <http://localhost:8080> is serving HTML content, it makes sense to use [XPath asserts].
@ -48,7 +42,7 @@ going to use the [XPath expression] `string(//head/title)`.
# that our server is up and running.
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
```
@ -75,7 +69,7 @@ There is no error so everything is good!
# that our server is up and running.
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quaz!"
```
@ -123,7 +117,7 @@ with `count`:
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -135,7 +129,7 @@ xpath "//button" count == 2
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -172,7 +166,7 @@ As our endpoint is serving UTF-8 encoded HTML content, we can check the value of
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -197,7 +191,7 @@ while the explicit one allows you to use other [predicates] (like `contains`, `s
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
# Implicitly testing response headers:
Content-Type: text/html;charset=UTF-8
[Asserts]
@ -223,7 +217,7 @@ So to test it, we can modify our Hurl file with another header assert.
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -250,7 +244,7 @@ So to test that our server is responding with a `HttpOnly` session cookie, we ca
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2

View File

@ -55,7 +55,7 @@ question2: 4edc1fdb
question3: 37b9eff3
question4: 0fec576c
HTTP/1.1 302
HTTP 302
```
> When sending form data with a Form parameters section, you don't need to set the
@ -67,10 +67,10 @@ HTTP/1.1 302
$ hurl --test create-quiz.hurl
create-quiz.hurl: Running [1/1]
error: Assert status code
--> create-quiz.hurl:10:10
--> create-quiz.hurl:6:10
|
10 | HTTP/1.1 302
| ^^^ actual value is <403>
10 | HTTP 302
| ^^^ actual value is <403>
|
create-quiz.hurl: Failure (1 request(s) in 5 ms)
@ -122,7 +122,7 @@ So, let's go!
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery)
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
```
@ -146,10 +146,11 @@ Now that we have captured the CSRF token value, we can inject it in the POST req
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery):
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
# Create a new quiz, using the captured CSRF token:
POST http://localhost:8080/new-quiz
[FormParams]
@ -160,8 +161,7 @@ question2: 4edc1fdb
question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
3. Run `create-quiz.hurl` and verify everything is ok:
@ -207,7 +207,7 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
[Captures]
detail_url: header "Location"
```
@ -232,7 +232,7 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
[Captures]
detail_url: header "Location"
[Asserts]
@ -250,7 +250,7 @@ header "Location" matches "/quiz/detail/[a-f0-9]{8}"
# Open the newly created quiz detail page:
GET {{detail_url}}
HTTP/1.1 200
HTTP 200
```
4. Run `create-quiz.hurl` and verify everything is ok:
@ -267,7 +267,7 @@ Duration: 46 ms
```
> You can force Hurl to follow redirection by using [`-L / --location` option].
> You can force Hurl to follow redirection by using [`-L / --location` option] or using an [`[Options]` section][options].
> In this case, asserts and captures will be run against the last redirection step.
@ -280,10 +280,11 @@ So, our test file `create-quiz.hurl` is now:
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery)
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
# Create a new quiz, using the captured CSRF token.
POST http://localhost:8080/new-quiz
[FormParams]
@ -295,15 +296,16 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
[Captures]
detail_url: header "Location"
[Asserts]
header "Location" matches "/quiz/detail/[a-f0-9]{8}"
# Open the newly created quiz detail page:
GET {{detail_url}}
HTTP/1.1 200
HTTP 200
```
We have seen how to [capture response data] in a variable and use it in others request.
@ -326,4 +328,5 @@ of a redirection.
[header capture]: /docs/capturing-response.md#header-capture
[`Location`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location
[`-L / --location` option]: /docs/manual.md#location
[capture response data]: /docs/capturing-response.md
[capture response data]: /docs/capturing-response.md
[options]: /docs/request.md#options

View File

@ -8,7 +8,7 @@ Our basic Hurl file is for the moment:
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -33,7 +33,7 @@ request following our first request. Let's say we want to test that we have a [4
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -48,7 +48,7 @@ cookie "JSESSIONID[HttpOnly]" exists
# Check that we have a 404 response for broken links:
GET http://localhost:8080/not-found
HTTP/1.1 404
HTTP 404
[Asserts]
header "Content-Type" == "text/html;charset=UTF-8"
xpath "string(//h1)" == "Error 404, Page not Found!"
@ -126,7 +126,7 @@ followed by a predicate. A [JsonPath query] is a simple expression to inspect a
# Check our health API:
GET http://localhost:8080/api/health
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" == "application/json"
jsonpath "$.status" == "RUNNING"
@ -155,7 +155,7 @@ through the API endpoint.
# Check question API:
GET http://localhost:8080/api/questions?offset=0&size=20&sort=oldest
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" == "application/json"
jsonpath "$" count == 20
@ -194,7 +194,7 @@ offset: 0
size: 20
sort: oldest
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" == "application/json"
jsonpath "$" count == 20
@ -208,7 +208,7 @@ Finally, our basic Hurl file, with four requests, looks like:
# Checking our home page:
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "string(//head/title)" == "Welcome to Quiz!"
xpath "//button" count == 2
@ -220,24 +220,27 @@ header "Content-Type" == "text/html;charset=UTF-8"
cookie "JSESSIONID" exists
cookie "JSESSIONID[HttpOnly]" exists
# Check that we have a 404 response for broken links:
GET http://localhost:8080/not-found
HTTP/1.1 404
HTTP 404
[Asserts]
header "Content-Type" == "text/html;charset=UTF-8"
xpath "string(//h1)" == "Error 404, Page not Found!"
# Check our health API:
GET http://localhost:8080/api/health
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" == "application/json"
jsonpath "$.status" == "RUNNING"
jsonpath "$.healthy" == true
jsonpath "$.operationId" exists
# Check question API:
GET http://localhost:8080/api/questions
[QueryStringParams]
@ -245,7 +248,7 @@ offset: 0
size: 20
sort: oldest
HTTP/1.1 200
HTTP 200
[Asserts]
header "Content-Type" == "application/json"
jsonpath "$" count == 20

View File

@ -77,7 +77,7 @@ really ready to accept incoming HTTP requests.
To do so, we can test our health API. With a function `wait_for_url`,
we use Hurl to check a given URL to return a `200 OK`. We loop on this function
until the check succeeds. Once the test has succeeded, we stop the container.
until the check succeeds with [`--retry`] Hurl option. Once the test has succeeded, we stop the container.
5. Modify `bin/integration.sh` to wait for the application to be ready:
@ -86,24 +86,13 @@ until the check succeeds. Once the test has succeeded, we stop the container.
set -eu
wait_for_url () {
echo "Testing $1"
max_in_s=$2
delay_in_s=1
total_in_s=0
while [ $total_in_s -le "$max_in_s" ]
do
echo "Wait ${total_in_s}s"
if (echo -e "GET $1\nHTTP/* 200" | hurl > /dev/null 2>&1;) then
return 0
fi
total_in_s=$(( total_in_s + delay_in_s))
sleep $delay_in_s
done
return 1
echo "Testing $1..."
echo -e "GET $1\nHTTP 200" | hurl --retry --retry-max-count "$2" > /dev/null;
return 0
}
echo "Starting Quiz container"
docker run --name quiz --rm --detach --publish 8080:8080 ghcr.io/jcamiel/quiz:latest
docker run --rm --detach --publish 8080:8080 --name quiz ghcr.io/jcamiel/quiz:latest
echo "Starting Quiz instance to be ready"
wait_for_url 'http://localhost:8080' 60
@ -275,3 +264,4 @@ Now, we can add more Hurl tests and start developing new features with confidenc
[GitLab CI/CD here]: https://about.gitlab.com/blog/2022/12/14/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd/
[GitLab CI/CD]: https://about.gitlab.com/why-gitlab/
[this detailed tutorial]: https://about.gitlab.com/blog/2022/12/14/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd/
[`--retry`]: /docs/manual.md#retry

View File

@ -239,7 +239,7 @@ offset: 0
size: 20
sort: oldest
HTTP/1.1 200
HTTP 200
# ...
```
@ -357,18 +357,19 @@ In this mode, headers of the last entry are displayed:
```shell
$ hurl -i basic.hurl
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 06 Jun 2021 15:11:31 GMT
HTTP/1.1 200
Set-Cookie: JSESSIONID=76984131F0D0821C4A8D5CB3FC27CD3B; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 13 Jan 2023 12:49:47 GMT
[{"id":"c0d80047-4fbe-4d45-a005-91b5c7018b34","created":"1995-12-17T03:24:00Z"....
{"status":"RUNNING","reportedDate":"2023-01-13T13:49:47+01:00","healthy":true,"operationId":3183000623}
```
If you want to inspect any entry other than the last one, you can run your test to a
@ -376,40 +377,50 @@ given entry with the [`--to-entry` option], starting at index 1:
```shell
$ hurl -i --to-entry 2 basic.hurl
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/html;charset=UTF-8
Content-Language: en-US
Transfer-Encoding: chunked
Date: Sun, 06 Jun 2021 15:14:20 GMT
HTTP/1.1 404
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: text/html;charset=UTF-8
Content-Language: en-FR
Transfer-Encoding: chunked
Date: Fri, 13 Jan 2023 12:50:52 GMT
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Error 404 - Quiz</title>
<link rel="stylesheet" href="/style.css">
<!--<script src="script.js"></script>-->
</head>
<body>
<div>
<img class="logo" src="/quiz.svg" alt="Quiz logo">
<a href="/"><img class="logo-img" src="/quiz.svg" alt="Quiz logo"></a>
</div>
<div class="main">
<h1>Error 404, Page not Found!</h1>
<a href="/">Quiz Home</a>
</div>
<footer>
<div class="footer">
<div class="footer-body">a game by <a href="https://hurl.dev">Hurl&RightArrowLeftArrow; Team</a></div>
</div>
</footer>
</body>
</html>
```
## Using a Proxy

View File

@ -8,10 +8,11 @@ endpoint. Our test file `create-quiz.hurl` now looks like:
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery)
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
# Create a new quiz, using the captured CSRF token.
POST http://localhost:8080/new-quiz
[FormParams]
@ -23,15 +24,16 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
[Captures]
detail_url: header "Location"
[Asserts]
header "Location" matches "/quiz/detail/[a-f0-9]{8}"
# Open the newly created quiz detail page:
GET {{detail_url}}
HTTP/1.1 200
HTTP 200
```
So far, we have tested a "simple" form creation: every value of the form is valid and sanitized, but what if the user
@ -88,7 +90,7 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "//label[@for='name'][@class='invalid']" exists
```
@ -123,7 +125,7 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "//label[@for='email'][@class='invalid']" exists
```
@ -156,8 +158,7 @@ question1: dd894cca
question2: 4edc1fdb
question3: 37b9eff3
question4: 0fec576c
HTTP/1.1 403
HTTP 403
```
> We're using [the exist predicate] to check labels in the DOM
@ -198,7 +199,7 @@ With Hurl, you will be able to check the content of the _real_ network data.
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery)
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
[Asserts]
@ -230,12 +231,13 @@ So, our test file `create-quiz.hurl` is now:
# the CSRF token (see https://en.wikipedia.org/wiki/Cross-site_request_forgery)
GET http://localhost:8080/new-quiz
HTTP/1.1 200
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
[Asserts]
xpath "//comment" count == 0 # Check that we don't leak comments
# Create a new quiz, using the captured CSRF token.
POST http://localhost:8080/new-quiz
[FormParams]
@ -247,15 +249,16 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 302
HTTP 302
[Captures]
detail_url: header "Location"
[Asserts]
header "Location" matches "/quiz/detail/[a-f0-9]{8}"
# Open the newly created quiz detail page:
GET {{detail_url}}
HTTP/1.1 200
HTTP 200
# Test various server-side validations:
@ -270,10 +273,11 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "//label[@for='name'][@class='invalid']" exists
# Invalid email parameter:
POST http://localhost:8080/new-quiz
[FormParams]
@ -286,10 +290,11 @@ question3: 37b9eff3
question4: 0fec576c
_csrf: {{csrf_token}}
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "//label[@for='email'][@class='invalid']" exists
# No CSRF token:
POST http://localhost:8080/new-quiz
[FormParams]
@ -300,8 +305,7 @@ question1: dd894cca
question2: 4edc1fdb
question3: 37b9eff3
question4: 0fec576c
HTTP/1.1 403
HTTP 403
```
We have seen that Hurl can be used as a security tool, to check you server-side validation.

View File

@ -127,7 +127,7 @@ the response and, at least, check that the HTTP response status code is [`200 OK
```hurl
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
```
4. Execute `basic.hurl`:
@ -173,7 +173,7 @@ Duration: 7 ms
```hurl
GET http://localhost:8080
HTTP/1.1 500
HTTP 500
```
7. Save and execute it:
@ -204,7 +204,7 @@ Duration: 13 ms
# Our first Hurl file, just checking
# that our server is up and running.
GET http://localhost:8080
HTTP/1.1 200
HTTP 200
```
## Recap

View File

@ -12,21 +12,24 @@
Hurl is a command line tool that runs <b>HTTP requests</b> defined in a simple <b>plain text format</b>.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very
versatile: it can be used for <b>fetching data</b>, <b>testing HTTP</b> sessions and testing <b>XML / JSON APIs</b>.
versatile: it can be used for both <b>fetching data</b> and <b>testing HTTP</b> sessions.
Hurl makes it easy to work with <b>HTML</b> content, <b>REST / SOAP / GraphQL</b> APIs, or any other <b>XML / JSON</b> based APIs.
```hurl
# Get home:
GET https://example.org
HTTP/1.1 200
HTTP 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: {{csrf_token}}
HTTP/1.1 302
HTTP 302
```
Chaining multiple requests is easy:
@ -55,7 +58,7 @@ POST https://example.org/api/tests
"evaluate": true
}
HTTP/1.1 200
HTTP 200
[Asserts]
header "X-Frame-Options" == "SAMEORIGIN"
jsonpath "$.status" == "RUNNING" # Check the status code
@ -68,12 +71,27 @@ jsonpath "$.id" matches /\d{4}/ # Check the format of the id
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
```
and even SOAP APIs
<b>GraphQL</b>
~~~hurl
POST https://example.org/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
HTTP 200
~~~
and even <b>SOAP APIs</b>
```hurl
POST https://example.org/InStock
@ -88,8 +106,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</m:GetStockPrice>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
Hurl can also be used to performance test HTTP endpoints:
@ -97,7 +114,7 @@ Hurl can also be used to performance test HTTP endpoints:
```hurl
GET https://example.org/api/v1/pets
HTTP/1.0 200
HTTP 200
[Asserts]
duration < 1000 # Duration in ms
```
@ -107,7 +124,7 @@ And response bytes
```hurl
GET https://example.org/data.tar.gz
HTTP/1.0 200
HTTP 200
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
@ -138,7 +155,7 @@ POST https://hurl.dev/api/feedback
"name": "John Doe",
"feedback": "Hurl is awesome !"
}
HTTP/1.1 200
HTTP 200
```
# Resources
@ -164,7 +181,9 @@ Table of Contents
* [Sending HTML Form Data](#sending-html-form-data)
* [Sending Multipart Form Data](#sending-multipart-form-data)
* [Posting a JSON Body](#posting-a-json-body)
* [Templating a JSON / XML Body](#templating-a-json--xml-body)
* [Templating a JSON Body](#templating-a-json-body)
* [Templating a XML Body](#templating-a-xml-body)
* [Using GraphQL Query](#using-graphql-query)
* [Testing Response](#testing-response)
* [Testing Response Headers](#testing-response-headers)
* [Testing REST APIs](#testing-rest-apis)
@ -172,6 +191,7 @@ Table of Contents
* [Testing Set-Cookie Attributes](#testing-set-cookie-attributes)
* [Testing Bytes Content](#testing-bytes-content)
* [Others](#others)
* [HTTP Version](#http-version)
* [Polling and Retry](#polling-and-retry)
* [Testing Endpoint Performance](#testing-endpoint-performance)
* [Using SOAP APIs](#using-soap-apis)
@ -354,15 +374,11 @@ file,data.json;
[Doc](https://hurl.dev/docs/request.html#file-body)
### Templating a JSON / XML Body
### Templating a JSON Body
Using templates with [JSON body] or [XML body] is not currently supported in Hurl.
Besides, you can use templates in [multiline string body] with variables to send a JSON or XML body:
~~~hurl
```hurl
PUT https://example.org/api/hits
Content-Type: application/json
```
{
"key0": "{{a_string}}",
"key1": {{a_bool}},
@ -370,7 +386,6 @@ Content-Type: application/json
"key3": {{a_number}}
}
```
~~~
Variables can be initialized via command line:
@ -393,8 +408,67 @@ Resulting in a PUT request with the following JSON body:
}
```
[Doc](https://hurl.dev/docs/templates.html)
### 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:
~~~hurl
POST https://example.org/echo/post/xml
```xml
<?xml version="1.0" encoding="utf-8"?>
<Request>
<Login>{{login}}</Login>
<Password>{{password}}</Password>
</Request>
```
~~~
[Doc](https://hurl.dev/docs/request.html#multiline-string-body)
### Using GraphQL Query
A simple GraphQL query:
~~~hurl
POST https://example.org/starwars/graphql
```graphql
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
```
~~~
A GraphQL query with variables:
~~~hurl
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](https://hurl.dev/docs/request.html#graphql-body)
## Testing Response
### Testing Response Headers
@ -404,7 +478,7 @@ Use implicit response asserts to test header values:
```hurl
GET https://example.org/index.html
HTTP/1.0 200
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
```
@ -417,7 +491,7 @@ Or use explicit response asserts with [predicates]:
```hurl
GET https://example.org
HTTP/1.1 302
HTTP 302
[Asserts]
header "Location" contains "www.example.net"
```
@ -433,7 +507,7 @@ Asserting JSON body response (node values, collection count etc...) with [JSONPa
GET https://example.org/order
screencapability: low
HTTP/1.1 200
HTTP 200
[Asserts]
jsonpath "$.validated" == true
jsonpath "$.userInfo.firstName" == "Franck"
@ -453,7 +527,7 @@ Testing status code:
```hurl
GET https://example.org/order/435
HTTP/1.1 200
HTTP 200
```
[Doc](https://hurl.dev/docs/asserting-response.html#version-status)
@ -462,7 +536,7 @@ HTTP/1.1 200
GET https://example.org/order/435
# Testing status code is in a 200-300 range
HTTP/1.1 *
HTTP *
[Asserts]
status >= 200
status < 300
@ -476,7 +550,7 @@ status < 300
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
Content-Type: text/html; charset=UTF-8
[Asserts]
@ -495,7 +569,7 @@ xpath "string(//div[1])" matches /Hello.*/
```hurl
GET http://myserver.com/home
HTTP/1.0 200
HTTP 200
[Asserts]
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
@ -525,6 +599,17 @@ sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
## Others
### HTTP Version
Testing HTTP version (1.0, 1.1 or 2):
```hurl
GET https://example.org/order/435
HTTP/2 200
```
[Doc](https://hurl.dev/docs/asserting-response.html#version-status)
### Polling and Retry
Retry request on any errors (asserts, captures, status code, runtime etc...):
@ -533,7 +618,7 @@ Retry request on any errors (asserts, captures, status code, runtime etc...):
# Create a new job
POST https://api.example.org/jobs
HTTP/* 201
HTTP 201
[Captures]
job_id: jsonpath "$.id"
[Asserts]
@ -545,7 +630,7 @@ GET https://api.example.org/jobs/{{job_id}}
[Options]
retry: true
HTTP/* 200
HTTP 200
[Asserts]
jsonpath "$.state" == "COMPLETED"
```
@ -559,7 +644,7 @@ jsonpath "$.state" == "COMPLETED"
```hurl
GET https://sample.org/helloworld
HTTP/* *
HTTP *
[Asserts]
duration < 1000 # Check that response time is less than one second
```
@ -582,7 +667,7 @@ SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
</soap:Body>
</soap:Envelope>
HTTP/1.1 200
HTTP 200
```
[Doc](https://hurl.dev/docs/request.html#xml-body)
@ -592,14 +677,14 @@ HTTP/1.1 200
```hurl
GET https://example.org
HTTP/* 200
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
HTTP 302
```
[Doc](https://hurl.dev/docs/capturing-response.html#xpath-capture)
@ -609,7 +694,7 @@ HTTP/* 302
```hurl
GET https://example.org/data.bin
HTTP/* 200
HTTP 200
[Asserts]
bytes startsWith hex,efbbbf;
```
@ -631,9 +716,9 @@ hurl - run and test HTTP requests.
## Description
**Hurl** is an HTTP client that performs HTTP requests defined in a simple plain text format.
**Hurl** is a command line tool that runs HTTP requests defined in a simple plain text format.
Hurl is very versatile. It enables chaining HTTP requests, capturing values from HTTP responses, and making assertions.
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very versatile, it can be used for fetching data and testing HTTP sessions: HTML content, REST / SOAP / GraphQL APIs, or any other XML / JSON based APIs.
```shell
$ hurl session.hurl
@ -690,11 +775,11 @@ GET http:/example.org/endpoint2
A value from an HTTP response can be-reused for successive HTTP requests.
A typical example occurs with csrf tokens.
A typical example occurs with CSRF tokens.
```hurl
GET https://example.org
HTTP/1.1 200
HTTP 200
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
@ -708,28 +793,28 @@ More information on captures can be found here [https://hurl.dev/docs/capturing-
### Asserts
The HTTP response defined in the Hurl session are used to make asserts.
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
At the minimum, the response includes the asserts on the HTTP version and status code.
At the minimum, response includes assert on the HTTP status code.
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
```
It can also include asserts on the response headers
```hurl
GET http:/google.com
HTTP/1.1 301
Location: http://www.google.com
GET http:/example.org
HTTP 301
Location: http://www.example.org
```
Explicit asserts can be included by combining a query and a predicate
```hurl
GET http:/google.com
HTTP/1.1 301
GET http:/example.org
HTTP 301
[Asserts]
xpath "string(//title)" == "301 Moved"
```
@ -753,56 +838,61 @@ $ hurl --location foo.hurl
will follow redirection for each entry in `foo.hurl`. You can also define an option only for a particular entry with an `[Options]` section. For instance, this Hurl file:
```hurl
GET https://google.com
HTTP/* 301
GET https://example.org
HTTP 301
GET https://google.com
GET https://example.org
[Options]
location: true
HTTP/* 200
HTTP 200
```
will follow a redirection only for the second entry.
Option | Description
--- | ---
<a href="#cacert" id="cacert"><code>--cacert</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br/>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br/>
<a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br/>
<a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br/>
<a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br/><br/>See also [`-m, --max-time`](#max-time) option.<br/>
<a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br/><br/>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br/>The file will be written using the Netscape cookie file format.<br/><br/>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br/>
<a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br/>By default, Hurl exits after an assert error in the HTTP response.<br/><br/>Note that this option does not affect the behavior with multiple input Hurl files.<br/><br/>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br/>
<a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br/>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br/>
<a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br/>
<a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br/><br/>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br/>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br/>
<a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br/>
<a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br/>
<a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br/>
<a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br/>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br/>
<a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br/>
<a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br/>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br/>
<a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br/><br/>See also [`--connect-timeout`](#connect-timeout) option.<br/>
<a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br/>
<a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br/>
<a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br/>Override value from Environment variable no_proxy.<br/>
<a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br/>
<a href="#proxy" id="proxy"><code>-x, --proxy [protocol://]host[:port]</code></a> | Use the specified proxy.<br/>
<a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br/><br/>If the FILE report already exists, it will be updated with the new test results.<br/>
<a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br/><br/>If the HTML report already exists, it will be updated with the new test results.<br/>
<a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br/>
<a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br/>
<a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br/>
<a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br/>
<a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br/>Ignore the remaining of the file. It is useful for debugging a session.<br/>
<a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br/>
<a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br/>
<a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br/>
<a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br/><br/>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br/><br/>Note that defining a variable twice produces an error.<br/>
<a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br/>Useful for debugging.<br/><br/>A line starting with '>' means data sent by Hurl.<br/>A line staring with '<' means data received by Hurl.<br/>A line starting with '*' means additional info provided by Hurl.<br/><br/>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br/>
<a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br/><br/>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br/>
<a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br/>
<a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br/>
| Option | Description |
|------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <a href="#cacert" id="cacert"><code>--cacert &lt;FILE&gt;</code></a> | Specifies the certificate file for peer verification. The file may contain multiple CA certificates and must be in PEM format.<br>Normally Hurl is built to use a default file for this, so this option is typically used to alter that default file.<br> |
| <a href="#cert" id="cert"><code>-E, --cert &lt;CERTIFICATE[:PASSWORD]&gt;</code></a> | Client certificate file and password.<br><br>See also [`--key`](#key).<br> |
| <a href="#color" id="color"><code>--color</code></a> | Colorize Output.<br> |
| <a href="#compressed" id="compressed"><code>--compressed</code></a> | Request a compressed response using one of the algorithms br, gzip, deflate and automatically decompress the content.<br> |
| <a href="#connect-timeout" id="connect-timeout"><code>--connect-timeout &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow Hurl's connection to take.<br><br>See also [`-m, --max-time`](#max-time).<br> |
| <a href="#connect-to" id="connect-to"><code>--connect-to &lt;HOST1:PORT1:HOST2:PORT2&gt;</code></a> | For a request to the given HOST1:PORT1 pair, connect to HOST2:PORT2 instead. This option can be used several times in a command line.<br><br>See also [`--resolve`](#resolve).<br> |
| <a href="#cookie" id="cookie"><code>-b, --cookie &lt;FILE&gt;</code></a> | Read cookies from FILE (using the Netscape cookie file format).<br><br>Combined with [`-c, --cookie-jar`](#cookie-jar), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#cookie-jar" id="cookie-jar"><code>-c, --cookie-jar &lt;FILE&gt;</code></a> | Write cookies to FILE after running the session (only for one session).<br>The file will be written using the Netscape cookie file format.<br><br>Combined with [`-b, --cookie`](#cookie), you can simulate a cookie storage between successive Hurl runs.<br> |
| <a href="#fail-at-end" id="fail-at-end"><code>--fail-at-end</code></a> | Continue executing requests to the end of the Hurl file even when an assert error occurs.<br>By default, Hurl exits after an assert error in the HTTP response.<br><br>Note that this option does not affect the behavior with multiple input Hurl files.<br><br>All the input files are executed independently. The result of one file does not affect the execution of the other Hurl files.<br> |
| <a href="#file-root" id="file-root"><code>--file-root &lt;DIR&gt;</code></a> | Set root file system to import files in Hurl. This is used for both files in multipart form data and request body.<br>When this is not explicitly defined, the files are relative to the current directory in which Hurl is running.<br> |
| <a href="#location" id="location"><code>-L, --location</code></a> | Follow redirect. To limit the amount of redirects to follow use the [`--max-redirs`](#max-redirs) option<br> |
| <a href="#glob" id="glob"><code>--glob &lt;GLOB&gt;</code></a> | Specify input files that match the given glob pattern.<br><br>Multiple glob flags may be used. This flag supports common Unix glob patterns like *, ? and []. <br>However, to avoid your shell accidentally expanding glob patterns before Hurl handles them, you must use single quotes or double quotes around each pattern.<br> |
| <a href="#include" id="include"><code>-i, --include</code></a> | Include the HTTP headers in the output (last entry).<br> |
| <a href="#ignore-asserts" id="ignore-asserts"><code>--ignore-asserts</code></a> | Ignore all asserts defined in the Hurl file.<br> |
| <a href="#insecure" id="insecure"><code>-k, --insecure</code></a> | This option explicitly allows Hurl to perform "insecure" SSL connections and transfers.<br> |
| <a href="#interactive" id="interactive"><code>--interactive</code></a> | Stop between requests.<br>This is similar to a break point, You can then continue (Press C) or quit (Press Q).<br> |
| <a href="#json" id="json"><code>--json</code></a> | Output each hurl file result to JSON. The format is very closed to HAR format. <br> |
| <a href="#key" id="key"><code>--key &lt;KEY&gt;</code></a> | Private key file name.<br> |
| <a href="#max-redirs" id="max-redirs"><code>--max-redirs &lt;NUM&gt;</code></a> | Set maximum number of redirection-followings allowed<br>By default, the limit is set to 50 redirections. Set this option to -1 to make it unlimited.<br> |
| <a href="#max-time" id="max-time"><code>-m, --max-time &lt;SECONDS&gt;</code></a> | Maximum time in seconds that you allow a request/response to take. This is the standard timeout.<br><br>See also [`--connect-timeout`](#connect-timeout).<br> |
| <a href="#no-color" id="no-color"><code>--no-color</code></a> | Do not colorize output.<br> |
| <a href="#no-output" id="no-output"><code>--no-output</code></a> | Suppress output. By default, Hurl outputs the body of the last response.<br> |
| <a href="#noproxy" id="noproxy"><code>--noproxy &lt;HOST(S)&gt;</code></a> | Comma-separated list of hosts which do not use a proxy.<br>Override value from Environment variable no_proxy.<br> |
| <a href="#output" id="output"><code>-o, --output &lt;FILE&gt;</code></a> | Write output to FILE instead of stdout.<br> |
| <a href="#proxy" id="proxy"><code>-x, --proxy &lt;[PROTOCOL://]HOST[:PORT]&gt;</code></a> | Use the specified proxy.<br> |
| <a href="#report-junit" id="report-junit"><code>--report-junit &lt;FILE&gt;</code></a> | Generate JUnit File.<br><br>If the FILE report already exists, it will be updated with the new test results.<br> |
| <a href="#report-html" id="report-html"><code>--report-html &lt;DIR&gt;</code></a> | Generate HTML report in DIR.<br><br>If the HTML report already exists, it will be updated with the new test results.<br> |
| <a href="#resolve" id="resolve"><code>--resolve &lt;HOST:PORT:ADDR&gt;</code></a> | Provide a custom address for a specific host and port pair. Using this, you can make the Hurl requests(s) use a specified address and prevent the otherwise normally resolved address to be used. Consider it a sort of /etc/hosts alternative provided on the command line.<br> |
| <a href="#retry" id="retry"><code>--retry</code></a> | Retry requests if any error occurs (asserts, captures, runtimes etc...).<br> |
| <a href="#retry-interval" id="retry-interval"><code>--retry-interval &lt;MILLISECONDS&gt;</code></a> | Duration in milliseconds between each retry. Default is 1000 ms.<br> |
| <a href="#retry-max-count" id="retry-max-count"><code>--retry-max-count &lt;NUM&gt;</code></a> | Maximum number of retries. Set this option to -1 to make it unlimited. Default is 10.<br> |
| <a href="#ssl-no-revoke" id="ssl-no-revoke"><code>--ssl-no-revoke</code></a> | (Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.<br> |
| <a href="#test" id="test"><code>--test</code></a> | Activate test mode: with this, the HTTP response is not outputted anymore, progress is reported for each Hurl file tested, and a text summary is displayed when all files have been run.<br> |
| <a href="#to-entry" id="to-entry"><code>--to-entry &lt;ENTRY_NUMBER&gt;</code></a> | Execute Hurl file to ENTRY_NUMBER (starting at 1).<br>Ignore the remaining of the file. It is useful for debugging a session.<br> |
| <a href="#user" id="user"><code>-u, --user &lt;USER:PASSWORD&gt;</code></a> | Add basic Authentication header to each request.<br> |
| <a href="#user-agent" id="user-agent"><code>-A, --user-agent &lt;NAME&gt;</code></a> | Specify the User-Agent string to send to the HTTP server.<br> |
| <a href="#variable" id="variable"><code>--variable &lt;NAME=VALUE&gt;</code></a> | Define variable (name/value) to be used in Hurl templates.<br> |
| <a href="#variables-file" id="variables-file"><code>--variables-file &lt;FILE&gt;</code></a> | Set properties file in which your define your variables.<br><br>Each variable is defined as name=value exactly as with [`--variable`](#variable) option.<br><br>Note that defining a variable twice produces an error.<br> |
| <a href="#verbose" id="verbose"><code>-v, --verbose</code></a> | Turn on verbose output on standard error stream.<br>Useful for debugging.<br><br>A line starting with '>' means data sent by Hurl.<br>A line staring with '<' means data received by Hurl.<br>A line starting with '*' means additional info provided by Hurl.<br><br>If you only want HTTP headers in the output, [`-i, --include`](#include) might be the option you're looking for.<br> |
| <a href="#very-verbose" id="very-verbose"><code>--very-verbose</code></a> | Turn on more verbose output on standard error stream.<br><br>In contrast to [`--verbose`](#verbose) option, this option outputs the full HTTP body request and response on standard error. In addition, lines starting with '**' are libcurl debug logs.<br> |
| <a href="#help" id="help"><code>-h, --help</code></a> | Usage help. This lists all current command line options with a short description.<br> |
| <a href="#version" id="version"><code>-V, --version</code></a> | Prints version information<br> |
## Environment
@ -810,23 +900,23 @@ Environment variables can only be specified in lowercase.
Using an environment variable to set the proxy has the same effect as using the [`-x, --proxy`](#proxy) option.
Variable | Description
--- | ---
`http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br/>
`https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br/>
`all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br/>
`no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br/>
`HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br/>
`NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br/>
| Variable | Description |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `http_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTP.<br> |
| `https_proxy [protocol://]<host>[:port]` | Sets the proxy server to use for HTTPS.<br> |
| `all_proxy [protocol://]<host>[:port]` | Sets the proxy server to use if no protocol-specific proxy is set.<br> |
| `no_proxy <comma-separated list of hosts>` | List of host names that shouldn't go through any proxy.<br> |
| `HURL_name value` | Define variable (name/value) to be used in Hurl templates. This is similar than [`--variable`](#variable) and [`--variables-file`](#variables-file) options.<br> |
| `NO_COLOR` | When set to a non-empty string, do not colorize output (see [`--no-color`](#no-color) option).<br> |
## Exit Codes
Value | Description
--- | ---
`1` | Failed to parse command-line options.<br/>
`2` | Input File Parsing Error.<br/>
`3` | Runtime error (such as failure to connect to host).<br/>
`4` | Assert Error.<br/>
| Value | Description |
|-------|---------------------------------------------------------|
| `1` | Failed to parse command-line options.<br> |
| `2` | Input File Parsing Error.<br> |
| `3` | Runtime error (such as failure to connect to host).<br> |
| `4` | Assert Error.<br> |
## WWW
@ -843,12 +933,12 @@ curl(1) hurlfmt(1)
### Linux
Precompiled binary is available at [hurl-1.8.0-x86_64-linux.tar.gz]:
Precompiled binary is available at [hurl-2.0.0-x86_64-linux.tar.gz]:
```shell
$ INSTALL_DIR=/tmp
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
$ curl -sL https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-2.0.0:$PATH
```
#### Debian / Ubuntu
@ -856,8 +946,8 @@ $ export PATH=$INSTALL_DIR/hurl-1.8.0:$PATH
For Debian / Ubuntu, Hurl can be installed using a binary .deb file provided in each Hurl release.
```shell
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl_1.8.0_amd64.deb
$ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
$ curl -LO https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl_2.0.0_amd64.deb
$ sudo apt update && apt install ./hurl_2.0.0_amd64.deb
```
#### Arch Linux / Manjaro
@ -870,7 +960,7 @@ $ sudo apt update && apt install ./hurl_1.8.0_amd64.deb
### macOS
Precompiled binary is available at [hurl-1.8.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-1.8.0-arm64-macos.tar.gz] for ARM CPUS.
Precompiled binary is available at [hurl-2.0.0-x86_64-macos.tar.gz] for x86 CPUs and [hurl-2.0.0-arm64-macos.tar.gz] for ARM CPUS.
#### Homebrew
@ -894,11 +984,11 @@ $ sudo pkg install hurl
#### Zip File
Hurl can be installed from a standalone zip file [hurl-1.8.0-win64.zip]. You will need to update your `PATH` variable.
Hurl can be installed from a standalone zip file [hurl-2.0.0-win64.zip]. You will need to update your `PATH` variable.
#### Installer
An installer [hurl-1.8.0-win64-installer.exe] is also available.
An installer [hurl-2.0.0-win64-installer.exe] is also available.
#### Chocolatey
@ -1011,7 +1101,7 @@ Please follow the [contrib on Windows section].
[libcurl]: https://curl.se/libcurl/
[JSON body]: https://hurl.dev/docs/request.html#json-body
[XML body]: https://hurl.dev/docs/request.html#xml-body
[multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
[XML multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
[predicates]: https://hurl.dev/docs/asserting-response.html#predicates
[JSONPath]: https://goessner.net/articles/JsonPath/
[Basic authentication]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme
@ -1022,12 +1112,13 @@ Please follow the [contrib on Windows section].
[curl]: https://curl.se
[entry]: https://hurl.dev/docs/entry.html
[`--test` option]: https://hurl.dev/docs/manual.html#test
[Hurl templates]: https://hurl.dev/docs/templates.html
[GitHub]: https://github.com/Orange-OpenSource/hurl
[hurl-1.8.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64.zip
[hurl-1.8.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-win64-installer.exe
[hurl-1.8.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-macos.tar.gz
[hurl-1.8.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-arm64-macos.tar.gz
[hurl-1.8.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/1.8.0/hurl-1.8.0-x86_64-linux.tar.gz
[hurl-2.0.0-win64.zip]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64.zip
[hurl-2.0.0-win64-installer.exe]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-win64-installer.exe
[hurl-2.0.0-x86_64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-macos.tar.gz
[hurl-2.0.0-arm64-macos.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-arm64-macos.tar.gz
[hurl-2.0.0-x86_64-linux.tar.gz]: https://github.com/Orange-OpenSource/hurl/releases/download/2.0.0/hurl-2.0.0-x86_64-linux.tar.gz
[AUR]: https://wiki.archlinux.org/index.php/Arch_User_Repository
[`hurl-bin` package]: https://aur.archlinux.org/packages/hurl-bin/
[install]: https://www.rust-lang.org/tools/install

View File

@ -112,16 +112,16 @@ pub fn app(version: &str) -> Command {
)
.arg(
clap::Arg::new("client_cert_file")
.short('E')
.long("cert")
.value_name("FILE")
.value_name("CERTIFICATE[:PASSWORD]")
.help("Client certificate file and password")
.num_args(1)
.short('E')
)
.arg(
clap::Arg::new("client_key_file")
.long("key")
.value_name("FILE")
.value_name("KEY")
.help("Private key file name")
.num_args(1)
)
@ -343,7 +343,7 @@ pub fn app(version: &str) -> Command {
.arg(
clap::Arg::new("ssl_no_revoke")
.long("ssl-no-revoke")
.help("(Schannel) This option tells curl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.")
.help("(Windows) This option tells Hurl to disable certificate revocation checks. WARNING: this option loosens the SSL security, and by using this flag you ask for exactly that.")
.action(ArgAction::SetTrue)
)
.arg(