test(chat): Can do a mention in a chat

`tst_chatFlow`:
- Commented out bc of `mailserver` issues and then weak.
- Added basic test scenario for sending a mention in a public chat with needed validations.
- Added basic test scenario to check a mention cannot be done if it is a non existing user.

`StatusChatScreen` updates:
- Updated join room method to validate the chat is loaded.
- Added methods for doing and verifying a mention.

`SquishDriver` updates:
- Added support in `SquishDriver` to click into a link in a text or label component.
- Minor function renames.

Closes #6879
This commit is contained in:
Noelia 2022-08-10 08:57:34 +02:00 committed by Noelia
parent 7b54bf31b4
commit 1334fbb5f4
11 changed files with 170 additions and 10 deletions

4
.gitignore vendored
View File

@ -42,9 +42,7 @@ nim_status_client.log
test/ui-test/testSuites/suite_status/config.xml test/ui-test/testSuites/suite_status/config.xml
test/ui-test/testSuites/suite_status/envvars test/ui-test/testSuites/suite_status/envvars
test/ui-test/testSuites/suite_status/shared/scripts/__pycache__/* test/ui-test/**/__pycache__/*
test/ui-test/testSuites/suite_status/shared/scripts/sections/__pycache__/*
# CPP app ===================================================================== # CPP app =====================================================================

View File

@ -8,6 +8,7 @@
# * \brief It contains generic Status view components definitions and Squish driver API. # * \brief It contains generic Status view components definitions and Squish driver API.
# *****************************************************************************/ # *****************************************************************************/
from enum import Enum from enum import Enum
import sys
# IMPORTANT: It is necessary to import manually the Squish drivers module by module. # IMPORTANT: It is necessary to import manually the Squish drivers module by module.
# More info in: https://kb.froglogic.com/display/KB/Article+-+Using+Squish+functions+in+your+own+Python+modules+or+packages # More info in: https://kb.froglogic.com/display/KB/Article+-+Using+Squish+functions+in+your+own+Python+modules+or+packages
@ -44,13 +45,23 @@ def is_loaded(objName: str):
except LookupError: except LookupError:
return False, obj return False, obj
def is_Visible(objName: str): # It tries to find if the object with given objectName is currently displayed (visible and enabled):
# It returns True in case it is found. Otherwise, false.
def is_found(objName: str):
try: try:
squish.findObject(getattr(names, objName)) squish.findObject(getattr(names, objName))
return True return True
except LookupError: except LookupError:
return False return False
# It waits for the object with given objectName to appear in the UI (visible and enabled):
# It returns True in case it appears without exceeding a specific timeout. Otherwise, false.
def is_displayed(objName: str):
try:
squish.waitForObject(getattr(names, objName))
return True
except LookupError:
return False
# It checks if the given object is visible and enabled. # It checks if the given object is visible and enabled.
def is_visible_and_enabled(obj): def is_visible_and_enabled(obj):
@ -151,3 +162,49 @@ def wait_for_object_and_type(objName: str, text: str):
return True return True
except LookupError: except LookupError:
return False return False
# Clicking link in label / textedit
def click_link(objName: str, link: str):
point = _find_link(getattr(names, objName), link)
if point[0] != -1 and point[1] != -1:
squish.mouseClick(getattr(names, objName), point[0], point[1], 0, squish.Qt.LeftButton)
# Global properties for getting link / hovered handler management:
_expected_link = None
_link_found = False
def _handle_link_hovered(obj, link):
global _link_found
if link == _expected_link:
_link_found = True
# It registers to hovered handler and moves mouse around a specific object.
# Return: If handler is executed, link has been found and the position of the link is returned. Otherwise, it returns position [-1, -1]
def _find_link(objName: str, link: str):
global _expected_link
global _link_found
_expected_link = link
_link_found = False
obj = squish.waitForObject(objName)
# Inject desired function into main module:
sys.modules['__main__']._handle_link_hovered = _handle_link_hovered
squish.installSignalHandler(obj, "linkHovered(QString)", "_handle_link_hovered")
# Start moving the cursor:
squish.mouseMove(obj, int(obj.x), int(obj.y))
end_x = obj.x + obj.width
end_y = obj.y + obj.height
y = int(obj.y)
while y < end_y:
x = int(obj.x)
while x < end_x:
squish.mouseMove(obj, x, y)
if _link_found:
squish.uninstallSignalHandler(obj, "linkHovered(QString)", "_handle_link_hovered")
return [x - obj.x, y - obj.y]
x += 10
y += 10
squish.uninstallSignalHandler(obj, "linkHovered(QString)", "_handle_link_hovered")
return [-1, -1]

