使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」

## はじめに

みなさんこんにちは！
この記事は「必要最小限の努力で最大限実戦で使える知識を提供するRSpec入門記事」、略して「使えるRSpec入門」の第4回です。

今回はCapybaraを使ったフィーチャスペックについて説明します。
ただし、今までの記事とは異なり、フィーチャスペックのイロハよりも「Capybaraの使い方」に重点を置きます。

なぜなら、僕個人の経験からいって、フィーチャスペックで困るのは「このブラウザの操作って、どうやってコードで表現するの？？」というケースが大半だからです。
それ以外は第1回～第3回の内容をそのまま応用できるので、特に「フィーチャスペックだから困る」ということはないと思います。

今回は説明する主な項目は以下の通りです。

- フィーチャスペックの基本
- ページの移動や画面のクリック、フォームの操作など
- 画面やフォームの検証
- 画面の操作や検証の応用テクニック
- その他、知っておくと便利なテクニック

なお、Capybaraの使い方は「逆引き形式」で説明しています。
Qiitaの「目次」を使えば、実現したいブラウザ操作へすぐにジャンプできるはずです。

フィーチャスペックは使いこなせると、とても有益なテストになります。
なぜなら、viewからデータベースまで、すべての動作確認を一つのテストで実現できるからです。
プログラムを変更してもフィーチャスペックがパスしていれば、「この機能はちゃんと使えているはず」という自信が持てます。（過信は禁物ですけどね）

ぜひフィーチャスペックを使いこなせるようになりましょう！

### 対象となる読者

- RSpecでフィーチャスペックを一度も書いたことがない人
- フィーチャスペックは書いているが、Capybaraの細かい機能はよく忘れる人（僕みたいに）

### 対象となるバージョン

- Rails 4.2
- Ruby 2.2
- rspec-rails 3.1
- Capybara 2.4
- Poltergeist 1.5.1

### 2021-9-1追記：システムスペックでもほぼそのまま活用できます！
この記事はフィーチャスペック（feature spec）を対象にして説明していますが、フィーチャスペックとシステムスペック（system spec）はほとんど同じですし、Capybaraの使い方自体も同じなので、システムスペックを書くときでも同じように活用できます。

### 第1回～第3回の記事はもう読みましたか？

第1回では「RSpecの基本的な構文や便利な機能を理解する」というテーマでRSpecの基本を説明しました。
第2回では「使用頻度の高いマッチャを使いこなす」というテーマでマッチャの使い方をいろいろと説明しました。
第3回では「ゼロからわかるモック（mock）を使ったテストの書き方」というテーマでモックの使い方を詳しく説明しました。

今回、「読者のみなさんは第1回と第2回の内容は理解できている」という前提で説明していくので、まだ読んでいない方は先に過去の記事を読んでおいてください。（第3回の内容は特に出てこないので必須ではありません）

