C# Selenium入門|環境構築からブラウザ自動操作・要素取得・待機処理まで実践解説

はじめに

C# Seleniumは、C#でブラウザ操作を自動化したい人にとって定番の選択肢です。Webサイトの画面を開く、検索フォームに文字を入力する、ボタンをクリックする、検索結果や一覧データを取得する、ログイン後の画面をテストするといった操作を、C#のコードから実行できます。

Seleniumの中心機能であるWebDriverは、実際のユーザー操作に近い形でブラウザを制御する仕組みです。公式ドキュメントでも、WebDriverはブラウザをネイティブに操作するためのインターフェースとして説明されています。Selenium

この記事では、C# Seleniumの入門者向けに、環境構築からブラウザ起動、要素取得、待機処理、検索操作の自動化、よくあるエラー対策までを実践的に解説します。サンプルコードは、Selenium WebDriver 4系を前提にしています。

1. C# Seleniumでできることと検索ユーザーの目的

1-1. C# Seleniumとは何か

C# Seleniumとは、C#からSelenium WebDriverを使ってブラウザを自動操作する方法のことです。Selenium自体は特定の言語専用のツールではなく、Java、Python、JavaScript、C#など複数の言語から利用できます。

C#でSeleniumを使う場合は、主にNuGetからSelenium.WebDriverを追加し、ChromeDriverEdgeDriverなどのブラウザドライバーを通じてブラウザを操作します。Selenium公式サイトでは、Seleniumはブラウザを自動化するためのツールであり、特にWebアプリケーションのテスト自動化で使われると説明されています。Selenium

C# Seleniumでよく行う処理は、次のようなものです。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

このように、C#のコードからChromeを起動し、指定したURLへアクセスし、ページタイトルを取得できます。

1-2. ブラウザ自動操作・Webスクレイピング・E2Eテストで使われる理由

C# Seleniumが使われる主な場面は、ブラウザ自動操作、Webスクレイピング、E2Eテストです。

ブラウザ自動操作では、手作業で繰り返している入力、クリック、画面遷移、ファイルアップロード、スクリーンショット保存などを自動化できます。社内システムの入力補助や、定期的な動作確認にも使われます。

Webスクレイピングでは、HTMLに表示されているテキスト、リンク、商品名、価格、検索結果などを取得できます。ただし、スクレイピング目的で使う場合は、対象サイトの利用規約、robots.txt、アクセス頻度、著作権、個人情報の扱いを必ず確認する必要があります。

E2Eテストでは、ユーザーが実際に画面で操作する流れを自動化できます。たとえば、ログイン、商品検索、カート追加、注文確認といった一連の操作をテストコードとして実行できます。Selenium WebDriverは、言語バインディングとブラウザ制御コードの実装を含む仕組みとして説明されています。Selenium

1-3. C# Seleniumを学ぶ前に知っておきたい前提知識

C# Seleniumをスムーズに学ぶには、次の知識があると理解しやすくなります。

まず、C#の基本文法です。変数、メソッド、クラス、例外処理、usingtry-catchList<T>などを理解しておくと、サンプルコードを読みやすくなります。

次に、HTMLとCSSの基礎です。Seleniumでは、HTML要素をidnameclass、CSSセレクター、XPathなどで探します。そのため、次のようなHTMLを読めることが重要です。

HTML
<input id="keyword" name="q" class="search-box" />
<button id="searchButton">検索</button>

このHTMLに対して、C# Seleniumでは次のように要素を取得できます。

C#
IWebElement input = driver.FindElement(By.Id("keyword"));
IWebElement button = driver.FindElement(By.Id("searchButton"));

また、ブラウザの開発者ツールを使ってHTML構造を確認する習慣も大切です。ChromeやEdgeで対象要素を右クリックし、「検証」を選ぶと、要素のidclass、属性、階層構造を確認できます。

1-4. Selenium IDE・Selenium WebDriver・Playwrightとの違い

Seleniumには複数の関連ツールがあります。入門時に混同しやすいのが、Selenium IDE、Selenium WebDriver、Playwrightです。

Selenium IDEは、ブラウザ操作を記録・再生できる拡張機能です。公式ドキュメントでも、Chrome、Firefox、Edge向けのプラグインとして、ユーザー操作を記録・再生できるものと説明されています。Selenium コードを書かずに簡単な操作を記録したい場合に便利ですが、複雑な条件分岐、保守性の高いテスト設計、業務システムへの組み込みには限界があります。

Selenium WebDriverは、C#などのプログラムからブラウザを制御する仕組みです。実務でC# Seleniumと呼ぶ場合、多くはこのSelenium WebDriverを指します。

Playwrightは、Microsoftが開発しているブラウザ自動化ライブラリです。モダンなWebアプリケーションやSPAのテストで使いやすい機能が多く、待機処理が比較的自動化されています。一方、Seleniumは歴史が長く、対応言語や利用実績、既存資産が多い点が強みです。

C#で既存の.NETプロジェクトやNUnit、xUnitと連携したい場合は、C# Seleniumが有力な選択肢になります。

2. C# Seleniumの環境構築

2-1. 必要な開発環境:Visual Studio・.NET・ブラウザ

C# Seleniumを始めるには、次の環境を準備します。

開発環境としては、Visual Studio、Visual Studio Code、Riderなどが使えます。初心者には、C#プロジェクトの作成やNuGet管理が簡単なVisual Studioがおすすめです。

.NET SDKも必要です。コンソールアプリとしてSeleniumを試す場合は、.NETのコンソールプロジェクトを作成します。

ブラウザは、Google ChromeまたはMicrosoft Edgeを使うのが一般的です。Chromeを操作する場合はChromeDriver、Edgeを操作する場合はEdgeDriverが使われます。ただし、Selenium 4.6以降ではSelenium Managerにより、ドライバー管理が自動化される場面が増えています。Selenium Managerは、Selenium用のドライバーとブラウザ管理を自動化するコマンドラインツールとして公式に説明されています。Selenium

2-2. C#プロジェクトの作成手順

Visual StudioでC# Selenium用のプロジェクトを作成する手順は、次のとおりです。

Visual Studioを開き、「新しいプロジェクトの作成」を選択します。テンプレートから「コンソールアプリ」を選びます。言語はC#、プロジェクト名は任意で構いません。たとえば、SeleniumSampleのような名前にします。

プロジェクト作成後、Program.csにSeleniumのコードを書いていきます。最初はテストフレームワークを使わず、コンソールアプリでブラウザ起動を確認するのがおすすめです。

コマンドラインで作成する場合は、次のように実行できます。

Bash
dotnet new console -n SeleniumSample
cd SeleniumSample

この状態ではまだSeleniumは使えないため、NuGetパッケージを追加します。

2-3. NuGetでSelenium WebDriverをインストールする方法

C#でSeleniumを使うには、NuGetからSelenium.WebDriverをインストールします。NuGetでは、Selenium WebDriverの.NETバインディングが提供されています。NuGet

Visual Studioで追加する場合は、プロジェクトを右クリックし、「NuGetパッケージの管理」を開きます。「参照」タブでSelenium.WebDriverを検索し、インストールします。

明示的な待機処理でWebDriverWaitを使う場合は、Selenium.Supportも追加します。NuGetではSelenium.Supportパッケージも提供されています。NuGet

コマンドラインで追加する場合は、次のように実行します。

Bash
dotnet add package Selenium.WebDriver
dotnet add package Selenium.Support

