HOME > Document > Selenium Tips

Selenium TIPS


Webアプリケーション テスト フレームワーク Selenium を効果的に活用するための Tips を公開しています。

複数Testcaseをパラレル実行するには

SauceLabsオンデマンドサービスではパラレルに複数Testcaseを実行することが可能です。

以下は、パラレル実行するためのサンプルコードgithubリンクです。

SauceLabsパラレル実行サンプル集Github TOP

Clojure + Javaサンプルコード

Java JUnitサンプルコード

Java TestNGサンプルコード

Perlサンプルコード

Pythonサンプルコード

Locatorを楽に管理する

アジャイルの世界でよく言われているのが絶対にビルドを壊すなですが、字義通りに解釈すると実際にはアジリティの妨げになってしまいます。 時には、ビルドを壊したくなるはずです。例えば、開発の状況が変化していたり、進展していたりする場合です。

もっと良い言い方は、ビルドを壊れたままにしておくなです。同様のことはSeleniumとエレメントロケータについても言えます。 壊れるだろうし、定期的に壊したくなる。こうした状況で考えておかないといけない点は、

-壊れている状態から修正するのにどの程度時間が掛かるか ? これは非常に短時間であるべきです。
-修正するのにどの程度改修しなければならないか
- 理想的には、エレメントに対して一行だけの改修で、全てのコードベースについてです。

問題はどう断続的に起こる破損を修正するかです。
一番簡単なやり方はidを使うことです。このやり方の最も良い点は、スタティックなidアトリビュートが関連するhtml要素に紐付けられることです。
将来、どの要素が自動化に必要になるのか分からないので、全ての要素にidを紐付けるのがベストです。 WSC標準ではidは単一のページ上でユニークでなければならないとしていますので、スクリプトはいつもページ上の要素を一意的に知ることが可能です。

この方法は、XPathやCSSセレクターを使って位置に係わらず要素を特定することから来る問題を回避することはできますが、id変更問題を解決する ことはできません。
これを解決するには、開発と自動化チーム間での決まりとコミュニケーションが必要になります。
この為に、私が係わったことのある地理的に分散したチームでの開発では次の二つのポリシーで運用しました。

1. だれでも未だidをつけていない要素にidを振ることが出来る 2. もしidを変更したなら、開発と自動化チームの両方に変更を連絡しなければならない

もちろん、変更によって数百ものスクリプトをアップデートしなければならない時もあり、変更されたことの通知を受けるのは苦い薬を飲むようなものです。
もっと良い方法はロケータの定義をメインのテストケースから分離することです。

Selenium IDE


ロケータとスクリプトを分離することは、Selenium IDEのユーザエクステンション(user-extensions)を使って実現可能です。
Sauce Labs Parametrizing Selenese testsという以前のブログで、エクステンションの使い方を説明していますが、Valueコラムを提供しているというより、 Targetコラムを提供していると言えます。

1. storevars["registrationFirstname"] = "first";
2. storevars["registrationLastname"] = "last";
3. storevars["registrationSubmit"] = "register";
Registration formに関連するスクリプトは下記のようになります。

