Skip to content

Commit

Permalink
Expanding playwright coverage over KB Dashboard and performing the fo…
Browse files Browse the repository at this point in the history
…llowing verifications:

- Ensure that the Live status, Needs Update, Ready for L10N, Stale & Expiry Date are successfully displayed & updated according to the corresponding modifications performed at KB article level.
- Ensure that changing the kb article title is also resembled inside the KB Dashboard.
- Ensure that the kb article title link (displayed inside the KB dashboard) redirects the user to the correct kb article page.

Updated the GH playwright workflow to include the new tests in our scheduled GH run.

Also performed some code refactoring by creating 'flow functions' for some repetitive kb actions (revision creation & article deletion).
  • Loading branch information
emilghittasv committed Mar 5, 2024
1 parent 4f6cdf2 commit c629cf7
Show file tree
Hide file tree
Showing 25 changed files with 993 additions and 188 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ jobs:
if: success() || failure() && steps.create-sessions.outcome == 'success'
run: |
poetry run pytest -m recentRevisionsDashboard --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_article_recent_revisions.html --capture=tee-sys
- name: Run KB Dashboard Tests (${{ env.BROWSER }})
working-directory: playwright_tests
if: success() || failure() && steps.create-sessions.outcome == 'success'
run: |
poetry run pytest -m kbDashboard --numprocesses 2 --browser ${{ env.BROWSER }} --reruns 1 --html=reports/${{ env.BROWSER }}_kb_dashboard.html --capture=tee-sys
- name: Combine Reports
working-directory: playwright_tests
if: success() || failure()
Expand Down
4 changes: 4 additions & 0 deletions playwright_tests/core/basepage.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ def _get_text_of_elements(self, xpath: str) -> list[str]:
def _get_text_of_element(self, xpath: str) -> str:
return self._get_element_locator(xpath).inner_text()

# Returning true if the inner text of an element is empty
def _is_element_empty(self, xpath: str) -> bool:
return not self._get_element_locator(xpath).inner_text()

# Elements count retrieval.
def _get_elements_count(self, xpath: str) -> int:
return self._get_element_locator(xpath).count()
Expand Down
15 changes: 14 additions & 1 deletion playwright_tests/core/testutilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class TestUtilities:
kb_new_thread_test_data = json.load(kb_new_thread_test_data_file)
kb_article_test_data_file.close()

with open("test_data/kb_revision.json", "r") as kb_revision_test_data_file:
kb_revision_test_data = json.load(kb_revision_test_data_file)
kb_revision_test_data_file.close()

with open("test_data/user_message.json", "r") as user_message_test_data_file:
user_message_test_data = json.load(user_message_test_data_file)
user_message_test_data_file.close()
Expand Down Expand Up @@ -142,9 +146,14 @@ def navigate_to_homepage(self):

# Navigating to a specific given link and waiting for the load state to finish.
def navigate_to_link(self, link: str):
self.page.goto(link)
with self.page.expect_navigation() as navigation_info:
self.page.goto(link)
response = navigation_info.value
self.wait_for_dom_to_load()

if response.status >= 400:
self.refresh_page()

# Wait for a given timeout
def wait_for_given_timeout(self, milliseconds: int):
self.page.wait_for_timeout(milliseconds)
Expand Down Expand Up @@ -220,6 +229,10 @@ def extract_month_day_year_from_string(self, timestamp_str: str) -> str:
timestamp = datetime.strptime(timestamp_str, "%b %d, %Y, %I:%M:%S %p")
return timestamp.strftime("%b %d, %Y")

def convert_string_to_datetime(self, timestamp_str: str) -> str:
date_object = datetime.strptime(timestamp_str, "%m.%d.%Y")
return date_object.strftime("%B {:d}, %Y").format(date_object.day)

def extract_date_to_digit_format(self, date_str: str) -> int:
date = datetime.strptime(date_str, "%b %d, %Y")
return int(date.strftime("%m%d%Y"))
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ def submit_simple_kb_article(self,
article_slug=None,
article_category=None,
allow_discussion=True,
allow_translations=True,
selected_relevancy=True,
selected_topics=True,
search_summary=None,
article_content=None,
submit_article=True,
is_template=False) -> dict[str, Any]:
is_template=False,
expiry_date=None) -> dict[str, Any]:
self._page.goto(KBArticlePageMessages.CREATE_NEW_KB_ARTICLE_STAGE_URL)