まずはこの2つを入れておけば、基本的なブラウザ操作、要素取得、待機処理を実装できます。

2-4. ChromeDriver・EdgeDriverの準備方法

従来のSeleniumでは、Chromeを操作するためにChromeDriverを手動でダウンロードし、ブラウザのバージョンと合わせる必要がありました。

ChromeDriverを手動で準備する場合は、利用しているChromeのバージョンに対応したChromeDriverを入手し、実行ファイルをプロジェクトの出力フォルダやPATHが通った場所に配置します。

Edgeを操作する場合は、EdgeDriverを使用します。EdgeDriverもブラウザのバージョンと合っていないと、起動時にエラーが発生することがあります。

ただし、現在のSeleniumではSelenium Managerを使うことで、ドライバーの自動取得・管理が可能です。特別な理由がなければ、最初は手動でDriverを配置するより、Selenium Managerに任せる構成から始めると簡単です。

2-5. Selenium Managerを使ったドライバー自動管理

Selenium Managerは、Selenium実行時に必要なブラウザドライバーを自動的に解決する仕組みです。Selenium公式ドキュメントでは、Selenium ManagerはRustで実装されたコマンドラインツールで、Seleniumのドライバーやブラウザ管理を自動化すると説明されています。Selenium

たとえば、次のようにChromeDriverを生成すると、環境によってはSelenium Managerが必要なドライバーを自動的に準備します。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

この方法なら、最初からChromeDriverの実行ファイルを手動でプロジェクトに入れなくても動作することがあります。

ただし、企業内ネットワークやプロキシ環境では、Selenium Managerが外部からドライバーを取得できない場合があります。その場合は、手動でChromeDriverやEdgeDriverを配置する方法も検討してください。

2-6. 初回実行で起こりやすい環境構築エラー

C# Seleniumの初回実行でよくあるエラーには、次のようなものがあります。

WebDriverExceptionが発生してブラウザが起動しない場合は、ブラウザがインストールされているか、Selenium WebDriverのパッケージが正しく入っているか、ドライバーを取得できる環境かを確認します。

ChromeDriverのバージョン不一致が原因の場合は、Chrome本体のバージョンとChromeDriverのバージョンを合わせる必要があります。Selenium Managerを使う場合でも、ネットワーク制限により自動取得に失敗するケースがあります。

OpenQA.Seleniumが見つからない場合は、Selenium.WebDriverがインストールされていないか、using OpenQA.Selenium;が不足しています。

WebDriverWaitが見つからない場合は、Selenium.Supportが入っていない可能性があります。

まずは、次の最小コードが動くか確認しましょう。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://example.com");

Console.WriteLine(driver.Title);

このコードが動けば、C# Seleniumの基本環境は整っています。

3. C# Seleniumの基本コードとブラウザ起動

3-1. Chromeを起動する最小コード

C# SeleniumでChromeを起動する最小コードは次のとおりです。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

これだけでChromeブラウザが起動します。IWebDriverは、ブラウザ操作を行うための中心的なインターフェースです。

実際には、ブラウザを開いたあとにURLへアクセスし、最後に終了する処理を書くことが多いです。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

driver.Quit();

usingを使っている場合、スコープ終了時にドライバーが破棄されますが、明示的にQuit()を書くと終了処理が分かりやすくなります。

3-2. 指定URLへアクセスする方法

指定したURLへアクセスするには、Navigate().GoToUrl()を使います。

C#
driver.Navigate().GoToUrl("https://www.bing.com/");

ページのタイトルを取得するには、Titleプロパティを使います。

C#
Console.WriteLine(driver.Title);

現在のURLを取得するには、Urlプロパティを使います。

C#
Console.WriteLine(driver.Url);

たとえば、アクセス後にタイトルとURLを表示するコードは次のようになります。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("https://www.bing.com/");

Console.WriteLine($"Title: {driver.Title}");
Console.WriteLine($"URL: {driver.Url}");

driver.Quit();

3-3. ブラウザを最大化・終了する方法

ブラウザを最大化するには、Manage().Window.Maximize()を使います。

C#
driver.Manage().Window.Maximize();

ウィンドウサイズを指定することもできます。

C#
driver.Manage().Window.Size = new System.Drawing.Size(1280, 800);

ブラウザを終了するには、Quit()を使います。

C#
driver.Quit();

Close()Quit()は似ていますが、意味が異なります。Close()は現在のウィンドウを閉じ、Quit()はWebDriverセッション全体を終了します。通常、処理の最後にはQuit()を使います。

C#
try
{
driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);
}
finally
{
driver.Quit();
}

try-finallyを使うと、途中で例外が発生してもブラウザを終了しやすくなります。

3-4. ヘッドレスモードで実行する方法

ヘッドレスモードとは、ブラウザ画面を表示せずに処理を実行するモードです。CI環境やサーバー上でSeleniumを動かす場合によく使います。

Chromeをヘッドレスモードで起動するには、ChromeOptionsを使います。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

var options = new ChromeOptions();
options.AddArgument("--headless=new");

using IWebDriver driver = new ChromeDriver(options);

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

driver.Quit();

ヘッドレスモードでは、画面サイズが通常起動時と異なるため、要素の表示状態やクリック位置に影響することがあります。安定させるために、ウィンドウサイズも指定しておくとよいです。

C#
options.AddArgument("--window-size=1280,800");

3-5. ChromeOptionsでブラウザ設定を変更する方法

ChromeOptionsを使うと、Chrome起動時の設定を変更できます。Selenium公式ドキュメントでも、Selenium 4ではブラウザオプションのクラスを使ってブラウザ設定を指定する考え方が説明されています。Selenium

よく使う設定例は次のとおりです。

C#
var options = new ChromeOptions();

options.AddArgument("--start-maximized");
options.AddArgument("--disable-notifications");
options.AddArgument("--window-size=1280,800");

using IWebDriver driver = new ChromeDriver(options);

ダウンロード先を指定する場合は、AddUserProfilePreferenceを使います。

C#
var options = new ChromeOptions();

options.AddUserProfilePreference("download.default_directory", @"C:\Temp\Downloads");
options.AddUserProfilePreference("download.prompt_for_download", false);

using IWebDriver driver = new ChromeDriver(options);

業務自動化では、通知の無効化、ウィンドウサイズ固定、ダウンロード先指定、ヘッドレス実行などを組み合わせることが多いです。

4. C# Seleniumによるブラウザ自動操作の基本

4-1. テキストボックスへ文字を入力する

テキストボックスへ文字を入力するには、対象要素を取得してSendKeys()を使います。

C#
IWebElement input = driver.FindElement(By.Name("q"));
input.SendKeys("c# selenium");

既に入力されている文字を消してから入力したい場合は、Clear()を使います。

C#
IWebElement input = driver.FindElement(By.Name("q"));

input.Clear();
input.SendKeys("c# selenium 入門");

フォーム入力では、Clear()SendKeys()をセットで使うことがよくあります。

C#
void TypeText(IWebElement element, string text)
{
element.Clear();
element.SendKeys(text);
}

このように共通メソッド化しておくと、入力処理を再利用しやすくなります。

4-2. ボタンをクリックする

ボタンをクリックするには、Click()を使います。

C#
IWebElement button = driver.FindElement(By.Id("searchButton"));
button.Click();

テキストボックスに入力してから検索ボタンを押す例は次のとおりです。

C#
IWebElement input = driver.FindElement(By.Name("q"));
input.SendKeys("c# selenium");

IWebElement button = driver.FindElement(By.Id("searchButton"));
button.Click();

