LoginSignup
116
85

More than 1 year has passed since last update.

ShellでSeleniumを作る ~Shellnium~

Last updated at Posted at 2020-06-28

はじめに

この記事はQiita夏祭り2020のイベントの1つ、パソナテックさんが開催している「〇〇(言語)のみを使って、今△△(アプリ)を作るとしたら」のテーマで投稿しています。

いやぁ良いお題ですよね。エンジニア心揺さぶられるし、景品も豪華だし、何よりちょっとワクワクしたので参加させていただきました。

今回は「ShellでSeleniumを作る ~Shellnium~」というお題でやっていきます。下記はShellniumで遊んでいるGIFです。
ブラウザの自動操作の模様をiTermの背景として鑑賞することができます。意味不明さがすごいですね。(透明度をいじってるとかではありません)

demo2.gif

いろんな言語で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だけ。

demo3.gif

Shellでブラウザの自動操作を行う方法

ブラウザの自動操作の流れは下記のような流れです。ブラウザ(のdriver)に対してcurlでJSONをPOSTしまくるだけです。

api.png

  1. ブラウザ起動
  2. URL遷移
  3. キーボード入力

を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"}のように指定します。

google.png

上記の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です。

demo3.gif

test.sh
#!/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してあげればとってもシンプルな見た目になります。

test.sh
#!/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でくっつけてみました。

demo2.gif

美しい...iTermで作業をしながら裏ではブラウザの自動操作が行われている。
作業をしながらタピオカの情報が得られるのでまるで分身の術を使ったかのような作業効率。

ソースはちょっと長いのでGithubにあげておきますが、main関数だけ載せると下記になります。

shellnium.sh
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!じゃなくて味変したいときに、今回のブラウザ操作をやってみても面白いかもしれませんね ^^。

参考

116
85
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
116
85