Skip to content

Commit

Permalink
feat(cli): add clip options
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mendez committed Feb 19, 2024
1 parent 588608f commit 082ae4b
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 35 deletions.
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ We are building a rust based runner called [kayle_innate](./kayle_innate/) that

## Extend Runner

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

```ts
import { extendRunner, kayle } from "kayle"
Expand All @@ -236,17 +236,12 @@ import { extendRunner, kayle } from "kayle"
extendRunner(
MainRunner.htmlcs,
`
// use console.log(JSON.stringify(Object.keys(window))) to see all of the objects to extend.
// set the function HTMLCS_WCAG2AAA_Sniffs_Principle2_Guideline2_4_2_4_2.process to a variable to re-use the logic prior in the call.
// 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, _);
// log something to test if output ran
console.log("Running extended head element case");
// we can write a test here that should pass some logic. For now we just add a new error
HTMLCS.addMessage(
HTMLCS.ERROR,
Expand Down Expand Up @@ -275,8 +270,7 @@ import { extendRunner, kayle } from "kayle"
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()
);
`.trimStart());

const results = await kayle({
page,
Expand Down
2 changes: 1 addition & 1 deletion kayle_cli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kayle_cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kayle_cli"
version = "0.0.3"
version = "0.0.5"
edition = "2021"
description = "The kayle CLI for web accessibility auditing"
repository = "https://github.com/a11ywatch/kayle"
Expand Down
49 changes: 39 additions & 10 deletions kayle_cli/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
# kayle_cli

The kayle CLI, for web accessibility audits. [WIP]
The kayle CLI, for web accessibility audits.

## Requirements

Node.js is required.

## Installation

If Rust is installed.

```sh
# with rust and cargo
cargo install kayle_cli
# or with node.
npm install kayle_cli
```

## Getting Started

Make sure you have an automation lib installed. Use the arg `--automation-lib` to switch from the default puppeteer to playwright.
Make sure you have an automation lib installed. Use the arg `--automation-lib` to switch from the default puppeteer to playwright.

```sh
kayle_cli --automation-lib puppeteer install
# configure the audits
kayle_cli --automation-lib puppeteer --standard wcag2aa configure
# install the deps for auditing like puppeteer, kayle, and etc.
kayle_cli install
```

Pass in a list of urls to get the results.
Expand Down Expand Up @@ -51,12 +51,12 @@ Options:

--include-warnings <INCLUDE_WARNINGS>
Include warnings in the audit

[possible values: true, false]

--include-notices <INCLUDE_NOTICES>
Include notices in the audit

[possible values: true, false]

-r, --runners <RUNNERS>
Expand All @@ -67,11 +67,40 @@ Options:
- htmlcs: htmlcs
- axe: axe

-w, --wait-for <WAIT_FOR>
WaitFor event for content to exist

Possible values:
- load: Waits till the window load event
- domcontent-loaded: The dom loaded content first
- commit: Wait for the commit event. Playwright only
- network-idle: Waits till there are no more network connections for at least 500 ms. Playwright only
- network-idle1: Waits till there are no more network connections for at least 500 ms. Puppeteer only
- network-idle2: Waits till there are no more than 2 network connections for at least 500 ms. Puppeteer only

--allow-images <ALLOW_IMAGES>
Allow images to render, useful when setting clip option for bounding box

[possible values: true, false]

--clip <CLIP>
Get the bounding box of an element

[possible values: true, false]

--clip-dir <CLIP_DIR>
The directory to store clip images

--clip-2-base64 <CLIP_2_BASE64>
Convert the clip to a base64 image

[possible values: true, false]

--automation-lib <AUTOMATION_LIB>
The automation lib to use either puppeteer or playwright

Possible values:
- puppeteer: The puppeteer library. Defaults to this
- puppeteer: The puppeteer library. The Default
- playwright: The playwright library by microsoft

-h, --help
Expand All @@ -83,5 +112,5 @@ Options:

```sh
kayle_cli https://www.drake.com
{"documentTitle":"Drake Industries | Custom, Durable, High-Quality Labels, Asset Tags and Custom Server Bezels","pageUrl":"https://www.drake.com/","issues":[{"context":"<h5 class=\"normal\">\n<div class=\"span12 widget-span ...</h5>","selector":"body>:nth-child(2)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_3.1_3_1_A.G141","type":"error","typeCode":1,"message":"The heading structure is not logically nested. This h5 element should be an h2 to be properly nested.","runner":"htmlcs","recurrence":0},{"context":"<h5>Labels</h5>","selector":"#hs_cos_wrapper_module_1569856007055222>:nth-child(1)>:nth-child(2)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_3.1_3_1_A.G141","type":"error","typeCode":1,"message":"The heading structure is not logically nested. This h5 element should be an h3 to be properly nested.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/labels?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856007055222>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/name-plates?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856034269224>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/equipment-panel-overlays?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856037305225>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/membrane-switches?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856084644237>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/oem-bezel-re-branding?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856082608235>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/safety?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856080132233>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a class=\"expandMenu\"><i></i><i></i><i></i></a>","selector":"#hs_cos_wrapper_module_14725592865174>:nth-child(1)","code":"WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId","type":"error","typeCode":1,"message":"Anchor element found with no link content and no name and/or ID attribute.","runner":"htmlcs","recurrence":0}],"meta":{"errorCount":9,"warningCount":0,"noticeCount":0,"accessScore":100},"automateable":{"missingAltIndexs":[]}}%
```
{"documentTitle":"Drake Industries | Custom, Durable, High-Quality Labels, Asset Tags and Custom Server Bezels","pageUrl":"https://www.drake.com/","issues":[{"context":"<h5 class=\"normal\">\n<div class=\"span12 widget-span ...</h5>","selector":"body>:nth-child(2)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)>:nth-child(2)>:nth-child(1)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_3.1_3_1_A.G141","type":"error","typeCode":1,"message":"The heading structure is not logically nested. This h5 element should be an h2 to be properly nested.","runner":"htmlcs","recurrence":0},{"context":"<h5>Labels</h5>","selector":"#hs_cos_wrapper_module_1569856007055222>:nth-child(1)>:nth-child(2)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_3.1_3_1_A.G141","type":"error","typeCode":1,"message":"The heading structure is not logically nested. This h5 element should be an h3 to be properly nested.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/labels?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856007055222>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/name-plates?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856034269224>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/equipment-panel-overlays?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856037305225>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/membrane-switches?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856084644237>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/oem-bezel-re-branding?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856082608235>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a href=\"https://www.drake.com/safety?hsLang=en\">Learn more</a>","selector":"#hs_cos_wrapper_module_1569856080132233>:nth-child(1)>:nth-child(3)>:nth-child(1)","code":"WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail","type":"error","typeCode":1,"message":"This element has insufficient contrast at this conformance level. Expected a contrast ratio of at least 4.5:1, but text in this element has a contrast ratio of 3.44:1. Recommendation: change text colour to #00171d.","runner":"htmlcs","recurrence":0},{"context":"<a class=\"expandMenu\"><i></i><i></i><i></i></a>","selector":"#hs_cos_wrapper_module_14725592865174>:nth-child(1)","code":"WCAG2AA.Principle4.Guideline4_1.4_1_2.H91.A.EmptyNoId","type":"error","typeCode":1,"message":"Anchor element found with no link content and no name and/or ID attribute.","runner":"htmlcs","recurrence":0}],"meta":{"errorCount":9,"warningCount":0,"noticeCount":0,"accessScore":100},"automateable":{"missingAltIndexs":[]}}%
```
120 changes: 114 additions & 6 deletions kayle_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod puppeteer_script;
use serde::{Deserialize, Serialize};

