WebDriver Level 2

Chrome DevTool Protocol,

這超新的,新到其實什麼都還沒有,不過總之記錄一下,有兩條路線匯流:

第一條是 E2E 測試,E2E 測試比較早期是 Selenium 一家獨大,以前不知道是用什麼方法控制瀏覽器,就我瞭解應該不是太正規的方式,後來到 Selenium 2 開始發展 WebDriver,而且各家 browser vendor 都還蠻支持的,也朝向標準化的方向前進,標準文件現在也已經是CR了,由Browser Testing and Tools 工作小組在維護,不過看了看 mailing list,該工作小組目前活躍度好像不高。標準化的好處就是大家都可以照著做,除了 Selenium WebDriver 之外的實做,現在還有WebDriverIO這個 nodejs 環境的實做,理論上可以只用 WebDriverIO 加上瀏覽器各自的 driver 而不用透過 Selenium 來做自動化測試

另外一條路線是 remote debugging,這個一開始是為了 debug 手機上的瀏覽器,後,讓手機上的 browser 傳送訊息到桌機上,用桌機瀏覽器的開發工具來顯示資訊,方便除錯,發展到後來,變成開發工具和瀏覽器之間的溝通協定都走同一套,也就是說現在桌機瀏覽器也是用 remote debugging 同樣的溝通方式在跟自己的開發工具溝通,兩者耦合就這樣拉開了,我最早知道可以這樣拆開的是 Opera 以前的Dragonfly,然後可以想見每家瀏覽器的協定內容不一樣,然後就有一位Kenneth Auchenberg的人出來說這應該要有個標準!然後弄了個remotedebug.org,初期計畫是希望大家都有個 adapter 可以轉譯自家的協定到公用的協定,像是 Mozilla 的Valence,然後接著就開始有一些利用這些協定的各種發展,像是幫 Node 程式除錯、或是 iOS App、Electron 應用程式的除錯,甚至是除錯工具的開發也是用除錯工具自己 remote debug 自己,同時 Kenneth Auchenberg 也在推動 W3C 的標準化,一開始(約三年前)就是找上 Browser Testing and Tools 工作小組,不過一開始不太順利,因為那邊的都是自動化測試專門的人,和除錯工具關係其實不大。

Remote debug protocol 的資訊種類和訊息量其實都很大,目前看起來也只有 Google Chrome 的DevTool Protocol整理的比較完整,而 Firefox 的 Valence 其實已經沒維護了,他們的 README 上說要盡量相容 Chrome 的 protocol,這點讓我有點失望也不太意外,一來是擴充套件的API已經被 Google帶著走了,二來是 debug 用的資訊太多太雜,不好維護,而且這樣似乎也是比較快可以統一的方式。而標準化的工作其實在去年有點進展,也就是 Browser Testing and Tools 工作小組 終於接納,要把他放進 WebDriver Level 2 裡面了,這其實是去年十月底的消息,在 remotedebug 的twitter上有發消息,也有工作小組章程修改的commit連結證實,接下來就看他們要怎麼標準化了,畢竟複雜度比 WebDriver Level 1 複雜許多,還有些部分是不穩定可能隨時會變動的。


CodeceptJS + puppeteer

看起來一切似乎都很美好,直到真的下去用。

這幾天就在這組合裡面打滾,昨天還花了幾乎半天在查一個問題,總之先條列一下目前覺得幾個重點:

  • CodeceptJS文件裡面有 code sample 用 generator function 的非同步取值,現在支援用 async await 了,不過 code sample 還沒改。
  • 每種 helper 可以用的 method 不完全相同,大部分一樣,不過也沒列出基本組合,所以好像也不是很好一組 test 測所有 helper。
  • Puppeteer helper 裡面其實有很多地方是直接跟 CDP(Chrome DevTools Protocol) 溝通的,這部分也可以印 debug log:env DEBUG="puppeteer:protocol" codeceptjs run --steps --verbose
  • 開 CDP 的 log 的話資訊量會超多,訊息內容還算好理解,細節網路上也有文件,左邊 sidebar 有很多不同領域的,上面的 DEBUG 參數也可以自己修改只顯示想要的,詳見puppeteer 文件
  • puppeteer 的page.goto有個選項是 waitUntil,預設是 load 事件,不過我發現這個事件有時候會觸發不到,雖然我看開發工具的 network 圖是有線出來,不過總之我後來會這樣的案例就先都改成networkidle2了。

然後昨天花很多時間查的問題已經上去發了issue,總之就是發點擊事件點連結後,要檢查新頁面的內容會出現錯誤:

Protocol error (Runtime.callFunctionOn): Cannot find context with specified id undefined

目前探究下來狀況應該是:puppeteer 的點擊回傳的 promise,在點擊完成就 resolve 了,這時候瀏覽器去開新網頁,才要開始發出請求,新的網頁還沒準備好,所以要做檢查的時候就會沒有 context。然後我有用 Nightwatch helper 測試過,是沒這問題的,總之就是個實做問題,puppeteer 目前這樣邏輯上也不算是錯誤的設計,不知道最後會怎麼修改,當然簡單一點就是 click 觸發 browser navigate 到別的網頁時就要等新網頁回來。目前的 work around 是自己多 wait 一下。

