読者です 読者をやめる 読者になる 読者になる

へっぽこびんぼう野郎のnewbie日記

けろけーろ(´・ω・`)!

3秒でわかるSeleniumのつかいかた(Python・ちょっとDjango)

2016/09/22 追記

3秒でわかるようになってなかったので追記。

Seleniumとはなにか

自動でブラウザ動かせれるやつ。テストとかにつかえる。

Seleniumのインストール方法

http://www.seleniumhq.org/download/

Chromeでやる場合、chromedriverをダウンロードする

https://sites.google.com/a/chromium.org/chromedriver/downloads

$ python
>>> from selenium import webdriver
>>> driver = webdriver.Chrome('chromedriverまでのパスをここに書いてね!')

どうやって自動で動かすのか?

概要

DOMの要素をとって、それを動かす
JavaScriptでDOMの要素を取得してうんちゃら〜って書くみたいに
PythonでDOMの要素を取得してうんちゃら〜って書く。あとブラウザの機能も使えるみたいな感じ。

7. WebDriver API — Selenium Python Bindings 2 documentation

HTMLタグのname属性を指定して要素を取得してる。
element = driver.find_element_by_name('hoge')
element.click()

(find_elementsという複数形じゃなくても使える点に注意)

CSSXPathで取る方法もある。
menu = driver.find_element_by_css_selector(".nav")

注意点

(以下参照)

2016/02/22 追記

ぼくは『書くのが結構たいへん + テストが遅くなる + 仕様が頻繁に変わる(1ヶ月毎にリリースするアジャイル開発をしてる)』が理由で使わなくなった。
画面でのテストは仕様を把握している人間がたまにパッと、手動でやるくらいがいいんじゃないかという感じ

確かに人間が行なうような動作をそのまま記述することはできる。ただ、人間だったらUIが変更されたときに「あ、ボタンここになったのか」と考えてすぐにやり方変えれるけど、動作が記述されてると「ボタンどこ?→エラー」ってなる。UIの変更は日常茶飯事なので、わざとUIの変更するときにテストを大幅に変更するのが何回もでてくると、辛くなってきて最終的に死にたくなる。

あとレイアウトの微妙なズレは検知できない。CSSがおかしなことになって位置がちょっとズレて、人間が見ると「なんかこのボタンの位置気持ち悪くなったな」みたいに感じられるようなことを平気でスルーする。
「人間の使い心地は人間がテストしないとわからない」という感じがする。

なので品質を保つためには、テスターを雇うという選択肢も十分考えられそう。

それからSelenium結合テストのためのデータを作るのが死ぬほど大変だったり、
マウスの動きなどを自由自在に表現するのが大変

別に画面でテストしなくても、サーバ側でHTTP POSTやGETのテストはふつうにできるので、そっちの方が良さげだった
画面のテストは、JSのテストフレームワークを使ったほうがうまくいきそう(JSのテストの書き方がよくわかっていないが)

自動で動いてるのを見て「おぉすげーーーーー!」ってなるだけな感じ

たぶんそれだけ。
一般ピーポーに『うぉおおおおすげえええええ! 神だあああ』と手軽に思われる方法の1つっていう感じある。

結局テストをがんばって書いたのに、すぐに仕様が変わってテストが意味をなさなくなり、メンテナンスコストも発生するし、特に品質向上に影響を及ぼさなかったので
テストとして使い物にならなかったというのが感想で、PythonSeleniumやるな感がすごい(あくまでサーバサイド側から見た印象)
Pythonでやる場合はpyqueryはBeatifulSoupを使って、エレメントがあるかないかのテストだけでいいんじゃないかと思う
2016/07/26 追記: これはサーバーサイドでHTMLレンダリングする場合に限る。

JSではわからないけど、アニメーションだとか非同期のテストって、工数の割に恩恵を受けられない気がして、あんまりテスト書きたくない。
『なんでテスト通らないんだ! 実際には通っているのに!』→「あぁ非同期ね……」みたいなことが多くて、テストを書いていたおかげで助かったという経験が少ない。
もっと単純なテストが落ちて『アッー ソウダッタァー シマッタァー!!』となることの方が多い

てかテストコードなんていくら書いても、結局バグなんていくらでも出るしな(減りはするけど)

リファクタリングのときに安心するためだとか、毎回ちゃんとした結果になってるか確認するのだりぃとか、これほんまに大丈夫なん?みたいなときにテストコードを書くべきであって、
男は黙ってTDDみたいなのはやめたほうがいいんじゃないかと思った
(テストはそもそも書くのがだるい。テスト増えるとCIで重くてイライラするし。長期的に見ても短期的に見ても開発効率や品質が上がらないテストは、書かなくてもいいんじゃねーのと思うって言ったらきっといろんな人からぶん殴られるんだろうな。)

テストが大事なんじゃない。品質と効率が大事なんだ!ということは心にとめておきたい。

Seleniumってなんだ?

ブラウザをプログラミング言語を使って操作できるツール。スクレイピングとは違う。
スクレイピングはファイルをパースするやつだけど、
Seleniumは、ブラウザに「このように動作してくれたまえ」と命令するやつ。

おもに「ちゃんとブラウザ動いて、正常な動作してるん?」という確認をするためのテストにつかう
まぁべつにスクレイピングとしてもつかえるけど、スクレイピングしたいならhttplib2とpyqueryやBeautifulsoupの組み合わせのほうが楽
JavaScript超実行するサイトならこっちのほうがいいかもしれん。しらん。

インストール方法

とてもかんたんなので割愛。

Firefoxをつかうばあい

デフォルトで入っている。がんばる。

Chromeブラウザを使う場合

まず、chromedriverをダウンロードしてしかるべき場所に置く。

Chromeブラウザのたちあげかた

単純に立ち上げるばあい
$ python
>>> from selenium import webdriver
>>> driver = webdriver.Chrome('<i>chromedriverまでのパス</i>')
>>> driver.get('http://www.google.com')
オプションとかつけたいとき

別にoptionはいらないけど、テストするときはなにかと設定を変えるはずなので、必要に応じて設定する。

↓設定の場所についてはここ
www.chromium.org

from selenium import webdriver

def get_chrome_browser():
    chrome_options = webdriver.ChromeOptions()
    # ChromeのUser Data Directory/Preferencesを変更している
    prefs = {"download.default_directory": './functional_tests/DownloadsByChrome'}
    chrome_options.add_experimental_option("prefs", prefs)
    return webdriver.Chrome(
        executable_path='./functional_tests/drivers/chromedriver',
        chrome_options=chrome_options
    )

ぼくは、Djangoで機能テストをしたかったから、
プロジェクトのルートディレクトリの下にfunctional_testsみたいなディレクトリをつくって、
INSTALLED_APPSに付け加えた。
その後driversというディレクトリもつくって、その中にchromedriverをいれた

最初に言った「しかるべき場所」というのがココのこと。

DOMの要素をとる場合

7. WebDriver API — Selenium Python Bindings 2 documentation
driver.find_element_by_name('hoge')とかでとる。

cssセレクタxpathもつかえる。

xpath
driver.find_element_by_xpath("//*[contains(text(), '%s')]" % 'moge')が重宝する。

これで<span>moge</span>とかがとれる。

とった要素はWebElementオブジェクトで、こいつが、
click()とかsend_keys()ってメソッドを持っているから
これを呼び出してあげれば、クリックしたことや、文字入力したことになる。

きほんてきに、Seleniumはこれを繰り返すだけ。

jQueryとかCSSとか普段やってるひとにはかんたん!

DOMの要素をとるにあたっての注意点

DOMの要素を指定するときは、画面で見えていないと『そんなのないよ!』って怒られるので、
『スクロールしないと見えない・クリックしないと見えないやつ』などは、見えるようになってから指定すること。
(これがかなりめんどくさい)

見えるようになるのを待つのが、とってもたいへん

世の中には非同期処理が多いから、処理したのに、まだ反映されて無くて、結果的に見えてないことにされた……
みたいなことが多い。
なので、ある処理が終わるまで「待つ」みたいな処理が必要になる。
代表的なのはImplicitlyWaittime.sleepで、楽。ただし失敗することも多い。

こうすると、ページ移動してタイトルが変わるまで待つことができる。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def wait_until_title_is(driver, wait_title):
    return WebDriverWait(driver, 30).until(
            EC.title_is(wait_title)
    )

ECの属性はいっぱいあるから、好きなのを選んでつかえばいいとおもう。

7. WebDriver API — Selenium Python Bindings 2 documentation


好きなのがなかったら

def hoge(driver):
 return driver.find_element_by_id('SSSS') if driver.find_element_by_id('moge') else False

とかで、きみだけのexpected_conditionをつくろう!!

基本的にブラウザでできることはだいたいできる。

文字入力
element.send_keys('もじをにゅうりょく!')

右クリックとかクリック系操作はActionChain
7. WebDriver API — Selenium Python Bindings 2 documentation

クッキーを消したり、JavaScriptを実行したりもできる。
↓これは、Accept-Languageのいちばん上を取得
lang = driver.execute_script("return window.navigator.language")

はまりどころ

  • 「あるはずなのにない・たまに失敗する」という、wait系。だいたい処理が追い付いていないせい。
  • iframeの中だった! ↓で解決する
driver.switch_to.frame(driver.find_element_by_tag_name('iframe'))
driver.find_element_by_css_selector('tr').click()
driver.switch_to.default_content()

感想

  • 要素調査めんどくさい
  • Selenium IDEはあんまり性能よくないかんじ
  • つくったあと自動でテスト動くのすごい
  • idちゃんとつけておかないとめんどくさい
  • API、そんなに量ないから全部読んでからやると超らくだと思う