はじめに
自分はゴシップサイトブロッカー(以下GSB)というGoogle Chrome / Firefox拡張を作成しています。ですが、最近メンテナンスリリースしかできていません。
環境が変わったのもありますが、一番の理由はCI環境が整備されていないので変更がつらいという点にあります。これは何とかせねばということで、CircleCIを使ってGoogle Chrome拡張をテストできる環境を作りました。
・・・といってもGSBはそれなりに規模が大きくなっているので、代わりに別の拡張機能のテストができる環境を先に作りました。
GSBもやってることは一緒なので大丈夫なはず。
「期間を指定」の書式変更
元にしたのは以下の拡張機能です。
これは何かというと、Google検索で期間を指定する際に「mm/dd/yyyy」というアメリカンな仕様を強制されるのが嫌でたまらなかったので、yyyy/mm/dd形式でも入力できるようにしたものです。それだけの拡張ですがまあ割と便利です。このテストをCircleCIを使って対応しました。
対応方法
CircleCI CLIを入れる
毎回pushして結果を待つのはだるいので、CLIを入れます。
以下の2つのコマンドが使えればとりあえず何とかなります。
-
circleci config validate
: 設定ファイルのバリデーション -
circleci local execute
: ローカルで実行
Node.jsを12にする
開発中に何度もエラーが出たのですが、スタックトレースが不完全で、
どこの行でエラーが出たのかが分かりませんでした。
これは困ったというわけで調べてみたところ、
Node.js 12から入った --async-stack-traces
というオプションを追加すると、
いい感じにスタックトレースが出てくれます。
CircleCIのDockerイメージには circleci/node:12-browsers
と指定します。
CLIでChrome拡張作成
CLIでChrome拡張を作成する必要がありますが、そのために使ったのはcrxというコマンドです。
こんな感じで使います。
mkdir -p /tmp/workspace
./node_modules/.bin/crx pack -o /tmp/workspace/google-search-datepicker.crx apps
Selenium WebDriverでテスト書き
定番のSelenium WebDriverでテストを書きました。
なお、テスト書いている途中でpuppeteerの存在を知りましたが、Chrome専用なので今回はSelenium WebDriverでいいかなと判断して採用を見送りました。
まず初期化です。
const { Builder, By, Capabilities } = require("selenium-webdriver");
const chrome = require("selenium-webdriver/chrome");
const fs = require("fs");
const assert = require("assert");
const extension = fs.readFileSync(
"/tmp/workspace/google-search-datepicker.crx",
"base64",
);
const options = new chrome.Options()
.addExtensions(extension)
.windowSize({ width: 1280, height: 800 });
const capabilities = Capabilities.chrome();
capabilities.set("chromeOptions", {
args: [
"--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
],
});
2点引っかかったポイントがあります。
まず拡張機能の読みこみがうまくいきませんでした。
.addExtensions
にはファイルパスが指定できると書かれてましたが、
なぜかうまくいかなかったので、base64文字列として読み込んだ後に渡しています。
次に、Google検索の結果が取得できませんでした。
User Agentの指定がないとダメなようです。
テストを書く
そしてテストを書きます。具体的にはこんな感じになりました。
所々 pause()
が入っているのはタイミングの問題で動かなかったためです。
(async () => {
const driver = await new Builder()
.withCapabilities(capabilities)
.setChromeOptions(options)
.build();
await driver.get("https://www.google.com/search?q=test");
// click 'Tools'
const tool = driver.findElement(By.linkText("ツール"));
await tool.click();
// click 'Any time'
const any_time = driver.findElement(By.css("[aria-label='期間指定なし']"));
await driver.actions().pause(500).click(any_time).perform();
// click 'Custom range...'
const custom_range = driver.findElement(By.id("cdrlnk"));
await driver.actions().pause(500).click(custom_range).perform();
// set 'From'
const cdr_min = driver.findElement(By.id("cdr_min"));
await driver
.actions()
.pause(500)
.click(cdr_min)
.sendKeys("2019/01/02")
.perform();
// click 'Go'
const go_button = driver.findElement(By.css("#cdr_frm input[value='選択']"));
await driver.actions().pause(500).click(go_button).perform();
// assert time range label.
const time_range_label = driver.findElement(By.className("hdtb-tsel"));
assert.strictEqual(
await time_range_label.getAttribute("aria-label"),
"2019年1月2日 – 今日",
);
})();
ヘッドレスで動かない件
これでテストも動くようになったのですが、Chromeをヘッドレスで動かそうとすると動かないことが分かりました。
調べてみたところ、サポート外だそうです。
仕方ないので他の方法を探してXvfbを使えばいいと知って試そうと思ったのですが・・・
CircleCIのDockerイメージに最初からXvfbが入っているようです。
何も設定変更せずに動きました。
参考資料
-
Testing Chrome Extensions with Selenium - DEV Community 👩💻👨💻
- Python + Selenium WebDriverを使ったChrome拡張のテスト。
-
GoogleChrome/puppeteer: Headless Chrome Node API
- 今回は使わなかったけど、便利そうなAPI
-
Faster async functions and promises · V8
- Node.js 12から使えるようになった
async-stack-traces
についての記述。
- Node.js 12から使えるようになった