最後就是,我終於可以順暢的把 puppeteer 這個單字打出來了QQ



分號大戰 again

今天一早起來就看到有人說 TC39 要準備建議 JavaScript 程式碼應該要加分號:

然後下面就一大串了,本來想說standardjs要哭哭了,難道semistandard要扶正了嗎?不過我仔細端詳了一下,發現這個PR還是 open 狀態,而且 Brendan Eich 甚至表態反對:

原因之一是已經有很多 standardjs 的 code 其實運作的很好,不過另外一個原因我覺得更有力,就是 TC39 的文件,做為 spec 似乎不該提出建議,當然提出這個 PR 的 Daniel Ehrenberg 其實也不是單純因為支持加分號才提的,他其實是Class field declarations的主要貢獻者,這是什麼呢?就是:

class Counter extends HTMLElement {
  x = 0;

  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }
}

這種在 class method 外面定義 class 屬性和預設值的語法(另外還有 private property),而這種很像是 expression 的語句,一定要 semicolon,不然會有他稱為 AST hazard 的情形,也就是很難評斷開發者實際上意圖的情形發生,也就無法用 ASI 自動補分號,對此 Brendan Eich 有個建議是在 class field 裡面關掉 ASI 機制,也就是這些 property 定義一定要加分號做結。

目前看起來,Brendan Eich 提的那點,TC39 不該做語法的建議實在很強而有力,應該也反駁不了,所以結果應該就是沒有收 PR,或是改成更中性的文字,不是建議的文字,不過想來想去還是沒有比較適合的,畢竟在 spec 文件裡面還提建議、警告開發者用的語法就很怪。


Web F2E 看 Python Syntax

Bruce Eckel's keynote,

雖然主業是 Web Front End,不過其實要搞好 Front End,後端也不可不知,所以我工作內容其實也寫 Python 寫了不少,最近終於可以跟Flake8相安無事,所以想來記錄一些對我來說很有趣的 Python Syntax,不全是喜歡的就是了,以下內容以 2.7 為主。

內建支援 String Formatting

第一個我覺得很棒的是,Python 內建有String Formatting Operations可以用,超方便的,所以我只要寫:

'%d: The answer to the ultimate question of life, the universe and everything' % 42

就可以把 42 填進去字串裡面了,雖然我第一次看到放最後面還以為是什麼奇妙的註解符號;傳統的 formatting 用來做翻譯字串就會發現,如果有多個變數,它們的順序在不同的語言可能有不同,傳統的 formatting 只能處理固定順序,不適合這種情景,這時候還有新的format()可以用,幫變數命名好、然後丟參數進去就可以了,例如:

'{author} wrote {name}'.format(author='JRR', name='TLOR')

雖然 Python 的 string format 很好用,不過文件寫得太高深了,所以還有人做了pyformat.info這個站,收集了不少實用的範例幫助大家理解,而且仔細看過之後發現舊的格式也可以用 dict 格式丟命名變數進去。

Multiline String

多行字串也是我蠻喜歡的,像是要弄 template 的時候就很方便,JavaScript 一直到 ES6 的 tempalte string 才算是有內建,Python 就用三個引號框起來就可以了:

template_string = """<div>
                       Wow
                     </div>"""

不過很理所當然的,那些為了縮排所填入的空白,就都是真的字串內容,所以Wow前面就是有 23 個空白字元,如果字串在 class 或是 function 定義裡面,那空白就會更多,在一些使用情境下,空白數量是影響很大的,所以就會有到底該怎樣排的問題,StackOverflow 上就可以找到相關的問題,還好我處理 HTML template 的話,影響不大,當然結果會造成一些多餘的資料傳輸啦。

Keyword Arguments

很久以前我寫過一篇options object的文章,為的就是處理參數太多造成程式碼不好解讀的問題,沒想到 Python 可以在呼叫函數的時候,傳入參數的名稱,例如以下的函數:

def func(a, b, c, d):
    return a + b + c + d

呼叫的時候可以分別說明每個參數的 key 和 value 對應關係:

func(a=1, b=2, c=3, d=4)

而且也可以混用:

func(1, 2, c=3, d=4)

覺得這語法真是領先超多,當然 ECMAScript 現在可以用 destructing assignment 的語法做到類似效果,不過我覺得還是有些差距。

而針對 Keyword Argument 其實還有特殊的 syntax 是**kwargs,其實我一開始是先看到這個語法的,想說 Python 怎麼有個很像 C++ 指標的東西,看了許久,某天終於會意到 kw 是 keyword 的意思,然後才終於理解是怎麼回事,後來查資料才知道還有*args,現在的 ECMAScript 的話可以用...spread operator 做到。

Circular Dependency

恩,可以做到循環相依,第一次看到真是覺得不可思議,不過後來慢慢瞭解限制,大概也知道怎麼實際上是如何跑的了。