ただし、ボタンがまだ表示されていない、画面外にある、別の要素に覆われている場合はクリックに失敗します。実務では、クリック可能になるまで待ってからClick()する実装が重要です。

4-3. フォームを送信する

フォームを送信する方法はいくつかあります。検索フォームのような場合、入力欄でEnterキーを送る方法が簡単です。

C#
IWebElement input = driver.FindElement(By.Name("q"));

input.SendKeys("c# selenium");
input.SendKeys(Keys.Enter);

Submit()を使う方法もあります。

C#
IWebElement form = driver.FindElement(By.TagName("form"));
form.Submit();

ただし、最近のWebアプリケーションではJavaScriptで送信処理を制御しているケースが多く、Submit()よりもボタンクリックやEnterキー送信の方が自然に動作することがあります。

4-4. チェックボックス・ラジオボタンを操作する

チェックボックスやラジオボタンは、Selectedプロパティで選択状態を確認できます。

C#
IWebElement checkbox = driver.FindElement(By.Id("agree"));

if (!checkbox.Selected)
{
checkbox.Click();
}

チェックを外したい場合は、選択されているときだけクリックします。

C#
if (checkbox.Selected)
{
checkbox.Click();
}

ラジオボタンも基本は同じです。

C#
IWebElement radio = driver.FindElement(By.Id("planPremium"));

if (!radio.Selected)
{
radio.Click();
}

チェックボックスやラジオボタンは、ラベル要素をクリックした方が安定する場合もあります。クリック対象が小さい場合は、labelfor属性や親要素も確認しましょう。

4-5. セレクトボックスを操作する

HTMLのselect要素を操作するには、OpenQA.Selenium.Support.UISelectElementを使います。

C#
using OpenQA.Selenium.Support.UI;

IWebElement selectElement = driver.FindElement(By.Id("category"));
var select = new SelectElement(selectElement);

select.SelectByText("ニュース");

値で選択する場合は、SelectByValue()を使います。

C#
select.SelectByValue("news");

インデックスで選択することもできます。

C#
select.SelectByIndex(1);

現在選択されている項目は、SelectedOptionで取得できます。

C#
Console.WriteLine(select.SelectedOption.Text);

ただし、HTMLのselectではなく、JavaScriptで作られた独自UIのドロップダウンではSelectElementが使えません。その場合は、通常のクリック操作で選択肢を開き、対象項目をクリックします。

4-6. 戻る・進む・更新などのブラウザ操作

ブラウザの戻る、進む、更新はNavigate()から実行できます。

C#
driver.Navigate().Back();
driver.Navigate().Forward();
driver.Navigate().Refresh();

ページ遷移後は、要素の再取得が必要になることがあります。遷移前に取得したIWebElementをそのまま使うと、StaleElementReferenceExceptionが発生することがあります。

C#
driver.Navigate().Back();

IWebElement newElement = driver.FindElement(By.Id("keyword"));

画面遷移や更新の後は、改めて要素を探すのが基本です。

5. C# Seleniumで要素を取得する方法

5-1. FindElementとFindElementsの違い

C# Seleniumで要素を取得する基本メソッドは、FindElementFindElementsです。

FindElementは、条件に一致する最初の要素を1つ取得します。要素が見つからない場合はNoSuchElementExceptionが発生します。

C#
IWebElement element = driver.FindElement(By.Id("keyword"));

FindElementsは、条件に一致する要素を複数取得します。要素が見つからない場合でも例外にはならず、空のコレクションが返ります。

C#
IReadOnlyCollection<IWebElement> elements = driver.FindElements(By.CssSelector(".result"));

Console.WriteLine(elements.Count);

必ず1つ存在する前提ならFindElement、存在しない可能性がある場合や一覧を取得したい場合はFindElementsを使うとよいです。Selenium公式ドキュメントでも、要素を扱う処理はSeleniumコードの大きな部分を占めると説明されています。Selenium

5-2. ID・Name・ClassNameで要素を取得する

要素取得で最も分かりやすいのは、idを使う方法です。

C#
IWebElement element = driver.FindElement(By.Id("loginButton"));

idはページ内で一意であることが期待されるため、安定した取得方法です。対象要素に固定のidがある場合は、まずBy.Idを検討しましょう。

name属性で取得する場合は、By.Nameを使います。

C#
IWebElement input = driver.FindElement(By.Name("email"));

classで取得する場合は、By.ClassNameを使います。

C#
IWebElement item = driver.FindElement(By.ClassName("item-title"));

ただし、ClassNameには複数クラスをまとめて指定できません。次のようなHTMLがある場合、

HTML
<div class="item active">商品A</div>

By.ClassName("item active")のようには指定できません。複数クラスを条件にしたい場合は、CSSセレクターを使います。

C#
IWebElement item = driver.FindElement(By.CssSelector(".item.active"));

5-3. CSSセレクターで要素を取得する

CSSセレクターは、C# Seleniumで非常によく使う要素取得方法です。Selenium公式ドキュメントでも、ロケーターはページ内の要素を識別するための方法として説明されています。Selenium

IDを指定する場合は、#idを使います。

C#
IWebElement element = driver.FindElement(By.CssSelector("#keyword"));

クラスを指定する場合は、.classを使います。

C#
IWebElement element = driver.FindElement(By.CssSelector(".search-box"));

属性を指定することもできます。

C#
IWebElement input = driver.FindElement(By.CssSelector("input[name='q']"));

親子関係を指定する場合は、次のように書けます。

C#
IWebElement title = driver.FindElement(By.CssSelector(".result-list .result-item a"));

複数要素を取得する場合は、FindElementsと組み合わせます。

C#
var links = driver.FindElements(By.CssSelector(".result-item a"));

foreach (var link in links)
{
Console.WriteLine(link.Text);
Console.WriteLine(link.GetAttribute("href"));
}

CSSセレクターは読みやすく、実務でも使いやすいため、C# Selenium入門では優先的に覚えたい取得方法です。

5-4. XPathで要素を取得する

XPathは、HTMLの階層構造やテキスト条件を使って要素を取得する方法です。

C#
IWebElement button = driver.FindElement(By.XPath("//button[text()='検索']"));

部分一致でテキストを探すこともできます。

C#
IWebElement link = driver.FindElement(By.XPath("//a[contains(text(), '詳細')]"));

属性条件を指定する場合は、次のように書きます。

C#
IWebElement input = driver.FindElement(By.XPath("//input[@name='q']"));

XPathは柔軟ですが、HTML構造に依存しすぎると壊れやすくなります。たとえば、次のような絶対パスは避けた方がよいです。

C#
driver.FindElement(By.XPath("/html/body/div[2]/main/div[1]/ul/li[3]/a"));

このようなXPathは、HTML構造が少し変わるだけで動かなくなります。実務では、属性、テキスト、安定した親要素を使って、できるだけ壊れにくいXPathを書くことが大切です。

5-5. 要素のテキスト・属性・HTMLを取得する

要素の表示テキストを取得するには、Textプロパティを使います。

C#
IWebElement title = driver.FindElement(By.CssSelector("h1"));
Console.WriteLine(title.Text);

属性を取得するには、GetAttribute()を使います。

C#
IWebElement link = driver.FindElement(By.CssSelector("a"));

string href = link.GetAttribute("href");
Console.WriteLine(href);

入力欄の値を取得する場合も、GetAttribute("value")を使います。

C#
IWebElement input = driver.FindElement(By.Name("q"));

