Playwright 赢的不是测试:它赢的是“被调用的能力”

如果你这两年留意过前端测试圈,大概会注意到一件事:新项目的端到端测试,默认都写成 Playwright 了。可这件事有点说不通:Cypress 也会自动等待,它的检查项甚至比 Playwright 还多,代码读起来也更舒服。

所以我想搞清楚一个问题:这一轮换代,为什么是 Playwright?

趋势本身很清楚:2025 年的行业调查里,它的使用率第一次超过 Cypress(50% 对 47%),而最新的周下载量是它的 17 倍。但数字只能说明趋势,说明不了原因。

功能表也回答不了,得往下一层看:那一行 click() 在真的落下之前,究竟检查了什么。我拿一颗 600 毫秒之后才可点的按钮当尺子,把它拆成四道关:怎么找到元素、怎么判断能点、怎么把动作送进浏览器、失败之后留下什么。四道关看完,再回头说这两年的事。

积木,而非成品:Pi Agent Harness 的克制与精妙

上一篇文章《给大脑配一副好鞍具》里,Pi 交出的成绩单是这样的:9 秒修完 3 个 bug,6 次工具调用,3375 个 token,成本 0.00007 美元。五款 agent harness 里最快,也最便宜。

这篇文章想把 Pi 拆开看看,把内部机制往深里挖一层。我想搞清楚两个问题:它是怎么一步步长成今天这样的,以及我们敲下一行命令之后,它内部究竟发生了什么。

第一个问题的答案不在文档里,在 git 历史里。所以先去翻它的提交记录。

向内归因的人,手里永远多出几个选项

最近一年多,我在团队里看到一个有点反直觉的现象:AI 编程工具普及之后,大部分人的产出都上去了,但人和人之间的差距没有缩小,反而拉大了。

按理说不该这样。同一件工具、同一类任务,所有人都能用;工具对每个人是公平的。那差额是从哪来的?

我的答案是:来自我们怎么解释自己的处境。

说得再直接一点:向内归因能解决更多问题。

这不是一句道德劝告,而是一个效率判断。同一个处境落到两个人手里,会向内归因的那个人,手里永远多出几个选项;而习惯向外归因的人,会先把选项一个个划掉,最后得出”我没什么可做的”。

这篇文章想说清三件事:为什么它在今天比过去更管用,怎么才算真正做好它,以及我自己就是它的受益者。

分金的人最后拿:制度设计的六个原则

带团队这么多年,我越来越觉得:管理者的终极武器不是”盯人”,而是”设计规则”。 一个人盯得了三五个人,盯不了三五十个人;但一套好的制度,可以让几百上千人各就各位、自动运转。反过来,一套坏的制度,哪怕配上再努力的人,也会把人心和干劲一点点磨掉。

邓小平在《党和国家领导制度的改革》里说过一段很重的话:

我们过去发生的各种错误,固然与某些领导人的思想、作风有关,但是组织制度、工作制度方面的问题更重要。这些方面的制度好可以使坏人无法任意横行,制度不好可以使好人无法充分做好事,甚至会走向反面。

这段话虽然是针对国家治理讲的,放到组织管理里同样成立。好的制度设计,能够最大限度释放生产力、优化资源配置;坏的制度设计,则会让好人做不了好事。 这篇文章想把我这些年读到、听到、实践过的一些制度案例整理成六个原则。案例尽量真实可查,文末附有出处,供大家批判性参考。

给大脑配一副好鞍具:五款 Agent Harness 的解剖与实测

如果你用过两个以上的 AI 编程工具,大概率会有这种困惑:同一个模型,装在 Claude Code 里和装在 OpenCode 里,干活的样子完全不一样。 一个慢条斯理、先列计划再动手;一个风风火火、脚本一甩就开跑;一个问东问西,一个闷头就干。模型明明是同一个,差别从哪来的?

答案藏在 harness 里。

“Harness” 的英文原意是马具、鞍具——套在牲口身上,让它的力气能变成拉车、耕地这些实际用途的那套东西。AI 语境下,Anthropic 官方文档给这类工具起过一个很准确的名字:agentic harness(Agent 鞍具)。大模型本身只会”说话”——从概率里吐出下一段 token;是 harness 给它装上了”手脚”(读写文件、跑命令)、”眼睛”(观察执行结果)、”红绿灯”(权限审批),它才真正能干起活来。

