Kedaruma_Bond
@Kedaruma_Bond (Kedaruma Bond)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Rails6 + Bootstrap4 + RSpec トグル表示のリンクやモーダルのテストについて

解決したいこと

  1. Navbar上のトグルスイッチを押してドロップメニューを表示
  2. メニューの中のログインのリンクを押すとログイン用フォームをモーダルで表示
  3. フォーム全部空欄で’送信’ボタンを押し、モーダル上でエラーメッセージが出ることを確認する アプリ上では動くのですが、一連の動作をどうやってRSpecで表記すればよいのか分からず詰まっています。 ご教授いただければ有難いです。

環境

主なGemファイルは以下です。

ruby '2.7.4'

gem 'active_storage_validations', '0.8.2'
gem 'aws-sdk-s3',                 '1.46.0', require: false
gem 'bcrypt',                     '3.1.13'
gem 'bootsnap',                   '1.4.5', require: false
gem 'bootstrap',                  '~> 4.6.0'
gem 'image_processing',           '1.9.3'
gem 'jbuilder',                   '2.9.1'
gem 'jquery-rails'
gem 'mini_magick',                '4.9.5'
gem 'puma',                       '4.3.6'
gem 'rails',                      '6.0.3'
gem 'rails-i18n'
gem 'sass-rails',                 '5.1.0'
gem 'sprockets-rails',             '~> 3.2.2'
gem 'turbolinks',                 '5.2.0'
gem 'uglifier'
gem 'webpacker',                  '~> 3.5'
gem 'will_paginate',              '3.1.8'

group :development, :test do
  gem 'byebug',            '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
  gem 'shoulda-matchers'
  gem 'spring-commands-rspec'
  gem 'sqlite3',           '1.4.2'
end

group :test do
  gem 'capybara',                 '3.28.0'
  gem 'database_cleaner'
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'guard',                    '2.13.0'
  gem 'guard-minitest',           '2.4.4'
  gem 'launchy'
  gem 'minitest-reporters',       '1.1.14'
  gem 'rails-controller-testing', '1.0.4'
  gem 'rspec-rails'
  gem 'selenium-webdriver',       '3.142.4'
  gem 'webdrivers',               '4.1.2'
end

spec/rails_helpの主な内容が以下です


require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
abort("The Rails environment is running in production mode!") if Rails.env.production?


require 'rspec/rails'
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
#capybara内のmethodを読み込む
require 'capybara/rspec'

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!

  config.filter_rails_from_backtrace!

  config.include FactoryBot::Syntax::Methods

  config.before(:each, type: :system) do
    driven_by :selenium_chrome_headless
  end

  config.include ApplicationHelper
  #signup_pathがundefinedと言われたので入れてみた
  config.include Rails.application.routes.url_helpers
  #強制的にCapybara::DSLを読み込む
  config.include Capybara::DSL

  #作成したヘルパーを追加
  config.include TechHelper

  #全てのテストにaggregate_failuresを適用する(ブロックまとめて書いても全部実行する)
  config.define_derived_metadata do |meta|
    meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
  end

end
#shoulda matcherを入れる
Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end
# トグルSWやモーダルなどで非表示になっているものをtestするため
Capybara.ignore_hidden_elements = false

# Capybaraのdriverを指定してみる(デフォルトは:rack_test)
Capybara.default_driver = :rack_test
# jsオプション有効時のドライバを設定(デフォルトは:selenium)
Capybara.javascript_driver = :selenium

#任意の場所でScreenshotを撮るために用意する
def take_screenshot
  page.save_screenshot "tmp/capybara/screenshot-#{DateTime.now}.png"
end

layouts/_navbar.html.erbの抜粋は以下です

# <div class="modal fade role="dialog" aria-hidden="true">はappeication.html.erbに記載

   <nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-secondary">
       <div class="container">
            <a id="logo" class="navbar-brand" href='/'>
                <i class="fas fa-hand-holding-heart"></i>
                ブランド
            </a>
            <button class="navbar-toggler" type="button" id="humberger"    
                    data-toggle="collapse"  
                    data-target="#navcol-1"
                    aria-controls="#navcol-1" aria-expanded="false"
                    aria-label="#navcol-1">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navcol-1">
                <ul class="nav navbar-nav ml-auto">
                    <li>
                        <a class="text-light" href='/'>Home</a>
                    </li>
                    <li>
                        <%= link_to "ログイン", login_path, remote: true, 
                            class: "text-light", id: "modal-open" %>
                    </li>
                    <li>
                        <a class="text-light" href='/signup'>新規登録</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>