View File

@ -88,3 +88,9 @@ def verify_the_app_is_closed(pid: int):
def verify_equals(val1, val2): def verify_equals(val1, val2):
test.compare(val1, val2, "1st value [" + str(val1) + ("] equal to " if val1 == val2 else "] NOT equal to ") + "2nd value [" + str(val2) + "]") test.compare(val1, val2, "1st value [" + str(val1) + ("] equal to " if val1 == val2 else "] NOT equal to ") + "2nd value [" + str(val2) + "]")
def verify_failure(errorMsg: str):
test.fail(errorMsg)
def log(text: str):
test.log(text)

View File

@ -53,7 +53,7 @@ class SettingsScreen:
click_obj_by_name(SidebarComponents.WALLET_ITEM.value) click_obj_by_name(SidebarComponents.WALLET_ITEM.value)
def activate_open_wallet_settings(self): def activate_open_wallet_settings(self):
if not (is_Visible(SidebarComponents.WALLET_ITEM.value)) : if not (is_found(SidebarComponents.WALLET_ITEM.value)) :
click_obj_by_name(SidebarComponents.ADVANCED_OPTION.value) click_obj_by_name(SidebarComponents.ADVANCED_OPTION.value)
click_obj_by_name(AdvancedOptionScreen.ACTIVATE_OR_DEACTIVATE_WALLET.value) click_obj_by_name(AdvancedOptionScreen.ACTIVATE_OR_DEACTIVATE_WALLET.value)
click_obj_by_name(AdvancedOptionScreen.I_UNDERSTAND_POP_UP.value) click_obj_by_name(AdvancedOptionScreen.I_UNDERSTAND_POP_UP.value)
@ -62,7 +62,7 @@ class SettingsScreen:
self.open_wallet_settings() self.open_wallet_settings()
def activate_open_wallet_section(self): def activate_open_wallet_section(self):
if not (is_Visible(SidebarComponents.WALLET_ITEM.value)): if not (is_found(SidebarComponents.WALLET_ITEM.value)):
click_obj_by_name(SidebarComponents.ADVANCED_OPTION.value) click_obj_by_name(SidebarComponents.ADVANCED_OPTION.value)
click_obj_by_name(AdvancedOptionScreen.ACTIVATE_OR_DEACTIVATE_WALLET.value) click_obj_by_name(AdvancedOptionScreen.ACTIVATE_OR_DEACTIVATE_WALLET.value)
click_obj_by_name(AdvancedOptionScreen.I_UNDERSTAND_POP_UP.value) click_obj_by_name(AdvancedOptionScreen.I_UNDERSTAND_POP_UP.value)

View File

