vue.js - 在使用 Vue 和 Rails 时如何避免闪烁的规范?
问题描述
我们最近一直在将 Vue 集成到我们的 Rails 应用程序中,但这导致了很多闪烁的 Capybara/rspec 测试,据我所知,这些测试来自竞争条件,其中部分或全部 Vue 元素在下一个 Capybara 动作/预期开始,或者可能正在渲染,但不知何故模糊了元素。
我出现的错误类型如下:
element click intercepted: Element is not clickable at point (392, 641)
(Session info: headless chrome=77.0.3865.90)
(Driver info: chromedriver=77.0.3865.40 (f484704e052e0b556f8030b65b953dce96503217-refs/branch-heads/3865@{#442}),platform=Mac OS X 10.14.6 x86_64) (Selenium::WebDriver::Error::ElementClickInterceptedError)
还有这些:
element click intercepted: Element <input type="submit" name="commit" value="Sign In" data-disable-with="Sign In" class="btn -light -full -solid"> is not clickable at point (272, 37). Other element would receive the click: <div class="site-header__menu__container | container gutter-lg">...</div>
(Session info: headless chrome=77.0.3865.90)
(Driver info: chromedriver=77.0.3865.40 (f484704e052e0b556f8030b65b953dce96503217-refs/branch-heads/3865@{#442}),platform=Mac OS X 10.14.6 x86_64) (Selenium::WebDriver::Error::ElementClickInterceptedError)
(可能值得一提的是,这些测试失败时给出的坐标因一次失败而异)
鉴于 Vue 元素以 0 高度标签开始,可能位于我们的站点菜单下方(本身就是一个 Vue 组件),我认为这两个错误具有相同的原因 - 即我们的目标元素没有(完全?)呈现但是,为了简洁起见,我将只显示其中的代码 - 尽管如果您认为我错了,请告诉我,我将编辑或创建另一个帖子。
我已经尝试了处理此类问题的两篇文章中的方法 - https://engineering.gusto.com/eliminating-flaky-ruby-tests/和https://thoughtbot.com/blog/write -reliable-asynchronous-integration-tests-with-capybara - 但两者似乎都没有太大效果(虽然我不知道该怎么做来代替第一个谴责的“访问”方法,这是核心大多数功能测试)。
这是第一个的相关功能代码:
@vcr
@javascript
Scenario: Successfully pledging after something like confirming my account
Given something like I am on the pledge form
When I submit a valid pledge
Then I should land on the HelloSign signature page
如此实施:
Given("something like I am on the pledge form") do
FactoryBot.create(:user, email: test_user_email, password: test_user_password)
test_user.confirm
step 'something like I have signed up and confirmed'
user = User.last
user.confirm
login_as(user, scope: :user)
visit new_pledge_path
end
When("I submit a valid pledge") do
fill_in('pledge_pledgor_home_postcode', with: 'Up a tree, cutting mistletoe')
fill_in('First name', with: "Asterix")
fill_in('Surname', with: "deGaulle")
fill_in('Phone number', with: '2345678')
fill_in('Home address', with: 'A quiet village near the fortified Roman camp')
fill_in('City', with: 'Totorum')
fill_in('pledge_companies_attributes_0_name', with: 'Circvmbendibvs Wheels')
fill_in('pledge_companies_attributes_0_number', with: '1')
# Minor hack to be able to select options given the JS Choices library's obfuscation:
find('#country-code-select-wrapper .choices').click
find('#choices--pledge_pledgor_phone_code-item-choice-3').click
find('#country-select-wrapper .choices').click
find('#choices--pledge_pledgor_home_country-item-choice-10').click
# Defocus the dropdowns before submitting:
find("body").click
# Need to activate this wrapping block, run once, then remove it when
# refreshing the cassette. This seems dumb - would be nice to find a better solution
# accept_alert do
click_button('Review & Submit') ## unless ENV['IS_CIRCLE'].present?
# end
end
按钮本身不是 Vue 元素,但在页面上它上面还有各种其他元素:
<%= form_for @pledge, html: { class: "standard-form | standard-form-base", id: 'pledge-form' } do |f| %>
<!-- pledge errors - plain html
<%= render "layouts/components/form_feedback" %>
<!-- Sub Section Header - again just html -->
<%= render "layouts/components/sub-section-header.html", content: @new_pledge_page.about_you %>
<!-- @new_pledge_page is a Contentful Model object that -->
<%= render "pledge_fields", content: @new_pledge_page.pledge_fields, f: f %>
<% end %>
而 _pledge_fields.html.erb 部分:
<!-- Group -->
<div class="form-group">
<div class="field-group">
<%= f.label :pledgor_first_name, content.first_name, class: "standard-label" %>
<%= f.text_field :pledgor_first_name, value: @pf_presenter.forenames_estimate %>
</div>
</div>
<!-- Group -->
<div class="form-group">
<div class="field-group">
<%= f.label :pledgor_surname, content.surname, class: "standard-label" %>
<%= f.text_field :pledgor_surname, value: @pf_presenter.surname_estimate %>
</div>
</div>
<!-- Group -->
<div class="form-group">
<div class="field-group | w-2/5" id="country-code-select-wrapper">
<label class="standard-label" for="country">
<%= content.phone_code %>
</label>
<select_box :opt="{ variant: '-standard -md', id: 'pledge_pledgor_phone_code', name: 'pledge[pledgor_phone_code]', value: '<%= f.object.pledgor_phone_code.presence || "1" %>' }">
<%= @pf_presenter.country_phone_code_options %>
</select_box>
</div>
<div class="field-group | w-3/5">
<%= f.label :pledgor_phone_number, content.phone_number, class: "standard-label" %>
<%= f.text_field :pledgor_phone_number, placeholder: "(000) 000-0000" %>
</div>
</div>
<!-- Group -->
<div class="form-group">
<div class="field-group" id="country-select-wrapper">
<label class="standard-label" for="country">
<%= content.country %>
</label>
<select_box :opt="{ variant: '-standard -md', id: 'pledge_pledgor_home_country', name: 'pledge[pledgor_home_country]', value: '<%= f.object.pledgor_home_country.presence || "US" %>' }">
<%= @pf_presenter.country_options(pledge: f.object) %>
</select_box>
</div>
</div>
<!-- Group -->
<div class="form-group">
<div class="field-group">
<%= f.label :pledgor_home_address, content.home_address, class: "standard-label" %>
<%= f.text_field :pledgor_home_address %>
</div>
</div>
<!-- Group -->
<div class="form-group">
<div class="field-group | w-1/3">
<%= f.label :pledgor_home_city, content.city, class: "standard-label" %>
<%= f.text_field :pledgor_home_city %>
</div>
<div class="field-group | w-2/3">
<% if @pf_presenter.probably_based_in?('us') %>
<%= f.label :pledgor_home_postcode, content.zip_code, class: "standard-label" %>
<% else %>
<%= f.label :pledgor_home_postcode, content.postcode, class: "standard-label" %>
<% end %>
<%= f.text_field :pledgor_home_postcode %>
</div>
</div>
<% if !@user.campaign %>
<!-- Sub Section Header -->
<%= render "layouts/components/sub-section-header.html", content: content.company_details_subheader %>
<%= render "company_fields", content: content.company_details, f: f %>
<% end %>
<!-- Sub Section Header -->
<%= render "layouts/components/sub-section-header.html", content: content.how_much_subheader %>
<!-- :id="pledge_percentage" -->
<!-- Range Slider -->
<range_slider :opt="{ id: 'pledge_percentage', name: 'pledge[percentage]' }" :min="2" :start="<%= f.object.percentage || 40 %>" :max="100" class="form-block">
</range_slider>
<!-- Sub Section Header -->
<%= render "layouts/components/sub-section-header.html", content: content.confirmation_subheader %>
<% if !current_user.try :OptedIntoComms__c %>
<div class="<%= 'hidden' if !@pf_presenter.assumed_in_eu? %>" id="js-gdpr-input">
<div class="form-group">
<div class="field-group">
<%= f.check_box :receive_comms, checked: false, disabled: !@pf_presenter.assumed_in_eu? %>
<%= f.label :receive_comms, "#{content.gdpr_label}", class: "standard-label -checkbox" %>
</div>
</div>
</div>
<% else %>
<%= f.hidden_field :receive_comms, value: true %>
<% end %>
<%= f.hidden_field :id %>
<%= f.submit content.review_submit, class: "btn -light -full -solid", id: "submit-pledge" %>
我们的 Gemfile 中的一些可能相关的规格:
ruby "2.6.1"
gem "rails", "5.1.6.2"
gem "rspec-rails", "~> 3.7.2"
gem 'webdrivers', '~> 4.1.2'
gem 'selenium-webdriver', '~> 3.142.4'
gem "capybara", "~> 3.22.0"
解决方案
从您提到的情况来看,您似乎在运行时 + 编译器模式下使用 Vue。
因此,HTML 元素将在 Vue 能够编译它们并使其交互之前可用。
您可以尝试使用v-cloak
,该指令将在元素编译后被删除。
v-cloak
然后,在 Capybara 中,您可以通过从结果中排除元素来确保等到元素编译完成。
:element
使用选择器的示例:
find(:element, 'select_box', 'v-cloak' => nil)
如果您需要经常这样做,您可以扩展 Capybara 的选择器以接收可选vue_loaded: true
属性:
Capybara::Selector.all.each_value do |selector|
selector.instance_eval do
expression_filter(:vue_loaded) do |expression|
builder(expression).add_attribute_conditions('v-cloak': nil)
end
describe_expression_filters do |**options|
if (value = options[:vue_loaded])
" that was already compiled"
end
end
end
end
接着:
find('range_slider', vue_loaded: true)
推荐阅读
- rust - Rayon find_any,并返回找到的项目的值
- mysql - MySQL错误FUNCTION不存在的原因是什么?它随机发生。服务器上有大量的数据库,比如 900
- r - 通过 stringmatch 与 dplyr 和 stringdist 合并两个数据帧
- excel - 如何将文本值单元格中的公式转换为另一个工作簿?
- asp.net-core - ASP.Net Core 2.2 ApiController 上不支持的媒体类型
- c++ - CRTP & 复制/移动赋值/构造函数继承
- django - 如何在docker中安装枕头。?
- c - 如何修复将 rlim_cur 设置为 7 后创建的无限子进程
- electron - 如何将动作的有效负载传递给 redux-observable 中的另一个 rxjs 运算符?
- javascript - 如何使节点 js 文件在 linux 中的某个时间在某个日期运行?