string value = input.GetAttribute("value");
Console.WriteLine(value);

要素のHTMLを取得したい場合は、outerHTMLinnerHTMLを取得します。

C#
string outerHtml = element.GetAttribute("outerHTML");
string innerHtml = element.GetAttribute("innerHTML");

表示状態や有効状態を確認するには、DisplayedEnabledを使います。Selenium公式ドキュメントでも、要素が表示されているか、有効かを確認するメソッドが説明されています。Selenium

C#
if (element.Displayed && element.Enabled)
{
element.Click();
}

5-6. 要素が見つからないときの確認ポイント

要素が見つからない場合は、まずロケーターが正しいか確認します。ChromeやEdgeの開発者ツールで対象要素を検証し、idnameclass、属性が実際に存在するかを見ます。

次に、要素がまだ読み込まれていない可能性を確認します。ページ表示直後にFindElementを実行すると、AjaxやJavaScriptによる描画が終わっておらず、要素が存在しないことがあります。この場合は、明示的な待機処理を使います。

また、要素がiframe内にある場合は、先にiframeへ切り替える必要があります。

C#
driver.SwitchTo().Frame("frameName");
IWebElement element = driver.FindElement(By.Id("insideFrame"));

別タブや別ウィンドウで開いている場合は、対象のウィンドウへ切り替える必要があります。

非表示の要素をクリックしようとしている場合も失敗します。DisplayedEnabledを確認し、必要に応じてスクロールや待機処理を入れましょう。

6. C# Seleniumの待機処理

6-1. なぜ待機処理が必要なのか

C# Seleniumで安定した自動操作を作るには、待機処理が非常に重要です。

Webページは、HTMLが一度にすべて読み込まれるとは限りません。JavaScriptで後から要素が追加されたり、Ajaxで検索結果が表示されたり、SPAで画面遷移のように見える描画が行われたりします。

人間が操作する場合は、画面にボタンが表示されるまで自然に待ちます。しかしSeleniumは、コードに書かれた処理を高速に実行します。そのため、まだ表示されていない要素を探したり、クリックできない状態のボタンをクリックしようとしたりして、エラーになることがあります。

待機処理は、このようなタイミングのズレを吸収するために使います。

6-2. Thread.Sleepを使うべきではない理由

待機処理として、次のようにThread.Sleep()を使いたくなるかもしれません。

C#
Thread.Sleep(3000);

これは3秒間、必ず処理を止めるコードです。簡単ですが、実務では多用しない方がよいです。

理由は、まず遅くなるからです。要素が0.5秒で表示されても、必ず3秒待つため、テスト全体が無駄に遅くなります。

次に、不安定だからです。通常は3秒で表示される要素でも、ネットワークやサーバー状況によって5秒かかることがあります。その場合、Thread.Sleep(3000)では待ちきれずに失敗します。

待機処理では、「固定秒数だけ待つ」のではなく、「条件を満たすまで最大何秒待つ」という書き方が重要です。そのために使うのが、Implicit WaitやExplicit Waitです。

6-3. Implicit Waitの使い方

Implicit Waitは、FindElementFindElementsで要素を探すとき、指定した時間まで待つ設定です。

C#
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);

この設定を行うと、要素がすぐに見つからない場合でも、最大10秒まで探し続けます。

全体に効くため簡単ですが、細かい条件を指定しにくい点に注意が必要です。たとえば、「要素が存在するまで」だけでなく、「クリック可能になるまで」「表示されるまで」「テキストが変わるまで」といった条件を待ちたい場合は、Explicit Waitの方が適しています。

また、Implicit WaitとExplicit Waitを過度に混在させると、想定より待機時間が長くなることがあります。実務では、Explicit Waitを中心に設計する方が管理しやすいです。

6-4. Explicit Waitの使い方

Explicit Waitは、特定の条件が満たされるまで待つ方法です。C#ではWebDriverWaitを使います。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

using IWebDriver driver = new ChromeDriver();

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

driver.Navigate().GoToUrl("https://example.com");

IWebElement element = wait.Until(d =>
d.FindElement(By.CssSelector("h1"))
);

Console.WriteLine(element.Text);

driver.Quit();

このコードでは、h1要素が見つかるまで最大10秒待ちます。見つかったらすぐに次の処理へ進みます。

要素が表示されていることまで確認したい場合は、次のように書けます。

C#
IWebElement element = wait.Until(d =>
{
var e = d.FindElement(By.Id("message"));
return e.Displayed ? e : null;
});

Explicit Waitは、C# Seleniumで安定した自動化を作るための基本です。

6-5. WebDriverWaitでクリック可能になるまで待つ方法

ボタンをクリックする前には、要素が存在するだけでなく、表示されていて有効であることを確認すると安定します。

C#
IWebElement button = wait.Until(d =>
{
var e = d.FindElement(By.Id("submitButton"));

if (e.Displayed && e.Enabled)
{
return e;
}

return null;
});

button.Click();

この実装では、ボタンが表示され、かつ有効になるまで待ってからクリックします。

よく使う処理なので、共通メソッド化しておくと便利です。

C#
static IWebElement WaitUntilClickable(
IWebDriver driver,
By by,
int seconds = 10)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(seconds));

return wait.Until(d =>
{
var element = d.FindElement(by);
return element.Displayed && element.Enabled ? element : null;
});
}

使う側は次のように書けます。

C#
IWebElement button = WaitUntilClickable(driver, By.Id("submitButton"));
button.Click();

6-6. Ajax・SPAサイトで要素表示を待つ方法

AjaxやSPAのサイトでは、URLが変わらないまま画面内容だけが更新されることがあります。この場合、単純なページ読み込み完了だけでは不十分です。

検索結果が表示されるまで待つ例は次のとおりです。

C#
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

IReadOnlyCollection<IWebElement> results = wait.Until(d =>
{
var items = d.FindElements(By.CssSelector(".search-result"));

return items.Count > 0 ? items : null;
});

ローディング表示が消えるまで待つ場合は、次のように書けます。

C#
wait.Until(d =>
{
var loaders = d.FindElements(By.CssSelector(".loading"));
return loaders.Count == 0 || loaders.All(x => !x.Displayed);
});

特定のテキストが表示されるまで待つこともできます。

C#
wait.Until(d =>
{
var message = d.FindElement(By.Id("status"));
return message.Text.Contains("完了");
});

AjaxやSPAでは、「どの状態になったら次へ進んでよいか」を明確に決めることが重要です。

6-7. TimeoutExceptionを防ぐ実装のコツ

TimeoutExceptionは、指定時間内に待機条件が満たされなかったときに発生します。完全に防ぐことはできませんが、原因を分かりやすくすることはできます。

まず、待機対象のロケーターを正確にします。対象要素が本当に存在するか、開発者ツールで確認しましょう。

次に、待機条件を現実の画面状態に合わせます。要素の存在だけを待てばよいのか、表示まで待つべきなのか、クリック可能まで待つべきなのかを切り分けます。

例外時にスクリーンショットを保存するのも有効です。

C#
try
{
IWebElement button = WaitUntilClickable(driver, By.Id("submitButton"));
button.Click();
}
catch (WebDriverTimeoutException)
{
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile("timeout.png");

throw;
}

タイムアウトを長くしすぎるだけでは根本解決になりません。対象画面のHTML、表示タイミング、iframe、別タブ、ローディング表示などを確認しながら修正しましょう。

7. 実践サンプル:C# Seleniumで検索操作を自動化する

