Expresso.Check.Browser (expresso v0.1.0)

Copy Markdown View Source

Runs the layout check and the accessibility check of a document in a headless browser

--check=layout and --check=a11y need Chrome or Chromium. The function find/1 finds the browser, and find_axe/2 finds axe-core for --check=a11y. run/3 writes a copy of the document to a temporary directory, with axe-core in it for --check=a11y. The browser opens the copy with ?check or ?check=a11y, and it writes the document to its standard output after the check. The script of the page writes the result into an element check-result. assets/src/check.ts gives the rules.

The page has the size 1920 × 1080. A browser can keep a part of the window for itself, so run/3 first opens a small page that writes its size, and it makes the window larger by the difference. A Chrome window is not narrower than 500 px, so run/3 returns an error when the page does not get the requested size.

Summary

Types

The result of the page: the size, the problems of the layout and the violations of axe-core

Functions

Read the JSON of the element check-result

Find Chrome or Chromium

Find the file axe.min.js of axe-core

Return the directories where the check looks for node_modules

Return the path of a browser of Playwright, or nil

Read the result of the page from the document that the browser writes

Run the check of the page on a document in the browser, and return the result

Return the result when the page has the requested size, or an error

Add the variant of a theme to the address of a page

Return true for a command that runs or installs a snap

Put axe-core into a document before the end of its body

Types

result()

@type result() :: %{
  width: pos_integer(),
  height: pos_integer(),
  layout: [String.t()],
  accessibility: [map()] | nil
}

The result of the page: the size, the problems of the layout and the violations of axe-core

Functions

decode(json)

@spec decode(String.t()) :: {:ok, result()} | {:error, String.t()}

Read the JSON of the element check-result

iex> Expresso.Check.Browser.decode(~s({"width":1920,"height":1080,"layout":["x"],"accessibility":[]}))
{:ok, %{width: 1920, height: 1080, layout: ["x"], accessibility: []}}

find(env \\ System.get_env())

@spec find(%{required(String.t()) => String.t()}) ::
  {:ok, Path.t()} | {:error, String.t()}

Find Chrome or Chromium

The variable EXPRESSO_CHROMIUM in env gives the path of the browser. Without it, the function looks in this sequence:

  1. The browsers of Playwright, as playwright/1 finds them. The headless shell starts fast, and it gives the page its size exactly.
  2. Each usual name of the browser on the PATH. Chrome comes before Chromium. The function skips a command that installs a snap, such as chromium on Ubuntu, because that command does not end.
  3. The usual places of Chrome and Chromium on macOS.

find_axe(directories, env \\ System.get_env())

@spec find_axe([Path.t()], %{required(String.t()) => String.t()}) ::
  {:ok, Path.t()} | {:error, String.t()}

Find the file axe.min.js of axe-core

The variable EXPRESSO_AXE in env gives the path of the file. Without it, the function looks for node_modules/axe-core/axe.min.js in each directory of directories, in each parent of them, and in the directory of Expresso.Check.Tools.

places(directories, env \\ System.get_env())

@spec places([Path.t()], %{required(String.t()) => String.t()}) :: [Path.t()]

Return the directories where the check looks for node_modules

The list holds each directory of directories, then each parent of them, and then the directory of Expresso.Check.Tools.directory/1. Thus a directory of directories comes before the parent of a different one.

iex> Expresso.Check.Browser.places(["/a/b", "/c"], %{"EXPRESSO_TOOLS" => "/t"})
["/a/b", "/c", "/a", "/", "/t"]

playwright(env \\ System.get_env())

@spec playwright(%{required(String.t()) => String.t()}) :: Path.t() | nil

Return the path of a browser of Playwright, or nil

The variable PLAYWRIGHT_BROWSERS_PATH in env gives the directory of the browsers, as for Playwright. Without it, the function looks in the cache of Playwright on Linux and on macOS. It prefers the headless shell, and the latest version of each browser, by the integer of its revision.

result(dom)

@spec result(String.t()) :: {:ok, result()} | {:error, String.t()}

Read the result of the page from the document that the browser writes

iex> dom = ~s(<body><script type="application/json" id="check-result">) <>
...>   ~s({"width":1920,"height":1080,"layout":[],"accessibility":null}</script></body>)
iex> Expresso.Check.Browser.result(dom)
{:ok, %{width: 1920, height: 1080, layout: [], accessibility: nil}}

run(html, browser, axe \\ nil, opts \\ [])

@spec run(String.t(), Path.t(), Path.t() | nil, keyword()) ::
  {:ok, result()} | {:error, String.t()}

Run the check of the page on a document in the browser, and return the result

With axe, the path of axe.min.js, the page also runs axe-core. The function returns an error when the browser does not start, or when the page writes no result. The option :probe_timeout gives the time in milliseconds for the small page of the size, 30 000 by default, and :timeout gives the time for the check, 600 000 by default. The option :scheme, :light or :dark, shows that variant of a theme with two variants, with ?scheme= in the address. The option :size, such as {1280, 720}, gives the size of the page, and it is {1920, 1080} by default.

same_size(page, arg, browser)

@spec same_size(result(), {pos_integer(), pos_integer()}, Path.t()) ::
  {:ok, result()} | {:error, String.t()}

Return the result when the page has the requested size, or an error

A Chrome window is not narrower than 500 px, so Chrome can give a page that is larger than the size of --size. The values of such a check come from the wrong size, so the function returns an error. The headless shell gives each size.

iex> page = %{width: 1920, height: 1080, layout: [], accessibility: nil}
iex> Expresso.Check.Browser.same_size(page, {1920, 1080}, "chrome")
{:ok, %{width: 1920, height: 1080, layout: [], accessibility: nil}}
iex> page = %{width: 500, height: 667, layout: [], accessibility: nil}
iex> {:error, message} = Expresso.Check.Browser.same_size(page, {375, 667}, "chrome")
iex> message
"The browser chrome made a page of 500 × 667, and not of 375 × 667. Chrome cannot " <>
  "make a page narrower than 500 px. Use the headless shell of Playwright in " <>
  "EXPRESSO_CHROMIUM, or a larger --size."

scheme(url, scheme)

@spec scheme(String.t(), :light | :dark | nil) :: String.t()

Add the variant of a theme to the address of a page

iex> Expresso.Check.Browser.scheme("file:///d.html?check", :dark)
"file:///d.html?check&scheme=dark"
iex> Expresso.Check.Browser.scheme("file:///d.html?check", nil)
"file:///d.html?check"

snap?(path)

@spec snap?(Path.t()) :: boolean()

Return true for a command that runs or installs a snap

A command of a snap is a link to /usr/bin/snap, or a short script that names snap, as chromium-browser on Ubuntu is.

with_axe(html, axe)

@spec with_axe(String.t(), Path.t() | nil) :: String.t()

Put axe-core into a document before the end of its body

axe is the path of axe.min.js, or nil for no change. A </script in the source of axe-core would close the element, so it gets a backslash, which JavaScript reads as the same text. Expresso.Check.Session also calls this function.