Capybaraのtableishもどきを読む

こちらのgist

page.find('table').all('tr').map { |row| row.all('th, td').map { |cell| cell.text.strip } }

というふうに以前のcucumberでできていたtableishをcapybaraで実現するコードが紹介されています。railsなどのwebアプリのテストで、一覧の出力順を含めたテストなどで非常に役立ちます。ただ、短いコードながら毎度毎度、読んで忘れて読んで忘れてを繰り返しているので、ちょっとした改造や、トラブルが起こった時にいっつも悩んでいたので、メモしておきます。

1行で書かれているものですが、各命令に分割して解読していきます。

解読した方法

基本的にはpryを使ってそれぞれの命令を順に実行して、APIドキュメントで情報を補足していきます。

例えばこんな感じ。

pryで止まった後

page.find('table').all('tr').class
=> Array

おぉArrayか、まぁ次はmapだからArrayの要素を見た方がいいのねっていうことで

page.find('table').all('tr')[0]
#<Capybara::Element tag="tr" path="/html/body/div/div[2]/table/tr[1]">

というところまで、まぁ自分がつまづくところまでいって、そこから一度立ち止まってCapybara::Node::ElementのAPIドキュメントで次に呼ばれるメソッドがどこで定義されているのかとか他の情報を補足していきました。

解読

page

Capybara::Session が入っている。ようするにwebページの情報が入っているもの。

page.find('table')

指定のタグに該当する Capybara::Node::Element を取得するもの。なので同一ページで複数のtableタグがあると(html的に)最初のtableしかとらないんだと思う。

Capybara::Node::Element#find というメソッドが存在しささそうで、継承やらインクルードで、このメソッドがどこで定義されているかがAPIドキュメント上見えない。ただし、定数 NODE_METHODSの中にfindという単語を見かけたので、なんらかうまくしてるんだと思う。

.all('tr')

Capybara::Node::Element#allCapybara::Node::Finders で定義されている。返却されるのはArray。各要素は Capybara::Element

ここまでで、tableタグ内のすべてのtrタグ(見出しを含めた全行)を取得している。ここで行列相当の情報がとれているはず。

.map{|row| row.all('th, td')

.all('tr') 返却値の各要素は Capybara::Element。なので、trタグの中にあるthかtdを取得している(各行にアクセスできる状態になっている)

.map { |cell| cell.text.strip }

cell というのはそのもの最小単位の要素のこと。

cell.text とするとよくわからん改行やら空白やらを含んだ文字列が取得される。 なので、 String#strip で不要な空白などを削除する。String#striprubyの標準のメソッドで前後の空白を削除するメソッド。

改造するには

こちらのgist にあるように 同じページの中にtableタグが複数あってidで取得するものを限定したい時は

page.find('table' + "#" + id)

とidを追加で指定すればいいようです。

同じようにそれぞれのレベル(その中この行は省きたいとか、このcellだけを取りたい)とかある時はそれぞれのレベルの all を呼び出しているところに指定したclassとかidとかを指定しちゃうとうまくいきそうです。

すっきり!