Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playwright handle wait timeouts and add new AAQ topics for AAQ test coverage #6359

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 34 additions & 16 deletions playwright_tests/core/basepage.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ def _get_element_locator(self, xpath: str, with_wait=True) -> Locator:
This helper function returns the element locator from a given xpath.
"""
if with_wait:
self._wait_for_dom_load_to_finish()
self.wait_for_dom_to_load()
return self.page.locator(xpath)

def _get_elements_locators(self, xpath: str) -> list[Locator]:
"""
This helper function returns a list of element locators from a given xpath.
"""
self._wait_for_dom_load_to_finish()
self.wait_for_dom_to_load()
return self.page.locator(xpath).all()

def _get_current_page_url(self) -> str:
Expand Down Expand Up @@ -80,7 +80,7 @@ def _get_element_attribute_value(self, element: Union[str, Locator, list[Locator
if isinstance(element, str):
return self._get_element_locator(element).get_attribute(attribute)
elif isinstance(element, list):
self._wait_for_dom_load_to_finish()
self.wait_for_dom_to_load()
values = []
for element in element:
values.append(element.get_attribute(attribute))
Expand Down Expand Up @@ -127,7 +127,7 @@ def _checkbox_interaction(self, element: [str, ElementHandle], check: bool, retr
element (Union[str, ElementHandle]): The element locator to interact with.
check (bool): Whether to check or uncheck the checkbox.
"""
self.page.wait_for_load_state("networkidle")
self.wait_for_networkidle()
for attempt in range(retries):
try:
locator = self._get_element_locator(element) if isinstance(
Expand Down Expand Up @@ -155,7 +155,7 @@ def _click(self, element: Union[str, Locator, ElementHandle], expected_locator=N
expected_url (str): The expected URL to wait for after the click.
with_force (bool): Whether to force the click.
"""
self.page.wait_for_load_state("networkidle")
self.wait_for_networkidle()
for attempt in range(retries):
try:
element_locator = self._get_element_locator(element) if isinstance(
Expand All @@ -178,14 +178,14 @@ def _click_on_an_element_by_index(self, xpath: str, index: int):
"""
This helper function clicks on a given element locator based on a given index.
"""
self.page.wait_for_load_state("networkidle")
self.wait_for_networkidle()
self._get_element_locator(xpath).nth(index).click()

def _click_on_first_item(self, xpath: str):
"""
This helper function clicks on the first item from a given web element locator list.
"""
self.page.wait_for_load_state("networkidle")
self.wait_for_networkidle()
self._get_element_locator(xpath).first.click()

def _fill(self, xpath: str, text: str):
Expand Down Expand Up @@ -267,15 +267,6 @@ def _is_checkbox_checked(self, xpath: str) -> bool:
"""
return self._get_element_locator(xpath).is_checked()

def _wait_for_dom_load_to_finish(self):
"""
This helper function performs two waits:
1. Waits for the dom load to finish.
2. Waits for the load event to be fired when the whole page, including resources has loaded
"""
self.page.wait_for_load_state("domcontentloaded")
self.page.wait_for_load_state("load")

def _wait_for_selector(self, xpath: str, timeout=3500):
"""
This helper function waits for a given element locator to be visible based on a given
Expand All @@ -295,3 +286,30 @@ def _move_mouse_to_location(self, x: int, y: int):
y (int): The y-coordinate.
"""
self.page.mouse.move(x, y)

def wait_for_page_to_load(self):
"""
This helper function awaits for the load event to be fired.
"""
try:
self.page.wait_for_load_state("load")
except PlaywrightTimeoutError:
print("Load event was not fired. Continuing...")

def wait_for_dom_to_load(self):
"""
This helper function awaits for the DOMContentLoaded event to be fired.
"""
try:
self.page.wait_for_load_state("domcontentloaded")
except PlaywrightTimeoutError:
print("DOMContentLoaded event was not fired. Continuing...")

def wait_for_networkidle(self):
"""
This helper function waits until there are no network connections for at least 500ms.
"""
try:
self.page.wait_for_load_state("networkidle")
except PlaywrightTimeoutError:
print("Network idle state was not reached. Continuing...")
45 changes: 40 additions & 5 deletions playwright_tests/core/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def navigate_to_homepage(self):
"""
This helper function navigates directly to the SUMO hompage.
"""
self.page.goto(HomepageMessages.STAGE_HOMEPAGE_URL)
self.navigate_to_link(HomepageMessages.STAGE_HOMEPAGE_URL)

def navigate_to_link(self, link: str):
"""
Expand Down Expand Up @@ -262,19 +262,28 @@ def wait_for_page_to_load(self):
"""
This helper function awaits for the load event to be fired.
"""
self.page.wait_for_load_state("load")
try:
self.page.wait_for_load_state("load")
except PlaywrightTimeoutError:
print("Load event was not fired. Continuing...")

def wait_for_dom_to_load(self):
"""
This helper function awaits for the DOMContentLoaded event to be fired.
"""
self.page.wait_for_load_state("domcontentloaded")
try:
self.page.wait_for_load_state("domcontentloaded")
except PlaywrightTimeoutError:
print("DOMContentLoaded event was not fired. Continuing...")

def wait_for_networkidle(self):
"""
This helper function waits until there are no network connections for at least 500ms.
"""
self.page.wait_for_load_state("networkidle")
try:
self.page.wait_for_load_state("networkidle")
except PlaywrightTimeoutError:
print("Network idle state was not reached. Continuing...")

def store_session_cookies(self, session_file_name: str):
"""
Expand Down Expand Up @@ -340,7 +349,10 @@ def refresh_page(self):
"""
This helper function performs a page reload.
"""
self.page.reload(wait_until="networkidle")
try:
self.page.reload(wait_until="networkidle")
except PlaywrightTimeoutError:
print("Network idle state was not reached. Continuing...")

def get_user_agent(self) -> str:
"""
Expand Down Expand Up @@ -575,6 +587,22 @@ def get_api_response(self, page: Page, api_url: str):
"""
return page.request.get(api_url)

def post_api_request(self, page: Page, api_url: str, data: dict):
"""Post the API request

Args:
page (Page): The page object
api_url (str): The API URL
data (dict): The data to be posted
"""

# It seems that playwright doesn't send the correct origin header by default.
headers = {
'origin': HomepageMessages.STAGE_HOMEPAGE_URL
}

return page.request.post(api_url, form=data, headers=headers)

def block_request(self, route):
"""
This function blocks a certain request
Expand All @@ -601,3 +629,10 @@ def re_call_function_on_error(self, func, *args, **kwargs):
if attempt < 2:
continue
break

def get_csrfmiddlewaretoken(self) -> str:
"""
This helper function fetches the csrfmiddlewaretoken from the page.
"""
return self.page.evaluate("document.querySelector('input[name=csrfmiddlewaretoken]')"
".value")
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from playwright.sync_api import Page
from typing import Any

from slugify import slugify

from playwright_tests.core.utilities import Utilities
from playwright_tests.flows.explore_articles_flows.article_flows.add_kb_media_flow import \
AddKbMediaFlow
Expand Down Expand Up @@ -48,7 +51,7 @@ def submit_simple_kb_article(self,
approve_first_revision=False,
ready_for_localization=False
) -> dict[str, Any]:
self.page.goto(KBArticlePageMessages.CREATE_NEW_KB_ARTICLE_STAGE_URL)
self.utilities.navigate_to_link(KBArticlePageMessages.CREATE_NEW_KB_ARTICLE_STAGE_URL)

kb_article_test_data = self.utilities.kb_article_test_data

Expand Down Expand Up @@ -314,3 +317,56 @@ def submit_new_kb_revision(self,
"revision_time": revision_time,
"changes_description": self.utilities.kb_article_test_data['changes_description']
}

def kb_article_creation_via_api(self, page: Page, approve_revision=False,
is_template=False) -> dict[str, Any]:
kb_article_test_data = self.utilities.kb_article_test_data
self.utilities.navigate_to_link(KBArticlePageMessages.CREATE_NEW_KB_ARTICLE_STAGE_URL)
if is_template:
kb_title = (kb_article_test_data["kb_template_title"] + self.utilities.
generate_random_number(0, 5000))
category = "60"
else:
kb_title = (kb_article_test_data["kb_article_title"] + self.utilities.
generate_random_number(0, 5000))
category = "10"

slug = slugify(kb_title)

form_data = {
"csrfmiddlewaretoken": self.utilities.get_csrfmiddlewaretoken(),
"title": kb_title,
"slug": slug,
"category": category,
"is_localizable": "on",
"products": "1",
"topics": "383",
"allow_discussion": "on",
"keywords": kb_article_test_data["keywords"],
"summary": kb_article_test_data["search_result_summary"],
"content": kb_article_test_data["article_content"],
"expires": "",
"based_on": "",
"comment": kb_article_test_data["changes_description"]
}

response = self.utilities.post_api_request(
page, KBArticlePageMessages.CREATE_NEW_KB_ARTICLE_STAGE_URL, data=form_data
)
print(response)
self.utilities.navigate_to_link(response.url)

first_revision_id = self.kb_article_show_history_page.get_last_revision_id()
if approve_revision:
self.approve_kb_revision(first_revision_id)

return {"article_title": kb_title,
"article_content": kb_article_test_data["article_content"],
"article_slug": slug,
"keyword": kb_article_test_data["keywords"],
"search_results_summary": kb_article_test_data["search_result_summary"],
"article_url": response.url.removesuffix("/history"),
"article_show_history_url": self.utilities.get_page_url(),
"first_revision_id": first_revision_id,
"article_review_description": kb_article_test_data["changes_description"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@ def fill_needs_change_textarea(self, text: str):

def click_on_save_changes_button(self):
self._click(self.__save_changes_button)
self._wait_for_dom_load_to_finish()
self.wait_for_page_to_load()
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def delete_all_inbox_messages_via_delete_selected_button(self, excerpt='', expec
excerpt: The excerpt of the message.
expected_url: The expected URL after deleting all the messages.
"""
self._wait_for_dom_load_to_finish()
self.wait_for_dom_to_load()
if excerpt != '':
inbox_messages_count = self._inbox_message_element_handles(excerpt)
else:
Expand Down
12 changes: 10 additions & 2 deletions playwright_tests/test_data/aaq_question.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@
},
"Firefox for Enterprise": {
"Accounts": "accounts",
"Browse": "browse",
"Installation and updates": "installation-and-updates",
"Performance and connectivity": "performance-and-connectivity",
"Privacy and security": "privacy-and-security",
"Settings": "settings",
"default_slug": "none"
"default_slug": "firefox-enterprise"
},
"Thunderbird": {
"Accessibility": "accessibility",
Expand All @@ -98,16 +100,22 @@
"Privacy and security": "privacy-and-security",
"Search, tag, and share": "search-tag-and-share",
"Settings": "settings",
"default_slug": "none"
"default_slug": "thunderbird"
},
"Thunderbird for Android": {
"Accessibility": "accessibility",
"Accounts": "accounts",
"Email and messaging": "email-and-messaging",
"Installation and updates": "installation-and-updates",
"Passwords and sign in": "passwords-and-sign-in",
"Performance and connectivity": "performance-and-connectivity",
"Privacy and security": "privacy-and-security",
"Search, tag, and share": "search-tag-and-share",
"Settings": "settings",
"default_slug": "thunderbird-android"
},
"Firefox Focus": {
"Browse": "browse",
"Installation and updates": "installation-and-updates",
"Performance and connectivity": "performance-and-connectivity",
"Privacy and security": "privacy-and-security",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import allure
import pytest
from pytest_check import check

from playwright.sync_api import expect, TimeoutError, Page
from playwright_tests.core.utilities import Utilities
from playwright_tests.messages.ask_a_question_messages.AAQ_messages.aaq_widget import (
Expand All @@ -13,7 +12,9 @@

# C890370, C890374
@pytest.mark.productSolutionsPage
def test_featured_articles_redirect(page: Page):
def test_featured_articles_redirect(page: Page, is_chromium):
if is_chromium:
pytest.skip("Skipping this test for chromium browser")
utilities = Utilities(page)
sumo_pages = SumoPages(page)
with allure.step("Accessing the contact support page via the top navbar Get Help > "
Expand Down
6 changes: 3 additions & 3 deletions playwright_tests/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def navigate_to_homepage(page: Page):
object.
"""
utilities = Utilities(page)
# Set default navigation timeout to 2 minutes.
page.set_default_navigation_timeout(120000)
# Set default navigation timeout to 30 seconds.
page.set_default_navigation_timeout(30000)

# Block pontoon requests in the current page context.
page.route("**/pontoon.mozilla.org/**", utilities.block_request)
Expand All @@ -36,7 +36,7 @@ def handle_502_error(response):
page.context.on("response", handle_502_error)

# Navigate to the SUMO stage homepage.
page.goto(HomepageMessages.STAGE_HOMEPAGE_URL)
utilities.navigate_to_link(HomepageMessages.STAGE_HOMEPAGE_URL)

return page

Expand Down
Loading