@ -8,6 +8,7 @@
# * \brief Chat Screen. # * \brief Chat Screen.
# *****************************************************************************/ # *****************************************************************************/
import re
from enum import Enum from enum import Enum
from drivers.SquishDriver import * from drivers.SquishDriver import *
@ -15,6 +16,8 @@ from drivers.SquishDriverVerification import *
from drivers.SDKeyboardCommands import * from drivers.SDKeyboardCommands import *
from common.Common import * from common.Common import *
_MENTION_SYMBOL = "@"
_LINK_HREF_REGEX = '<a href="(.+?)">'
class ChatComponents(Enum): class ChatComponents(Enum):
MESSAGE_INPUT = "chatView_messageInput" MESSAGE_INPUT = "chatView_messageInput"
@ -25,6 +28,10 @@ class ChatComponents(Enum):
REPLY_TO_MESSAGE_BUTTON = "chatView_replyToMessageButton" REPLY_TO_MESSAGE_BUTTON = "chatView_replyToMessageButton"
DELETE_MESSAGE_BUTTON = "chatView_DeleteMessageButton" DELETE_MESSAGE_BUTTON = "chatView_DeleteMessageButton"
CONFIRM_DELETE_MESSAGE_BUTTON = "chatButtonsPanelConfirmDeleteMessageButton_StatusButton" CONFIRM_DELETE_MESSAGE_BUTTON = "chatButtonsPanelConfirmDeleteMessageButton_StatusButton"
SUGGESTIONS_BOX = "chatView_SuggestionBoxPanel"
SUGGESTIONS_LIST = "chatView_suggestion_ListView"
MENTION_PROFILE_VIEW = "chatView_userMentioned_ProfileView"
class ChatMessagesHistory(Enum): class ChatMessagesHistory(Enum):
CHAT_CREATED_TEXT = 1 CHAT_CREATED_TEXT = 1
@ -35,6 +42,9 @@ class StatusChatScreen:
def __init__(self): def __init__(self):
verify_screen(ChatComponents.MESSAGE_INPUT.value) verify_screen(ChatComponents.MESSAGE_INPUT.value)
verify_screen(ChatComponents.TOOLBAR_INFO_BUTTON.value) verify_screen(ChatComponents.TOOLBAR_INFO_BUTTON.value)
def chat_loaded(self):
verify(is_displayed(ChatComponents.LAST_MESSAGE_TEXT.value), "Checking chat is loaded by looking if last message is displayed.")
# Screen actions region: # Screen actions region:
def send_message(self, message: str): def send_message(self, message: str):
@ -53,6 +63,30 @@ class StatusChatScreen:
test.passes("Success: No message was found") test.passes("Success: No message was found")
return return
verify_text_does_not_contain(str(last_message_obj.text), str(message)) verify_text_does_not_contain(str(last_message_obj.text), str(message))
# This method expects to have just one mention / link in the last chat message
def verify_last_message_sent_contains_mention(self, displayName: str, message: str):
[loaded, last_message_obj] = is_loaded_visible_and_enabled(ChatComponents.LAST_MESSAGE_TEXT.value)
if loaded:
# Verifying mention
verify_text_contains(str(last_message_obj.text), displayName)
# Verifying message
verify_text_contains(str(last_message_obj.text), message)
# Get link value from chat text:
try:
href_info = re.search(_LINK_HREF_REGEX, str(last_message_obj.text)).group(1)
except AttributeError:
# <a href=, "> not found in the original string
verify_failure("Mention link not found in last chat message.")
click_link(ChatComponents.LAST_MESSAGE_TEXT.value, href_info)
verify(is_found(ChatComponents.MENTION_PROFILE_VIEW.value), "Checking user mentioned profile popup is open.")
else:
verify_failure("No messages found in chat.")
def verify_chat_title(self, title: str): def verify_chat_title(self, title: str):
info_btn = get_obj(ChatComponents.TOOLBAR_INFO_BUTTON.value) info_btn = get_obj(ChatComponents.TOOLBAR_INFO_BUTTON.value)
@ -113,7 +147,36 @@ class StatusChatScreen:
def cannot_delete_last_message(self): def cannot_delete_last_message(self):
[loaded, last_message_obj] = is_loaded_visible_and_enabled(ChatComponents.LAST_MESSAGE_TEXT.value) [loaded, last_message_obj] = is_loaded_visible_and_enabled(ChatComponents.LAST_MESSAGE_TEXT.value)
if not loaded: if not loaded:
test.fail("No message found") verify_failure("No message found")
return return
hover_obj(last_message_obj) hover_obj(last_message_obj)
object_not_enabled(ChatComponents.DELETE_MESSAGE_BUTTON.value) object_not_enabled(ChatComponents.DELETE_MESSAGE_BUTTON.value)
def send_message_with_mention(self, displayName: str, message: str):
self.do_mention(displayName)
self.send_message(message)
def cannot_do_mention(self, displayName: str):
self.chat_loaded()
type(ChatComponents.MESSAGE_INPUT.value, _MENTION_SYMBOL + displayName)
displayed = is_displayed(ChatComponents.SUGGESTIONS_BOX.value)
verify(displayed == False , "Checking suggestion box is not displayed when trying to mention a non existing user.")
def do_mention(self, displayName: str):
self.chat_loaded()
type(ChatComponents.MESSAGE_INPUT.value, _MENTION_SYMBOL + displayName)
displayed = is_displayed(ChatComponents.SUGGESTIONS_BOX.value)
verify(displayed, "Checking suggestion box displayed when trying to do a mention")
[loaded, suggestions_list] = is_loaded_visible_and_enabled(ChatComponents.SUGGESTIONS_LIST.value)
verify(suggestions_list.count > 0, "Checking if suggestion list is greater than 0")
found = False
if loaded:
for index in range(suggestions_list.count):
user_mention = suggestions_list.itemAtIndex(index)
if user_mention.objectName == displayName:
found = True
click_obj(user_mention)
break
verify(found, "Checking if the following display name is in the mention's list: " + displayName)

