Skip to content

Commit

Permalink
Merge pull request #5870 from emilghittasv/playwright-kb-article-threads
Browse files Browse the repository at this point in the history
Expand playwright coverage over kb discussion threads
  • Loading branch information
emilghittasv authored Feb 2, 2024
2 parents 5deed9f + 1e8320c commit a47e77a
Show file tree
Hide file tree
Showing 14 changed files with 1,594 additions and 18 deletions.
19 changes: 15 additions & 4 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,27 @@ jobs:
if: success() || failure() && steps.create-sessions.outcome == 'success'
run: |
poetry run pytest -m kbProductsPage --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_products_page.html --capture=tee-sys
- name: Run Product Support Page Tests (${{ env.BROWSER }})
- name: Run KB Article Creation And Access Tests (${{ env.BROWSER }})
working-directory: playwright_tests
if: success() || failure() && steps.create-sessions.outcome == 'success'
run: |
poetry run pytest -m productSupportPage --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_product_support_page.html --capture=tee-sys
- name: Run KB Article Creation And Access Tests (${{ env.BROWSER }})
poetry run pytest -m kbArticleCreationAndAccess --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_article_creation_and_access.html --capture=tee-sys
- name: Run before kb thread tests setup (${{ env.BROWSER }})
id: kb-threads-setup
working-directory: playwright_tests
if: success() || failure() && steps.create-sessions.outcome == 'success'
run: |
poetry run pytest -m kbArticleCreationAndAccess --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_article_creation_and_access.html --capture=tee-sys
poetry run pytest -m beforeThreadTests --numprocesses 1 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_before_thread_tests_setup.html --capture=tee-sys
- name: Run KB article threads Tests (${{ env.BROWSER }})
working-directory: playwright_tests
if: success() || failure() && steps.create-sessions.outcome == 'success' && steps.kb-threads-setup.outcome == 'success'
run: |
poetry run pytest -m articleThreads --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_article_threads.html --capture=tee-sys
- name: Run KB article threads tear down (${{ env.BROWSER }})
working-directory: playwright_tests
if: success() || failure() && steps.create-sessions.outcome == 'success' && steps.kb-threads-setup.outcome == 'success'
run: |
poetry run pytest -m afterThreadTests --numprocesses 1 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_article_threads_tear_down.html --capture=tee-sys
- name: Combine Reports
working-directory: playwright_tests
if: success() || failure()
Expand Down
6 changes: 6 additions & 0 deletions playwright_tests/core/testutilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,9 @@ def remove_character_from_string(self, string: str, character_to_remove: str) ->
def create_slug_from_title(self, article_title: str) -> str:
initial_title = article_title.split()
return '-'.join(initial_title).lower()

def is_descending(self, list_of_items: list[str]):
if all(list_of_items[i] >= list_of_items[i + 1] for i in range(len(list_of_items) - 1)):
return True
else:
return False
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ class PostNewDiscussionThreadFlow(TestUtilities, KBArticleDiscussionPage):
def __init__(self, page: Page):
super().__init__(page)

def add_new_kb_discussion_thread(self) -> dict[str, Any]:
thread_title = (super().kb_new_thread_test_data['new_thread_title'] + super()
.generate_random_number(0, 1000))
def add_new_kb_discussion_thread(self, title='') -> dict[str, Any]:
if title == '':
thread_title = (super().kb_new_thread_test_data['new_thread_title'] + super()
.generate_random_number(0, 1000))
else:
thread_title = (title + super()
.generate_random_number(0, 1000))
thread_body = super().kb_new_thread_test_data['new_thread_body']

# Adding text to the title field.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ class KBArticlePageMessages:
KB_ARTICLE_PAGE_URL = "https://support.allizom.org/en-US/kb/"
KB_ARTICLE_HISTORY_URL_ENDPOINT = "/history"
KB_ARTICLE_DISCUSSIONS_ENDPOINT = "/discuss/"
KB_ARTICLE_DISCUSSIONS_NEW_ENDPOINT = "/discuss/new"
KB_ARTICLE_LOCKED_THREAD_MESSAGE = ("This thread has been locked. It is no longer possible to "
"post new replies.")
KB_ARTICLE_LOCK_THIS_THREAD_OPTION = "Lock this thread"
KB_ARTICLE_UNLOCK_THIS_THREAD_OPTION = "Unlock this thread"
KB_ARTICLE_STICKY_THIS_THREAD_OPTION = "Sticky this thread"
KB_ARTICLE_UNSTICKY_OPTION = "Unsticky this thread"
GET_COMMUNITY_SUPPORT_ARTICLE_LINK = ("https://support.allizom.org/en-US/kb/get-community"
"-support?exit_aaq=1")
UNREVIEWED_REVISION_STATUS = "Unreviewed"
Expand Down
5 changes: 4 additions & 1 deletion playwright_tests/pages/auth_page.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from playwright.sync_api import Page
from playwright.sync_api import Page, Locator
from playwright_tests.core.basepage import BasePage