kb_article_test_data = super().kb_article_test_data
Expand Down Expand Up @@ -51,6 +53,9 @@ def submit_simple_kb_article(self,
else:
super()._select_category_option_by_text(article_category)

if not allow_translations:
super()._check_allow_translations_checkbox()

if selected_relevancy is True:
super()._click_on_a_relevant_to_option_checkbox(
kb_article_test_data["relevant_to_option"]
Expand Down Expand Up @@ -90,7 +95,9 @@ def submit_simple_kb_article(self,
if article_content is None:
super()._add_text_to_content_textarea(kb_article_test_data["article_content"])

super()._add_text_to_expiry_date_field(kb_article_test_data["expiry_date"])
if expiry_date is not None:
super()._add_text_to_expiry_date_field(expiry_date)

# We need to evaluate in order to fetch the slug field value
slug = self._page.evaluate(
'document.getElementById("id_slug").value'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from typing import Any

from playwright_tests.core.testutilities import TestUtilities
from playwright.sync_api import Page
from playwright_tests.pages.explore_help_articles.articles.kb_article_page import KBArticlePage
from playwright_tests.pages.explore_help_articles.articles.kb_article_review_revision_page import \
KBArticleReviewRevisionPage
from playwright_tests.pages.explore_help_articles.articles.kb_article_show_history_page import \
KBArticleShowHistoryPage
from playwright_tests.pages.explore_help_articles.articles.kb_edit_article_page import (
Expand All @@ -10,7 +14,8 @@
class AddKBArticleRevision(TestUtilities,
KBArticlePage,
EditKBArticlePage,
KBArticleShowHistoryPage):
KBArticleShowHistoryPage,
KBArticleReviewRevisionPage):
def __init__(self, page: Page):
super().__init__(page)

Expand All @@ -21,7 +26,7 @@ def submit_new_kb_revision(self,
expiry_date=None,
changes_description=None,
is_admin=False
) -> str:
) -> dict[str, Any]:

super()._click_on_edit_article_option()

Expand Down Expand Up @@ -71,4 +76,26 @@ def submit_new_kb_revision(self,

super()._click_edit_article_changes_panel_submit_button()

return super()._get_last_revision_id()
return {"revision_id": super()._get_last_revision_id(),
"changes_description": self.kb_article_test_data['changes_description']
}

def approve_kb_revision(self, revision_id: str,
revision_needs_change=False,
ready_for_l10n=False):
super()._click_on_review_revision(
revision_id
)
super()._click_on_approve_revision_button()

if revision_needs_change:
if not super()._is_needs_change_checkbox_checked():
super()._click_on_needs_change_checkbox()
super()._add_text_to_needs_change_comment(
super().kb_revision_test_data['needs_change_message']
)

if ready_for_l10n:
super()._check_ready_for_localization_checkbox()

super()._click_accept_revision_accept_button()
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from playwright_tests.core.testutilities import TestUtilities
from playwright.sync_api import Page

from playwright_tests.pages.explore_help_articles.articles.kb_article_page import KBArticlePage
from playwright_tests.pages.explore_help_articles.articles.kb_article_show_history_page import \
KBArticleShowHistoryPage
from playwright_tests.pages.top_navbar import TopNavbar


class DeleteKbArticleFlow(TestUtilities, KBArticleShowHistoryPage, KBArticlePage, TopNavbar):

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

def delete_kb_article(self):
# If the delete button is not displayed we presume that we are not on the show history page
# Clicking on the 'Show History' page.
if not super()._is_delete_button_displayed():
super()._click_on_show_history_option()
super()._click_on_delete_this_document_button()
super()._click_on_confirmation_delete_button()
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from playwright_tests.core.testutilities import TestUtilities
from playwright.sync_api import Page

from playwright_tests.pages.explore_help_articles.articles.kb_edit_article_meta import (
KBArticleEditMetadata)


class EditArticleMetaFlow(TestUtilities, KBArticleEditMetadata):

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

def edit_article_metadata(self, title=None, needs_change=False, needs_change_comment=False):
if title is not None:
super()._add_text_to_title_field(title)

# If it needs change we are going to ensure that the needs change checkbox is checked.
if needs_change:
if not super()._is_needs_change_checkbox():
super()._click_needs_change_checkbox()
# If it needs change with comment we are also adding the comment.
if needs_change_comment:
super()._fill_needs_change_textarea(
super().kb_revision_test_data['needs_change_message']
)
# If it doesn't need comment we are ensuring that the textarea field is empty.
else:
super()._fill_needs_change_textarea('')

# If it doesn't need change we are ensuring that the checkbox is not checked.
else:
if super()._is_needs_change_checkbox():
super()._click_needs_change_checkbox()

super()._click_on_save_changes_button()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class KBDashboardPageMessages:
KB_LIVE_STATUS = "Live"
GENERAL_NEGATIVE_STATUS = "No"
GENERAL_POSITIVE_STATUS = "Yes"

def get_kb_not_live_status(self, revision_note: str) -> str:
return f"Review Needed: {revision_note}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from playwright_tests.core.basepage import BasePage
from playwright.sync_api import Page, Locator


class KBDashboard(BasePage):
# Top-level filter locators
__products_filter_button = ("//article[@id='localize']/div[@class='mzp-c-menu-list "
"featured-dropdown is-details']//button")
__products_filter_complete_overview = "//div[@id='product-selector']/select"
__filter_by_type = "//div[@class='table-filters']//select"
__filter_by_type_complete_overview = "//select[@name='category']"
__subscribe_button = "//div[@id='doc-watch']/button"
__complete_overview_link = "//div[@class='table-footer']/a"

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

# KB Dashboard actions
def _get_a_particular_article_title_locator(self, article_name: str) -> Locator:
xpath = f"//td/a[text()='{article_name}']"
return super()._get_element_locator(xpath)

def _click_on_article_title(self, article_name: str):
xpath = f"//td/a[text()='{article_name}']"
super()._click(xpath)

def _get_a_particular_article_status(self, article_name: str) -> str:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'status ')]")
return super()._get_text_of_element(xpath)

def _get_needs_update_status(self, article_name: str) -> str:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'needs-update')]")
return super()._get_text_of_element(xpath)

