Using WebIDE to Define Locators
WebIDE can connect to firefox OS devices, and lets the user to view the locators of Firefox OS app. For basics of WebIDE, please refer to this page.
- From the phone, go to Settings -> Developer and select "ADB and DevTools" under "Debugging via USB"
- Connect the phone to PC via USB.
- From WebIDE, click 'Select Runtime' and select the phone under "USB Devices".
- From the device, allow USB connection.
- From the upper right corner, select the app that you wish to view. If no apps are shown in the list, go to Runtime -> Runtime Info drop-down menu, and click "Request Higher Privileges" button. The phone will restart, and you should be able to see all apps loaded on the phone.
Finding locators in Apps:
When an app is selected from WebIDE, it will automatically open on the device as well. Select the pick button on WebIDE, (left of inspector tab), and when the pick button is highlighted, tap the element that you wish to locate. Corresponding line would be highlighted.
- For example, if you see <progress id="crop-view" class="skin-dark" ..., you can set your locator as: _locator = (By.ID, "crop-view").
- If you're looking for a sub-attribute, for example, data-l10n-id="back-button" then you can set your locator as _locator = (By.CSS_SELECTOR, '[data-l10n-id="back-button"]')
The WebIDE shows the css source in real time, so you should be able to see the changes in value after UI interactions.
Finding locators on System level:
Some of the dialogs or windows actually belongs to the System level, even when they are instantiated from pressing a button in an app. If tapping the UI element does not highlight the element, try switching the app to 'System' and tap it again. Remember, if you're doing this, you also have to switch to the system frame by calling self.marionette.switch_to_frame(), and switch back to the app frame once you return to the app window.
Finding locators in Browser:
There is a difference between Browser and Browser Tabs. When you select Browser on WebIDE, you're checking the main view of the Browser app only. In order to check the actual content of an URL, you have to select the corresponding tab. Pick the tab listed under the 'Tab' heading at the bottom of the app list in WebIDE.
There are cases where you see it on WebIDE, but Marionette can't find them. In such case, it's usually caused by one of the following reasons:
- Marionette is on the wrong app frame
- Marionette is on the wrong shadow DOM context
- (In case of Browser) Marionette is on the wrong Search window context
Use following references as guides.
Handling shadow DOM
What is shadow DOM?
Basically, shadow DOM enables to encapsulate a DOM subtree in a document, without exposing its inner working to the main DOM tree. This makes marionette unable to see the elements under the shadow DOM, even if you can see it on WebIDE. For a concise explanation of shadow DOM, refer to this page.
Python Marionette Methods
Python Marionette handles shadow DOM issues by changing the frame with the command switch_to_shadow_root(). What this command does is to change the context into the shadow DOM, so you can view the elements within. Once you're done with shadow DOM elements, you have to return to the standard app frame by either switch_to_shadow_root() call without parameter, or self.apps.switch_to_displayed_app()
Note that a shadow DOMs can be nested, in which case you have to call switch_to_shadow_root() multiple times. Refer to this comment for such case.
It's not always straightforward to see which ones are shadow DOM elements. Sure way to know it is when marionette cannot find the element that is being shown on WebIDE, then one of the parent element is a shadow DOM element. Also, when you call switch_to_shadow_root() method on a non-shadow DOM element, it will throw an error.
Music helper methods contain a number of examples of shadow DOM usage.
def tap_cover_in_player_view(self): # here, we switch to the active iframe view, and then switch into the shadow DOM within # because self._rating_view_locator is within the shadow DOM self.marionette.switch_to_frame(self.marionette.find_element(*self._active_view_locator)) self.marionette.switch_to_shadow_root(self.marionette.find_element(*self._cover_image_shadow_dom_locator)) # wait until the overlay disappears, and once the check is done, get out of the shadow root # but you'll be still inside the self._active_view_locator frame Wait(self.marionette).until(expected.element_not_displayed(*self._rating_view_locator)) self.marionette.switch_to_shadow_root() # after tapping the element, re-enter into the shadow DOM, and check for the self._rating_view_locator being displayed # self.apps.switch_to_displayed_app() exits both shadow DOM and the active iframe view self.marionette.find_element(*self._cover_image_shadow_dom_locator).tap() self.marionette.switch_to_shadow_root(self.marionette.find_element(*self._cover_image_shadow_dom_locator)) Wait(self.marionette).until(expected.element_displayed( Wait(self.marionette).until(expected.element_present(*self._rating_view_locator)))) self.apps.switch_to_displayed_app()
Switching frames and System Frame
A frame is basically the context where marionette operates on. If marionette is on a frame that does not contain currently displayed UI elements, marionette will not find those UI elements, even if the user can see them on the phone at the time of the test.
System frame is a level above from app frame, where the window handle for each app or system dialog are accessible. When you're on system frame, you won't be able to access UI elements that belongs to apps.
switch_to_frame() command lets you change the frame, and you may need to do this when you need to manipulate system dialog or browser window instance.
# switch to the system frame self.marionette.switch_to_frame() # look for camera frame on the system frame secure_camera_frame = self.marionette.find_element(*self._secure_camera_frame_locator) # switch to the found camera frame self.marionette.switch_to_frame(secure_camera_frame) self.wait_for_capture_ready()
gaia_test.py has a helper app to switch to the displayed app, switch_to_displayed_app().
def switch_to_settings_app(self): # wait until settings app is displayed self.wait_to_be_displayed() # now switch to the settings app self.apps.switch_to_displayed_app()
Handling Browser Instances
As mentioned above, there is a difference between Browser and Browser Tabs. Also, the browser elements (URL bar, dialogs, buttons, etc.) are on a separate frame (system frame) from the displayed web contents.
In order to properly detect and manipulate all of them, it is necessary to switch between separate frames.
search = Search(self.marionette) search.launch() # Note that within the go_to_url() method, it switches to the system frame, # operates with the URL bar, and then returns the browser instance object browser = search.go_to_url(self.test_url) # here, it switches to the iframe of the browser, to check the web content browser.switch_to_content() Wait(self.marionette).until(lambda m: m.title == 'Mozilla') link = self.marionette.find_element(By.CSS_SELECTOR, '#community a') link.tap() Wait(self.marionette).until(lambda m: m.title == 'Mozilla Community') # need to go back to the system frame in order to tap the back button. browser.switch_to_chrome() browser.tap_back_button() # need to switch into the iframe to check the displayed content browser.switch_to_content() Wait(self.marionette).until(lambda m: m.title == 'Mozilla')
Returning the Page Object After Completing an Action
When invoking a certain UI action causes the phone to open a new page, it is recommended to return the appropriate page object.
On a related note, each page should have its own class definition, where it lists methods that manipulates and checks the associated UI elements.
music_app.wait_for_music_tiles_displayed() # tapping albums tab returns the list_view object, # because it opens the new view list_view = music_app.tap_albums_tab() # tapping the first album returns the sublist_view object sublist_view = albums.tap_first_album() # tapping the song opens the player view player_view = sublist_view.tap_first_song() # select stop player_view.tap_play()
Use Python Debugger
You can stop the execution of the script and inject custom python statements by using pdb. just add below lines where you'd like to start debugging:
import pdb pdb.set_trace()
Here is the informative tutorial regarding pdb.
Debug a non-working wait
When a wait raises an exception while you actually see something happening on the device, you can use a snippet like:
# Do something that changes the value of element(s) do_motion() for i in range(0, 20) print i, expected_end_value, element_that_is_moving.rect["y"] Wait(self.marionette).until(lambda m: expected_end_value == element_that_is_moving.rect["y"])