DEV: Speed up core system tests (#21394)

What is the problem?

We are relying on RSpec custom matchers in system tests by defining
predicates in page objects. The problem is that this can result in a
system test unnecessarily waiting up till the full duration of
Capybara's default wait time when the RSpec custom matcher is used with
`not_to`. Considering this topic page object where we have a `has_post?`
predicate defined.

```
class Topic < PageObject
  def has_post?
    has_css?('something')
  end
end
```

The assertion `expect(Topic.new).not_to have_post` will end up waiting
the full Capybara's default wait time since the RSpec custom matcher is
calling Capybara's `has_css?` method which will wait until the selector
appear. If the selector has already disappeared by the time the
assertion is called, we end up waiting for something that will never
exists.

This commit fixes such cases by introducing new predicates that uses
the `has_no_*` versions of Capybara's node matchers.

For future reference, `to have_css` and `not_to have_css` is safe to sue
because the RSpec matcher defined by Capbyara is smart enough to call
`has_css?` or `has_no_css?` based on the expectation of the assertion.
This commit is contained in:
Alan Guo Xiang Tan 2023-05-05 07:45:53 +08:00 committed by GitHub
parent f705e6d367
commit e323628d8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 96 additions and 27 deletions

View File

@ -43,7 +43,7 @@ describe "Bookmarking posts and topics", type: :system, js: true do
bookmark_modal.fill_name("something important") bookmark_modal.fill_name("something important")
bookmark_modal.cancel bookmark_modal.cancel
expect(topic_page).not_to have_post_bookmarked(post) expect(topic_page).to have_no_post_bookmarked(post)
expect(Bookmark.exists?(bookmarkable: post, user: user)).to eq(false) expect(Bookmark.exists?(bookmarkable: post, user: user)).to eq(false)
end end

View File

@ -28,7 +28,7 @@ describe "Edit Category", type: :system, js: true do
it "should allow you to select and save a form template" do it "should allow you to select and save a form template" do
category_page.visit_edit_template(category) category_page.visit_edit_template(category)
category_page.toggle_form_templates category_page.toggle_form_templates
expect(category_page).not_to have_d_editor expect(category_page).to have_no_d_editor
category_page.select_form_template(form_template.name) category_page.select_form_template(form_template.name)
expect(category_page).to have_selected_template(form_template.name) expect(category_page).to have_selected_template(form_template.name)
category_page.save_settings category_page.save_settings
@ -57,7 +57,7 @@ describe "Edit Category", type: :system, js: true do
it "should have form templates enabled and showing the selected templates" do it "should have form templates enabled and showing the selected templates" do
category_page.visit_edit_template(category) category_page.visit_edit_template(category)
expect(category_page).to have_form_template_enabled expect(category_page).to have_form_template_enabled
expect(category_page).not_to have_d_editor expect(category_page).to have_no_d_editor
selected_templates = "#{form_template.name},#{form_template_2.name}" selected_templates = "#{form_template.name},#{form_template_2.name}"
expect(category_page).to have_selected_template(selected_templates) expect(category_page).to have_selected_template(selected_templates)
end end

View File

@ -91,7 +91,7 @@ describe "Custom sidebar sections", type: :system, js: true do
expect(sidebar).to have_section("Edited section") expect(sidebar).to have_section("Edited section")
expect(sidebar).to have_section_link("Edited Tag") expect(sidebar).to have_section_link("Edited Tag")
expect(sidebar).not_to have_section_link("Sidebar Categories") expect(sidebar).to have_no_section_link("Sidebar Categories")
end end
it "allows the user to reorder links in custom section" do it "allows the user to reorder links in custom section" do
@ -161,7 +161,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.delete section_modal.delete
section_modal.confirm_delete section_modal.confirm_delete
expect(sidebar).not_to have_section("My section") expect(sidebar).to have_no_section("My section")
end end
it "allows admin to create, edit and delete public section" do it "allows admin to create, edit and delete public section" do
@ -188,7 +188,7 @@ describe "Custom sidebar sections", type: :system, js: true do
section_modal.delete section_modal.delete
section_modal.confirm_delete section_modal.confirm_delete
expect(sidebar).not_to have_section("Edited public section") expect(sidebar).to have_no_section("Edited public section")
end end
it "shows anonymous public sections" do it "shows anonymous public sections" do

View File

@ -54,19 +54,19 @@ describe "Emoji deny list", type: :system, js: true do
composer.click_toolbar_button("insert-emoji") composer.click_toolbar_button("insert-emoji")
expect(composer.emoji_picker).to be_visible expect(composer.emoji_picker).to be_visible
expect(emoji_picker).not_to have_emoji("fu") expect(emoji_picker).to have_no_emoji("fu")
end end
it "should not show denied emojis and aliases in emoji autocomplete" do it "should not show denied emojis and aliases in emoji autocomplete" do
topic_page.visit_topic_and_open_composer(topic) topic_page.visit_topic_and_open_composer(topic)
composer.type_content(":poop") # shows no results composer.type_content(":poop") # shows no results
expect(composer).not_to have_emoji_autocomplete expect(composer).to have_no_emoji_autocomplete
composer.clear_content composer.clear_content
composer.type_content(":middle") # middle_finger is alias composer.type_content(":middle") # middle_finger is alias
expect(composer).not_to have_emoji_suggestion("fu") expect(composer).to have_no_emoji_suggestion("fu")
end end
it "should not show denied emoji in preview" do it "should not show denied emoji in preview" do
@ -78,7 +78,7 @@ describe "Emoji deny list", type: :system, js: true do
composer.clear_content composer.clear_content
composer.fill_content(":fu:") composer.fill_content(":fu:")
expect(composer).not_to have_emoji_preview("fu") expect(composer).to have_no_emoji_preview("fu")
end end
end end

View File

@ -76,12 +76,26 @@ module PageObjects
has_css?(AUTOCOMPLETE_MENU) has_css?(AUTOCOMPLETE_MENU)
end end
def has_no_emoji_autocomplete?
has_no_css?(AUTOCOMPLETE_MENU)
end
EMOJI_SUGGESTION_SELECTOR = "#{AUTOCOMPLETE_MENU} .emoji-shortname"
def has_emoji_suggestion?(emoji) def has_emoji_suggestion?(emoji)
has_css?("#{AUTOCOMPLETE_MENU} .emoji-shortname", text: emoji) has_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
end
def has_no_emoji_suggestion?(emoji)
has_no_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
end end
def has_emoji_preview?(emoji) def has_emoji_preview?(emoji)
page.has_css?(".d-editor-preview .emoji[title=':#{emoji}:']") page.has_css?(emoji_preview_selector(emoji))
end
def has_no_emoji_preview?(emoji)
page.has_no_css?(emoji_preview_selector(emoji))
end end
def composer_input def composer_input
@ -91,6 +105,12 @@ module PageObjects
def composer_popup def composer_popup
find("#{COMPOSER_ID} .composer-popup") find("#{COMPOSER_ID} .composer-popup")
end end
private
def emoji_preview_selector(emoji)
".d-editor-preview .emoji[title=':#{emoji}:']"
end
end end
end end
end end

View File

@ -18,6 +18,10 @@ module PageObjects
def has_emoji?(emoji_name) def has_emoji?(emoji_name)
page.has_css?(emoji_button_selector(emoji_name)) page.has_css?(emoji_button_selector(emoji_name))
end end
def has_no_emoji?(emoji_name)
page.has_no_css?(emoji_button_selector(emoji_name))
end
end end
end end
end end

View File

@ -38,19 +38,35 @@ module PageObjects
end end
def has_section_link?(name, href: nil, active: false) def has_section_link?(name, href: nil, active: false)
attributes = {} section_link_present?(name, href: href, active: active, present: true)
attributes[:href] = href if href end
attributes[:class] = SIDEBAR_SECTION_LINK_SELECTOR
attributes[:class] += "--active" if active def has_no_section_link?(name, href: nil, active: false)
has_link?(name, **attributes) section_link_present?(name, href: href, active: active, present: false)
end end
def custom_section_modal_title def custom_section_modal_title
find("#discourse-modal-title") find("#discourse-modal-title")
end end
SIDEBAR_WRAPPER_SELECTOR = ".sidebar-wrapper"
def has_section?(name) def has_section?(name)
find(".sidebar-wrapper").has_button?(name) find(SIDEBAR_WRAPPER_SELECTOR).has_button?(name)
end
def has_no_section?(name)
find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name)
end
private
def section_link_present?(name, href: nil, active: false, present:)
attributes = {}
attributes[:href] = href if href
attributes[:class] = SIDEBAR_SECTION_LINK_SELECTOR
attributes[:class] += "--active" if active
page.public_send(present ? :has_link? : :has_no_link?, name, **attributes)
end end
end end
end end