def _is_needs_change_empty(self, article_name: str) -> bool:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'needs-update')]")
return super()._is_element_empty(xpath)

def _get_ready_for_l10n_status(self, article_name: str) -> str:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'ready-for-l10n')]")
return super()._get_text_of_element(xpath)

def _get_stale_status(self, article_name: str) -> str:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'stale ')]")
return super()._get_text_of_element(xpath)

def _is_stale_status_empty(self, article_name: str) -> bool:
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td[contains(@class,"
f"'stale ')]")
return super()._is_element_empty(xpath)

def _get_existing_expiry_date(self, article_name: str) -> str:
xpath = f"//td/a[text()='{article_name}']/../following-sibling::td/time"
return super()._get_text_of_element(xpath)

def _get_expiry_date_locator(self, article_name: str) -> Locator:
xpath = f"//td/a[text()='{article_name}']/../following-sibling::td/time"
return super()._get_element_locator(xpath)

def _click_on_show_translations_option(self, article_name: str):
xpath = (f"//td/a[text()='{article_name}']/../following-sibling::td/a[contains(text(),"
f"'Show translations')]")
super()._click(xpath)

def _click_on_the_complete_overview_link(self):
super()._click(self.__complete_overview_link)
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@ class KBArticleReviewRevisionPage(BasePage):
__revision_rendered_html_header = "//h3[text()='Revision rendered html:']"
__revision_rendered_html_content = "//div[@id='doc-content']/p"

__defer_revision_button = "//button[@id='btn-reject']"
__approve_revision_button = "//button[@id='btn-approve']"

# Defer revision modal
__defer_button = "//form[@id='reject-modal']//button"
__cancel_defer = "//form[@id='reject-modal']//a"

# Approve revision modal
__accept_revision_modal_header = "//div[@class='kbox-title']"

# Need to add locators for approving own edit revision warning.
__ready_for_localization_modal_checkbox = "//input[@id='id_is_ready_for_localization']"
__needs_change_modal_checkbox = "//input[@id='id_needs_change']"
__needs_change_comment_textarea = "//textarea[@id='id_needs_change_comment']"
__modal_accept_button = "//button[text()='Accept']"
__modal_cancel_button = "//form[@id='approve-modal']//a[text()='Cancel']"

Expand Down Expand Up @@ -87,6 +93,15 @@ def _is_revision_rendered_html_header_visible(self) -> bool:
def _get_revision_rendered_html_content(self) -> str:
return super()._get_text_of_element(self.__revision_rendered_html_content)

def _click_on_defer_revision_button(self):
super()._click(self.__defer_revision_button)

def _click_on_defer_confirm_button(self):
super()._click(self.__defer_button)

def _click_on_cancel_defer_button(self):
super()._click(self.__cancel_defer)

def _click_on_approve_revision_button(self):
super()._click(self.__approve_revision_button)

Expand All @@ -99,3 +114,12 @@ def _click_accept_revision_accept_button(self):

def _check_ready_for_localization_checkbox(self):
super()._click(self.__ready_for_localization_modal_checkbox)

def _is_needs_change_checkbox_checked(self) -> bool:
return super()._is_checkbox_checked(self.__needs_change_modal_checkbox)

def _click_on_needs_change_checkbox(self):
super()._click(self.__needs_change_modal_checkbox)

def _add_text_to_needs_change_comment(self, text: str):
super()._fill(self.__needs_change_comment_textarea, text)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class KBArticleShowHistoryPage(BasePage):
__show_history_category_link = "//dt[text()='Category:']/following-sibling::dd/a"
__show_history_revision_history_for = ("//dt[text()='Revision history "
"for:']/following-sibling::dd[1]")
__ready_for_l10_modal_submit_button = "//button[@id='submit-l10n']"

# Document contributors locators
__show_history_page_banner = "//li[@class='mzp-c-notification-bar mzp-t-success']/p"
Expand Down Expand Up @@ -54,13 +55,23 @@ def _click_on_a_particular_revision_editor(self, revision_id: str, username: str
xpath = f"//tr[@id='{revision_id}']//a[contains(text(),'{username}')]"
super()._click(xpath)

def _click_on_ready_for_l10n_option(self, revision_id: str):
xpath = f"//tr[@id='{revision_id}']/td[@class='l10n']/a"
super()._click(xpath)

def _click_on_submit_l10n_readiness_button(self):
super()._click(self.__ready_for_l10_modal_submit_button)

# Delete document actions.
def _click_on_delete_this_document_button(self):
super()._click(self.__delete_this_document_button)

def _get_delete_this_document_button_locator(self) -> Locator:
return super()._get_element_locator(self.__delete_this_document_button)

def _is_delete_button_displayed(self) -> bool:
return super()._is_element_visible(self.__delete_this_document_button)

def _click_on_confirmation_delete_button(self):
super()._click(self.__delete_this_document_confirmation_delete_button)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class KBArticleEditMetadata(BasePage):
__obsolete_checkbox = "//input[@id='id_is_archived']"
__allow_discussion_checkbox = "//input[@id='id_allow_discussion']"
__needs_change_checkbox = "//input[@id='id_needs_change']"
__needs_change_textarea = "//textarea[@id='id_needs_change_comment']"
__save_changes_button = "//button[text()='Save']"

def __init__(self, page: Page):
Expand Down Expand Up @@ -83,11 +84,14 @@ def _is_allow_discussion_checkbox_checked(self) -> bool:
def _click_on_allow_discussion_on_article_checkbox(self):
super()._click(self.__allow_discussion_checkbox)

def _is_needs_change_checkbox_checked(self) -> bool:
def _is_needs_change_checkbox(self) -> bool:
return super()._is_checkbox_checked(self.__needs_change_checkbox)

def _click_needs_change_checkbox(self):
super()._click(self.__needs_change_checkbox)

def _fill_needs_change_textarea(self, text: str):
super()._fill(self.__needs_change_textarea, text)

def _click_on_save_changes_button(self):
super()._click(self.__save_changes_button)
Loading

0 comments on commit c629cf7

Please sign in to comment.