Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit d697853

Browse files
author
Serhii Khalymon
authored
Bugfix/running with mobile device (#30)
* Fix issue during scaling on mobile * Fix issue with logging * Fix blank screenshots from mobile device * Update style * Specify pytest version below 4 * Add mobile platforms tests to test runner * Add more ignores to flake8
1 parent 89fda29 commit d697853

File tree

12 files changed

+142
-75
lines changed

12 files changed

+142
-75
lines changed

applitools/core/eyes_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ def set_viewport_size(driver, viewport_size):
228228
pass
229229

230230
@abc.abstractmethod
231-
def _assign_viewport_size(self):
231+
def _ensure_viewport_size(self):
232232
# type: () -> None
233233
"""
234234
Assign the viewport size we need to be in the default content frame.
@@ -502,7 +502,7 @@ def _create_start_info(self):
502502
def _start_session(self):
503503
# type: () -> None
504504
logger.debug("_start_session()")
505-
self._assign_viewport_size()
505+
self._ensure_viewport_size()
506506

507507
# initialization of Eyes parameters if empty from ENV variables
508508
if not self.branch_name:

applitools/core/scaling.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ def update_scale_ratio(self, image_to_scale_width):
7070
logger.info('Calculating the scale ratio..')
7171
self._scale_ratio = 1.0 / self.device_pixel_ratio
7272
if self.is_mobile_device:
73+
# FIXME: This code should execute cause strange bugs with calculation
7374
logger.info('Mobile device, so using 2 step calculation for scale ration...')
74-
logger.info('Scale ratio based on DRP: ' + self._scale_ratio)
75+
logger.info('Scale ratio based on DRP: ' + str(self._scale_ratio))
7576
self._scale_ratio = self.get_scale_ratio_to_viewport(viewport_width,
7677
image_to_scale_width,
7778
self._scale_ratio)

applitools/selenium/capture/eyes_webdriver_screenshot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
from applitools.core import EyesScreenshot, EyesError, Point, Region, OutOfBoundsError
99
from applitools.utils import image_utils
10+
from applitools.selenium import eyes_selenium_utils
1011

1112
if tp.TYPE_CHECKING:
1213
from PIL import Image
13-
1414
from applitools.utils.custom_types import ViewPort
15-
from applitools.selenium import EyesWebDriver, eyes_selenium_utils
15+
from applitools.selenium import EyesWebDriver
1616

1717

1818
class EyesWebDriverScreenshot(EyesScreenshot):

applitools/selenium/eyes.py

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ def _get_environment(self):
132132
logger.info("Setting OS: " + os)
133133
else:
134134
logger.info('No mobile OS detected.')
135-
app_env = {'os': os, 'hostingApp': self.host_app,
135+
app_env = {'os': os, 'hostingApp': self.host_app,
136136
'displaySize': self._viewport_size,
137-
'inferred': self._get_inferred_environment()}
137+
'inferred': self._get_inferred_environment()}
138138
return app_env
139139

140140
def get_driver(self):
@@ -149,28 +149,10 @@ def get_viewport_size(self):
149149
"""
150150
Returns the size of the viewport of the application under test (e.g, the browser).
151151
"""
152-
return self._driver.get_viewport_size()
153-
154-
def _assign_viewport_size(self):
155-
# type: () -> None
156-
"""
157-
Assign the viewport size we need to be in the default content frame.
158-
"""
159-
original_frame_chain = self._driver.get_frame_chain()
160-
self._driver.switch_to.default_content()
161-
try:
162-
if self._viewport_size:
163-
logger.debug("Assigning viewport size {0}".format(self._viewport_size))
164-
self.set_viewport_size(self._driver, self._viewport_size)
165-
else:
166-
logger.debug("No viewport size given. Extracting the viewport size from the driver...")
167-
self._viewport_size = self.get_viewport_size()
168-
logger.debug("Viewport size {0}".format(self._viewport_size))
169-
except EyesError:
170-
raise TestFailedError('Failed to assign viewport size!')
171-
finally:
172-
# Going back to the frame we started at
173-
self._driver.switch_to.frames(original_frame_chain)
152+
if self._viewport_size:
153+
return self._viewport_size
154+
self._viewport_size = self._driver.get_viewport_size()
155+
return self._viewport_size
174156

175157
def get_title(self):
176158
if self._should_get_title:
@@ -209,10 +191,11 @@ def _update_scaling_params(self):
209191
logger.info("Setting scale provider...")
210192
try:
211193
scale_provider = ContextBasedScaleProvider(
212-
top_level_context_entire_size=self._driver.get_entire_page_size(),
213-
viewport_size=self._driver.get_viewport_size(),
214-
device_pixel_ratio=device_pixel_ratio,
215-
is_mobile_device=eyes_selenium_utils.is_mobile_device(self._driver)) # type: ScaleProvider
194+
top_level_context_entire_size=self._driver.get_entire_page_size(),
195+
viewport_size=self.get_viewport_size(),
196+
device_pixel_ratio=device_pixel_ratio,
197+
# always False as in Java version
198+
is_mobile_device=False) # type: ScaleProvider
216199
except Exception:
217200
# This can happen in Appium for example.
218201
logger.info("Failed to set ContextBasedScaleProvider.")
@@ -284,14 +267,20 @@ def _viewport_screenshot(self, scale_provider):
284267
# type: (ScaleProvider) -> EyesWebDriverScreenshot
285268
logger.info('Viewport screenshot requested')
286269
screenshot64 = self._driver.get_screesnhot_as_base64_from_main_frame(
287-
self.seconds_to_wait_screenshot)
270+
self.seconds_to_wait_screenshot)
288271
screenshot = image_utils.image_from_bytes(base64.b64decode(screenshot64))
289272
scale_provider.update_scale_ratio(screenshot.width)
290273
pixel_ratio = 1 / scale_provider.scale_ratio
291274
if pixel_ratio != 1.0:
292275
screenshot = image_utils.scale_image(screenshot, 1.0 / pixel_ratio)
293276
return EyesWebDriverScreenshot.create_from_image(screenshot, self._driver).get_viewport_screenshot()
294277

278+
def _ensure_viewport_size(self):
279+
if self._viewport_size is None:
280+
self._viewport_size = self._driver.get_default_content_viewport_size()
281+
if not eyes_selenium_utils.is_mobile_device(self._driver):
282+
self.set_viewport_size(self._driver, self._viewport_size)
283+
295284
def open(self, driver, app_name, test_name, viewport_size=None):
296285
# type: (AnyWebDriver, tp.Text, tp.Text, tp.Optional[ViewPort]) -> EyesWebDriver
297286
if self.is_disabled:
@@ -307,6 +296,7 @@ def open(self, driver, app_name, test_name, viewport_size=None):
307296
logger.info("WARNING: driver is not a RemoteWebDriver (class: {0})".format(driver.__class__))
308297
self._driver = EyesWebDriver(driver, self, self._stitch_mode)
309298

299+
self._ensure_viewport_size()
310300
self.open_base(app_name, test_name, viewport_size)
311301

312302
return self._driver
@@ -485,5 +475,5 @@ def try_capture_dom(self):
485475
return dom_json
486476
except Exception as e:
487477
warnings.warn(
488-
'Exception raising during capturing DOM Json. Passing...\n Got next error: {}'.format(str(e)))
478+
'Exception raising during capturing DOM Json. Passing...\n Got next error: {}'.format(str(e)))
489479
return None

applitools/selenium/eyes_selenium_utils.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ def set_browser_size_by_viewport_size(driver, actual_viewport_size, required_siz
165165
browser_size = get_window_size(driver)
166166
logger.debug("Current browser size: {}".format(browser_size))
167167
required_browser_size = dict(
168-
width=browser_size['width'] + (required_size['width'] - actual_viewport_size['width']),
169-
height=browser_size['height'] + (required_size['height'] - actual_viewport_size['height'])
168+
width=browser_size['width'] + (required_size['width'] - actual_viewport_size['width']),
169+
height=browser_size['height'] + (required_size['height'] - actual_viewport_size['height'])
170170
)
171171
return set_browser_size(driver, required_browser_size)
172172

@@ -223,8 +223,8 @@ def set_viewport_size(driver, required_size):
223223
curr_height_change += height_step
224224

225225
required_browser_size = dict(
226-
width=browser_size['width'] + curr_width_change,
227-
height=browser_size['height'] + curr_height_change)
226+
width=browser_size['width'] + curr_width_change,
227+
height=browser_size['height'] + curr_height_change)
228228
if required_browser_size == last_required_browser_size:
229229
logger.info("Browser size is as required but viewport size does not match!")
230230
logger.info("Browser size: {}, Viewport size: {}".format(required_browser_size,
@@ -258,3 +258,55 @@ def set_overflow(driver, overflow):
258258
def timeout(timeout):
259259
time.sleep(timeout)
260260
yield
261+
262+
263+
def is_landscape_orientation(driver):
264+
if is_mobile_device(driver):
265+
appium_driver = get_underlying_driver(driver) # type: AppiumRemoteWebDriver
266+
267+
original_context = None
268+
try:
269+
# We must be in native context in order to ask for orientation,
270+
# because of an Appium bug.
271+
original_context = appium_driver.context
272+
if len(appium_driver.contexts) > 1 and not original_context.uppar() == 'NATIVE_APP':
273+
appium_driver.switch_to.context('NATIVE_APP')
274+
else:
275+
original_context = None
276+
except WebDriverException as e:
277+
original_context = None
278+
279+
try:
280+
orieintation = appium_driver.orientation
281+
return orieintation.lower() == 'landscape'
282+
except Exception as e:
283+
logger.debug("WARNING: Couldn't get device orientation. Assuming Portrait.")
284+
finally:
285+
if original_context is not None:
286+
appium_driver.switch_to.context(original_context)
287+
288+
return False
289+
290+
291+
def get_viewport_size_or_display_size(driver):
292+
logger.info("get_viewport_size_or_display_size()")
293+
294+
try:
295+
return get_viewport_size(driver)
296+
except Exception as e:
297+
logger.info("Failed to extract viewport size using Javascript: {}".format(str(e)))
298+
# If we failed to extract the viewport size using JS, will use the
299+
# window size instead.
300+
301+
logger.info("Using window size as viewport size.")
302+
window_size = get_window_size(driver)
303+
width = window_size['width']
304+
height = window_size['height']
305+
try:
306+
if is_landscape_orientation(driver) and height > width:
307+
height, width = width, height
308+
except WebDriverException as e:
309+
# Not every WebDriver supports querying for orientation.
310+
pass
311+
logger.info("Done! Size {:d} x {:d}".format(width, height))
312+
return dict(width=width, height=height)

applitools/selenium/webdriver.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ class EyesWebDriver(object):
195195
'desired_capabilities', 'log_types', 'name', 'page_source', 'title',
196196
'window_handles', 'switch_to', 'mobile', 'current_context', 'context',
197197
'current_activity', 'network_connection', 'available_ime_engines',
198-
'active_ime_engine', 'device_time', 'w3c', 'contexts', 'current_package']
198+
'active_ime_engine', 'device_time', 'w3c', 'contexts', 'current_package',
199+
# Appium specific
200+
'battery_info']
199201
_SETTABLE_PROPERTIES = ['orientation', 'file_detector']
200202

201203
# This should pretty much cover all scroll bars (and some fixed position footer elements :) ).
@@ -219,6 +221,7 @@ def __init__(self, driver, eyes, stitch_mode=StitchMode.Scroll):
219221
# tp.List of frames the user switched to, and the current offset, so we can properly
220222
# calculate elements' coordinates
221223
self._frames = [] # type: tp.List[EyesFrame]
224+
self._default_content_viewport_size = None # type: tp.Optional[ViewPort]
222225
self.driver_takes_screenshot = driver.capabilities.get('takesScreenshot', False)
223226

224227
# Creating the rest of the driver interface by simply forwarding it to the underlying
@@ -661,19 +664,27 @@ def get_viewport_size(self):
661664
"""
662665
return eyes_selenium_utils.get_viewport_size(self)
663666

664-
def get_default_content_viewport_size(self):
665-
# type: () -> ViewPort
667+
def get_default_content_viewport_size(self, force_query=False):
668+
# type: (bool) -> ViewPort
666669
"""
667670
Gets the viewport size.
668671
669672
:return: The viewport size of the most outer frame.
670673
"""
674+
if self._default_content_viewport_size and not force_query:
675+
return self._default_content_viewport_size
676+
671677
current_frames = self.get_frame_chain()
672678
# If we're inside a frame, then we should first switch to the most outer frame.
673-
self.switch_to.default_content()
674-
viewport_size = self.get_viewport_size()
675-
self.switch_to.frames(current_frames)
676-
return viewport_size
679+
# Optimization
680+
if current_frames:
681+
self.switch_to.default_content()
682+
self._default_content_viewport_size = eyes_selenium_utils.get_viewport_size_or_display_size(self.driver)
683+
684+
if current_frames:
685+
self.switch_to.frames(current_frames)
686+
687+
return self._default_content_viewport_size
677688

678689
def reset_origin(self):
679690
# type: () -> None

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import-order-style = pep8
2020
application-import-names = applitools
2121
max-line-length = 120
2222
max-complexity = 12
23-
ignore = F401,W503,F841,B006,E731
23+
ignore = F401,W503,F841,B006,E731,E241,E126
2424
exclude = .git,__pycache__,test,.tox,*.egg
2525

2626
[mypy]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def get_version():
6565
'towncrier',
6666
],
6767
'testing': [
68-
'pytest >= 3.0.0',
68+
'pytest < 4.0.0',
6969
'pytest-cov',
7070
'pytest-xdist',
7171
],

tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,9 @@ def driver(request, browser_config):
101101
username = os.environ.get('SAUCE_USERNAME', None)
102102
access_key = os.environ.get('SAUCE_ACCESS_KEY', None)
103103

104+
force_remote = request.config.getoption('remote')
104105
selenium_url = os.environ.get('SELENIUM_SERVER_URL', 'http://127.0.0.1:4444/wd/hub')
105-
if 'ondemand.saucelabs.com' in selenium_url:
106+
if 'ondemand.saucelabs.com' in selenium_url or force_remote:
106107
selenium_url = "https://%s:%[email protected]:443/wd/hub" % (username, access_key)
107108
logger.debug('SELENIUM_URL={}'.format(selenium_url))
108109

@@ -133,6 +134,7 @@ def pytest_addoption(parser):
133134
parser.addoption("--platform", action="store")
134135
parser.addoption("--browser", action="store")
135136
parser.addoption("--headless", action="store")
137+
parser.addoption("--remote", action="store")
136138

137139

138140
def _get_capabilities(platform_name=None, browser_name=None, headless=False):

tests/platfroms.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def get_browser_capabilities(self, browser_name, headless=False):
5454
browser_caps = options.to_capabilities() if options else {}
5555
browser_name, browser_version = [b for b in self.browsers if browser_name.lower() == b[0].lower()][0]
5656
browser_caps.update({'browserName': browser_name,
57-
'version': browser_version,
58-
'platform': self.full_name})
57+
'version': browser_version,
58+
'platform': self.full_name})
5959
if isinstance(self.extra, dict):
6060
browser_caps.update(self.extra)
6161
return browser_caps
@@ -80,17 +80,29 @@ def full_name(self):
8080
Platform(name='Linux', version='', browsers=COMMON_BROWSERS, extra=None),
8181
Platform(name='macOS', version='10.13', browsers=COMMON_BROWSERS + [('safari', 'latest')], extra=None),
8282

