巧用浏览器控制台进行无障碍测试:实用脚本集锦

浏览器控制台常用脚本(无障碍测试)

工作中,其实我们只需几行简单的 JavaScript 代码,就可以在任何网页上高亮显示标题、检查图片替代文本、查看Landmarks分布?

浏览器控制台不仅是开发者的调试工具,也是无障碍测试的利器。通过运行简单的脚本,我们可以直观地看到网页的结构和无障碍特性,而无需修改源代码或安装复杂的插件。

什么是控制台?

控制台是现代浏览器内置的工具,它可以:

  • 运行临时的 JavaScript 代码。

  • 快速检查页面信息。

  • 在不改变源码的情况下测试想法。

在控制台中运行的代码仅在当前会话有效,刷新页面即可恢复原样。

如何打开控制台

  • Mac 系统:

    • Chrome: Option + Command + J

    • Firefox: Option + Command + K

    • Safari: 先在“设置 → 高级”中启用“在菜单栏中显示开发菜单”,然后按 Option + Command + C

  • Windows 系统:

    • Chrome / Edge / Firefox: Ctrl + Shift + J (或 K)

快速上手示例

在进入复杂脚本前,你可以先尝试这两个简单的命令:

1. 列出页面所有链接:

1
document.querySelectorAll("a");

2. 高亮页面所有链接:

1
document.querySelectorAll("a").forEach(a => a.style.outline = "2px solid red");

14 个进阶无障碍测试脚本

以下脚本均采用相同的逻辑:在页面上方创建一个透明层(Overlay),并在对应元素上添加边框和悬浮标签(Badge)。

使用方法:

  1. 打开浏览器控制台。

  2. 复制并粘贴下方脚本。

  3. ENTER 键执行。

  4. 刷新页面即可清除所有高亮标记。

1. 显示所有标题 (Headings)

此脚本按级别(H1-H6)用不同颜色标注标题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

const colours = { H1: "red", H2: "green", H3: "blue", H4: "purple", H5: "deepPink", H6: "black" };

document.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach(h => {
    const tag = h.tagName.toUpperCase();
    const colour = colours[tag] || "#147580";
    h.style.outline = `3px solid ${colour}`;
    const rect = h.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = tag;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX - 20}px; top:${rect.top + window.scrollY - 30}px; background:${colour}; color:white; padding:5px 8px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});

2. 显示所有按钮及其可访问名称 (Buttons)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

function getAccessibleName(el) {
    const ariaLabel = el.getAttribute("aria-label");
    if (ariaLabel && ariaLabel.trim()) return ariaLabel.trim();
    const labelledby = el.getAttribute("aria-labelledby");
    if (labelledby) {
        return labelledby.split(/\s+/).map(id => document.getElementById(id)?.textContent.trim()).filter(Boolean).join(" ");
    }
    if (el.tagName === "INPUT") return el.value.trim();
    return el.textContent.trim() || "(no name)";
}

const buttonSelector = 'button, [role="button"], input[type="button"], input[type="submit"], input[type="reset"]';
document.querySelectorAll(buttonSelector).forEach(btn => {
    const name = getAccessibleName(btn);
    btn.style.outline = "3px solid #ca6510";
    const rect = btn.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = `Button: ${name}`;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 28}px; background:#ca6510; color:white; padding:5px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});

3. 检查图片替代文本 (Image Alt Text)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

document.querySelectorAll("img").forEach(img => {
    const alt = img.getAttribute("alt");
    const altText = (alt !== null) ? (alt.trim() || "(empty alt)") : "(missing alt)";
    img.style.outline = "2px solid #b23a48";
    const rect = img.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = altText;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 30}px; background:#b23a48; color:white; padding:5px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

function getLinkName(el) {
    const ariaLabel = el.getAttribute("aria-label");
    if (ariaLabel) return ariaLabel.trim();
    const text = el.textContent.trim();
    if (text) return text;
    const img = el.querySelector("img");
    if (img) return img.getAttribute("alt") || "(img no alt)";
    return "(no name)";
}

document.querySelectorAll("a").forEach(a => {
    const name = getLinkName(a);
    a.style.outline = "2px solid #6a1b9a";
    const rect = a.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = name;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 28}px; background:#6a1b9a; color:white; padding:4px 6px; border-radius:4px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});

5. 显示列表和列表项 (Lists & Items)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

function draw(el, label, color) {
    el.style.outline = `3px solid ${color}`;
    const rect = el.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = label;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 28}px; background:${color}; color:white; padding:5px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
}

document.querySelectorAll("ul").forEach(el => draw(el, "UL", "#006d77"));
document.querySelectorAll("ol").forEach(el => draw(el, "OL", "#005f8f"));
document.querySelectorAll("li").forEach(el => draw(el, "LI", "#8a5a44"));

6. 显示表格标题和表头 (Tables)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

document.querySelectorAll("table").forEach(table => {
    table.style.outline = "3px solid #d35400";
    const caption = table.querySelector("caption")?.textContent.trim() || "(no caption)";
    const rect = table.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = `Table: ${caption}`;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 30}px; background:#d35400; color:white; padding:4px 6px; border-radius:4px; font-size:14px; z-index:999999;`;
    overlay.appendChild(badge);
});

document.querySelectorAll("th").forEach(th => {
    th.style.outline = "2px dashed #8e44ad";
});

7. 显示地标角色 (Landmarks)

地标区域(如 header, nav, main)对屏幕阅读器用户至关重要。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

const roleColors = { banner: "#006d77", navigation: "#005f8f", main: "#4a148c", contentinfo: "#8a5a44", region: "#7f3d9c", complementary: "#b83b5e", search: "#d35400" };

function getRole(el) {
    const role = el.getAttribute("role");
    if (role) return role;
    const tag = el.tagName.toLowerCase();
    if (tag === "header") return "banner";
    if (tag === "nav") return "navigation";
    if (tag === "main") return "main";
    if (tag === "footer") return "contentinfo";
    if (tag === "aside") return "complementary";
    return null;
}

document.querySelectorAll('header, nav, main, footer, aside, section, article, [role]').forEach(el => {
    const role = getRole(el);
    if (!roleColors[role]) return;
    el.style.outline = `3px solid ${roleColors[role]}`;
    const rect = el.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = role;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY}px; background:${roleColors[role]}; color:white; padding:5px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});

8. 显示表单标签名称 (Form Labels)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const overlay = document.createElement("div");
overlay.style.cssText = "position:absolute; top:0; left:0; width:100%; pointer-events:none; z-index:999999;";
document.body.appendChild(overlay);

function getLabel(el) {
    if (el.id) {
        const label = document.querySelector(`label[for="${el.id}"]`);
        if (label) return label.textContent.trim();
    }
    return el.getAttribute("aria-label") || "(no label)";
}

document.querySelectorAll('input, select, textarea').forEach(el => {
    const name = getLabel(el);
    el.style.outline = "2px solid #0a558c";
    const rect = el.getBoundingClientRect();
    const badge = document.createElement("div");
    badge.textContent = name;
    badge.style.cssText = `position:absolute; left:${rect.left + window.scrollX}px; top:${rect.top + window.scrollY - 30}px; background:#0a558c; color:white; padding:5px; border-radius:5px; font-size:16px; z-index:999999;`;
    overlay.appendChild(badge);
});

9. 显示表单字段描述 (aria-describedby)

1
2
3
4
5
6
document.querySelectorAll("[aria-describedby]").forEach(el => {
    const ids = el.getAttribute("aria-describedby").split(/\s+/);
    const description = ids.map(id => document.getElementById(id)?.textContent.trim()).filter(Boolean).join(" ");
    el.style.outline = "3px solid #0d6efd";
    console.log("Description for", el, ":", description);
});

10. 显示字段集和标题 (Fieldsets & Legends)

1
2
3
4
5
document.querySelectorAll("fieldset").forEach(fs => {
    const legend = fs.querySelector("legend")?.textContent.trim() || "(no legend)";
    fs.style.outline = "3px solid #1e8449";
    console.log("Fieldset:", legend);
});

11. 实时显示可聚焦元素 (Focus Tracking)

运行此脚本后,按 TAB 键切换焦点,控制台会实时打印当前获得焦点的元素。

1
2
3
document.addEventListener('focusin', () => {
    console.log('Focused element:', document.activeElement);
});

12. 显示 aria-owns 关系

1
2
3
4
document.querySelectorAll("[aria-owns]").forEach(el => {
    el.style.outline = "3px solid #c2185b";
    console.log("Element owns:", el.getAttribute("aria-owns"));
});

13. 显示 aria-controls 关系

1
2
3
4
document.querySelectorAll("[aria-controls]").forEach(el => {
    el.style.outline = "3px solid #00796b";
    console.log("Element controls:", el.getAttribute("aria-controls"));
});

14. 显示聚焦顺序 (Focus Order Tracker)

运行后,每当你按 TAB 键,页面上会显示一个带数字的标签,标注你的操作顺序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let focusStep = 0;
const badge = document.createElement("div");
badge.style.cssText = "position:absolute; background:#111; color:white; padding:5px 8px; border-radius:6px; font-size:16px; z-index:999999; pointer-events:none;";
document.body.appendChild(badge);

document.addEventListener('focusin', (e) => {
    const el = e.target;
    if (el === document.body) return;
    focusStep++;
    el.style.outline = "3px solid #111";
    const rect = el.getBoundingClientRect();
    badge.textContent = `${focusStep}. ${el.tagName.toLowerCase()}`;
    badge.style.left = `${rect.left + window.scrollX}px`;
    badge.style.top = `${rect.top + window.scrollY - 30}px`;
});

总结

使用控制台进行测试的优势在于即时性灵活性。你不需要安装任何扩展程序,只需一段脚本,就能以“机器”的视角重新审视你的网页。希望这些脚本能帮助你构建更具包容性的 Web 环境!

使用 Hugo 构建
主题 StackJimmy 设计