かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

RSpec Capybara 非表示な要素のテストにハマる

RSpec でテストをしていて、非表示にしてある要素が取れずにハマった。

Capybara さんはデフォルトでは画面上に見える要素のみを検索するので display: nonehidden になっている要素を触るできないっぽい。

<div class="modal" style="display:none"></div>
<input type="hidden" id="secret_val" value="XXX">
scenario 'モーダルが存在する' do
  expect(page).to have_selector('.modal')
end

scenario '#secret_val の値は XXX' do
  expect( find('#secret_val').value ).to eq 'XXX'
end

画面に見えてない要素は触れないのでテストが通らない

visible: false オプションを使う

scenario 'モーダルが存在する' do
  expect(page).to have_selector('.modal', visible: false)
end

scenario '#secret_val の値は XXX' do
  expect( find('#secret_val', visible: false).value ).to eq 'XXX'
end

👉 success
オプションを付ければ非表示要素も検索してくれるのでテストが可能になる!

Options Hash (**options):
visible (Boolean, Symbol) — Only find elements with the specified visibility:

  • true - only finds visible elements.
  • false - finds invisible and visible elements.
  • :all - same as false; finds visible and invisible elements.
  • :hidden - only finds invisible elements.
  • :visible - same as true; only finds visible elements.

cf. Module: Capybara::Node::Finders — Documentation for jnicklas/capybara (master)

非表示になっている要素のコンテンツは have_content / have_text で取れない

例えばデフォルトで非表示になっているモーダルのタイトルの内容があっているかテストしたいような場合

<div class="modal" style="display:none">
  <p class="modal_title">アイカツを見ろ</p>
</div>
scenario '正しいモーダルが出力されていること' do
  expect( find('.modal', visible: false) ).to have_content 'アイカツを見ろ'
end

👇

Failure/Error: expect( find('.modal', visible: false) ).to have_content 'アイカツを見ろ'
   expected to find text "アイカツを見ろ" in "". (However, it was found 1 time including non-visible text.)

非表示の要素は取れていても have_contenthave_text は 空 ("") と判断されてテストが落ちてしまう

have_selectortext: オプションも使ってマッチさせることでコンテンツ内容のテストができる

但し、非表示要素内の要素は have_selector でも visible オプションが必要

非表示なコンテナ内にあって、直接 display: none などのスタイルを当てられててない要素でも visible オプションがないと要素を取得できずにテストが落ちてしまう

scenario '正しいモーダルが出力されていること' do
  expect( find('.modal', visible: false) ).to have_selector('.modal_title', text: 'アイカツを見ろ')
end

👇

Failure/Error: expect( find('.modal', visible: false) ).to have_selector('.modal_title', text: 'アイカツを見ろ')
   expected to find visible css ".modal_title" with text "アイカツを見ろ" within #<Capybara::Node::Element tag="div" path="/HTML/BODY[1]/DIV[2]/DIV[2]/DIV[2]/DIV[1]"> but there were no matches. Also found "", which matched the selector but not all filters.

have_selector にも visible オプションを付ける

scenario '正しいモーダルが出力されていること' do
  expect( find('.modal', visible: false) ).to have_selector('.modal_title', visible: false, text: 'アイカツを見ろ')
end

👉 success
これならテストが通る!
ただ感覚的に「◯◯のテキスト が 'XXXX'」となってる方が直感的なわかり易さもテストの構造としても良い気がするので微妙な感じ…

.text(:all) で非表示になっているテキストノードを取得できる

#text(type = nil, normalize_ws: false) ⇒ String
Retrieve the text of the element. If ignore_hidden_elements is true, which it is by default, then this will return only text which is visible. The exact semantics of this may differ between drivers, but generally any text within elements with display:none is ignored. This behaviour can be overridden by passing :all to this method.
cf. Method: Capybara::Node::Element#text — Documentation for jnicklas/capybara (master)

text(:all) でテキストを取得したほうが「◯◯のテキスト が 'XXXX'」と書けるので非表示になっている要素の内容のテストには良さそう

<div class="modal" style="display:none">
  <p class="modal_title">アイカツを見ろ</p>
  <div class="modal_body">
    <strong>アイカツオンパレード始まったから絶対見て!!!!</strong>
  </div>
</div>
scenario '正しいモーダルが出力されていること' do
  # 文字列に含まれるをテストするので include を使う
  expect( find('.modal', visible: false).text(:all) ).to include 'アイカツを見ろ'
end

👉 success
テストが通りました ٩(ˊᗜˋ*)و

非表示の要素内のテキストは find()visible オプション・ text():all 引数の両方がないとダメ

text:all がないと空("")になるのでテストが通らない

scenario '正しいモーダルが出力されていること' do
  expect( find('.modal', visible: false).text ).to include 'アイカツを見ろ'
end

👇

Failure/Error: expect( find('.modal', visible: false).text ).to include 'アイカツを見ろ'
   expected "" to include "アイカツを見ろ"

find()visible オプションがないとそもそも要素を取得できないのでテキストも取得できない

