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

f:id:ryoutaku_jo:20190817012750p:plain

【結論】

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

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

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

【目次】

【本題】

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

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

f:id:ryoutaku_jo:20190817030734g:plain

問題: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

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

f:id:ryoutaku_jo:20190817031029p:plain

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

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

↓テスト中にダイアログ

f:id:ryoutaku_jo:20190817031404p:plain

↓本来のダイアログ

f:id:ryoutaku_jo:20190817031352p:plain

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

f:id:ryoutaku_jo:20190817031519p:plain

つまり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 - Ruby Capybara でiframe内のボタンをクリックしたい|teratail

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

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

《今日の学習進捗(3年以内に10000時間に向けて)》

テストを拡張していたが、JavaScriptが絡むテストコードの実装に苦慮している。

1: まず、CKEditorでの画像アップロードのテストを実装していたが、ダイアログ中の要素を取得する事が出来ない問題が発生した。 調べてみると、どうやら「iframe」で生成されている要素は「within_frame」で明示的に呼び出さす必要がある様で、それで解消した。

2: 次に、何度かテストを繰り返すと、たまにテストが失敗する問題が発生した。 expectでボタンの存在を判定してから次の処理に進む様に記述したが、それでも稀にテストがコケる事があり、最終的にsleepで次の処理に進むタイミングをズラす事で一旦解消した。 しかし、sleepの使用は推奨されていない解説なども散見されたので、他の方法を模索した方が良いかもしれない。

3: ローカルでは正常にテストが通るが、CIでは通らない問題が発生した。 調べてみると、CI環境ではCLI上でテストを実行する必要があるので、ヘッドレスドライバを使用する必要がある様だった。 現状、ChromeDriverをwebdriversでインストールしているが、サポートが終了しているchromedriver-helperでインストールして実装する記事ばかり出てきて、上手く実装することが出来なかった。 これ以上、自分で調査しても解決困難なので、週明けにCTOへ相談したい。 なお、jsのテストが全て落ちるわけではないので、別要因かもしれない。

学習開始からの期間 :252日
今日までの合計時間:2391h
一日あたりの平均学習時間:9.5h
今日までに到達すべき目標時間:2301h
目標との解離:90h
「10,000時間」まで、

残り・・・「7609時間!」