83-
Platform(name='iPhone', version='10.0', browsers=[], extra={
84-
"appiumVersion": "1.7.2",
85-
"deviceName": "Iphone Emulator",
83+
Platform(name='iPhone', version='11.3', browsers=[], extra={
84+
"appiumVersion": "1.9.1",
85+
"deviceName": "Iphone Emulator",
8686
"deviceOrientation": "portrait",
87-
"browserName": "Safari",
87+
"browserName": "Safari",
8888
}),
8989
Platform(name='Android', version='6.0', browsers=[], extra={
90-
"appiumVersion": "1.7.2",
91-
"deviceName": "Android Emulator",
90+
"appiumVersion": "1.9.1",
91+
"deviceName": "Android Emulator",
92+
"deviceOrientation": "portrait",
93+
"browserName": "Chrome",
94+
}),
95+
Platform(name='Android', version='7.0', browsers=[], extra={
96+
"appiumVersion": "1.9.1",
97+
"deviceName": "Android Emulator",
98+
"deviceOrientation": "portrait",
99+
"browserName": "Chrome",
100+
}),
101+
Platform(name='Android', version='8.0', browsers=[], extra={
102+
"appiumVersion": "1.9.1",
103+
"deviceName": "Samsung S9+",
92104
"deviceOrientation": "portrait",
93-
"browserName": "Browser",
105+
"browserName": "Chrome",
94106
})
95107
]
96108
SUPPORTED_PLATFORMS_DICT = {platform.full_name: platform for platform in SUPPORTED_PLATFORMS}

0 commit comments

Comments
 (0)