зеркало из https://github.com/mozilla/kitsune.git
Merge pull request #6344 from emilghittasv/playwright-expand-group-coverage
Playwright expand coverage to group pages
This commit is contained in:
Коммит
a42e1eacb0
|
@ -38,6 +38,7 @@ on:
|
|||
- kbDashboard
|
||||
- exploreByTopics
|
||||
- searchTests
|
||||
- userGroupsTests
|
||||
|
||||
env:
|
||||
TEST_ACCOUNT_12: ${{secrets.AUTOMATION_TEST_ACCOUNT_12}}
|
||||
|
@ -103,7 +104,7 @@ jobs:
|
|||
if: success() || failure() && steps.create-sessions.outcome == 'success'
|
||||
run: |
|
||||
declare dispatch_test_suite="${{inputs.TestSuite}}"
|
||||
declare all_test_suites=("homePageTests" "topNavbarTests" "footerSectionTests" "contributePagesTests" "messagingSystem" "messagingSystemCleanup" "userContributionTests" "userProfile" "userSettings" "editUserProfileTests" "userQuestions" "contactSupportPage" "productSolutionsPage" "productSupportPage" "productTopicsPage" "aaqPage" "postedQuestions" "kbProductsPage" "kbArticleCreationAndAccess" "beforeThreadTests" "articleThreads" "afterThreadTests" "kbArticleShowHistory" "recentRevisionsDashboard" "kbDashboard" "kbRestrictedVisibility" "kbArticleTranslation" "exploreByTopics", "searchTests")
|
||||
declare all_test_suites=("homePageTests" "topNavbarTests" "footerSectionTests" "contributePagesTests" "messagingSystem" "messagingSystemCleanup" "userContributionTests" "userProfile" "userSettings" "editUserProfileTests" "userQuestions" "contactSupportPage" "productSolutionsPage" "productSupportPage" "productTopicsPage" "aaqPage" "postedQuestions" "kbProductsPage" "kbArticleCreationAndAccess" "beforeThreadTests" "articleThreads" "afterThreadTests" "kbArticleShowHistory" "recentRevisionsDashboard" "kbDashboard" "kbRestrictedVisibility" "kbArticleTranslation" "exploreByTopics", "searchTests", "userGroupsTests")
|
||||
if [ "$dispatch_test_suite" == "All" ] || [ "${{ github.event_name}}" == "schedule" ] ; then
|
||||
for test in "${all_test_suites[@]}"; do
|
||||
if ! poetry run pytest -m ${test} --numprocesses 4 --browser ${{ env.BROWSER }} --reruns 1; then
|
||||
|
|
|
@ -5,10 +5,12 @@ import time
|
|||
import re
|
||||
import json
|
||||
import random
|
||||
from PIL import Image
|
||||
from PIL import ImageChops
|
||||
from typing import Any, Union
|
||||
from datetime import datetime
|
||||
from nltk import SnowballStemmer, WordNetLemmatizer
|
||||
from playwright.sync_api import Page
|
||||
from playwright.sync_api import Page, Locator
|
||||
from playwright_tests.messages.homepage_messages import HomepageMessages
|
||||
from requests.exceptions import HTTPError
|
||||
from playwright_tests.pages.top_navbar import TopNavbar
|
||||
|
@ -192,6 +194,42 @@ class Utilities:
|
|||
if response.status >= 400:
|
||||
self.refresh_page()
|
||||
|
||||
def upload_file(self, element: str, path_to_file: str):
|
||||
"""This helper function uploads the test-image.png file to a given file element chooser.
|
||||
|
||||
Args:
|
||||
element (str): The element file chooser locator's xpath.
|
||||
path_to_file (str): The path to the file to be uploaded.
|
||||
"""
|
||||
with self.page.expect_file_chooser() as file_chooser:
|
||||
self.page.locator(element).click()
|
||||
file_chooser_value = file_chooser.value
|
||||
file_chooser_value.set_files(os.path.abspath(path_to_file))
|
||||
|
||||
def screenshot_the_locator(self, locator: Locator, path_to_save: str):
|
||||
"""
|
||||
This helper function takes a screenshot of a given locator.
|
||||
|
||||
Args:
|
||||
locator (Locator): The locator of the targeted element.
|
||||
path_to_save (str): The path where to save the screenshot.
|
||||
"""
|
||||
locator.screenshot(path=path_to_save)
|
||||
|
||||
def are_images_different(self, image1_path: str, image2_path: str) -> tuple:
|
||||
"""
|
||||
This helper function compares two images and returns the bounding box of the difference.
|
||||
If there is no difference this helper function will return None.
|
||||
|
||||
Args:
|
||||
image1_path (str): The path of the first image
|
||||
image2_path (str): The path of the second image
|
||||
"""
|
||||
first_image = Image.open(image1_path).convert('RGB')
|
||||
second_image = Image.open(image2_path).convert('RGB')
|
||||
|
||||
return ImageChops.difference(first_image, second_image).getbbox()
|
||||
|
||||
def set_extra_http_headers(self, headers):
|
||||
"""
|
||||
This helper function sets some extra headers to the request.
|
||||
|
|
|
@ -1,3 +1,62 @@
|
|||
class UserGroupMessages:
|
||||
def get_user_added_success_message(self, username: str) -> str:
|
||||
DELETE_AVATAR_PAGE_INFO = ("You are about to permanently delete the avatar. "
|
||||
"This cannot be undone! You can always upload another avatar to "
|
||||
"replace the current one.")
|
||||
GROUP_INFORMATION_UPDATE_NOTIFICATION = "Group information updated successfully!"
|
||||
|
||||
def get_user_added_success_message(username: str) -> str:
|
||||
"""Get the user added success message.
|
||||
|
||||
Args:
|
||||
username (str): The username of the user added to the group
|
||||
"""
|
||||
return f"{username} added to the group successfully!"
|
||||
|
||||
def get_user_removed_success_message(username: str) -> str:
|
||||
"""Get the user removed success message.
|
||||
|
||||
Args:
|
||||
username (str): The username of the user removed from the group
|
||||
"""
|
||||
return f"{username} removed from the group successfully!"
|
||||
|
||||
def get_change_avatar_page_header(user_group: str) -> str:
|
||||
"""Get the change avatar page header.
|
||||
|
||||
Args:
|
||||
user_group (str): The group name.
|
||||
"""
|
||||
return f"Change {user_group} group avatar"
|
||||
|
||||
def get_change_uploaded_avatar_page_header(user_group: str) -> str:
|
||||
"""Get the change uploaded avatar page header.
|
||||
|
||||
Args:
|
||||
user_group (str): The group name.
|
||||
"""
|
||||
return f"Change {user_group} group avatar"
|
||||
|
||||
def get_delete_uploaded_avatar_page_header(user_group: str) -> str:
|
||||
"""Get the delete uploaded avatar page header.
|
||||
|
||||
Args:
|
||||
user_group (str): The group name.
|
||||
"""
|
||||
return f"Are you sure you want to delete the {user_group} group avatar?"
|
||||
|
||||
def get_delete_user_header(username: str, group: str) -> str:
|
||||
"""Get the delete user page header.
|
||||
|
||||
Args:
|
||||
username (str): The username of the user to delete.
|
||||
group (str): The group name.
|
||||
"""
|
||||
return f"Are you sure you want to remove {username} from {group}?"
|
||||
|
||||
def get_edit_profile_information_page_header(group_name: str) -> str:
|
||||
"""Get the edit profile information page header.
|
||||
|
||||
Args:
|
||||
group_name (str): The group name.
|
||||
"""
|
||||
return f"Edit {group_name} profile information"
|
||||
|
|
|
@ -3,44 +3,272 @@ from playwright.sync_api import Page, Locator
|
|||
|
||||
|
||||
class GroupsPage(BasePage):
|
||||
__private_message_group_members_button = "//section[@id='main-area']/p[@class='pm']/a"
|
||||
__user_added_notification = "//ul[@class='user-messages']//p"
|
||||
__edit_group_members_option = "//div[@id='group-members']/a"
|
||||
__add_group_member_field = "//div[@id='group-members']//input[@id='token-input-id_users']"
|
||||
__add_member_button = "//div[@id='group-members']//input[@value='Add Member']"
|
||||
__remove_user_from_group_confirmation_button = "//input[@value='Remove member']"
|
||||
GENERAL_GROUPS_PAGE_LOCATORS = {
|
||||
"add_group_profile_button": "//a[normalize-space(text())= 'Add group profile']"
|
||||
}
|
||||
|
||||
GROUP_PAGE_LOCATORS = {
|
||||
"group_avatar_image": "//section[@id='avatar-area']/img",
|
||||
"change_uploaded_group_image_option": "//section[@id='avatar-area']/p/a[normalize-space("
|
||||
"text())='Change']",
|
||||
"delete_uploaded_group_image_option": "//section[@id='avatar-area']/p/a["
|
||||
"@title='Delete avatar']",
|
||||
"change_avatar_button": "//section[@id='avatar-area']//p/a[@title='Change avatar']",
|
||||
"edit_in_admin_button": "//section[@id='main-area']/a[text()='Edit in admin']",
|
||||
"group_profile_information": "//div[@id='doc-content']/p",
|
||||
"edit_group_profile_page_header": "//article[@id='group-profile']/h1",
|
||||
"edit_group_profile_button": "//section[@id='main-area']/a[text()='Edit group profile']",
|
||||
"edit_group_profile_textarea": "//textarea[@id='id_information']",
|
||||
"save_group_profile_edit_button": "//article[@id='group-profile']//input[@value='Save']",
|
||||
"edit_group_leaders_button": "//div[@id='group-leaders']/a[text()='Edit group leaders']",
|
||||
"private_message_group_members_button": "//section[@id='main-area']/p[@class='pm']/a",
|
||||
"user_notification": "//ul[@class='user-messages']//p",
|
||||
"edit_group_members_option": "//div[@id='group-members']/a",
|
||||
"add_group_member_field": "//div[@id='group-members']//input[@id='token-input-id_users']",
|
||||
"add_member_button": "//div[@id='group-members']//input[@value='Add Member']",
|
||||
"remove_user_from_group_confirmation_button": "//input[@value='Remove member']",
|
||||
"group_members_list": "//div[@id='group-members']//div[@class='info']/a"
|
||||
}
|
||||
|
||||
CHANGE_AVATAR_PAGE_LOCATORS = {
|
||||
"upload_avatar_page_header": "//article[@id='change-avatar']/h1",
|
||||
"upload_avatar_image_preview": "//input[@id='id_avatar']/preceding-sibling::img",
|
||||
"upload_avatar_browse_button": "//input[@id='id_avatar']",
|
||||
"upload_avatar_button": "//input[@type='submit']",
|
||||
"upload_avatar_cancel_option": "//a[normalize-space(text())='Cancel']"
|
||||
}
|
||||
|
||||
DELETE_AVATAR_PAGE_LOCATORS = {
|
||||
"delete_uploaded_avatar_page_header": "//article[@id='avatar-delete']/h1",
|
||||
"delete_uploaded_avatar_image_preview": "//div[@id='avatar-preview']/img",
|
||||
"delete_uploaded_avatar_page_info": "//form/p",
|
||||
"delete_uploaded_avatar_button": "//input[@value='Delete avatar']",
|
||||
"delete_uploaded_avatar_cancel_button": "//a[normalize-space(text())='Cancel']"
|
||||
}
|
||||
|
||||
REMOVE_USER_PAGE_LOCATORS = {
|
||||
"remove_user_page_header": "//article[@id='remove-member']/h1",
|
||||
"remove_member_button": "//input[@value='Remove member']",
|
||||
"remove_member_cancel_button": "//div[@class='form-actions']/a[text()='Cancel']"
|
||||
}
|
||||
|
||||
def __init__(self, page: Page):
|
||||
super().__init__(page)
|
||||
|
||||
# Add Group member
|
||||
def get_user_added_successfully_message(self) -> str:
|
||||
return self._get_text_of_element(self.__user_added_notification)
|
||||
|
||||
def get_pm_group_members_button(self) -> Locator:
|
||||
return self._get_element_locator(self.__private_message_group_members_button)
|
||||
# Actions against the all groups page.
|
||||
def is_add_group_profile_button_visible(self) -> bool:
|
||||
"""Check if the add group profile button is visible"""
|
||||
return self._is_element_visible(
|
||||
self.GENERAL_GROUPS_PAGE_LOCATORS["add_group_profile_button"])
|
||||
|
||||
def click_on_a_particular_group(self, group_name):
|
||||
"""Click on a particular group
|
||||
|
||||
Args:
|
||||
group_name (str): The name of the group to click on
|
||||
"""
|
||||
self._click(f"//a[text()='{group_name}']")
|
||||
|
||||
# Actions against the group page.
|
||||
def get_all_members_name(self) -> list[str]:
|
||||
"""Get the names of all the members in the group"""
|
||||
return self._get_text_of_elements(self.GROUP_PAGE_LOCATORS["group_members_list"])
|
||||
|
||||
def get_group_avatar_locator(self) -> Locator:
|
||||
"""Get the locator of the group avatar image"""
|
||||
return self._get_element_locator(self.GROUP_PAGE_LOCATORS["group_avatar_image"])
|
||||
|
||||
def is_change_avatar_button_visible(self) -> bool:
|
||||
"""Check if the change avatar button is visible"""
|
||||
return self._is_element_visible(self.GROUP_PAGE_LOCATORS["change_avatar_button"])
|
||||
|
||||
def is_edit_in_admin_button_visible(self) -> bool:
|
||||
"""Check if the edit in admin button is visible"""
|
||||
return self._is_element_visible(self.GROUP_PAGE_LOCATORS["edit_in_admin_button"])
|
||||
|
||||
def get_edit_group_profile_page_header(self) -> str:
|
||||
"""Get the text of the edit group profile page header"""
|
||||
return self._get_text_of_element(self.GROUP_PAGE_LOCATORS
|
||||
["edit_group_profile_page_header"])
|
||||
|
||||
def is_edit_group_profile_button_visible(self) -> bool:
|
||||
"""Check if the edit group profile button is visible"""
|
||||
return self._is_element_visible(self.GROUP_PAGE_LOCATORS["edit_group_profile_button"])
|
||||
|
||||
def click_on_edit_group_profile_button(self):
|
||||
"""Click on the edit group profile button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["edit_group_profile_button"])
|
||||
|
||||
def get_edit_group_profile_textarea_content(self) -> str:
|
||||
"""Get the content of the edit group profile textarea"""
|
||||
return self._get_element_input_value(self.GROUP_PAGE_LOCATORS
|
||||
["edit_group_profile_textarea"])
|
||||
|
||||
def type_into_edit_group_profile_textarea(self, text: str):
|
||||
"""Type into the edit group profile textarea
|
||||
|
||||
Args:
|
||||
text (str): The text to type into the edit group profile textarea
|
||||
"""
|
||||
self._clear_field(self.GROUP_PAGE_LOCATORS["edit_group_profile_textarea"])
|
||||
self._fill(self.GROUP_PAGE_LOCATORS["edit_group_profile_textarea"], text)
|
||||
|
||||
def get_profile_information(self) -> str:
|
||||
"""Get the profile information"""
|
||||
return self._get_text_of_element(self.GROUP_PAGE_LOCATORS["group_profile_information"])
|
||||
|
||||
def click_on_edit_group_profile_save_button(self):
|
||||
"""Click on the save group profile edit button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["save_group_profile_edit_button"])
|
||||
|
||||
def is_edit_group_leaders_button_visible(self) -> bool:
|
||||
"""Check if the edit group leaders button is visible"""
|
||||
return self._is_element_visible(self.GROUP_PAGE_LOCATORS["edit_group_leaders_button"])
|
||||
|
||||
def is_edit_group_members_option_visible(self) -> bool:
|
||||
"""Check if the edit group members option is visible"""
|
||||
return self._is_element_visible(self.GROUP_PAGE_LOCATORS["edit_group_members_option"])
|
||||
|
||||
def click_on_pm_group_members_button(self):
|
||||
self._click(self.__private_message_group_members_button)
|
||||
"""Click on the PM group members button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["private_message_group_members_button"])
|
||||
|
||||
def click_on_pm_for_a_particular_user(self, username: str):
|
||||
"""Click on the PM button for a particular user
|
||||
|
||||
Args:
|
||||
username (str): The username of the user to click on the PM button for
|
||||
"""
|
||||
self._click(
|
||||
f"//div[@class='info']/a[normalize-space(text())='{username}']/following-sibling::p/a")
|
||||
|
||||
# Add Group member
|
||||
def get_group_update_notification(self) -> str:
|
||||
"""Get the text of the user added successfully message"""
|
||||
return self._get_text_of_element(self.GROUP_PAGE_LOCATORS["user_notification"])
|
||||
|
||||
def get_pm_group_members_button(self) -> Locator:
|
||||
"""Get the locator of the PM group members button"""
|
||||
return self._get_element_locator(self.GROUP_PAGE_LOCATORS
|
||||
["private_message_group_members_button"])
|
||||
|
||||
def click_on_change_uploaded_avatar_button(self):
|
||||
"""Click on the change uploaded avatar button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["change_uploaded_group_image_option"],
|
||||
expected_locator=self.CHANGE_AVATAR_PAGE_LOCATORS["upload_avatar_page_header"])
|
||||
|
||||
def click_on_delete_uploaded_avatar_button(self):
|
||||
"""Click on the delete uploaded avatar button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["delete_uploaded_group_image_option"])
|
||||
|
||||
def click_on_change_avatar_button(self):
|
||||
"""Click on the change avatar button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["change_avatar_button"],
|
||||
expected_locator=self.CHANGE_AVATAR_PAGE_LOCATORS["upload_avatar_page_header"])
|
||||
|
||||
def click_on_edit_group_members(self):
|
||||
self._click(self.__edit_group_members_option)
|
||||
"""Click on the edit group members option"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["edit_group_members_option"])
|
||||
|
||||
# Actions against the change avatar page.
|
||||
def get_change_avatar_image_preview_locator(self) -> Locator:
|
||||
"""Get the locator of the change avatar image preview"""
|
||||
return self._get_element_locator(self.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
["upload_avatar_image_preview"])
|
||||
|
||||
def get_upload_avatar_page_header(self) -> str:
|
||||
"""Get the text of the upload avatar page header"""
|
||||
return self._get_text_of_element(self.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
["upload_avatar_page_header"])
|
||||
|
||||
def click_on_upload_avatar_button(self, expected_url=None):
|
||||
"""Click on the upload avatar button"""
|
||||
self._click(self.CHANGE_AVATAR_PAGE_LOCATORS["upload_avatar_button"],
|
||||
expected_url=expected_url)
|
||||
|
||||
def click_on_upload_avatar_cancel_button(self):
|
||||
"""Click on the upload avatar cancel button"""
|
||||
self._click(self.CHANGE_AVATAR_PAGE_LOCATORS["upload_avatar_cancel_option"])
|
||||
|
||||
# Actions against the delete avatar page.
|
||||
def get_delete_avatar_image_preview_locator(self) -> Locator:
|
||||
"""Get the locator of the delete avatar image preview"""
|
||||
return self._get_element_locator(self.DELETE_AVATAR_PAGE_LOCATORS
|
||||
["delete_uploaded_avatar_image_preview"])
|
||||
|
||||
def click_on_cancel_delete_avatar_button(self):
|
||||
"""Click on the cancel delete avatar button"""
|
||||
self._click(self.DELETE_AVATAR_PAGE_LOCATORS["delete_uploaded_avatar_cancel_button"])
|
||||
|
||||
def get_delete_avatar_page_header(self) -> str:
|
||||
"""Get the text of the delete avatar page header"""
|
||||
return self._get_text_of_element(self.DELETE_AVATAR_PAGE_LOCATORS
|
||||
["delete_uploaded_avatar_page_header"])
|
||||
|
||||
def is_image_preview_visible(self) -> bool:
|
||||
"""Check if the image preview is visible"""
|
||||
return self._is_element_visible(self.DELETE_AVATAR_PAGE_LOCATORS
|
||||
["delete_uploaded_avatar_image_preview"])
|
||||
|
||||
def get_delete_avatar_page_info(self) -> str:
|
||||
"""Get the text of the delete avatar page info"""
|
||||
return self._get_text_of_element(self.DELETE_AVATAR_PAGE_LOCATORS
|
||||
["delete_uploaded_avatar_page_info"])
|
||||
|
||||
def click_on_delete_avatar_button(self):
|
||||
"""Click on the delete avatar button"""
|
||||
self._click(self.DELETE_AVATAR_PAGE_LOCATORS["delete_uploaded_avatar_button"])
|
||||
|
||||
# Actions against the removal or user addition
|
||||
def click_on_remove_a_user_from_group_button(self, username: str):
|
||||
self._click(f"//div[@class='info']/a[text()='{username}']/../..//a"
|
||||
f"[@title='Remove user from group']")
|
||||
"""Click on the remove a user from group button
|
||||
|
||||
Args:
|
||||
username (str): The username of the user to remove from the group
|
||||
"""
|
||||
self._click(f"//div[@class='info']/a[text()='{username}']/../..//a[@title="
|
||||
f"'Remove user from group']")
|
||||
|
||||
def click_on_remove_member_confirmation_button(self):
|
||||
self._click(self.__remove_user_from_group_confirmation_button)
|
||||
"""Click on the remove member confirmation button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["remove_user_from_group_confirmation_button"])
|
||||
|
||||
def type_into_add_member_field(self, text: str):
|
||||
self._type(self.__add_group_member_field, text, delay=0)
|
||||
"""Type into the add member field
|
||||
|
||||
Args:
|
||||
text (str): The text to type into the add member field
|
||||
"""
|
||||
self._type(self.GROUP_PAGE_LOCATORS["add_group_member_field"], text, delay=0)
|
||||
|
||||
def group_click_on_a_searched_username(self, username: str):
|
||||
"""Click on a searched username
|
||||
|
||||
Args:
|
||||
username (str): The username to click on
|
||||
"""
|
||||
self._click(f"//div[@class='name_search']/b[text()='{username}']")
|
||||
|
||||
def click_on_add_member_button(self):
|
||||
self._click(self.__add_member_button)
|
||||
"""Click on the add member button"""
|
||||
self._click(self.GROUP_PAGE_LOCATORS["add_member_button"])
|
||||
|
||||
def click_on_a_listed_group_user(self, username: str):
|
||||
"""Click on a listed group user.
|
||||
|
||||
Args:
|
||||
username (str): The username of the user to click on
|
||||
"""
|
||||
self._click(f"//div[@class='info']/a[text()='{username}']")
|
||||
|
||||
def get_remove_user_page_header(self) -> str:
|
||||
"""Get the text of the remove user page header"""
|
||||
return self._get_text_of_element(self.REMOVE_USER_PAGE_LOCATORS["remove_user_page_header"])
|
||||
|
||||
def click_on_remove_member_button(self):
|
||||
"""Click on the remove member button"""
|
||||
self._click(self.REMOVE_USER_PAGE_LOCATORS["remove_member_button"])
|
||||
|
||||
def click_on_remove_member_cancel_button(self):
|
||||
"""Click on the remove member cancel button"""
|
||||
self._click(self.REMOVE_USER_PAGE_LOCATORS["remove_member_cancel_button"])
|
||||
|
|
|
@ -228,7 +228,7 @@ class InboxPage(BasePage):
|
|||
excerpt: The excerpt of the message.
|
||||
"""
|
||||
inbox_checkbox = self.inbox_message_select_checkbox_element(excerpt)
|
||||
inbox_checkbox[0].check()
|
||||
self._checkbox_interaction(inbox_checkbox[0], True)
|
||||
|
||||
def delete_all_inbox_messages_via_delete_selected_button(self, excerpt='', expected_url=None):
|
||||
"""Delete all the inbox messages via the delete selected button.
|
||||
|
@ -237,6 +237,7 @@ class InboxPage(BasePage):
|
|||
excerpt: The excerpt of the message.
|
||||
expected_url: The expected URL after deleting all the messages.
|
||||
"""
|
||||
self._wait_for_dom_load_to_finish()
|
||||
if excerpt != '':
|
||||
inbox_messages_count = self._inbox_message_element_handles(excerpt)
|
||||
else:
|
||||
|
|
|
@ -247,6 +247,14 @@ class MyProfilePage(BasePage):
|
|||
"""Get the locator for the groups section."""
|
||||
return self._get_element_locator(self.PROFILE_DETAILS_LOCATORS["groups_section"])
|
||||
|
||||
def click_on_a_particular_profile_group(self, group_name: str):
|
||||
"""Click on a particular profile group.
|
||||
|
||||
Args:
|
||||
group_name (str): The name of the group to click on
|
||||
"""
|
||||
self._click(f"//section[@class='groups']//a[text()='{group_name}']")
|
||||
|
||||
def edit_user_profile_option_element(self) -> Locator:
|
||||
"""Get the locator for the edit user profile option."""
|
||||
return self._get_element_locator(self.ADMIN_ACTIONS_LOCATORS["edit_user_profile_option"])
|
||||
|
|
|
@ -32,4 +32,5 @@ markers =
|
|||
create_delete_article: Fixture used for creating and deleting test articles.
|
||||
exploreByTopics: Tests belonging to the explore help articles by topics page.
|
||||
searchTests: Tests belonging to the search functionality.
|
||||
userGroupsTests: Tests belonging to the user groups section.
|
||||
addopts = --alluredir=./reports/allure_reports --tb=no
|
||||
|
|
Двоичные данные
playwright_tests/test_data/test-image.png
Двоичные данные
playwright_tests/test_data/test-image.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 26 KiB После Ширина: | Высота: | Размер: 26 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 4.3 KiB |
|
@ -0,0 +1,314 @@
|
|||
import os
|
||||
import allure
|
||||
import pytest
|
||||
from playwright.sync_api import Page
|
||||
from pytest_check import check
|
||||
from playwright_tests.core.utilities import Utilities
|
||||
from playwright_tests.messages.user_groups_messages import UserGroupMessages
|
||||
from playwright_tests.pages.sumo_pages import SumoPages
|
||||
|
||||
|
||||
# C2083482
|
||||
@pytest.mark.userGroupsTests
|
||||
def test_edit_groups_details_visibility(page: Page):
|
||||
sumo_pages = SumoPages(page)
|
||||
utilities = Utilities(page)
|
||||
with check, allure.step("Navigating to the general groups page and verifying that the 'edit "
|
||||
"groups details' button is not visible while signed out"):
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
assert not sumo_pages.user_groups.is_add_group_profile_button_visible()
|
||||
|
||||
with check, allure.step("Signing in with a non-admin account and verifying that the 'edit "
|
||||
"groups details' button is not visible"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_13']
|
||||
))
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
assert not sumo_pages.user_groups.is_add_group_profile_button_visible()
|
||||
|
||||
with check, allure.step("Signing in with an admin account and verifying that the 'edit group "
|
||||
"details' button is visible"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MODERATOR']
|
||||
))
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
assert sumo_pages.user_groups.is_add_group_profile_button_visible()
|
||||
|
||||
|
||||
# C2083482, C2083483, C2715808, C2715807
|
||||
@pytest.mark.userGroupsTests
|
||||
def test_group_edit_buttons_visibility(page: Page):
|
||||
sumo_pages = SumoPages(page)
|
||||
utilities = Utilities(page)
|
||||
|
||||
with check, allure.step("Navigating to a test group and verifying that the edit buttons are "
|
||||
"not available for signed out users"):
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
sumo_pages.user_groups.click_on_a_particular_group(
|
||||
utilities.user_message_test_data['test_groups'][1])
|
||||
|
||||
assert not sumo_pages.user_groups.is_change_avatar_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_in_admin_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_profile_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_leaders_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_members_option_visible()
|
||||
|
||||
with check, allure.step("Signing in with a non-admin and a non group member and verifying "
|
||||
"that the edit buttons are not available"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_13']
|
||||
))
|
||||
assert not sumo_pages.user_groups.is_change_avatar_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_in_admin_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_profile_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_leaders_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_members_option_visible()
|
||||
|
||||
with check, allure.step("Signing in with a group member and verifying that the edit buttons "
|
||||
"are not available"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MESSAGE_3']
|
||||
))
|
||||
assert not sumo_pages.user_groups.is_change_avatar_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_in_admin_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_profile_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_leaders_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_members_option_visible()
|
||||
|
||||
with check, allure.step("Signing in with the group leader and verifying that only the 'edit "
|
||||
"in admin' and 'edit group leaders' buttons are available"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MESSAGE_4']
|
||||
))
|
||||
assert sumo_pages.user_groups.is_change_avatar_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_in_admin_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_group_profile_button_visible()
|
||||
assert not sumo_pages.user_groups.is_edit_group_leaders_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_group_members_option_visible()
|
||||
|
||||
with check, allure.step("Signing in with an admin account and verifying that all edit options "
|
||||
"are available"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MODERATOR']
|
||||
))
|
||||
assert sumo_pages.user_groups.is_change_avatar_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_in_admin_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_group_profile_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_group_leaders_button_visible()
|
||||
assert sumo_pages.user_groups.is_edit_group_members_option_visible()
|
||||
|
||||
|
||||
# C2783730, C2715807
|
||||
@pytest.mark.userGroupsTests
|
||||
def test_change_group_avatar(page: Page):
|
||||
utilities = Utilities(page)
|
||||
sumo_pages = SumoPages(page)
|
||||
targeted_group = utilities.user_message_test_data['test_groups'][0]
|
||||
displayed_image = os.path.abspath("test_data/visual_testing/displayed_group_image.png")
|
||||
first_uploaded_image = os.path.abspath("test_data/test-image.png")
|
||||
second_uploaded_image = os.path.abspath("test_data/test-image2.png")
|
||||
|
||||
with allure.step("Signing in with a group leader and accessing the test group"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MESSAGE_2']
|
||||
))
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
sumo_pages.user_groups.click_on_a_particular_group(targeted_group)
|
||||
group_url = utilities.get_page_url()
|
||||
utilities.screenshot_the_locator(sumo_pages.user_groups.get_group_avatar_locator(),
|
||||
displayed_image)
|
||||
|
||||
with allure.step("Clicking on the 'Change' avatar button, uploading the image and clicking on"
|
||||
"the 'Cancel' button"):
|
||||
sumo_pages.user_groups.click_on_change_avatar_button()
|
||||
utilities.upload_file(sumo_pages.user_groups.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
['upload_avatar_browse_button'], first_uploaded_image)
|
||||
sumo_pages.user_groups.click_on_upload_avatar_cancel_button()
|
||||
|
||||
with check, allure.step("Verifying that the uploaded avatar image is not displayed inside the "
|
||||
"group"):
|
||||
assert utilities.are_images_different(displayed_image, first_uploaded_image)
|
||||
|
||||
with allure.step("Clicking on the 'Change' avatar button, uploading the image and clicking on"
|
||||
"the 'Upload' button"):
|
||||
sumo_pages.user_groups.click_on_change_avatar_button()
|
||||
utilities.upload_file(sumo_pages.user_groups.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
['upload_avatar_browse_button'], first_uploaded_image)
|
||||
sumo_pages.user_groups.click_on_upload_avatar_button(expected_url=group_url)
|
||||
|
||||
with check, allure.step("Verifying that the uploaded image is successfully displayed inside "
|
||||
"the group"):
|
||||
utilities.screenshot_the_locator(sumo_pages.user_groups.get_group_avatar_locator(),
|
||||
displayed_image)
|
||||
assert not utilities.are_images_different(displayed_image, first_uploaded_image)
|
||||
|
||||
with check, allure.step("Clicking on the 'Change' avatar option and verifying that the "
|
||||
"uploaded image is successfully displayed inside the image preview"):
|
||||
sumo_pages.user_groups.click_on_change_uploaded_avatar_button()
|
||||
utilities.screenshot_the_locator(
|
||||
sumo_pages.user_groups.get_change_avatar_image_preview_locator(),
|
||||
displayed_image
|
||||
)
|
||||
assert not utilities.are_images_different(displayed_image,
|
||||
first_uploaded_image)
|
||||
|
||||
with check, allure.step("Verifying that the correct page header is displayed"):
|
||||
assert (sumo_pages.user_groups.get_upload_avatar_page_header() == UserGroupMessages.
|
||||
get_change_uploaded_avatar_page_header(targeted_group))
|
||||
|
||||
with check, allure.step("Adding a new image, clicking on the 'Cancel' button and verifying "
|
||||
"that the new image was not added to the group"):
|
||||
utilities.upload_file(sumo_pages.user_groups.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
['upload_avatar_browse_button'], second_uploaded_image)
|
||||
sumo_pages.user_groups.click_on_upload_avatar_cancel_button()
|
||||
assert utilities.are_images_different(first_uploaded_image, second_uploaded_image)
|
||||
|
||||
with check, allure.step("Adding a new image, clicking on the 'Upload' button and verifying "
|
||||
"that the newly uploaded image is displayed inside the group"):
|
||||
sumo_pages.user_groups.click_on_change_uploaded_avatar_button()
|
||||
utilities.upload_file(sumo_pages.user_groups.CHANGE_AVATAR_PAGE_LOCATORS
|
||||
['upload_avatar_browse_button'], second_uploaded_image)
|
||||
sumo_pages.user_groups.click_on_upload_avatar_button(expected_url=group_url)
|
||||
|
||||
utilities.screenshot_the_locator(sumo_pages.user_groups.get_group_avatar_locator(),
|
||||
displayed_image)
|
||||
assert not utilities.are_images_different(displayed_image, second_uploaded_image)
|
||||
|
||||
with check, allure.step("Clicking on the 'Delete' avatar button and verifying the page"
|
||||
"content"):
|
||||
sumo_pages.user_groups.click_on_delete_uploaded_avatar_button()
|
||||
assert (sumo_pages.user_groups.get_delete_avatar_page_header() == UserGroupMessages.
|
||||
get_delete_uploaded_avatar_page_header(targeted_group))
|
||||
|
||||
assert (sumo_pages.user_groups.get_delete_avatar_page_info() == UserGroupMessages.
|
||||
DELETE_AVATAR_PAGE_INFO)
|
||||
|
||||
utilities.screenshot_the_locator(
|
||||
sumo_pages.user_groups.get_delete_avatar_image_preview_locator(), displayed_image)
|
||||
assert not utilities.are_images_different(displayed_image, second_uploaded_image)
|
||||
|
||||
with allure.step("Clicking on the 'Cancel' button and verifying that the image was not "
|
||||
"deleted"):
|
||||
sumo_pages.user_groups.click_on_cancel_delete_avatar_button()
|
||||
utilities.screenshot_the_locator(sumo_pages.user_groups.get_group_avatar_locator(),
|
||||
displayed_image)
|
||||
assert not utilities.are_images_different(displayed_image, second_uploaded_image)
|
||||
|
||||
with allure.step("Clicking on the 'Delete' avatar button, confirming the deletion and "
|
||||
"verifying that the image was deleted"):
|
||||
sumo_pages.user_groups.click_on_delete_uploaded_avatar_button()
|
||||
sumo_pages.user_groups.click_on_delete_avatar_button()
|
||||
utilities.screenshot_the_locator(sumo_pages.user_groups.get_group_avatar_locator(),
|
||||
displayed_image)
|
||||
assert utilities.are_images_different(displayed_image, second_uploaded_image)
|
||||
|
||||
|
||||
# C2083499, C2715807
|
||||
@pytest.mark.userGroupsTests
|
||||
@pytest.mark.parametrize("user", ['TEST_ACCOUNT_MESSAGE_2', 'TEST_ACCOUNT_MODERATOR'])
|
||||
def test_add_group_members(page: Page, user):
|
||||
utilities = Utilities(page)
|
||||
sumo_pages = SumoPages(page)
|
||||
test_user = utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts[
|
||||
"TEST_ACCOUNT_12" if user == 'TEST_ACCOUNT_MESSAGE_2' else "TEST_ACCOUNT_13"]
|
||||
)
|
||||
|
||||
test_group = utilities.user_message_test_data['test_groups'][0]
|
||||
|
||||
with allure.step("Signing in and accessing the test group"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts[user]
|
||||
))
|
||||
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
sumo_pages.user_groups.click_on_a_particular_group(test_group)
|
||||
|
||||
with allure.step("Adding a new group member"):
|
||||
sumo_pages.user_group_flow.add_a_user_to_group(test_user)
|
||||
|
||||
with allure.step("Verifying that the correct banner is displayed"):
|
||||
assert (sumo_pages.user_groups.get_group_update_notification() == UserGroupMessages.
|
||||
get_user_added_success_message(test_user))
|
||||
|
||||
with allure.step("Clicking on the added user"):
|
||||
sumo_pages.user_groups.click_on_a_listed_group_user(test_user)
|
||||
|
||||
with allure.step("Verifying that the group is listed inside the users profile group list"):
|
||||
assert test_group in sumo_pages.my_profile_page.get_my_profile_groups_items_text()
|
||||
|
||||
with allure.step("Clicking on the group link from the profile page"):
|
||||
sumo_pages.my_profile_page.click_on_a_particular_profile_group(test_group)
|
||||
|
||||
with check, allure.step("Clicking on the 'Delete' button for the newly added user and "
|
||||
"verifying that the correct page header is displayed"):
|
||||
sumo_pages.user_groups.click_on_edit_group_members()
|
||||
sumo_pages.user_groups.click_on_remove_a_user_from_group_button(test_user)
|
||||
assert (sumo_pages.user_groups.get_remove_user_page_header() == UserGroupMessages.
|
||||
get_delete_user_header(test_user, test_group))
|
||||
|
||||
with check, allure.step("Clicking on the 'Cancel' button and verifying that the user was not "
|
||||
"removed"):
|
||||
sumo_pages.user_groups.click_on_remove_member_cancel_button()
|
||||
assert test_user in sumo_pages.user_groups.get_all_members_name()
|
||||
|
||||
with check, allure.step("Deleting the user and verifying that the user was removed"):
|
||||
sumo_pages.user_group_flow.remove_a_user_from_group(test_user)
|
||||
assert test_user not in sumo_pages.user_groups.get_all_members_name()
|
||||
|
||||
with check, allure.step("Verifying that the correct banner is displayed"):
|
||||
assert (sumo_pages.user_groups.get_group_update_notification() == UserGroupMessages.
|
||||
get_user_removed_success_message(test_user))
|
||||
|
||||
|
||||
# C2784450
|
||||
@pytest.mark.userGroupsTests
|
||||
@pytest.mark.parametrize("user", ['TEST_ACCOUNT_MESSAGE_2', 'TEST_ACCOUNT_MODERATOR'])
|
||||
def test_edit_group_profile(page: Page, user):
|
||||
utilities = Utilities(page)
|
||||
sumo_pages = SumoPages(page)
|
||||
test_group = (utilities.user_message_test_data['test_groups']
|
||||
[0] if user == 'TEST_ACCOUNT_MESSAGE_2' else utilities.
|
||||
user_message_test_data['test_groups'][1])
|
||||
|
||||
with allure.step("Signing in and accessing the test group"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts[user]
|
||||
))
|
||||
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
sumo_pages.user_groups.click_on_a_particular_group(test_group)
|
||||
|
||||
group_profile_info = sumo_pages.user_groups.get_profile_information()
|
||||
|
||||
with check, allure.step("Clicking on the 'Edit' button and verifying that the correct page "
|
||||
"header is displayed"):
|
||||
sumo_pages.user_groups.click_on_edit_group_profile_button()
|
||||
assert (sumo_pages.user_groups.get_edit_group_profile_page_header() == UserGroupMessages.
|
||||
get_edit_profile_information_page_header(test_group))
|
||||
|
||||
with check, allure.step("Verifying that the correct information was pre-filled inside the "
|
||||
"textarea field"):
|
||||
assert (sumo_pages.user_groups.
|
||||
get_edit_group_profile_textarea_content() == group_profile_info)
|
||||
|
||||
with allure.step("Changing the group profile information and verifying that the new info is "
|
||||
"displayed"):
|
||||
sumo_pages.user_groups.type_into_edit_group_profile_textarea(group_profile_info + " v2")
|
||||
sumo_pages.user_groups.click_on_edit_group_profile_save_button()
|
||||
assert (sumo_pages.user_groups.get_group_update_notification() == UserGroupMessages.
|
||||
GROUP_INFORMATION_UPDATE_NOTIFICATION)
|
||||
assert (sumo_pages.user_groups.get_profile_information() == group_profile_info + " v2")
|
||||
|
||||
with allure.step("Signing out and verifying that the new group profile information is "
|
||||
"displayed for signed out users"):
|
||||
utilities.delete_cookies()
|
||||
assert (sumo_pages.user_groups.get_profile_information() == group_profile_info + " v2")
|
||||
|
||||
with allure.step("Signing back in and reverting the profile information change"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts[user]
|
||||
))
|
||||
sumo_pages.user_groups.click_on_edit_group_profile_button()
|
||||
sumo_pages.user_groups.type_into_edit_group_profile_textarea(group_profile_info)
|
||||
sumo_pages.user_groups.click_on_edit_group_profile_save_button()
|
|
@ -1060,6 +1060,80 @@ def test_unable_to_send_group_messages_to_profiless_groups(page: Page):
|
|||
expect(sumo_pages.new_message_page.get_no_user_to_locator()).to_be_visible(timeout=10000)
|
||||
|
||||
|
||||
# C2083482
|
||||
@pytest.mark.messagingSystem
|
||||
def test_pm_group_member(page: Page, is_firefox):
|
||||
utilities = Utilities(page)
|
||||
sumo_pages = SumoPages(page)
|
||||
message_body = "Test " + utilities.generate_random_number(1, 1000)
|
||||
target_user = utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_MESSAGE_3']
|
||||
)
|
||||
with allure.step("Navigating to test group and click on the 'Private Message for a certain "
|
||||
"user'"):
|
||||
utilities.navigate_to_link(utilities.general_test_data['groups'])
|
||||
sumo_pages.user_groups.click_on_a_particular_group(
|
||||
utilities.user_message_test_data['test_groups'][0])
|
||||
group_link = utilities.get_page_url()
|
||||
sumo_pages.user_groups.click_on_pm_for_a_particular_user(target_user)
|
||||
|
||||
with check, allure.step("Verifying that the user is redirected to the auth page"):
|
||||
assert (
|
||||
sumo_pages.auth_page.is_continue_with_firefox_button_displayed()
|
||||
), "The auth page is not displayed! It should be!"
|
||||
|
||||
with allure.step("Signing in and sending a pm via the group page"):
|
||||
utilities.navigate_to_link(group_link)
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts['TEST_ACCOUNT_12']
|
||||
))
|
||||
sumo_pages.user_groups.click_on_pm_for_a_particular_user(target_user)
|
||||
|
||||
with allure.step("Verifying that the receiver is automatically added inside the 'To' "
|
||||
"field"):
|
||||
# Firefox GH runner fails here. We are running this assertion only in Chrome for now
|
||||
if not is_firefox:
|
||||
assert sumo_pages.new_message_page.get_user_to_text() == target_user, (
|
||||
f"Incorrect 'To' receiver. Expected: {target_user}. "
|
||||
f"Received: {sumo_pages.new_message_page.get_user_to_text()}"
|
||||
)
|
||||
|
||||
with allure.step("Sending a message to the user"):
|
||||
sumo_pages.messaging_system_flow.complete_send_message_form_with_data(
|
||||
message_body=message_body)
|
||||
|
||||
with check, allure.step("Verifying that the correct message sent banner is displayed"):
|
||||
assert sumo_pages.inbox_page.get_text_inbox_page_message_banner_text(
|
||||
) == InboxPageMessages.MESSAGE_SENT_BANNER_TEXT
|
||||
|
||||
with allure.step("Clicking on the 'Sent Messages option"):
|
||||
sumo_pages.mess_system_user_navbar.click_on_messaging_system_nav_sent_messages()
|
||||
|
||||
with allure.step("Verifying that the sent message is displayed"):
|
||||
expect(
|
||||
sumo_pages.sent_message_page.sent_messages_by_excerpt_locator(message_body)
|
||||
).to_be_visible()
|
||||
|
||||
with allure.step("Deleting the message from the sent messages page"):
|
||||
sumo_pages.messaging_system_flow.delete_message_flow(
|
||||
excerpt=message_body, from_sent_list=True)
|
||||
|
||||
with allure.step("Signing in with the user which received the message"):
|
||||
utilities.start_existing_session(utilities.username_extraction_from_email(
|
||||
utilities.user_secrets_accounts["TEST_ACCOUNT_MESSAGE_3"]
|
||||
))
|
||||
|
||||
with allure.step("Accessing the Inbox section"):
|
||||
sumo_pages.top_navbar.click_on_inbox_option()
|
||||
|
||||
with allure.step("Verifying that the inbox contains the previously sent messages"):
|
||||
expect(sumo_pages.inbox_page._inbox_message_based_on_excerpt(message_body)).to_be_visible()
|
||||
|
||||
with allure.step("Deleting the messages from the inbox section"):
|
||||
sumo_pages.messaging_system_flow.delete_message_flow(
|
||||
excerpt=message_body, from_inbox_list=True)
|
||||
|
||||
|
||||
@pytest.mark.messagingSystemCleanup
|
||||
def test_clear_inbox_and_outbox(page: Page):
|
||||
utilities = Utilities(page)
|
||||
|
|
Загрузка…
Ссылка в новой задаче