2022-05-31 15:36:54 +03:00
|
|
|
# Samples
|
|
|
|
|
|
|
|
To run a sample, edit a file with the sample content, and run Hurl:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
$ vi sample.hurl
|
|
|
|
|
|
|
|
GET https://example.org
|
|
|
|
|
|
|
|
$ hurl sample.hurl
|
|
|
|
```
|
|
|
|
|
2022-06-11 13:49:24 +03:00
|
|
|
By default, Hurl behaves like [curl] and outputs the last HTTP response's [entry]. To have a test
|
|
|
|
oriented output, you can use [`--test` option]:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
$ hurl --test sample.hurl
|
|
|
|
```
|
|
|
|
|
|
|
|
|
2022-05-31 15:36:54 +03:00
|
|
|
You can check [Hurl tests suite] for more samples.
|
|
|
|
|
|
|
|
## Getting Data
|
|
|
|
|
|
|
|
A simple GET:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#method)
|
|
|
|
|
|
|
|
### HTTP Headers
|
|
|
|
|
|
|
|
A simple GET with headers:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/news
|
|
|
|
User-Agent: Mozilla/5.0
|
|
|
|
Accept: */*
|
|
|
|
Accept-Language: en-US,en;q=0.5
|
|
|
|
Accept-Encoding: gzip, deflate, br
|
|
|
|
Connection: keep-alive
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#headers)
|
|
|
|
|
|
|
|
### Query Params
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/news
|
|
|
|
[QueryStringParams]
|
|
|
|
order: newest
|
|
|
|
search: something to search
|
|
|
|
count: 100
|
|
|
|
```
|
|
|
|
|
|
|
|
Or:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/news?order=newest&search=something%20to%20search&count=100
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#query-parameters)
|
|
|
|
|
2022-08-29 00:26:11 +03:00
|
|
|
### Basic Authentication
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/protected
|
|
|
|
[BasicAuth]
|
|
|
|
bob: secret
|
|
|
|
```
|
|
|
|
|
2022-09-02 16:48:41 +03:00
|
|
|
[Doc](/docs/request.md#basic-authentication)
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
This is equivalent to construct the request with a [Authorization] header:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
# Authorization header value can be computed with `echo -n 'bob:secret' | base64`
|
|
|
|
GET https://example.org/protected
|
|
|
|
Authorization: Basic Ym9iOnNlY3JldA==
|
|
|
|
```
|
|
|
|
|
2022-09-02 16:48:41 +03:00
|
|
|
Basic authentication allows per request authentication.
|
2022-09-28 11:24:24 +03:00
|
|
|
If you want to add basic authentication to all the requests of a Hurl file
|
2022-05-31 15:36:54 +03:00
|
|
|
you could use [`-u/--user` option].
|
|
|
|
|
|
|
|
## Sending Data
|
|
|
|
|
2022-09-25 13:52:24 +03:00
|
|
|
### Sending HTML Form Data
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
```hurl
|
|
|
|
POST https://example.org/contact
|
|
|
|
[FormParams]
|
|
|
|
default: false
|
|
|
|
token: {{token}}
|
|
|
|
email: john.doe@rookie.org
|
|
|
|
number: 33611223344
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#form-parameters)
|
|
|
|
|
2022-09-25 13:52:24 +03:00
|
|
|
### Sending Multipart Form Data
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
```hurl
|
|
|
|
POST https://example.org/upload
|
|
|
|
[MultipartFormData]
|
|
|
|
field1: value1
|
|
|
|
field2: file,example.txt;
|
2022-11-05 20:43:29 +03:00
|
|
|
# One can specify the file content type:
|
2022-05-31 15:36:54 +03:00
|
|
|
field3: file,example.zip; application/zip
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#multipart-form-data)
|
|
|
|
|
|
|
|
### Posting a JSON Body
|
|
|
|
|
|
|
|
With an inline JSON:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
POST https://example.org/api/tests
|
|
|
|
{
|
|
|
|
"id": "456",
|
|
|
|
"evaluate": true
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#json-body)
|
|
|
|
|
|
|
|
With a local file:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
POST https://example.org/api/tests
|
|
|
|
Content-Type: application/json
|
|
|
|
file,data.json;
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#file-body)
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
### Templating a JSON Body
|
2022-05-31 15:36:54 +03:00
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
```hurl
|
2022-05-31 15:36:54 +03:00
|
|
|
PUT https://example.org/api/hits
|
|
|
|
Content-Type: application/json
|
|
|
|
{
|
|
|
|
"key0": "{{a_string}}",
|
|
|
|
"key1": {{a_bool}},
|
|
|
|
"key2": {{a_null}},
|
|
|
|
"key3": {{a_number}}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Variables can be initialized via command line:
|
|
|
|
|
|
|
|
```shell
|
|
|
|
$ hurl --variable a_string=apple \
|
|
|
|
--variable a_bool=true \
|
|
|
|
--variable a_null=null \
|
|
|
|
--variable a_number=42 \
|
|
|
|
test.hurl
|
|
|
|
```
|
|
|
|
|
|
|
|
Resulting in a PUT request with the following JSON body:
|
|
|
|
|
|
|
|
```
|
|
|
|
{
|
|
|
|
"key0": "apple",
|
|
|
|
"key1": true,
|
|
|
|
"key2": null,
|
|
|
|
"key3": 42
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
[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>
|
|
|
|
```
|
|
|
|
~~~
|
|
|
|
|
2022-11-05 20:43:29 +03:00
|
|
|
[Doc](/docs/request.md#multiline-string-body)
|
2022-05-31 15:36:54 +03:00
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
### 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)
|
|
|
|
|
2022-05-31 15:36:54 +03:00
|
|
|
## Testing Response
|
|
|
|
|
|
|
|
### Testing Response Headers
|
|
|
|
|
|
|
|
Use implicit response asserts to test header values:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/index.html
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
Set-Cookie: theme=light
|
|
|
|
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#headers)
|
|
|
|
|
|
|
|
|
|
|
|
Or use explicit response asserts with [predicates]:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 302
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
header "Location" contains "www.example.net"
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#header-assert)
|
|
|
|
|
|
|
|
|
2022-09-28 11:34:00 +03:00
|
|
|
### Testing REST APIs
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
Asserting JSON body response (node values, collection count etc...) with [JSONPath]:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/order
|
|
|
|
screencapability: low
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
jsonpath "$.validated" == true
|
|
|
|
jsonpath "$.userInfo.firstName" == "Franck"
|
|
|
|
jsonpath "$.userInfo.lastName" == "Herbert"
|
|
|
|
jsonpath "$.hasDevice" == false
|
|
|
|
jsonpath "$.links" count == 12
|
|
|
|
jsonpath "$.state" != null
|
|
|
|
jsonpath "$.order" matches "^order-\\d{8}$"
|
2022-09-25 13:52:24 +03:00
|
|
|
jsonpath "$.order" matches /^order-\d{8}$/ # Alternative syntax with regex literal
|
2022-05-31 15:36:54 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#jsonpath-assert)
|
|
|
|
|
|
|
|
|
|
|
|
Testing status code:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/order/435
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#version-status)
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/order/435
|
|
|
|
|
|
|
|
# Testing status code is in a 200-300 range
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP *
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
status >= 200
|
|
|
|
status < 300
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#status-assert)
|
|
|
|
|
|
|
|
|
|
|
|
### Testing HTML Response
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
Content-Type: text/html; charset=UTF-8
|
|
|
|
|
|
|
|
[Asserts]
|
|
|
|
xpath "string(/html/head/title)" contains "Example" # Check title
|
|
|
|
xpath "count(//p)" == 2 # Check the number of p
|
|
|
|
xpath "//p" count == 2 # Similar assert for p
|
|
|
|
xpath "boolean(count(//h2))" == false # Check there is no h2
|
|
|
|
xpath "//h2" not exists # Similar assert for h2
|
|
|
|
xpath "string(//div[1])" matches /Hello.*/
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#xpath-assert)
|
|
|
|
|
|
|
|
### Testing Set-Cookie Attributes
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET http://myserver.com/home
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
|
|
|
|
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
|
|
|
|
cookie "JSESSIONID[Expires]" contains "Wed, 13 Jan 2021"
|
|
|
|
cookie "JSESSIONID[Secure]" exists
|
|
|
|
cookie "JSESSIONID[HttpOnly]" exists
|
|
|
|
cookie "JSESSIONID[SameSite]" == "Lax"
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#cookie-assert)
|
|
|
|
|
|
|
|
### Testing Bytes Content
|
|
|
|
|
|
|
|
|
|
|
|
Check the SHA-256 response body hash:
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/data.tar.gz
|
|
|
|
|
2023-01-28 16:04:10 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#sha-256-assert)
|
|
|
|
|
|
|
|
|
|
|
|
## Others
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
### 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)
|
|
|
|
|
2022-10-24 21:58:56 +03:00
|
|
|
### Polling and Retry
|
|
|
|
|
|
|
|
Retry request on any errors (asserts, captures, status code, runtime etc...):
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
# Create a new job
|
|
|
|
POST https://api.example.org/jobs
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 201
|
2022-10-24 21:58:56 +03:00
|
|
|
[Captures]
|
|
|
|
job_id: jsonpath "$.id"
|
|
|
|
[Asserts]
|
|
|
|
jsonpath "$.state" == "RUNNING"
|
|
|
|
|
|
|
|
|
|
|
|
# Pull job status until it is completed
|
|
|
|
GET https://api.example.org/jobs/{{job_id}}
|
|
|
|
[Options]
|
|
|
|
retry: true
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-10-24 21:58:56 +03:00
|
|
|
[Asserts]
|
|
|
|
jsonpath "$.state" == "COMPLETED"
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/entry.md#retry)
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-05-31 15:36:54 +03:00
|
|
|
### Testing Endpoint Performance
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://sample.org/helloworld
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP *
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
duration < 1000 # Check that response time is less than one second
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#duration-assert)
|
|
|
|
|
2022-09-28 11:34:00 +03:00
|
|
|
### Using SOAP APIs
|
2022-05-31 15:36:54 +03:00
|
|
|
|
|
|
|
```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>
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/request.md#xml-body)
|
|
|
|
|
|
|
|
### Capturing and Using a CSRF Token
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
[Captures]
|
|
|
|
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
|
|
|
|
|
2023-01-28 16:04:10 +03:00
|
|
|
|
2022-05-31 15:36:54 +03:00
|
|
|
POST https://example.org/login?user=toto&password=1234
|
|
|
|
X-CSRF-TOKEN: {{csrf_token}}
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 302
|
2022-05-31 15:36:54 +03:00
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/capturing-response.md#xpath-capture)
|
|
|
|
|
|
|
|
### Checking Byte Order Mark (BOM) in Response Body
|
|
|
|
|
|
|
|
```hurl
|
|
|
|
GET https://example.org/data.bin
|
|
|
|
|
2022-12-19 23:30:08 +03:00
|
|
|
HTTP 200
|
2022-05-31 15:36:54 +03:00
|
|
|
[Asserts]
|
|
|
|
bytes startsWith hex,efbbbf;
|
|
|
|
```
|
|
|
|
|
|
|
|
[Doc](/docs/asserting-response.md#bytes-assert)
|
|
|
|
|
|
|
|
|
|
|
|
[JSON body]: /docs/request.md#json-body
|
|
|
|
[XML body]: /docs/request.md#xml-body
|
2022-12-19 23:30:08 +03:00
|
|
|
[XML multiline string body]: /docs/request.md#multiline-string-body
|
2022-05-31 15:36:54 +03:00
|
|
|
[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
|
|
|
|
[`Authorization` header]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
|
|
|
|
[Hurl tests suite]: https://github.com/Orange-OpenSource/hurl/tree/master/integration/tests_ok
|
|
|
|
[Authorization]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
|
2022-09-02 15:45:54 +03:00
|
|
|
[`-u/--user` option]: /docs/manual.md#user
|
2022-06-11 13:49:24 +03:00
|
|
|
[curl]: https://curl.se
|
|
|
|
[entry]: /docs/entry.md
|
2022-09-28 11:24:24 +03:00
|
|
|
[`--test` option]: /docs/manual.md#test
|
2022-12-19 23:30:08 +03:00
|
|
|
[Hurl templates]: /docs/templates.md
|