~ 5 min read

Automate HTTP Testing with hurl: Generate HTML and JUnit reports via GitLab CI

Written by Brie Carranza

Using hurl to automate website testing.

I read about hurl.dev on HN and was quite taken with the description:

Hurl is a command line tool that runs HTTP requests defined in a simple plain text format.

That sounds super useful for monitoring my various sites and projects around the Web. The tool even purports to be designed with CI/CD integration in mind.

I set it up to test the httpcat.us site I wrote about in my last post. In this post, I:

  • Describe a super-simple .hurl file
  • Improve the .hurl file to check for content with asserts
  • Generate HTML and a JUnit Report from hurl
  • Run hurl via GitLab CI

TL;DR - 🔗 The code for this project is stored at gitlab.com/brie/hurl.

A super simple .hurl file

The simplest .hurl file looks something like this:

GET https://httpcat.us

That can be built on a bit:

GET https://httpcat.us
HTTP/* 200

With that content in a file like hello.hurl, run hurl --test hello.hurl and you’ll get something like this:

hello.hurl: Running [1/1]
hello.hurl: Success (1 request(s) in 103 ms)
--------------------------------------------------------------------------------
Executed files:  1
Succeeded files: 1 (100.0%)
Failed files:    0 (0.0%)
Duration:        108 ms

In order to ensure the site is served via HTTP/2, the hello.hurl file would look like this instead:

GET https://httpcat.us
HTTP/2 200

The syntax so far is pretty intuitive:

  • We’re checking that we can GET https://httpcat.us.
  • We can check for a specific version of HTTP and confirm that the response code is 200.

Let’s keep going. Let’s add a test for a page that should 404. To do that, the hello.hurl file gets updated to look like this:

GET https://httpcat.us
HTTP/2 200
GET https://httpcat.us/lolno
HTTP/2 404

Improve the .hurl file to check for content with asserts

So far, we are checking that the site is reachable, is serving the expected version of HTTP and the desired HTTP response status code. That’s great but I want to test that the images on httpcat.us are present. We can do this by giving the appropriate XPath to hurl.

You can use Chrome Developer Tools to identify, evaluate and validate XPath values. The linked article has more but in short:

  • Open Developer Tools
  • In the Console tab, navigate to the desired element
  • Right-click > Copy > Copy XPath

Here’s how the hello.hurl file we have been building looks with some Asserts:

GET https://httpcat.us
HTTP/* 200
[Asserts]
xpath "string(//head/title)" ==  "HTTP Status Codes -- with cats"
xpath "string(//body/div/div/div[2]/div/p[2]/text()[1])" contains "👋"
xpath "//img" count == 6

I’ll summarize the purpose of each xpath statement:

xpath “string(//head/title)” == “HTTP Status Codes — with cats”

This checks to make sure that the <title> tag is exactly HTTP Status Codes -- with cats.

xpath “string(//body/div/div/div[2]/div/p[2]/text()[1])” contains ”👋“

This checks to make sure that the selected text contains the 👋 emoji.

xpath “//img” count == 6

This checks that there are six <img> tags on the front page. I want to make sure that each of the six cats on the home page is present. 🐱

The [Asserts] section can also check response headers. These two lines can be added to the [Asserts] section:

header "Content-Type"  == "text/html; charset=utf-8"
header "Server" == "Google Frontend"

Let’s add in the tests for the headers and a few more tests for the 404 page:

GET https://httpcat.us
HTTP/* 200
[Asserts]
xpath "string(//head/title)" ==  "HTTP Status Codes -- with cats"
xpath "//img" count == 6
xpath "string(//body/div/div/div[2]/div/p[2]/text()[1])" contains "👋"
header "Content-Type"  == "text/html; charset=utf-8"
header "Server" == "Google Frontend"

GET https://httpcat.us/not-found
HTTP/* 404
[Asserts]
header "Content-Type" == "text/html; charset=utf-8"
xpath "string(//h1)" == "MEOW?"
xpath "string(//h2)" == "What were you looking for?"

Awesome! The hello.hurl file is complete. Let’s do more with it than hurl --test hello.hurl.

Generating HTML and JUnit Reports with hurl

The manual documents a range of options. The ones we are interested in for this section are:

  • --report-html
  • --report-junit

To put the HTML report in a directory called public/ and the JUnit report file in a file called http.xml, we’ll adjust our hurl command to:

hurl --test --report-html public --report-junit http.xml hello.hurl

The hurl tutorial and manual have more information on extending the content of the .hurl file and the options when we run hurl. Let’s put what we have in a pipeline. 💚

[Top]

Running hurl via GitLab CI

With the .hurl file and the desired options ready, it’s time to put this all in a CI pipeline. The .gitlab-ci.yml below will:

pages:
    image:
        name: orangeopensource/hurl
        entrypoint: [""]
    script:
        - hurl --test --report-html public --report-junit http.xml *.hurl
    artifacts:
        paths:
            - http.xml
            - public/
        reports:
          junit: http.xml

The HTML report generated by the hello.hurl we have been building is at brie.gitlab.io/hurl.

🚀 Get started!

You can 🍴 fork the brie/hurl project to get up and running with hurl in a CI pipeline.

In the linked project, the .gitlab-ci.yml job is expanded to use the services keyword. The services keyword is used to start a Web server that the hurl test are run against. This is useful if you don’t have a Web server to be tested ready just yet.

The .gitlab-ci.yml from that project looks like this:

pages:
    services:
      - name: kennethreitz/httpbin
        alias: httpbin
    allow_failure: true
    image:
        name: orangeopensource/hurl
        entrypoint: [""]
    script:
        - hurl --test --report-html public --report-junit http.xml *.hurl
    artifacts:
        paths:
            - http.xml
            - public/
        reports:
          junit: http.xml

The kennethreitz/httpbin image spins up an instance of the very wonderful httpbin, a simple HTTP request and response service.

[Top]


The image atop this post was generated via Midjourney with the prompt:

giant sunset featuring lemon slice sun