View File

@ -18,6 +18,9 @@ chatView_replyToMessageButton = {"container": chatView_log, "objectName": "reply
chatView_DeleteMessageButton = {"container": chatView_log, "objectName": "chatDeleteMessageButton", "type": "StatusFlatRoundButton"} chatView_DeleteMessageButton = {"container": chatView_log, "objectName": "chatDeleteMessageButton", "type": "StatusFlatRoundButton"}
chatButtonsPanelConfirmDeleteMessageButton_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatButtonsPanelConfirmDeleteMessageButton", "type": "StatusButton"} chatButtonsPanelConfirmDeleteMessageButton_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatButtonsPanelConfirmDeleteMessageButton", "type": "StatusButton"}
mark_as_Read_StatusMenuItemDelegate = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatMarkAsReadMenuItem", "type": "StatusMenuItemDelegate", "visible": True} mark_as_Read_StatusMenuItemDelegate = {"container": statusDesktop_mainWindow_overlay, "objectName": "chatMarkAsReadMenuItem", "type": "StatusMenuItemDelegate", "visible": True}
chatView_SuggestionBoxPanel ={"container": statusDesktop_mainWindow, "objectName": "suggestionsBox", "type": "SuggestionBoxPanel"}
chatView_suggestion_ListView ={"container": chatView_SuggestionBoxPanel, "objectName": "suggestionBoxList", "type": "StatusListView"}
chatView_userMentioned_ProfileView ={"container": statusDesktop_mainWindow_overlay, "objectName": "profileView", "type": "ProfileView"}
# Join chat popup: # Join chat popup:
startChat_Btn = {"container": statusDesktop_mainWindow_overlay, "objectName": "startChatButton", "type": "StatusButton"} startChat_Btn = {"container": statusDesktop_mainWindow_overlay, "objectName": "startChatButton", "type": "StatusButton"}

View File

@ -13,6 +13,7 @@ _statusCreateChatView = StatusCreateChatScreen()
@When("user joins chat room |any|") @When("user joins chat room |any|")
def step(context, room): def step(context, room):
_statusMain.join_chat_room(room) _statusMain.join_chat_room(room)
_statusChat.chat_loaded()
@When("the user creates a group chat adding users") @When("the user creates a group chat adding users")
def step(context): def step(context):
@ -22,6 +23,10 @@ def step(context):
@When("the user clicks on |any| chat") @When("the user clicks on |any| chat")
def step(context, chatName): def step(context, chatName):
_statusMain.open_chat(chatName) _statusMain.open_chat(chatName)
@When("the user inputs a mention to |any| with message |any|")
def step(context,displayName,message):
_statusChat.send_message_with_mention(displayName, message)
@Then("user is able to send chat message") @Then("user is able to send chat message")
def step(context): def step(context):
@ -85,3 +90,10 @@ def step(context, message):
def step(context): def step(context):
_statusChat.verify_last_message_sent_is_not(context.userData["randomMessage"]) _statusChat.verify_last_message_sent_is_not(context.userData["randomMessage"])
@Then("the user cannot input a mention to a not existing user |any|")
def step(context, displayName):
_statusChat.cannot_do_mention(displayName)
@Then("the |any| mention with message |any| have been sent")
def step(context,displayName,message):
_statusChat.verify_last_message_sent_contains_mention(displayName, message)

