質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.78%

RSpec + CapybaraでのE2EテストにてCKEditor部のfill_inでエラーが発生する

受付中

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,318

sanezane

score 85

■環境情報

ruby on rails:5.2.3
ruby:2.6.1
rspec-rails:3.8.2
capybara:3.18.0
CKEditor(cdn):https://cdn.ckeditor.com/ckeditor5/12.1.0/classic/ckeditor.js
※一旦この状態のソースをgithubへあげています。
Q&A_githubリンク

現状の説明

Q&Aサービス風ポートフォリオを作成中。
RSpec(Capybara)でのE2EテストでCKEditorで実装しているフォームにfill_inで値を入れてもテストが失敗してしまう。以下エラーが発生

エラー内容
Failure/Error: fill_in 'question[content]', with: question_content

     Capybara::ElementNotFound:
       Unable to find visible field "question[content]" that is not disabled


以下specファイルのfill_in 'question[content]', with: question_content部で落ちる。

describe 'QandA管理機能', type: :system do
  # ユーザAを定義
  let(:user_a) { FactoryBot.create(:user, name: 'ユーザA', email: 'a0@email.com') }

  describe '質問新規投稿機能' do
      #ユーザAでログイン
      let(:login_user) { user_a }

      before do
        visit new_question_path
        fill_in 'question[title]', with: question_title
        fill_in 'question[content]', with: question_content #ココ!!!!!!!!!!!!!!!!!!!!!!!!!!!1
        click_button 'Save'
      end

    context '質問新規作成画面で質問を投稿する' do
      let(:question_title) { 'test question title' }
      let(:question_content) { 'test question content' }

      it '投稿が失敗する' do
        expect(page).to have_selector '.alert-danger', text: '質問の投稿に失敗しました。'
      end
    end
  end

end

気になっているところ

CKEditorの実装が間違っているのではないか。。。というのも、ディベロッパーツールで確認してみるとCKEditorは実装できているがid:'editor'部分が別でtextareaとして残っている。そこに追加される形でCKEditorが実装されており、CKEditor部にはidは付与されていない。(しかもtextareaはdisplay:noneで隠れている)
言葉ではわかりにくいので以下にキャプチャを示します。

現状(隠れている状態)

イメージ説明

display:noneを外すと「質問内容」の下になんか出る!!textareaとCKEditor部は別々で表示されるもの??コレ合ってる??

イメージ説明

現状のCKEditorの実装方法

cndを読み込んでjsファイルを作成しCKEditorを適用させたい部分にidを付与してjsを呼ぶ。
以下、jsとCKEditor適用部分のソース。

$(function(){
ClassicEditor
    .create( document.querySelector( '#editor' ) )
    .catch( error => {
        console.error( error );
    } );
    config.width = 500;
    config.height = 400;
});
view(slimで記述)

id:'editor'部がCKEditorを実装している部分。

= form_with model: @question, local: true do |f|
  = f.hidden_field :user_id, { value: current_user.id}
  = f.hidden_field :author, { value: current_user.name}
  .form-group
    label
      | Title
    = f.text_field :title, class: "form-control"
  .form-group
    label
      | 質問内容
    = f.text_area :content, class: "form-control",id:'editor'
    / = f.text_area :content, class: "form-control"
    .text-center
      = f.submit "Save", class: "btn btn-primary mt-3"
div
  = link_to '> Home', root_path, class: 'btn btn-outline-primary mb-3 ml-3'

「CKEditor RSpec」でググっても私と同じ事象で困っている記事が出てこないためご指摘頂ければ幸いです。

5/29追記
teratailのCKEditor見たらちゃんとtextareaになっている。。。やはりCKEditorの実装に問題があるぽい。質問のタイトルも変えるべきか。。
イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

+1

こんにちは。
ちょっと間違っているかもしれませんが、同じようにCKEditorの動きを確認する機会があったので、それを踏まえてコメントしますね。

アプリケーションがRailsの前提でお話します。

id="editor" に対しての操作ができない理由

本来のtextareaに対して、確かCKEditorは以下のように働きます。

  1. 本来のフォームを描画(RailsのViewでのレンダリング)
  2. 非同期であとからCKEditor用のJSの処理が呼び出される
    (私の手元のではiframeを使っていましたが、そうではないかもしれません)
  3. 本来のフォームはdisplat: noneで非表示にする(CKEditor側がそのように変更する)
  4. CKEditor側のメニュー・入力フォームを差し替えて有効な状態にする

Seleniumは要素が非表示だと、セレクタで指定しようとしても操作ができなくなります。
なので、<textarea id='editor'></textarea> はHTML上では存在しているけれど、Seleniumから操作ができないということになります。

一方で、じゃあCKEditorの入力したデータはなぜサーバ側に正しく渡るのかというと、Submitの時にCKEditorのテキストエリアの入力値を、本来の id="editor" のデータに差し替えて送信しているからです。

CKEditorのテキストエリアにSelenimで値をセットするにはどうするの?

では、CKEditor側のテキストエリアに文字をセットできればきっと大丈夫...というところですが、どう指定するといいのか?

いくつか方法があります。

(1) Seleniumのレコーディング機能を使って、いったん手動で目的の操作を実行する。