layouts/sessions/_login_form-modal.html.erbの内容が以下です

<!-- ログインフォーム用モーダル   -->
<div class="modal-dialog" role="document">
  <div class="modal-content">
    <div class="modal-header">
      <h5 class="modal-title">ログイン</h5>
      <button type="button" class="close" data-dismiss="modal" aria-label="Close">
        <span aria-hidden="true">&times;</span>
      </button>
    </div>
    <hr style="margin-bottom: 3px">
    <div class="modal-body">

        <%= form_with(url: login_path, scope: :session, 
                                       class: 'js-form') do |f| %>
          <!-- ここにエラーメッセージが出る-->
          <div class="js-message-error"></div>

          <%= f.label :email, "メールアドレス" %>
          <%= f.email_field :email, class: 'form-control' %>

          <%= f.label :password, "パスワード" %>
          <%= f.password_field :password, class: 'form-control' %>

          <hr />

          <%= f.submit "送信", class: "mt2 btn btn-info form-btn float-right" %>

        <% end %>   

    </div>
  </div> 
</div>

テストを試みているspecファイル(spec/systems/sessions_spec.rb)は以下です。

require 'rails_helper'

RSpec.describe 'Sessions', type: :system do
  before do
    visit root_path
  end

  it 'モーダルが表示されるか', js: true do 
      find(".navbar-toggler").click
      sleep 0.5
      take_screenshot
      page.evaluate_script('$(".fade").removeClass("fade")')
      click_link 'ログイン'
      sleep 0.5
      is_expected.to have_selector(text: "メールアドレス")
  end

  #一連の流れを以下のように書きましたが、うまくいかず、上記のexampleを書いて確認を試みました。
    # #モーダルを表示して無効な値を入力する
    # describe 'enter an invalid values', js: true do
    #   before do
    #   find(".navbar-toggler").click
    #   page.evaluate_script('$(".fade").removeClass("fade")')
    #   click_link 'ログイン' 
    #   fill_in 'メールアドバイス', with: ''
    #   fill_in 'パスワード', with: ''
    #   click_button '送信'
    # end
    # #エラーメッセージが出る
    # it 'gets an error message' do
    #   is_expected.to have_selector(text: "入力内容に誤りがあります")
    # end
    # #一度モーダルを閉じる
    # context 'close modal' do
    #   before do
    #     click_on 'x'

    #     click_on 'ログイン'
    #   end
    #   #エラーメッセージが消える
    #   it 'is message disappear' do
    #     is_expected.to_not have_selector(text: "入力内容に誤りがあります" )
    #   end
  #   end
  # end
end

発生している問題・エラー

Failures:

  1) Sessions モーダルが表示されるか
     Failure/Error: click_link 'ログイン'

     Selenium::WebDriver::Error::ElementNotInteractableError:
       element not interactable
         (Session info: headless chrome=93.0.4577.82)

     [Screenshot]: toggleが開いておらずプルダウンが出てないスクリーンショット


     # 0   chromedriver                        0x0000000100c976d9 chromedriver + 2758361
     # 1   chromedriver                        0x000000010134a893 chromedriver + 9783443
     # 2   chromedriver                        0x0000000100a2298f chromedriver + 182671
     # 3   chromedriver                        0x0000000100a57f39 chromedriver + 401209
     # 4   chromedriver                        0x0000000100a4c358 chromedriver + 353112
     # 5   chromedriver                        0x0000000100a74022 chromedriver + 516130
     # 6   chromedriver                        0x0000000100a4c0e5 chromedriver + 352485
     # 7   chromedriver                        0x0000000100a7429e chromedriver + 516766
     # 8   chromedriver                        0x0000000100a86514 chromedriver + 591124
     # 9   chromedriver                        0x0000000100a74243 chromedriver + 516675
     # 10  chromedriver                        0x0000000100a4aa0e chromedriver + 346638
     # 11  chromedriver                        0x0000000100a4bc75 chromedriver + 351349
     # 12  chromedriver                        0x0000000100c5e1df chromedriver + 2523615
     # 13  chromedriver                        0x0000000100c709f2 chromedriver + 2599410
     # 14  chromedriver                        0x0000000100c4302b chromedriver + 2412587
     # 15  chromedriver                        0x0000000100c71e1a chromedriver + 2604570
     # 16  chromedriver                        0x0000000100c5396c chromedriver + 2480492
     # 17  chromedriver                        0x0000000100c8bd38 chromedriver + 2710840
     # 18  chromedriver                        0x0000000100c8bec1 chromedriver + 2711233
     # 19  chromedriver                        0x0000000100c9c6f8 chromedriver + 2778872
     # 20  libsystem_pthread.dylib             0x00007fff204a78fc _pthread_start + 224
     # 21  libsystem_pthread.dylib             0x00007fff204a3443 thread_start + 15
     # ./spec/systems/sessions_spec.rb:13:in `block (2 levels) in <main>'

