如何让 RSpec & Capybara 等待足够长的时间让 ActionCable 完成?

How to make RSpec & Capybara wait long enough for ActionCable to complete?

Rails 6 与 ActionCable Rspec 3.10 水豚 3.36

  1. 用户访问 edit_inventory_path @inventory 并看到 Count 的集合。

  2. 点击计数会触发 AJAX 调用以使用计数 _form

    填充模式
  3. 填写表格并点击提交触发器:

CountsChannel.broadcast_to(
  @inventory,
  {
    count_id: @count.id,
    html_slug: render_to_string(partial: 'counts/count', collection: @inventory.counts.sort_by { |c| [c.sort_by_status, - c.item.name] }),
    uncounted: "#{view_context.pluralize(@inventory.counts.uncounted.size, 'item')} uncounted."
  }
)

这将替换页面上的每个计数 div。新提交的计数排序到页面底部,按钮文字发生变化。

我的测试期望按钮从“计数”变为“编辑”。

RSpec.describe 'Performing an inventory', type: :system do
  ...
  context 'when submitting a count' do
  ...

    it 'changes the count button text' do
      # making sure page is loaded
      expect(page).to have_content "Edit #{inventory.name}"

      # clicking the button opens a modal.
      # AJAX puts the count edit form in the modal before it open
      find("div#count_#{Count.first.id} a.count-btn").click
      # wait to make sure the modal is open
      find('input#count_unopened_boxes_count')

      fill_in 'count_unopened_boxes_count', with: 5
      click_button('Submit')
      Rails.logger.debug 'HEYA just hit the submit button.'
      # clicking the button closes and clears the modal
      # and AJAXs form data to CountsController#update
      # which triggers the ActionCable broadcast

      # HERE: find('the button', wait: 5) does nothing
      # because the old div is still on the page
      # so Capybara finds it right away
      # wait is a maximum wait time, not an absolute
      count_1_btn_text = find("div#count_#{Count.first.id} a.count-btn").text
      Rails.logger.debug 'HEYA found the button.'

      Rails.logger.debug 'HEYA hoping ActionCable is complete.'
      expect(count_1_btn_text).to eq 'Loose Count'


      # some time later, the button text actually changes
    end
  end
end

我在结帐时知道这是一个时间问题 test.log:

...
Started GET "/inventories/1/edit" for 127.0.0.1 at 2021-11-29 12:51:05 -0500
  ...
Processing by InventoriesController#edit as HTML
  Parameters: {"id"=>"1"}
  ...
  Rendering layout layouts/application.haml
  Rendering inventories/edit.haml within layouts/application
  ...
  Rendered collection of counts/_count.haml [11 times] (Duration: 53.0ms | Allocations: 26025)
  ...
Completed 200 OK in 10635ms (Views: 10574.2ms | ActiveRecord: 15.7ms | Allocations: 645565)
...
Started GET "/assets/channels/consumer-ddc23d99c3f3572746769fa1851baebf4ef5d005651f6ac89f0739c1682a80bc.js" for 127.0.0.1 at 2021-11-29 12:51:15 -0500
Started GET "/assets/channels/counts_channel-6e2c07931c38a8f979370ee55ad4ca4783da4a2def12996ad4efe6f213d4fb78.js" for 127.0.0.1 at 2021-11-29 12:51:16 -0500
Started GET "/cable" for 127.0.0.1 at 2021-11-29 12:51:16 -0500
Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2021-11-29 12:51:16 -0500
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
  [1m[36mUser Load (0.6ms)[0m  [1m[34mSELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1[0m
Registered connection (Z2lkOi8vYnVpbGQtcGxhbm5lci9Vc2VyLzE)
...
  [1m[36mInventory Load (44.2ms)[0m  [1m[34mSELECT "inventories".* FROM "inventories" WHERE "inventories"."id" = 1 LIMIT 1[0m
CountsChannel is transmitting the subscription confirmation
CountsChannel is streaming from counts:Z2lkOi8vYnVpbGQtcGxhbm5lci9JbnZlbnRvcnkvMQ
  ...
  ...
  Rendered counts/_edit.haml (Duration: 48.7ms | Allocations: 28122)
  Rendered counts/edit.js.erb (Duration: 50.3ms | Allocations: 28697)
Completed 200 OK in 66ms (Views: 56.2ms | ActiveRecord: 3.4ms | Allocations: 34258)

HEYA just hit the submit button.

  ...
Started PATCH "/inventories/1/counts/1" for 127.0.0.1 at 2021-11-29 12:51:17 -0500
Processing by CountsController#update as JS
  Parameters: {"count"=>{"loose_count"=>"0", "unopened_boxes_count"=>"5"}, "partial_box"=>"Submit Box Count", "inventory_id"=>"1", "id"=>"1"}

HEYA found the button.

HEYA hoping ActionCable is complete.

... 49 lines later ...
[ActionCable] Broadcasting to counts:Z2lkOi8vYnVpbGQtcGxhbm5lci9JbnZlbnRvcnkvMQ: {:count_id=>1, :html_slug=>"...real long string...", :uncounted=>"11 items uncounted."}
  Rendering counts/update.js.erb
CountsChannel transmitting {"count_id"=>1, "html_slug"=>"...really long string... (via streamed from counts:Z2lkOi8vYnVpbGQtcGxhbm5lci9JbnZlbnRvcnkvMQ)
  Rendered counts/update.js.erb (Duration: 41.1ms | Allocations: 674)
Completed 200 OK in 344ms (Views: 56.4ms | ActiveRecord: 123.9ms | Allocations: 30670)
Finished "/cable/" [WebSocket] for 127.0.0.1 at 2021-11-29 12:51:18 -0500
CountsChannel stopped streaming from counts:Z2lkOi8vYnVpbGQtcGxhbm5lci9JbnZlbnRvcnkvMQ

我被教导做 sleep() 之类的事情是不好的做法,那么我有什么选择可以强迫水豚等几秒钟?

只需告诉 Capybara 您希望它在页面上找到什么以及等待它出现的最长时间。在您的问题中,您说您希望按钮从“计数”更改为“编辑”,但随后您的代码正在检查“松散计数”,因此我不完全清楚您期望的可见变化到底是什么,但假设是后者,你可以做类似

的事情
expect(page).to have_css("div#count_#{Count.first.id} a.count-btn", text: 'Loose Count', wait: 10) 

此外,在 find 之后执行 fill_in 只是为了等待元素出现是没有意义的,因为 fill_in 已经在等待元素出现,所以

find('input#count_unopened_boxes_count')
fill_in 'count_unopened_boxes_count', with: 5

实际上是一样的
fill_in 'count_unopened_boxes_count', with: 5