1818from .errorhandler import MobileErrorHandler
1919from .switch_to import MobileSwitchTo
2020from .webelement import WebElement as MobileWebElement
21+ from .imagelement import ImageElement
2122
2223from appium .webdriver .clipboard_content_type import ClipboardContentType
2324from appium .webdriver .common .mobileby import MobileBy
2627
2728from selenium .webdriver .common .by import By
2829from selenium .webdriver .support .ui import WebDriverWait
29- from selenium .common .exceptions import TimeoutException , InvalidArgumentException
30+ from selenium .common .exceptions import (TimeoutException ,
31+ WebDriverException , InvalidArgumentException , NoSuchElementException )
3032
3133from selenium .webdriver .remote .command import Command as RemoteCommand
3234
3335import base64
3436import copy
3537
38+ DEFAULT_MATCH_THRESHOLD = 0.5
39+
3640# From remote/webdriver.py
3741_W3C_CAPABILITY_NAMES = frozenset ([
3842 'acceptInsecureCerts' ,
@@ -84,7 +88,9 @@ def _make_w3c_caps(caps):
8488 always_match ['moz:firefoxOptions' ] = new_opts
8589 return {'firstMatch' : [{}], 'alwaysMatch' : always_match }
8690
91+
8792class WebDriver (webdriver .Remote ):
93+
8894 def __init__ (self , command_executor = 'http://127.0.0.1:4444/wd/hub' ,
8995 desired_capabilities = None , browser_profile = None , proxy = None , keep_alive = False ):
9096
@@ -102,6 +108,7 @@ def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
102108 By .IOS_CLASS_CHAIN = MobileBy .IOS_CLASS_CHAIN
103109 By .ANDROID_UIAUTOMATOR = MobileBy .ANDROID_UIAUTOMATOR
104110 By .ACCESSIBILITY_ID = MobileBy .ACCESSIBILITY_ID
111+ By .IMAGE = MobileBy .IMAGE
105112
106113 def start_session (self , capabilities , browser_profile = None ):
107114 """
@@ -206,6 +213,9 @@ def find_element(self, by=By.ID, value=None):
206213 # elif by == By.NAME:
207214 # by = By.CSS_SELECTOR
208215 # value = '[name="%s"]' % value
216+ if by == By .IMAGE :
217+ return self .find_element_by_image (value )
218+
209219 return self .execute (RemoteCommand .FIND_ELEMENT , {
210220 'using' : by ,
211221 'value' : value })['value' ]
@@ -235,6 +245,10 @@ def find_elements(self, by=By.ID, value=None):
235245
236246 # Return empty list if driver returns null
237247 # See https://github.com/SeleniumHQ/selenium/issues/4555
248+
249+ if by == By .IMAGE :
250+ return self .find_elements_by_image (value )
251+
238252 return self .execute (RemoteCommand .FIND_ELEMENTS , {
239253 'using' : by ,
240254 'value' : value })['value' ] or []
@@ -327,6 +341,52 @@ def find_elements_by_android_uiautomator(self, uia_string):
327341 """
328342 return self .find_elements (by = By .ANDROID_UIAUTOMATOR , value = uia_string )
329343
344+ def find_element_by_image (self , png_img_path ,
345+ match_threshold = DEFAULT_MATCH_THRESHOLD ):
346+ """Finds a portion of a screenshot by an image.
347+ Uses driver.find_image_occurrence under the hood.
348+
349+ :Args:
350+ - png_img_path - a string corresponding to the path of a PNG image
351+ - match_threshold - a double between 0 and 1 below which matches will
352+ be rejected as element not found
353+
354+ :return: an ImageElement object
355+ """
356+ screenshot = self .get_screenshot_as_base64 ()
357+ with open (png_img_path , 'rb' ) as png_file :
358+ b64_data = base64 .encodestring (png_file .read ())
359+ try :
360+ res = self .find_image_occurrence (screenshot , b64_data ,
361+ threshold = match_threshold )
362+ except WebDriverException as e :
363+ if 'Cannot find any occurrences' in str (e ):
364+ raise NoSuchElementException (e )
365+ raise
366+ rect = res ['rect' ]
367+ return ImageElement (self , rect ['x' ], rect ['y' ], rect ['width' ],
368+ rect ['height' ])
369+
370+ def find_elements_by_image (self , png_img_path ,
371+ match_threshold = DEFAULT_MATCH_THRESHOLD ):
372+ """Finds a portion of a screenshot by an image.
373+ Uses driver.find_image_occurrence under the hood. Note that this will
374+ only ever return at most one element
375+
376+ :Args:
377+ - png_img_path - a string corresponding to the path of a PNG image
378+ - match_threshold - a double between 0 and 1 below which matches will
379+ be rejected as element not found
380+
381+ :return: possibly-empty list of ImageElements
382+ """
383+ els = []
384+ try :
385+ els .append (self .find_element_by_image (png_img_path , match_threshold ))
386+ except NoSuchElementException :
387+ pass
388+ return els
389+
330390 def find_element_by_accessibility_id (self , id ):
331391 """Finds an element by accessibility id.
332392
@@ -1419,4 +1479,4 @@ def _addCommands(self):
14191479 self .command_executor ._commands [Command .GET_CLIPBOARD ] = \
14201480 ('POST' , '/session/$sessionId/appium/device/get_clipboard' )
14211481 self .command_executor ._commands [Command .COMPARE_IMAGES ] = \
1422- ('POST' , '/session/$sessionId/appium/compare_images' )
1482+ ('POST' , '/session/$sessionId/appium/compare_images' )
0 commit comments