---
title: "不用瀏覽器的前端開發"
date: 2023-05-03T00:00:00+08:00
publishDate: 2023-05-03T00:00:00Z
lastmod: 2023-05-01T22:02:24+08:00
tags: ["心得","經驗","前端","測試"]
toc: true
permalink: "https://blog.aotoki.me/posts/2023/05/03/write-frontend-without-use-browser/"
language: "zh-tw"
---


最近在準備明年（2024）的連載主題，在實作的時候意外發現自己逐漸習慣「不使用瀏覽器」的狀況下去進行開發，除了比較常用的後端情境之外，連前端也很順利的可以實踐這件事情。

<!--more-->

## 測試驅動開發{#test-driven-development}

一直到現在，我都很難實踐測試驅動開發。畢竟有非常多情況是很難適用的，例如在做 PoC（Proof of concept，概念驗證）的時候，還硬要寫測試是有點浪費時間，或者在對需求很不確定時也只能硬著頭皮做看看。

如果在需求足夠明確的狀況下，要實現這件事情就沒有預想中的困難。但這也不表示所有的開發過程都要先寫測試，採用 ATDD（Acceptance test-driven development，驗收測試驅動開發）的方式更為適合。

簡單來說，就是我們以「想要的成果」當作前提撰寫測試，並且順著這些測試來實作。在過程中會不斷的重構出新的元件或模組，他們的測試可能是在重構（Refactoring）的過程中實現，不一定會在初期完成。

舉個例子，像是我在後端預期有一個這樣的 API 回應：

```json
{
  "data": [
	  { "id": 1, "done": true, "description": "write article" }
  ]
}
```

那麼我可以先寫一段測試驗證，可以用 Cucumber、RSpec、Vitest 等等各種測試框架來實現。

```ruby
RSpec.describe 'GET /todos' do
  # ...
  subject { get todos_path }

  it { is_expected.to include(/"data": \[/) }
  it { is_expected.to include(/"id": 1/) }
  # ...
  # or customize matcher to verify response
end
```

在這個階段，我們可以會有一些 Controller、Model 之類的物件被實現，但是我們還不需要去撰寫相關的單元測試（Unit Test）只需要先關注在這個功能的輸入與輸出即可。

## 前端的目標{#target-of-frontend}

這幾年對 OKR（Objective Key-Result）的概念比較熟悉後，會發在軟體產業的產品開發，不外乎就是去實現滿足使用者需求的功能，那麼在 ATDD 的思考方式下，我們只需要關注「如何使用達成目的」這樣的目標即可。

假設我們要製作一個計算機，可能會有一份這樣的 User Story（使用者故事，描述使用者的一系列動作）這裡直接使用 Cucumber 的 Gherkin 格式撰寫，現實狀況可能更像一段文章。

```gherkin
#language:zh-TW
功能: 計算機
  場景: 蒼時可以透過點選 1 + 1 獲得 2 的結果
    當 點選按鈕 "1"
    並且 點選按鈕 "+"
    並且 點選按鈕 "1"
    並且 點選按鈕 "C"
    那麼 可以看到 "2" 作為結果
```

這裡描述了一個加法計算的情況，並且詳細的描述我們從使用者故事中提取出來的「預期操作」來反應這件事情，最終的目標是有一段程式能實現這件事情。

過去我會覺得這樣的測試非常難以處理，主要是在環境準備的過程中有不少條件需要滿足。在準備文章一開始提到明年的連載時，也是在這個階段摸魚了一段時間才開始。然而，現在有非常多容易使用的工具，我們可以很輕鬆的實現這件事情。

## 用 Playwright 實現{#use-playwright}

選用 [Playwright](https://playwright.dev/) 的理由是我用起來覺得蠻順手的，如果習慣使用像是 [Cypress](https://www.cypress.io/) 這類工具也完全沒問題。

我們這邊直接以 Playwright 的方式撰寫測試，如何跟 Cucumber 搭配就請期待我在 2024 年的新連載系列。

```ts
import { test, expect } from '@playwright/test';

test('aotoki can calculate 1 + 1', async ({ page }) => {
  await page.getByRole('button', { name: '1' }).click()
  await page.getByRole('button', { name: '+' }).click()
  await page.getByRole('button', { name: '1' }).click()
  await page.getByRole('button', { name: 'C' }).click()

  await expect(page.getByTestId('result')).toHaveText('2')
});
```

上面這段測試程式，跟 Cucumber 描述的行為是完全一致，也就是說只要我們能在我們的前端實作中符合這些條件，那麼就可以實現這個功能，而這個功能在開發過程中是不需要「在瀏覽器測試」的。

以這個案例，我們是無法驗證有 1 以外的按鈕，因此我們可以繼續改善測試的描述。

```gherkin
#language:zh-TW
功能: 計算機
  場景: 蒼時可以透過點選 1 + 1 獲得 2 的結果
    當 點選按鈕 "<btn1>"
    並且 點選按鈕 "<btn2>"
    並且 點選按鈕 "<btn3>"
    並且 點選按鈕 "C"
    那麼 可以看到 "<res>" 作為結果
    例子:
      | btn1 | btn2 | btn3 | res |
      | 1    | +    | 1    | 2   |
      | 2    | +    | 3    | 5   |
      # ...
```

這件事情有幾個面向可以思考：

* 使用者真的需要 2 ~ 9 的按鈕嗎？
* 在目前這個階段有需要嗎？

因為不是這篇文章的重點，就留給大家思考。直到我們的功能完善後，我們在何時需要瀏覽器呢？主要就是「切版」的任務，這件事情有可能是由設計團隊負責，像是 Design System（設計系統）這樣的東西。

不論如何，假設我們試著將「畫面呈現」的因素排除專注在功能上，在需要處理「視覺」問題之前，也許都不需要使用瀏覽器輔助。

> 切版某種程度上也能做到自動化，然而要追求到精確的符合設計稿難度實在太高，我認為乖乖打開瀏覽器還是比較實用的方式。

