Accessibility and Performance Budgets: Making Streaming Interfaces Work for Everyone
Streaming output is a disaster for screen readers, and pointing a live region at the streaming element is both the most common and the most wrong fix. Solve it with sentence-level announcement, set a performance budget for the whole workbench, and package seven days of work into a portfolio project.
Today's Goals
- Explain why pointing a live region at a streaming element fails, and implement sentence-level announcement instead
- Set and measure a performance budget, and say which metrics matter most for agent interfaces
- Complete keyboard access and focus management so the approval flow works without a mouse
The last day, covering two things we have been deferring. When you finish, scroll back up and tick the three goals off.
Plain-Language Walkthrough
Captions for those who cannot hear: not by retyping the sentence every word
Conferences usually provide captions alongside interpretation, for deaf and hard-of-hearing attendees.
Imagine a captioner who works like this: every time the interpreter says a new word, they retype the entire sentence from the beginning. What happens to the people reading? They can never finish a sentence — halfway through, it is wiped and restarted.
That is precisely what a streaming interface does to a screen reader if you take the most natural approach: adding aria-live to the element rendering the stream.
The core conclusion of today is that the most natural approach is wrong.
The two live-region traps
Screen readers notice content changes through aria-live. Putting it on the streaming message element seems obvious. But there are two configurations, and neither works:
| Configuration | Result |
|---|---|
aria-atomic="true" | Every delta re-announces the whole passage; the user hears endless restarts |
aria-atomic="false" | High-frequency DOM changes are skipped entirely; the user hears nothing |
The first is our captioner. The second is subtler: screen readers throttle internally, and dozens of changes per second exceed their pacing, so they give up.
Worse, NVDA, JAWS, and VoiceOver each handle high-frequency changes differently. "I tried it on my machine and it read fine" is especially unreliable here — you verified one third of the field.
The fix: silence while streaming, announce sentence by sentence
The idea is to decouple visual from auditory presentation:
- Visually, text keeps appearing character by character (sighted users need that feedback)
- Audibly, nothing is announced while streaming; once a sentence is complete, that sentence goes to the live region
function flush(fullText: string, force: boolean) {
let rest = fullText.slice(consumed) // only the part not yet announced
while (rest.length > 0) {
const match = /[。!?;.!?;]\s*/.exec(rest)
if (match) {
const end = match.index + match[0].length
emit(rest.slice(0, end).trim()) // push one complete sentence
consumed += end
rest = rest.slice(end)
continue
}
// No terminal punctuation: force a chunk when long or finishing, else wait
if (force || rest.length >= maxChars) {
emit(rest.trim())
consumed += rest.length
}
return
}
}Two details:
consumed tracks what has been announced, so only new text is pushed, never a repeat. That is the basis of "no restating".
Long text without punctuation needs a fallback. Otherwise an unpunctuated passage never announces at all and the user simply waits.
The contrast is stark: a three-sentence passage in this course's lab produces 3 announcements with the correct implementation, and 37 if it degrades to announcing the full text each time, each one starting over.
The live region element has three traps of its own
Correct chunking is not enough; the element carrying the announcements has three easy mistakes.
One, visual hiding cannot use display: none.
/* Wrong: screen readers ignore it too, defeating the whole purpose */
.hidden {
display: none;
}
/* Right: hidden visually, still available to assistive tech. The standard pattern. */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}visibility: hidden behaves the same way and is equally unusable.
Two, it must be empty on page load. A region that appears already containing content is not announced — the spec requires the region to exist first and the content to change afterwards.
Three, use polite, not assertive. assertive interrupts whatever the user is currently hearing. An agent's reply is information, not an alarm, and interrupting is rude. Reserve assertive for errors and approvals that demand an immediate decision.
Keyboard access: can someone approve an action without a mouse?
Agent interfaces have one especially critical keyboard scenario: approval.
If a user can only use a keyboard — through visual impairment, motor impairment, or simple preference — and your approval buttons cannot be reached by Tab, then they cannot authorize or decline anything. That is not an inconvenience, it is functional exclusion.
Concrete requirements:
Focus must be visible. Many projects strip outline for aesthetics, leaving keyboard users completely lost. :focus-visible shows it only during keyboard interaction and not on mouse clicks, which satisfies both.
Provide a skip link. Make the first focusable element a "skip to main content" link so keyboard users need not traverse the entire navigation every time. It hides off-screen until focused.
Be restrained with focus management. Should focus move to a new message when it arrives? No. The user may be typing, and stealing focus is hostile. Use the live region to inform rather than focus to compel.
There is one case that warrants moving focus: opening a modal dialog, since the user's other interactions are already blocked.
Two easily missed accessibility requirements
Beyond announcement and keyboard access, two more matter especially in streaming interfaces.
Respect the reduced-motion preference. Users who enable it are sensitive to animation, sometimes to the point of vertigo or migraine. Streaming interfaces move a lot by nature: blinking cursors, smooth scrolling, content continuously appearing.
@media (prefers-reduced-motion: reduce) {
.cursor {
animation: none;
}
* {
scroll-behavior: auto !important;
}
}Note this preference does not mean disabling streaming itself — text appearing progressively is functionality, not decoration. What stops is purely decorative animation.
State must not be conveyed by color alone. If a tool card's success and failure differ only in green versus red, users with color vision deficiency cannot tell them apart. This course's cards carry text labels ("complete", "failed", "rejected") with color as reinforcement. The rule is simple enough to be routinely forgotten: anything communicated by color needs a color-independent equivalent.
Performance budgets: four measurable lines for an agent UI
The previous days involved a lot of optimization, but "how fast is fast enough" was never answered. A budget is that answer.
Why a budget rather than "as fast as possible": without numbers there is no criterion, and every discussion collapses into competing impressions.
The four used here were chosen for agent-specific bottlenecks, not copied from a generic web checklist:
| Metric | Budget | Why this one |
|---|---|---|
| Time to first token | 1000ms | Users are waiting for the model to speak; the single most important metric |
| Longest blocking task | 50ms | Beyond this, typing feels laggy |
| State updates per second | 70 | Day two's per-frame batching exists for this; exceeding it means batching is not working |
| DOM node count | 5000 | Memory and render cost in long sessions; past this, adopt virtualization |
Note that first contentful paint is not on this list. The metric traditional web performance cares most about matters far less in an agent interface. Choose budgets for your scenario rather than copying a generic list.
One implementation detail: unmeasured metrics should display as "no data", not zero. Zero reads as passing.
Wrapping up: turning seven days into a portfolio project
The last section is about making this workbench presentable.
Talk about problems, not features. "Implemented streaming" is a feature. "Solved a render storm at 80 deltas per second, cutting renders from 176 to 57" is a problem and a result. Only the second shows you understood what you built.
Be able to justify every decision. Interviewers will pick one thing and ask why. Every deliberate trade-off in this course is good material: why approval is interrupt-and-resume, why replace must fail strictly, why long tasks get no percentage bar, why announcements do not point at the streaming element.
Label honestly what you did not do. Open-ended generative UI was not built, virtualization was explained but not adopted, screen reader behavior was only verified on VoiceOver. Stating the boundaries is more credible than implying completeness, and it often steers the conversation somewhere you want it.
Seven days done. You now hold a workbench aligned with an open protocol, free of any agent frontend SDK, with explicit positions on both performance and accessibility — well beyond the completeness of most "AI chat pages".
Verifying accessibility in CI, and its limits
Some of today's requirements can be checked automatically, and it is worth doing.
An automated pass can assert that the live region exists and starts empty, that its attributes are what you intended, that every interactive element is reachable by keyboard, and that announcement counts stay far below delta counts. Tools such as axe can also catch missing labels and insufficient contrast.
What automation cannot check is everything temporal: whether announcements were throttled away, whether pacing felt usable, whether an interruption landed at a sensible moment. Those need a person and a real screen reader.
So treat automated accessibility checks the way you treat type checking — valuable because they are cheap and catch regressions, never sufficient on their own.
Source Reading
Hands-On Lab
This is the day that most needs a real person. Please actually turn on a screen reader (Cmd+F5 for VoiceOver on macOS) and toggle aria-atomic between true and false to hear the difference yourself. One listen beats ten readings of the documentation.
- Implement the announcer that pushes completed sentences to the live region
- Verify once on VoiceOver and record what you actually heard
- Complete keyboard access and focus management, then run the approval flow with keyboard only
- Set the performance budget, measure it, and record the values
- Write the portfolio description explaining which problems this workbench solves
Interview Questions
Four questions below, focused on accessible announcement of streaming content, setting and measuring performance budgets, and interaction accessibility. Open each one and read the analysis before the answer points — practicing the derivation beats memorizing bullets. The "common in China / common globally" tags let you filter by target market.
Checklist and Tomorrow
- Explain why pointing a live region at a streaming element fails, and implement sentence-level announcement instead
- Set and measure a performance budget, and say which metrics matter most for agent interfaces
- Complete keyboard access and focus management so the approval flow works without a mouse
- Explain why keyboard focus should not follow newly arriving messages
- All 6 lab acceptance criteria pass
- Answer at least 3 of the 4 interview questions without looking at the answer points
Seven days are done. Looking back at the arc: D1 consumed a stream, D2 made it smooth, D3 let users see and stop tool calls, D4 surfaced thinking and state, D5 let the model shape the interface, D6 made sessions controllable and branchable, D7 made all of it usable by everyone. One sentence runs through all of it — turning "unknown" into "visible", and the rest is that sentence applied at different layers. The most valuable next step is wiring this workbench to a real model backend (the D7 lab explains how), where you will immediately discover what the determinism of offline scripts was hiding.
Interview questions
Why is pointing a live region at the streaming element wrong, and what should you do instead?为什么把实时区域直接指向流式渲染的元素是错的?正确做法是什么?
Common in ChinaCommon overseasDeep dive#accessibility#aria-live#streamingHow to reason about it · think before answering
- A strong discriminator, because the wrong approach is nearly everyone's first instinct and looks entirely reasonable — content is changing, so add aria-live and let the screen reader know.
- The problem is that both configurations fail, and you should name both: with aria-atomic true, every delta **re-announces the whole passage**, producing a stutter of restarts; with false, dozens of DOM mutations per second exceed the screen reader's pacing and it **skips them entirely**, so the user hears nothing.
- One more aggravating factor: NVDA, JAWS, and VoiceOver each handle high-frequency changes differently. 'It read fine on my machine' is especially unreliable here — you verified one third of the field.
- The right approach **decouples visual from auditory presentation**: visually the text keeps appearing character by character, while audibly nothing is announced until a sentence completes, at which point that sentence is pushed to the live region. Sighted users read progressively, screen reader users hear sentence by sentence, comparable pacing and neither is flooded.
- Two implementation details matter: keep a cursor of what has already been announced and push only the new portion, and add a character-count fallback for long text without terminal punctuation, or an unpunctuated passage never announces at all.
- Expect the follow-up on the live region element itself: three things. Visual hiding must not use display none or visibility hidden, since screen readers ignore those; use the standard absolutely-positioned clipped pattern. The region must be empty on page load, because a region that appears with content in it is not announced. And use polite rather than assertive, since assertive interrupts what the user is currently hearing, and an agent's reply is information, not an alarm.
分析过程 · 先想清楚再作答
- 这题的区分度极高,因为那个错误做法是几乎所有人的第一反应,而且它看起来完全合理——内容在变,加个 aria-live 让屏幕阅读器知道,有什么问题?
- 问题在于两种配置都不行,要能把两种都说出来:aria-atomic 为 true 时,每来一个增量就**重念整段**,用户听到的是不断从头开始的噪音;为 false 时,每秒几十次的 DOM 变化超出了屏幕阅读器的处理节奏,它会**直接跳过**,用户什么都听不到。
- 还有一层加重了问题:NVDA、JAWS、VoiceOver 三家对高频变化的处理各不相同。所以「我在我电脑上试过能读」在这里特别不可靠——你只验证了三分之一。
- 正确做法是把**视觉呈现与听觉呈现解耦**:视觉上文字继续逐字出现,听觉上流式期间完全静默,等一个句子完整了才把这一句推进实时区域。视觉用户逐字看,屏幕阅读器用户按句子听,节奏相当而且都不被淹没。
- 实现上有两个必须做对的细节:用一个已播报位置的游标,只推新增部分绝不重复;以及给没有句末标点的长文本一个字符数兜底,否则一段没有句号的文字会一直不播报,用户干等着。
- 可预期的追问是「实时区域元素本身有什么讲究」——三个:视觉隐藏不能用 display 为 none 或 visibility 为 hidden(那样屏幕阅读器也读不到),要用绝对定位加裁剪的标准写法;页面加载时必须是空的,带着内容出现的区域不会被播报;用 polite 不用 assertive,因为 assertive 会打断用户正在听的内容,而 Agent 的回复是信息不是警报。
Key points
- aria-atomic true re-announces everything per delta; false gets skipped entirely. Neither works.
- The three major screen readers differ on high-frequency changes, so single-machine verification is unreliable.
- Decouple visual from auditory: stay silent while streaming and announce sentence by sentence.
- Track what has been announced to avoid repeats, and add a character-count fallback for unpunctuated text.
- The region itself: never hide with display none, keep it empty on load, and use polite rather than assertive.
答题要点
- atomic 为 true 会每个增量重念整段,为 false 会被屏幕阅读器整个跳过,两种都不行。
- 三家屏幕阅读器对高频变化处理各不相同,单机验证不可靠。
- 正确做法是视觉与听觉解耦:流式期间静默,按句子完成时分段播报。
- 实现要点是只推新增部分不重复,以及给没有标点的长文本加字符数兜底。
- 实时区域本身:视觉隐藏不能用 display none,加载时必须为空,用 polite 不用 assertive。
What performance budgets would you set for an agent chat interface, and how would you measure each?你会给一个 Agent 聊天界面设哪几条性能预算?分别怎么测?
Common in ChinaCommon overseasIntermediate#performance#metrics#agent-uiHow to reason about it · think before answering
- This tests whether you choose metrics for the scenario rather than reciting a generic web performance list. Answering LCP, FID, and CLS suggests you have not considered what makes agent interfaces different.
- Start with why budgets exist: **without numbers there is no criterion**, and every 'is it fast enough' discussion degenerates into competing impressions. With a line drawn, over is over.
- Four budgets chosen for actual agent bottlenecks: time to first token (users are waiting for the model to speak, the single most important one, measured from click to first delta); longest single blocking task (beyond about 50ms typing feels laggy, measured with a long-task observer or the profiler); state updates per second (a direct signal of whether per-frame batching works, counted and divided by elapsed time); and DOM node count (memory and render cost in long sessions, a threshold for adopting virtualization).
- Worth volunteering: **first contentful paint is not on this list**. The metric traditional web performance cares most about matters far less here, because the user's anxiety is about when the model starts talking, not when the page finishes painting. Saying this shows you reason from the scenario.
- A small but important detail: metrics you have not measured should display as 'no data', never zero, since zero reads as passing.
- Expect the follow-up on exceeding budget: follow day two's order — profile first to locate the cost in rendering, parsing, or layout, then apply per-frame batching, lowered update priority, memoization, and two-pass rendering, leaving virtualization last since it complicates scroll anchoring and search.
分析过程 · 先想清楚再作答
- 这题在考你会不会按场景选指标,而不是背一份通用 Web 性能清单。直接答 LCP、FID、CLS 那几个,说明没想过 Agent 界面特殊在哪。
- 先说为什么要有预算:**没有数字就没有判据**,每次关于「够不够快」的讨论都会变成主观感受之争。定下线之后,超了就是超了。
- 四条按 Agent 界面实际瓶颈选的:首字延迟(用户等的是模型开口,这是最关键的一条,用点击到第一个增量到达的时间差测);最长单次阻塞(超过 50ms 用户就能感到输入卡顿,用长任务观察器或性能面板测);每秒状态更新次数(按帧合并有没有生效的直接指标,自己计数除以耗时);DOM 节点数(长会话的内存与渲染成本,超了说明该上虚拟化)。
- 值得主动说出来的是**首屏渲染时间不在这个表里**。传统 Web 最看重的指标在这里远不如首字延迟重要,因为用户的等待焦虑来自模型什么时候开口,不是页面什么时候画完。这一句能说明你是按场景思考的。
- 实现上有个小而重要的细节:没测到的指标要如实显示「没数据」而不是 0,显示 0 会让人误以为达标。
- 可预期的追问是「预算超了怎么办」——按 D2 的顺序处理:先量清楚瓶颈在渲染、解析还是布局,再依次上按帧合并、降低更新优先级、memo、两趟渲染,虚拟化放最后因为它会让滚动锚定和搜索全部变复杂。
Key points
- State the principle: without numbers there is no criterion, and budgets end arguments from impression.
- Four budgets: time to first token, longest blocking task, state updates per second, DOM node count.
- First contentful paint is deliberately absent: users await the model speaking, not the paint.
- Unmeasured metrics show 'no data', never zero, which would read as passing.
- When over budget, measure before tuning, and leave virtualization last since it complicates other features.
答题要点
- 先说原则:没有数字就没有判据,预算的价值是终结主观感受之争。
- 四条是首字延迟、最长单次阻塞、每秒状态更新次数、DOM 节点数。
- 首屏渲染刻意不在表里:用户等的是模型开口,不是页面画完。
- 没测到的指标显示「没数据」而不是 0,显示 0 会被误读成达标。
- 超标时按先量后调的顺序处理,虚拟化放最后因为它会让别的功能变复杂。
When a new message streams in, should keyboard focus follow it? Justify your answer.新消息流式到达时,键盘焦点应该跟着走吗?说出你的判断和理由。
Common in ChinaCommon overseasIntermediate#accessibility#keyboard#focus-managementHow to reason about it · think before answering
- A trap question, because 'move focus to new content' sounds like an accessibility improvement while actually doing harm.
- The answer is **no**, and one sentence suffices: the user may be typing in the input, and stealing focus is hostile. Worse, streaming content changes constantly, so focus chasing it makes keyboard operation impossible.
- The right approach is to **inform** via a live region rather than **compel** via focus. That generalizes: live regions for notification, focus only for navigation the user initiated.
- The one case that warrants moving focus is **opening a modal dialog**, since the user's other interactions are already blocked; moving focus in is then required, as is restoring it to the triggering element on close.
- Two related keyboard requirements earn extra credit: focus must be **visible** — many projects remove the outline for aesthetics and leave keyboard users lost, whereas focus-visible shows it only for keyboard interaction — and a skip link as the first focusable element spares keyboard users from tabbing through the whole navigation.
- Expect the follow-up on where keyboard access matters most in an agent UI: **approval**. If the approve and reject buttons cannot be reached by keyboard, keyboard-only users cannot authorize or decline anything, which is functional exclusion rather than an inconvenience.
分析过程 · 先想清楚再作答
- 这题是个陷阱题,因为「让焦点跟随新内容」听起来像是在做无障碍优化,实际上是帮倒忙。
- 答案是**不该**,理由一句话就够:用户可能正在输入框里打字,抢走焦点是很粗暴的。而且流式内容每秒都在变,焦点跟着跑会让键盘用户完全无法操作。
- 正确做法是用实时区域**告知**,而不是用焦点**强迫**。这是一条通用原则:通知用途用实时区域,焦点只用于用户主动发起的导航。
- 唯一该主动移焦点的情况是**打开了模态对话框**——因为那时用户的其余操作本来就被阻断了,把焦点移进去反而是必须的(还要记住关闭时把焦点还回原来的触发元素)。
- 顺带说两件相关的键盘要求会加分:焦点必须**看得见**,很多项目为了好看去掉 outline,那会让纯键盘用户彻底迷路,用 focus-visible 可以只在键盘操作时显示;以及给一个跳转链接作为页面第一个可聚焦元素,让键盘用户不必每次穿过整个导航。
- 可预期的追问是「Agent 界面里键盘可达最关键的是哪里」——**审批**。如果审批按钮 Tab 不到,纯键盘用户就无法批准或拒绝任何操作,这不是体验问题而是功能性排除。
Key points
- No: the user may be typing, stealing focus is hostile, and streaming content changes constantly.
- Inform with a live region rather than compelling with focus; focus is for user-initiated navigation.
- The one exception is a modal dialog, where focus should move in and be restored to the trigger on close.
- Focus must be visible; focus-visible shows the outline only for keyboard interaction.
- Approval is the critical keyboard path in an agent UI; unreachable buttons are functional exclusion.
答题要点
- 不该跟随:用户可能正在打字,抢焦点很粗暴,而且流式内容每秒都在变。
- 用实时区域告知,不要用焦点强迫;通知用实时区域,焦点只用于用户主动发起的导航。
- 唯一例外是打开模态框,那时该移焦点进去,关闭时还要把焦点还回触发元素。
- 焦点必须看得见,用 focus-visible 可以只在键盘操作时显示 outline。
- Agent 界面里键盘可达最关键的是审批,按钮 Tab 不到等于功能性排除。
Your streaming UI is green across automated tests, yet users report problems with screen readers. What does that tell you?你的流式界面在自动化测试里全绿,但用户报告说屏幕阅读器上有问题。这说明什么?
Common in ChinaCommon overseasDeep dive#testing#accessibility#engineering-practiceHow to reason about it · think before answering
- This probes your understanding of testing boundaries rather than a specific technique, and doubles as a self-check on whether you have ever actually turned a screen reader on.
- The root cause is that **automated tests verify DOM structure while screen reader behavior is temporal**. You can assert the live region exists, has the right attributes, and changed content, but not what the user actually heard — whether announcements were throttled away, repeated, or spaced far enough apart. None of that is visible in the DOM.
- A concrete example from building this course's lab: the logic-layer selftest was fully green, yet the second sentence came out as a fragment with its opening swallowed. The announcer tracked its position per message and was not reset when a new message began, so it reused the previous offset. Sentence splitting within one message was perfectly correct, which is why logic tests missed it.
- The second cause is **inconsistency between screen readers**. NVDA, JAWS, and VoiceOver handle high-frequency changes differently, so passing on one says little about the other two.
- The conclusion: interfaces like this require a **manual acceptance checklist** that is actually executed. It should include listening to announcement pacing with a real screen reader, completing the critical flow with keyboard only, and deliberately breaking a key attribute once to hear the difference.
- Expect the follow-up on what automation is still worth: it guards structural regressions — the region exists, starts empty, keeps its attributes, and announces far fewer times than there are deltas. It is necessary but not sufficient. **Automate what can be automated, and list the rest honestly rather than pretending it is covered.**
分析过程 · 先想清楚再作答
- 这题考的是对测试边界的认识,不是某个具体技术点。它也是个很好的自我检验:你有没有真的打开过屏幕阅读器。
- 根本原因是**自动化测试验的是 DOM 结构,而屏幕阅读器的行为是时序性的**。你可以断言实时区域存在、属性正确、内容变了,但断言不了「用户实际听到了什么」——播报会不会被节流吃掉、会不会重复、两段之间有没有留够间隔,这些 DOM 上都看不出来。
- 我自己写这门课的 lab 时就踩到过一个具体例子:逻辑层自检全绿,浏览器里听到的第二句却是「速高于行业均值」——开头被吞了。原因是播报器按消息追踪已播报位置,第二条消息开始时没重置,沿用了第一条的偏移量。单条消息的分段完全正确,所以逻辑测试发现不了。
- 第二个原因是**屏幕阅读器之间行为不一致**。NVDA、JAWS、VoiceOver 对高频变化的处理各不相同,在一个上验过不代表另外两个也行。
- 所以结论是:这类界面必须有**手动验收清单**,而且要真的执行。清单上该有的项目包括真开一次屏幕阅读器听播报节奏、纯键盘走完一遍关键流程、把关键属性改错一次感受差别。
- 可预期的追问是「那自动化测试还有什么用」——有用,它守住的是结构层的回归:区域存在、加载时为空、属性没被改错、播报段数远少于增量数。它是必要不充分条件。**能自动验的尽量自动验,验不了的要诚实列进手动清单,而不是假装覆盖到了。**
Key points
- Automation checks DOM structure, but screen reader behavior is temporal and what was heard cannot be asserted.
- Concrete case: per-message splitting was correct but the offset was not reset across messages, swallowing the second message's opening while tests stayed green.
- The three major screen readers differ on high-frequency changes, so one passing proves little.
- You need a manual acceptance checklist that is actually run, including a real screen reader and a keyboard-only pass.
- Automation still guards structural regressions; automate what you can and list the rest honestly.
答题要点
- 自动化测试验的是 DOM 结构,而屏幕阅读器行为是时序性的,听到什么断言不了。
- 具体例子:单条消息分段正确但跨消息没重置偏移,第二条开头被吞,逻辑测试全绿。
- 三家屏幕阅读器对高频变化处理不同,在一个上验过不代表另外两个也行。
- 结论是必须有真正执行的手动验收清单,包括真开屏幕阅读器和纯键盘走一遍。
- 自动化仍然有用,它守结构层回归;能自动验的自动验,验不了的诚实列进清单。