Ternary Operator

三元運算,Python 的語法真的是比較特別一點,其它語言比較常看到的是用?,不過 Python 是用後置的if else

reality = True if isReal else False

其實我還蠻喜歡後置的if語句,第一次看到這種寫法是在 CoffeeScript,我很常用在一些參數特殊狀況的處理,一樣 CoffeeScript,不用後置if的時候:

filename = file.name

if file.hash
  filename = filename + '-' + file.hash
  
if file.ext
  filename = filename + '.' + file.ext

用了後置if的話可以寫成:

filename = file.name

filename = filename + '-' + file.hash if file.hash

filename = filename + '.' + file.ext if file.ext

看起來整齊許多,視覺上(?)少了一層縮排,不過 Python 的三元運算,和 CoffeeScript 的後置if語法是不一樣的東西,雖然可以用來做類似的事情,但是因為他是三元運算,所以一定要提供else區段:

filename = filename + '.' + file.ext if file.ext else filename

就比較不喜歡這樣就是了。

Tuple

Python 的 List 資料型態可以比做 JS 的 Array、Dict 可以比做 Object,兩種資料型態分別是使用中括號和大括號,不過在 Python 語言裡,還有一種用小括號的 Tuple 資料型態。

Tuple 資料型態似乎還蠻少見的,我第一次聽到這個名詞的時候是在學校學資料庫系統的時候,一筆資料稱為一個 Tuple,不知道為什麼印象很深,然後第一次看到使用 tuple 的程式碼自然是不太理解,不過還算直觀看的懂,後來不知道為什麼查到這種語法其實是一種資料型態叫 Tuple 的,意義上和資料庫系統的 Tuple 感覺還蠻像的,理解這是個資料型態之後用起來覺得順手很多,而且 Python 還蠻自由,很多地方和 List 都可以用一樣的操作,像是in運算,或是作為 function 的多個回傳值(多回傳值的函數也蠻方便的)。

in 運算

上面提到的in運算,用來判斷一個 List 或 Tuple 是否包含特定元素:

if target.stat in ('ACTIVE', 'PREMIUM')
    ok()

對於常在古早 JS 開發的人,真的是超羨慕的,可能有人說可以用indexOf做,雖然 JS String 的 indexOf 很早就有了,但是 Array 的 indexOf 卻是到 ES5.1 才正式進標準,IE9 之前的都不支援,所以要用他來判斷一個元素是否在一個陣列內,首先要確定你不支援 IE8 之前的瀏覽器,不過就算支援,其實程式碼也沒in運算來的漂亮,後來 ES2015 有個比較好一點的Array.includes可以用就是了。

Dict

Dict 可以比做 JS 的 Object 比較好理解,對於這個我不能適應的有兩個地方,一是 Dict 不是 class,所以不能直接用.取屬性,一定要用[]或是內建的get(),再來就是用[]取屬性的時候,一定要 key 存在,用到不存在的 key 就會噴錯誤,如果一定要這樣操作就要改用get(),get 還有一個特點是可以給 default 值,如果是複雜的結構,想要一口氣很深入就可以寫成:

data.get('attr1', {}).get('attr2', {}).get('attr3', None)

實在是有點難看,CoffeeScript 是有 Existential Operator 可以做這種多階層的取值:

data.attr1?.attr2?.attr3?

在 TC39 的草案也有類似的Optional Chainging,這兩樣都是上一篇文章有提到的東西。

Unix Timestamp

內建的 datetime 似乎沒有支援直接輸出 Unix Timestamp,是說目前有需要都用Pendulum,還蠻好用的,API 介面也蠻直接,也有完整的時區、Period、Duration 等觀念。

PEP8, Flake8

文章一開始提到的 Flake8 把好幾個 code checker 包進去,包括了官方的PEP8、PyFlakes、pycodestyle 等,我用 Vim 的Syntastic都有支援,只要有安裝就會偵測到執行檔,然後就可以用來檢查了,一開始裝起來的時候就和第一次用 JSLint 一樣傷感情,不過兩個月過後到是還蠻適應的,其中比較和以往習慣不一樣的就是 function 參數的值,不論是定義時的 default value 還是呼叫時的 keyword argument,=的左右兩邊都是不加空白的,例如:

def hello(name='John'):
  return 'Hello ' + name

hello(name='Hancock')

另外就是特殊情況需要循環相依,或是 import 但是不使用時,會需要關閉一些檢查,可以在該行末端加上註解關閉特定項目:

import pages  # noqa: F401

錯誤的編號可以參考 Flake8 的文件

Python Enhancement Proposals (PEPs)

之前在研究 Joda Time 的時候,發現 Java 有個JSR(Java Specification Requests),在找 Python 的 coding style 的時候則是發現了PEPs(Python Enhancement Proposals),不過 ECMAScript 目前是沒有類似的、完整的收集並編目各個 Proposal 文件的地方,甚至連語言本身的官網都沒有啊...XDrz


➡ 看看其它文章