Capybaraでiframe内の要素を取得/操作する方法(Rails)

【結論】

・iframeタグ配下の要素は、そのままでは直接操作することが出来ない

・iframe内の要素を取得/操作するにはwithin_frameで、iframeを明示的に呼び出す必要がある

within_frameのブロック内であれば、通常のCapybaraの処理でiframe内の要素を取得/操作できる

【目次】

【本題】

経緯:CKEditorで画像アップロードするテストコードを実装したい

以下の様な、CKEditorで画像をアップロードする際の挙動をテストするコードを実装しようとした時の話です。

問題:iframe内の要素が操作出来ない

一先ず以下の様なテストコードを書きましたが、問題箇所でコケてしまいます・・・

  it '画像アップロード', js: true do
    visit new_path

    expect(page).to have_css('.cke_button__image')
    page.first('.cke_button__image').click

    expect(find('.cke_dialog_body')).to have_content('アップロード')
    click_link('アップロード')

    expect(find('.cke_dialog_body')).to have_content('サーバーに送信')
    file_path = Rails.root.join('spec', 'fixtures', 'test_image.png')

 # ここが問題箇所
    page.attach_file('upload', file_path)
    click_link('サーバーに送信')

    expect(find('.cke_dialog_body')).to have_content('URL')
    click_link('OK')

    expect(page).to have_content('保存')
    click_button('保存')

    expect(all('img')[1][:src]).to include('/uploads/image/')
  end

以下がエラーメッセージです。

Campaigns 画像アップロード
     Failure/Error: page.attach_file('upload', file_path)
     
     Capybara::ElementNotFound:
       Unable to find file field "upload"

該当箇所にsave_and_open_pageを設置してみると、ファイル選択のアイコンが表示されていないことが分かりました

↓テスト中にダイアログ

↓本来のダイアログ

デベロッパーツールで確認してみると、このファイル選択の部分だけ、iframeで生成されていることが分かりました。

つまりiframeで後から生成される部分は、そのままでは直接操作することが出来ないという事です。

対応:within_frameでiframeを呼び出す

調べてみると、iframeで生成される箇所は、within_frameで明示的に呼び出す事で、テスト環境でも取得/操作が可能になる様です。

それを受けて、改修したコードがこちらです。

  it '画像アップロード', js: true do
    visit new_path

    expect(page).to have_css('.cke_button__image')
    page.first('.cke_button__image').click

    expect(find('.cke_dialog_body')).to have_content('アップロード')
    click_link('アップロード')

    expect(find('.cke_dialog_body')).to have_content('サーバーに送信')
    file_path = Rails.root.join('spec', 'fixtures', 'test_image.png')

 # ここが修正箇所
    within_frame(all('iframe')[1]) do
      file_path = Rails.root.join('spec', 'fixtures', 'test_image.png')
      page.attach_file('upload', file_path)
    end

    expect(find('.cke_dialog_body')).to have_content('URL')
    click_link('OK')

    expect(page).to have_content('保存')
    click_button('保存')

    expect(all('img')[1][:src]).to include('/uploads/image/')
  end

これで無事テストが通る様になりました!

参考情報

Method: Capybara::Session#within_frame — Documentation for jnicklas/capybara (master)

Ruby Capybara でiframe内のボタンをクリックしたい

Rspec Capybaraで実際テストを書いて困ったシチュエーションの解消法 - Qiita

TinyMCE redux · Issue #445 · teampoltergeist/poltergeist · GitHub