Skip to content

Commit 5128614

Browse files
authored
Bc/new fxa (#88)
* new model script * fix up new_model, add basic fxa test * add more fixtures, POM * test up to confirming account in browser * finish test, add some additional helpers * docs for new_model.py * update unstable test; lint * lint * mark unstable * documentation * doc; test unstable
1 parent 27d2008 commit 5128614

16 files changed

+457
-27
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pytest-html = "*"
1515
pypom = "*"
1616
jsonpath-ng = "*"
1717
pillow = "*"
18+
pyfxa = "*"
1819

1920
[dev-packages]
2021
werkzeug = "*"

modules/browser_object_panel_ui.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,30 @@ def navigate_to_about_addons(self):
6565
self.select_panel_setting("customize-toolbar")
6666
with self.driver.context(self.driver.CONTEXT_CHROME):
6767
self.get_element("manage-themes").click()
68+
69+
def click_sync_sign_in_button(self) -> BasePage:
70+
"""
71+
Click FxA sync button.
72+
"""
73+
with self.driver.context(self.driver.CONTEXT_CHROME):
74+
self.open_panel_menu()
75+
self.select_panel_setting("fxa-sign-in")
76+
return self
77+
78+
def manage_fxa_account(self) -> BasePage:
79+
"""
80+
Open the FxA management flow.
81+
"""
82+
with self.driver.context(self.driver.CONTEXT_CHROME):
83+
self.click_sync_sign_in_button()
84+
self.get_element("fxa-manage-account-button").click()
85+
return self
86+
87+
def confirm_sync_in_progress(self) -> BasePage:
88+
"""
89+
Check that FxA Sync Label is set to "Syncing…"
90+
"""
91+
with self.driver.context(self.driver.CONTEXT_CHROME):
92+
self.click_sync_sign_in_button()
93+
self.element_has_text("fxa-sync-label", "Syncing")
94+
return self

modules/browser_object_tabbar.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
from typing import Union
33

4-
from selenium.common.exceptions import NoSuchElementException, TimeoutException
4+
from selenium.common.exceptions import NoSuchElementException
55
from selenium.webdriver import Keys
66
from selenium.webdriver.common.by import By
77
from selenium.webdriver.remote.webelement import WebElement
@@ -237,12 +237,3 @@ def get_bar_y():
237237
self.actions.move_by_offset(0, (sign * pixels))
238238
self.actions.release()
239239
self.actions.perform()
240-
241-
def wait_for_num_tabs(self, num_tabs: int) -> BasePage:
242-
"""
243-
Waits for the driver.window_handles to be updated accordingly with the number of tabs requested
244-
"""
245-
try:
246-
self.wait.until(lambda _: len(self.driver.window_handles) == num_tabs)
247-
except TimeoutException:
248-
logging.warn("Timeout waiting for the number of windows to be:", num_tabs)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"login-email-input": {
3+
"selectorData": "input[type='email']",
4+
"strategy": "css",
5+
"groups": [
6+
"requiredForPage"
7+
]
8+
},
9+
10+
"submit-button": {
11+
"selectorData": "button[type='submit']",
12+
"strategy": "css",
13+
"groups": [
14+
"doNotCache"
15+
]
16+
},
17+
18+
"password-input": {
19+
"selectorData": "[data-testid='new-password-input-field']",
20+
"strategy": "css",
21+
"groups": []
22+
},
23+
24+
"password-repeat-input": {
25+
"selectorData": "[data-testid='verify-password-input-field']",
26+
"strategy": "css",
27+
"groups": []
28+
},
29+
30+
"age-input": {
31+
"selectorData": "[data-testid='age-input-field']",
32+
"strategy": "css",
33+
"groups": []
34+
},
35+
36+
"card-header": {
37+
"selectorData": "card-header",
38+
"strategy": "class",
39+
"groups": []
40+
},
41+
42+
"otp-input": {
43+
"selectorData": "[data-testid='confirm-signup-code-input-field']",
44+
"strategy": "css",
45+
"groups": []
46+
},
47+
48+
"connected-heading": {
49+
"selectorData": "fxa-connected-heading",
50+
"strategy": "id",
51+
"groups": []
52+
},
53+
54+
"continue-browsing-link": {
55+
"selectorData": "cad-not-now",
56+
"strategy": "id",
57+
"groups": []
58+
},
59+
60+
"login-password-input": {
61+
"selectorData": "input#password",
62+
"strategy": "css",
63+
"groups": []
64+
},
65+
66+
"sign-in-button": {
67+
"selectorData": "use-logged-in",
68+
"strategy": "id",
69+
"groups": []
70+
}
71+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"body": {
3+
"selectorData": "body",
4+
"strategy": "tag",
5+
"groups": []
6+
}
7+
}

