WebDriverManagerを利用してSeleniumでのWebDriverの更新を楽にする

ブラウザでのテストだけではなく、ちょっとしたブラウザ操作の自動化にもSeleniumを利用するのですが、久しぶりに起動すると、各ブラウザのDriverが古くでエラーになる、、みたいなことが多々発生していました。

今更ながら、他の方々はどうやってこの問題を解決しているのだろうと調べてみたら、WebDriverManagerというとても便利なものがあることを知りました。

WebDriverManagerは、下記のようなコードを書くだけで、、

WebDriverManager.chromedriver().setup();
  • PCにインストールされているブラウザのバージョンをチェック。
  • バージョンと一致するDriverをダウンロード。ローカルにキャッシュするので、2回目以降はキャッシュが利用される。
  • そのDriverを実行するのに必要なシステム変数(webdriver.chrome.driverとか)を設定。

といったことをやってくれます。これを使えば、自分でいちいち最新のDriverをダウンロードしてくるといったことをする必要はありません。

個人のプロダクトでSelenium使っているものは、全部WebDriverManagerに置き換えました。

サンプルとしてRedmineに対してのテストをGradleプロジェクトとして書いているので、気になる方はぜひ。

SeleniumでChromeをヘッドレスモードで動作させた場合に org.openqa.selenium.ElementNotInteractableException: element not interactable が発生する

SeleniumでChromeDriverで実行した際に、ヘッドレスモードだとエラー、ヘッドレスモードにしないと成功するといったテストがありました。

エラー内容は下記のような感じで、要素が操作できないといったものでした。

org.openqa.selenium.ElementNotInteractableException: element not interactable
  (Session info: headless chrome=86.0.4240.183)
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:17:03'
System info: host: 'DESKTOP-U275NQ4', ip: '192.168.33.1', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_222'
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 86.0.4240.183, chrome: {chromedriverVersion: 86.0.4240.22 (398b0743353ff..., userDataDir: C:\Users\xxxxx\AppData\Lo...}, goog:chromeOptions: {debuggerAddress: localhost:57435}, javascriptEnabled: true, networkConnectionEnabled: false, pageLoadStrategy: normal, platform: WINDOWS, platformName: WINDOWS, proxy: Proxy(), setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:virtualAuthenticators: true}
Session ID: 467a08d7a335a893b5ec767a330cc821
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.createException(W3CHttpResponseCodec.java:187)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:122)
    at org.openqa.selenium.remote.http.W3CHttpResponseCodec.decode(W3CHttpResponseCodec.java:49)
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:158)
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:83)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:552)
    at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:285)
    at org.openqa.selenium.remote.RemoteWebElement.click(RemoteWebElement.java:84)
    at com.github.onozaty.selenium.RedmineTest.login(RedmineTest.java:114)
    at com.github.onozaty.selenium.RedmineTest.チケット作成(RedmineTest.java:41)

調べたところ、下記のような記事を見つけました。

ヘッドレスモードの場合、ウインドウのデフォルトサイズは 800 × 600 になり、ヘッドレスモードではないときよりサイズが小さくなるため、このような現象になるようです。

下記のような形で、ウインドウサイズを指定してあげることで回避できました。

        ChromeOptions options = new ChromeOptions()
                .setHeadless(true)
                .addArguments("-window-size=1280,1024");
        WebDriver driver = new ChromeDriver(options);

XMLをCSVに変換するツール xml2csvを作りました。

XMLをCSVに変換するツールxml2csvを作りました。
JUnitのXMLをCSVに変換する処理書いていて、これは汎用的にできるなーってことで、Goの勉強も兼ねて書きました。

マッピング情報を元に、XMLからCSVに変換します。

$ xml2csv -i input.xml -m mapping.json -o output.csv
Usage of xml2csv:
  -b    CSV with BOM
  -h    Help
  -i string
        XML input file path or directory or url
  -m string
        XML to CSV mapping file path or url
  -o string
        CSV output file path

マッピング情報は下記のような感じです。
XPathを使って行の一覧と各カラムを指定するようなイメージです。

{
    "rowsPath": "//item",
    "columns": [
        {
            "header": "title",
            "valuePath": "/title"
        },
        {
            "header": "link",
            "valuePath": "/link"
        },
        {
            "header": "description",
            "valuePath": "/description"
        }
    ]
}

マッピング情報の各項目の説明は、下記をご参照ください。

実行ファイルは下記からダウンロードできます。

