LiveBook for Testing

2024-11-19

This post shows how to build a quick screenshotting tool using Elixir LiveBook. LiveBook is a versatile tool that can be used for many purposes, for example, prototyping, data analysis or exploration.

What is LiveBook?

LiveBook is an interactive web-based notebook interface for Elixir, similar to Jupyter notebook for Python. It allows you to write and execute Elixir code in isolated blocks, see results immediately, and mix markdown with code for documentation. One of its standout features is real-time collaboration capabilities, making it excellent for team environments.

The platform has robust built-in support for charts, visualisations, and smart cells for common tasks. It’s particularly powerful because it can connect to running Elixir applications. The conference talk “Livebook in the cloud: GPUs and clustered workflows in seconds” even shows how to use it for machine learning and connecting to 64 instances to run workloads.

LiveBook has found its niche in various use cases within the Elixir ecosystem. It's extensively used for learning and teaching Elixir, prototyping code, and conducting data analysis and exploration. The platform is great for creating documentation with live examples, facilitating team collaboration, and system exploration and debugging. Being maintained by the Elixir core team, it receives regular updates and improvements, making it a reliable tool for both educational and professional purposes.

How to Install LiveBook?

LiveBook can be installed by downloading the package from the LiveBook website or running it through Docker. LiveBook contents are stored in LiveBook files with the livemd extension and can be stored in version control as any other code.

How to Access it Locally?

When you start the LiveBook application on a Mac, it will run in the background and show a LiveBook icon in the menubar. From there, you can open the page in the browser or directly click on the New Notebook menu item. A new notebook won't have a name or any code to execute yet. There is a section for Notebook dependencies and setup and a section with an empty code cell.

How is it Useful for Testing?

Test automation is often considered a valuable but fairly complicated topic. The main effort in test automation is building test frameworks and suites for testing the application’s functionality. These tests are run in the continuous integration pipeline to ensure broken code is not merged with the main codebase. In addition to functional test automation, you sometimes need to run other checks and use a simple tool for one specific thing. For example, you might want to take screenshots of pages to make sure you haven’t broken anything or to review designs in both desktop and mobile resolutions. This is one of the many cases for which I’ve recently used LiveBook.

What Is Required to Take Screenshots?

To take screenshots of web pages, we need three things:

  • a library to control the browser
  • the page URLs to take screenshots of
  • the code to make the browser navigate to the page and take the screenshot

Installing the dependencies

The first cell in any LiveBook is about setting up and installing dependencies. To take screenshots, we need a library to control the browser. One such library in Elixir is Wallaby, which connects to chromedriver to control an instance of Chrome. We can specify options, such as window size, which is handy for checking pages in multiple resolutions for responsive web design.

Dependencies can be installed using the Mix. install command, which takes a list of libraries and their versions as an argument. Adding this code into the Notebook dependencies and setup block and clicking on Reconnect and setup will install the dependencies. Note: A directory for screenshots is defined in the code below. Feel free to adjust it to your liking.

Mix.install(
  [
    {:wallaby, "~> 0.3"}
  ],
  config: [
    wallaby: [
      chromedriver: [
        path: "/opt/homebrew/bin/chromedriver",
        capabilities: %{
          chromeOptions: %{
            args: [
              "--no-sandbox",
              "window-size=400,1920",
              "--disable-gpu",
              "--headless",
              "--fullscreen",
              "--user-agent=Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"
            ]
          }
        }
      ],
      screenshot_dir: "~/Desktop/page_screenshots"
    ]
  ]
)

Defining the Pages to Screenshot

We can add a list of page paths to access in the empty code cell. These URLs probably won't exist on your website, so adjust accordingly.

page_list = [
  "/",
  "/services",
  "/blog",
  "/about",
  "/blog/planning-poker-a-liveview-project"
]

There’s an Evaluate button with a play icon above the code cell to evaluate the code. Clicking that will run the code.

Taking the Screenshots

The next step is to navigate to the page and take a screenshot. To do that, we need to add a new code cell. To get the browser to navigate the page and take the screenshot, we can use the following code by hovering below the current last code cell and clicking Elixir. This will add a new cell where we can enter the code to take the screenshots.

alias Wallaby.Browser

{:ok, session} = Wallaby.start_session()

site_url = "http://localhost:4004"

page_list
|> Enum.each(fn page_path ->
  url = site_url <> page_path

  name =
    page_path
    |> String.trim_leading("/")
    |> String.trim_trailing("/")
    |> String.replace("/", "_")

  session
  |> Browser.visit(url)
  |> Browser.take_screenshot([{:name, name}])

  :timer.sleep(100)
end)


Wallaby.end_session(session)

A new folder with screenshots will appear on the desktop when evaluating the code. You might want to store them in a different location.

Retrieving Paths from Sitemap

Hardcoding can sometimes be a good option, but we usually want to get the URLs to screenshot from the source—in this case, the sitemap. Sitemaps are XML formatted files located in /sitemap.xml for most websites. To get the data from the sitemap, we need two things:

  • a way to retrieve the file
  • a way to parse the file

Install Additional Dependencies

The first change we must make is adding new dependencies to fetch and parse the sitemap. We'll install Req for HTTP requests and SweetXml for parsing XML.


```diff
Mix.install(
   [
-    {:wallaby, "~> 0.3"}
+    {:wallaby, "~> 0.3"},
+    {:req, "~> 0.5.6"},
+    {:sweet_xml, "~> 0.7.3"}
   ],
   config: [
     wallaby: [

The next thing is to fetch and parse the sitemap and store the URLs into a variable:

-page_list = [
-  "/",
-  "/services",
-  "/blog",
-  "/about",
-  "/blog/planning-poker-a-liveview-project"
-]
+import SweetXml

+# Set the site base URL
+site_url = "http://localhost:4004"
+
+# Get the sitemap
+sitemap_xml =
+  (site_url <> "/sitemap.xml")
+  |> Req.get!()
+  |> Map.get(:body)

+# Parse the locations from the sitemap
+locations = sitemap_xml |> xpath(~x"//loc/text()"ls)

And finally, we need to update the screenshot code

-site_url = "http://localhost:4004"
-
-page_list
-|> Enum.each(fn page_path ->
-  url = site_url <> page_path

-  name =
-    page_path
-    |> String.trim_leading("/")
-    |> String.trim_trailing("/")
-    |> String.replace("/", "_")
+locations
+|> Enum.each(fn page_url ->
+  filename = String.split(page_url, "/") |> List.last()

   session
-  |> Browser.visit(url)
-  |> Browser.take_screenshot([{:name, name}])
+  |> Browser.visit(page_url)
+  |> Browser.take_screenshot([{:name, filename}])

Conclusion

And that’s all there is to it. In this example, the LiveBook is run locally but it can also be deployed deployed using Docker or LiveBook Teams, depending on your organisation’s needs. This is a very basic tool, but it can already bring value for a team. The purpose of this post is not so much this specific tool, but to introduce LiveBook and show how simple is to use for various tasks.

Feel free to contact me on LinkedIn should you want to explore test automation or other utilities to ease your company's development process.