2023-09-26 18:30:49 +03:00
< picture >
2023-10-20 17:43:41 +03:00
< source media = "(prefers-color-scheme: light)" srcset = "/docs/assets/img/logo-light.svg?sanitize=true" >
< source media = "(prefers-color-scheme: dark)" srcset = "/docs/assets/img/logo-dark.svg?sanitize=true" >
< img src = "/docs/assets/img/logo-light.svg?sanitize=true" width = "264px" alt = "Hurl Logo" >
2023-09-26 18:30:49 +03:00
< / picture >
2023-09-21 13:56:10 +03:00
2022-11-22 10:03:13 +03:00
[![deploy status ](https://github.com/Orange-OpenSource/hurl/workflows/test/badge.svg )](https://github.com/Orange-OpenSource/hurl/actions)
2022-08-11 23:51:17 +03:00
[![coverage ](https://Orange-OpenSource.github.io/hurl/coverage/badges/flat.svg )](https://Orange-OpenSource.github.io/hurl/coverage)
2021-07-03 15:17:29 +03:00
[![Crates.io ](https://img.shields.io/crates/v/hurl.svg )](https://crates.io/crates/hurl)
2022-08-19 12:04:16 +03:00
[![documentation ](https://img.shields.io/badge/-documentation-ff0288 )](https://hurl.dev)
2020-08-27 10:07:46 +03:00
2021-11-12 18:25:13 +03:00
# What's Hurl?
2020-08-27 10:07:46 +03:00
2021-10-22 16:04:50 +03:00
Hurl is a command line tool that runs < b > HTTP requests< / b > defined in a simple < b > plain text format< / b > .
2022-10-24 21:58:56 +03:00
It can chain requests, capture values and evaluate queries on headers and body response. Hurl is very
2022-12-19 23:30:08 +03:00
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.
2020-08-27 10:07:46 +03:00
```hurl
# Get home:
2022-05-31 15:38:37 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 200
2020-08-27 10:07:46 +03:00
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
2022-12-19 23:30:08 +03:00
2020-08-27 10:07:46 +03:00
# Do login!
2022-05-31 15:38:37 +03:00
POST https://example.org/login?user=toto& password=1234
2020-08-27 10:07:46 +03:00
X-CSRF-TOKEN: {{csrf_token}}
2022-12-19 23:30:08 +03:00
HTTP 302
2020-08-27 10:07:46 +03:00
```
Chaining multiple requests is easy:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/api/health
GET https://example.org/api/step1
GET https://example.org/api/step2
GET https://example.org/api/step3
2020-08-27 10:07:46 +03:00
```
2021-11-12 18:25:13 +03:00
# Also an HTTP Test Tool
2020-08-27 10:07:46 +03:00
2021-10-22 16:04:50 +03:00
Hurl can run HTTP requests but can also be used to < b > test HTTP responses< / b > .
2022-05-31 15:38:37 +03:00
Different types of queries and predicates are supported, from [XPath] and [JSONPath] on body response,
2021-10-22 16:04:50 +03:00
to assert on status code and response headers.
2020-08-27 10:07:46 +03:00
2023-10-20 17:43:41 +03:00
< a href = "https://hurl.dev/player.html?id=hurl&speed=3" > < img src = "/docs/assets/img/poster-hurl.png" width = "100%" alt = "Hurl Demo" / > < / a >
2022-10-10 18:42:28 +03:00
2022-09-28 11:34:00 +03:00
It is well adapted for < b > REST / JSON APIs< / b >
2020-08-27 10:07:46 +03:00
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/api/tests
2020-08-27 10:07:46 +03:00
{
2022-02-11 16:29:06 +03:00
"id": "4568",
2020-08-27 10:07:46 +03:00
"evaluate": true
}
2022-12-19 23:30:08 +03:00
HTTP 200
2020-08-27 10:07:46 +03:00
[Asserts]
2022-02-15 20:28:49 +03:00
header "X-Frame-Options" == "SAMEORIGIN"
2021-10-22 16:04:50 +03:00
jsonpath "$.status" == "RUNNING" # Check the status code
jsonpath "$.tests" count == 25 # Check the number of items
2022-02-11 16:29:06 +03:00
jsonpath "$.id" matches /\d{4}/ # Check the format of the id
2020-08-27 10:07:46 +03:00
```
2021-10-22 16:04:50 +03:00
< b > HTML content< / b >
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 200
2021-10-22 16:04:50 +03:00
[Asserts]
xpath "normalize-space(//head/title)" == "Hello world!"
```
2022-12-19 23:30:08 +03:00
< 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 >
2020-08-27 10:07:46 +03:00
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/InStock
2020-08-27 10:07:46 +03:00
Content-Type: application/soap+xml; charset=utf-8
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
<?xml version="1.0" encoding="UTF-8"?>
2022-05-31 15:38:37 +03:00
< soap:Envelope xmlns:soap = "http://www.w3.org/2003/05/soap-envelope" xmlns:m = "https://example.org" >
2020-08-27 10:07:46 +03:00
< 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
2020-08-27 10:07:46 +03:00
```
2023-06-29 17:05:31 +03:00
Hurl can also be used to test the < b > performance< / b > of HTTP endpoints
2021-02-12 10:52:29 +03:00
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/api/v1/pets
2022-12-19 23:30:08 +03:00
HTTP 200
2021-02-12 10:52:29 +03:00
[Asserts]
2021-09-11 23:38:55 +03:00
duration < 1000 # Duration in ms
2021-02-12 10:52:29 +03:00
```
2023-06-29 17:05:31 +03:00
And check response bytes
2021-10-22 16:04:50 +03:00
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/data.tar.gz
2022-12-19 23:30:08 +03:00
HTTP 200
2021-10-22 16:04:50 +03:00
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
2021-02-12 10:52:29 +03:00
2023-10-20 17:43:41 +03:00
Finally, Hurl is easy to < b > integrate in CI/CD< / b > , with text, JUnit, TAP and HTML reports
2023-06-29 17:05:31 +03:00
2023-10-20 17:43:41 +03:00
< picture >
< source media = "(prefers-color-scheme: light)" srcset = "/docs/assets/img/home-waterfall-light.png" >
< source media = "(prefers-color-scheme: dark)" srcset = "/docs/assets/img/home-waterfall-dark.png" >
< img src = "/docs/assets/img/home-waterfall-light.png" width = "480" alt = "HTML report" / >
< / picture >
2021-02-12 10:52:29 +03:00
2021-11-12 18:25:13 +03:00
# Why Hurl?
2021-02-12 10:52:29 +03:00
2021-10-22 16:04:50 +03:00
< ul class = "showcase-container" >
2023-07-21 14:54:15 +03:00
< li > < b > Text Format:< / b > for both devops and developers< / li >
< li > < b > Fast CLI:< / b > a command line for local dev and continuous integration< / li >
< li > < b > Single Binary:< / b > easy to install, with no runtime required< / li >
2021-10-22 16:04:50 +03:00
< / ul >
2021-11-12 18:25:13 +03:00
# Powered by curl
2021-10-22 16:04:50 +03:00
2022-05-31 15:38:37 +03:00
Hurl is a lightweight binary written in [Rust]. Under the hood, Hurl HTTP engine is
2022-09-28 11:24:24 +03:00
powered by [libcurl], one of the most powerful and reliable file transfer libraries.
With its text file format, Hurl adds syntactic sugar to run and test HTTP requests,
2023-10-20 17:43:41 +03:00
but it's still the [curl] that we love: __fast__ , __efficient__ and __HTTP/3 ready__ .
2021-10-22 16:04:50 +03:00
2021-11-12 18:25:13 +03:00
# Feedbacks
2021-10-22 16:04:50 +03:00
2023-04-14 10:46:27 +03:00
To support its development, [star Hurl on GitHub]!
[Feedback, suggestion, bugs or improvements] are welcome.
2021-10-22 16:04:50 +03:00
```hurl
POST https://hurl.dev/api/feedback
{
"name": "John Doe",
2023-06-28 17:14:06 +03:00
"feedback": "Hurl is awesome!"
2021-10-22 16:04:50 +03:00
}
2022-12-19 23:30:08 +03:00
HTTP 200
2021-10-22 16:04:50 +03:00
```
2021-11-12 18:25:13 +03:00
# Resources
2021-02-12 10:52:29 +03:00
2021-10-22 16:04:50 +03:00
[License]
2020-08-27 10:07:46 +03:00
2022-02-10 23:51:21 +03:00
[Blog]
2022-06-13 09:06:13 +03:00
[Tutorial]
2021-10-22 16:04:50 +03:00
[Documentation]
2020-08-27 10:07:46 +03:00
2021-10-22 16:04:50 +03:00
[GitHub]
2020-08-27 10:07:46 +03:00
2021-11-08 17:51:59 +03:00
Table of Contents
=================
* [Samples ](#samples )
* [Getting Data ](#getting-data )
2021-11-20 22:27:44 +03:00
* [HTTP Headers ](#http-headers )
2021-11-08 17:51:59 +03:00
* [Query Params ](#query-params )
2022-08-29 00:26:11 +03:00
* [Basic Authentication ](#basic-authentication )
2024-04-03 15:24:25 +03:00
* [Passing Data between Requests ](#passing-data-between-requests )
2021-11-08 17:51:59 +03:00
* [Sending Data ](#sending-data )
2022-09-25 13:52:24 +03:00
* [Sending HTML Form Data ](#sending-html-form-data )
* [Sending Multipart Form Data ](#sending-multipart-form-data )
2021-11-08 17:51:59 +03:00
* [Posting a JSON Body ](#posting-a-json-body )
2022-12-19 23:30:08 +03:00
* [Templating a JSON Body ](#templating-a-json-body )
* [Templating a XML Body ](#templating-a-xml-body )
* [Using GraphQL Query ](#using-graphql-query )
2021-11-08 17:51:59 +03:00
* [Testing Response ](#testing-response )
2024-04-03 15:24:25 +03:00
* [Testing Status Code ](#testing-status-code )
2021-11-08 17:51:59 +03:00
* [Testing Response Headers ](#testing-response-headers )
2022-09-28 11:34:00 +03:00
* [Testing REST APIs ](#testing-rest-apis )
2021-11-08 17:51:59 +03:00
* [Testing HTML Response ](#testing-html-response )
* [Testing Set-Cookie Attributes ](#testing-set-cookie-attributes )
2022-05-12 17:25:29 +03:00
* [Testing Bytes Content ](#testing-bytes-content )
2023-05-03 12:52:31 +03:00
* [SSL Certificate ](#ssl-certificate )
2024-04-03 15:24:25 +03:00
* [Checking Full Body ](#checking-full-body )
* [Reports ](#reports )
* [HTML Report ](#html-report )
* [JUnit Report ](#junit-report )
* [TAP Report ](#tap-report )
* [JSON Output ](#json-output )
2021-11-08 17:51:59 +03:00
* [Others ](#others )
2022-12-19 23:30:08 +03:00
* [HTTP Version ](#http-version )
2022-10-24 21:58:56 +03:00
* [Polling and Retry ](#polling-and-retry )
2024-01-17 15:34:16 +03:00
* [Delaying Requests ](#delaying-requests )
* [Skipping Requests ](#skipping-requests )
2021-11-08 17:51:59 +03:00
* [Testing Endpoint Performance ](#testing-endpoint-performance )
2022-09-28 11:34:00 +03:00
* [Using SOAP APIs ](#using-soap-apis )
2021-11-08 17:51:59 +03:00
* [Capturing and Using a CSRF Token ](#capturing-and-using-a-csrf-token )
* [Checking Byte Order Mark (BOM) in Response Body ](#checking-byte-order-mark-bom-in-response-body )
2023-09-19 16:12:18 +03:00
* [AWS Signature Version 4 Requests ](#aws-signature-version-4-requests )
2022-09-02 01:29:23 +03:00
* [Manual ](#manual )
2021-11-08 17:51:59 +03:00
* [Name ](#name )
* [Synopsis ](#synopsis )
* [Description ](#description )
* [Hurl File Format ](#hurl-file-format )
* [Capturing values ](#capturing-values )
* [Asserts ](#asserts )
* [Options ](#options )
* [Environment ](#environment )
* [Exit Codes ](#exit-codes )
* [WWW ](#www )
* [See Also ](#see-also )
* [Installation ](#installation )
* [Binaries Installation ](#binaries-installation )
* [Linux ](#linux )
* [Debian / Ubuntu ](#debian--ubuntu )
2023-09-13 15:05:36 +03:00
* [Alpine ](#alpine )
2021-11-08 17:51:59 +03:00
* [Arch Linux / Manjaro ](#arch-linux--manjaro )
2022-04-13 23:09:33 +03:00
* [NixOS / Nix ](#nixos--nix )
2021-11-08 17:51:59 +03:00
* [macOS ](#macos )
2022-10-10 18:42:28 +03:00
* [Homebrew ](#homebrew )
* [MacPorts ](#macports )
2022-12-02 19:24:07 +03:00
* [FreeBSD ](#freebsd )
2021-11-08 17:51:59 +03:00
* [Windows ](#windows )
* [Zip File ](#zip-file )
* [Installer ](#installer )
2021-11-12 18:25:13 +03:00
* [Chocolatey ](#chocolatey )
* [Scoop ](#scoop )
* [Windows Package Manager ](#windows-package-manager )
2021-11-08 17:51:59 +03:00
* [Cargo ](#cargo )
2024-01-08 15:30:09 +03:00
* [conda-forge ](#conda-forge )
2021-12-16 12:32:00 +03:00
* [Docker ](#docker )
2022-05-31 15:38:37 +03:00
* [npm ](#npm )
2021-11-08 17:51:59 +03:00
* [Building From Sources ](#building-from-sources )
2022-08-29 00:26:11 +03:00
* [Build on Linux ](#build-on-linux )
2022-04-01 15:27:56 +03:00
* [Debian based distributions ](#debian-based-distributions )
* [Red Hat based distributions ](#red-hat-based-distributions )
2022-05-31 15:38:37 +03:00
* [Arch based distributions ](#arch-based-distributions )
2024-01-08 15:30:09 +03:00
* [Alpine based distributions ](#alpine-based-distributions )
2022-08-29 00:26:11 +03:00
* [Build on macOS ](#build-on-macos )
2021-11-08 17:51:59 +03:00
* [Build on Windows ](#build-on-windows )
2020-09-04 22:09:30 +03:00
# Samples
2021-12-08 16:27:22 +03:00
To run a sample, edit a file with the sample content, and run Hurl:
2021-02-12 10:52:29 +03:00
2021-11-20 22:27:44 +03:00
```shell
2021-02-12 10:52:29 +03:00
$ vi sample.hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2021-02-12 10:52:29 +03:00
$ hurl sample.hurl
```
2022-06-13 09:06:13 +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
```
2024-04-03 15:24:25 +03:00
A particular response can be saved with [`[Options] section`][option]:
```hurl
GET https://example.ord/cats/123
[Options]
output: cat123.txt # use - to output to stdout
HTTP 200
GET https://example.ord/dogs/567
HTTP 200
```
2022-06-13 09:06:13 +03:00
2022-05-31 15:38:37 +03:00
You can check [Hurl tests suite] for more samples.
2021-10-22 16:04:50 +03:00
2020-09-04 22:09:30 +03:00
## Getting Data
A simple GET:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2020-09-04 22:09:30 +03:00
```
2024-04-03 15:24:25 +03:00
Requests can be chained:
```hurl
GET https://example.org/a
GET https://example.org/b
HEAD https://example.org/c
GET https://example.org/c
```
2020-09-04 22:09:30 +03:00
[Doc ](https://hurl.dev/docs/request.html#method )
2021-11-20 22:27:44 +03:00
### HTTP Headers
2020-09-04 22:09:30 +03:00
A simple GET with headers:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/news
2021-10-22 16:04:50 +03:00
User-Agent: Mozilla/5.0
2020-09-04 22:09:30 +03:00
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
```
[Doc ](https://hurl.dev/docs/request.html#headers )
### Query Params
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/news
2020-09-04 22:09:30 +03:00
[QueryStringParams]
order: newest
search: something to search
count: 100
```
Or:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/news?order=newest& search=something%20to%20search& count=100
2020-09-04 22:09:30 +03:00
```
2024-04-03 15:24:25 +03:00
> With `[QueryStringParams]` section, params don't need to be URL escaped.
2020-09-04 22:09:30 +03:00
[Doc ](https://hurl.dev/docs/request.html#query-parameters )
2022-08-29 00:26:11 +03:00
### Basic Authentication
2022-02-11 16:29:06 +03:00
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/protected
2022-02-11 16:29:06 +03:00
[BasicAuth]
bob: secret
```
2022-09-02 16:48:41 +03:00
[Doc ](https://hurl.dev/docs/request.html#basic-authentication )
2022-02-11 16:29:06 +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`
2022-05-31 15:38:37 +03:00
GET https://example.org/protected
2022-02-11 16:29:06 +03:00
Authorization: Basic Ym9iOnNlY3JldA==
```
2024-04-03 15:24:25 +03:00
Basic authentication section allows per request authentication. If you want to add basic authentication to all the
requests of a Hurl file you could use [`-u/--user` option]:
```shell
$ hurl --user bob=secret login.hurl
```
[`--user`] option can also be set per request:
```hurl
GET https://example.org/login
[Options]
user: bob:secret
HTTP 200
GET https://example.org/login
[Options]
user: alice:secret
HTTP 200
```
### Passing Data between Requests
[Captures] can be used to pass data from one request to another:
```hurl
POST https://sample.org/orders
HTTP 201
[Captures]
order_id: jsonpath "$.order.id"
GET https://sample.org/orders/{{order_id}}
HTTP 200
```
[Doc ](https://hurl.dev/docs/capturing-response.html )
2022-02-11 16:29:06 +03:00
2020-09-04 22:09:30 +03:00
## Sending Data
2022-09-25 13:52:24 +03:00
### Sending HTML Form Data
2020-09-04 22:09:30 +03:00
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/contact
2020-09-04 22:09:30 +03:00
[FormParams]
default: false
token: {{token}}
email: john.doe@rookie.org
number: 33611223344
```
[Doc ](https://hurl.dev/docs/request.html#form-parameters )
2022-09-25 13:52:24 +03:00
### Sending Multipart Form Data
2020-09-04 22:09:30 +03:00
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/upload
2020-09-04 22:09:30 +03:00
[MultipartFormData]
field1: value1
field2: file,example.txt;
2022-11-05 20:43:29 +03:00
# One can specify the file content type:
2020-09-04 22:09:30 +03:00
field3: file,example.zip; application/zip
```
[Doc ](https://hurl.dev/docs/request.html#multipart-form-data )
2023-04-14 10:46:27 +03:00
Multipart forms can also be sent with a [multiline string body]:
~~~hurl
POST https://example.org/upload
Content-Type: multipart/form-data; boundary="boundary"
```
--boundary
Content-Disposition: form-data; name="key1"
value1
--boundary
Content-Disposition: form-data; name="upload1"; filename="data.txt"
Content-Type: text/plain
Hello World!
--boundary
Content-Disposition: form-data; name="upload2"; filename="data.html"
Content-Type: text/html
< div > Hello < b > World< / b > !< / div >
--boundary--
```
~~~
In that case, files have to be inlined in the Hurl file.
[Doc ](https://hurl.dev/docs/request.html#multiline-string-body )
2020-09-04 22:09:30 +03:00
### Posting a JSON Body
With an inline JSON:
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/api/tests
2020-09-04 22:09:30 +03:00
{
"id": "456",
"evaluate": true
}
```
[Doc ](https://hurl.dev/docs/request.html#json-body )
With a local file:
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/api/tests
2020-09-04 22:09:30 +03:00
Content-Type: application/json
file,data.json;
```
[Doc ](https://hurl.dev/docs/request.html#file-body )
2022-12-19 23:30:08 +03:00
### Templating a JSON Body
2020-09-04 22:09:30 +03:00
2022-12-19 23:30:08 +03:00
```hurl
2022-05-31 15:38:37 +03:00
PUT https://example.org/api/hits
2020-09-04 22:09:30 +03:00
Content-Type: application/json
{
"key0": "{{a_string}}",
"key1": {{a_bool}},
"key2": {{a_null}},
"key3": {{a_number}}
}
```
Variables can be initialized via command line:
2021-11-20 22:27:44 +03:00
```shell
2022-02-11 16:29:06 +03:00
$ hurl --variable a_string=apple \
--variable a_bool=true \
--variable a_null=null \
--variable a_number=42 \
2021-10-22 16:04:50 +03:00
test.hurl
2020-09-04 22:09:30 +03:00
```
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 ](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 >
```
~~~
2022-11-05 20:43:29 +03:00
[Doc ](https://hurl.dev/docs/request.html#multiline-string-body )
2020-09-04 22:09:30 +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 ](https://hurl.dev/docs/request.html#graphql-body )
2020-09-04 22:09:30 +03:00
## Testing Response
2024-04-03 15:24:25 +03:00
Responses are optional, everything after `HTTP` is part of the response asserts.
```hurl
# A request with (almost) no check:
GET https://foo.com
# A status code check:
GET https://foo.com
HTTP 200
# A test on response body
GET https://foo.com
HTTP 200
[Asserts]
jsonpath "$.state" == "running"
```
### Testing Status Code
```hurl
GET https://example.org/order/435
HTTP 200
```
[Doc ](https://hurl.dev/docs/asserting-response.html#version-status )
```hurl
GET https://example.org/order/435
# Testing status code is in a 200-300 range
HTTP *
[Asserts]
status >= 200
status < 300
```
[Doc ](https://hurl.dev/docs/asserting-response.html#status-assert )
2021-02-12 10:52:29 +03:00
### Testing Response Headers
Use implicit response asserts to test header values:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/index.html
2022-12-19 23:30:08 +03:00
HTTP 200
2021-02-12 10:52:29 +03:00
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
```
[Doc ](https://hurl.dev/docs/asserting-response.html#headers )
2021-10-22 16:04:50 +03:00
Or use explicit response asserts with [predicates]:
2021-02-12 10:52:29 +03:00
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 302
2021-02-12 10:52:29 +03:00
[Asserts]
header "Location" contains "www.example.net"
```
2021-10-22 16:04:50 +03:00
[Doc ](https://hurl.dev/docs/asserting-response.html#header-assert )
2024-04-03 15:24:25 +03:00
Implicit and explicit asserts can be combined:
```hurl
GET https://example.org/index.html
HTTP 200
Set-Cookie: theme=light
Set-Cookie: sessionToken=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT
[Asserts]
header "Location" contains "www.example.net"
```
2021-02-12 10:52:29 +03:00
2022-09-28 11:34:00 +03:00
### Testing REST APIs
2020-09-04 22:09:30 +03:00
2021-11-20 22:27:44 +03:00
Asserting JSON body response (node values, collection count etc...) with [JSONPath]:
2021-02-12 10:52:29 +03:00
2020-09-04 22:09:30 +03:00
```hurl
2021-11-20 22:27:44 +03:00
GET https://example.org/order
2020-09-04 22:09:30 +03:00
screencapability: low
2022-12-19 23:30:08 +03:00
HTTP 200
2020-09-04 22:09:30 +03:00
[Asserts]
2021-09-11 23:38:55 +03:00
jsonpath "$.validated" == true
jsonpath "$.userInfo.firstName" == "Franck"
jsonpath "$.userInfo.lastName" == "Herbert"
jsonpath "$.hasDevice" == false
jsonpath "$.links" count == 12
jsonpath "$.state" != null
2022-02-11 16:29:06 +03:00
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
2024-03-26 17:20:21 +03:00
jsonpath "$.created" isIsoDate
2020-09-04 22:09:30 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#jsonpath-assert )
2021-12-08 16:27:22 +03:00
2020-09-04 22:09:30 +03:00
### Testing HTML Response
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 200
2020-09-04 22:09:30 +03:00
Content-Type: text/html; charset=UTF-8
[Asserts]
xpath "string(/html/head/title)" contains "Example" # Check title
2021-10-22 16:04:50 +03:00
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
2022-02-11 16:29:06 +03:00
xpath "string(//div[1])" matches /Hello.*/
2020-09-04 22:09:30 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#xpath-assert )
2021-02-12 10:52:29 +03:00
### Testing Set-Cookie Attributes
```hurl
2023-05-03 12:52:31 +03:00
GET https://example.org/home
2022-12-19 23:30:08 +03:00
HTTP 200
2021-02-12 10:52:29 +03:00
[Asserts]
2021-09-11 23:38:55 +03:00
cookie "JSESSIONID" == "8400BAFE2F66443613DC38AE3D9D6239"
cookie "JSESSIONID[Value]" == "8400BAFE2F66443613DC38AE3D9D6239"
2021-02-12 10:52:29 +03:00
cookie "JSESSIONID[Expires]" contains "Wed, 13 Jan 2021"
cookie "JSESSIONID[Secure]" exists
cookie "JSESSIONID[HttpOnly]" exists
2021-09-11 23:38:55 +03:00
cookie "JSESSIONID[SameSite]" == "Lax"
2021-02-12 10:52:29 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#cookie-assert )
2022-05-12 17:25:29 +03:00
### Testing Bytes Content
Check the SHA-256 response body hash:
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/data.tar.gz
2023-01-28 16:04:10 +03:00
HTTP 200
2022-05-12 17:25:29 +03:00
[Asserts]
sha256 == hex,039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81;
```
[Doc ](https://hurl.dev/docs/asserting-response.html#sha-256-assert )
2023-05-03 12:52:31 +03:00
### SSL Certificate
Check the properties of a SSL certificate:
```hurl
GET https://example.org
HTTP 200
[Asserts]
certificate "Subject" == "CN=example.org"
certificate "Issuer" == "C=US, O=Let's Encrypt, CN=R3"
certificate "Expire-Date" daysAfterNow > 15
2023-06-28 17:14:06 +03:00
certificate "Serial-Number" matches /[\da-f]+/
2023-05-03 12:52:31 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#ssl-certificate-assert )
2024-04-03 15:24:25 +03:00
### Checking Full Body
Use implicit body to test an exact JSON body match:
```hurl
GET https://example.org/api/cats/123
HTTP 200
{
"name" : "Purrsloud",
"species" : "Cat",
"favFoods" : ["wet food", "dry food", "< strong > any< / strong > food"],
"birthYear" : 2016,
"photo" : "https://learnwebcode.github.io/json-example/images/cat-2.jpg"
}
```
[Doc ](https://hurl.dev/docs/asserting-response.html#json-body )
Or an explicit assert file:
```hurl
GET https://example.org/index.html
HTTP 200
[Asserts]
body == file,cat.json;
```
[Doc ](https://hurl.dev/docs/asserting-response.html#body-assert )
Implicit asserts supports XML body:
```hurl
GET https://example.org/api/catalog
HTTP 200
<?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 >
```
[Doc ](https://hurl.dev/docs/asserting-response.html#xml-body )
Plain text:
~~~hurl
GET https://example.org/models
HTTP 200
```
Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00
```
~~~
[Doc ](https://hurl.dev/docs/asserting-response.html#multiline-string-body )
One line:
```hurl
POST https://example.org/helloworld
HTTP 200
`Hello world!`
```
[Doc ](https://hurl.dev/docs/asserting-response.html#oneline-string-body )
File:
```hurl
GET https://example.org
HTTP 200
file,data.bin;
```
[Doc ](https://hurl.dev/docs/asserting-response.html#file-body )
## Reports
### HTML Report
```shell
$ hurl --test --report-html build/report/ *.hurl
```
[Doc ](https://hurl.dev/docs/running-tests.html#generating-report )
### JUnit Report
```shell
$ hurl --test --report-junit build/report.xml *.hurl
```
[Doc ](https://hurl.dev/docs/running-tests.html#generating-report )
### TAP Report
```shell
$ hurl --test --report-tap build/report.txt *.hurl
```
[Doc ](https://hurl.dev/docs/running-tests.html#generating-report )
### JSON Output
A structured output of running Hurl files can be obtained with [`--json` option]. Each file will produce a JSON export of the run.
```shell
$ hurl --json *.hurl
```
2022-05-12 17:25:29 +03:00
2020-09-04 22:09:30 +03:00
## Others
2022-12-19 23:30:08 +03:00
### HTTP Version
2024-04-03 15:24:25 +03:00
Testing HTTP version (HTTP/1.0, HTTP/1.1, HTTP/2 or HTTP/3):
2022-12-19 23:30:08 +03:00
```hurl
2024-04-03 15:24:25 +03:00
GET https://foo.com
2023-10-21 20:34:42 +03:00
HTTP/3 200
2024-04-03 15:24:25 +03:00
GET https://bar.com
HTTP/2 200
2022-12-19 23:30:08 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#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]
2023-06-28 17:14:06 +03:00
retry: 10 # maximum number of retry, -1 for unlimited
2022-12-19 23:30:08 +03:00
HTTP 200
2022-10-24 21:58:56 +03:00
[Asserts]
jsonpath "$.state" == "COMPLETED"
```
[Doc ](https://hurl.dev/docs/entry.html#retry )
2024-01-17 15:34:16 +03:00
### Delaying Requests
Add delay for every request, or a particular requests:
```hurl
# Delaying this request by 5s
GET https://example.org/turtle
[Options]
delay: 5000
HTTP 200
# No delay!
GET https://example.org/turtle
HTTP 200
```
[Doc ](https://hurl.dev/docs/manual.html#delay )
### Skipping Requests
```hurl
2024-04-03 15:24:25 +03:00
# a, c, d are run, b is skipped
2024-01-17 15:34:16 +03:00
GET https://example.org/a
GET https://example.org/b
[Options]
skip: true
GET https://example.org/c
GET https://example.org/d
```
[Doc ](https://hurl.dev/docs/manual.html#skip )
2022-10-24 21:58:56 +03:00
2021-02-12 10:52:29 +03:00
### Testing Endpoint Performance
```hurl
GET https://sample.org/helloworld
2022-12-19 23:30:08 +03:00
HTTP *
2021-02-12 10:52:29 +03:00
[Asserts]
2021-09-11 23:38:55 +03:00
duration < 1000 # Check that response time is less than one second
2021-02-12 10:52:29 +03:00
```
[Doc ](https://hurl.dev/docs/asserting-response.html#duration-assert )
2022-09-28 11:34:00 +03:00
### Using SOAP APIs
2020-09-04 22:09:30 +03:00
```hurl
2022-05-31 15:38:37 +03:00
POST https://example.org/InStock
2020-09-04 22:09:30 +03:00
Content-Type: application/soap+xml; charset=utf-8
SOAPAction: "http://www.w3.org/2003/05/soap-envelope"
<?xml version="1.0" encoding="UTF-8"?>
2022-05-31 15:38:37 +03:00
< soap:Envelope xmlns:soap = "http://www.w3.org/2003/05/soap-envelope" xmlns:m = "https://example.org" >
2020-09-04 22:09:30 +03:00
< 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
2020-09-04 22:09:30 +03:00
```
[Doc ](https://hurl.dev/docs/request.html#xml-body )
### Capturing and Using a CSRF Token
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 200
2020-09-04 22:09:30 +03:00
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"
2023-01-28 16:04:10 +03:00
2022-05-31 15:38:37 +03:00
POST https://example.org/login?user=toto& password=1234
2020-09-04 22:09:30 +03:00
X-CSRF-TOKEN: {{csrf_token}}
2022-12-19 23:30:08 +03:00
HTTP 302
2020-09-04 22:09:30 +03:00
```
[Doc ](https://hurl.dev/docs/capturing-response.html#xpath-capture )
2021-10-22 16:04:50 +03:00
### Checking Byte Order Mark (BOM) in Response Body
```hurl
2022-05-31 15:38:37 +03:00
GET https://example.org/data.bin
2022-12-19 23:30:08 +03:00
HTTP 200
2021-10-22 16:04:50 +03:00
[Asserts]
bytes startsWith hex,efbbbf;
```
[Doc ](https://hurl.dev/docs/asserting-response.html#bytes-assert )
2023-09-19 16:12:18 +03:00
### AWS Signature Version 4 Requests
2023-09-13 15:05:36 +03:00
2023-09-19 16:12:18 +03:00
Generate signed API requests with [AWS Signature Version 4], as used by several cloud providers.
2023-09-13 15:05:36 +03:00
```hurl
POST https://sts.eu-central-1.amazonaws.com/
[Options]
aws-sigv4: aws:amz:eu-central-1:sts
[FormParams]
Action: GetCallerIdentity
Version: 2011-06-15
```
2024-04-03 15:24:25 +03:00
The Access Key is given per [`--user`], either with command line option or within the [`[Options]`][option] section:
```hurl
POST https://sts.eu-central-1.amazonaws.com/
[Options]
aws-sigv4: aws:amz:eu-central-1:sts
user: bob=secret
[FormParams]
Action: GetCallerIdentity
Version: 2011-06-15
```
2023-09-13 15:05:36 +03:00
[Doc ](https://hurl.dev/docs/manual.html#aws-sigv4 )
2021-10-22 16:04:50 +03:00
2022-09-02 01:29:23 +03:00
# Manual
2021-10-22 16:04:50 +03:00
## Name
hurl - run and test HTTP requests.
## Synopsis
**hurl** [options] [FILE...]
## Description
2022-12-19 23:30:08 +03:00
**Hurl** is a command line tool that runs HTTP requests defined in a simple plain text format.
2021-10-22 16:04:50 +03:00
2022-12-19 23:30:08 +03:00
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.
2022-09-03 01:32:04 +03:00
```shell
2021-10-22 16:04:50 +03:00
$ hurl session.hurl
```
2022-08-16 13:08:14 +03:00
If no input files are specified, input is read from stdin.
2021-10-22 16:04:50 +03:00
2022-09-03 01:32:04 +03:00
```shell
2021-10-22 16:04:50 +03:00
$ echo GET http://httpbin.org/get | hurl
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "hurl/0.99.10",
"X-Amzn-Trace-Id": "Root=1-5eedf4c7-520814d64e2f9249ea44e0"
},
"origin": "1.2.3.4",
"url": "http://httpbin.org/get"
}
```
2022-09-02 01:11:36 +03:00
Output goes to stdout by default. To have output go to a file, use the [`-o, --output` ](#output ) option:
2021-10-22 16:04:50 +03:00
2022-09-03 01:32:04 +03:00
```shell
2021-10-22 16:04:50 +03:00
$ hurl -o output input.hurl
```
By default, Hurl executes all HTTP requests and outputs the response body of the last HTTP call.
2022-08-16 13:08:14 +03:00
To have a test oriented output, you can use [`--test` ](#test ) option:
2022-06-23 16:22:16 +03:00
2022-09-03 01:32:04 +03:00
```shell
2022-06-23 16:22:16 +03:00
$ hurl --test *.hurl
```
2021-10-22 16:04:50 +03:00
## Hurl File Format
2022-08-16 13:08:14 +03:00
The Hurl file format is fully documented in [https://hurl.dev/docs/hurl-file.html ](https://hurl.dev/docs/hurl-file.html )
2021-10-22 16:04:50 +03:00
It consists of one or several HTTP requests
```hurl
2023-09-13 15:05:36 +03:00
GET http://example.org/endpoint1
GET http://example.org/endpoint2
2021-10-22 16:04:50 +03:00
```
### Capturing values
A value from an HTTP response can be-reused for successive HTTP requests.
2022-12-19 23:30:08 +03:00
A typical example occurs with CSRF tokens.
2021-10-22 16:04:50 +03:00
```hurl
2022-06-13 09:06:13 +03:00
GET https://example.org
2022-12-19 23:30:08 +03:00
HTTP 200
2021-10-22 16:04:50 +03:00
# Capture the CSRF token value from html body.
[Captures]
csrf_token: xpath "normalize-space(//meta[@name='_csrf_token']/@content)"
# Do the login !
2022-06-13 09:06:13 +03:00
POST https://example.org/login?user=toto& password=1234
2021-10-22 16:04:50 +03:00
X-CSRF-TOKEN: {{csrf_token}}
```
2022-09-02 01:11:36 +03:00
More information on captures can be found here [https://hurl.dev/docs/capturing-response.html ](https://hurl.dev/docs/capturing-response.html )
2022-06-23 16:22:16 +03:00
2021-10-22 16:04:50 +03:00
### Asserts
2022-12-19 23:30:08 +03:00
The HTTP response defined in the Hurl file are used to make asserts. Responses are optional.
2021-10-22 16:04:50 +03:00
2022-12-19 23:30:08 +03:00
At the minimum, response includes assert on the HTTP status code.
2021-10-22 16:04:50 +03:00
```hurl
2023-09-13 15:05:36 +03:00
GET http://example.org
2022-12-19 23:30:08 +03:00
HTTP 301
2021-10-22 16:04:50 +03:00
```
It can also include asserts on the response headers
```hurl
2023-09-13 15:05:36 +03:00
GET http://example.org
2022-12-19 23:30:08 +03:00
HTTP 301
Location: http://www.example.org
2021-10-22 16:04:50 +03:00
```
2022-09-02 01:11:36 +03:00
Explicit asserts can be included by combining a query and a predicate
2021-10-22 16:04:50 +03:00
```hurl
2023-09-13 15:05:36 +03:00
GET http://example.org
2022-12-19 23:30:08 +03:00
HTTP 301
2021-10-22 16:04:50 +03:00
[Asserts]
2022-02-15 20:28:49 +03:00
xpath "string(//title)" == "301 Moved"
2021-10-22 16:04:50 +03:00
```
2022-09-02 01:11:36 +03:00
With the addition of asserts, Hurl can be used as a testing tool to run scenarios.
2021-10-22 16:04:50 +03:00
2022-09-02 01:11:36 +03:00
More information on asserts can be found here [https://hurl.dev/docs/asserting-response.html ](https://hurl.dev/docs/asserting-response.html )
2021-02-13 22:01:47 +03:00
## Options
2022-09-28 11:24:24 +03:00
Options that exist in curl have exactly the same semantics.
2022-08-16 13:08:14 +03:00
2024-03-19 20:39:12 +03:00
Options specified on the command line are defined for every Hurl file's entry,
except if they are tagged as cli-only (can not be defined in the Hurl request [Options] entry)
2022-08-16 13:08:14 +03:00
For instance:
2022-09-03 01:32:04 +03:00
```shell
2022-08-16 13:08:14 +03:00
$ hurl --location foo.hurl
```
2022-09-02 01:11:36 +03:00
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:
2022-08-16 13:08:14 +03:00
```hurl
2022-12-19 23:30:08 +03:00
GET https://example.org
HTTP 301
2022-08-16 13:08:14 +03:00
2022-12-19 23:30:08 +03:00
GET https://example.org
2022-08-16 13:08:14 +03:00
[Options]
location: true
2022-12-19 23:30:08 +03:00
HTTP 200
2022-08-16 13:08:14 +03:00
```
2022-09-02 01:11:36 +03:00
will follow a redirection only for the second entry.
2021-02-13 22:01:47 +03:00
2024-03-19 20:39:12 +03:00
| Option | Description |
|-------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| < a href = "#aws-sigv4" id = "aws-sigv4" >< code > --aws-sigv4 < PROVIDER1[:PROVIDER2[:REGION[:SERVICE]]]> </ code ></ a > | Generate an `Authorization` header with an AWS SigV4 signature.< br >< br > Use [`-u, --user` ](#user ) to specify Access Key Id (username) and Secret Key (password).< br >< br > To use temporary session credentials (e.g. for an AWS IAM Role), add the `X-Amz-Security-Token` header containing the session token.< br > |
| < a href = "#cacert" id = "cacert" > < code > --cacert < FILE> < / 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 < CERTIFICATE[:PASSWORD]> </ code ></ a > | Client certificate file and password.< br >< br > See also [`--key` ](#key ).< br > |
| < a href = "#color" id = "color" > < code > --color< / code > < / a > | Colorize debug output (the HTTP response output is not colorized).< br > < br > This is a cli-only option.< 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 < SECONDS> </ 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 < HOST1:PORT1:HOST2:PORT2> </ 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 = "#continue-on-error" id = "continue-on-error" > < code > --continue-on-error< / 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 > < br > This is a cli-only option.< br > |
| < a href = "#cookie" id = "cookie" >< code > -b, --cookie < FILE> </ 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 >< br > This is a cli-only option.< br > |
| < a href = "#cookie-jar" id = "cookie-jar" >< code > -c, --cookie-jar < FILE> </ 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 >< br > This is a cli-only option.< br > |
| < a href = "#delay" id = "delay" > < code > --delay < MILLISECONDS> < / code > < / a > | Sets delay before each request.< br > |
| < a href = "#error-format" id = "error-format" > < code > --error-format < FORMAT> < / code > < / a > | Control the format of error message (short by default or long)< br > < br > This is a cli-only option.< br > |
| < a href = "#file-root" id = "file-root" > < code > --file-root < DIR> < / code > < / a > | Set root directory to import files in Hurl. This is used for files in multipart form data, request body and response output.< br > When it is not explicitly defined, files are relative to the current directory in which Hurl is running.< br > < br > This is a cli-only option.< br > |
| < a href = "#from-entry" id = "from-entry" > < code > --from-entry < ENTRY_NUMBER> < / code > < / a > | Execute Hurl file from ENTRY_NUMBER (starting at 1).< br > < br > This is a cli-only option.< br > |
| < a href = "#glob" id = "glob" > < code > --glob < GLOB> < / 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 > < br > This is a cli-only option.< br > |
| < a href = "#http10" id = "http10" > < code > -0, --http1.0< / code > < / a > | Tells Hurl to use HTTP version 1.0 instead of using its internally preferred HTTP version.< br > |
| < a href = "#http11" id = "http11" > < code > --http1.1< / code > < / a > | Tells Hurl to use HTTP version 1.1.< br > |
| < a href = "#http2" id = "http2" > < code > --http2< / code > < / a > | Tells Hurl to use HTTP version 2.< br > For HTTPS, this means Hurl negotiates HTTP/2 in the TLS handshake. Hurl does this by default.< br > For HTTP, this means Hurl attempts to upgrade the request to HTTP/2 using the Upgrade: request header.< br > |
| < a href = "#http3" id = "http3" > < code > --http3< / code > < / a > | Tells Hurl to try HTTP/3 to the host in the URL, but fallback to earlier HTTP versions if the HTTP/3 connection establishment fails. HTTP/3 is only available for HTTPS and not for HTTP URLs.< br > |
| < a href = "#ignore-asserts" id = "ignore-asserts" > < code > --ignore-asserts< / code > < / a > | Ignore all asserts defined in the Hurl file.< br > < br > This is a cli-only option.< br > |
| < a href = "#include" id = "include" > < code > -i, --include< / code > < / a > | Include the HTTP headers in the output< br > < br > This is a cli-only option.< 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 > < br > This is similar to a break point, You can then continue (Press C) or quit (Press Q).< br > < br > This is a cli-only option.< br > |
| < a href = "#ipv4" id = "ipv4" > < code > -4, --ipv4< / code > < / a > | This option tells Hurl to use IPv4 addresses only when resolving host names, and not for example try IPv6.< br > |
| < a href = "#ipv6" id = "ipv6" > < code > -6, --ipv6< / code > < / a > | This option tells Hurl to use IPv6 addresses only when resolving host names, and not for example try IPv4.< 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 > < br > This is a cli-only option.< br > |
| < a href = "#key" id = "key" > < code > --key < KEY> < / code > < / a > | Private key file name.< 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 = "#location-trusted" id = "location-trusted" >< code > --location-trusted</ code ></ a > | Like [`-L, --location` ](#location ), but allows sending the name + password to all hosts that the site may redirect to.< br > This may or may not introduce a security breach if the site redirects you to a site to which you send your authentication info (which is plaintext in the case of HTTP Basic authentication).< br > |
2024-04-08 10:28:31 +03:00
| < a href = "#max-filesize" id = "max-filesize" > < code > --max-filesize < BYTES> < / code > < / a > | Specify the maximum size (in bytes) of a file to download. If the file requested is larger than this value, the transfer does not start.< br > < br > This is a cli-only option.< br > |
2024-03-19 20:39:12 +03:00
| < a href = "#max-redirs" id = "max-redirs" > < code > --max-redirs < NUM> < / code > < / a > | Set maximum number of redirection-followings allowed< br > < 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 < SECONDS> </ 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 >< br > This is a cli-only option.< br > |
| < a href = "#netrc" id = "netrc" >< code > -n, --netrc</ code ></ a > | Scan the .netrc file in the user's home directory for the username and password.< br >< br > See also [`--netrc-file` ](#netrc-file ) and [`--netrc-optional` ](#netrc-optional ).< br > |
| < a href = "#netrc-file" id = "netrc-file" >< code > --netrc-file < FILE> </ code ></ a > | Like [`--netrc` ](#netrc ), but provide the path to the netrc file.< br >< br > See also [`--netrc-optional` ](#netrc-optional ).< br > |
| < a href = "#netrc-optional" id = "netrc-optional" >< code > --netrc-optional</ code ></ a > | Similar to [`--netrc` ](#netrc ), but make the .netrc usage optional.< br >< br > See also [`--netrc-file` ](#netrc-file ).< br > |
| < a href = "#no-color" id = "no-color" > < code > --no-color< / code > < / a > | Do not colorize output.< br > < br > This is a cli-only option.< 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 > < br > This is a cli-only option.< br > |
| < a href = "#noproxy" id = "noproxy" > < code > --noproxy < HOST(S)> < / code > < / a > | Comma-separated list of hosts which do not use a proxy.< br > < br > Override value from Environment variable no_proxy.< br > |
| < a href = "#output" id = "output" > < code > -o, --output < FILE> < / code > < / a > | Write output to FILE instead of stdout.< br > |
| < a href = "#path-as-is" id = "path-as-is" > < code > --path-as-is< / code > < / a > | Tell Hurl to not handle sequences of /../ or /./ in the given URL path. Normally Hurl will squash or merge them according to standards but with this option set you tell it not to do that.< br > |
| < a href = "#proxy" id = "proxy" > < code > -x, --proxy < [PROTOCOL://]HOST[:PORT]> < / code > < / a > | Use the specified proxy.< br > |
| < a href = "#report-html" id = "report-html" > < code > --report-html < DIR> < / 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 > < br > This is a cli-only option.< br > |
| < a href = "#report-junit" id = "report-junit" > < code > --report-junit < FILE> < / code > < / a > | Generate JUnit File.< br > < br > If the FILE report already exists, it will be updated with the new test results.< br > < br > This is a cli-only option.< br > |
| < a href = "#report-tap" id = "report-tap" > < code > --report-tap < FILE> < / code > < / a > | Generate TAP report.< br > < br > If the FILE report already exists, it will be updated with the new test results.< br > < br > This is a cli-only option.< br > |
| < a href = "#resolve" id = "resolve" > < code > --resolve < HOST:PORT:ADDR> < / 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 < NUM> < / code > < / a > | Maximum number of retries, 0 for no retries, -1 for unlimited retries. Retry happens if any error occurs (asserts, captures, runtimes etc...).< br > |
| < a href = "#retry-interval" id = "retry-interval" > < code > --retry-interval < MILLISECONDS> < / code > < / a > | Duration in milliseconds between each retry. Default is 1000 ms.< 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 > < br > This is a cli-only option.< 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 > < br > This is a cli-only option.< br > |
| < a href = "#to-entry" id = "to-entry" > < code > --to-entry < ENTRY_NUMBER> < / 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 > < br > This is a cli-only option.< br > |
| < a href = "#unix-socket" id = "unix-socket" > < code > --unix-socket < PATH> < / code > < / a > | (HTTP) Connect through this Unix domain socket, instead of using the network.< br > |
2024-03-26 17:20:21 +03:00
| < a href = "#user" id = "user" > < code > -u, --user < USER:PASSWORD> < / code > < / a > | Add basic Authentication header to each request.< br > |
2024-03-19 20:39:12 +03:00
| < a href = "#user-agent" id = "user-agent" > < code > -A, --user-agent < NAME> < / code > < / a > | Specify the User-Agent string to send to the HTTP server.< br > < br > This is a cli-only option.< br > |
| < a href = "#variable" id = "variable" > < code > --variable < NAME=VALUE> < / code > < / a > | Define variable (name/value) to be used in Hurl templates.< br > |
| < a href = "#variables-file" id = "variables-file" >< code > --variables-file < FILE> </ 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 >< br > This is a cli-only option.< 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 > |
2021-10-22 16:04:50 +03:00
2021-02-13 22:01:47 +03:00
## Environment
Environment variables can only be specified in lowercase.
2022-08-16 13:08:14 +03:00
Using an environment variable to set the proxy has the same effect as using the [`-x, --proxy` ](#proxy ) option.
2021-02-13 22:01:47 +03:00
2023-06-29 17:13:42 +03:00
| 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 > |
2021-02-13 22:01:47 +03:00
2021-10-22 16:04:50 +03:00
## Exit Codes
2021-02-13 22:01:47 +03:00
2023-06-29 17:13:42 +03:00
| Value | Description |
|-------|---------------------------------------------------------|
| `0` | Success.< br > |
| `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 > |
2021-10-22 16:04:50 +03:00
## WWW
[https://hurl.dev ](https://hurl.dev )
2021-02-13 22:01:47 +03:00
2020-11-29 18:55:26 +03:00
2021-10-22 16:04:50 +03:00
## See Also
curl(1) hurlfmt(1)
# Installation
## Binaries Installation
### Linux
2023-09-21 16:17:22 +03:00
Precompiled binary is available at [Hurl latest GitHub release]:
2021-10-22 16:04:50 +03:00
```shell
2021-11-20 22:27:44 +03:00
$ INSTALL_DIR=/tmp
2024-01-11 15:21:45 +03:00
$ VERSION=4.2.0
2023-09-21 16:17:22 +03:00
$ curl --silent --location https://github.com/Orange-OpenSource/hurl/releases/download/$VERSION/hurl-$VERSION-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $INSTALL_DIR
$ export PATH=$INSTALL_DIR/hurl-$VERSION:$PATH
2021-10-22 16:04:50 +03:00
```
#### Debian / Ubuntu
For Debian / Ubuntu, Hurl can be installed using a binary .deb file provided in each Hurl release.
```shell
2024-01-11 15:21:45 +03:00
$ VERSION=4.2.0
2024-02-16 20:26:21 +03:00
$ curl --location --remote-name https://github.com/Orange-OpenSource/hurl/releases/download/$VERSION/hurl_${VERSION}_amd64.deb
$ sudo apt update & & sudo apt install ./hurl_${VERSION}_amd64.deb
2021-10-22 16:04:50 +03:00
```
2023-09-13 15:05:36 +03:00
#### Alpine
Hurl is available on `testing` channel.
```shell
$ apk add --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing hurl
```
2021-10-22 16:04:50 +03:00
#### Arch Linux / Manjaro
2022-04-13 23:09:33 +03:00
[`hurl-bin` package] for Arch Linux and derived distros is available via [AUR].
#### NixOS / Nix
[NixOS / Nix package] is available on stable channel.
2021-10-22 16:04:50 +03:00
### macOS
2023-09-21 16:17:22 +03:00
Precompiled binaries for Intel and ARM CPUs are available at [Hurl latest GitHub release].
2021-10-22 16:04:50 +03:00
2022-10-10 18:42:28 +03:00
#### Homebrew
2021-10-22 16:04:50 +03:00
```shell
2021-11-20 22:27:44 +03:00
$ brew install hurl
2021-10-22 16:04:50 +03:00
```
2022-10-10 18:42:28 +03:00
#### MacPorts
```shell
$ sudo port install hurl
```
2022-12-04 01:28:05 +03:00
### FreeBSD
2022-10-10 18:42:28 +03:00
2022-12-02 19:24:07 +03:00
```shell
$ sudo pkg install hurl
```
2022-10-10 18:42:28 +03:00
2021-10-22 16:04:50 +03:00
### Windows
#### Zip File
2023-09-21 16:17:22 +03:00
Hurl can be installed from a standalone zip file at [Hurl latest GitHub release]. You will need to update your `PATH` variable.
2021-10-22 16:04:50 +03:00
#### Installer
2023-09-21 16:17:22 +03:00
An executable installer is also available at [Hurl latest GitHub release].
2021-11-12 18:25:13 +03:00
#### Chocolatey
2021-11-20 22:27:44 +03:00
```shell
$ choco install hurl
2021-11-12 18:25:13 +03:00
```
#### Scoop
2021-11-20 22:27:44 +03:00
```shell
$ scoop install hurl
2021-11-12 18:25:13 +03:00
```
2021-10-22 16:04:50 +03:00
2021-11-12 18:25:13 +03:00
#### Windows Package Manager
2021-11-20 22:27:44 +03:00
```shell
$ winget install hurl
2021-11-12 18:25:13 +03:00
```
2021-10-22 16:04:50 +03:00
### Cargo
If you're a Rust programmer, Hurl can be installed with cargo.
2021-11-20 22:27:44 +03:00
```shell
$ cargo install hurl
2021-10-22 16:04:50 +03:00
```
2024-01-08 15:30:09 +03:00
### conda-forge
```shell
$ conda install -c conda-forge hurl
```
Hurl can also be installed with [`conda-forge`] powered package manager like [`pixi`].
2021-12-16 12:32:00 +03:00
### Docker
2022-02-10 23:51:21 +03:00
```shell
2023-01-25 19:47:26 +03:00
$ docker pull ghcr.io/orange-opensource/hurl:latest
2021-12-16 12:32:00 +03:00
```
2022-05-31 15:38:37 +03:00
### npm
```shell
$ npm install --save-dev @orangeopensource/hurl
```
2021-10-22 16:04:50 +03:00
## Building From Sources
Hurl sources are available in [GitHub].
2022-08-29 00:26:11 +03:00
### Build on Linux
2020-11-29 18:55:26 +03:00
2021-02-04 16:04:17 +03:00
Hurl depends on libssl, libcurl and libxml2 native libraries. You will need their development files in your platform.
2020-11-29 18:55:26 +03:00
2022-04-01 15:27:56 +03:00
#### Debian based distributions
2021-02-04 16:04:17 +03:00
```shell
2022-04-13 17:02:55 +03:00
$ apt install -y build-essential pkg-config libssl-dev libcurl4-openssl-dev libxml2-dev
2022-04-01 15:27:56 +03:00
```
2021-02-04 16:04:17 +03:00
2022-04-01 15:27:56 +03:00
#### Red Hat based distributions
```shell
$ yum install -y pkg-config gcc openssl-devel libxml2-devel
```
2021-02-04 16:04:17 +03:00
2022-05-31 15:38:37 +03:00
#### Arch based distributions
2022-03-20 02:31:57 +03:00
2022-04-01 15:27:56 +03:00
```shell
2023-11-09 12:46:31 +03:00
$ pacman -S --noconfirm pkgconf gcc glibc openssl libxml2
2022-04-01 15:27:56 +03:00
```
2024-01-08 15:30:09 +03:00
#### Alpine based distributions
```shell
$ apk add curl-dev gcc libxml2-dev musl-dev openssl-dev
```
2022-08-29 00:26:11 +03:00
### Build on macOS
2022-04-01 15:27:56 +03:00
```shell
$ xcode-select --install
$ brew install pkg-config
2020-11-29 18:55:26 +03:00
```
2021-02-04 16:04:17 +03:00
2021-10-22 16:04:50 +03:00
Hurl is written in [Rust]. You should [install] the latest stable release.
2021-02-04 16:04:17 +03:00
```shell
2021-11-20 22:27:44 +03:00
$ curl https://sh.rustup.rs -sSf | sh -s -- -y
$ source $HOME/.cargo/env
$ rustc --version
$ cargo --version
2020-11-29 18:55:26 +03:00
```
2022-04-01 15:27:56 +03:00
Then build hurl:
2021-02-04 16:04:17 +03:00
```shell
2021-11-20 22:27:44 +03:00
$ git clone https://github.com/Orange-OpenSource/hurl
$ cd hurl
$ cargo build --release
$ ./target/release/hurl --version
2020-11-29 18:55:26 +03:00
```
2021-01-05 11:06:14 +03:00
2021-10-22 16:04:50 +03:00
### Build on Windows
Please follow the [contrib on Windows section].
[XPath]: https://en.wikipedia.org/wiki/XPath
[JSONPath]: https://goessner.net/articles/JsonPath/
[Rust]: https://www.rust-lang.org
[curl]: https://curl.se
[the installation section]: https://hurl.dev/docs/installation.html
2021-12-08 20:19:44 +03:00
[Feedback, suggestion, bugs or improvements]: https://github.com/Orange-OpenSource/hurl/issues
2021-10-22 16:04:50 +03:00
[License]: https://hurl.dev/docs/license.html
2022-06-13 09:06:13 +03:00
[Tutorial]: https://hurl.dev/docs/tutorial/your-first-hurl-file.html
2021-12-11 18:34:16 +03:00
[Documentation]: https://hurl.dev/docs/installation.html
2022-02-10 23:51:21 +03:00
[Blog]: https://hurl.dev/blog/
2021-10-22 16:04:50 +03:00
[GitHub]: https://github.com/Orange-OpenSource/hurl
[libcurl]: https://curl.se/libcurl/
2023-04-14 10:46:27 +03:00
[star Hurl on GitHub]: https://github.com/Orange-OpenSource/hurl/stargazers
2021-10-22 16:04:50 +03:00
[JSON body]: https://hurl.dev/docs/request.html#json-body
[XML body]: https://hurl.dev/docs/request.html#xml-body
2022-12-19 23:30:08 +03:00
[XML multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
2023-04-14 10:46:27 +03:00
[multiline string body]: https://hurl.dev/docs/request.html#multiline-string-body
2021-10-22 16:04:50 +03:00
[predicates]: https://hurl.dev/docs/asserting-response.html#predicates
[JSONPath]: https://goessner.net/articles/JsonPath/
2021-11-20 22:27:44 +03:00
[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
2024-01-08 15:30:09 +03:00
[Hurl tests suite]: https://github.com/Orange-OpenSource/hurl/tree/master/integration/hurl/tests_ok
2022-02-11 16:29:06 +03:00
[Authorization]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
2022-09-02 15:45:54 +03:00
[`-u/--user` option]: https://hurl.dev/docs/manual.html#user
2022-06-13 09:06:13 +03:00
[curl]: https://curl.se
[entry]: https://hurl.dev/docs/entry.html
2022-09-02 15:45:54 +03:00
[`--test` option]: https://hurl.dev/docs/manual.html#test
2023-09-13 15:05:36 +03:00
[`--user`]: https://hurl.dev/docs/manual.html#user
2022-12-19 23:30:08 +03:00
[Hurl templates]: https://hurl.dev/docs/templates.html
2023-09-19 16:12:18 +03:00
[AWS Signature Version 4]: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
2024-04-03 15:24:25 +03:00
[Captures]: https://hurl.dev/docs/capturing-response.html
[option]: https://hurl.dev/docs/request.html#options
[`--json` option]: https://hurl.dev/docs/manual.html#json
2021-10-22 16:04:50 +03:00
[GitHub]: https://github.com/Orange-OpenSource/hurl
2023-09-21 16:17:22 +03:00
[Hurl latest GitHub release]: https://github.com/Orange-OpenSource/hurl/releases/latest
2021-10-22 16:04:50 +03:00
[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
[Rust]: https://www.rust-lang.org
[contrib on Windows section]: https://github.com/Orange-OpenSource/hurl/blob/master/contrib/windows/README.md
2022-09-25 13:52:24 +03:00
[NixOS / Nix package]: https://search.nixos.org/packages?from=0& size=1& sort=relevance& type=packages& query=hurl
2024-01-08 15:30:09 +03:00
[`conda-forge`]: https://conda-forge.org
[`pixi`]: https://prefix.dev
2021-02-04 16:04:17 +03:00