Skip to content

Commit

Permalink
Merge pull request mozilla#5911 from emilghittasv/playwright-kb-dashb…
Browse files Browse the repository at this point in the history
…oard

Expanding playwright coverage over KB Dashboard
  • Loading branch information
emilghittasv authored Mar 5, 2024
2 parents 4f6cdf2 + c629cf7 commit fee3820
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 fee3820

Please sign in to comment.