はじめに
この記事はQiita夏祭り2020のイベントの1つ、パソナテックさんが開催している「〇〇(言語)のみを使って、今△△(アプリ)を作るとしたら」のテーマで投稿しています。
いやぁ良いお題ですよね。エンジニア心揺さぶられるし、景品も豪華だし、何よりちょっとワクワクしたので参加させていただきました。
今回は「ShellでSeleniumを作る ~Shellnium~」というお題でやっていきます。下記はShellniumで遊んでいるGIFです。
ブラウザの自動操作の模様をiTermの背景として鑑賞することができます。意味不明さがすごいですね。(透明度をいじってるとかではありません)
いろんな言語でSeleniumは扱える
Seleniumは皆さん御存知の通り、ブラウザの自動操作をするツールですね。
SeleniumはWebDriverを介してブラウザの自動操作を行っていくもので、php
, python
, javascript
など様々な言語のWebDriverが存在します。
例えばphp
でやるならfacebook-webdriverを使ってこんな感じですね。
// chromedriverの起動
$driver = RemoteWebDriver::create($host, DesiredCapabilities::chrome());
// 指定URLへ遷移
$driver->get('https://www.google.co.jp/');
javascript
だったらselenium-webdriverを使ってこんな感じ。
// chromedriverの起動
const capabilities = Capabilities.chrome()
const driver = await new Builder().withCapabilities(capabilities).build()
// 指定URLへ遷移
await driver.get('https://www.google.co.jp/')
大体どの言語でも似たような形で書けるので、なんらかの言語でSeleniumを扱ったことがあるならば他の言語でもスイスイと書けると思います。
そう、ShellScript以外ならね。
WebDriverにBashやZshがないのはおかしい
ShellScript用のWebDriverは見た限りどうも作られていないようなので、「ShellでサクッとSelenium試したい」という方には敷居が高いものになります。
npm install
やらcomposer install
やら一々しないと環境が整わない。
こちとらもっと手軽にブラウザ自動操作ができると思ったのに話が違うじゃないか、となってしまうわけですね。
Shell芸人のためのSelenium 〜Shellnium〜
ということでShellScriptで手軽にブラウザ自動操作をしたい方へおくります。
下記のようにブラウザ自動操作をShellScriptだけで実現できます。必要なものはbash
またはzsh
だけ。
Shellでブラウザの自動操作を行う方法
ブラウザの自動操作の流れは下記のような流れです。ブラウザ(のdriver)に対してcurlでJSONをPOSTしまくるだけです。
- ブラウザ起動
- URL遷移
- キーボード入力
を1つずつ実行することで感覚を掴みましょう。
0. chromedriverを起動
まずはchromedriver
を起動しておきましょう。chromedriver
がない方はbrew install chromedriver
で一撃です。(もしくは公式HPから直接ダウンロード)
$ chromedriver
Starting ChromeDriver 2.40.565386 (45a059dc425e08165f9a10324bd1380cc13ca363) on port 9515
Only local connections are allowed.
これでhttp://localhost:9515
でchromedriverが待ち受ける状態になります。
1. ブラウザの起動&セッションIDの取得
では早速Shellからブラウザを起動してみましょう。
curl -X POST -H 'Content-Type: application/json' \
-d '{"desiredCapabilities": { "browserName": "chrome" }}' \
http://localhost:9515/session
これだけです、簡単ですね。実行してみるとブラウザが起動すると思います。
今後このブラウザに命令を送るために、セッションを指定して命令を送る必要があるのでセッションIDを取得しておきます。
セッションIDは上記のcurlのレスポンスでJSONが返ってくるので、その中にあるsessionId
を抽出します。せっかくShellScriptを使っているのでjq
を使うと楽ですね。もちろんgrep
等で取り出してもGood。
$ curl ... http://localhost:9515/session | jq -r '.sessionId'
# セッションIDが取得できる
e1c3d895e7c7d42383c110e5e4cd7bbf
2. 指定のURLに遷移する
curl -X POST -H 'Content-Type: application/json' \
-d '{"url":"https://google.co.jp"}' http://localhost:9515/session/${SESSION_ID}/url
実行するとGoogleのTOPページに遷移すると思います。ここで指定する${SESSION_ID}
は先程取得したものですね。
3. 要素の取得&キーボード入力
それではSeleniumっぽいことをしていきましょう。まずはname属性から要素を取得する、findElementByName()
のようなことをしていきます。
curl -X POST -H 'Content-Type: application/json' \
-d '{"using":"name", "value": "q"}' http://localhost:9515/session/${SESSION_ID}/element \
| jq -r '.value.ELEMENT'
{"using":"プロパティ名", "value": "値"}
で要素を取得することができます。Googleの検索ボックスはname="q"
となっているので{"using":"name", "value": "q"}
のように指定します。
上記のcurlを実行すると検索ボックスの要素IDが取得できます。
$ curl ... http://localhost:9515/session/${SESSION_ID}/element | jq -r '.value.ELEMENT'
# 要素IDが取得できる
0.5757440182130869-1
要素のクリック、キーボード入力、フォーカスなど、要素を利用してなんらかの動作をするには「要素ID」が必要になってきます。
キーボード入力の場合は下記のような形
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"value": ["タピオカ\n"]}' http://localhost:9515/session/${SESSION_ID}/element/${elementId}/value
注意したいのは配列形式で入力する文字列を渡してあげるところですね。また、\n
を末尾に加えることでENTERの役割をしてくれます。
Seleniumっぽい動きになる
上記を1つのShellScriptにまとめてあげて実行すればもう完全にSeleniumです。
#!/usr/bin/env bash
main() {
# Googleのトップページに遷移
navigate_to 'https://google.co.jp'
# 検索ボックスの要素を取得
local searchBox=$(find_element 'name' 'q')
# 検索ボックスに入力&検索実行
send_keys $searchBox "タピオカ\n"
}
get_session_id() {
curl -s -X POST -H 'Content-Type: application/json' \
-d '{
"desiredCapabilities": {
"browserName": "chrome"
}
}' \
${ROOT}/session | jq -r '.sessionId'
}
ROOT=http://localhost:9515
SESSION_ID=$(get_session_id)
BASE_URL=${ROOT}/session/${SESSION_ID}
navigate_to() {
local url=$1
curl -s -X POST -H 'Content-Type: application/json' -d '{"url":"'${url}'"}' ${BASE_URL}/url
}
find_element() {
local property=$1
local value=$2
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"using":"'$property'", "value": "'$value'"}' ${BASE_URL}/element | jq -r '.value.ELEMENT'
}
send_keys() {
local elementId=$1
local value=$2
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"value": ["'$value'"]}' ${BASE_URL}/element/${elementId}/value >/dev/null
}
main
とっても簡単ですね。curl
でJSONを投げる箇所を小さな関数にすることで、main関数の中身だけみるともう完全にWebDriverの記述になっています。
関数にまとめた部分はselenium.sh
などにまとめておいて下記のようにsource
してあげればとってもシンプルな見た目になります。
#!/usr/bin/env bash
source ./selenium.sh
main() {
# Googleのトップページに遷移
navigate_to 'https://google.co.jp'
# 検索ボックスの要素を取得
local searchBox=$(find_element 'name' 'q')
# 検索ボックスに入力&検索実行
send_keys $searchBox "タピオカ\n"
}
main
ここからがShellScriptの本領発揮
以上でShellScript版のWebDriverっぽいものが出来上がりです。あとは関数を増やしていけばよりWebDriverに近いものが出来上がるでしょう。
...ただこんなところで終われません。せっかくShellScriptで書いているのだからもっとShellらしいことがしたい。
Shellだからといって命令を送るだけじゃなく、もっとビジュアル的に「ああこいつやべえ、、、」と思われたい。ShellScriptにすればありとあらゆるインターフェースを1つにまとめられる、ここにShellScriptの楽しみがある。私はそう思うんですよね。
iTermとSeleniumの夢のコラボを実現する
ということでiTermとSeleniumをShellScriptでくっつけてみました。
美しい...iTermで作業をしながら裏ではブラウザの自動操作が行われている。
作業をしながらタピオカの情報が得られるのでまるで分身の術を使ったかのような作業効率。
ソースはちょっと長いのでGithubにあげておきますが、main関数だけ載せると下記になります。
main() {
screen_clear
navigate_to 'https://google.co.jp'
local searchBox=$(find_element 'name' 'q')
set_background_image
# 入力している感を出すため1文字ずつ入力
local words=('タ' 'ピ' 'オ' 'カ')
for word in ${words[@]}; do
send_keys $searchBox $word
set_background_image
done
# ENTER
send_keys $searchBox '\n'
set_background_image
for i in `seq 0 100 1000`; do
exec_sync_script "window.scroll(0,$i)"
set_background_image
done
}
終わり
今回のは割とタイトル詐欺というか、Shell(Script)でSelenium作ってはいないし厳密にはWebDriverですらない。
Seleniumの仕組みを知っている人にはおわかりだと思いますが、WebDriverが中でやっていることをShellScriptで直接実行しているだけですね。
Shellにはクラスやインターフェースなど、いわゆるオブジェクト指向的な概念が存在しないため、ブラウザの差異を吸収するといったことがなかなか難しい。
今回はとりあえずchromeに絞ってやってみましたが、他のブラウザもサポートするとなると中々泥臭くなりそうです。
それでもShellScriptはやっぱり手軽だし、環境構築がほぼないし、なにより楽しいからやめられないんですよね。
研修とかで「APIを叩いてみよう」みたいなときに、お天気情報をAPIでGET!じゃなくて味変したいときに、今回のブラウザ操作をやってみても面白いかもしれませんね ^^。