7-1. Google検索・Bing検索の自動操作サンプル

ここでは、C# Seleniumで検索エンジンの検索操作を自動化するサンプルを紹介します。実際にスクレイピングや自動アクセスを行う場合は、対象サービスの利用規約やrobots.txt、アクセス頻度に注意してください。

Bing検索を例にすると、基本的な流れは次のとおりです。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

using IWebDriver driver = new ChromeDriver();
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

driver.Navigate().GoToUrl("https://www.bing.com/");

IWebElement input = wait.Until(d => d.FindElement(By.Name("q")));
input.SendKeys("c# selenium");
input.SendKeys(Keys.Enter);

wait.Until(d => d.FindElements(By.CssSelector("li.b_algo")).Count > 0);

Console.WriteLine(driver.Title);

driver.Quit();

このコードでは、Bingを開き、検索ボックスにc# seleniumと入力し、Enterキーで検索しています。その後、検索結果が表示されるまで待っています。

7-2. 検索キーワードを入力して検索結果を取得する

検索キーワードを変数にしておくと、複数キーワードの検索にも対応しやすくなります。

C#
string keyword = "c# selenium";

driver.Navigate().GoToUrl("https://www.bing.com/");

IWebElement input = wait.Until(d => d.FindElement(By.Name("q")));
input.Clear();
input.SendKeys(keyword);
input.SendKeys(Keys.Enter);

検索結果が表示されるまで待ちます。

C#
wait.Until(d =>
{
var results = d.FindElements(By.CssSelector("li.b_algo"));
return results.Count > 0;
});

検索結果ページでは、検索結果のタイトルやリンクを取得できます。

C#
var resultItems = driver.FindElements(By.CssSelector("li.b_algo"));

foreach (var item in resultItems)
{
var titleElement = item.FindElement(By.CssSelector("h2"));
var linkElement = item.FindElement(By.CssSelector("h2 a"));

Console.WriteLine(titleElement.Text);
Console.WriteLine(linkElement.GetAttribute("href"));
}

7-3. 検索結果タイトル・URLを一覧取得する

検索結果を扱いやすくするために、結果用のクラスを作成します。

C#
public class SearchResult
{
public string Title { get; set; } = "";
public string Url { get; set; } = "";
}

検索結果をList<SearchResult>に格納するコードは次のとおりです。

C#
var results = new List<SearchResult>();

var items = driver.FindElements(By.CssSelector("li.b_algo"));

foreach (var item in items)
{
var link = item.FindElement(By.CssSelector("h2 a"));

results.Add(new SearchResult
{
Title = link.Text,
Url = link.GetAttribute("href") ?? ""
});
}

foreach (var result in results)
{
Console.WriteLine($"{result.Title} - {result.Url}");
}

取得時には、要素が存在しない検索結果ブロックが混ざることもあります。実務では、try-catchFindElementsを使って安全に取得する方が安定します。

C#
foreach (var item in items)
{
var links = item.FindElements(By.CssSelector("h2 a"));

if (links.Count == 0)
{
continue;
}

var link = links.First();

results.Add(new SearchResult
{
Title = link.Text,
Url = link.GetAttribute("href") ?? ""
});
}

7-4. 複数ページを巡回する処理

検索結果の複数ページを巡回する場合は、現在ページの結果を取得し、次ページリンクをクリックする処理を繰り返します。

C#
for (int page = 1; page <= 3; page++)
{
wait.Until(d => d.FindElements(By.CssSelector("li.b_algo")).Count > 0);

var items = driver.FindElements(By.CssSelector("li.b_algo"));

foreach (var item in items)
{
var links = item.FindElements(By.CssSelector("h2 a"));

if (links.Count == 0)
{
continue;
}

var link = links.First();
Console.WriteLine($"[{page}] {link.Text}");
Console.WriteLine(link.GetAttribute("href"));
}

var nextLinks = driver.FindElements(By.CssSelector("a.sb_pagN"));

if (nextLinks.Count == 0)
{
break;
}

nextLinks.First().Click();
}

複数ページ巡回では、次ページをクリックした後に前ページの要素を再利用しないよう注意してください。ページ遷移後は、要素参照が古くなるため、再度FindElementsで取得し直します。

また、検索エンジンやWebサイトに対して大量の自動アクセスを行うと、利用規約違反やアクセス制限の対象になる可能性があります。実務では、APIが提供されている場合はAPI利用を優先しましょう。

7-5. CSVやExcelへ取得結果を保存する考え方

取得した検索結果は、CSVやExcelに保存できます。CSVであれば、標準のSystem.IOだけでも簡単に出力できます。

C#
using System.Text;

var lines = new List<string>
{
"Title,Url"
};

foreach (var result in results)
{
string title = result.Title.Replace("\"", "\"\"");
string url = result.Url.Replace("\"", "\"\"");

lines.Add($"\"{title}\",\"{url}\"");
}

File.WriteAllLines("results.csv", lines, Encoding.UTF8);

Excel形式で保存したい場合は、ClosedXMLEPPlusなどのライブラリを使う方法があります。ただし、まずはCSVで保存する方がシンプルです。

業務用途では、取得日時、検索キーワード、順位、タイトル、URL、説明文などを列として保存すると、後から分析しやすくなります。

C#
public class SearchResult
{
public string Keyword { get; set; } = "";
public int Rank { get; set; }
public string Title { get; set; } = "";
public string Url { get; set; } = "";
public DateTime CreatedAt { get; set; }
}

8. C# Seleniumでよく使う応用操作

8-1. スクロール処理を実装する

画面外の要素をクリックしたい場合や、無限スクロールのページを操作したい場合は、JavaScriptでスクロールします。

C#
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;

js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");

特定の要素までスクロールする場合は、次のように書きます。

C#
IWebElement element = driver.FindElement(By.Id("target"));

js.ExecuteScript("arguments[0].scrollIntoView(true);", element);

スクロール後にクリックする例です。

C#
IWebElement button = driver.FindElement(By.Id("submitButton"));

js.ExecuteScript("arguments[0].scrollIntoView({block: 'center'});", button);
button.Click();

無限スクロールでは、スクロール後に新しい要素が読み込まれるまで待機する必要があります。

C#
int beforeCount = driver.FindElements(By.CssSelector(".item")).Count;

js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight);");

wait.Until(d =>
{
int afterCount = d.FindElements(By.CssSelector(".item")).Count;
return afterCount > beforeCount;
});

8-2. JavaScriptを実行する

C# Seleniumでは、IJavaScriptExecutorを使ってJavaScriptを実行できます。

C#
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;

string title = (string)js.ExecuteScript("return document.title;");
Console.WriteLine(title);

要素のクリックをJavaScriptで実行することもできます。

C#
IWebElement button = driver.FindElement(By.Id("submitButton"));

js.ExecuteScript("arguments[0].click();", button);

ただし、通常のClick()が失敗するからといって、すぐにJavaScriptクリックへ逃げるのはおすすめしません。ユーザー操作に近いテストをしたい場合、通常のクリックで失敗する原因を調査することが重要です。

JavaScript実行は、スクロール、画面状態の確認、通常操作では難しい補助処理に使うのがよいでしょう。

8-3. iframe内の要素を操作する

iframe内の要素を操作するには、まずiframeへ切り替えます。

C#
driver.SwitchTo().Frame("frameName");

iframe内の要素を取得します。

C#
IWebElement input = driver.FindElement(By.Id("insideInput"));
input.SendKeys("テスト");

元のページに戻るには、DefaultContent()を使います。

