8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

docker-machineでselenium実行時にダウンロードしたファイル名がおかしくなる

Last updated at Posted at 2016-04-27

docker-machineでselenium実行時にダウンロードしたファイル名がおかしくなる

docker-machine固有の問題なのかどうかわからないのですが、コンテナ上で実行しているseleniumでファイルをダウンロードした際に何故かダウンロードしたファイル名がおかしくなる(どうおかしいかは後述)という問題でかなりハマったのでその作業メモ

  • 環境
    • ruby 2.3.0
    • docker-machine 0.7.0 (virtual box)
    • docker 1.11.0

 seleniumでテストする準備

まず問題のファイルダウンロードの前にseleniumでテストするための環境です。

seleniumの操作環境構築

seleniumの操作はrubyで行おうと思うのでgemのインストールです。

  • 環境
    • ruby 2.3.0
bundle init

echo "gem 'selenium-webdriver'" >> Gemfile

bundle install --path=vendor/bundle

seleniumコンテナ起動

こちらはすごく簡単で、公式のコンテナを起動するだけです。今回は現時点での最新版の2.53.0で試します。

docker run -d -p 4444:4444 -p 5900:5900 selenium/standalone-chrome-debug:2.53.0

ポート4444が実際にseleniumにアクセスするポートで、ポート5900がコンテナの中でブラウザが動いている様子を見るためのvncの入り口です。

vncサーバに接続

コンテナ内で動くブラウザの様子を確認するためにvncサーバに接続します。macの場合はvncクライアントが標準で入っているので、
Finder起動 -> command + Kサーバへ接続のダイアログが出てくるので、サーバアドレスのところに

vnc://<docker-machineのip>:5900

スクリーンショット 2016-04-26 23.17.32.png

を入力します。
するとパスワードを聞かれるのでsecretを入力します。
スクリーンショット 2016-04-26 23.18.51.png

下記のような画面になれば準備完了です。

スクリーンショット 2016-04-26 23.19.35.png

selenium操作スクリプト

単純にグーグルを開いて検索して検索結果を取得、という処理を書いてみましょう。

run-selenium.rb
require 'selenium-webdriver'

# webdriver操作用のユーティリティ
def with_webdriver
  driver = Selenium::WebDriver.for(
    :remote,
    url: "http://192.168.99.100:4444/wd/hub",
    desired_capabilities: :chrome
  )

  # implict_waitを設定しておくとfind_elementなどで描画待ちでまだ取得できない場合などに待ってくれる
  driver.manage.timeouts.implicit_wait = 10

  yield driver
ensure
  driver.quit
end

with_webdriver do |d|
  # gooleにアクセス
  d.navigate.to "https://www.google.co.jp"

  # 検索ボックス取得
  search_box = d.find_element(id: "lst-ib")

  # 検索ボックスに検索ワードを入れて
  search_box.send_keys("docker selenium-webdriver")
  # enter押下
  search_box.send_keys(:return)

  # 検索結果の見出しはclassが"r"のdivに囲まれた"a"タグのようなのでそれを全部表示
  d.find_elements(:class, "r").each do |result|
    puts result.text
  end

  # この状態をスクリーンショットで保存
  d.save_screenshot "google_search_result.png"
end

これを実行すると下記のように検索結果の見出行が表示された上でその時のスクリーンショットがpngで保存されます。

$ bundle exec ruby run-selenium.rb
GitHub - SeleniumHQ/docker-selenium: Docker images for ...
Docker で Selenium Grid の環境構築をもっと楽に! - Cybozu ...
selenium/hub public - Docker Hub
Docker で Selenium Grid による並列実行環境を構築 - Qiita
Dockerを使って素早くSelenium Grid環境を構築しよう | hifive ...
SeleniumサーバもDockerで。「Docker Selenium」レビュー ...
Docker Selenium. Getting Started - Selenium Conf 2015 ...
Docker Hub の Selenium Hub/Node イメージ + fig で ...
Selenium Grid with Docker | TEST DETECTIVE
Running headless Selenium WebDriver tests in Docker ...

取得したスクリーンショット(google_search_result.png)
google_search_result.png

便利ですね。

ファイルのダウンロードで問題発生

さて、次にファイルのダウンロードを行いましょう。

適当に郵便局のページから北海道の郵便番号ファイルを取得してみます。

スクリーンショット 2016-04-27 0.05.29.png

download-zip.rb
require 'selenium-webdriver'

