kayle

MIT licensed CI Discord chat

A fast and accurate web accessibility engine.

Getting Started

Install your browser automation lib playwright or puppeteer.

# npm i puppeteer or npm i playwright
npm install kayle --save
import { kayle } from "kayle";

// Playwright 🎭 or Puppeteer 🤖
const page = await browser.newPage();

// audit with a url or raw html
const results = await kayle({
  page,
  browser,
  runners: ["htmlcs"] // options are  "htmlcs" and "axe". The order is from fastest to slowest. Defaults to htmlcs.
  origin: "https://a11ywatch.com",
  // html: "<html>...</html>"
});

It is recommended to use htmlcs as the runner or simply not declare a runner for the default. We did a massive rewrite on htmlcs and it is extremely fast and stable.

When passing raw html try to also include the origin or the url, this sets window.origin and helps scripts that rely on it to work correctly or else relatives scripts will not work since the relative path does not exist on the local machine.

If you need to run a full site-wide crawl import autoKayle which uses Rust/Wasm to gather the links fast.

import { autoKayle, setLogging } from "kayle";
import { chromium } from "playwright";

// enable kayle log output
setLogging(true);

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();

// crawls are continued to the next page automatically fast!
const results = await autoKayle({
  page,
  browser,
  includeWarnings: true,
  origin: "https://a11ywatch.com",
  waitUntil: "domcontentloaded",
  cb: function callback(result) {
    console.log(result);
  },
});
# Output of time between runners with a realistic run - Rust/WASM is in the making and not yet ready.
# Measurement is only calculated from the runner and not the extra latency to get the page initially. View the `innate` test to see more detals.

# puppeteer - speed is stable across most versions
# Rust/WASM 10.863582968711853
# FAST_HTMLCS 29.915208011865616
# FAST_AXE 162.87204200029373

# playwright - the speed depends on the version
# Rust/WASM TIME  10.163457989692688
# FAST_HTMLCS TIME 33.50962498784065
# FAST_AXE TIME 203.2565419971943

CLI

If you need to use kayle in the CI or shell use kayle CLI.

npm install kayle_cli
# configure the engine defaults.
kayle_cli --automation-lib puppeteer --standard wcag2aa configure
# install the deps for the runner.
kayle_cli install
# audit a website url.
kayle_cli https://www.somewebsite.com

Clips

You can include base64 images with the audits to get a visual of the exact location of the issue.

const results = await kayle({
  page,
  browser,
  includeWarnings: true,
  origin: "https://www.drake.com",
  waitUntil: "domcontentloaded",
  allowImages: true,
  clip: true, // get the clip cords to display in browser. Use clipDir or clip2Base64 to convert to image.
  clipDir: "./_data/drake.com", // optional: directory to store the clip as an image.
  clip2Base64: true, // optional: attach a base64 property of the clip
});

Runners

kayle supports multiple test runners which return different results. The built-in test runners are:

Linting

Straight forward linting without a browser. You can pass a url or valid html. Linting is handled on the same machine not sandboxed. You also need to install jsdom before hand ex: yarn add jsdom.

import { kayleLint } from "kayle/build/lint";

await kayleLint("https://a11ywatch.com");

Configuration

The extraConfigs object has the following:

type RunnerConfig = {
  browser: Partial<Browser>;
  page: Partial<Page>;
  // a custom cdp session. Useful for playwright persisting sessions.
  cdpSession?: Partial<CDPSession>;
  // configure if you know how the page will operate headless.
  waitUntil?: LifeCycleEvent;
  // actions to perform.
  actions?: string[];
  // ignore list of elements using css selectors.
  hideElements?: string;
  // ignore test cases WCAG.
  ignore?: string[];
  // include notices in results.
  includeNotices?: boolean;
  // include warnings in results.
  includeWarnings?: boolean;
  // the root element to test.
  rootElement?: string;
  // only allow WCAG RULES.
  rules?: string[];
  // axe or htmlcs - the forks.
  runners?: ("axe" | "htmlcs")[];
  // the accessibility standard.
  standard?: Standard;
  // stop test that go beyond time.
  timeout?: number;
  // allow capturing the image visually to base64
  clip?: boolean;
  // store clips to a directory must have allowImages set or CDP reset of intercepts
  clipDir?: string;
  // store a clip to base64 on the issue
  clip2Base64?: boolean;
  // allow images to render.
  allowImages?: boolean;
  // the website url: include this even with static html to fetch assets correct.
  origin?: string;
  // the langauge to use.
  language?: string;
};

Optional Features

  1. adblock - You can enable Brave’s adblock engine with adblock-rs by installing npm i adblock-rs to the project. This module needs to be manually installed and the env variable KAYLE_ADBLOCK needs to be set to true.

Localization