- [使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)
- [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
- [使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」](https://qiita.com/jnchito/items/640f17e124ab263a54dd)

それでは始めましょう！

## フィーチャスペックの基本

フィーチャスペック（feature spec）はブラウザ上の操作をシミュレートして、実行結果を検証するテストです。
こうしたテストは一般的に「統合テスト」や「エンドツーエンドテスト（E2Eテスト）」と呼ばれるものです。

以下はログイン機能をテストする、フィーチャスペックの単純なサンプルです。

```ruby
require 'rails_helper'

RSpec.feature 'ログインとログアウト' do
  background do
    # ユーザを作成する
    User.create!(email: 'foo@example.com', password: '123456')
  end
  scenario 'ログインする' do
    # トップページを開く
    visit root_path
    # ログインフォームにEmailとパスワードを入力する
    fill_in 'Email', with: 'foo@example.com'
    fill_in 'Password', with: '123456'
    # ログインボタンをクリックする
    click_on 'ログイン'
    # ログインに成功したことを検証する
    expect(page).to have_content 'ログインしました'
  end
end
```

### フィーチャスペックのエイリアス

フィーチャスペックでは `describe` や `it` の代わりに、`feature` や `scenario` といったエイリアスをよく使います。

フィーチャスペックで使われるエイリアスの対応関係は以下の通りです。

- `describe` <=> `feature`
- `it` <=> `scenario`
- `let` <=> `given`
- `let!` <=> `given!`
- `before` <=> `background`

`context` や `after` にはエイリアスがないのでそのまま使います。

ただし、あくまでエイリアスなので、`describe` や `it` を使って書いても構いません。

```ruby
require 'rails_helper'

RSpec.describe 'ログインとログアウト', type: :feature do
  before do
    # ユーザを作成する
    User.create!(email: 'foo@example.com', password: '123456')
  end
  it 'ログインする' do
    # トップページを開く
    visit root_path
    # ログインフォームにEmailとパスワードを入力する
    fill_in 'Email', with: 'foo@example.com'
    fill_in 'Password', with: '123456'
    # ログインボタンをクリックする
    click_on 'ログイン'
    # ログインに成功したことを検証する
    expect(page).to have_content 'ログインしました'
  end
end
```

## フィーチャスペックのセットアップ

Railsでフィーチャスペックを書く場合は rspec-rails や Capybara といったgemのインストールや、設定ファイルの作成が必要になります。

詳しい方法は以下の記事を参考にしてみてください。
（ただしバージョンがちょっと古くなっているので、バージョン番号は適宜最新のものを指定してください）

[初心者大歓迎！RSpec 3でドラッグアンドドロップ機能をテストする方法(スクリーンキャスト付き)](https://qiita.com/jnchito/items/bdd80c1085fa082e7d7a)

それでは、次のセクション以降でCapybaraの使い方を逆引き形式で説明していきます。
こうした機能を組み合わせれば、フィーチャスペックを自由自在に書けるはずです。

## ページの移動や画面のクリック、フォームの操作など

### 特定のページへ移動する

`visit + path` で特定のページに移動できます。

```ruby
visit root_path
visit new_user_path
```

### 現在のページを再読み込み（リロード）する

Capybaraの標準APIにはリロード用のメソッドが無いので、「現在のパスに再度移動する」という操作でリロードをシミュレートします。

```ruby 
visit current_path
```

### ボタンをクリックする

「ボタン」というのはsubmitボタンやbuttonタグを指します。

```html
<input type="submit" name="commit" value="Save">
<!-- または -->
<!-- <button>Save</button> -->
```

```ruby
click_button 'Save'
```

### リンクをクリックする

CSSによって見た目がボタンっぽくなっていても、HTML上はa要素になっている場合は「リンク」と見なされるので注意が必要です。

```html
<a href="/users/new">New User</a>
```

```ruby
click_link 'New User'
```

### リンクまたはボタンをクリックする

クリックする要素がリンクなのかボタンなのか、いちいち考えたくない場合は `click_on` が便利です。

```html
<a href="/users/new">New User</a>
<input type="submit" name="commit" value="Save">
```

```ruby
click_on 'New User'
click_on 'Save'
```

### 画像のalt属性を利用してリンクをクリックする

```html
<a href="/users/1">
  <img alt="Alice" src="./profile.jpg">
</a>

<!-- buttonの場合はNG -->
<!-- <button> -->
<!--   <img alt="Alice" src="./profile.jpg"> -->
<!-- </button> -->
```

```rb
click_on 'Alice'
# または
# click_link 'Alice'
```

### テキストボックスまたはテキストエリアに文字を入力する

```html
<label for="blog_title">タイトル</label>
<input type="text" value="" name="blog[title]" id="blog_title">
```

```ruby
fill_in 'タイトル', with: 'あけましておめでとうございます。'
```

### セレクトボックスを選択する

```html
<label for="japanese_calendar">和暦</label>
<select name="japanese_calendar" id="japanese_calendar">
  <option value="0">明治</option>
  <option value="1">大正</option>
  <option value="2">昭和</option>
  <option value="3">平成</option>
</select>
``` 

```ruby
select '平成', from: '和暦'
```

### チェックボックスのチェックをON/OFFする

```html
<label for="mailmagazine">
    <input type="checkbox" name="mailmagazine" id="mailmagazine" value="1">
    メールマガジンを購読する
</label>
```

```ruby
check 'メールマガジンを購読する'
uncheck 'メールマガジンを購読する'
```

### ラジオボタンを選択する

```html
<label>
  <input id="user_sex_male" name="user[sex]" type="radio" value="male" checked="checked">
  男性
</label>
<label>
  <input id="user_sex_female" name="user[sex]" type="radio" value="female">
  女性
</label>
```

```ruby
choose '女性'
```

### ファイルを添付する

`"#{Rails.root}/spec/factories/profile_image.jpg"` の部分は実際にファイル配置しているパスを指定してください。

```ruby
attach_file 'プロフィール画像', "#{Rails.root}/spec/factories/profile_image.jpg"
```

### hiddenに値をセットする

```html
<input type="hidden" name="secret_value" id="secret_value">
```

`find` を普通に使うと画面に表示されている要素しか検索しないので、 `visible: false` オプションを付けます。


```ruby
find('#secret_value', visible: false).set('secret!!')
```

### ラベルがないフィールドを操作する


ラベルの代わりにid、name、placeholderを指定できます。

#### テキストボックスの場合

```html
<input type="text" name="blog[title]" id="blog_title" value="" placeholder="ここにタイトルを入力します">
```

```ruby
fill_in 'blog_title', with: 'あけましておめでとうございます。'
# または
# fill_in 'blog[title]', with: 'あけましておめでとうございます。'
# fill_in 'ここにタイトルを入力します', with: 'あけましておめでとうございます。'
```

#### セレクトボックスの場合

```html
<select name="japanese_calendar" id="japanese_calendar">
  <option value="0">明治</option>
  <option value="1">大正</option>
  <option value="2">昭和</option>
  <option value="3">平成</option>
</select>
``` 

```ruby
select '平成', from: 'japanese_calendar'
```

#### チェックボックスの場合

```html
<input type="checkbox" name="mailmagazine" id="mailmagazine" value="1">
```

```ruby
check 'mailmagazine'
```

#### FAQ「ラベルはあるのにラベル名を指定できません😣」

本当はこんなテストコードを書きたいのに・・・

```ruby
fill_in '本文', with: '今日はいい天気'
```

「そんなフィールドは見つからない（`Capybara::ElementNotFound: Unable to find field "本文" that is not disabled`）」と怒られるので、泣く泣くこう書いてます😭

```ruby
fill_in 'blog[content]', with: '今日はいい天気'
```

っていう人をよく見かけます。

そういう人はChromeのデベロッパーツールなどでHTMLを確認してください。
もしかするとこんなふうになってませんか？

```html
<label>本文</label>
<input type="text" name="blog[content]" id="blog_content">
```

Capybaraでラベル名で選択できるようにするためにはlabelタグのfor属性を正しく設定（inputタグのid属性を指定）する必要があります。
具体的にはこうなってる必要があります。

```html
<label for="blog_contet">本文</label>
<input type="text" name="blog[content]" id="blog_content">
```

シンプルなRailsアプリケーションであればViewを以下のように書けば自動的にlabelに適切なid属性が付くはずです。

```erb 
<%= form_with(model: blog) do |f| %>
  <%= f.label :content %>
  <%= f.text_field :content %>
<% end %>
```

Vue.jsやReactでフォームを作っている場合でも考え方は同じでfor属性が適切に設定されていればOKなので、タグの構造を見直してみましょう。

## 画面やフォームの検証

### ページ内に特定の文字列が表示されていることを検証する

```html
<div class="alert alert-success">
  ユーザーが作成されました。
</div>
```

```ruby
expect(page).to have_content 'ユーザーが作成されました。'
```

### 特定のタグやCSS要素に特定の文字列が表示されていることを検証する

```html
<h1 class="information" id="information">大事なお知らせ</h1>
```

```ruby
expect(page).to have_selector 'h1', text: '大事なお知らせ'
```

要素ではなく、CSSクラスやIDを使うこともできます。

```ruby
expect(page).to have_selector '.information', text: '大事なお知らせ'
# または
expect(page).to have_selector '#information', text: '大事なお知らせ'
# 要素とクラスを組み合わせるのも可
expect(page).to have_selector 'h1.information', text: '大事なお知らせ'
```

要素の属性も指定することができます。

```html
<a href="contacts/1" data-method="delete">delete</a>
```

```ruby
expect(page).to have_selector 'a[data-method=delete]', text: 'delete'
```

`text` には正規表現を渡すこともできます。

```ruby
expect(page).to have_selector 'h1', text: /^大事なお知らせ$/
```


### ページ内に特定のボタンが表示されていることを検証する

```html
<input type="submit" name="commit" value="Save">
```

```ruby
expect(page).to have_button 'Save'
```

### ページ内に特定のリンクが表示されていることを検証する

```html
<a href="/users/sign_up">会員登録はこちら</a>
```

```ruby
expect(page).to have_link '会員登録はこちら'
```

### ページ内に特定のCSS要素が表示されていることを検証する

```html
<!-- バリデーションエラー時のHTML -->
<div class="field_with_errors">
  <input type="text" value="" name="blog[title]" id="blog_title">
</div>
```

```ruby
# ブログの新規作成画面を開く
visit new_blog_path
# 何も入力せずに作成ボタンをクリックする
click_on 'Create Blog'
# フォーム内の要素にfield_with_errorsクラスが付いていることを検証する
expect(page).to have_css '.field_with_errors'
```

### テキストボックスに特定の値が入っていることを検証する

```html
<!-- ブログの編集画面を開いたときのHTML -->
<label for="blog_title">タイトル</label>
<input type="text" value="あけましておめでとうございます。" name="blog[title]" id="blog_title">
```

```ruby
# ブログの編集画面を開く
click_link 'Edit'
# 編集前のタイトルが表示されていることを検証する
expect(page).to have_field 'タイトル', with: 'あけましておめでとうございます。'
```

### チェックボックスがチェックされていること/いないことを検証する

```html
<label>
    <input type="checkbox" name="mailmagazine" id="mailmagazine" value="yes" checked="checked">
    メールマガジンを購読する
</label>
```

```ruby
expect(page).to have_checked_field('メールマガジンを購読する')
```

チェックされて **いないこと** を検証する場合は `have_unchecked_field` を使います。

```ruby
expect(page).to have_unchecked_field('メールマガジンを購読する')
```

### チェックボックスが表示されていることを検証する（チェックの有無は無視する）

チェックボックスが表示されていることだけを検証したい、チェックの有無は気にしない、という場合は `have_field` を使います。

```html
<label>
    <input type="checkbox" name="mailmagazine" id="mailmagazine" value="yes" checked="checked">
    メールマガジンを購読する
</label>
```

```ruby
# チェックが付いていてもいなくてもパスする 
expect(page).to have_field('メールマガジンを購読する')
```

### セレクトボックスで特定の項目が選択されていることを検証する

```html
<label for="japanese_calendar">和暦</label>
<select name="japanese_calendar" id="japanese_calendar">
  <option value="明治">明治</option>
  <option value="大正">大正</option>
  <option selected="selected" value="昭和">昭和</option>
  <option value="平成">平成</option>
</select>
```

```ruby
expect(page).to have_select('和暦', selected: '昭和')
```

### セレクトボックスで特定の項目が存在することを検証する

```html
<label for="japanese_calendar">和暦</label>
<select name="japanese_calendar" id="japanese_calendar">
  <option value="明治">明治</option>
  <option value="大正">大正</option>
  <option value="昭和">昭和</option>
  <option value="平成">平成</option>
</select>
```

```ruby
expect(page).to have_select('和暦', options: ['明治', '大正', '昭和', '平成'])
```

`selected`と一緒に検証することもできます。

```ruby
expect(page).to have_select('和暦', selected: '昭和', options: ['明治', '大正', '昭和', '平成'])
```

この検証方法はこちらの記事で実際の使用例を見ることができます。

- [Ajaxでセレクトボックスの中身が動的に変わるRailsアプリの作り方](https://qiita.com/jnchito/items/59a5f6bea3d7be84b839)

### （応用） セレクトボックスでselectedになっている項目がないことを検証する

以下のように `selected` になっている項目がないセレクトボックスを想定します。

```html
<label for="japanese_calendar">和暦</label>
<select name="japanese_calendar" id="japanese_calendar">
  <option value="meiji">明治</option>
  <option value="taisho">大正</option>
  <option value="showa">昭和</option>
  <option value="heisei">平成</option>
</select>
```

画面上は一番先頭にある "明治" が表示されていますが、だからといって次のようなコードを書くと検証に失敗します。（"明治"は `selected` になっていないため）

```ruby
# NG:検証に失敗する
expect(page).to have_select('和暦', selected: '明治')
```

「どれも `selected` ではないこと」をスマートに検証できるメソッドはないので、もしやるとすれば次のようにループを回して全項目が `selected` でないことを検証する必要があります。

```rb 
within find_field('和暦') do
  all('option').each do |option|
    expect(option['selected']).to be_blank
  end
end
```

なお、`selected`になっていなくても、`value`メソッドを使うと画面上に表示されている項目のvalueを取得できます。

```rb 
# selected になっていなくても、画面上に表示されている項目のvalueは取得できる 
expect(find_field('和暦').value).to eq 'meiji'
```


### ラジオボタンで特定の項目が選択されていることを検証する

```html
<label>
  <input id="user_sex_male" name="user[sex]" type="radio" value="male" checked="checked">
  男性
</label>
<label>
  <input id="user_sex_female" name="user[sex]" type="radio" value="female">
  女性
</label>
```

```ruby
expect(page).to have_checked_field('男性')
```

### title要素の文言を検証する

```html
<html>
<head>
<title>My favorite songs</title>
...
```

```ruby
expect(page).to have_title 'My favorite songs'
```

### 「表示されていないこと」や「選択されていないこと」を検証する

以下は画面に特定の文字列が表示されていないことを検証するコードです。

```ruby
expect(page).to_not have_content '秘密のパスワード'
```

上のコードは次のように `to + have_no_content` を使って書くこともできます。

```ruby
expect(page).to have_no_content '秘密のパスワード'
```

他にも `have_no_xxx` の形式で検証できる要素があります。

- `have_no_button`
- `have_no_checked_field`
- `have_no_css`
- `have_no_field`
- `have_no_link`
- `have_no_select`
- `have_no_selector`
- `have_no_table`
- `have_no_text`
- `have_no_title`
- `have_no_unchecked_field`
- `have_no_xpath`


### hidden項目に設定されている値を検証する


```html
<input type="hidden" name="secret_value" id="secret_value" value="secret!!">
```

`find` を普通に使うと画面に表示されている要素しか検索しないので、 `visible: false` オプションを付けます。

```ruby
expect(find('#secret_value', visible: false).value).to eq 'secret!!'
```


### フォームの要素がdisabledになっていることを検証する

```html
<label for="blog_title">タイトル</label>
<input type="text" value="" name="blog[title]" id="blog_title" disabled="disabled">
```

disabledでなければ `find_field('タイトル')` のように書けますが、disabledだと使えないので、代わりに `find + id` を使います。

```ruby
expect(find('#blog_title')).to be_disabled
```

**（2015.10.22 追記）disabledオプションを使って検証する**

以下のように `disabled: true` を指定すれば disabled になっているコントロールも検証できることがわかりました。
こちらの方が自然で理解しやすいと思います。

```rb 
expect(page).to have_field 'タイトル', disabled: true 
```

テキストボックスだけでなく、チェックボックスやボタン等でも使えます。

```rb
expect(page).to have_checked_field 'メールを受け取る', disabled: true

expect(page).to have_button '更新する', disabled: true 
```

### 特定のCSS要素が非表示になっていることを検証する

```html
<div style="display: none;" class="secret-message">
  よい子は見ちゃダメ
</div>
```

`find` を普通に使うと画面に表示されている要素しか検索しないので、 `visible: false` オプションを付けます。


```ruby
expect(find('.secret-message', visible: false)).to_not be_visible
```

### 要素の属性値を検証する

```html
<input type="submit" value="Create User" data-confirm="Are you sure?">
```

```ruby
button = find_button 'Create User'
expect(button['data-confirm']).to eq 'Are you sure?'
```

### 現在のページが特定のパスであることを検証する

```ruby
click_on 'New User'
expect(current_path).to eq new_user_path
```

### レスポンスのステータスコードを検証する

```ruby
click_on 'Create User'
expect(page).to have_http_status(:success)
# または
# expect(page).to have_http_status(:ok)
# expect(page).to have_http_status(200)
```

その他のパターンについては公式ドキュメントを参照してください。

https://www.relishapp.com/rspec/rspec-rails/docs/matchers/have-http-status-matcher

## 画面の操作や検証の応用テクニック

### 文字列で指定できない（または指定しにくい）要素を操作する

リンクにアイコンしか表示されていない場合など、画面に表示されている文字列を指定できない（または指定しにくい）場合は、`find + id/class + action` で操作します。

以下は class でリンクを指定し、そのリンクをクリックする例です。

```html
<a class="settings-link" href="/settings">
  <i class="fa fa-gears"></i>
</a>
```

```ruby
find('.settings-link').click
```

id で指定する場合は、ドット（.）ではなくシャープ（#）で指定します。

```html
<a id="settings-link" href="/settings">
  <i class="fa fa-gears"></i>
</a>
```

```ruby
find('#settings-link').click
```

`find`と組み合わせられるアクションはクリックだけでなく、`find('#some-textbox').set('Hello')` や `find('#some-checkbox').set(true)` のようにフォームに値をセットしたりすることもできます。

詳しくはAPIドキュメントを参照してください。

https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Element

### 文字列で指定できない（または指定しにくい）要素を検証する

リンクにアイコンしか表示されていない場合など、画面に表示されている文字列を指定できない（または指定しにくい）場合は、`find + id/class + プロパティ` で検証します。

以下は class でリンクを指定し、そのリンクのhref属性を検証する例です。

```html
<a class="settings-link" href="/settings">
  <i class="fa fa-gears"></i>
</a>
```

```ruby
link = find('.settings-link')
expect(link[:href]).to eq edit_user_path
```

id で指定する場合は、ドット（.）ではなくシャープ（#）で指定します。

```html
<a id="settings-link" href="/settings">
  <i class="fa fa-gears"></i>
</a>
```

```ruby
link = find('#settings-link')
expect(link[:href]).to eq edit_user_path
```

`find` + 要素名と、要素内のテキストを組み合わせて取得することも可能です。

```html
<a>About Ruby</a>
```

```ruby
# href属性がないと click_link 'About Ruby' ではクリックできないため、以下のようにする
link = find('a', text: 'About Ruby')
link.click
```

`find` と組み合わせて取得できる値は要素の属性だけでなく、`find('#some-textbox').value` や `find('#some-checkbox').checked?` のようにフォームの値を取得したりすることもできます。

詳しくはAPIドキュメントを参照してください。

https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Element

### 画面上に複数現れる要素を絞り込む

Capybaraは指定された条件に一致する要素が複数見つかるとエラーを投げます。

たとえば、以下の例では "はい" と "いいえ" のラジオボタンが2つずつあるので、単純に選択しようとするとエラーが起きます。（どちらの "はい" なのか決められないため）

```html
<div class="section-drug">
  現在薬を飲んでいますか？
  <label>
    <input id="user_drug_yes" name="user[drug]" type="radio" value="yes" checked="checked">
    はい
  </label>
  <label>
    <input id="user_drug_no" name="user[drug]" type="radio" value="no">
    いいえ
  </label>
</div>

<div class="section-disease">
  大きな病気にかかったことはありますか？
  <label>
    <input id="user_disease_yes" name="user[disease]" type="radio" value="yes" checked="checked">
    はい
  </label>
  <label>
    <input id="user_disease_no" name="user[disease]" type="radio" value="no">
    いいえ
  </label>
</div>
```

```ruby
choose 'はい'
# => Capybara::Ambiguous: Ambiguous match, found 2 elements matching radio button "はい"
```

このような場合は `within` を使ってスコープを絞ってから操作を実行します。

```ruby
within '.section-drug' do
  choose 'はい'
end

within '.section-disease' do
  choose 'いいえ'
end
```

もしくは「n個ある要素の一番最初の要素を選択」でいい場合は`match: :first`オプションを付けます。

```ruby 
# 画面上2つある「はい」のうち、最初の「はい」を選択
choose 'はい', match: :first 
```

#### Tips：テストしにくいviewはテストをしやすいように変更する

`within`　で絞り込める条件がない（上の例だと、`section-drug` や `section-disease` のdivがない）場合は、テストしやすいようにidやclassを付けることを検討してください。

### 条件に合致する要素を配列として取得する

下のように、3件のデータが表示されているテーブルがあったとします。

````html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mozell Conn</td>
      <td>
        <a href="/contacts/1/edit">Edit</a>
      </td>
    </tr>
    <tr>
      <td>Adaline Gleason</td>
      <td>
        <a href="/contacts/2/edit">Edit</a>
      </td>
    </tr>
    <tr>
      <td>Jazmyne Goldner</td>
      <td>
        <a href="/contacts/3/edit">Edit</a>
      </td>
    </tr>
  </tbody>
</table>
````

このテーブルの中にある2行目の Edit リンクをクリックしたい、という場合は以下のように `all` メソッドを活用します。

````ruby
all('tbody tr')[1].click_link 'Edit'
````

`all('tbody tr')` は「`tbody` 要素内に存在する、すべての `tr` 要素を選択する」を意味します。

`all` メソッドでは条件に合致した要素の配列が返ってくるので、2行目を選択したい場合は `[1]` とすれば、2行目の `tr` 要素だけにフォーカスできます。

あとは「この `tr` 要素内に存在する Edit リンクをクリックする」という意味で `click_link 'Edit'` と書けば、2行目に表示されている Edit リンクをクリックできます。

**2022.1.28追記** 
これとよく似た内容の記事を書きました。こちらも合わせてどうぞ。

https://qiita.com/jnchito/items/7bd131d99c28f21834dd

### 最初に出現する要素を取得する

たとえば以下のように、最初に出現する `tr` 要素を取得するコードがあったとします。

````ruby
all('tbody tr')[0].click_link 'Edit'
````

これは `first` を使うと簡潔に書けます。

````ruby
first('tbody tr').click_link 'Edit'
````

応用テクニックして、 `within` と組み合わせて使うこともできます。

````ruby
within first('tbody tr') do
  click_link 'Edit'
end
````

ちなみに、 `last` や `second` のようなショートカットメソッドはないみたいです。
`all('tbody tr').last` や `all('tbody tr')[1]` のように書いてください。

参考: http://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders

### 特定のパスへのリンクを検証する/クリックする

ページ内に同じようなリンクが複数あり、特定のリンクだけにフォーカスしたい場合は具体的なパス（a要素のhref属性）で絞り込むこともできます。

````html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mozell Conn</td>
      <td>
        <a href="/contacts/1/edit">Edit</a>
      </td>
    </tr>
    <tr>
      <td>Adaline Gleason</td>
      <td>
        <a href="/contacts/2/edit">Edit</a>
      </td>
    </tr>
    <tr>
      <td>Jazmyne Goldner</td>
      <td>
        <a href="/contacts/3/edit">Edit</a>
      </td>
    </tr>
  </tbody>
</table>
````

```ruby
contact = Contact.find_by(name: 'Adaline Gleason')

expect(page).to have_link 'Edit', href: edit_contact_path(contact)

click_link 'Edit', href: edit_contact_path(contact)
```

リンク文字列がない場合（アイコンが表示されている場合など）は`href`だけで絞り込むこともできます。


````html
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Mozell Conn</td>
      <td>
        <a href="/contacts/1/edit">
          <i class="fa fa-pencil"></i>
        </a>
      </td>
    </tr>
    <tr>
      <td>Adaline Gleason</td>
      <td>
        <a href="/contacts/2/edit">
          <i class="fa fa-pencil"></i>
        </a>
      </td>
    </tr>
    <tr>
      <td>Jazmyne Goldner</td>
      <td>        
        <a href="/contacts/3/edit">
          <i class="fa fa-pencil"></i>
        </a>
      </td>
    </tr>
  </tbody>
</table>
````

```ruby
contact = Contact.find_by(name: 'Adaline Gleason')

expect(page).to have_link nil, href: edit_contact_path(contact)

click_link nil, href: edit_contact_path(contact)
```


## その他、知っておくと便利なテクニック

### テストが失敗する直前の画面をブラウザで開く

以下のように、なぜか失敗するテストがあったとします。

```ruby
click_on 'Create User'
expect(page).to have_content 'ユーザを作成しました'
# エラー => expected to find text "ユーザを作成しました" in "...
```

失敗する原因がすぐにわからない場合は `save_and_open_page` メソッドを使うと、ブラウザが自動的に起動し、その時点の画面を確認できます。
（ただし、CSSは適用されないので見た目は不格好になります）

```ruby
click_on 'Create User'
# ブラウザで画面を開く
save_and_open_page
expect(page).to have_content 'ユーザを作成しました'
```

#### ブラウザの自動起動には launchy gem が必要

"Please install the launchy gem to open the file automatically." という警告が出てブラウザが起動しない場合は、launchy gemをインストールしてください。

```ruby
# Gemfile
group :test do 
  gem 'launchy'
end
```

```bash
bundle install
```

#### テスト失敗時に自動的に save_and_open_page を呼ぶ

テスト失敗時に自動的に save_and_open_page が呼ばれるようにしておくと、効率よくデバッグできるかもしれません。
詳しくは以下の記事を読んでみてください。

[フィーチャスペックでテスト失敗時に自動的に save_and_open_page を実行する方法](https://qiita.com/jnchito/items/6b3e1ab9baf692b986df)

### 手っ取り早くテストをskipする

`scenario` を `xscenario` に変更するとそのテストはskipされて実行されなくなります。（pending扱いになります）

```rb 
xscenario '一時的に実行させたくないシナリオ' do
  # ...
end
```

### JavaScriptを使わないと操作できない処理をテストする

Ajaxを使った処理や、クリックイベントによるモーダル画面の表示など、JavaScriptなしでは実行できない操作がある場合は `js: true` タグを付けます。

```ruby
scenario 'Userの作成', js: true do
# または
# it 'Userの作成', js: true do
```

#### JavaScriptドライバのセットアップ方法について

JavaScriptを実行する場合は、PoltergeistのようなJavaScriptドライバの設定が必要になります。
具体的な手順は以下の記事を参照してください。

[初心者大歓迎！RSpec 3でドラッグアンドドロップ機能をテストする方法(スクリーンキャスト付き)](https://qiita.com/jnchito/items/bdd80c1085fa082e7d7a)

- [テストを書くためのセットアップ](https://qiita.com/jnchito/items/bdd80c1085fa082e7d7a#1-3)
- [Poltergeist をセットアップする](https://qiita.com/jnchito/items/bdd80c1085fa082e7d7a#2-18)

#### テストが失敗する場合はsleep等でテストを停止させてみる

JavaScriptの処理が完了しないうちにテストが先に進んでしまうとテストが失敗します。
その場合は `sleep` を挟むなどして、JavaScriptの処理が終わるまでテストを停止させてください。

```ruby
# Ajaxでデータを保存するテストの例
expect { 
  click_link 'Save'
  sleep 0.5 # 秒数は適当に調整 
}.to change { Item.count }.by(1)
```

このあたりのテクニックは `sleep` 以外を使う方法もいろいろあるので、必要に応じてネットの情報等を参考にしてください。
（とはいえ、僕はたいてい `sleep` を使っているんですが）

**2015.5.10 追記：sleepの代わりにfindやhave_xxxを使う方がベターです**

JavaScriptを使う操作の後に画面上の表示が変化する場合は`find`や`have_xxx`を使うとCapybaraが自動的に処理が完了するまで（＝画面に変化が現れるまで）待ってくれます。（デフォルトの待ち時間は最大2秒間です）

```ruby
expect { 
  click_link 'Save'
  # sleepの代わりにfindで待つ
  find '.alert', text: '保存しました' 
}.to change { Item.count }.by(1)

# または
click_link 'Save'
# sleepの代わりにhave_selectorで待つ
expect(page).to have_selector '.alert', text: '保存しました'
expect(Item.count).to eq 1 
```

詳しくは @syossan27 さんのこちらの記事を参考にしてください。

- [RSpecのsleep処理について](https://qiita.com/syossan27/items/78c479a46dcf963f55ef)

**2020.9.6 追記 他にもこんな記事が参考になります**

sleepに頼らないテクニックについては以下の記事も参考になります。こちらもあわせてどうぞ。

- [sleepやリトライに頼らない安定した feature spec を書こう \- Qiita](https://qiita.com/shunichi/items/1cb7f7cfca74438513d3)
- [不安定テストを生み出すCapybaraを調教する \- Google スライド](https://docs.google.com/presentation/d/11gyKlUfixNjTKi--0KcZ2yR6ZN95b_lYwSvYohvzjHQ/edit#slide=id.g85b58ed1e9_0_132)

### なぜかときどき落ちるテストでリトライする

自動化テストは百発百中で毎回すべてパスするのが理想的ですが、現実的には「よくわからないがいくつかのテストが時々落ちる」ということが起きたりします。
僕の経験上、特に `js: true` を付けたフィーチャスペックでよく起きる印象があります。

どうしても原因を追及できない場合は、「何度かリトライしてパスすればOK、何度やってもダメならNG」と見なすのもアリです。

テストをリトライしたい場合は [rspec-retry](https://github.com/NoRedInk/rspec-retry) というgemを使うと便利です。

```Gemfile 
group :test do
  # ...
  gem 'rspec-retry'
end
```

```rails_helper.rb
# ...
require 'rspec/retry'

RSpec.configure do |config|
  # ...

  # 実行中にリトライのステータスを表示する
  config.verbose_retry = true
  # リトライの原因となった例外を表示する
  config.display_try_failure_messages = true

  # js: true のフィーチャスペックのみリトライを有効にする
  config.around :each, :js do |ex|
    ex.run_with_retry retry: 3
  end
end
```

その他、詳しい使い方はgemのREADMEを参照してください。

https://github.com/NoRedInk/rspec-retry

### マウスオーバー(hover)している間しか表示されない要素をクリックする

マウスオーバー(hover)を再現したい場合は `hover` メソッドを使います。
`js: true` を付けている点にも注意してください。

````ruby
scenario '秘密の隠しボタンをクリックする', js: true do
  visit secret_path
  find('.secret-area').hover
  # hover中しか表示されないボタンをクリックする
  find('.secret-button').click
end
````

### リターンキーの押下をシミュレートする
 
以下の画像のように検索実行ボタンがなく、検索フィールド内でリターンキーを押して検索を実行するフォームを想定します。

![Screen Shot 2016-05-07 at 14.13.43.png](https://qiita-image-store.s3.amazonaws.com/0/7465/4db36d87-5ca6-935a-dcb7-240d4ce98074.png)

```html
<form (略)>
<input type="text" id="search-text">
</form>
```

リターンキーのシミュレートは `js: true` でJavaScriptを有効にした場合しか実行できません。
以下はJSドライバとしてPoltergeistを使った場合の実行例です。（Poltergeist以外では動作確認していません）

```rb 
# 検索テキストを入力
fill_in 'search-text', with: 'Capybara'
# テキストボックス内でリターンキーを押下
find('#search-text').native.send_key(:Enter)
# 検索結果の検証
expect(page).to have_content 'Capybaraの検索結果'
```
 
### HTMLソースを取得する

たとえばHTML要素のサニタイズが適切に行われているかどうかを検証するために、HTMLソースを直接参照したいケースがあります。
以下はページ全体のHTMLを取得するコード例です。

```rb 
visit root_path
html = page.html
# 取得結果 
# <!DOCTYPE html>
# <html>
# <head>
# ...
```

特定の要素だけにフォーカスする場合は以下のようにします。

```rb 
visit root_path

# <div class="navbar">内のHTMLを取得する

# js: true を使わない場合
html = find('.navbar').native.inner_html

# js: true を使う場合 （Poltergeist 1.8以上で有効。Poltergeist以外では動作未確認）
html = find('.navbar')['innerHTML']
```

### User-Agentを偽装する

以下はUser-Agentを iPhone 6（iOS 8.1）に偽装する例です。
テストを実行するドライバによって設定方法が異なるので注意してください。

#### Capybara単体で実行する場合

```ruby
user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/38.0.2125.67 Mobile/12B411 Safari/600.1.4'

Capybara.current_session.driver.header('User-Agent', user_agent)
```

#### Capybara + Poltergeistで実行する場合

```ruby
user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/38.0.2125.67 Mobile/12B411 Safari/600.1.4'

page.driver.headers = { "User-Agent" => user_agent }
```

### プログラミング的にログイン状態を作る/OAuth認証を偽装する

DeviseやOmniAuthなど、メジャーな認証系のgemは、たいていテストを考慮した機能を持っています。
そうした機能を使うとテストコード側でプログラミング的にログイン状態を作ったり、OAuth認証を偽装したりすることができます。

```ruby
# Deviseでプログラミング的にログイン状態を作る
user = User.create(email: 'foo@example.com')
login_as user, scope: :user
visit edit_settings_path

# OmniAuthでTwitter認証を偽装する
OmniAuth.config.test_mode = true
params = { provider: 'twitter', uid: '123545' }
OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new(params)
click_on 'Twitter Login'
```

詳しい使い方は公式Wikiページなど、ネットの情報を参考にしてください。

- [How To: Test with Capybara - plataformatec/devise](https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara)
- [Integration Testing - intridea/omniauth](https://github.com/intridea/omniauth/wiki/Integration-Testing)

### 外部APIと連携する機能をテストする

TwitterやFacebook等のAPIと連携する場合、テストで本当に通信を発生させるのは得策ではありません。
テストが遅くなったり、本物のアカウントが必要になったりして、「いつでも、どこでも、誰でも、何回でもテストを実行できる」ということが困難になるからです。

そこで、こういうケースではモックを使ってAPI連携部分を偽装します。

モックの使い方は以下の記事で詳しく説明しているので、ここでは割愛します。
モックに詳しくない方はぜひ参考にしてみてください。

[使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」](https://qiita.com/jnchito/items/640f17e124ab263a54dd)

また、別のアプローチとして、[VCR](https://github.com/vcr/vcr)というgemを使う方法もあります。
VCRは最初の1回だけ本当に通信を発生＆記録して、2回目以降は記録したデータをモック代わりに使うという、ちょっと変わったgemです。

VCRの使い方はネットの情報や、後述する「[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)」の第10章を参考にしてみてください。


### 新しいウインドウ（新しいタブ）の内容を検証する

```html
<a href="/some_information" target="_blank">新しいウインドウを開く</a>
```

```ruby
scenario '新しいウインドウの内容を検証する', js: true do
  visit some_path
  click_on '新しいウインドウを開く'
  handle = page.driver.browser.window_handles.last
  page.driver.browser.within_window(handle) do
    expect(page).to have_content '新しいウインドウの内容'
  end
end
```

このコードを動かす際は以下の点に注意してください。

- Capybaraのデフォルトドライバではこのテクニックが使えません。必ず `js: true` を付けてください。
- 上記のコードはCapybara(2.4.4)、Poltergeist(1.5.1)の環境で動作確認しました。JavaScriptドライバやgemのバージョンが変わると動作しない恐れがあります。（まだAPIが安定していない気がします）

上記のコードは@tyn-iMarketさんの以下の記事を参考にさせてもらいました。どうもありがとうございました。

[CapybaraのPoltergeistを使ったテストでブラウザの別のタブに移動する](https://qiita.com/tyn-iMarket/items/1481d47a556a45b96496)

## さらにさらに・・・？？

「あ、これも使えそう」と思うようなテクニックが見つかったらまた追記していきます。
いいネタを仕入れたときはQiitaの「通知」でお知らせしますので、よかったらストックしていってください！

## まとめ

というわけで、今回はCapybaraを使ったブラウザ操作の方法を逆引き形式で説明してみました。

Capybaraには他にもいろんな機能がありますし、同じ操作を別の方法で実現する方法もあります。
ただし、僕の経験上は今回紹介した機能で9割以上のフィーチャスペックをカバーできています。
よっぽど凝ったUI操作でない限り、今回紹介した機能でほぼ十分フィーチャスペックが書けるはずです。

まだフィーチャスペックを書いたことがない人は、簡単なテストでいいので今回の記事を参考にしながら一度フィーチャスペックを書いてみてください。

「使えるRSpec入門」シリーズは今回の記事で一応おしまいです。
RSpecに抵抗があった人もこの連載を読んで、「あ、なんか使えそう」って思ってもらえると嬉しいです。

Railsをメインで開発している人は続編としてぜひ **「Everyday Rails - RSpecによるRailsテスト入門」** を読んでみてください。
本書では今回説明したフィーチャスペックを含めて、「Railsではどういうふうにテストを書けば良いのか」を実践的なサンプルを使って説明しています。


[Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)
![Everyday Rails](http://cdn-ak.f.st-hatena.com/images/fotolife/J/JunichiIto/20141024/20141024121511.png)

また、今後もときどきQiitaやブログにRubyやRails、RSpecに関するお役立ち情報を書いていくつもりなので、よかったらQiitaやTwitterのフォローもお願いします。

- [@jnchito - Qiita](https://qiita.com/jnchito)
- [@jnchito - Twitter](https://twitter.com/jnchito)

それではここまで読んでくれてどうもありがとうございました！
またお会いしましょう～。

### 参考文献

- https://github.com/jnicklas/capybara
  - [日本語版README](https://github.com/willnet/capybara-readme-ja)
- https://www.rubydoc.info/github/jnicklas/capybara/master/
  - https://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions
  - https://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Matchers
  - https://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Finders
  - https://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Element