scenario '正しいモーダルが出力されていること' do
  expect( find('.modal').text(:all) ).to include 'アイカツを見ろ'
end

👇

Failure/Error: expect( find('.modal').text(:all) ).to include 'アイカツを見ろ'
  Capybara::ElementNotFound:
    Unable to find visible css ".modal"
text の引数はこんな感じになっているそうです

見えないテキスト(display:none)が text メソッドの戻り値に含まれるかどうかについて
2.1 では、text メソッドの引数によって挙動を切り替えることもできる

find("#thing").text           # Capybara.ignore_hidden_elements によって挙動が変わる
find("#thing").text(:all)     # 見えないテキストも含む
find("#thing").text(:visible) # 見えるテキストだけ
cf. capybara 2.1 を学ぶ - おもしろwebサービス開発日記

感想

  • 非表示になっている要素には visible: false オプションを使う
  • 非表示になっている要素の内容をテストするには find(selector, visible: false).text(:all) を使う

のが良さそうです。
 
RSpec も Capybara も何も分からん… (docker-mac でのテスト走らせるのが遅くて辛いからどうにかなってほしぃ…


[参考]

カピバラさん PCクッション

カピバラさん PCクッション

RSpec Capybara href の無い a タグにハマる

RSpecの feature spec で href の無い a タグのテストをしようとしてハマったのでメモ

ボタン / リンクの存在

ボタン

buttonsubmit

<button>ボタンのラベル</button>
<input type="submit" value="ボタンのラベル" />
expect(page).to have_button 'ボタンのラベル'
リンク

a タグはボタンではなくリンクでないとマッチしない

<a href="example">リンクテキスト</a>
expect(page).to have_link 'リンクテキスト'

リンク先 (href) も含めてマッチ

expect(page).to have_link 'リンクテキスト', href: 'example'

ボタン / リンクのクリック

Capybara でクリックさせる

ボタン

buttonsubmit

<button>ボタンのラベル</button>
<input type="submit" value="ボタンのラベル" />
click_button 'ボタンのラベル'
リンク

a タグ

<a href="example">リンクテキスト</a>
click_link 'リンクテキスト'

ボタンまたはリンク

button, submit, 'a' タグ どれでもマッチする

<button>ボタンのラベル</button>
<input type="submit" value="ボタンのラベル" />
<a href="example">リンクテキスト</a>
click_on 'ボタンのラベル'
click_on 'リンクテキスト'

href が無い a タグは have_link, click_link, click_on にマッチしない

<a>リンクテキスト</a>
expect(page).to have_link 'リンクテキスト'

👇

Failure/Error: expect(page).to have_link 'リンクテキスト'
  expected to find link "リンクテキスト" but there were no matches

href が無い a タグはマッチせず存在しないと言われてしまいました…

a tags without an href are not links, they are placeholders for links. That's how the HTML spec defines it, that's how every modern browser treats them. Capybara does indeed only click on links which have the href attribute, and imho, that's sensible behaviour. click_link 'foo' not working on links without href · Issue #379 · teamcapybara/capybara · GitHub

href がないとリンクではないからと言うことのみたい

href のない a タグの存在

have_selector を使う

<a>リンクテキスト</a>
expect(page).to have_selector('a', text: '<リンクテキスト>')

href のない a タグをクリック

find で要素を選択してクリックさせる

<a>リンクテキスト</a>
find('a', text: 'リンクテキスト').click

a タグに CSS のクラス名があるならクラス名で find してもOK

<a class="link_icon">
  <i class="icon"></i>
</a>
find('.link_icon').click

 

RSpec 時々しか書かないから何も分からん…


[参考]

React JSX 三項演算子で Left side of comma operator is unused and has no side effects なエラーにハマる

危機管理〜
JSX内でコンポーネントを出し分けしようとしてハマったのでメモ

ダメだったコード

function App( isAdmin ) {
  return (
    <>
     {isAdmin? (
       <AdminMain />
       <AdminAside />
     ) : (
       <Main />
       <Aside />
     )}
    </>
  );
}

エディターのシンタックスハイライトで次のようなエラーが表示されていた Left side of comma operator is unused and has no side effects
当然エラーになって画面にも表示されない

三項演算子() のとトップレベルに複数のタグがあるとダメ

Left side of comma って表示されてたから三項演算子が問題なんだと思ってたら、JSX のトップレベルに複数のタグを置けない問題だった。
先の例は三項演算子で返す () 内を <></> で囲えばOK

function App( isAdmin ) {
  return (
    <>
     {/* return している () の直下はタグでないとエラーになる */}
     {isAdmin? (
       <>
         <AdminMain />
         <AdminAside />
       </>
     ) : (
       <>
         <Main />
         <Aside />
       </>
     )}
    </>
  );
}

で返すで囲われたコンポーネントの直下に三項演算子持ってくることができないので、こんな構成だとすごく冗長に…
return ({isAdmin? (<>...</>) : (<>...</>) } みたいな書き方はエラーになる

所感

React のエラーの表示がわかりにくいんじゃい!!!!!!!!

(完)


参考

シャミ子が悪いんだよ…