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
@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
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 Chrome or Chromium
The variable EXPRESSO_CHROMIUM in env gives the path of the browser.
Without it, the function looks in this sequence:
- The browsers of Playwright, as
playwright/1finds them. The headless shell starts fast, and it gives the page its size exactly. - Each usual name of the browser on the
PATH. Chrome comes before Chromium. The function skips a command that installs a snap, such aschromiumon Ubuntu, because that command does not end. - The usual places of Chrome and Chromium on macOS.
@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.
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"]
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.
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}}
@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.
@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."
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"
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.
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.