mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-07 02:22:49 +08:00
…by using auto-retrying assertions and avoiding stale element issues
with non-chained `find` and using `has_css` without `find`.
The original errors:
```
1) AI Composer Proofreading Features when triggering via keyboard shortcut confirms changes when pressing Enter
Failure/Error: expect(composer_input.value).to eq(value)
expected: "hello world"
got: "hello worrld"
(compared using ==)
```
```
1) Thread indicator for chat messages when threading is enabled for the channel updates the last reply excerpt and participants when a new message is added to the thread
Failure/Error:
expect(channel_page.message_thread_indicator(thread_1.original_message)).to have_participant(
new_user,
)
expected `#<PageObjects::Components::Chat::ThreadIndicator:0x00007f6760a37680 @context=".chat-channel .chat-messages-container .chat-message-container[data-id=\"10000000001\"]">.has_participant?(#<User …)` to be truthy, got false
```
416 lines
10 KiB
Ruby
416 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module PageObjects
|
|
module Components
|
|
class Composer < PageObjects::Components::Base
|
|
COMPOSER_ID = "#reply-control"
|
|
AUTOCOMPLETE_MENU = ".autocomplete.ac-emoji"
|
|
HASHTAG_MENU = ".autocomplete.hashtag-autocomplete"
|
|
MENTION_MENU = ".autocomplete.ac-user"
|
|
RICH_EDITOR = ".d-editor-input.ProseMirror"
|
|
POST_LANGUAGE_SELECTOR = ".post-language-selector-trigger"
|
|
|
|
def initialize(composer_id = COMPOSER_ID)
|
|
@composer_id = composer_id
|
|
end
|
|
|
|
def rich_editor
|
|
find(RICH_EDITOR)
|
|
end
|
|
|
|
def has_rich_editor?
|
|
page.has_css?(RICH_EDITOR)
|
|
end
|
|
|
|
def has_no_rich_editor?
|
|
page.has_no_css?(RICH_EDITOR)
|
|
end
|
|
|
|
def opened?
|
|
page.has_css?("#{@composer_id}.open")
|
|
end
|
|
|
|
def closed?
|
|
page.has_css?("#{@composer_id}.closed", visible: :all)
|
|
end
|
|
|
|
def minimized?
|
|
page.has_css?("#{@composer_id}.draft")
|
|
end
|
|
|
|
def open_composer_actions
|
|
find(".composer-action-title .btn").click
|
|
self
|
|
end
|
|
|
|
def click_toolbar_button(button_class)
|
|
find(".d-editor-button-bar button.#{button_class}").click
|
|
self
|
|
end
|
|
|
|
def heading_menu
|
|
PageObjects::Components::DMenu.new(find(".d-editor-button-bar button.heading"))
|
|
end
|
|
|
|
def list_menu
|
|
PageObjects::Components::DMenu.new(find(".d-editor-button-bar button.list"))
|
|
end
|
|
|
|
def focus
|
|
find(composer_input_selector).click
|
|
self
|
|
end
|
|
|
|
def fill_title(title)
|
|
find("#{@composer_id} #reply-title").fill_in(with: title)
|
|
self
|
|
end
|
|
|
|
def has_input_title?(value)
|
|
has_field?("reply-title", with: value)
|
|
end
|
|
|
|
def fill_content(content)
|
|
find("#{@composer_id} .d-editor .d-editor-input").fill_in(with: content)
|
|
self
|
|
end
|
|
|
|
def minimize
|
|
find("#{@composer_id} .toggle-minimize").click
|
|
self
|
|
end
|
|
|
|
def append_content(content)
|
|
current_content = composer_input.value
|
|
composer_input.set(current_content + content)
|
|
self
|
|
end
|
|
|
|
def fill_form_template_field(field, content)
|
|
form_template_field(field).fill_in(with: content)
|
|
self
|
|
end
|
|
|
|
def type_content(content)
|
|
composer_input.send_keys(content)
|
|
self
|
|
end
|
|
|
|
def clear_content
|
|
fill_content("")
|
|
end
|
|
|
|
def has_content?(content)
|
|
composer_input.value == content
|
|
end
|
|
|
|
def has_value?(value)
|
|
within(@composer_id) do
|
|
if value.nil?
|
|
has_no_field?(class: "d-editor-input")
|
|
else
|
|
has_field?(class: "d-editor-input", with: value)
|
|
end
|
|
end
|
|
end
|
|
|
|
def has_popup_content?(content)
|
|
composer_popup.has_content?(content)
|
|
end
|
|
|
|
def has_no_action?(action)
|
|
!actions.include?(action)
|
|
end
|
|
|
|
def select_action(action)
|
|
find(action(action)).click
|
|
self
|
|
end
|
|
|
|
def reply_button_focused?
|
|
page.has_css?("#{@composer_id} .btn-primary:focus")
|
|
end
|
|
|
|
def create
|
|
find("#{@composer_id} .btn-primary").click
|
|
end
|
|
|
|
def action(action_title)
|
|
".composer-action-title .select-kit-collection li[title='#{action_title}']"
|
|
end
|
|
|
|
def actions
|
|
all(".composer-action-title .select-kit-collection li").map { |el| el[:title] }
|
|
end
|
|
|
|
def button_label
|
|
find("#{@composer_id} .btn-primary .d-button-label")
|
|
end
|
|
|
|
def emoji_picker
|
|
find("#{@composer_id} .emoji-picker")
|
|
end
|
|
|
|
def emoji_autocomplete
|
|
find(AUTOCOMPLETE_MENU)
|
|
end
|
|
|
|
def category_chooser
|
|
Components::SelectKit.new(".category-chooser")
|
|
end
|
|
|
|
def locale
|
|
find("#{@composer_id} .d-editor-button-bar #{POST_LANGUAGE_SELECTOR}")
|
|
end
|
|
|
|
def set_locale(locale)
|
|
click_toolbar_button("post-language-selector-trigger")
|
|
within("#d-menu-portals", visible: false) { find("button", text: locale).click }
|
|
end
|
|
|
|
def switch_category(category_name)
|
|
category_chooser.expand
|
|
category_chooser.select_row_by_name(category_name)
|
|
end
|
|
|
|
def preview
|
|
find("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_discard_draft_modal?
|
|
page.has_css?(".discard-draft-modal")
|
|
end
|
|
|
|
def has_hashtag_autocomplete?
|
|
has_css?(HASHTAG_MENU)
|
|
end
|
|
|
|
def has_mention_autocomplete?
|
|
has_css?(MENTION_MENU)
|
|
end
|
|
|
|
def mention_menu_autocomplete_username_list
|
|
find(MENTION_MENU).all("a").map { |a| a.text }
|
|
end
|
|
|
|
def has_emoji_autocomplete?
|
|
has_css?(AUTOCOMPLETE_MENU)
|
|
end
|
|
|
|
def has_no_emoji_autocomplete?
|
|
has_no_css?(AUTOCOMPLETE_MENU)
|
|
end
|
|
|
|
EMOJI_SUGGESTION_SELECTOR = "#{AUTOCOMPLETE_MENU} .emoji-shortname"
|
|
|
|
def has_emoji_suggestion?(emoji)
|
|
has_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
|
|
end
|
|
|
|
def has_no_emoji_suggestion?(emoji)
|
|
has_no_css?(EMOJI_SUGGESTION_SELECTOR, text: emoji)
|
|
end
|
|
|
|
def has_emoji_preview?(emoji)
|
|
page.has_css?(emoji_preview_selector(emoji))
|
|
end
|
|
|
|
def has_no_emoji_preview?(emoji)
|
|
page.has_no_css?(emoji_preview_selector(emoji))
|
|
end
|
|
|
|
def composer_input_selector
|
|
"#{@composer_id} .d-editor-input"
|
|
end
|
|
|
|
def has_no_composer_input?
|
|
page.has_no_css?(composer_input_selector)
|
|
end
|
|
|
|
def has_composer_input?
|
|
page.has_css?(composer_input_selector)
|
|
end
|
|
|
|
def has_composer_preview?
|
|
page.has_css?("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_no_composer_preview?
|
|
page.has_no_css?("#{@composer_id} .d-editor-preview-wrapper")
|
|
end
|
|
|
|
def has_composer_preview_toggle?
|
|
page.has_css?("#{@composer_id} .toggle-preview")
|
|
end
|
|
|
|
def has_no_composer_preview_toggle?
|
|
page.has_no_css?("#{@composer_id} .toggle-preview")
|
|
end
|
|
|
|
def has_form_template?
|
|
page.has_css?(".form-template-form__wrapper")
|
|
end
|
|
|
|
def has_form_template_field?(field)
|
|
page.has_css?(".form-template-field[data-field-type='#{field}']")
|
|
end
|
|
|
|
def has_form_template_field_required_indicator?(field)
|
|
page.has_css?(
|
|
".form-template-field[data-field-type='#{field}'] .form-template-field__required-indicator",
|
|
)
|
|
end
|
|
|
|
FORM_TEMPLATE_CHOOSER_SELECTOR = ".composer-select-form-template"
|
|
|
|
def has_no_form_template_chooser?
|
|
page.has_no_css?(FORM_TEMPLATE_CHOOSER_SELECTOR)
|
|
end
|
|
|
|
def has_form_template_chooser?
|
|
page.has_css?(FORM_TEMPLATE_CHOOSER_SELECTOR)
|
|
end
|
|
|
|
def has_form_template_field_error?(error)
|
|
page.has_css?(".form-template-field__error", text: error, visible: :all)
|
|
end
|
|
|
|
def has_no_form_template_field_error?(error)
|
|
page.has_no_css?(".form-template-field__error", text: error, visible: :all)
|
|
end
|
|
|
|
def has_form_template_field_label?(label)
|
|
page.has_css?(".form-template-field__label", text: label)
|
|
end
|
|
|
|
def has_form_template_field_description?(description)
|
|
page.has_css?(".form-template-field__description", text: description)
|
|
end
|
|
|
|
def has_post_error?(error)
|
|
page.has_css?(".popup-tip", text: error, visible: all)
|
|
end
|
|
|
|
def has_no_post_error?(error)
|
|
page.has_no_css?(".popup-tip", text: error, visible: all)
|
|
end
|
|
|
|
def composer_input
|
|
find("#{@composer_id} .d-editor-input")
|
|
end
|
|
|
|
def composer_popup
|
|
find("#{@composer_id} .composer-popup")
|
|
end
|
|
|
|
def form_template_field(field)
|
|
find(".form-template-field[data-field-type='#{field}']")
|
|
end
|
|
|
|
def move_cursor_after(text)
|
|
execute_script(<<~JS, text)
|
|
const text = arguments[0];
|
|
const composer = document.querySelector("#{@composer_id} .d-editor-input");
|
|
const index = composer.value.indexOf(text);
|
|
const position = index + text.length;
|
|
|
|
composer.focus();
|
|
composer.setSelectionRange(position, position);
|
|
JS
|
|
end
|
|
|
|
def select_all
|
|
find(composer_input_selector).send_keys([PLATFORM_KEY_MODIFIER, "a"])
|
|
end
|
|
|
|
def select_range(start_index, length)
|
|
execute_script(<<~JS, text)
|
|
const composer = document.querySelector("#{@composer_id} .d-editor-input");
|
|
composer.focus();
|
|
composer.setSelectionRange(#{start_index}, #{length});
|
|
JS
|
|
end
|
|
|
|
def select_range_rich_editor(start_index, length)
|
|
focus
|
|
select_text_range(RICH_EDITOR, start_index, length)
|
|
end
|
|
|
|
def submit
|
|
find("#{@composer_id} .save-or-cancel .create").click
|
|
end
|
|
|
|
def discard
|
|
find("#{@composer_id} .discard-button").click
|
|
end
|
|
|
|
def close
|
|
find("#{@composer_id} .toggle-save-and-close").click
|
|
end
|
|
|
|
def has_no_in_progress_uploads?
|
|
find("#{@composer_id}").has_no_css?("#file-uploading")
|
|
end
|
|
|
|
def has_in_progress_uploads?
|
|
find("#{@composer_id}").has_css?("#file-uploading")
|
|
end
|
|
|
|
def select_pm_user(username)
|
|
select_kit = PageObjects::Components::SelectKit.new("#private-message-users")
|
|
select_kit.expand
|
|
select_kit.search(username)
|
|
select_kit.select_row_by_value(username)
|
|
select_kit.collapse
|
|
end
|
|
|
|
def has_rich_editor_active?
|
|
find("#{@composer_id}").has_css?(".d-editor-container.--rich-editor-enabled")
|
|
end
|
|
|
|
def has_no_rich_editor_active?
|
|
find("#{@composer_id}").has_css?(".d-editor-container.--markdown-editor-enabled")
|
|
end
|
|
|
|
def has_markdown_editor_active?
|
|
has_no_rich_editor_active?
|
|
end
|
|
|
|
def toggle_rich_editor
|
|
rich = page.find(".composer-toggle-switch")["data-rich-editor"]
|
|
|
|
editor_toggle_switch.click
|
|
|
|
if rich
|
|
has_no_rich_editor_active?
|
|
else
|
|
has_rich_editor_active?
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def has_toggle_switch?
|
|
page.has_css?("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def has_no_toggle_switch?
|
|
page.has_no_css?("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def editor_toggle_switch
|
|
find("#{@composer_id} .composer-toggle-switch")
|
|
end
|
|
|
|
def image_grid
|
|
Components::ComposerImageGrid.new(rich_editor)
|
|
end
|
|
|
|
private
|
|
|
|
def emoji_preview_selector(emoji)
|
|
".d-editor-preview .emoji[title=':#{emoji}:']"
|
|
end
|
|
end
|
|
end
|
|
end
|