modules/data/panel_ui.components.json

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,40 @@
44
"selectorData": "PanelUI-menu-button",
55
"strategy": "id",
66
"groups": [
7-
"requiredForPage"
7+
"requiredForPage",
8+
"doNotCache"
9+
]
10+
},
11+
12+
"sync-user-button": {
13+
"selectorData": "toolbarbutton[id='fxa-toolbar-menu-button']",
14+
"strategy": "css",
15+
"groups": []
16+
},
17+
18+
"fxa-manage-account-button": {
19+
"selectorData": "fxa-manage-account-button",
20+
"strategy": "id",
21+
"groups": []
22+
},
23+
24+
"sync-fxa": {
25+
"selectorData": "appMenu-fxa-status2",
26+
"strategy": "id",
27+
"groups": []
28+
},
29+
30+
"fxa-sign-in": {
31+
"selectorData": "#appMenu-fxa-status2 toolbarbutton",
32+
"strategy": "css",
33+
"groups": []
34+
},
35+
36+
"fxa-sync-label": {
37+
"selectorData": "syncnow-label",
38+
"strategy": "class",
39+
"groups": [
40+
"doNotCache"
841
]
942
},
1043

modules/page_base.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from selenium.common.exceptions import NoSuchElementException, TimeoutException
1212
from selenium.webdriver import ActionChains, Firefox
1313
from selenium.webdriver.common.by import By
14+
from selenium.webdriver.common.keys import Keys
1415
from selenium.webdriver.remote.webelement import WebElement
1516
from selenium.webdriver.support import expected_conditions as EC
1617
from selenium.webdriver.support.wait import WebDriverWait
@@ -100,6 +101,16 @@ def set_content_context(self):
100101
if self._xul_source_snippet in self.driver.page_source:
101102
self.driver.set_context(self.driver.CONTEXT_CONTENT)
102103

