Selenium WebDriverでクライアント証明書を使う時の問題
selenium-2.46.0、IE8を使用。
Selenium WebDriverでクライアント証明書を使う時の問題を解決する。
- IEではWindows セキュリティのポップアップを制御できない問題
- ブラウザなしで動作するHtmlUnitDriverでは、クライアント証明書を設定する機能がない問題
解決策
AWTを使って力ずくでEnterを押す。
WebDriverがポップアップを認識できないという問題、また画面上でOKを押すまでWebDriverから通信の応答が返らない(=
new InternetExplorerDriver()
が終わらない)という問題に対応するため、非同期にAWTを使ってEnterキーを押下する。
ソースコードのpressEnterAsynchronously()
を参照。HtmlUnitDriverクラスを拡張して、SSLクライアント認証の機能をつける。
HtmlUnitDriverはWebClientというブラウザを模したオブジェクトを所有しており、WebClientOptionsというオプションによってJavaScriptを有効にするなど、ブラウザの設定が制御できる。
WebClientOptionsは元々setSSLClientCertificate()
というクライアント証明書を設定するメソッドを持っているため、HtmlUnitDriverからsetSSLClientCertificate()
を呼べるようにするだけで良い。
今回はコンストラクタで設定できるように拡張した。import java.awt.AWTException; import java.awt.Robot; import java.awt.event.KeyEvent; import java.io.File; import java.net.MalformedURLException; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.openqa.selenium.ie.InternetExplorerDriver; import org.openqa.selenium.ie.InternetExplorerDriverService; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import com.gargoylesoftware.htmlunit.WebClientOptions; public class WebDriverSample { static WebDriver driver; /*------------ 実行環境設定 ---------------*/ static final String IE_DRIVER_EXE = "C:\\IEDriverServer.exe"; static final String CLIENT_CERT = "C:\\client.p12"; static final String CLIENT_CERT_PASS = "password"; static final String CERTIFICATE_TYPE = "pkcs12"; /*------------ サイト設定 ---------------*/ static final String SITE_URL = "https://example.com/test/"; /*------------ ログインページ ---------------*/ static final String $LOGIN_ID = "user_id"; static final String $PASSWORD = "password"; static final String $BTN_LOGIN = "#btn_login"; static final String LOGIN_ID = "user_a"; static final String LOGIN_PASS = "mypassword"; /*------------ ポータルページ ---------------*/ static final By BY_P_FORM = By.name("portalForm"); public static void main(String[] args) { String browser = args.length > 0 ? args[0] : ""; createDriver(browser); try { $($LOGIN_ID).sendKeys(LOGIN_ID); WebElement password = $($PASSWORD); // パスワードの自動補完がされた場合を考えて、一旦クリア password.clear(); password.sendKeys(LOGIN_PASS); $($BTN_LOGIN).click(); // 第二引数で最大の待機時間(秒)を設定 WebDriverWait wait = new WebDriverWait(driver, 10); // 条件を満たすまで待つ wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(BY_P_FORM)); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(driver.getPageSource()); driver.quit(); } } private static void createDriver(String browser) { switch (browser) { case "IE": createIEDriver(SITE_URL); break; case "HEADLESS": createHeadlessDriver(SITE_URL); break; default : throw new IllegalArgumentException("args[0] requires browser type: IE, HEADLESS"); } } /*------------ IE用 ---------------*/ private static void createIEDriver(String url) { // IEDriverServer.exeの指定 System.setProperty( InternetExplorerDriverService.IE_DRIVER_EXE_PROPERTY, IE_DRIVER_EXE); DesiredCapabilities caps = DesiredCapabilities.internetExplorer(); // 以下、対策をしていないときに発生するSessionNotFoundExceptionの抑制 // ・「保護モードを有効にする」の設定をすべてのゾーン(インターネット、ローカルイントラネット、信頼済みサイト及び制限付きサイト)で統一する。 // ・「信頼済みサイト」ゾーンにテスト対象のサイトを追加する。 caps.setCapability(InternetExplorerDriver.INTRODUCE_FLAKINESS_BY_IGNORING_SECURITY_DOMAINS, true); // ブラウザ起動時のURL caps.setCapability(InternetExplorerDriver.INITIAL_BROWSER_URL, url); // クライアント証明書の選択を行う「Windows セキュリティ」ポップアップに対して、AWTを使って力ずくでEnterを押す。 // Threadで非同期実行している理由はRemoteWebDriver#startSessionの以下コードが、画面上でOKを押すまで応答しないから。 // Response response = execute(DriverCommand.NEW_SESSION, parameters); pressEnterAsynchronously(); driver = new InternetExplorerDriver(caps); initialized = true; } static boolean initialized = false; static final int delay = 8000; static final int interval = 2000; private static void pressEnterAsynchronously() { (new Thread() { @Override public void run() { Robot robot = new Robot(); robot.delay(delay); while (true) { try { System.out.println("press enter"); robot.keyPress(KeyEvent.VK_ENTER); if (initialized) { return; } robot.delay(interval); } catch (AWTException e) { e.printStackTrace(); return; } } } }).start(); } /*------------ ブラウザなし用 ---------------*/ private static void createHeadlessDriver(String url) { driver = new HtmlUnitDriverEx(CLIENT_CERT, CLIENT_CERT_PASS, CERTIFICATE_TYPE); driver.get(url); } /*------------ Utility ---------------*/ private static WebElement $(String name) { if (name.startsWith("#")) { return driver.findElement(By.id(name.substring(1))); } return driver.findElement(By.name(name)); } } /** * <pre> * HtmlUnitDriverの拡張 * SSLクライアント認証(SSLClientCertificate)を使えるようにした。 * </pre> */ class HtmlUnitDriverEx extends HtmlUnitDriver { public HtmlUnitDriverEx(String certificatePath, String certificatePassword, String certificateType) { super(true); // turn off htmlunit warnings java.util.logging.Logger.getLogger("com.gargoylesoftware.htmlunit").setLevel(java.util.logging.Level.OFF); java.util.logging.Logger.getLogger("org.apache.http").setLevel(java.util.logging.Level.OFF); WebClientOptions options = super.getWebClient().getOptions(); try { options.setSSLClientCertificate(new File(certificatePath).toURI().toURL(), certificatePassword, certificateType); } catch (MalformedURLException e) { e.printStackTrace(); } } }