所以,你买的从来不是模型,而是”模型 + 一整套鞍具”。 这篇文章想做的事很朴素:拿一把解剖刀,把五款有代表性的 agent harness——Claude Code、Codex、OpenCode、Pi、DeepSeek Harness——按同一套框架拆开,看看它们各自把”身体”装成了什么样;再把我亲手实测的记录摆上来,让结论都落在事实上。

为了拆得干净,我给自己配了一套统一的解剖框架。先讲框架,再拆标本。

Terminal UI:用 React 的方式重做命令行

如果你用过 Claude Code、Gemini CLI 或 GitHub Copilot CLI,会立刻注意到一件事:这些工具在终端里的表现,和过去那种黑底白字、一行行 console.log 的 CLI 完全不同。它们有清晰的布局——左侧是对话区,右侧是状态或工具面板,底部是输入框;有焦点切换、有动效、有层次感。这不是偶然。这些 AI Coding 工具都选择了同一条技术路线:用 React 写终端界面。

本文想和你聊聊这个方向的代表方案 Ink。文章分两层:先是「怎么用」——组件、Flexbox、Hooks,几乎不用换脑子;再是「为什么」——我会把 Ink 拆开,读它的源码(reconciler、Yoga 布局、2D buffer 渲染、终端写入),再跑真实的例子把输出摆上来,让结论都落在事实上。当「前端之美」的心智迁移到终端这块看似受限的介质时,会发生什么?值得亲眼看一下。

AI 时代会需要更多程序员

“AI 会取代程序员吗?”

这个问题从 AI Coding 工具开始真正进入工程实践以来,就一直萦绕在很多程序员的心头。虽然 GitHub Copilot 早在 2021 年就已发布,Cursor 也在 2023 年面世,但 2024 年大家并没有很认真地对待 AI coding,主要还停留在代码补全和测试用例生成等简单场景。然而到了 2025 年,随着 Claude Code 等新工具的推出,AI 在编程辅助上的应用真的变得广泛起来,从最初只能做简单的代码补全,到现在可以连续数小时不间断地生成相对完善的实现,这种变化让程序员的焦虑也在不断加剧。

作为一个在技术行业摸爬滚打多年的程序员,我想分享一个可能和主流焦虑不太一样的观点:AI 时代不仅不会让程序员失业,反而会需要更多的程序员——但前提是,我们必须学会与新的生产力工具共舞,升级自己的技能和思维方式。

AI 时代会需要更多程序员

父爱如山 - 读 Kite Runner 中 Baba 有感

很久没有读过这样扣人心弦的小说了,记得上一次有这种爱不释手的感觉还是在高铁上读《三体》。一方面,作者讲述故事的方式非常精巧,细节和铺垫特别多,让人感觉既真实又意料之外。另一方面,因为作者的母语并不是英语,所以读英文原版就没有那么的吃力。好像和他一起在学习各种进阶词汇和表达一样,非常适合我。

读完后的感受颇多,很多人都写过主人公自己的背叛和救赎,这篇我想聚焦在另一个主要的角色上:Baba,主人公 Amir 的父亲。

善于经营

文章开篇就介绍了 Amir 家境的优越,而这一切都可以归功于 Baba:

Everyone agreed that my father, my Baba, had built the most beautiful house in the Wazir Akbar Khan district, a new and affluent neighborhood in the northern part of Kabul. Some thought it was the prettiest house in all of Kabul.

Baba proved them all wrong by not only running his own business but becoming one of the richest merchants in Kabul. Baba and Rahim Khan built a wildly successful carpet-exporting business, two pharmacies, and a restaurant.

Baba 不但是一位成功的商人,而且他还非常乐善好施。一个具体的例子就是他投入了巨大的精力和财力,花费了三年时间建设了一座孤儿院。在开业典礼上,Baba 做了一场精心准备的演讲,虽然还有些滑稽的场面,但最终赢得了大家经久不息的热烈掌声。