View File

@ -40,8 +40,14 @@ module PageObjects
find(".d-toggle-switch .toggle-template-type", visible: false)["aria-checked"] == "true" find(".d-toggle-switch .toggle-template-type", visible: false)["aria-checked"] == "true"
end end
D_EDITOR_SELECTOR = ".d-editor"
def has_d_editor? def has_d_editor?
page.has_selector?(".d-editor") page.has_selector?(D_EDITOR_SELECTOR)
end
def has_no_d_editor?
page.has_no_selector?(D_EDITOR_SELECTOR)
end end
def has_selected_template?(template_name) def has_selected_template?(template_name)

View File

@ -29,16 +29,28 @@ module PageObjects
find(".d-header #search-button").click find(".d-header #search-button").click
end end
SEARCH_RESULT_SELECTOR = ".search-results .fps-result"
def has_search_result? def has_search_result?
page.has_selector?(".search-results .fps-result") page.has_selector?(SEARCH_RESULT_SELECTOR)
end
def has_no_search_result?
page.has_no_selector?(SEARCH_RESULT_SELECTOR)
end end
def has_warning_message? def has_warning_message?
page.has_selector?(".search-results .warning") page.has_selector?(".search-results .warning")
end end
def is_search_page SEARCH_PAGE_SELECTOR = "body.search-page"
has_css?("body.search-page")
def active?
has_css?(SEARCH_PAGE_SELECTOR)
end
def not_active?
has_no_css?(SEARCH_PAGE_SELECTOR)
end end
end end
end end

