{"id":13341458,"url":"https://github.com/a11ywatch/kayle","last_synced_at":"2025-04-06T11:08:41.246Z","repository":{"id":63895683,"uuid":"569708046","full_name":"a11ywatch/kayle","owner":"a11ywatch","description":"Next-gen automated web accessibility testing","archived":false,"fork":false,"pushed_at":"2025-03-21T02:32:21.000Z","size":5370,"stargazers_count":30,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-30T09:09:56.053Z","etag":null,"topics":["a11y","accessibility-automation","accessibility-testing","headless","wcag","web-accessibility","web-accessibility-tool"],"latest_commit_sha":null,"homepage":"https://a11ywatch.github.io/kayle/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/a11ywatch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["a11ywatch"]}},"created_at":"2022-11-23T12:44:06.000Z","updated_at":"2025-03-21T02:32:25.000Z","dependencies_parsed_at":"2023-01-14T12:30:49.975Z","dependency_job_id":"2557344e-5bce-40ea-8583-a41790c48c0e","html_url":"https://github.com/a11ywatch/kayle","commit_stats":{"total_commits":324,"total_committers":1,"mean_commits":324.0,"dds":0.0,"last_synced_commit":"05613fec83755d657776a40b037228adca438fd0"},"previous_names":["a11ywatch/a11y","a11ywatch/litepa11y"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a11ywatch%2Fkayle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a11ywatch%2Fkayle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a11ywatch%2Fkayle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/a11ywatch%2Fkayle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/a11ywatch","download_url":"https://codeload.github.com/a11ywatch/kayle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247471520,"owners_count":20944158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a11y","accessibility-automation","accessibility-testing","headless","wcag","web-accessibility","web-accessibility-tool"],"created_at":"2024-07-29T19:25:26.314Z","updated_at":"2025-04-06T11:08:41.205Z","avatar_url":"https://github.com/a11ywatch.png","language":"TypeScript","funding_links":["https://github.com/sponsors/a11ywatch"],"categories":[],"sub_categories":[],"readme":"# kayle\n\n[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n[![CI](https://github.com/a11ywatch/kayle/actions/workflows/CI.yml/badge.svg)](https://github.com/a11ywatch/kayle/actions?query=workflow%3ABuild)\n[![Discord chat][discord-badge]][discord-url]\n\nA **fast** and **accurate** web accessibility engine.\n\n- Leading in performance\n- Asynchronous design\n- Tested and **correct**\n- Extensive production use\n- Jampacked with features for accessibility testing\n- Works with headless browsers like ([playwright](https://github.com/microsoft/playwright) and [puppeteer](https://github.com/puppeteer/puppeteer)) or raw markup\n\n## Getting Started\n\nInstall your browser automation lib [playwright](https://github.com/microsoft/playwright) or [puppeteer](https://github.com/puppeteer/puppeteer).\n\n```sh\n# npm i puppeteer or npm i playwright\nnpm install kayle --save\n```\n\n```ts\nimport { kayle } from \"kayle\";\n\n// Playwright 🎭 or Puppeteer 🤖\nconst page = await browser.newPage();\n\n// audit with a url or raw html\nconst results = await kayle({\n  page,\n  browser,\n  runners: [\"htmlcs\"] // options are  \"htmlcs\" and \"axe\". The order is from fastest to slowest. Defaults to htmlcs.\n  origin: \"https://a11ywatch.com\",\n  // html: \"\u003chtml\u003e...\u003c/html\u003e\"\n});\n```\n\nIt is recommended to use `htmlcs` as the runner or simply not declare a runner for the default.\nWe did a massive rewrite on htmlcs and it is extremely fast and stable.\n\nWhen 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.\n\nIf you need to run a full site-wide crawl import `autoKayle` which uses Rust/Wasm to gather the links fast.\n\n```ts\nimport { autoKayle, setLogging } from \"kayle\";\nimport { chromium } from \"playwright\";\n\n// enable kayle log output\nsetLogging(true);\n\nconst browser = await chromium.launch({ headless: true });\nconst page = await browser.newPage();\n\n// crawls are continued to the next page automatically fast!\nconst results = await autoKayle({\n  page,\n  browser,\n  includeWarnings: true,\n  origin: \"https://a11ywatch.com\",\n  waitUntil: \"domcontentloaded\",\n  cb: function callback(result) {\n    console.log(result);\n  },\n});\n```\n\n```sh\n# Output of time between runners with a realistic run - Rust/WASM is in the making and not yet ready.\n# 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.\n\n# puppeteer - speed is stable across most versions\n# Rust/WASM 10.863582968711853\n# FAST_HTMLCS 29.915208011865616\n# FAST_AXE 162.87204200029373\n\n# playwright - the speed depends on the version\n# Rust/WASM TIME  10.163457989692688\n# FAST_HTMLCS TIME 33.50962498784065\n# FAST_AXE TIME 203.2565419971943\n```\n\n## CLI\n\nIf you need to use kayle in the CI or shell use [kayle CLI](./kayle_cli/).\n\n```sh\nnpm install kayle_cli\n# configure the engine defaults.\nkayle_cli --automation-lib puppeteer --standard wcag2aa configure\n# install the deps for the runner.\nkayle_cli install\n# audit a website url.\nkayle_cli https://www.somewebsite.com\n```\n\n## Clips\n\nYou can include base64 images with the audits to get a visual of the exact location of the issue.\n\n```ts\nconst results = await kayle({\n  page,\n  browser,\n  includeWarnings: true,\n  origin: \"https://www.drake.com\",\n  waitUntil: \"domcontentloaded\",\n  allowImages: true,\n  clip: true, // get the clip cords to display in browser. Use clipDir or clip2Base64 to convert to image.\n  clipDir: \"./_data/drake.com\", // optional: directory to store the clip as an image.\n  clip2Base64: true, // optional: attach a base64 property of the clip\n});\n```\n\n## Runners\n\n`kayle` supports multiple test runners which return different results. The built-in test runners are:\n\n- [`fast_axecore`](./fast_htmlcs/README.md): run tests using fork of [axe-core](./lib/runners/axe.ts).\n- [`fast_htmlcs`](./fast_htmlcs/README.md): run tests using fork of [HTML CodeSniffer](./lib/runners/htmlcs.ts).\n- [`kayle`](./kayle_innate/README.md): run tests using the incomplete Rust/wasm [Kayle Innate](./kayle_innate).\n- `custom`: custom runners using [`injectRunner`](./kayle/lib/runner-js.ts#l=57) util - library authors.\n\n## Linting\n\nStraight 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`.\n\n```js\nimport { kayleLint } from \"kayle/build/lint\";\n\nawait kayleLint(\"https://a11ywatch.com\");\n```\n\n## Configuration\n\nThe `extraConfigs` object has the following:\n\n```ts\ntype RunnerConfig = {\n  browser: Partial\u003cBrowser\u003e;\n  page: Partial\u003cPage\u003e;\n  // a custom cdp session. Useful for playwright persisting sessions.\n  cdpSession?: Partial\u003cCDPSession\u003e;\n  // configure if you know how the page will operate headless.\n  waitUntil?: LifeCycleEvent;\n  // actions to perform.\n  actions?: string[];\n  // ignore list of elements using css selectors.\n  hideElements?: string;\n  // ignore test cases WCAG.\n  ignore?: string[];\n  // include notices in results.\n  includeNotices?: boolean;\n  // include warnings in results.\n  includeWarnings?: boolean;\n  // the root element to test.\n  rootElement?: string;\n  // only allow WCAG RULES.\n  rules?: string[];\n  // axe or htmlcs - the forks.\n  runners?: (\"axe\" | \"htmlcs\")[];\n  // the accessibility standard.\n  standard?: Standard;\n  // stop test that go beyond time.\n  timeout?: number;\n  // allow capturing the image visually to base64\n  clip?: boolean;\n  // store clips to a directory must have allowImages set or CDP reset of intercepts\n  clipDir?: string;\n  // store a clip to base64 on the issue\n  clip2Base64?: boolean;\n  // allow images to render.\n  allowImages?: boolean;\n  // the website url: include this even with static html to fetch assets correct.\n  origin?: string;\n  // the langauge to use.\n  language?: string;\n};\n```\n\n### Optional Features\n\n1. adblock - You can enable Brave's adblock engine with [adblock-rs](https://github.com/brave/adblock-rust) 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`.\n\n## Localization\n\n[Locales](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/i18n) 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.\n\n1. da (\"Danish\")\n1. de (\"German\")\n1. es (\"Spanish\")\n1. ja (\"Japanese\")\n1. eu (\"Basque\")\n1. fr (\"French\")\n1. ar (\"Arabic\")\n1. ko (\"Korean\")\n1. he (\"Hebrew\")\n1. nl (\"Dutch\")\n1. no-NB (\"Norwegian\")\n1. pl (\"Polish Poland\")\n1. pt-BR (\"Portuguese Brazil\")\n1. zh-CN (\"Chinese-Simplified\")\n1. zh-TW (\"Chinese-Traditional\")\n\n## Browser Extension\n\nIf 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.\n\nFirst build the extension with the command:\n\n1. `yarn build:extension`\n\nCopy the contents into your directory to load using chromes `--load-extension` and enable the flag `--extensions-on-chrome-urls`.\n\nView the [extension-test](kayle/tests/extension.ts) for an example on how to setup chrome with the generated extension.\n\nCurrently we only have english support for extensions. We can add different locales for the generated scripts by manually adjusting the targets.\n\nIf you want to test the extension use `yarn test:puppeteer:extension`.\n\nThe `kayle` function also expects a field called `browserExtension` with the option set to `true`. Currently the extension handling is experimental reason for the name.\n\n## Extend Runner\n\nExtending a runner and adding new rules can be done with the following at runtime.\n\n```ts\nimport { extendRunner, kayle } from \"kayle\"\n\n// pure javascript required. No typescript!\n  extendRunner(\n    MainRunner.htmlcs,\n    `\n  // store the prior sniff in a variable to re-use the logic\n  const prevHeadSniffCase = HTMLCS_WCAG2AAA_Sniffs_Principle2_Guideline2_4_2_4_2.process;\n\n  HTMLCS_WCAG2AAA_Sniffs_Principle2_Guideline2_4_2_4_2.process = (element, _) =\u003e {\n    // re-run the logic for the case\n    prevHeadSniffCase(element, _);\n    // we can write a test here that should pass some logic. For now we just add a new error\n    HTMLCS.addMessage(\n        HTMLCS.ERROR,\n        element,\n        HTMLCS.getTranslation(\"2_4_2_H25.1.NoHeadEl\"),\n        \"H25.1.NoHeadEl\"\n    );\n  }\n\n  // Add a new rule example - 4_1_4_1_4\n\n  window[\"HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_4\"] = {\n    register: () =\u003e [\"html\"],\n    process: (element, _) =\u003e {\n        console.log(\"NEW Rule run!\");\n        HTMLCS.addMessage(\n            HTMLCS.ERROR,\n            element,\n            \"This is some new rule for something.\",\n            \"H55.1.NoItem\"\n        );\n    },\n  };\n\n  // push the new sniff to the list\n  HTMLCS_WCAG2AAA.sniffs.push(\"Principle4.Guideline4_1.4_1_4\");\n  // register the new sniff rule to run\n  HTMLCS.registerSniff(\"WCAG2AAA\", \"Principle4.Guideline4_1.4_1_4\");\n  `.trimStart());\n\nconst results = await kayle({\n  page,\n  browser,\n  runners: [\"htmlcs\", \"htmlcs_extended\"]\n  origin: \"https://a11ywatch.com\",\n});\n```\n\n## Custom Runner (library authors)\n\nBelow is an example on adding a new custom runner. Take a look at [HTMLCS](fast_htmlcs/HTMLCS.ts) and [HTMLCS_Runner](kayle/lib/runners/htmlcs.ts) 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.\n\n```ts\nimport { injectRunner, kayle } from \"kayle\"\n\n// example of the custom script\ninjectRunner(\"htmlcs_extended\", \"./custom_htmlcs_script\", \"en\")\n\nconst results = await kayle({\n  page,\n  browser,\n  runners: [\"htmlcs\", \"htmlcs_extended\"]\n  origin: \"https://a11ywatch.com\",\n});\n```\n\n## Testing\n\nFor the comparison between using `fast_htmlcs`, `fast_axecore`, and the metrics for the 3rd party `@axe-core/playwright`.\n\n1. `yarn build:test`\n\nCheckout the [playwright-example](./kayle/tests/basic-playwright.spec.ts) or [puppeteer-example](./kayle/tests/basic.ts) for more information.\n\n## Benchmarks\n\n1. `fast_htmlcs` runs up to 110x base faster than HTML_CodeSniffer.\n1. `fast_axecore` runs up to 2.5x - 15x base faster than the original axe by default and scales the larger the website.\n\nCurrently `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.\n\nIf you use [`@playwright/axe-core`](https://playwright.dev/docs/next/accessibility-testing) you can swap it out with the following [playwright-axe-example](./kayle/tests/basic-axe-playwright.spec.ts) 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.\n\n### Performance Tips\n\nAs 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.\n\n1. Use a fast concurrent [crawler](https://github.com/a11ywatch/crawler) to gather all of the html to send to a web accessibility service that can perform audits like [pagemind](https://github.com/a11ywatch/pagemind) over CDP.\n2. Use the pre-compiled browser extensions to avoid over the wire latency `yarn build:extension`.\n\n## Developing\n\nIn order to develop you need yarn v2 installed for the workspace.\n\nRun the following to install on ^node@18\n\n`corepack enable \u0026\u0026 corepack prepare yarn@stable --activate` and reload shell after.\n\nUse the command `yarn build` to compile all the scripts for each locale.\n\n## Discord\n\nIf you want to chat about the project checkout our [Discord][discord-url].\n\n## About\n\nThis project took Axecore and HTMLCS from versions that were complete and semi-stable.\nWe patched and fixed a lot of bugs that increased the accuracy of tests passing and issues being found.\nOne 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.\n\n## LICENSE\n\nCheck the license file in the root of each project.\n\n[discord-badge]: https://img.shields.io/discord/860982761137111040.svg?logo=discord\n[discord-url]: https://discord.gg/H82vceTBNu\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa11ywatch%2Fkayle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fa11ywatch%2Fkayle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fa11ywatch%2Fkayle/lists"}