A fast and accurate web accessibility engine.
- Leading in performance
- Asynchronous design
- Tested and correct
- Extensive production use
- Jampacked with features for accessibility testing
- Works with headless browsers like (playwright and puppeteer) or raw markup
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
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
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
});
kayle
supports multiple test runners which return different results. The built-in test runners are:
fast_axecore
: run tests using fork of axe-core.fast_htmlcs
: run tests using fork of HTML CodeSniffer.kayle
: run tests using the incomplete Rust/wasm Kayle Innate.custom
: custom runners usinginjectRunner
util - library authors.
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");
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;
};
- 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 variableKAYLE_ADBLOCK
needs to be set totrue
.
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.
- da ("Danish")
- de ("German")
- es ("Spanish")
- ja ("Japanese")
- eu ("Basque")
- fr ("French")
- ar ("Arabic")
- ko ("Korean")
- he ("Hebrew")
- nl ("Dutch")
- no-NB ("Norwegian")
- pl ("Polish Poland")
- pt-BR ("Portuguese Brazil")
- zh-CN ("Chinese-Simplified")
- zh-TW ("Chinese-Traditional")
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:
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.
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",
});
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",
});
For the comparison between using fast_htmlcs
, fast_axecore
, and the metrics for the 3rd party @axe-core/playwright
.
yarn build:test
Checkout the playwright-example or puppeteer-example for more information.
fast_htmlcs
runs up to 110x base faster than HTML_CodeSniffer.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.
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.
- 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.
- Use the pre-compiled browser extensions to avoid over the wire latency
yarn build:extension
.
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.
If you want to chat about the project checkout our Discord.
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.
Check the license file in the root of each project.