C#
driver.SwitchTo().DefaultContent();

iframe要素をWebElementとして取得してから切り替えることもできます。

C#
IWebElement frame = driver.FindElement(By.CssSelector("iframe.payment-frame"));
driver.SwitchTo().Frame(frame);

要素が見つからない場合、iframeの中にあるかどうかは必ず確認しましょう。決済フォーム、広告、チャットウィジェット、埋め込みフォームなどはiframeで表示されていることが多いです。

8-4. 別タブ・別ウィンドウを操作する

別タブや別ウィンドウへ切り替えるには、WindowHandlesSwitchTo().Window()を使います。

C#
string originalWindow = driver.CurrentWindowHandle;

IWebElement link = driver.FindElement(By.CssSelector("a[target='_blank']"));
link.Click();

wait.Until(d => d.WindowHandles.Count > 1);

foreach (string handle in driver.WindowHandles)
{
if (handle != originalWindow)
{
driver.SwitchTo().Window(handle);
break;
}
}

Console.WriteLine(driver.Title);

元のウィンドウへ戻る場合は、保存しておいたハンドルを使います。

C#
driver.SwitchTo().Window(originalWindow);

現在のタブを閉じて元に戻る場合は、次のように書きます。

C#
driver.Close();
driver.SwitchTo().Window(originalWindow);

別タブ操作では、今どのウィンドウを操作しているかを常に意識することが重要です。

8-5. アラートを操作する

JavaScriptのアラートを操作するには、SwitchTo().Alert()を使います。

C#
IAlert alert = driver.SwitchTo().Alert();

Console.WriteLine(alert.Text);
alert.Accept();

キャンセルする場合は、Dismiss()を使います。

C#
alert.Dismiss();

入力付きのプロンプトでは、SendKeys()を使います。

C#
IAlert prompt = driver.SwitchTo().Alert();

prompt.SendKeys("入力テキスト");
prompt.Accept();

アラートが表示されるまで待つには、次のようにします。

C#
IAlert alert = wait.Until(d =>
{
try
{
return d.SwitchTo().Alert();
}
catch (NoAlertPresentException)
{
return null;
}
});

アラートが出ている間は、通常のページ要素を操作できません。アラートを承認またはキャンセルしてから次の処理へ進みましょう。

8-6. スクリーンショットを保存する

スクリーンショットを保存するには、ITakesScreenshotを使います。

C#
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile("screenshot.png");

エラー発生時にスクリーンショットを保存すると、原因調査がしやすくなります。

C#
try
{
driver.FindElement(By.Id("submitButton")).Click();
}
catch (Exception)
{
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile($"error_{DateTime.Now:yyyyMMdd_HHmmss}.png");

throw;
}

テストコードでは、失敗時にスクリーンショットをテスト結果へ添付する設計にすると便利です。

8-7. ファイルアップロードを自動化する

HTMLのinput type="file"にファイルパスを送ることで、ファイルアップロードを自動化できます。

C#
IWebElement fileInput = driver.FindElement(By.CssSelector("input[type='file']"));

fileInput.SendKeys(@"C:\Temp\sample.pdf");

ファイル選択ダイアログをSeleniumで直接操作するのではなく、input[type='file']に直接パスを渡すのが基本です。

ただし、アップロードUIが独自実装で、ファイル入力要素が非表示になっている場合があります。その場合でも、HTML内にinput[type='file']が存在していれば、そこへSendKeys()できることがあります。

アップロード後は、ファイル名が表示される、アップロード完了メッセージが出るなど、完了条件を待機しましょう。

C#
wait.Until(d =>
{
var message = d.FindElement(By.Id("uploadStatus"));
return message.Text.Contains("完了");
});

9. C# Seleniumで発生しやすいエラーと解決策

9-1. NoSuchElementExceptionの原因と対処法

NoSuchElementExceptionは、指定した条件に一致する要素が見つからないときに発生します。

主な原因は、ロケーターの指定ミス、要素の読み込み前に探している、iframe内にある、別タブを操作している、ログイン状態や画面条件が想定と違う、といったものです。

対処法としては、まず開発者ツールで対象要素を確認します。idclassが動的に変わるサイトでは、固定値として使えないことがあります。

次に、待機処理を入れます。

C#
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

IWebElement element = wait.Until(d =>
d.FindElement(By.Id("target"))
);

存在しない可能性がある要素には、FindElementsを使うと安全です。

C#
var elements = driver.FindElements(By.Id("target"));

if (elements.Count > 0)
{
elements.First().Click();
}

9-2. ElementClickInterceptedExceptionの原因と対処法

ElementClickInterceptedExceptionは、クリックしようとした要素が別の要素に覆われているときなどに発生します。

よくある原因は、ローディング画面、モーダル、固定ヘッダー、Cookie同意バナー、アニメーション中の要素です。

まず、クリック対象が表示され、有効であることを待ちます。

C#
IWebElement button = wait.Until(d =>
{
var e = d.FindElement(By.Id("submitButton"));
return e.Displayed && e.Enabled ? e : null;
});

次に、要素を画面中央へスクロールします。

C#
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;

js.ExecuteScript("arguments[0].scrollIntoView({block: 'center'});", button);
button.Click();

Cookieバナーやモーダルが邪魔している場合は、先に閉じる処理を入れます。

C#
var closeButtons = driver.FindElements(By.CssSelector(".modal-close"));

if (closeButtons.Count > 0 && closeButtons.First().Displayed)
{
closeButtons.First().Click();
}

9-3. StaleElementReferenceExceptionの原因と対処法

StaleElementReferenceExceptionは、取得済みの要素参照が古くなったときに発生します。

たとえば、ページ更新、Ajaxによる再描画、画面遷移、リストの再生成があると、以前取得したIWebElementは使えなくなることがあります。

悪い例は次のようなコードです。

C#
IWebElement button = driver.FindElement(By.Id("submitButton"));

driver.Navigate().Refresh();

button.Click();

ページ更新後に、更新前のbuttonを使っているため失敗します。

対処法は、操作直前に要素を再取得することです。

C#
driver.Navigate().Refresh();

IWebElement button = wait.Until(d =>
d.FindElement(By.Id("submitButton"))
);

button.Click();

一覧処理でも、ページ遷移や再描画を挟む場合は、毎回取得し直す設計にします。

9-4. WebDriverExceptionの原因と対処法

WebDriverExceptionは、WebDriverやブラウザ側で発生する広い範囲のエラーです。

原因としては、ブラウザドライバーの起動失敗、ChromeDriverとChromeのバージョン不一致、ブラウザがクラッシュした、ドライバーのパスが不正、権限不足、ヘッドレス環境の設定不足などがあります。

まず確認すべきことは、最小コードでブラウザが起動するかです。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

using IWebDriver driver = new ChromeDriver();

driver.Navigate().GoToUrl("https://example.com");
Console.WriteLine(driver.Title);

driver.Quit();

このコードが失敗する場合は、アプリの処理ではなく環境構築に問題がある可能性が高いです。

CI環境やLinux環境でChromeを動かす場合は、ヘッドレス設定や依存ライブラリが必要になることがあります。まずはローカル環境で動作確認し、その後CI環境へ移すと切り分けやすくなります。

9-5. ChromeDriverのバージョン不一致を解決する方法

ChromeDriverのバージョン不一致は、C# Seleniumでよくあるエラーのひとつです。

