從使用到體驗
一份報告命名了一個症狀。真正的工作是它背後的體驗。「在對話轉移到要點時,客戶端就消失了」是一個錯誤。「一場對話會聚集它所討論的內容:當客戶端加入它的要點時,客戶端會停留」才是要建構、檢查和拍攝的內容。首先寫下那句話。修復允許症狀發生的結構,然後透過使用產品來證明那句話。
循環
- 收集使用情況. 閱讀報告,然後是真實使用的痕跡:該人員的對話、日誌、記錄、出席情況、他們接觸過的檔案。它們承載著沒有人寫下的期望(「他們進行到哪裡了?」期望的是要點本身,而不是摘要)。
- 寫下體驗. 每份報告一句話,使用現在式,從人員的角度出發,不使用實作詞彙。保留這份清單。它將成為影片的旁白和斷言的訊息。
- 找出結構性原因. 詢問為什麼產品會發生這種情況。一個原因通常可以解釋多份報告(從顯示名稱取得的身份;兩個共用一個詞的計數;一個在插入時丟失類別的渲染器)。修復原因,而不是顯示該問題的畫面。
- 親自使用它. 透過瀏覽器命名空間(如下),以桌面和手機寬度,分別扮演每位相關人員來操作真實的應用程式。查看螢幕截圖。計算頁面實際顯示的內容,而不是程式碼意圖要呈現的內容。
- 保留它. 將手動步驟提升為場景:操作變成事件,檢查變成斷言,體驗句變成場景的旁白。將模型的操作回合錄製在磁帶上,使影片能在幾秒內以零成本重播。
- 報告體驗,而非補丁. 在摘要中,先說明人員現在會發現什麼,然後是原因,接著是影片以及如何重播它。
團隊品味 (Othman 反覆要求的內容)
- 減少文字說明. 狀態是繪製出來的:色調、粗細、位置、圓環、標記。一句解釋畫面的句子意味著畫面本身就是錯的。
- 每個概念一個詞. 維護一份術語表並堅持使用:「critère」(依據程序判斷)永遠不會是「point」(一個人決定的東西)。如果兩個計數共用一個詞,就會有人把它們加起來。
- 事物會累積. 標籤會疊加(先類型,然後客戶,然後案由)。目前的標籤是填滿的,其他的則保持為輪廓。永遠不要取代人員已經看到的上下文。
- 數字先於文字繪製. 歷史記錄以圖表展開,每個系列一個圖表,數值有標籤。文字位於下方,每個事件一行,並按需展開。
- 每個引用都指向其位置. 一個段落的數字會滾動到該段落並將其高亮。一個報價會找到它的段落。滾動條會標記事物的位置,並使用它們的顏色。
- 預設為即時. 不進行完整重新載入。一個人所做的更改會立即顯示在所有開啟的頁面上。在慢速頁面上重複點擊不能重複效果:手勢是冪等的。
- 控制項位於事物所在之處,並且僅供可以使用它們的人使用。唯讀人員會看到狀態,但沒有控制項。
- 知道誰在那裡. 身份和角色會到達每個畫面和每個代理。不要告訴合作夥伴「合作夥伴必須確認」。
- 誠實的邊界. 直白地說出產品尚未能做的事情。永遠不要指向一個無法做到的事情的畫面。
- 手機寬度是一流的讀者視角: 16px 內邊距,無水平滾動(使用
minmax(0,1fr),而非1fr),且帳戶始終可達。 - 使用產品的語言製作線稿和旁白(對於 Knowledger 來說是法語)。
使用瀏覽器命名空間手動測試
Beam Lisp 的原生瀏覽器(optional/browser-port,位於 beam-lisp 目錄樹中)在一個私有的無頭 Sway 中運行有頭 Chromium,具有受信任的輸入和連續的螢幕錄影。它與場景驅動的是同一個物件,因此手動流程可以無需重寫而成長為測試。使用 agent-browser 進行快速查看。對於任何值得保留的內容,請使用瀏覽器命名空間。
每台機器執行一次
cd ~/projects/beam-lisp/optional/browser-port
nix develop --command cargo build --release --locked --features native-browser
nix build .#runtime-wayland --out-link ~/.local/share/bl-browser/runtime-wayland # chromium, sway, wayvnc
command -v ffmpeg || nix profile add nixpkgs#ffmpeg
使用以下指令執行任何使用它的東西:bl run -p ~/projects/beam-lisp/optional/browser-port/bl …(或 bl test -p …)。
探測器:一個檔案,一個人,真實輸入
templates/probe.bl 是起點。其核心:
(ns probe (:require [browser.page :as page]))
(def rt (str (System/get_env "HOME") "/.local/share/bl-browser/runtime-wayland/bin/"))
(def p (page/open! {:executable (str (System/get_env "HOME") "/projects/beam-lisp/optional/browser-port/target/release/bl-browser-port")
:width 1440 :height 860
:launch {"profile" "/tmp/probe/profile" "runtime" "/tmp/probe/runtime"
"chromium" (str rt "chromium") "compositor" (str rt "sway") "vnc" (str rt "wayvnc")
"width" 1440 "height" 960}}))
(try
;; 誰在看:代理會添加的身份(此處為 Tailscale headers);它也會到達即時 socket
(page/cdp! p "Network.enable" {})
(page/cdp! p "Network.setExtraHTTPHeaders" {"headers" {"tailscale-user-login" "[email protected]" "tailscale-user-name" "timinou"}})
(page/record! p "/tmp/probe/take.mp4" {:fps 24}) ; 可選:整個流程的影片
(page/navigate! p "http://127.0.0.1:24025/a/sahel-…")
(page/wait! p "document.documentElement.dataset.live==='1'") ; 一個 JS 謂詞,預設超時 30 秒
(page/click! p ".rv-rail a.para[data-goto]") ; 受信任的指針:移動、按下、釋放
(println (page/evaluate! p "scrollY")) ; 詢問頁面人員會看到什麼
(page/screenshot! p "/tmp/probe/after.png") ; 然後查看它
(page/stop-recording! p)
(finally (page/close! p)))
動詞有 navigate! wait! click! hover! fill! key! drag! evaluate! screenshot! record! stop-recording! show-cursor! cdp!。cdp! 可以存取 Chromium 提供的任何其他功能:視口、網路、Cookie、模擬(用於手機的 Emulation.setDeviceMetricsOverride)。
讓探測器值得保留的習慣
- 詢問頁面,而非程式碼. 每個檢查都是一個 JS 表達式,當人員會看到該事物時返回
true:行數、標籤的文字、scrollY、指針下方元素的類別。這些表達式會原封不動地變成斷言。 - 依意義選擇. 在測試需要的地方,為應用程式提供穩定的資料屬性 (
button.cell[data-person='Karim Saadi'][data-team='Associés']),永遠不要用「第五個按鈕」。 - 扮演每個人. 為每個人以他們的身份開啟一個頁面。兩個頁面並排是證明「即時」的方式:一個操作,另一個無需重新載入即可更改。
- 檢查首次繪製和修補後的頁面. 即時應用程式會渲染一次 HTML,然後從資料修補。錯誤隱藏在差異中(插入時丟失的類別、以文字開頭被誤讀為標籤名稱的清單)。對每個畫面,透過 URL 造訪一次,再透過導航到它造訪一次。
- 等待頁面變成即時,而不只是連線. 一個即時客戶端可能會用其 socket 的首次渲染替換伺服器渲染的頁面。在此期間輸入的文字會丟失,對於測試和在載入後快速輸入的人來說都是如此。應用程式應該在跨越該掛載時保留輸入的內容,然後宣告它已準備就緒(
data-mounted)。測試會等待這個標誌。 - 查看手機寬度. 在 390px 寬度下檢查
document.documentElement.scrollWidth === innerWidth,然後查看螢幕截圖。 - 永遠不要觸碰真實資料,並證明它. 將應用程式指向其資料的 reflink 副本 (
cp -a --reflink=auto) 和一個備用連接埠。然後在信任它之前證明隔離性:在副本中重新命名一個東西(一個金絲雀:一個名為「AssociésX」的團隊),對它進行操作,然後檢查副本已更改,而真實檔案的時間戳沒有更改。對每種會進行寫入的進程(頁面處理器、即時 socket、背景任務)執行一次此操作。在 Beam Lisp 中,測試中的System/put_env會影響頁面處理器,但不會影響即時 socket:一個讀取未配置資料夾的 socket 會回退到預設值,也就是真實資料。應用程式必須在其伺服器啟動時將配置固定到整個 VM(persistent_term)並從那裡讀取。如果必須碰觸即時資料,請對檔案進行快照、操作、用 diff 確認只有你的更改,然後恢復。
從探測器到影片
一個 scenario.demo/defdemo 就是一個普通的測試(bl test)。步驟一一對應:
| 探測器 | 場景 |
|---|---|
(page/navigate! p url) | (s/does :othman [:navigate url]) |
(page/wait! p js) | (s/does :othman [:wait js]) |
(page/click! p sel) / hover! / fill! / key! | [:click sel] [:hover sel] [:fill sel text] [:key "Enter"] |
| 為眼睛停留的暫停 | [:pause ms] |
必須為 true 的 (page/evaluate! p js) | (s/sees :othman (fn [p _] (= true (page/evaluate! p js))) "the experience") |
| 一個人 | browser.film/owned-worlds 中的一個 actor,包裝以在開啟時設定其身份 |
| 一段旁白 | (demo/scene :id {:title … :say … :capture :video} events…) |
templates/feature-demo.bl 是一個完整的檔案:應用程式資料的隔離副本、應用程式在測試 VM 的備用連接埠上啟動、具有身份的已記錄世界、當沒有語音金鑰時的靜音軌道,以及一個包含三個場景的示範。工作範例:knowledger-interface-review/apps/practice/test/features-demo.bl(五部影片:回顧、歷史、片段、一個跨兩人即時更改的團隊、一個對話錄音)。
以體驗而非修復來撰寫場景:「每個要點都指向它的位置」,永遠不要「修復 §-link 處理器」。旁白就是文件。
磁帶上的模型回合
代理做的任何事情都應該透過應用程式自己的工具門,因此只有模型的選擇才是模型的。一個磁帶為一次模型執行保存了它依序進行的門調用及其最終答案,並以模型、系統提示和提示(時鐘、紀元數字和十六進制 ID 已被遮罩)的雜湊值鍵入。
record: 重播磁帶上已有的內容,為沒有的內容執行模型,並保存它。replay(預設): 不在磁帶上的執行會產生一個錯誤回合。重播永遠不會在靜默中回退到模型。- 重播透過相同的門、以一定的節奏進行相同的調用,因此產品會再次執行該工作,畫面會顯示它,只需幾秒鐘且免費。
將所有影響提示的內容(公司名稱、日期,如果有的話)固定在測試資料中,否則金鑰會偏移。提交磁帶。參考實作:apps/practice/src/practice/cassette.bl 及其在 conversation.bl 中的兩個鉤子(回合和門)。
執行和渲染
bl test -p ~/projects/beam-lisp/optional/browser-port/bl app/test/features-demo.bl # 重播、證明、擷取
DEMO_RECORD=1 bl test -p … # 將缺少的模型回合放入磁帶(需要模型憑證)
BL_DEMO_EXPORT=1 bl test -p … # 同時渲染每個影片的 MP4 + 字幕 + 章節
每個影片會在其 fixture 中清除上一次的錄製,因為錄製永遠不會覆蓋已有的。保持每個旁白在兩行字幕內(約 130 個字元),否則渲染會拒絕它。機器負載過重可能會導致螢幕錄影編碼器資源不足:檢查 frames/recordings.json,其中 encodedFrames 應該約等於秒數 × fps,如果錄製片段過短則重新執行。
渲染(scenario.film/export!)需要 Python 3 和帶有 libass 的 FFmpeg。當設定了 DEEPGRAM_API_KEY 時,會朗讀旁白。否則,每個場景會獲得一個與其字幕朗讀時間一樣長的靜音軌道。永遠不要假設語音金鑰存在。使用 (scenario.demo/serve! dir {:port 4163}) 在本地環回接口瀏覽影片,或使用 work serve 發布它們。
在你說完成之前
- 每個回報的項目都有其體驗句、原因和在真實瀏覽器中證明它的斷言,且是針對每位相關人員。
- 已查看桌面和手機寬度,並有螢幕截圖,而不僅僅是斷言。
- 對於觸及的每個畫面,都檢查了首次繪製和修補後的頁面。
- 在需要的地方,已用兩個人證明即時行為。
- 測試未更改任何真實資料。任何觸及的東西都已恢復,並顯示了差異。
- 影片在沒有網路的情況下從磁帶重播。磁帶和場景已提交,渲染結果則否。
- 摘要以人員現在會發現的內容作為開頭。