Locales supported by the runner using pre-compilition. In order to pre-compile the locales run yarn build. Some locales are only available in certain runners.

  1. da (“Danish”)
  2. de (“German”)
  3. es (“Spanish”)
  4. ja (“Japanese”)
  5. eu (“Basque”)
  6. fr (“French”)
  7. ar (“Arabic”)
  8. ko (“Korean”)
  9. he (“Hebrew”)
  10. nl (“Dutch”)
  11. no-NB (“Norwegian”)
  12. pl (“Polish Poland”)
  13. pt-BR (“Portuguese Brazil”)
  14. zh-CN (“Chinese-Simplified”)
  15. zh-TW (“Chinese-Traditional”)

Browser Extension

If you want to compile a chrome extension for preloading scripts without needing to worry about bandwidth cost use the following to generate a custom extension to use.

First build the extension with the command:

  1. yarn build:extension

Copy the contents into your directory to load using chromes --load-extension and enable the flag --extensions-on-chrome-urls.

View the extension-test for an example on how to setup chrome with the generated extension.

Currently we only have english support for extensions. We can add different locales for the generated scripts by manually adjusting the targets.

If you want to test the extension use yarn test:puppeteer:extension.

The kayle function also expects a field called browserExtension with the option set to true. Currently the extension handling is experimental reason for the name.

Extend Runner

Extending a runner and adding new rules can be done with the following at runtime.

import { extendRunner, kayle } from "kayle"

// pure javascript required. No typescript!
  extendRunner(
    MainRunner.htmlcs,
    `
  // store the prior sniff in a variable to re-use the logic
  const prevHeadSniffCase = HTMLCS_WCAG2AAA_Sniffs_Principle2_Guideline2_4_2_4_2.process;

  HTMLCS_WCAG2AAA_Sniffs_Principle2_Guideline2_4_2_4_2.process = (element, _) => {
    // re-run the logic for the case
    prevHeadSniffCase(element, _);
    // we can write a test here that should pass some logic. For now we just add a new error
    HTMLCS.addMessage(
        HTMLCS.ERROR,
        element,
        HTMLCS.getTranslation("2_4_2_H25.1.NoHeadEl"),
        "H25.1.NoHeadEl"
    );
  }

  // Add a new rule example - 4_1_4_1_4

  window["HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_4"] = {
    register: () => ["html"],
    process: (element, _) => {
        console.log("NEW Rule run!");
        HTMLCS.addMessage(
            HTMLCS.ERROR,
            element,
            "This is some new rule for something.",
            "H55.1.NoItem"
        );
    },
  };

  // push the new sniff to the list
  HTMLCS_WCAG2AAA.sniffs.push("Principle4.Guideline4_1.4_1_4");
  // register the new sniff rule to run
  HTMLCS.registerSniff("WCAG2AAA", "Principle4.Guideline4_1.4_1_4");
  `.trimStart());

const results = await kayle({
  page,
  browser,
  runners: ["htmlcs", "htmlcs_extended"]
  origin: "https://a11ywatch.com",
});

Custom Runner (library authors)

Below is an example on adding a new custom runner. Take a look at HTMLCS and HTMLCS_Runner setup for an example of how to setup the scripts, it could take a bit of time to get familiar. You can also overwride the base runners by taking the runnersJavascript object and appending to the script.

import { injectRunner, kayle } from "kayle"

// example of the custom script
injectRunner("htmlcs_extended", "./custom_htmlcs_script", "en")

const results = await kayle({
  page,
  browser,
  runners: ["htmlcs", "htmlcs_extended"]
  origin: "https://a11ywatch.com",
});

Testing

For the comparison between using fast_htmlcs, fast_axecore, and the metrics for the 3rd party @axe-core/playwright.

  1. yarn build:test

Checkout the playwright-example or puppeteer-example for more information.

Benchmarks

  1. fast_htmlcs runs up to 110x base faster than HTML_CodeSniffer.
  2. fast_axecore runs up to 2.5x - 15x base faster than the original axe by default and scales the larger the website.

Currently fast_htmlcs runs around 50x faster than axe-core and has several differences of handling the way issues are found. They both capture different cases and is best to used together which this library handles efficiently.

If you use @playwright/axe-core you can swap it out with the following playwright-axe-example and get an increase in issues found and major performance boost of at least 100%. You can also include multiple runners to extend the issues beyond the basics in folds.

Performance Tips

As we set the foundation to mark test cases that can pass and increase our target on automating accessibility we have a couple of layers that can make a major difference to the project. The following will save drastic time and money if done.

  1. Use a fast concurrent crawler to gather all of the html to send to a web accessibility service that can perform audits like pagemind over CDP.
  2. Use the pre-compiled browser extensions to avoid over the wire latency yarn build:extension.

Developing

In order to develop you need yarn v2 installed for the workspace.

Run the following to install on ^node@18

corepack enable && corepack prepare yarn@stable --activate and reload shell after.

Use the command yarn build to compile all the scripts for each locale.

Discord

If you want to chat about the project checkout our Discord.

About

This project took Axecore and HTMLCS from versions that were complete and semi-stable. We patched and fixed a lot of bugs that increased the accuracy of tests passing and issues being found. One of the main goals was to have the audit run quickly since we noticed some of the tests would take several seconds to complete. Right now, the project is moving forward based on performance and accuracy for ensuring minimal false positives.

LICENSE

Check the license file in the root of each project.