Expand Down Expand Up @@ -78,3 +78,6 @@ def _is_enter_otp_code_input_field_displayed(self) -> bool:
def _is_continue_with_firefox_button_displayed(self) -> bool:
super()._wait_for_selector(self.__continue_with_firefox_accounts_button)
return super()._is_element_visible(self.__continue_with_firefox_accounts_button)

def _get_continue_with_firefox_button_locator(self) -> Locator:
return super()._get_element_locator(self.__continue_with_firefox_accounts_button)
Original file line number Diff line number Diff line change
@@ -1,45 +1,198 @@
from playwright.sync_api import Page, Locator
from playwright_tests.core.basepage import BasePage
import re


class KBArticleDiscussionPage(BasePage):
# Filters locators
__article_thread_author_filter = "//th[contains(@class,'author')]/a"
__article_thread_replies_filter = "//th[contains(@class,'replies')]/a"

# Post a new thread locator.
__post_a_new_thread_option = "//a[@id='new-thread']"

# New Thread related locators.
__new_thread_title_input_field = "//input[@id='id_title']"
__new_thread_body_input_field = "//textarea[@id='id_content']"
__new_thread_submit_button = "//button[text()='Post Thread']"
__new_thread_cancel_button = "//a[text()='Cancel']"

# Thread Editing tools
__delete_thread = "//a[text()='Delete this thread']"
__lock_this_thread = "//a[@data-form='thread-lock-form']"
__sticky_this_thread = "//a[@data-form='thread-sticky-form']"

# Thread content locators.
__article_thread_edit_this_thread = "//a[text()='Edit this thread']"
__article_thread_sticky_status = "//ul[@id='thread-meta']/li[text()='Sticky']"
__article_thread_locked_status = "//ul[@id='thread-meta']/li[text()='Locked']"
__thread_body_content = "//div[@class='content']/p"
__thread_body_content_title = "//h1[@class='sumo-page-heading']"

# Thread replies locators.
__delete_thread_reply_confirmation_page_button = "//input[@value='Delete']"
__thread_post_a_reply_textarea_field = "//textarea[@id='id_content']"
__article_thread_locked_message = "//p[@id='thread-locked']"
__thread_page_replies_counter = "//ul[@id='thread-meta']/li[1]"
__thread_page_last_reply_by_text = "//ul[@id='thread-meta']/li[2]"
__thread_post_a_new_reply_button = "//button[text()='Post Reply']"

# Article discussions content locators.
__all_article_threads_titles = "//td[@class='title']/a"
__all_article_threads_authors = "//td[@class='author']"
__all_article_thread_replies = "//td[@class='replies']"

# Edit thread page
__edit_article_thread_title_field = "//input[@id='id_title']"
__edit_article_thread_cancel_button = "//a[text()='Cancel']"
__edit_article_thread_update_thread_button = "//button[text()='Update thread']"

def __init__(self, page: Page):
super().__init__(page)

# Edit thread page actions
def _get_edit_this_thread_locator(self) -> Locator:
return super()._get_element_locator(self.__article_thread_edit_this_thread)

def _click_on_edit_this_thread_option(self):
super()._click(self.__article_thread_edit_this_thread)

def _add_text_to_edit_article_thread_title_field(self, text: str):
super()._clear_field(self.__edit_article_thread_title_field)
super()._fill(self.__edit_article_thread_title_field, text)

def _click_on_edit_article_thread_cancel_button(self):
super()._click(self.__edit_article_thread_cancel_button)

def _click_on_edit_article_thread_update_button(self):
super()._click(self.__edit_article_thread_update_thread_button)

# Filter actions.
def _click_on_article_thread_author_replies_filter(self):
super()._click(self.__article_thread_author_filter)

def _click_on_article_thread_replies_filter(self):
super()._click(self.__article_thread_replies_filter)

# Post a new thread button action.
def _click_on_post_a_new_thread_option(self):
super()._click(self.__post_a_new_thread_option)

def _click_on_thread_post_reply_button(self):
super()._click(self.__thread_post_a_new_reply_button)

def _get_thread_reply_id(self, url: str) -> str:
return re.search(r'post-(\d+)', url).group(0)

# Actions related to posting a new thread.
def _add_text_to_new_thread_title_field(self, text: str):
super()._fill(self.__new_thread_title_input_field, text)

def _clear_new_thread_title_field(self):
super()._clear_field(self.__new_thread_title_input_field)

def _add_text_to_new_thread_body_input_field(self, text: str):
super()._fill(self.__new_thread_body_input_field, text)

def _clear_new_thread_body_field(self):
super()._clear_field(self.__new_thread_body_input_field)

def _click_on_cancel_new_thread_button(self):
super()._click(self.__new_thread_cancel_button)

def _click_on_submit_new_thread_button(self):
super()._click(self.__new_thread_submit_button)

def _get_posted_thread_locator(self, thread_id: str) -> Locator:
xpath = f"//tr[@class='threads']/td[@class='title']//a[contains(@href, '{thread_id}')]"
return super()._get_element_locator(xpath)

