C# + Selenium + ChromeDriverを使って、HTMLフォームへ入力してみた

C# + Selenium + ChromeDriverを使って、HTMLフォームへ入力することがあったため、メモを残しておきます。

 

環境

 

準備

Python + Djangoアプリの準備

GitHubからclone・セットアップし、アプリを起動しておきます。

>cd path\to\dir

# GitHubからカレントディレクトリへclone
path\to\dir>git clone https://github.com/thinkAmi-sandbox/Django_form_sample.git .

# virtualenv環境の作成とactivate
path\to\dir>virtualenv -p c:\python34\python.exe env
path\to\dir>env\Scripts\activate

# requirements.txtよりインストール
(env)path\to\dir>pip install -r requirements.txt

# マイグレーションと初期データ投入
(env)path\to\dir>python manage.py migrate
(env)path\to\dir>python manage.py loaddata initial_data

# 開発サーバの起動
(env)path\to\dir>python manage.py runserver

# 開発サーバのURLを既定のブラウザで開く
# (別のコマンドプロンプトを開いて実行)
>start http://localhost:8000/site/register

 
起動したWebアプリのフォームは以下の通りです。

ブラウザ画面

f:id:thinkAmi:20160112222909p:plain

HTMLソース
<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta charset="utf-8" />
    <title>My Site</title>
  </head>
  <body>
    <div id="main">
      <form method="post" action="" name="detail">
        <fieldset>
          <input type='hidden' name='csrfmiddlewaretoken' value='ikcYFvPfvvYuXlhdIrDN1t80ejAleTOH' />
          <p>
            <label for="id_registration_date">RegistrationDate:</label>
            <input id="id_registration_date" name="registration_date" type="date" value="2016-01-12" />
            <input id="initial-id_registration_date" name="initial-registration_date" type="hidden" value="2016-01-12 13:02:15+00:00" />
          </p>
          <p>
            <label for="id_pushed_0">Pushed:</label>
            <ul id="id_pushed">
              <li>
                <label for="id_pushed_0">
                  <input id="id_pushed_0" name="pushed" type="radio" value="1" />radio1
                </label>
              </li>
              <li>
                <label for="id_pushed_1">
                  <input id="id_pushed_1" name="pushed" type="radio" value="2" />radio2
                </label>
              </li>
              <li>
                <label for="id_pushed_2">
                  <input id="id_pushed_2" name="pushed" type="radio" value="3" />radio3
                </label>
              </li>
            </ul>
          </p>
          <p>
            <label for="id_checked">YesNo:</label>
            <input id="id_checked" name="checked" type="checkbox" />
          </p>
          <p>
            <label for="id_checked_multiple_0">CheckdMultiple:</label>
            <ul id="id_checked_multiple">
              <li>
                <label for="id_checked_multiple_0">
                  <input id="id_checked_multiple_0" name="checked_multiple" type="checkbox" value="1" />checkbox1
                </label>
              </li>
              <li>
                <label for="id_checked_multiple_1">
                  <input id="id_checked_multiple_1" name="checked_multiple" type="checkbox" value="2" />checkbox2
                </label>
              </li>
              <li>
                <label for="id_checked_multiple_2">
                  <input id="id_checked_multiple_2" name="checked_multiple" type="checkbox" value="3" />checkbox3
                </label>
              </li>
            </ul>
          </p>
          <p>
            <label for="id_selected">Selected:</label>
            <select id="id_selected" name="selected">
              <option value="" selected="selected">---------</option>
              <option value="1">select1</option>
              <option value="2">select2</option>
              <option value="3">select3</option>
            </select>
          </p>
          <p>
            <label for="id_selected_multiple">SelectedMultiple:</label>
            <select multiple="multiple" id="id_selected_multiple" name="selected_multiple">
              <option value="1">select_multi1</option>
              <option value="2">select_multi2</option>
              <option value="3">select_multi3</option>
            </select>
          </p>
          <p>
            <label for="id_input_text">InputText:</label>
            <input id="id_input_text" maxlength="255" name="input_text" type="text" />
          </p>
          <p>
            <label for="id_text_area">TextArea:</label>
            <textarea cols="40" id="id_text_area" name="text_area" rows="10"></textarea>
          </p>
          <input type="submit" id="save" value="Save">
        </fieldset>
      </form>
    </div>
  </body>
</html>

 

C#のプロジェクト作成

今回はコンソールアプリとして作成します。

 

NuGetパッケージのインストール

以下の2つのNuGetパッケージをインストールしておきます。

 

SeleniumChrome Driverのダウンロード

Chrome Driver(chromedriver_win32.zip)を以下よりダウンロードします。 ChromeDriver - WebDriver for Chrome

その後、

  • zipファイルを解凍
  • 解凍したディレクトリにあるchromedriver.exeファイルを、コンソールアプリプロジェクトへ追加
  • ファイルのプロパティで、出力ディレクトリにコピーの値を常にコピーするへと変更

を行います。

 

Seleniumを使ったフォームへの入力など

ChromeDriverの利用

ChromeDriverはIDisposableインタフェースを実装しているため、usingステートメントを使えます。

この場合、実行後にChromeが自動終了してしまうため、コマンドプロンプトにて入力待ちを実装しておきます。

また、driver.Navigate().GoToUrl()を使って、対象のフォーム画面へと遷移します。