従来は、Chromeのバージョンを確認し、対応するChromeDriverを手動でダウンロードする必要がありました。現在はSelenium Managerにより、ドライバー管理を自動化しやすくなっています。公式ドキュメントでも、最近のSeleniumではシステムにバージョンが見つからない場合、Selenium Managerが自動的にダウンロードする旨が説明されています。Selenium

まずは、Selenium.WebDriverを最新の安定版へ更新し、手動で古いChromeDriverを参照していないか確認します。

Bash
dotnet add package Selenium.WebDriver

もし手動配置しているChromeDriverを使っている場合は、Chrome本体のバージョンに合うものへ更新します。

また、PATHに古いchromedriver.exeが残っていると、意図せず古いドライバーが使われることがあります。コマンドラインで確認しましょう。

Bash
where chromedriver

複数表示される場合は、不要な古いChromeDriverを削除するか、PATHの優先順位を見直します。

9-6. 動くコードと動かないコードを切り分けるデバッグ方法

C# Seleniumのデバッグでは、まず問題を小さく切り分けます。

最初に、ブラウザ起動だけの最小コードを動かします。次に、対象URLへのアクセスだけを確認します。その後、要素取得、入力、クリック、待機処理を1つずつ追加します。

ログを出すことも重要です。

C#
Console.WriteLine("ページを開きます");
driver.Navigate().GoToUrl(url);

Console.WriteLine("検索ボックスを探します");
IWebElement input = wait.Until(d => d.FindElement(By.Name("q")));

Console.WriteLine("キーワードを入力します");
input.SendKeys(keyword);

例外発生時には、現在のURL、タイトル、スクリーンショット、HTMLの一部を保存すると原因を調べやすくなります。

C#
catch (Exception)
{
Console.WriteLine(driver.Url);
Console.WriteLine(driver.Title);

var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile("error.png");

File.WriteAllText("page.html", driver.PageSource);

throw;
}

「どの行で失敗しているか」「その時点の画面はどうなっているか」を確認できるようにすることが、安定したC# Selenium開発の近道です。

10. C# Seleniumを安定して運用するための設計ポイント

10-1. 要素取得ロジックを共通化する

Seleniumコードが増えると、同じようなFindElementや待機処理が何度も出てきます。そのまま書き続けると、ロケーター変更時の修正箇所が増え、保守が大変になります。

よく使う待機付き要素取得を共通化しましょう。

C#
public static class WebDriverExtensions
{
public static IWebElement WaitFindElement(
this IWebDriver driver,
By by,
int seconds = 10)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(seconds));

return wait.Until(d =>
{
var element = d.FindElement(by);
return element.Displayed ? element : null;
});
}

public static IWebElement WaitClickable(
this IWebDriver driver,
By by,
int seconds = 10)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(seconds));

return wait.Until(d =>
{
var element = d.FindElement(by);
return element.Displayed && element.Enabled ? element : null;
});
}
}

使う側は次のように簡潔に書けます。

C#
driver.WaitFindElement(By.Name("q")).SendKeys("c# selenium");
driver.WaitClickable(By.Id("submitButton")).Click();

共通化すると、待機時間や例外時のログ出力も一元管理できます。

10-2. Page Object Modelで保守性を高める

E2EテストでC# Seleniumを使う場合は、Page Object Modelの考え方が役立ちます。

Page Object Modelとは、画面ごとにクラスを作り、要素取得や操作処理をそのクラスにまとめる設計です。

たとえば、ログインページを次のように表現します。

C#
public class LoginPage
{
private readonly IWebDriver _driver;

private By EmailInput => By.Id("email");
private By PasswordInput => By.Id("password");
private By LoginButton => By.Id("loginButton");

public LoginPage(IWebDriver driver)
{
_driver = driver;
}

public void Login(string email, string password)
{
_driver.FindElement(EmailInput).Clear();
_driver.FindElement(EmailInput).SendKeys(email);

_driver.FindElement(PasswordInput).Clear();
_driver.FindElement(PasswordInput).SendKeys(password);

_driver.FindElement(LoginButton).Click();
}
}

テストコード側は、画面の細かいHTML構造を意識せずに書けます。

C#
var loginPage = new LoginPage(driver);
loginPage.Login("user@example.com", "password");

画面のHTMLが変わった場合も、修正箇所をPage Object内に集約できます。

10-3. テストコードで使う場合のNUnit・xUnit連携

C# Seleniumは、NUnitやxUnitと組み合わせてE2Eテストとして実行できます。

NUnitの例です。

C#
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

[TestFixture]
public class LoginTests
{
private IWebDriver _driver = null!;

[SetUp]
public void SetUp()
{
_driver = new ChromeDriver();
}

[Test]
public void Login_Should_Succeed()
{
_driver.Navigate().GoToUrl("https://example.com/login");

_driver.FindElement(By.Id("email")).SendKeys("user@example.com");
_driver.FindElement(By.Id("password")).SendKeys("password");
_driver.FindElement(By.Id("loginButton")).Click();

Assert.That(_driver.Title, Does.Contain("マイページ"));
}

[TearDown]
public void TearDown()
{
_driver.Quit();
}
}

xUnitの場合は、コンストラクタとDispose()を使って前処理・後処理を管理できます。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using Xunit;

public class LoginTests : IDisposable
{
private readonly IWebDriver _driver;

public LoginTests()
{
_driver = new ChromeDriver();
}

[Fact]
public void Login_Should_Succeed()
{
_driver.Navigate().GoToUrl("https://example.com/login");

_driver.FindElement(By.Id("email")).SendKeys("user@example.com");
_driver.FindElement(By.Id("password")).SendKeys("password");
_driver.FindElement(By.Id("loginButton")).Click();

Assert.Contains("マイページ", _driver.Title);
}

public void Dispose()
{
_driver.Quit();
}
}

テストでは、失敗時のスクリーンショット保存やログ出力も組み込むと、CIでの調査がしやすくなります。

10-4. スクレイピングで使う場合の注意点

C# Seleniumはスクレイピングにも使えますが、注意点があります。

まず、対象サイトの利用規約を確認してください。自動取得が禁止されているサイトもあります。

次に、アクセス頻度を抑えることが重要です。短時間に大量アクセスすると、対象サイトに負荷をかけたり、アクセス制限の対象になったりします。

また、ログインが必要なサイト、個人情報を含むサイト、有料コンテンツを含むサイトでは、法的・契約上の問題が発生しやすいため慎重に扱う必要があります。

可能であれば、公式APIを利用する方が安全で安定しています。Seleniumは実ブラウザを操作するため、HTML構造や画面仕様の変更に影響されやすいからです。

スクレイピング用途では、取得対象、取得頻度、保存するデータ、エラー時の再試行、停止条件を明確に設計しましょう。

10-5. 自動操作が失敗しにくい待機・リトライ設計

安定したC# Seleniumコードを書くには、待機とリトライの設計が重要です。

まず、すべての操作をすぐ実行するのではなく、操作前に状態を確認します。

C#
IWebElement button = driver.WaitClickable(By.Id("submitButton"));
button.Click();

次に、一時的な失敗に対してリトライできるようにします。

C#
public static void Retry(Action action, int maxAttempts = 3)
{
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
action();
return;
}
catch (StaleElementReferenceException)
{
if (attempt == maxAttempts)
{
throw;
}
}
}
}

使う側は次のように書けます。

C#
Retry(() =>
{
var button = driver.WaitClickable(By.Id("submitButton"));
button.Click();
});