def with_webdriver
  driver = Selenium::WebDriver.for(
    :remote,
    url: "http://192.168.99.100:4444/wd/hub",
    desired_capabilities: :chrome
  )
  driver.manage.timeouts.implicit_wait = 10
  yield driver
ensure
  driver.quit
end

with_webdriver do |d|
  d.navigate.to "http://www.post.japanpost.jp/zipcode/dl/oogaki-zip.html"

  # ダウンロードリンク取得
  link = d.find_element(:xpath, "//*[@href='oogaki/zip/01hokkai.zip']")
  link.click

  #ダウンロード待ち
  sleep 10
end

実行

bundle exec ruby download-zip.rb

ダウンロードされたファイルはどこにあるんでしょうか。コンテナの中を検索してみましょう。

# 起動中のseleniumコンテナのコンテナ名確認
$ docker ps --format "table {{.Image}}\t{{.Names}}"
IMAGE                                     NAMES
selenium/standalone-chrome-debug:2.53.0   hopeful_albattani
# そのコンテナに対してzipファイルの検索実行
$ docker exec hopeful_albattani find / -name '*.zip'
/home/seluser/Downloads/01hokkai.zip

/home/seluser/Downloads以下に保存されるようですね。

コンテナのファイルをホスト側で取得

ダウンロードしたならそのファイルをスクリプトから取得したいですね。
というわけで保存場所も分かったので、volumeでマウントしてみます。

# コンテナを落とします。(コンテナ名は先程調べた名称)
$ docker kill hopeful_albattani
# ダウンロードファイルを保存するフォルダを作ります。
$ mkdir downloads
# このフォルダをマウントして再度コンテナを起動します。
$ docker run -d -v $(pwd)/downloads:/home/seluser/Downloads -p 4444:4444 -p 5900:5900 selenium/standalone-chrome-debug:2.53.0
$ ls downloads
# => まだ空っぽ

この状態でダウンロード処理を実行してからdownloadsフォルダを確認します。

$ bundle exec ruby download-zip.rb
$ ls downloads
01hokkai.zip

ダウンロードファイルをホスト側に取り込めました!

もう一回実行してみましょう。

$ bundle exec ruby download-zip.rb
$ ls downloads
01hokkai (1).zip  01hokkai.zip

実際に手動で操作した時のように同じファイルが既に存在したら、ファイル名 (n).拡張子の形式で保存されてますね。

 ここでハマる

いまやろうとしているのは繰り返しダウンロード処理を実行出来るようにしたかったのでプログラム中でダウンロードしたファイルを使ったら都度消して次回実行時も同じファイル名で取得できるようにしたかったので、ダウンロードが完了するまで待って、中身を使って、消す、という処理を行う必要がありました。

seleniumでのwait処理

先ほどの処理のようにダウンロード完了をsleepで待っていると無駄にまったり、まだ終わっていないのにsleepが終わる可能性があります。

seleniumにはwaitすることも考慮されていて待つためのユーティリティがそろっています。

waitオブジェクト

waitオブジェクトを作ってuntilメソッドをブロック付きで実行すると
最大waitオブジェクトに設定した待ち時間待つかブロックが真を返すまで待ってくれます。
待ち時間をすぎれば例外、ブロックが真を返せばその結果がuntilの結果として返されます。
このときブロックは複数回実行されるので注意が必要です。

  w = Selenium::WebDriver::Wait.new(:timeout => 10)
  w.until do 
    # このブロックが真(というか非nil)を返すまで複数回実行される
  end

これを利用して特定の条件がそろうまでwaitするメソッドを作成します。

def wait_until(&b)
  w = Selenium::WebDriver::Wait.new(:timeout => 10)
  w.until(&b)
end

これを使うとdonwloadsフォルダに01hokkai.zipが出来るまで待つ、という処理は

def wait_until_download(file)
  wait_until do 
    File.exists?(file)
  end
end

というような感じでかけます。

というわけで下記のような処理を実行してみます。

download-zip.rb
require 'selenium-webdriver'

def with_webdriver
  driver = Selenium::WebDriver.for(
    :remote,
    url: "http://192.168.99.100:4444/wd/hub",
    desired_capabilities: :chrome
  )
  driver.manage.timeouts.implicit_wait = 10
  yield driver
ensure
  driver.quit
end

def wait_until(&b)
  w = Selenium::WebDriver::Wait.new(:timeout => 10)
  w.until(&b)
end

def wait_until_download(file)
  wait_until do 
    File.exists?(file)
  end