#[derive(Debug, Subcommand, Serialize, Deserialize)]
/// The CLI commands to run.
enum Commands {
/// Upgrade kayle and the dependencies required.
Upgrade,
Expand All @@ -27,7 +28,7 @@ enum Commands {
#[derive(Debug, Default, Clone, PartialEq, ValueEnum, Serialize, Deserialize)]
enum AutomationLib {
#[default]
/// The puppeteer library. Defaults to this.
/// The puppeteer library. The Default.
Puppeteer,
/// The playwright library by microsoft
Playwright,
Expand Down Expand Up @@ -72,7 +73,7 @@ enum AccessibilityRunner {
}

impl AccessibilityRunner {
/// get the standard to string
/// get the runner to string
pub fn to_str(&self) -> &'static str {
match self {
AccessibilityRunner::Kayle => "kayle",
Expand All @@ -82,6 +83,56 @@ impl AccessibilityRunner {
}
}

/// Wait for events.
#[derive(Debug, Default, Clone, PartialEq, ValueEnum, Serialize, Deserialize)]
enum WaitFor {
#[default]
/// Waits till the window load event.
Load,
/// The dom loaded content first
DomcontentLoaded,
/// Wait for the commit event. Playwright only.
Commit,
/// Waits till there are no more network connections for at least 500 ms. Playwright only.
NetworkIdle,
/// Waits till there are no more network connections for at least 500 ms. Puppeteer only.
NetworkIdle1,
/// Waits till there are no more than 2 network connections for at least 500 ms. Puppeteer only.
NetworkIdle2,
}

impl WaitFor {
/// get the wait_for event to string
pub fn to_str(&self, puppeteer: bool) -> &'static str {
match self {
WaitFor::Load => "load",
WaitFor::DomcontentLoaded => "domcontentloaded",
WaitFor::Commit => if puppeteer { "networkidle2" } else { "commit" },
WaitFor::NetworkIdle => {
if puppeteer {
"networkidle1"
} else {
"networkidle"
}
}
WaitFor::NetworkIdle1 => {
if puppeteer {
"networkidle1"
} else {
"networkidle"
}
}
WaitFor::NetworkIdle2 => {
if puppeteer {
"networkidle2"
} else {
"networkidle"
}
}
}
}
}

/// Web Accessibility Auditing using the kayle engine
#[derive(Parser, Serialize, Deserialize, Debug)]
#[command(version, about, long_about = None)]
Expand All @@ -98,9 +149,25 @@ struct Args {
/// The accessibility runner to use htmlcs, axecore, or kayle.
#[arg(short, long)]
runners: Option<Vec<AccessibilityRunner>>,
/// WaitFor event for content to exist.
#[arg(short, long, value_enum)]
wait_for: Option<WaitFor>,
#[arg(long)]
/// Allow images to render, useful when setting clip option for bounding box.
allow_images: Option<bool>,
#[arg(long)]
/// Get the bounding box of an element.
clip: Option<bool>,
#[arg(long)]
/// The directory to store clip images.
clip_dir: Option<String>,
#[arg(long)]
/// Convert the clip to a base64 image.
clip_2_base64: Option<bool>,
#[arg(long, value_enum)]
/// The automation lib to use either puppeteer or playwright.
automation_lib: Option<AutomationLib>,
/// The commands for the CLI.
#[command(subcommand)]
command: Commands,
}
Expand Down Expand Up @@ -197,6 +264,40 @@ fn main() -> io::Result<()> {
.map(|r| r.to_str())
.collect::<Vec<&'static str>>()
.join(",");
let wait_for = if args.wait_for.is_some() {
args.wait_for
} else {
loaded_config.wait_for
}
.unwrap_or_default();

let allow_images = if args.allow_images.is_some() {
args.allow_images
} else {
loaded_config.allow_images
}
.unwrap_or_default();

let clip = if args.clip.is_some() {
args.clip
} else {
loaded_config.clip
}
.unwrap_or_default();

let clip_dir = if args.clip_dir.is_some() {
args.clip_dir
} else {
loaded_config.clip_dir
}
.unwrap_or_default();

let clip_2_base64 = if args.clip_2_base64.is_some() {
args.clip_2_base64
} else {
loaded_config.clip_2_base64
}
.unwrap_or_default();

let headless_script = if puppeteer {
puppeteer_script::SCRIPT_EXECUTION
Expand All @@ -208,12 +309,19 @@ fn main() -> io::Result<()> {
Command::new("node")
.args([
"-e",
headless_script,
u.to_str().unwrap(),
&accessibility_standard.to_str(),
headless_script,
u.to_str().unwrap(),
&accessibility_standard.to_str(),
if include_notices { "true"} else { "false" },
if include_warnings { "true"} else { "false" },
&runners])
&runners,
&wait_for.to_str(puppeteer),
if allow_images { "true"} else { "false" },
if clip { "true"} else { "false" },
&clip_dir,
if clip_2_base64 { "true"} else { "false" },

])
.status()
.expect(if puppeteer {
"Failed to execute node command - make sure node and puppeteer is installed."
Expand Down
Loading

0 comments on commit 082ae4b

Please sign in to comment.