cbrowser 6.5.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +63 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +988 -0
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +362 -2
- package/dist/cli.js.map +1 -1
- package/dist/types.d.ts +221 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -3
package/dist/cli.js
CHANGED
|
@@ -14,8 +14,8 @@ const daemon_js_1 = require("./daemon.js");
|
|
|
14
14
|
function showHelp() {
|
|
15
15
|
console.log(`
|
|
16
16
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
17
|
-
║ CBrowser CLI
|
|
18
|
-
║ AI-powered browser automation with
|
|
17
|
+
║ CBrowser CLI v7.1.0 ║
|
|
18
|
+
║ AI-powered browser automation with cross-browser visual testing ║
|
|
19
19
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
20
20
|
|
|
21
21
|
NAVIGATION
|
|
@@ -202,6 +202,62 @@ VISUAL REGRESSION (v2.5.0)
|
|
|
202
202
|
visual list List all saved baselines
|
|
203
203
|
visual delete <name> Delete a baseline
|
|
204
204
|
|
|
205
|
+
AI VISUAL REGRESSION (v7.0.0)
|
|
206
|
+
ai-visual capture <url> Capture AI-analyzed visual baseline
|
|
207
|
+
--name <name> Baseline name (required)
|
|
208
|
+
--selector <sel> Capture specific element instead of viewport
|
|
209
|
+
--device <device> Device emulation (iphone-15, pixel-7, etc.)
|
|
210
|
+
--width <n> Viewport width (default: 1920)
|
|
211
|
+
--height <n> Viewport height (default: 1080)
|
|
212
|
+
--wait <ms|selector> Wait before capture (ms or CSS selector)
|
|
213
|
+
Examples:
|
|
214
|
+
cbrowser ai-visual capture "https://example.com" --name homepage
|
|
215
|
+
cbrowser ai-visual capture "https://example.com/dashboard" --name dashboard --device iphone-15
|
|
216
|
+
|
|
217
|
+
ai-visual test <url> <baseline> Test current page against baseline using AI
|
|
218
|
+
--threshold <n> Similarity threshold 0-1 (default: 0.9)
|
|
219
|
+
--sensitivity <level> Detection sensitivity: low, medium, high (default: medium)
|
|
220
|
+
--html Generate HTML report
|
|
221
|
+
--output <file> Save JSON report to file
|
|
222
|
+
Examples:
|
|
223
|
+
cbrowser ai-visual test "https://staging.example.com" homepage
|
|
224
|
+
cbrowser ai-visual test "https://staging.example.com" homepage --sensitivity high
|
|
225
|
+
|
|
226
|
+
ai-visual suite <file.json> Run visual regression suite
|
|
227
|
+
--threshold <n> Global similarity threshold (default: 0.9)
|
|
228
|
+
--html Generate HTML report
|
|
229
|
+
--output <file> Save JSON report to file
|
|
230
|
+
|
|
231
|
+
ai-visual list List all AI visual baselines
|
|
232
|
+
ai-visual show <name> Show baseline details
|
|
233
|
+
ai-visual delete <name> Delete a baseline
|
|
234
|
+
|
|
235
|
+
CROSS-BROWSER VISUAL TESTING (v7.1.0)
|
|
236
|
+
cross-browser <url> Compare visual rendering across browsers
|
|
237
|
+
--browsers <list> Browsers to test: chromium,firefox,webkit (default: all)
|
|
238
|
+
--width <n> Viewport width (default: 1920)
|
|
239
|
+
--height <n> Viewport height (default: 1080)
|
|
240
|
+
--wait <ms> Wait before screenshot (ms)
|
|
241
|
+
--wait-for <selector> Wait for selector before screenshot
|
|
242
|
+
--sensitivity <level> Comparison sensitivity: low, medium, high
|
|
243
|
+
--html Generate HTML report
|
|
244
|
+
--output <file> Save JSON report to file
|
|
245
|
+
Examples:
|
|
246
|
+
cbrowser cross-browser "https://example.com"
|
|
247
|
+
cbrowser cross-browser "https://example.com" --browsers chromium,firefox
|
|
248
|
+
cbrowser cross-browser "https://example.com" --html --output report.html
|
|
249
|
+
|
|
250
|
+
cross-browser suite <file.json> Run cross-browser test suite
|
|
251
|
+
--html Generate HTML report
|
|
252
|
+
--output <file> Save JSON report to file
|
|
253
|
+
|
|
254
|
+
Suite file format:
|
|
255
|
+
{
|
|
256
|
+
"name": "My Site",
|
|
257
|
+
"urls": ["https://example.com", "https://example.com/about"],
|
|
258
|
+
"options": { "browsers": ["chromium", "firefox"] }
|
|
259
|
+
}
|
|
260
|
+
|
|
205
261
|
ACCESSIBILITY (v2.5.0)
|
|
206
262
|
a11y audit Run WCAG accessibility audit
|
|
207
263
|
--url <url> Navigate to URL first
|
|
@@ -1884,6 +1940,310 @@ async function main() {
|
|
|
1884
1940
|
break;
|
|
1885
1941
|
}
|
|
1886
1942
|
// =========================================================================
|
|
1943
|
+
// AI Visual Regression (v7.0.0)
|
|
1944
|
+
// =========================================================================
|
|
1945
|
+
case "ai-visual": {
|
|
1946
|
+
const subcommand = args[0];
|
|
1947
|
+
switch (subcommand) {
|
|
1948
|
+
case "capture": {
|
|
1949
|
+
const url = args[1];
|
|
1950
|
+
if (!url || !options.name) {
|
|
1951
|
+
console.error("Usage: cbrowser ai-visual capture <url> --name <name> [options]");
|
|
1952
|
+
console.error(" --selector <sel> Capture specific element");
|
|
1953
|
+
console.error(" --device <device> Device emulation");
|
|
1954
|
+
console.error(" --width <n> Viewport width");
|
|
1955
|
+
console.error(" --height <n> Viewport height");
|
|
1956
|
+
console.error(" --wait <ms|sel> Wait before capture");
|
|
1957
|
+
process.exit(1);
|
|
1958
|
+
}
|
|
1959
|
+
console.log(`📸 Capturing visual baseline: ${options.name}`);
|
|
1960
|
+
console.log(` URL: ${url}`);
|
|
1961
|
+
const baseline = await (0, browser_js_1.captureVisualBaseline)(url, options.name, {
|
|
1962
|
+
selector: options.selector,
|
|
1963
|
+
device: options.device,
|
|
1964
|
+
viewport: options.width || options.height ? {
|
|
1965
|
+
width: parseInt(options.width) || 1920,
|
|
1966
|
+
height: parseInt(options.height) || 1080,
|
|
1967
|
+
} : undefined,
|
|
1968
|
+
waitFor: options.wait ? (/^\d+$/.test(options.wait)
|
|
1969
|
+
? parseInt(options.wait)
|
|
1970
|
+
: options.wait) : undefined,
|
|
1971
|
+
});
|
|
1972
|
+
console.log(`\n✅ Baseline captured successfully!`);
|
|
1973
|
+
console.log(` ID: ${baseline.id}`);
|
|
1974
|
+
console.log(` Viewport: ${baseline.viewport.width}x${baseline.viewport.height}`);
|
|
1975
|
+
console.log(` Screenshot: ${baseline.screenshotPath}`);
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1978
|
+
case "test": {
|
|
1979
|
+
const url = args[1];
|
|
1980
|
+
const baselineName = args[2];
|
|
1981
|
+
if (!url || !baselineName) {
|
|
1982
|
+
console.error("Usage: cbrowser ai-visual test <url> <baseline-name> [options]");
|
|
1983
|
+
console.error(" --threshold <n> Similarity threshold 0-1 (default: 0.9)");
|
|
1984
|
+
console.error(" --sensitivity <l> low, medium, high (default: medium)");
|
|
1985
|
+
console.error(" --html Generate HTML report");
|
|
1986
|
+
console.error(" --output <file> Save JSON report");
|
|
1987
|
+
process.exit(1);
|
|
1988
|
+
}
|
|
1989
|
+
console.log(`\n🔍 Running AI visual regression test...`);
|
|
1990
|
+
console.log(` URL: ${url}`);
|
|
1991
|
+
console.log(` Baseline: ${baselineName}\n`);
|
|
1992
|
+
const result = await (0, browser_js_1.runVisualRegression)(url, baselineName, {
|
|
1993
|
+
threshold: options.threshold ? parseFloat(options.threshold) : 0.9,
|
|
1994
|
+
sensitivity: options.sensitivity || "medium",
|
|
1995
|
+
generateDiff: true,
|
|
1996
|
+
});
|
|
1997
|
+
// Print report
|
|
1998
|
+
console.log((0, browser_js_1.formatVisualRegressionReport)(result));
|
|
1999
|
+
// Save JSON output if requested
|
|
2000
|
+
if (options.output && !options.html) {
|
|
2001
|
+
const fs = await import("fs");
|
|
2002
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2003
|
+
console.log(`\n📄 JSON report saved to: ${options.output}`);
|
|
2004
|
+
}
|
|
2005
|
+
// Generate HTML report if requested
|
|
2006
|
+
if (options.html) {
|
|
2007
|
+
const fs = await import("fs");
|
|
2008
|
+
const suiteResult = {
|
|
2009
|
+
suite: {
|
|
2010
|
+
name: baselineName,
|
|
2011
|
+
pages: [{ name: baselineName, url, baselineName }],
|
|
2012
|
+
},
|
|
2013
|
+
results: [result],
|
|
2014
|
+
summary: {
|
|
2015
|
+
total: 1,
|
|
2016
|
+
passed: result.passed ? 1 : 0,
|
|
2017
|
+
failed: result.passed ? 0 : 1,
|
|
2018
|
+
warnings: result.analysis.overallStatus === "warning" ? 1 : 0,
|
|
2019
|
+
},
|
|
2020
|
+
duration: result.duration,
|
|
2021
|
+
timestamp: new Date().toISOString(),
|
|
2022
|
+
};
|
|
2023
|
+
const htmlReport = (0, browser_js_1.generateVisualRegressionHtmlReport)(suiteResult);
|
|
2024
|
+
const outputPath = options.output || `visual-regression-${baselineName}-${Date.now()}.html`;
|
|
2025
|
+
fs.writeFileSync(outputPath, htmlReport);
|
|
2026
|
+
console.log(`\n📄 HTML report saved to: ${outputPath}`);
|
|
2027
|
+
}
|
|
2028
|
+
if (!result.passed) {
|
|
2029
|
+
process.exit(1);
|
|
2030
|
+
}
|
|
2031
|
+
break;
|
|
2032
|
+
}
|
|
2033
|
+
case "suite": {
|
|
2034
|
+
const suiteFile = args[1];
|
|
2035
|
+
if (!suiteFile) {
|
|
2036
|
+
console.error("Usage: cbrowser ai-visual suite <file.json> [options]");
|
|
2037
|
+
console.error(" --threshold <n> Global similarity threshold");
|
|
2038
|
+
console.error(" --html Generate HTML report");
|
|
2039
|
+
console.error(" --output <file> Save report to file");
|
|
2040
|
+
process.exit(1);
|
|
2041
|
+
}
|
|
2042
|
+
const fs = await import("fs");
|
|
2043
|
+
if (!fs.existsSync(suiteFile)) {
|
|
2044
|
+
console.error(`Suite file not found: ${suiteFile}`);
|
|
2045
|
+
process.exit(1);
|
|
2046
|
+
}
|
|
2047
|
+
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2048
|
+
console.log(`\n🔍 Running visual regression suite: ${suite.name}`);
|
|
2049
|
+
const result = await (0, browser_js_1.runVisualRegressionSuite)(suite, {
|
|
2050
|
+
threshold: options.threshold ? parseFloat(options.threshold) : 0.9,
|
|
2051
|
+
});
|
|
2052
|
+
// Save outputs
|
|
2053
|
+
if (options.output && !options.html) {
|
|
2054
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2055
|
+
console.log(`📄 JSON report saved to: ${options.output}`);
|
|
2056
|
+
}
|
|
2057
|
+
if (options.html) {
|
|
2058
|
+
const htmlReport = (0, browser_js_1.generateVisualRegressionHtmlReport)(result);
|
|
2059
|
+
const outputPath = options.output || `visual-suite-${Date.now()}.html`;
|
|
2060
|
+
fs.writeFileSync(outputPath, htmlReport);
|
|
2061
|
+
console.log(`📄 HTML report saved to: ${outputPath}`);
|
|
2062
|
+
}
|
|
2063
|
+
if (result.summary.failed > 0) {
|
|
2064
|
+
process.exit(1);
|
|
2065
|
+
}
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
case "list": {
|
|
2069
|
+
const baselines = (0, browser_js_1.listVisualBaselines)();
|
|
2070
|
+
if (baselines.length === 0) {
|
|
2071
|
+
console.log("\nNo AI visual baselines saved.\n");
|
|
2072
|
+
console.log("Capture one with:");
|
|
2073
|
+
console.log(" cbrowser ai-visual capture <url> --name <name>\n");
|
|
2074
|
+
}
|
|
2075
|
+
else {
|
|
2076
|
+
console.log("\n📸 AI Visual Baselines:\n");
|
|
2077
|
+
console.log("┌──────────────────────┬─────────────────────────────────┬────────────────────┬──────────────┐");
|
|
2078
|
+
console.log("│ Name │ URL │ Viewport │ Created │");
|
|
2079
|
+
console.log("├──────────────────────┼─────────────────────────────────┼────────────────────┼──────────────┤");
|
|
2080
|
+
for (const b of baselines) {
|
|
2081
|
+
const name = b.name.substring(0, 20).padEnd(20);
|
|
2082
|
+
const url = b.url.substring(0, 31).padEnd(31);
|
|
2083
|
+
const viewport = `${b.viewport.width}x${b.viewport.height}`.padEnd(18);
|
|
2084
|
+
const created = new Date(b.timestamp).toLocaleDateString().padEnd(12);
|
|
2085
|
+
console.log(`│ ${name} │ ${url} │ ${viewport} │ ${created} │`);
|
|
2086
|
+
}
|
|
2087
|
+
console.log("└──────────────────────┴─────────────────────────────────┴────────────────────┴──────────────┘\n");
|
|
2088
|
+
}
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
case "show": {
|
|
2092
|
+
const name = args[1];
|
|
2093
|
+
if (!name) {
|
|
2094
|
+
console.error("Usage: cbrowser ai-visual show <name>");
|
|
2095
|
+
process.exit(1);
|
|
2096
|
+
}
|
|
2097
|
+
const baseline = (0, browser_js_1.getVisualBaseline)(name);
|
|
2098
|
+
if (!baseline) {
|
|
2099
|
+
console.error(`Baseline not found: ${name}`);
|
|
2100
|
+
process.exit(1);
|
|
2101
|
+
}
|
|
2102
|
+
console.log("\n📸 AI Visual Baseline Details:\n");
|
|
2103
|
+
console.log(` Name: ${baseline.name}`);
|
|
2104
|
+
console.log(` ID: ${baseline.id}`);
|
|
2105
|
+
console.log(` URL: ${baseline.url}`);
|
|
2106
|
+
console.log(` Viewport: ${baseline.viewport.width}x${baseline.viewport.height}`);
|
|
2107
|
+
if (baseline.device) {
|
|
2108
|
+
console.log(` Device: ${baseline.device}`);
|
|
2109
|
+
}
|
|
2110
|
+
if (baseline.selector) {
|
|
2111
|
+
console.log(` Selector: ${baseline.selector}`);
|
|
2112
|
+
}
|
|
2113
|
+
console.log(` Created: ${baseline.timestamp}`);
|
|
2114
|
+
console.log(` Screenshot: ${baseline.screenshotPath}`);
|
|
2115
|
+
console.log("");
|
|
2116
|
+
break;
|
|
2117
|
+
}
|
|
2118
|
+
case "delete": {
|
|
2119
|
+
const name = args[1];
|
|
2120
|
+
if (!name) {
|
|
2121
|
+
console.error("Usage: cbrowser ai-visual delete <name>");
|
|
2122
|
+
process.exit(1);
|
|
2123
|
+
}
|
|
2124
|
+
if ((0, browser_js_1.deleteVisualBaseline)(name)) {
|
|
2125
|
+
console.log(`✅ Baseline deleted: ${name}`);
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
console.error(`Baseline not found: ${name}`);
|
|
2129
|
+
process.exit(1);
|
|
2130
|
+
}
|
|
2131
|
+
break;
|
|
2132
|
+
}
|
|
2133
|
+
default:
|
|
2134
|
+
console.error("Usage: cbrowser ai-visual [capture|test|suite|list|show|delete]");
|
|
2135
|
+
console.error(" cbrowser ai-visual capture <url> --name <name>");
|
|
2136
|
+
console.error(" cbrowser ai-visual test <url> <baseline>");
|
|
2137
|
+
console.error(" cbrowser ai-visual suite <file.json>");
|
|
2138
|
+
console.error(" cbrowser ai-visual list");
|
|
2139
|
+
console.error(" cbrowser ai-visual show <name>");
|
|
2140
|
+
console.error(" cbrowser ai-visual delete <name>");
|
|
2141
|
+
}
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
// =========================================================================
|
|
2145
|
+
// Cross-Browser Visual Testing (v7.1.0)
|
|
2146
|
+
// =========================================================================
|
|
2147
|
+
case "cross-browser": {
|
|
2148
|
+
const subcommand = args[0];
|
|
2149
|
+
if (subcommand === "suite") {
|
|
2150
|
+
// Cross-browser suite
|
|
2151
|
+
const suiteFile = args[1];
|
|
2152
|
+
if (!suiteFile) {
|
|
2153
|
+
console.error("Usage: cbrowser cross-browser suite <file.json> [options]");
|
|
2154
|
+
console.error(" --html Generate HTML report");
|
|
2155
|
+
console.error(" --output <file> Save report to file");
|
|
2156
|
+
process.exit(1);
|
|
2157
|
+
}
|
|
2158
|
+
const fs = await import("fs");
|
|
2159
|
+
if (!fs.existsSync(suiteFile)) {
|
|
2160
|
+
console.error(`Suite file not found: ${suiteFile}`);
|
|
2161
|
+
process.exit(1);
|
|
2162
|
+
}
|
|
2163
|
+
const suite = JSON.parse(fs.readFileSync(suiteFile, "utf-8"));
|
|
2164
|
+
const result = await (0, browser_js_1.runCrossBrowserSuite)(suite);
|
|
2165
|
+
// Save outputs
|
|
2166
|
+
if (options.output && !options.html) {
|
|
2167
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2168
|
+
console.log(`\n📄 JSON report saved to: ${options.output}`);
|
|
2169
|
+
}
|
|
2170
|
+
if (options.html) {
|
|
2171
|
+
const htmlReport = (0, browser_js_1.generateCrossBrowserHtmlReport)(result);
|
|
2172
|
+
const outputPath = options.output || `cross-browser-${Date.now()}.html`;
|
|
2173
|
+
fs.writeFileSync(outputPath, htmlReport);
|
|
2174
|
+
console.log(`\n📄 HTML report saved to: ${outputPath}`);
|
|
2175
|
+
}
|
|
2176
|
+
// Summary
|
|
2177
|
+
console.log(`\n${"═".repeat(60)}`);
|
|
2178
|
+
console.log(` Results: ${result.summary.consistent} consistent, ${result.summary.minorDifferences} minor, ${result.summary.majorDifferences} major`);
|
|
2179
|
+
console.log(`${"═".repeat(60)}\n`);
|
|
2180
|
+
if (result.summary.majorDifferences > 0) {
|
|
2181
|
+
process.exit(1);
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
else {
|
|
2185
|
+
// Single URL test
|
|
2186
|
+
const url = subcommand; // First arg is the URL
|
|
2187
|
+
if (!url || url.startsWith("--")) {
|
|
2188
|
+
console.error("Usage: cbrowser cross-browser <url> [options]");
|
|
2189
|
+
console.error(" cbrowser cross-browser suite <file.json>");
|
|
2190
|
+
console.error("\nOptions:");
|
|
2191
|
+
console.error(" --browsers <list> chromium,firefox,webkit (default: all)");
|
|
2192
|
+
console.error(" --width <n> Viewport width (default: 1920)");
|
|
2193
|
+
console.error(" --height <n> Viewport height (default: 1080)");
|
|
2194
|
+
console.error(" --wait <ms> Wait before screenshot");
|
|
2195
|
+
console.error(" --wait-for <sel> Wait for selector");
|
|
2196
|
+
console.error(" --sensitivity <l> low, medium, high");
|
|
2197
|
+
console.error(" --html Generate HTML report");
|
|
2198
|
+
console.error(" --output <file> Save report");
|
|
2199
|
+
process.exit(1);
|
|
2200
|
+
}
|
|
2201
|
+
const browsers = options.browsers
|
|
2202
|
+
? options.browsers.split(",")
|
|
2203
|
+
: undefined;
|
|
2204
|
+
const result = await (0, browser_js_1.runCrossBrowserTest)(url, {
|
|
2205
|
+
browsers,
|
|
2206
|
+
viewport: options.width || options.height ? {
|
|
2207
|
+
width: parseInt(options.width) || 1920,
|
|
2208
|
+
height: parseInt(options.height) || 1080,
|
|
2209
|
+
} : undefined,
|
|
2210
|
+
waitBeforeCapture: options.wait ? parseInt(options.wait) : undefined,
|
|
2211
|
+
waitForSelector: options["wait-for"],
|
|
2212
|
+
sensitivity: options.sensitivity,
|
|
2213
|
+
});
|
|
2214
|
+
// Print report
|
|
2215
|
+
console.log("\n" + (0, browser_js_1.formatCrossBrowserReport)(result));
|
|
2216
|
+
// Save outputs
|
|
2217
|
+
const fs = await import("fs");
|
|
2218
|
+
if (options.output && !options.html) {
|
|
2219
|
+
fs.writeFileSync(options.output, JSON.stringify(result, null, 2));
|
|
2220
|
+
console.log(`\n📄 JSON report saved to: ${options.output}`);
|
|
2221
|
+
}
|
|
2222
|
+
if (options.html) {
|
|
2223
|
+
const suiteResult = {
|
|
2224
|
+
suite: { name: "Single URL Test", urls: [url] },
|
|
2225
|
+
results: [result],
|
|
2226
|
+
summary: {
|
|
2227
|
+
total: 1,
|
|
2228
|
+
consistent: result.overallStatus === "consistent" ? 1 : 0,
|
|
2229
|
+
minorDifferences: result.overallStatus === "minor_differences" ? 1 : 0,
|
|
2230
|
+
majorDifferences: result.overallStatus === "major_differences" ? 1 : 0,
|
|
2231
|
+
},
|
|
2232
|
+
duration: result.duration,
|
|
2233
|
+
timestamp: result.timestamp,
|
|
2234
|
+
};
|
|
2235
|
+
const htmlReport = (0, browser_js_1.generateCrossBrowserHtmlReport)(suiteResult);
|
|
2236
|
+
const outputPath = options.output || `cross-browser-${Date.now()}.html`;
|
|
2237
|
+
fs.writeFileSync(outputPath, htmlReport);
|
|
2238
|
+
console.log(`\n📄 HTML report saved to: ${outputPath}`);
|
|
2239
|
+
}
|
|
2240
|
+
if (result.overallStatus === "major_differences") {
|
|
2241
|
+
process.exit(1);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
break;
|
|
2245
|
+
}
|
|
2246
|
+
// =========================================================================
|
|
1887
2247
|
// Accessibility (Tier 2)
|
|
1888
2248
|
// =========================================================================
|
|
1889
2249
|
case "a11y": {
|