特定ユーザに対して一部管理者メニューを非表示にする(Redmine View Customize Plugin)

特定ユーザに対して一部管理者メニューを非表示にしたいといった要望です。(2つあって、2つ目の方)

一律非表示ならば、CSSで出来るのですが、特定ユーザに対してのみ適用したいのでJavaScriptで書く必要があります。

以下のサンプルでは、ユーザID:10のユーザに対して、ワークフロー、選択肢の値、設定の3つを非表示にしています。

設定内容

  • Insertion position: Head of all pages
$(function() {

  // user_id = 10 に対して非表示に
  if (ViewCustomize.context.user.id == 10) {

    $('li:has(a[href="/workflows/edit"])').hide();  // Workflow
    $('li:has(a[href="/enumerations"])').hide();    // Enumerations
    $('li:has(a[href="/settings"])').hide();        // Settings
  }
});

動作

左がユーザID:10で、右がその他のユーザです。

f:id:onozaty:20200921224717p:plain

特定ユーザを削除、ロックできないようにする(Redmine View Customize Plugin)

初期ユーザ(user_id=1)を、削除やロックできないようにしたいという要望です。(2つあって1つ目の方)

ボタンを非表示にするだけなので、CSSで対応します。

設定内容

  • Insertion position: Head of all pages
a[data-method="put"][href="/users/1?user%5Bstatus%5D=3"],
a[data-method="delete"][href^="/users/1"] {
  display: none;
}

動作

ユーザ一覧で表示されていたロック、削除ボタンが、

f:id:onozaty:20200920233656p:plain

下記のように非表示になります。

f:id:onozaty:20200920233515p:plain

また、ユーザの詳細画面でもボタンが消えます。

f:id:onozaty:20200920233623p:plain

Redmine Japan 2020 で「Redmineの画面をあなた好みにカスタマイズ – View customize pluginの紹介 –」というタイトルで発表しました

Redmine Japan 2020というイベントでView customize pluginについて発表しました。

資料はこちら。

まつもとさんの基調講演、前田さんの招待講演と続いて、いろいろな方々の経験にもとづく話が聞けて良かったです。
仕事があったので、自分の発表が終わったら抜けたのですが、ほんと最後まで聞けなくて残念でした。

自分の発表は、時間がギリギリになってしまったため、最後かなり巻きになってしまいました。時間配分考えて臨んだつもりが、、今回の失敗は次に生かそうと思います。
今回の発表でView customize pluginに興味持っていただいた方が一人でもいらっしゃったらうれしいです。

ありがとうございました。

JUnitの結果をCSVファイルに変換するツール(junit-xml2csv)を作りました

JUnitの結果を一覧で見たいなーってことで、CSVファイルに変換するツールを作りました。遅いテストを探すのに、EXCELでソートしたかったからです。

JUnitの結果XMLファイルが配置されているディレクトリ(Gradleだとbuild/test-results/testとか)と、出力ファイル名を指定すると、XMLファイル読み込んでCSVに変換します。

こんな感じのCSVになります。

TestSuite: Name,TestSuite: Timestamp,TestCase: ClassName,TestCase: Name,TestCase: Time,TestCase: Result
com.github.onozaty.junit.xml2csv.TestCase1,2020-08-28T04:39:49,com.github.onozaty.junit.xml2csv.TestCase1,test1,0.0,PASSED
com.github.onozaty.junit.xml2csv.TestCase1,2020-08-28T04:39:49,com.github.onozaty.junit.xml2csv.TestCase1,test2,0.0,FAILURE
com.github.onozaty.junit.xml2csv.TestCase1,2020-08-28T04:39:49,com.github.onozaty.junit.xml2csv.TestCase1,test3,0.001,ERROR
com.github.onozaty.junit.xml2csv.TestCase1,2020-08-28T04:39:49,com.github.onozaty.junit.xml2csv.TestCase1,test4,0.0,SKIPED
com.github.onozaty.junit.xml2csv.TestCase1,2020-08-28T04:39:49,com.github.onozaty.junit.xml2csv.TestCase1,test5,0.002,PASSED
com.github.onozaty.junit.xml2csv.TestCase2,2020-08-28T04:39:50,com.github.onozaty.junit.xml2csv.TestCase2,test1,0.0,PASSED
com.github.onozaty.junit.xml2csv.TestCase2,2020-08-28T04:39:50,com.github.onozaty.junit.xml2csv.TestCase2,test2,0.002,FAILURE