104+
def custom_wait(self, **kwargs) -> WebDriverWait:
105+
"""
106+
Create a custom WebDriverWait object, refer to Selenium docs
107+
for explanations of the arguments.
108+
Examples:
109+
self.custom_wait(timeout=45).until(<condition>)
110+
self.custom_wait(poll_frequency=1).until(<condition>)
111+
"""
112+
return WebDriverWait(self.driver, **kwargs)
113+
103114
def expect(self, condition) -> Page:
104115
"""Use the Page's wait object to assert a condition or wait until timeout"""
105116
logging.info("Expecting...")
@@ -231,12 +242,15 @@ def get_element(
231242
logging.info(f"Getting multiple elements by name {name}")
232243
if labels:
233244
logging.info(f"Labels: {labels}")
245+
logging.info(f"Groups: {self.elements[name]['groups']}")
234246
cache_name = name
235247
if labels:
236248
labelscode = "".join(labels)
237249
cache_name = f"{name}{labelscode}"
238250
if cache_name not in self.elements:
239251
self.elements[cache_name] = deepcopy(self.elements[name])
252+
if multiple:
253+
logging.info(f"Multiples: Not caching {cache_name}...")
240254
if not multiple and "seleniumObject" in self.elements[cache_name]:
241255
# no caching for multiples
242256
cached_element = self.elements[cache_name]["seleniumObject"]
@@ -257,6 +271,7 @@ def get_element(
257271
shadow_parent, selector, context=self.context
258272
)
259273
if "doNotCache" not in element_data["groups"]:
274+
logging.info(f"Not caching {cache_name}...")
260275
self.elements[cache_name]["seleniumObject"] = shadow_element
261276
return shadow_element
262277
else:
@@ -278,6 +293,7 @@ def get_element(
278293
if not multiple:
279294
found_element = self.driver.find_element(*selector)
280295
if "doNotCache" not in element_data["groups"]:
296+
logging.info(f"Caching {cache_name}...")
281297
self.elements[cache_name]["seleniumObject"] = found_element
282298
logging.info(f"Returning element {cache_name}.\n")
283299
return found_element
@@ -367,6 +383,43 @@ def url_contains(self, url_part: str) -> Page:
367383
self.expect(EC.url_contains(url_part))
368384
return self
369385

386+
def fill(
387+
self, name: str, term: str, clear_first=True, press_enter=True, labels=[]
388+
) -> Page:
389+
"""
390+
Get a fillable element and fill it with text. Return self.
391+
392+
...
393+
394+
Arguments
395+
---------
396+
397+
name: str
398+
The key of the entry in self.elements, parsed from the elements JSON
399+
400+
labels: list[str]
401+
Strings that replace instances of {.*} in the "selectorData" subentry of
402+
self.elements[name]
403+
404+
term: str
405+
The text to enter into the element
406+
407+
clear_first: bool
408+
Call .clear() on the element first. Default True
409+
410+
press_enter: bool
411+
Press Keys.ENTER after filling the element. Default True
412+
"""
413+
if self.context == "chrome":
414+
self.set_chrome_context()
415+
el = self.get_element(name, labels=labels)
416+
self.element_clickable(name, labels=labels)
417+
if clear_first:
418+
el.clear()
419+
end = Keys.ENTER if press_enter else ""
420+
el.send_keys(f"{term}{end}")
421+
return self
422+
370423
def multi_click(
371424
self, iters: int, reference: Union[str, tuple, WebElement], labels=[]
372425
) -> Page:
@@ -415,6 +468,16 @@ def context_click_element(self, element: WebElement) -> Page:
415468
self.actions.context_click(element).perform()
416469
return self
417470

471+
def wait_for_num_tabs(self, num_tabs: int) -> Page:
472+
"""
473+
Waits for the driver.window_handles to be updated accordingly with the number of tabs requested
474+
"""
475+
try:
476+
self.wait.until(lambda _: len(self.driver.window_handles) == num_tabs)
477+
except TimeoutException:
478+
logging.warn("Timeout waiting for the number of windows to be:", num_tabs)
479+
return self
480+
418481
def hide_popup(self, context_menu: str, chrome=False) -> Page:
419482
"""
420483
Given the ID of the context menu, it will dismiss the menu.

modules/page_object.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from modules.page_object_autofill_credit_card import *
1111
from modules.page_object_autofill_test_basic import *
1212
from modules.page_object_example_page import *
13-
from modules.page_object_generic_pdf import *
13+
from modules.page_object_fxa_new_account import *
14+
from modules.page_object_generics import *
1415
from modules.page_object_google_search import *
1516
from modules.page_object_wiki_firefox_logo import *
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from modules.page_base import BasePage
2+
3+
4+
class FxaNewAccount(BasePage):
5+
"""
6+
Page Object Model for FxA signup flow.
7+
Initialize with fxa_url=<the url of the FxA instance>
8+
"""
9+
10+
URL_TEMPLATE = "{fxa_url}"
11+
12+
def sign_up_sign_in(self, email: str) -> BasePage:
13+
"""From the entry point, enter email to sign up or sign in"""
14+
self.fill("login-email-input", email, press_enter=False)
15+
self.get_element("submit-button").click()
16+
return self
17+
18+
def create_new_account(self, password: str, age=30) -> BasePage:
19+
"""Fill out the password and age fields, then submit and wait for code"""
20+
self.fill("password-input", password, press_enter=False)
21+
self.fill("password-repeat-input", password, press_enter=False)
22+
self.fill("age-input", str(age), press_enter=False)
23+
self.get_element("submit-button").click()
24+
self.element_has_text("card-header", "Enter confirmation code")
25+
return self
26+
27+
def confirm_new_account(self, otp: str) -> BasePage:
28+
"""Given an OTP, confirm the account, submit, and wait for account activation"""
29+
self.fill("otp-input", otp, press_enter=False)
30+
self.get_element("submit-button").click()
31+
self.element_exists("connected-heading")
32+
return self
33+
34+
def finish_account_setup(self, password: str) -> BasePage:
35+
"""Walk through the 'Finish Account Setup' flow"""
36+
self.wait_for_num_tabs(2)
37+
self.driver.switch_to.window(self.driver.window_handles[-1])
38+
self.fill("login-password-input", password, press_enter=False)
39+
self.get_element("submit-button").click()
40+
return self

modules/page_object_generic_pdf.py renamed to modules/page_object_generics.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from modules.page_base import BasePage
22

33

4+
class GenericPage(BasePage):
5+
"""
6+
Generic POM for a page we don't care to map
7+
"""
8+
9+
URL_TEMPLATE = "{url}"
10+
11+
412
class GenericPdf(BasePage):
513
"""
614
Generic POM for any page with an open PDF in it.

0 commit comments

Comments
 (0)