Finished in 6.9 seconds (files took 1.23 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/systems/sessions_spec.rb:8 # Sessions モーダルが表示されるか

自分で試したこと

qiitaの記事などを参考に
js: true を入れる、sleepで少し待つ、page.evaluate_script('$(".fade").removeClass("fade")')を入れてみる
を試みましたが、どれも効果がありませんでした。
スクショではそもそもプルダウンが開いていないので、何か根本的に誤りがあるのかもと感じています

何かお気づきの点があればご指摘いただきたく。
よろしくお願いします。

0

2Answer

# <div class="modal fade role="dialog" aria-hidden="true">はappeication.html.erbに記載

文字通りだとすると class を閉じるダブルクオートが抜けています。入れてみてください。

0Like

Comments

  1. @Kedaruma_Bond

    Questioner

    回答ありがとうございます。
    Codeが長くなってしまい見づらくて申し訳ないです。
    こちらのCodeはapplication.html.erbからの引用箇所で、この部分がないとmodalが表示しないのでおかしいと誤解されないかなと思いコメントで記載した部分です。
    元のファイルからコピペした際に閉じ側ダブルクオートを誤って消してしまったようで、元のファイルのCodeはダブルクオート閉じていました。
  2. なるほど。 save_screenshot の代わりに save_page してみてはどうでしょうか。その時点のページ構造のスナップショットが保存されます。
  3. @Kedaruma_Bond

    Questioner

    引き続きアドバイスありがとうございます。
    なんだかよく分からないのですが、自己解決できたようです。詳細は別途投稿します。
  4. expect(page).to have_link 'ログイン'
    は条件が満たされない場合スリープを挟んで何度かリトライします。
    それで直ったということは長時間のスリープが必要だったのかもしれません。
  5. @Kedaruma_Bond

    Questioner

    ほへぁ・・・なるほど コメントありがとうございます。
    そんなこともあるのですね。

よくわかりませんが、自己解決できたようです。
理由は皆目わかりませんが、

it 'モーダルが表示されるか', js: true do
      find(".navbar-toggler").click
      expect(page).to have_link 'ログイン', href: login_path, visible: false
      page.evaluate_script('$(".fade").removeClass("fade")')
      click_link 'ログイン'
      expect(page).to have_content('メールアドレス')
  end

これでモーダルの表示は確認できました。
sleepを消していますが、別にあってもなくても結果は変わらないので消しています。
何がきっかけで通ったのかは正直全くわかりません。困惑しております。

これを参考にして、やりたかったテストを以下のように記載しました

context '無効な値を入力したとき', js: true do
      before do
      find(".navbar-toggler").click
      page.evaluate_script('$(".fade").removeClass("fade")')
      click_on 'ログイン'
      fill_in 'メールアドレス', with: ''
      fill_in 'パスワード', with: ''
      click_button '送信'
      end
      
      it 'エラーメッセージが出ること' do
        expect(page).to have_content('入力内容に誤りがあります')
      end

    context 'モーダルを一度閉じて再度開いたとき', js: true do
      before do
        page.evaluate_script("$('#login-modal').modal('hide')")
        click_on 'ログイン'
      end
      it 'エラーメッセージが消えていること' do
        expect(page).to_not have_content('入力内容に誤りがあります')
      end
    end
  end

これでテストが通りましたので一先ずよしとしてます。

0Like

Your answer might help someone💌