def _get_thread_by_title_locator(self, thread_title: str) -> Locator:
xpath = f"//tr[@class='threads']/td[@class='title']/a[text()='{thread_title}']"
return super()._get_element_locator(xpath)

def _click_on_a_particular_thread(self, thread_id: str):
xpath = f"//tr[@class='threads']/td[@class='title']//a[contains(@href, '{thread_id}')]"
super()._click(xpath)

# Actions related to thread content
def _get_post_a_reply_textarea_field(self) -> Locator:
def _get_thread_title_text(self) -> str:
return super()._get_text_of_element(self.__thread_body_content_title)

def _get_locked_article_status(self) -> Locator:
return super()._get_element_locator(self.__article_thread_locked_status)

def _get_lock_this_article_thread_option_text(self) -> str:
return super()._get_text_of_element(self.__lock_this_thread)

def _get_lock_this_article_thread_locator(self) -> Locator:
return super()._get_element_locator(self.__lock_this_thread)

def _click_on_lock_this_article_thread_option(self):
super()._click(self.__lock_this_thread)

def _get_sticky_this_thread_locator(self) -> Locator:
return super()._get_element_locator(self.__sticky_this_thread)

def _get_text_of_sticky_this_thread_option(self) -> str:
return super()._get_text_of_element(self.__sticky_this_thread)

def _get_sticky_this_thread_status_locator(self) -> Locator:
return super()._get_element_locator(self.__article_thread_sticky_status)

def _click_on_sticky_this_thread_option(self):
super()._click(self.__sticky_this_thread)

def _get_thread_post_a_reply_textarea_field(self) -> Locator:
return super()._get_element_locator(self.__thread_post_a_reply_textarea_field)

def _fill_the_thread_post_a_reply_textarea_field(self, text: str):
super()._fill(self.__thread_post_a_reply_textarea_field, text)

def _get_thread_page_counter_replies_text(self) -> str:
return super()._get_text_of_element(self.__thread_page_replies_counter)

def _get_thread_page_replied_by_text(self) -> str:
return super()._get_text_of_element(self.__thread_page_last_reply_by_text)

# Article discussions page content actions
def _get_article_discussions_thread_counter(self, thread_id: str) -> str:
xpath = (f"//tr[@class='threads']/td[@class='title']//a[contains(@href, "
f"'{thread_id}')]/../following-sibling::td[@class='replies']")
return super()._get_text_of_element(xpath)

def _get_all_article_threads_titles(self) -> list[str]:
return super()._get_text_of_elements(self.__all_article_threads_titles)

def _get_all_article_threads_authors(self) -> list[str]:
return super()._get_text_of_elements(self.__all_article_threads_authors)

def _get_all_article_threads_replies(self) -> list[str]:
return super()._get_text_of_elements(self.__all_article_thread_replies)

def _get_text_of_locked_article_thread_text(self) -> str:
return super()._get_text_of_element(self.__article_thread_locked_message)

def _get_text_of_locked_article_thread_locator(self) -> Locator:
return super()._get_element_locator(self.__article_thread_locked_message)

# Actions related to thread replies.
def _click_on_dotted_menu_for_a_certain_reply(self, thread_id: str):
xpath = f"//li[@id='{thread_id}']//span[@class='icon-button is-summary']//button"
super()._click(xpath)

def _click_on_delete_this_thread_option(self):
super()._click(self.__delete_thread)

def _click_on_edit_this_thread_reply(self, thread_id: str):
xpath = (f"//li[@id='{thread_id}']//div[@class='mzp-c-menu-list is-details']//a[text("
")='Edit this post']")
super()._click(xpath)

def _click_on_delete_this_thread_reply(self, thread_id: str):
xpath = (f"//li[@id='{thread_id}']//div[@class='mzp-c-menu-list is-details']//a[text("
")='Delete this post']")
super()._click(xpath)

def _click_on_delete_this_thread_reply_confirmation_button(self):
super()._click(self.__delete_thread_reply_confirmation_page_button)
7 changes: 6 additions & 1 deletion playwright_tests/test_data/kb_new_thread.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"new_thread_title": "Automation Thread ",
"new_thread_body": "Automation test thread body"
"updated_thread_title": "Automation Thread Updated",
"second_thread_updated_title": "Automation Thread Updated 2",
"new_thread_reduced_title": "Atest",
"new_thread_body": "Automation test thread body",
"new_thread_reduced_body": "Abody",
"thread_reply_body": "Automation thread reply"
}
Empty file.
2 changes: 1 addition & 1 deletion playwright_tests/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def pytest_runtest_makereport(item):
'onclick="window.open(this.src)" align="right"/></div>' % file_name
)

extra.append(pytest_html.extras.html(html))
extra.append(pytest_html.extras.html(html))
report.extra = extra
except Exception as e:
print(e)
Loading

0 comments on commit a47e77a

Please sign in to comment.