Skip to content

Commit

Permalink
feat(runner): add kayle runner expose
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez committed Feb 12, 2024
1 parent d3c6bf2 commit 28e68b9
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 59 deletions.
4 changes: 3 additions & 1 deletion kayle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ kayle supports multiple test runners which return different results. The built-i

- `axe`: run tests using [axe-core](./lib/runners/axe.ts).
- `htmlcs` (default): run tests using [HTML CodeSniffer](./lib/runners/htmlcs.ts)
- `kayle` (experimental): run tests using with [Rust Kayle_Innate or accessibility-rs](https://github.com/a11ywatch/accessibility-rs).
- `custom`: custom runners using `injectRunner` util.

## Playwright/Puppeteer
Expand All @@ -103,7 +104,8 @@ If you are using puppeteer expect around 2x slower results.

Straight forward linting. You can pass a url or valid html.

Linting is handled on the same machine not sandboxed.
Linting is handled on the same machine not sandboxed. You can also use the `kayleInnateBuilder` to
get the wasm kayle setup to lint pages.

```js
import { kayleLint } from "kayle/lint";
Expand Down
28 changes: 25 additions & 3 deletions kayle/lib/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RunnerConfig } from "./config";

export type IssueType = "error" | "warning" | "notice";

export type MetaInfo = {
errorCount: number;
warningCount: number;
Expand All @@ -11,17 +13,37 @@ export type Issue = {
context: string;
code: string;
message: string;
type: "error" | "warning" | "notice";
type: IssueType;
typeCode: number;
runner: "htmlcs" | "axe" | "a11ywatch";
// kayle is mapped from accessibility-rs
runner: "htmlcs" | "axe" | "kayle";
runnerExtras: Record<string, unknown>;
recurrence: number;
selector: string;
// the position on the dom to use for screenshots, targets, and etc.
clip?: DOMRect;
clip?: Pick<DOMRect, "x" | "y" | "height" | "width">;
// base64 image to display in browser.
clipBase64?: string;
};

export type InnateIssue = {
context: string;
selectors: string[];
code: string;
issue_type: IssueType;
type_code: number;
message: string;
runner: "accessibility-rs";
runner_extras: { help_url: string; description: string; impact: string };
recurrence: number;
clip?: {
x: number;
y: number;
height: number;
width: number;
};
};

// indexs of automatable issues
export type Automatable = {
// indexs of all missing alt tags.
Expand Down
4 changes: 4 additions & 0 deletions kayle/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ export type RunnerConfig = {
_watcher?: Watcher;
// initial fake request ran to enable Js
_initRequest?: boolean;
// the incomplete kayle wasm runner
_kayleRunner?: boolean;
// contains a base runner htmlcs or axe
_includesBaseRunner?: boolean;
};

// log singleton
Expand Down
78 changes: 69 additions & 9 deletions kayle/lib/kayle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { RunnerConfig, _log } from "./config";
import { runnersJavascript, getRunner } from "./runner-js";
import { goToPage, setNetworkInterception } from "./utils/go-to-page";
import { Watcher } from "./watcher";
import { Audit, RunnerConf } from "./common";
import { Audit, type InnateIssue, RunnerConf } from "./common";
import { getAllCss } from "./wasm";

// perform audit
const audit = async (config: RunnerConfig): Promise<Audit> => {
Expand Down Expand Up @@ -98,6 +99,65 @@ export const auditExtension = async (config: RunnerConfig): Promise<Audit> => {
);
};

// lazy load kayle innate
let kayle_innate;

// run the rust wasm audit.
// we do not need timers here since almost all audits perform under 25ms.
// we still need to add the score map and apply the score based on what is found.
// the thing is we need the old map to make sure we do not repeat values.
const auditPageInnate = async (
config: ReturnType<typeof extractArgs>,
results: Audit
) => {
if (!config._includesBaseRunner) {
await runActionsList(config as RunnerConfig);
}

const html = await config.page.content();
const css = await getAllCss(config as RunnerConfig);

if (!kayle_innate) {
kayle_innate = await import("kayle_innate");
}

const innateAudit: InnateIssue[] = await kayle_innate.audit(
html,
css,
config.clip
);

for (const innateIssue of innateAudit) {
if (innateIssue.issue_type === "error") {
results.meta.errorCount += innateIssue.recurrence || 1;
}
if (innateIssue.issue_type === "warning") {
results.meta.warningCount += innateIssue.recurrence || 1;
}
if (innateIssue.issue_type === "notice") {
results.meta.noticeCount += innateIssue.recurrence || 1;
}

results.issues.push({
code: innateIssue.code,
type: innateIssue.issue_type,
typeCode: innateIssue.type_code,
message: innateIssue.message,
recurrence: innateIssue.recurrence,
clip: innateIssue.clip,
runner: "kayle",
runnerExtras: innateIssue.runner_extras,
selector: innateIssue.selectors.join(","), // combo the selectors
context: innateIssue.context,
});

// add value to missing alt index handling
if (innateIssue.code === "Principle1.Guideline1_1.1_1_1.H37") {
results.automateable.missingAltIndexs.push(results.issues.length - 1);
}
}
};

/**
* Run accessibility tests for page.
* @param {Object} [config={}] config - Options to change the way tests run.
Expand Down Expand Up @@ -125,11 +185,9 @@ export const kayle = async (

clearTimeout(watcher.timer as number);

if (results && o.clip && Array.isArray(results.issues)) {
if (results && o.clip && o.clip2Base64 && Array.isArray(results.issues)) {
results.issues = await Promise.all(
results.issues.map(async (item) => {
const { clip, selector } = item;

// prevent screenshots
if (typeof o.clipMax === "number") {
if (!o.clipMax) {
Expand All @@ -143,15 +201,13 @@ export const kayle = async (
path: o.clipDir
? `${o.clipDir}${
o.clipDir.endsWith("/") ? "" : "/"
}${selector.trim()}.png`
}${item.selector.trim()}.png`
: undefined,
clip,
clip: item.clip,
});