end

with_webdriver do |d|
  d.navigate.to "http://www.post.japanpost.jp/zipcode/dl/oogaki-zip.html"

  # ダウンロードリンク取得
  link = d.find_element(:xpath, "//*[@href='oogaki/zip/01hokkai.zip']")
  link.click

  begin
    # ダウンロードを待つ
    path = "./downloads/01hokkai.zip"
    wait_until_download(path)

    # ダウンロードしたファイルを使う
    p [path, File.size(path)]
  ensure
    # 終わったらダウンロード削除
    File.delete(path) if File.exists?(path)
  end
end

実行してみましょう。した後、downloadsフォルダを確認してみましょう。

$ bundle exec ruby download-zip.rb
["./downloads/01hokkai.zip", 107252]
$ ls downloads
$ #何も無い

きちんとダウンローしたものをプログラムで使用してから削除できました。

では、続けて2回目を実行してみましょう。

$ bundle exec ruby download-zip.rb
〜略〜/vendor/bundle/ruby/2.3.0/gems/selenium-webdriver-2.53.0/lib/selenium/webdriver/common/wait.rb:76:in `until': timed out after 10 seconds (Selenium::WebDriver::Error::TimeOutError)
        from download-zip.rb:17:in `wait_until'
        from download-zip.rb:21:in `wait_until_download'
        from download-zip.rb:36:in `block in <main>'
        from download-zip.rb:10:in `with_webdriver'
        from download-zip.rb:26:in `<main>'
$ ls downloads
01hokkai (1).zip

なぜかダウンロードに失敗して異常終了し、downloadsフォルダには連番付きで01hokkai (1).zipでファイルができています。
ファイルはきちんと消していたのに何故??

解決?

いろいろ調べましたが。これ!というような原因はわかりませんでした。ですが、1つ考えられるのは、docker-machineでvirtual boxのファイル共有機能でコンテナとファイルを共有しているので、その辺かな?と思い、
ダウンロードファイルがを待つ処理(wait_until_download)を

  • ファイルが存在しており、そのファイルが前回調べた時と同じファイルサイズのまま

という条件にしてみました。

def wait_until_download(file)
  wait_until do 
    prev_size = -1
    wait_until do
      if File.exists?(pati)
        size = File.size(file)
        ret = if size == prev_size
                true
              else
                prev_size = size
                false
              end
        sleep 1
        ret
      end
    end
  end
end

sleep 1なんかが、なかなか無理矢理感がありますがこれ入れないと何故かエラーがでてしまい。。

で最終的に下記のスクリプトが完成品です。

download-zip.rb
require 'selenium-webdriver'

def with_webdriver
  driver = Selenium::WebDriver.for(
    :remote,
    url: "http://192.168.99.100:4444/wd/hub",
    desired_capabilities: :chrome
  )
  driver.manage.timeouts.implicit_wait = 10
  yield driver
ensure
  driver.quit
end

def wait_until(&b)
  w = Selenium::WebDriver::Wait.new(:timeout => 10)
  w.until(&b)
end

def wait_until_download(file)
  prev_size = -1
  wait_until do
    if File.exists?(file)
      size = File.size(file)
      ret = if size == prev_size
              true
            else
              prev_size = size
              false
            end
      sleep 1
      ret
    end
  end
end

with_webdriver do |d|
  d.navigate.to "http://www.post.japanpost.jp/zipcode/dl/oogaki-zip.html"

  # ダウンロードリンク取得
  link = d.find_element(:xpath, "//*[@href='oogaki/zip/01hokkai.zip']")
  link.click

  begin
    # ダウンロードを待つ
    path = "./downloads/01hokkai.zip"
    wait_until_download(path)

    # ダウンロードしたファイルを使う
    p [path, File.size(path)]
  ensure
    # 終わったらダウンロード削除
    File.delete(path) if File.exists?(path)
  end
end

これで2回連続実行してみましょう。

$ bundle exec ruby download-zip.rb
["./downloads/01hokkai.zip", 107252]
$ ls downloads
# =>何も無い
$ bundle exec ruby download-zip.rb
["./downloads/01hokkai.zip", 107252]
$ ls downloads
# =>何も無い

無事連続してダウンロード出来るようになりました。しかしどういう理由なんでしょうねぇ。

ダウンロードするコンテナ側のプロセスがファイルを掴んでる間にホスト側でファイルを消してしまってる??よくわかりません。

以上の内容を

に置いています。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?