ただし、何でもリトライすればよいわけではありません。ロケーターが間違っている、仕様が変わった、ログインできていないといった根本原因は、リトライしても解決しません。

リトライは、一時的な描画遅延やDOM再生成に限定して使いましょう。

10-6. CI環境でC# Seleniumを実行する考え方

CI環境でC# Seleniumを実行する場合は、ヘッドレスモードを使うことが多いです。

C#
var options = new ChromeOptions();

options.AddArgument("--headless=new");
options.AddArgument("--window-size=1280,800");

using IWebDriver driver = new ChromeDriver(options);

CIでは、ローカル環境と異なる点に注意が必要です。画面サイズ、フォント、ネットワーク速度、権限、ダウンロードフォルダ、ブラウザのインストール状況などが違うため、ローカルでは成功するテストがCIで失敗することがあります。

CIで安定させるには、次のような工夫が有効です。

C#
options.AddArgument("--disable-dev-shm-usage");
options.AddArgument("--no-sandbox");
options.AddArgument("--window-size=1280,800");

ただし、--no-sandboxは環境によって必要な場合がありますが、セキュリティ上の意味を理解したうえで使うべきです。

CI実行時は、失敗時のスクリーンショット、HTML、ログを成果物として保存すると、原因調査がしやすくなります。

11. C# Selenium学習の次のステップ

11-1. まず覚えるべき基本メソッド一覧

C# Selenium入門者がまず覚えるべき基本メソッドは、次のとおりです。

ブラウザ操作では、Navigate().GoToUrl()Navigate().Back()Navigate().Forward()Navigate().Refresh()Quit()を使います。

要素取得では、FindElement()FindElements()By.Id()By.Name()By.CssSelector()By.XPath()を覚えましょう。

要素操作では、Click()SendKeys()Clear()Submit()をよく使います。

情報取得では、TextGetAttribute()DisplayedEnabledSelectedが重要です。

待機処理では、WebDriverWaitUntil()を使えるようにしましょう。

まずは、次の流れを自力で書けるようになることが目標です。

C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

using IWebDriver driver = new ChromeDriver();
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));

driver.Navigate().GoToUrl("https://www.bing.com/");

IWebElement input = wait.Until(d => d.FindElement(By.Name("q")));
input.SendKeys("c# selenium");
input.SendKeys(Keys.Enter);

wait.Until(d => d.FindElements(By.CssSelector("li.b_algo")).Count > 0);

Console.WriteLine(driver.Title);

driver.Quit();

11-2. 実務でよく使う自動化パターン

実務でよく使うC# Seleniumのパターンには、ログイン自動化、検索条件入力、一覧データ取得、詳細ページ巡回、ファイルダウンロード、ファイルアップロード、スクリーンショット保存、E2Eテストがあります。

ログイン自動化では、IDとパスワードを入力し、ログイン後の画面表示を待ちます。

検索条件入力では、テキストボックス、セレクトボックス、チェックボックス、日付入力を組み合わせます。

一覧データ取得では、FindElementsで行要素を取得し、各行からテキストやリンクを抽出します。

詳細ページ巡回では、一覧ページでURLを収集してから、各詳細ページへアクセスする設計にすると安定します。

ファイルダウンロードでは、ChromeOptionsでダウンロード先を固定し、ダウンロード完了をファイル存在確認で待つ方法が使えます。

E2Eテストでは、Page Object Model、待機処理、失敗時の証跡保存を組み合わせることが重要です。

11-3. Selenium公式ドキュメントの活用方法

C# Seleniumを学ぶうえで、公式ドキュメントの活用は重要です。Seleniumの公式ドキュメントには、WebDriver、要素取得、ブラウザオプション、ドライバーサービス、Selenium Managerなどの情報が整理されています。Seleniumの日本語ドキュメントでも、Seleniumの中核はWebDriverであり、さまざまなブラウザに対して命令を実行できるインターフェースであることが説明されています。Selenium

特に確認しておきたいテーマは、次のとおりです。

WebDriverの基本、ロケーター戦略、要素操作、待機処理、ブラウザオプション、Selenium Manager、Grid、テストのベストプラクティスです。

ブログ記事やサンプルコードは便利ですが、Seleniumのバージョンが古い場合があります。コードが動かない場合は、公式ドキュメントやNuGetの現在のパッケージ情報を確認しましょう。

11-4. PlaywrightやPuppeteerも比較検討すべきケース

C# Seleniumは強力ですが、すべてのケースで最適とは限りません。

モダンなSPAのE2Eテストを新規で作る場合は、Playwrightも比較対象になります。Playwrightは自動待機の仕組みが強く、複数ブラウザ対応やテストランナーとの統合も使いやすいです。

Puppeteerは、ChromeやChromiumの自動操作でよく使われます。Node.js環境との相性がよく、PDF生成やブラウザ操作にも使われます。

一方で、C#や.NETの既存資産がある、NUnitやxUnitと連携したい、既にSeleniumのテスト資産がある、複数言語や複数環境で運用している場合は、C# Seleniumを選ぶメリットがあります。

重要なのは、目的に合わせて選ぶことです。既存の.NETプロジェクトに組み込むならC# Selenium、新規のE2Eテスト基盤を作るならPlaywrightも候補、Chrome中心の軽量な自動化ならPuppeteerも候補になります。

11-5. C# Selenium入門後に作るべき練習アプリ・練習コード

C# Seleniumを学んだら、実際に小さな練習コードを作るのがおすすめです。

最初の練習として、指定URLを開いてタイトルを表示するコードを書きます。

次に、検索フォームにキーワードを入力し、検索結果のタイトルとURLを取得するコードを作ります。

その後、取得結果をCSVへ保存する処理を追加します。

さらに、ログインフォームのある練習用ページを用意し、IDとパスワードを入力してログイン後のメッセージを確認するテストを作ります。

慣れてきたら、Page Object Modelで画面クラスを分け、NUnitやxUnitでテストとして実行できるようにします。

おすすめの練習テーマは、次のようなものです。

C#
// 練習テーマ例
// 1. ページタイトル取得
// 2. 検索フォーム操作
// 3. 検索結果一覧取得
// 4. CSV保存
// 5. ログイン画面のE2Eテスト
// 6. スクリーンショット保存
// 7. iframe操作
// 8. 別タブ操作
// 9. ファイルアップロード
// 10. Page Object Model化

小さなコードを積み上げることで、C# Seleniumの基本操作から実務的な設計まで自然に身につきます。

まとめ

C# Seleniumは、C#からブラウザを自動操作できる便利な技術です。Webサイトを開く、フォームへ入力する、ボタンをクリックする、要素のテキストや属性を取得する、検索結果を一覧化する、E2Eテストを実行するなど、幅広い用途で使えます。

入門時にまず押さえるべきポイントは、環境構築、ブラウザ起動、要素取得、クリックや入力、待機処理です。特に待機処理は、C# Seleniumの安定性を大きく左右します。Thread.Sleepに頼るのではなく、WebDriverWaitを使って「必要な状態になるまで待つ」実装を心がけましょう。

また、実務で使う場合は、要素取得ロジックの共通化、Page Object Model、エラー時のスクリーンショット保存、リトライ設計、CI環境でのヘッドレス実行なども重要です。

C# Seleniumを習得するには、まず最小コードでChromeを起動し、検索フォームを操作し、検索結果を取得するところから始めるのがおすすめです。その後、待機処理、iframe、別タブ、スクリーンショット、ファイルアップロード、テストフレームワーク連携へ進むと、実務で使える自動化スキルにつながります。