// use a dynamic property to inject - todo: set the config initially before this iteration to keep shape aligned.
if (o.clip2Base64) {
item.clipBase64 = buffer.toString("base64");
}
item.clipBase64 = buffer.toString("base64");
} catch (_) {
// most likely not in the viewport
// console.error(e);
Expand All @@ -163,6 +219,10 @@ export const kayle = async (
);
}

if (config._kayleRunner) {
await auditPageInnate(config, results);
}

if (!preventClose && navigate) {
try {
await config.page.close();
Expand Down
20 changes: 13 additions & 7 deletions kayle/lib/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ export function extractArgs(o, watcher?: Watcher) {
includeWarnings: o.includeWarnings,
rootElement: o.rootElement,
rules: o.rules || [],
// soon move default to kayle
runners: o.runners || ["htmlcs"],
standard: o.standard,
origin: o.origin || (o.html && "http://localhost") || "",
language: o.language || "en",
// store clip tracking element position
clip: o.clip,
_kayleRunner: false,
_includesBaseRunner: false,
};

// parse hidden elements into string
Expand All @@ -45,16 +48,19 @@ export function extractArgs(o, watcher?: Watcher) {

// default to a runner
if (
!options.runners.some(
(runner) => runner === "axe" || runner === "htmlcs"
// ||
// // wasm build when released
// runner === "kayle"
)
options.runners.forEach((runner, runnerIndex, ar) => {
if (runner === "axe" || runner === "htmlcs") {
options._includesBaseRunner = true;
}
if (runner === "kayle") {
options._kayleRunner = true;
ar.splice(runnerIndex, 1);
}
}) &&
(options._includesBaseRunner || options._kayleRunner)
) {
options.runners.push("htmlcs");
}

// todo: validate all options
return options;
}
1 change: 0 additions & 1 deletion kayle/lib/runner-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const getRunner = (

export type Runner = Exclude<
keyof typeof runnersJavascript,
| "kayle"
| "htmlcs_es"
| "htmlcs_ja"
| "htmlcs_fr"
Expand Down
6 changes: 5 additions & 1 deletion kayle/lib/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@
if (issue.element) {
context = getElementContext(issue.element);
selector = getElementSelector(issue.element);
if (cliped && typeof issue.element.getBoundingClientRect === "function") {
if (
cliped &&
!issue.bounds &&
typeof issue.element.getBoundingClientRect === "function"
) {
const { x, y, width, height } = issue.element.getBoundingClientRect();

clip = {
Expand Down
2 changes: 1 addition & 1 deletion kayle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kayle",
"version": "0.8.12",
"version": "0.8.13",
"description": "Extremely fast and accurate accessibility engine built for any headless tool like playwright or puppeteer.",
"main": "./build/index.js",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion kayle/tests/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { performance } from "perf_hooks";
const { issues, pageUrl, documentTitle, meta, automateable } = await kayle({
page,
browser,
runners: ["htmlcs", "axe"],
runners: ["htmlcs", "axe", "kayle"],
includeWarnings: true,
html: drakeMock,
standard: Standard.WCAG2AA,
Expand Down
44 changes: 9 additions & 35 deletions kayle/tests/innate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { audit } from "kayle_innate";
import puppeteer from "puppeteer";
import { innateBuilder, kayle } from "kayle";
import { kayle } from "kayle";
import { drakeMock } from "./mocks/html-mock";
import { performance } from "perf_hooks";

Expand All @@ -11,15 +10,7 @@ import { performance } from "perf_hooks";
if (process.env.LOG_ENABLED) {
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));
}
const { html, css } = await innateBuilder({
page,
browser,
includeWarnings: true,
origin: "https://www.drake.com",
html: drakeMock,
});

const mock = html
const mock = drakeMock
.replace(
"<title>Drake Industries | Custom, Durable, High-Quality Labels, Asset Tags and Custom Server Bezels</title>",
""
Expand All @@ -33,35 +24,18 @@ import { performance } from "perf_hooks";
);

const startTime = performance.now();
await audit(mock, css, false);
const nextTime = performance.now() - startTime;
console.log("Rust/WASM TIME ", nextTime);

const st = performance.now();
await kayle({
// run kayle with a real browser against the page.
const issues = await kayle({
page,
browser,
runners: ["htmlcs"],
runners: ["kayle"],
includeWarnings: true,
origin: "https://www.drake.com",
html: drakeMock,
noIntercept: true,
html: mock,
});
const nt = performance.now() - st;
console.log("FAST_HTMLCS TIME", nt);

const s = performance.now();
await kayle({
page,
browser,
runners: ["axe"],
includeWarnings: true,
origin: "https://www.drake.com",
html: drakeMock,
noIntercept: true,
});
const n = performance.now() - s;
console.log("FAST_AXE TIME", n);
const nextTime = performance.now() - startTime;
console.log("Kayle Innate TIME ", nextTime);
console.log(issues);

await browser.close();
})();

0 comments on commit 28e68b9

Please sign in to comment.