その際に、実行記録がテストケースとして自動で書き出されますので、それをもとにどんな操作をしているのかを判定する。

以下のような感じになります。

イメージ説明

CKEDitorのテキストエリアに文字を入力している部分は、Selenium IDEのコマンド的には"edit content" で、セレクタは class=.cke_edittable です。
お手元の環境だと、ここがどれに当たるかは若干違うかもしれないので、同じようにレコーディングをしてみてセレクタがどこにあたるかを特定するといいと思います。

同じようなクラスを持った要素がもしたくさんあったとして、できるだけ絞り込めるようにするといいので、おそらく class=.cke_edittable cke_xxxx cke_xxxxxx みたいなものかと思います。

また、レコーディングした処理はエクスポートが可能です。

https://www.seleniumhq.org/selenium-ide/docs/en/introduction/code-export/
https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd (今回キャプチャに利用したもの)

(2) edit content が使えない場合

さて、レコーディングした結果だと、edit content というコマンドを使っているのですが、Capybaraでspecを書く場合、これに対応する操作が見当たらない、実行できないかもしれません。
(細かくAPIドキュメントを読んでいないのですみません)

もしうまくいかない場合は、Capybaraの処理の中で、「HTMLの要素に対しいてキーやマウス入力をさせる」のではなくて、「JavaScriptを実行してCKEditorのテキストエリアに値をセットする」という操作で代用できます。

CKEditorは、CKEDITOR.instances という形でJavaScriptから特定できます
テキストエリアであれば、おそらく CKEDITOR.instances.editor に値を設定できればいいと思います。

Exp. CKEDITOR.instances.editor.setData('なにかの値');

Capybaraの処理の中でここをどう書くかですが、page.execute_script( ... JavaScript ... ); で実行できると思います。

以下も参考になるかと思います。

https://stackoverflow.com/questions/10957869/how-to-fill-ckeditor-from-capybara-with-webkit-or-selenium-driver

長くなりましたが、全く外していましたらご容赦くださいませ...。

追記

CKEditor not definedの件

まず、developmentモードではただしくCKEditorが表示される場合。
testモードでCapybaraでのE2Eテストをする際に、ヘッドレスモードにしていますか?
もしCIではなくローカル開発環境で実行しているようでしたら、可能だったらブラウザが開いて自動で操作が進むなか、CKEditorが描画されるところまでいっているか眺めてみてください。

描画されない場合:CKEditorの設定を確認
描画される場合:テストをwaitさせてみる

自動でテストの場合はブラウザでの読み込みが完了する前にテストのステップが進行してしまうので、「そんな要素は無いよ」といったエラーになることがあります。
CKEditorは上記の通りAjaxで後追いで読み込まれるので、操作をする少し前に、sleep処理を挟んだりAjax処理が完了するのを待つ処理を入れたりするといいのかもしれません。

CKEditorではないのですが、わたしもAjaxの処理を待つ関係で以下のような処理を入れています。

参考:RSpec+Capybaraでajaxとかcssとかを待つ

JSを使わない方法

Capybara + CKEditor + RSpecだと情報が少ないかもしれませんが、CKEditor + Selenium だと、もう少し情報が出てきそうです。
ちょっと探したら以下のようなものがありました。

Pythonのコードですが、page.execute_script() ではなく一般的な「HTMLの要素を見つけてそこにテキストをセットする」方法です。

本来はこちらのほうが自然な気はしますね。
https://bangladroid.wordpress.com/2016/08/20/switch-to-ckeditor-in-selenium-python/

やり方としては、Capybaraでの page が現在のメインのページを表しているので、以下の流れになっています。

  • 念の為CKEditorが描画されるまで少し待つ(ここでもsleepで待っている感じです)
  • CKEditorを描画しているiframe側に操作対象を移す
  • ブラウザで表示の際、CKEditorを描画しているifarmeがありませんか?ターゲットはそこになります
  • 操作対象をiframeに切り替え、その中の body (CKEditorのテキストエリア部分)を特定する
  • editor_body.send_keys("Test Body") を実行
  • 入力後 iframeから外れる場合は操作対象を切り替え

まだ手元では試していないのですが、わたしも知っておいてこれは良さそう!と思いましたので、やってみようと思います。(ソースありがとうございます!)
もしよければ試してみてくださいね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/05/31 00:27

    まず、とても丁寧に回答していただきまして有り難うございます。感動してます。。
    やはりtextareaはdisplay:noneになるんですか!コレは正しかったんですね。
    JavaScriptに依存しているんですね。Capybaraでjavascriptを実行するとのことで提示していただいたサンプルを参考に実装してみました。その中で疑問があり、関連記事がなかなか見つからなかったのでお聞きしたいのですが、`CKEDITOR is not defined`というエラーが出ましてエラー内容から読み解くとCKeditorのjsを何かしらの形でspecへ読み込む必要があると思うのですが、suamaさんはどのように解決しましたか?

    キャンセル

  • 2019/05/31 08:40

    ソースを拝見したら、RailsのSystemTestなんですね。こちらこそ参考になります!
    JSの読み込みなどで遅延があるかもしれないので、ちょっと追記をしてみました
    もし外していたらすみません。。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る