open / registration
type ${registrationFirstname} fred
type ${registrationFirstname} flintstone
click ${registrationSubmit
Seleneseスクリプトに戻ってアップデートするのは苦痛ですが、ユーザエクステンションを使ってロケータを扱うことで、 外部のJSファイルをアップデートするようにシンプルになります。

Selenium RC

ほとんどのSelenium RCの言語はネイティブに外部ファイルからランタイムのプロパティを読み込む機能を持っています。
・	Java ? Properties
・	Python ? ConfigParser, 若しくは単なるpythonディクショナリー
・	Ruby ? YAML
・	C# - App.Config
もしJavaを使っている場合、下記のコードを含んだlocators.propertiesファイルを作成する必要があります。
# uses the id locator
registration.firstname=firstname
# xpath
registration.lastname=//form[@name='lastname']/input[@type='text'][2]
# css
registration.submit=css=form submit
下記が非常にシンプルなregistration formに関連するJavaクラスで、 ロケータ用の外部propertiesファイル (上記)を使っています。

import java.io.InputStream;
import java.util.Properties;

import org.junit.Before;
import org.junit.Test;
import org.junit.After;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

public class TestRegistration.
{
    private Selenium selenium;
    public static Properties locators = new Properties();

    @Before
    public void setUp() throws Exception
    {
        InputStream is = 
          TestRegistration.class.getResourceAsStream
           ("/locators.properties");
        locators.load(is);

        selenium 
         = new DefaultSelenium(
            "localhost", 4444, "*firefox", "http://localhost:3000");
        selenium.start();
        selenium.setTimeout("90000");
        selenium.windowMaximize();
    }

    @Test
    public void testVisibleElement() throws Exception
    {
        selenium.open("/registration");
        selenium.waitForPageToLoad("30000");
        selenium.type(locators.getProperty(
        	"registration.firstname"), "fred");
        selenium.type(locators.getProperty(
        	"registration.lastname"), "flintstone");
        selenium.click(locators.getProperty(
        	"registration.firstname");
        selenium.waitForPageToLoad("30000");
    }

    @After
    public void tearDown() throws Exception
    {
        selenium.stop();
    }
}
テストの中にどこにもハードコードしたロケータストリングが無いことに気が付かれるでしょう。 つまり、スクリプトを変更することなく、ロケータを変更することができます。 また、もし一貫性があり、よく考えられた名前のコンバージョンをproperty名として使われているなら、実質的にスクリプトの可読性を 高めることができます。スクリプトの全体を通して、同じlocators.propertiesを使うことによって、変更を一箇所にまとめられます。
行えば全てのスクリプトに反映してくれます。 また、それまでにidが付与されていない要素にidが付与されたときに、そうしたidをより構造に依存した XPath, CSSの様な手法に簡単に変換することができます。 更に、ファイルをスキャンして瞬時に、XPathがまだ使われている場所を特定することができます。 ちなみに、Sauce Labsが現在推奨している手法はCSSロケータを使うことであり、パフォーマンスを良くすることが可能です。 converting from XPath to CSS を参照下さい。

Pageオブジェクト

Pageオブジェクトは一つのクラス (或いは複数のクラス)としてウェブページを表現する方法で、Seleniumにオブジェクト指向のテクニックとパターンを 融合した形になります。 ほとんどのPageオブジェクトのパターンは実際のPageオブジェクトからより高いレベルの親クラスにロケータを抽象化します。 しかし、その場合でも、ロケータはリコンパイルをしないでロケータの変更が出来るように外部のファイルで管理すべきです。 Pageオブジェクトについては今回のスコープを越えていますが、入門的な簡単なPythonの実行パターンは Page Objects in Python ? Automating Page Checking without Brittlenessを参照下さい。

次のステップ

スクリプトとロケータを分けることは、自動化を運用する為に必要なチームの時間を削減する為の重要なステップになります。 しかし、このアイディアに跳び付いて貴重な時間を全てのテストスイートに使ってしまうと、新たに追加される機能のテスト開発が更に遅れてしまいます。 チーム全員でスクリプトとロケータを分離することをまず決め、その後はこのモデルでスクリプトを作成し、既存のコードに問題がある時に、ロケータストリング の抜き出しをバックポートする方が良いやり方です。 当面はハイブリッドのスクリプトですが、結果としてより早く対応出来るアプローチです。 (オリジナルソース http://saucelabs.com/blog/index.php/2010/09/how-to-minimize-the-pain-of-locator-breakage/)

Selenium の CSS Locators は、なぜ jQuery にするべきなのか

ご存じない方もいるかも知れませんが、しばらく前にJason HugginsはSelenium1のCSSロケータエンジンをCSSQueryからSizzle (jQueryのCSSセレクターライブラリー)に変更しました。

この変更はユーザにとって影響がないように聞こえるかもしれませんが、そうではありません。 このブログポストで、変更した理由とこの変更に伴ったクールな機能の全てを説明したいと思います。

なぜSizzleはすごいか?


SizzleはjQueryのセレクターエンジンで、おおきな意味があります。 jQueryはjavascriptのライブラリーで 世界中のウェブサイトの約30%で使われています。

その為、Sizzleは信じられないくらい使用されており、またテストにも使われています。
Sizzleのコードは平均的にウェブサイトの3つに1つのサイトで使われており、 コードベースにはgithub上で千人以上のフォロワーがいて 80以上のフォークがあります。バグを発見し、パフォーマンスを改善するのに十分な人がいるわけです。

しかし、Sizzleは単により強固で高速なCSSの実行機能を持っているのではなく、 無駄なCSSセレクターを取り除き、 テストを書いているエンジニアに本当に有益な機能を追加したものなのです。

すぐに使えるSizzleの追加機能


ドキュメントにあるように、Sizzleは実質的に全ての CSS3セレクターを実行するだけでなく、拡張し追加を行っています。 結果として、Seleniumテストを書く為に非常に有用になっています。

:not


:notセレクターは似ているが全く同じでは無い要素をフィルターするときに使います。下記のような状況を想定しましょう。


<a href="#meh" class="confirmation_link hidden">Confirm</a>
... lots of html ...
<a href="#bleh" class="confirmation_link">Confirm</a>

ご覧のように、テストしようとするページに二つのリンクがあります。両方のリンクには同じテキストがある為、link=Confirmではうまく行きません。 というのも、両方共に同じクラスでしかも特定するためのidが無いからです。こうした状況で、:notセレクターが最強のツールになります。
下記のロケータを書くぐらい簡単です。


selenium.click("css=a.confirmation_link:not(.hidden)")

これで、複雑なフィルターが:notの中にいれてしまえます。Sizzleに感謝!! 下記は追加のサンプルです。


selenium.click("css=.confirmation_link:not(div)")

:contains


:containsはすでにcssQueryとふるいバージョンのSeleniumで使われていますが、内部テキストに依存している要素をフィルターする 為に追加します。

下記がサンプルです。


selenium.click("css=input[type=button]:not(p#not_this_input > input)")

:eq/:nth


このセレクターは発生する全てを見つける処理を行い、その後、リストのn番目をフィルターします。
もし、:nth-of-typeとか:nth-child等の紛らわしいフィルターを使われているのであれば、このセレクターはその代わりになるかもしれません。


selenium.click("css=table a:contains(Change password):nth(5)")

:header


このセレクターで、どんなheader要素でも見つけることができます。つまり、h1, h2, h3, h4, h5, h6というようにです。
すごい! これで、開発エンジニアがページに使ったheaderのタイプを知る必要がなくなります。

特定のテキストを含むheaderのアサートに、もってこいです。


assert selenium.is_element_present(":header:contains(Users Admin)")

Form helpers


更に、Sizzleはいくつかのform要素のショートカットをサポートしていますので、要素がtextarea要素かinput要素かを調べるのに時間を セーブできます。
また、input[type-checkbox]といいた醜いロケータを書くことがなくなります。

- :input: 全てのinput要素を検索します (textarea, selects, buttonsを含む)
- :text, :checkbox, file, password, :submit, :image, :reset, :button: 特定のinputタイプと共にinput要素を検索します。 (:buttonはbutton要素を検索します)

上述した内容が私は重要とおもいますが、詳細についてはSizzle’s wiki を参照してください。 Sauce Labsは CSS selectors quick referenceをリリースしています。
もし必要であれば、テストを書いている間、プリントしたものを机の横にでも貼っておいてください。

皆さんがjQuery Seleniumセレクターを書けるようになって、時間のセーブと頭痛から開放されたと思います。

もちろん、これらはSauce オンデマンド, bwosers in the cloud service でサポート済みです。
無償でトライアルができるので使ってみてください。

(オリジナルソース http://saucelabs.com/blog/index.php/tag/css-selectors/)

CSS セレクタ リファレンス

SeleniumのCSSロケータ方式は、現在Sizzle JS Libraryが使われています。Seleniumであまり使われないものもあり
ますが、SizzleはW3CのCSSセレクタの多くを実装しています。

理想としては、ページ内アイテムをユニークIDで参照することです。

もし、これができない場合には、XPath や CSS によって参照が可能になります。

しかしXPathはInternet Explorerの場合、パフォーマンスの問題を抱えています。
XPathをCSSに置き換えることで、テスト実行時間は短縮されます。

ロケーション特定には、XPath,CSSが混在される場合もあり、これはニーズに依存します。

すべてのXPathをCSSに変更することは、たとえ実行時間が非常に長くても得策ではないかも知れません。

また、XPathとCSS間では各機能のダイレクトマッピングはなく、例えばgetXPathCountに相当するものは存在しなかったり
します。

XPathと同様にCSSも構造化ベースですので、できる限り特定できるようにすることを強く推奨します。

それによってページ変更の際に、非常に神経質になることのないようにするためです。

すべてのCSSロケータは、css= で始まります。これによってSeleniumに対して、これは id ではなく CSS であることを
知らせます。

以下のリファレンスを参考にしてください。


Basics

Element
? css=input
Element id
? css=input#inputUser
Element class
? css=input.plain

Relativity

Child
? css=form > input.formSubmit
Descendant
? css=body input#inputUse
Contiguous
? css=input#inputUser + input#inputPass
Shared Parent
? css=input#inputUser ~ input#inputPass

Position

First
? css=ul#reasons li:first
Last
? css=ul#reasons li:last
Specific
? css=ul#reasons li:nth(2)

Attributes

Single
? css=input[value=lacrosse]
Chained
? css=input[value=swim][type=checkbox]
Starts-with
? css=input[value^=ballroom]
End-with
? css=input[value$=field]
Contains
? css=input[value*=room]
(オリジナルソース http://saucelabs.com/downloads/documentation/css-selector-quickreference.pdf)

TOPへ

Selenium 1 から Selenium 2 へのマイグレーション

このブログはHearSay SocailのソフトウェアエンジニアであるRoger Huが書いたものです。

HearSay Socailでは、テスト環境をSelenium2にアップグレードしました。

Selenium1から切替えた理由はパフォーマンスが2倍から4倍という大幅な改善ができるだろうという確信があったからです。
アップグレードを行う中で注意しておいた方が良い事を経験しましので、これからSelenium2にアップグレードする人達の為にも我々が経験した事を伝えておいた方が良いと思いました。

・ Selenium2は、FirefoxのNPAPIプラグインであれ、IE用のDLLモジュールであれ、ブラウザそのものに一番合ったように作り替えられているので、 非常に高速になっている事を確認しました。特に、速度に問題のあるJavaScriptエンジンを使っているIEに効果があります。
Selenium2の新しいアーキテクチャでは、JavaScriptベースのSelenium1から受ける制約の為に必要だったセキュリティのオプションを変更することなく、 Internet Exploreへのテストを行うことが可能になりより使い易くなっています。

・ Selenium2ではブラウザ上でのユーザの動作をより忠実に再現しています。実際にクリックされたDOMエレメントはマウスイベントのX/Y軸によって定義されています。
従って、もし他のエレメントの後ろにある場合や、それが障害になっている場合、一番上にあるエレメントが常にクリックされることになる為、 SeleniumサーバからElementNotVisibleExceptionのエラーを貰うことになります。
Selenium1ではこの制約はなかったので、Selenium2でテストを書き直すときには注意が必要です。
(Hearsay Socialでは、Django Webフレームワークと人気のあるdjango-debug-toolbarを使っていますが、 ツールバーがpop-upオーバーレイとなる為Seleniumテストの間はdisable にしなければなりません。)

・ 新しいSelenium2のWebDriver-APIは開発エンジニアが習得し易いことがわかりました。
Selenium2のドキュメントは未だ充実しておらず、特に最新のPythonバインディングに対して少ない為、どんなAPIコマンドがあるかを調べるには現状ではソースコードを読むことがベストです。
(Hearsay Socialの場合、remote/webdriver.pyremote/webelement.pyのコードです)
Javaの開発者の場合はWebDriverBackedSeleniumにアクセスすれば、WebDriverベースのAPIを利用しながらSelenium1の既存のコードを使うことが可能です。
しかし、同様のサポートはPythonには無い為躊躇はありましたが、思い切って我々のテストケースのほとんどをSelenium2にリファクターしました。
Webdriver/remote/webelement.py:

@property
   def tag_name(self):
       “””Gets this element’s tagName property.”””
       return self._execute(Command.GET_ELEMENT_TAG_NAME)[‘value’]

   @property
   def text(self):
       “””Gets the text of the elment.”””
       return.self._execute(Command.GET_ELEMENT_TEXT)[‘value’]

   def click(self)
       “””Clicks the element.”””
       self._execute(Command.CLICK_ELEMENT)

   def submit(self)
       “””Submits a form.”””
       Self._execute(Command.SUBMIT_ELEMENT)

   def clear(self)
       “””Clears the text if it’s a tst entry element.”””
       Self._execute(Command.CLEAR_ELEMENT)
サーバサイドについては、クライアントAPIがどのようにリモートコマンドを送っているかを確認することが重要で、 SeleniumのwikiにあるJasonWireProtocolのドキュメントを参照できます。 また、Sauce Labsのログではクライアントが実際にどんなコマンドを出しているのかを確認することが出来ます。



?Selenium2を試すうちに、Seleniumサーバをダウンロードしてローカルで新しい WebDriver APIのテストをするのはSelenium1と比べ非常に簡単であることが解りました。
ローカルにサーバを置くことで、Sauce Labsのアカウントを使った時に起こるタイムアウトを気にする必要がありませんし、気軽に様々なコマンドを色々試してみることが出来ます。
もし、ローカルなサーバを使って外部にあるWebサーバに対してブラウザの動作の検証をする場合、リバースSSHトンネル を用意することが必要で、デバッガのブレークポイントやAPIのバインディングを試したりして Selenium2のAPIの実験が出来ます。但し、最終的にはクラウド上でバーチャルマシンをホスティングしている Sauce Labsのサービスを使ってブラウザのテストを行うのが良いでしょう。

?アプリケーションのデバッグにFirebugを使いたいユーザにSeleniium2はFirefox profilesをインジェクトする機能を用意しています。このプラグインエクステンションを使ってFirefoxプロファイルが作成できますし、 Selenim2はリモートホストからダウンロードされるBase64のzipエンコードされたプロファイル用のAPIを持っています。但し、このやり方はローカルにSeleniumサーバを立てた時にだけ有効です。
(Sauce Labsを使った場合にはVideo outputにしかアクセス出来ません。)

・Selenium2ではAPIの仕様が未だ固まっていません。従って、Selenium HQ blogでアナウンスされるリリースノートに注意し追随する必要があります。
最近、我々もtoggle()コマンドとselect()コマンドが非推奨どころか、全くサポートされなくなったことがわかりました。
これらのコマンドを使っても、Seleniumサーバはコマンドを全く理解せず、WebDriverExceptionsが帰ってきます。
Seleniumのバージョンを確認するのがベストです。ここに挙げたサンプルはSeleniumの最新のリリース候補であるバージョン2.0.0を使っています。
また、-debug flagを使って、.jarファイルのインスタンスの作成が出来、 クライアントバインディングがSeleniumサーバにどうAPIを実行しているかを確認することができます。
20:38:02. 687 INFO - Java: Sun Microsystems Inc. 20. 1-b02
29:38:02. 687 INFO ? OS: Windows XP 5.1 x86
29:38:02. 703 INFO ? v2.0.0, with Core v2.0.0. Built from revision 12817
・Selenium1でサポートされていたis_test_present()とwaif_for_confition()
コマンドは無くなっていて、click()eventの開始や、get_attribute()を使ってアトリビュート値をアーカイブする前に elementを選択するというDOMを使ったアプローチに置き換えられています。
ページロードの際のwait_for_condition()も必要なくなりました。
替わりに、implicitly_wait()で或るタイムリミットを設定し、find_element_by_id()を使ってページロードの間にDOMエレメントが取得されるのを待たせることができます。

・最後に、コンカレントなAjaxリクエストの取り扱いについての質問がSelenium Discussion Groupでなされていることに気が付きました。
多くのテストフレームワークには、それぞれのテスト間でデータベースのsetupとtear downのコンセプトがあります。
我々が直面した問題のひとつですが、ブラウザが複数のリクエストを発行している場合、tear down機能ではデータベースがunknownなステータスの時にリクエストが到着する可能性がある為、 Ajaxリクエストが全て完了するのを待つべきだという点です。

この問題が起こってしまった場合、Seleniumテストは失敗し、こうした競合条件を調べるのに余分な時間を掛けることになります。
もし、jQueryを使っているのであれば、ajax.global stateを調べてページ間でどう設定すれば良いか決めることが出来ます。
(つまり、execute_script(“return jQuery.active ===0”)) 条件が整うまで、ループをかけたくなるはずです。
(例えば、独自のwait_for_condition()コマンドと同等なコードの実行。こちらを参照) これらのヒントがSelenium2へのマイグレーションの参考になればと思います。 Happy testing!