此外,Baba 还是一位非常活跃的社交家,这一点在 Amir 10 岁生日聚会上体现得淋漓尽致。

Baba’s motto about throwing parties was this: Invite the whole world or it’s not a party. I remember scanning over the invitation list a week before my birthday party and not recognizing at least three-quarters of the four hundred-plus Kakas and Khalas who were going to bring me gifts and congratulate me for having lived to thirteen. Then I realized they weren’t really coming for me. It was my birthday, but I knew who the real star of the show was.

The night before the big party Baba’s friend Del-Muhammad, who owned a kabob house in Shar-e-Nau, came to the house with his bags of spices. Like the butcher, Del-Muhammad – or Dello, as Baba called him – refused payment for his services. He said Baba had done enough for his family already. It was Rahim Khan who whispered to me, as Dello marinated the meat, that Baba had lent Dello the money to open his restaurant. Baba had refused repayment until Dello had shown up one day in our driveway in a Benz and insisted he wouldn’t leave until Baba took his money.

第一次读到这段内容的时候,我脑中浮现的是教父在女儿婚礼上的那一幕幕经典场景,众人环绕,德高望重。

毫无疑问,有如此一位成功的父亲,Amir 是十足幸运的。

Just Culture

“尚春,你去写一份 Case Study 吧”。

当师父对我说出这句话的时候,我的第一反应是吃惊和委屈。根据我的了解,Case Study 一般是在发生较大的线上故障后才由当事人总结的。由于我们技术部门刚刚推行这个制度不久,当时只有很少的人写过。因此,我下意识地认为这更多是一种羞辱和惩罚,觉得本来一个不太大的问题会被所有人都知道,自己的面子都挂不住。

师父对我们一直都非常好,幽默风趣,善于授人以渔。他对技术和界面都有着极致的追求。虽然觉得写 Case Study 对这次线上故障有些小题大做,但我还是硬着头皮打开文档平台,开始着手写总结。首先,我简单了解了 Case Study 的文档结构,一般包含:

  • 关键事件时间线。主要记录线上故障的发生、发现、处理和解决过程;
  • 预估损失。不同的故障所造成的损失也不不尽相同。不过一般可以分为直接损失和间接损失。间接损失主要和 KPI 挂钩;
  • 原因分析。要求采用 5 Why 准则来层层递进地分析问题的本源;
  • 改进措施。根据之前的分析罗列出来具体的改进措施和负责人。

写完总结后,师父很满意,同时也给了一些建议,并嘱咐我一定要抓紧落实改进措施。这时我开始有些新的认识:他是想让更多人避免类似的问题,而不仅仅是惩罚我。

编译型框架 SvelteJS

2016 年,Rollup 等工具的作者 Rich Harris 推出了又一力作:Svelte。这一新兴框架独辟蹊径,旨在解决 React 等框架的一系列问题。这几年 Svelte 虽然没有大红大紫,但从 State of JS 统计来看,它一直保持着持久的热度: 使用度紧跟三大框架 兴趣度蝉联榜首多年

本文尝试由浅入深的介绍 Svelte,希望更多人在适合的场景下可以考虑尝试它。

缘起

Rich 非常喜欢 React 等前端库和框架带来的更加声明式、低心智负担的开发方式,这让前端工程师可以更好地控制复杂度、提升开发效率和质量。然而他也敏锐地洞察到了一些不那么尽如人意之处。

Virtual DOM 真的快吗?

React、Vue 都引入了 Virtual DOM 的概念,从而能够进行现有 DOM 以及期望 DOM 的对比,然后再执行相对较重的 DOM 操作使得两者一致。

在 Virtual DOM is pure overhead 一文中, Rich 指出 Virtual DOM 其实更多的是一种实现声明式、状态驱动 UI 开发的一种手段,并非不可或缺。它主要的问题在于有很多没有必要的 Diff。

举个例子,假设我们有如下一个 React App:

1
2
3
4
5
6
7
8
9
10
function App() {
const [count, setCount] = useState(0);
const handleClick = () => setCount(count + 1);
return (
<div className="app">
<p>Count: {count}</p>
<button onClick={handleClick}>+1</button>
</div>
);
}

如果点击一次按钮,新旧 Virtual DOM 对比如下: