RGAA中的无障碍测试标准并不是扁平化的一串编号,它按照主题、标准、测试步骤、适用技术等多个维度组织。例如主题1主要涉及图片替代文本,主题3负责颜色对比,主题9处理页面结构与语义。使用TypeScript为这些标准封装类型,能够在编译阶段就拒绝无效的标准引用,并让测试结果的流转具备更强的可预期性。

一、从主题到标准:建立RGAA类型体系的基础
RGAA的标准编号通常采用主题编号加序号的形式,例如主题1中的1.1、1.2,主题3中的3.1、3.2。若在TypeScript中只把标准编号声明为string,编译器无法区分合法编号与任意文本。因此第一步是定义主题和标准编号的字面量联合类型,把指南中需要用到的部分固定下来。这样测试用例在引用标准时若有拼写错误,会在编辑器和编译阶段直接报错。
主题编号可以声明为以下类型:
type RgaaTheme = '1' | '3' | '9';
type ResultStatus = 'passed' | 'failed' | 'not-applicable' | 'not-tested';
type CriterionId = '1.1' | '1.2' | '3.1' | '3.2' | '9.1' | '9.2';
interface RgaaCriterion {
id: CriterionId;
theme: RgaaTheme;
result: ResultStatus;
description: string;
}
这里没有使用string作为标准编号类型,而是用少量示例编号构成联合类型。实际项目中如果标准数量很多,可以先生成一份常量数组,再用typeof提取类型。这种方式的好处是数据源和类型保持同步,避免维护两套清单。结果状态被限定为四种取值,避免出现passed之外的大小写变体或缩写。
除了编号和结果外,无障碍测试还经常涉及适用技术,例如HTML、CSS、JavaScript以及ARIA。把技术维度也定义为字面量联合类型,后续可以根据不同技术拆分测试参数,形成更具针对性的数据结构。
二、判别联合让不同测试拥有不同参数
RGAA中不同标准的测试方式差异很大。颜色对比测试需要前景色和背景色,而结构语义测试可能只需要一个CSS选择器。如果用同一个接口容纳所有可选字段,就会出现大量可选属性,调用方很难知道某个字段在何时有效。判别联合可以通过kind字段区分测试类别,并为每个类别定义专属字段。
interface BaseRgaaTest {
id: CriterionId;
result: ResultStatus;
}
interface HtmlStructureTest extends BaseRgaaTest {
kind: 'html-structure';
selector: string;
}
interface ColorContrastTest extends BaseRgaaTest {
kind: 'color-contrast';
foreground: string;
background: string;
ratio: number;
}
type RgaaTest = HtmlStructureTest | ColorContrastTest;
在函数中处理RgaaTest时,TypeScript会根据kind字段自动收窄类型。例如当判断为color-contrast时,可以直接访问foreground和background,而不会提示这些属性不存在于HtmlStructureTest。switch语句同样能够获得完整的联合类型检查,如果缺少某个分支,可以启用noImplicitReturns或使用never类型做兜底。
这种设计也方便后续扩展。当需要加入视频字幕或键盘焦点测试时,只要新增一个接口并纳入联合类型,相关处理函数就会在编译期提示新增的kind分支,避免遗漏逻辑。相比把所有字段堆在一起的可选属性方案,判别联合在数据准确性和可维护性上更优。
三、类型守卫用于解析外部报告数据
静态类型只在编译期起作用。无障碍审计结果常常来自JSON文件、浏览器扩展导出的报告或第三方API,这些数据在运行时本质上是unknown。直接使用类型断言虽然能通过编译,但无法发现数据中实际存在的不合法字段。类型守卫可以把未知数据安全地收窄为已定义类型。
const resultStatuses: readonly ResultStatus[] = ['passed', 'failed', 'not-applicable', 'not-tested'];
function isResultStatus(value: unknown): value is ResultStatus {
return typeof value === 'string' && (resultStatuses as readonly string[]).includes(value);
}
const criterionIds: readonly CriterionId[] = ['1.1', '1.2', '3.1', '3.2', '9.1', '9.2'];
function isCriterionId(value: unknown): value is CriterionId {
return typeof value === 'string' && (criterionIds as readonly string[]).includes(value);
}
可以在一个解析函数中按照对象结构逐层校验。先检查值是否为对象,再验证id、result等字段。对于不满足条件的报告数据,不直接抛错中断整个流程,而是返回带错误信息的解析结果。这样测试运行器可以汇总所有无效记录,而不是在第一条异常数据处停止。
类型守卫还适合处理来自浏览器扩展的数据。很多无障碍扩展会在页面中注入测试结果,数据层级可能与后端报告不同。通过为每种来源编写独立的守卫函数,可以让内部业务逻辑只依赖统一的RgaaTest类型,无需关心数据原始格式的差异。
四、将类型集成到Vitest测试用例
当RgaaTest类型定义好后,可以把它作为测试夹具和用例的约束。Vitest运行TypeScript测试时会进行类型检查,无效的标准编号或结果状态会直接导致测试文件无法通过编译。比如在测试用例中定义一个报告数组,如果其中一个元素的result写成pass,编译器就会提示该值不属于ResultStatus。
import { describe, it, expect } from 'vitest';
const sampleTests: RgaaTest[] = [
{
id: '1.1',
kind: 'html-structure',
result: 'passed',
selector: 'img'
},
{
id: '3.1',
kind: 'color-contrast',
result: 'failed',
foreground: '#333333',
background: '#ffffff',
ratio: 4.8
}
];
describe('RGAA report summary', function () {
it('should count failed tests', function () {
const failed = sampleTests.filter(function (test) {
return test.result === 'failed';
});
expect(failed.length).toBe(1);
});
});
上面的代码中,sampleTests使用了RgaaTest类型,任何字段错误都会被TypeScript捕获。除了单测之外,汇总统计函数也可以受益于完整的联合类型。统计各类结果数量时,使用映射类型或switch语句遍历所有状态,可以让报告生成逻辑覆盖全部情况,不会因为新增状态而静默漏算。
实际项目中,可以把类型定义单独放在一个包或模块中,测试规则、报告解析、审计UI都从该模块导入。这样做不仅让标准编号和状态语义保持一致,也为后续RGAA版本更新提供了清晰的修改入口。当指南新增标准时,只需要更新常量数组和相应类型,编译器会立即指出所有需要适配的位置。
通过字面量类型、判别联合和类型守卫,TypeScript可以为RGAA测试标准提供接近指南结构的静态模型。这样在编写自动化无障碍测试时,数据约束从运行时提前到编译期,审计脚本的可靠性会明显提升。
TypeScriptRGAA无障碍测试修改时间:2026-08-22 16:46:07