Skip to content

Commit

Permalink
feat(images): add image capturing element
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez committed Sep 18, 2023
1 parent e951738 commit c5266ac
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 5 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,7 @@ kayle/screenshot.png
chrome-extension

# build custom extension
build-extension.js
build-extension.js

# data dir
_data
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,25 @@ const results = await autoKayle({
});
```

## Clips

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

```ts
const results = await kayle({
page,
browser,
runners: ["axe"],
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:
Expand Down Expand Up @@ -103,6 +122,12 @@ type RunnerConfig = {
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.
Expand All @@ -112,7 +137,7 @@ type RunnerConfig = {
};
```

### Features
### Optional Features

1. 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`.

Expand All @@ -134,6 +159,8 @@ type RunnerConfig = {
1. zh-CN ("Chinese-Simplified")
1. zh-TW ("Chinese-Traditional")

![Video of clips being stored of the issue to get visual feedback](https://user-images.githubusercontent.com/8095978/268726837-f362a490-b611-4acf-8cb6-104f58a0a6c7.gif)

## Testing

For the comparison between using `fast_htmlcs`, `fast_axecore`, and the metrics for the 3rd party `@axe-core/playwright`.
Expand Down
9 changes: 9 additions & 0 deletions kayle/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export type Issue = {
runnerExtras: Record<string, unknown>;
recurrence: number;
selector: string;
// the position on the dom to use for screenshots, targets, and etc.
clip?: {
x: number;
y: number;
width: number;
height: number;
};
// base64 image to display in browser.
clipBase64?: string;
};
// indexs of automatable issues
export type Automatable = {
Expand Down
15 changes: 15 additions & 0 deletions kayle/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ type Page = {
title(): Promise<string>;
content(): Promise<string>;
emulateCPUThrottling(factor: number | null): Promise<void>;
screenshot(s: {
path?: string;
clip?: {
x: number;
y: number;
width: number;
height: number;
};
});
};

export interface CDPSession {
Expand All @@ -150,6 +159,12 @@ export type RunnerConfig = {
runners?: Runner[];
standard?: keyof typeof Standard | Standard;
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.
Expand Down
37 changes: 37 additions & 0 deletions kayle/lib/kayle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const audit = async (config: RunnerConfig): Promise<Audit> => {
standard: config.standard,
origin: config.origin,
language: config.language,
clip: config.clip,
}
);
};
Expand Down Expand Up @@ -93,6 +94,7 @@ export const auditExtension = async (config: RunnerConfig): Promise<Audit> => {
standard: config.standard,
origin: config.origin,
language: config.language,
clip: config.clip,
}
);
};
Expand Down Expand Up @@ -124,6 +126,41 @@ export const kayle = async (

clearTimeout(watcher.timer);

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

try {
const buffer = await o.page.screenshot({
path: o.clipDir
? `${o.clipDir}${
o.clipDir.endsWith("/") ? "" : "/"
}${selector.trim()}.png`
: undefined,
clip: {
x: clip.x,
y: clip.y,
width: clip.width,
height: clip.height,
},
});

// 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");
}
} catch (_) {
// most likely not in the viewport
// console.error(e);
item.clipBase64 = "";
}

return item;
})
);
}

if (!preventClose && navigate) {
try {
await config.page.close();
Expand Down
2 changes: 2 additions & 0 deletions kayle/lib/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export function extractArgs(o, watcher?: Watcher) {
standard: o.standard || "WCAG2AA",
origin: o.origin || (o.html && "http://localhost") || "",
language: o.language || "en",
// store clip tracking element position
clip: o.clip,
};

// parse hidden elements into string
Expand Down
9 changes: 7 additions & 2 deletions kayle/lib/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@
let hiddenElements = null;

// shape the issue
const shapeIssue = (issue) => {
const shapeIssue = (issue, cliped?: boolean) => {
let context = "";
let selector = "";
let clip;

if (issue.element) {
context = getElementContext(issue.element);
selector = getElementSelector(issue.element);
if (cliped) {
clip = issue.element.getBoundingClientRect();
}
}

return {
Expand All @@ -64,6 +68,7 @@
runner: issue.runner || "kayle",
runnerExtras: issue.runnerExtras,
recurrence: issue.recurrence || 0,
clip,
};
};

Expand Down Expand Up @@ -182,7 +187,7 @@
continue;
}

const issue = shapeIssue(is);
const issue = shapeIssue(is, options.clip);

const errorType = issue.type === "error";

Expand Down
3 changes: 2 additions & 1 deletion kayle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kayle",
"version": "0.5.32",
"version": "0.6.0",
"description": "Extremely fast and accurate accessibility engine built for any headless tool like playwright or puppeteer.",
"main": "./build/index.js",
"keywords": [
Expand Down Expand Up @@ -39,6 +39,7 @@
"test:playwright": "npm run compile:test && npx playwright test ./tests/basic-playwright.spec.ts",
"test:playwright:axe": "npm run compile:test && npx playwright test ./tests/basic-axe-playwright.spec.ts",
"test:playwright:htmlcs": "npm run compile:test && npx playwright test ./tests/basic-htmlcs-playwright.spec",
"test:playwright:clips": "npm run compile:test && npx playwright test ./tests/clips-playwright.spec.ts",
"test:puppeteer:wasm": "npm run compile:test && node _tests/tests/wasm.js",
"test:puppeteer:automa": "npm run compile:test && node _tests/tests/automa.js",
"test:puppeteer:extension": "npm run compile:test && yarn build:extension && node _tests/tests/extension.js",
Expand Down
65 changes: 65 additions & 0 deletions kayle/tests/clips-playwright.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import assert from "assert";
import { writeFileSync } from "fs";
import { kayle } from "kayle";
import { drakeMock } from "./mocks/html-mock";
import { performance } from "perf_hooks";
import { test } from "@playwright/test";

test.setTimeout(120000);

test("fast_axecore audit drakeMock", async ({ page, browser }, testInfo) => {
if (process.env.LOG_ENABLED) {
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));
}
const startTime = performance.now();
const results = await kayle({
page,
browser,
runners: ["axe"],
includeWarnings: true,
origin: "https://www.drake.com",
html: drakeMock,
waitUntil: "domcontentloaded",
allowImages: true,
clip: true,
clipDir: "./_data/drake.com",
clip2Base64: true,
});
const endTime = performance.now() - startTime;

const { issues, pageUrl, documentTitle, meta, automateable } = results;

console.log(issues);

console.log([{ meta, automateable }, ["fast_axecore: time took", endTime]]);

// valid list
assert(Array.isArray(issues));
assert(typeof pageUrl === "string");
assert(typeof documentTitle === "string");
assert(meta.warningCount === 9);
assert(meta.errorCount === 34);

writeFileSync(
testInfo.outputPath("axe-core.json"),
JSON.stringify(results, null, 2),
"utf8"
);

writeFileSync(
testInfo.outputPath("axe-core_stats.json"),
JSON.stringify(
{
mock: "[drakeMock]",
htmlSize: drakeMock.length,
duration: endTime,
errors: meta.errorCount,
warnings: meta.warningCount,
runner: ["fast_axecore"],
},
null,
2
),
"utf8"
);
});

0 comments on commit c5266ac

Please sign in to comment.