using (var driver = new OpenQA.Selenium.Chrome.ChromeDriver())
{
    driver.Navigate().GoToUrl("http://localhost:8000/site/register/");

    // 自動終了しないようにする
    Console.WriteLine("何かキーを押すことで終了します");
    Console.ReadKey();
}

 

<input type="text"><textarea>

両方とも、SendKeys()で設定、Clear()でクリアします。

var element = driver.FindElement(OpenQA.Selenium.By.Id("id_input_text"));

// <input type="text">へデータを設定
// なお、既にデータがあったり複数回の入力の場合、
// データは追記されていく
element.SendKeys("テキスト");
element.SendKeys("txt");


// <input type="text">のデータ削除
element.Clear();

 

<input type="date">

SendKeys()で設定・クリアなどを行います。

以下を参考に実装しましたが、色々と手間があるため、もしかしたら他に良い方法があるかもしれません。

var element = driver.FindElement(OpenQA.Selenium.By.Id("id_registration_date"));

// <input type="date">へデータを設定
// デフォルトでは本日日付が入っているWebアプリなので、
// 年月日いずれも1つ前のデータを入力してみる
var inputDate = DateTime.Now.AddYears(-1).AddMonths(-1).AddDays(-1);

// ChromeDriverの場合、
// 年の位置にカーソルがある状態で、
// 年6桁・月2桁・日2桁という各桁数を持つ、
// 前ゼロ詰め数字を入力すると、
// 既存の値を上書きして設定される
// *`2016/01/01`や`2016-01-01`ではうまく設定できず
var year = string.Format("{0:D6}", inputDate.Year);
var month = string.Format("{0:D2}", inputDate.Month);
var day = string.Format("{0:D2}", inputDate.Day);
element.SendKeys(year + month + day);


// 後続の処理のために:
// この時点ではカーソルが`日`の位置にあるため、
// driver.FindElement()した時のカーソル位置(`年`)へ戻す
// なお、Keysを連結して.SendKeys()に渡した場合、
// 連結したキーをすべて含む動作をしてくれる
element.SendKeys(Keys.ArrowLeft + Keys.ArrowLeft);


// <input type="date">のデータ削除
// ChromeDriverの場合、.Clear()ではエラーになる
// invalid element state: Element must be user-editable in order to clear it.
//element.Clear();

// そのため、.SendKeys()でDeleteキーを使って削除する
// ただ、Deleteキーを1回押すだけでは`年`だけが消えるので、カーソルキーを併用して
// 1回目:年、2回目:月、3回目:日をそれぞれ消す
for (int i = 0; i < 3; i++)
{
    element.SendKeys(Keys.Delete);
    element.SendKeys(Keys.ArrowRight);
}


// データ削除したところに再入力する場合は、
// 年の位置へカーソルを戻してから、再度入力する
element.SendKeys(Keys.ArrowLeft + Keys.ArrowLeft);

var reInputDate = DateTime.Now.AddYears(1).AddMonths(1).AddDays(1);
var reYear = string.Format("{0:D6}", reInputDate.Year);
var reMonth = string.Format("{0:D2}", reInputDate.Month);
var reDay = string.Format("{0:D2}", reInputDate.Day);
element.SendKeys(reYear + reMonth + reDay);

 

<input type="date / radio / checkbox">

いずれもelementをClick()します。

var element = driver.FindElement(OpenQA.Selenium.By.Id("id_pushed_1"));
element.Click();

 

<select> / <select multiple="multiple">

SELECT要素の選択を行うには、Selenium.SupportパッケージのUI.SelectElementを使います。
How to Using Webdriver Selenium for selecting an option in C#? - Stack Overflow

以下は <select multiple="multiple">ですが、両方とも同じような書き方です。

var element = driver.FindElement(By.Id("id_selected_multiple"));
var selectElement = new SelectElement(element);

// 全部を選択
selectElement.SelectByValue("1");
selectElement.SelectByValue("2");
selectElement.SelectByValue("3");

// 一部を解除
// 選択と同様、Index, Value, Textの3種類あり
selectElement.DeselectByValue("2");

// もしくは、一括で解除
selectElement.DeselectAll();

 

フォームのSubmit

Submitが用意されています。

var element = driver.FindElement(By.Id("save"));
element.Submit();

 

スクリーンショットを撮る

フォームとは関係ありませんが、スクリーンショットを撮る方法も載せておきます。

var s = ((OpenQA.Selenium.ITakesScreenshot)driver).GetScreenshot();
s.SaveAsFile(System.IO.Path.Combine(
    System.Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), @"selenium.jpg"),
    System.Drawing.Imaging.ImageFormat.Jpeg);

 
なお、以下のstackoverflowでは各言語のスクリーンショットの撮り方が記載されています。
Take a screenshot with Selenium WebDriver - Stack Overflow

 

ソースコード

今回使用したC#のコンソールプロジェクトは、GitHubに上げておきました。
SeleniumSampleUsingCsharp/InputFormRunner at master · thinkAmi-sandbox/SeleniumSampleUsingCsharp

 

参考

APIドキュメント

SeleniumHQ/selenium - GitHubに、C#APIドキュメントへのリンクがありました。フッターには2013とありますが、たぶん大丈夫でしょう...
WebDriver - Table of Content

 

Seleniumの設定