View File

@ -58,9 +58,11 @@ module PageObjects
end end
def has_post_bookmarked?(post) def has_post_bookmarked?(post)
within post_by_number(post) do is_post_bookmarked(post, bookmarked: true)
has_css?(".bookmark.with-reminder.bookmarked")
end end
def has_no_post_bookmarked?(post)
is_post_bookmarked(post, bookmarked: false)
end end
def expand_post_actions(post) def expand_post_actions(post)
@ -142,6 +144,15 @@ module PageObjects
def topic_footer_button_id(button) def topic_footer_button_id(button)
"#topic-footer-button-#{button}" "#topic-footer-button-#{button}"
end end
def is_post_bookmarked(post, bookmarked:)
within post_by_number(post) do
page.public_send(
bookmarked ? :has_css? : :has_no_css?,
".bookmark.with-reminder.bookmarked",
)
end
end
end end
end end
end end

View File

@ -23,7 +23,7 @@ describe "Search", type: :system, js: true do
expect(search_page.heading_text).not_to eq("Search") expect(search_page.heading_text).not_to eq("Search")
search_page.click_home_logo search_page.click_home_logo
expect(search_page.is_search_page).to be_falsey expect(search_page).to be_not_active
page.go_back page.go_back
# ensure results are still there when using browser's history # ensure results are still there when using browser's history
@ -32,7 +32,7 @@ describe "Search", type: :system, js: true do
search_page.click_home_logo search_page.click_home_logo
search_page.click_search_icon search_page.click_search_icon
expect(search_page).not_to have_search_result expect(search_page).to have_no_search_result
expect(search_page.heading_text).to eq("Search") expect(search_page.heading_text).to eq("Search")
end end
end end