View File

@ -4,7 +4,8 @@
# https://cucumber.io/docs/gherkin/reference/ # https://cucumber.io/docs/gherkin/reference/
Feature: Status Desktop Chat Feature: Status Desktop Chat
As a user I want to join a room and chat. # TODO The complete feature / all scenarios have a chance to fail since they rely on the mailserver (at least, to verify a chat is loaded, something in the history needs to be displayed).
As a user I want to join a room and chat and do basic interactions.
The following scenarios cover basic chat flows. The following scenarios cover basic chat flows.
@ -13,7 +14,7 @@ Feature: Status Desktop Chat
When user signs up with username tester123 and password TesTEr16843/!@00 When user signs up with username tester123 and password TesTEr16843/!@00
Then the user lands on the signed in app Then the user lands on the signed in app
Scenario: User joins a room and chats Scenario: User joins a public room and chats
When user joins chat room test When user joins chat room test
Then user is able to send chat message Then user is able to send chat message
| message | | message |
@ -54,3 +55,19 @@ Feature: Status Desktop Chat
# Scenario: User cannot delete another user's message # Scenario: User cannot delete another user's message
# When user joins chat room test # When user joins chat room test
# Then the user cannot delete the last message # Then the user cannot delete the last message
# Scenario Outline: The user can do a mention
# When user joins chat room test
# And the user inputs a mention to <displayName> with message <message>
# Then the <displayName> mention with message <message> have been sent
# Examples:
# | displayName | message |
# | tester123 | testing mention |
# Scenario Outline: The user can not do a mention to not existing users
# When user joins chat room test
# Then the user cannot input a mention to a not existing user <displayName>
# Examples:
# | displayName |
# | notExistingAccount |
# | asdfgNoNo |

View File

@ -119,6 +119,7 @@ Rectangle {
StatusListView { StatusListView {
id: listView id: listView
objectName: "suggestionBoxList"
model: mentionsListDelegate model: mentionsListDelegate
keyNavigationEnabled: true keyNavigationEnabled: true
anchors.fill: parent anchors.fill: parent
@ -194,6 +195,7 @@ Rectangle {
delegate: Rectangle { delegate: Rectangle {
id: itemDelegate id: itemDelegate
objectName: model.name
color: ListView.isCurrentItem ? Style.current.backgroundHover : Style.current.transparent color: ListView.isCurrentItem ? Style.current.backgroundHover : Style.current.transparent
border.width: 0 border.width: 0
width: parent.width width: parent.width

View File

@ -689,6 +689,7 @@ Rectangle {
SuggestionBoxPanel { SuggestionBoxPanel {
id: suggestionsBox id: suggestionsBox
objectName: "suggestionsBox"
model: control.usersStore ? control.usersStore.usersModel : [] model: control.usersStore ? control.usersStore.usersModel : []
x : messageInput.x x : messageInput.x
y: -height - Style.current.smallPadding y: -height - Style.current.smallPadding

View File

@ -82,6 +82,7 @@ Rectangle {
signal contactRemoved(publicKey: string) signal contactRemoved(publicKey: string)
signal nicknameEdited(publicKey: string) signal nicknameEdited(publicKey: string)
objectName: "profileView"
implicitWidth: modalContent.implicitWidth implicitWidth: modalContent.implicitWidth
implicitHeight: modalContent.implicitHeight implicitHeight: modalContent.implicitHeight