Skip to content

Commit f573a24

Browse files
committed
Adding mobile support
1 parent bfbc97b commit f573a24

30 files changed

+855
-94
lines changed

Pipfile

+8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ name = "pypi"
33
url = "https://pypi.org/simple"
44
verify_ssl = true
55

6+
[[source]]
7+
name = "test"
8+
url = "https://test.pypi.org/simple"
9+
verify_ssl = true
10+
611
[dev-packages]
712
twine = "*"
813
setuptools = "*"
914
wheel = "*"
1015
autopep8 = "*"
16+
samu-todo = "*"
1117

1218
[packages]
1319
selenium = "*"
@@ -19,6 +25,8 @@ jinja2 = "*"
1925
coverage = "*"
2026
sphinx = "*"
2127
pyunitreport = "*"
28+
appium-python-client = "*"
29+
allure-behave = "*"
2230

2331
[requires]
2432
python_version = "3.7"

Pipfile.lock

+225-25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![CircleCI](https://circleci.com/gh/malazay/samuranium.svg?style=svg)](https://circleci.com/gh/malazay/samuranium)
1+
[![CircleCI](https://circleci.com/gh/malazay/samuranium.svg?style=shield)](https://circleci.com/gh/malazay/samuranium)
22

33
# Samuranium Automation Framework
44

behave.ini

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[behave]
2+
paths =
3+
/features
4+
5+
[behave.formatters]
6+
allure = allure_behave.formatter:AllureFormatter

build_test.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
python3 setup.py sdist bdist_wheel
2-
twine upload --repository-url https://test.pypi.org/legacy/ dist/*build.sh
1+
python3 setup_test.py sdist bdist_wheel
2+
twine upload --repository-url https://test.pypi.org/legacy/ dist/*

samuranium/WebElement.py

+66-14
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import time
2+
13
from selenium.webdriver.remote.webelement import WebElement as we
24
from selenium.webdriver.common.by import By
35
from samuranium.utils.time import get_current_time
4-
from samuranium.utils.logger import Logger
56
from timeit import default_timer as timer
7+
from selenium.common.exceptions import NoSuchElementException
68

79

810
class WebElement:
911
"""
1012
Web element class
1113
"""
12-
def __init__(self, browser, selector, max_wait_time=30):
13-
self.browser = browser
14-
self.logger = Logger() # In the future this will be obtained from the core class so there is only 1 logger instance
14+
def __init__(self, samuranium, selector, exact_selector=False, max_wait_time=None):
15+
self.samuranium = samuranium
16+
self.browser = self.samuranium.browser
17+
self.logger = self.samuranium.logger
1518
self.selector = selector
16-
self.max_wait_time = max_wait_time
19+
self.exact_selector = exact_selector
20+
self.max_wait_time = max_wait_time if max_wait_time else float(self.samuranium.config.default_wait_time)
1721

1822
@property
1923
def element(self):
@@ -27,21 +31,66 @@ def element(self):
2731
def text(self):
2832
return self.element.text
2933

34+
def is_present(self):
35+
return self.element is not None
36+
37+
38+
def ensure_element_exists(self):
39+
if not self.is_present():
40+
raise NoSuchElementException('Element with selector "{}" was not found after {} seconds'.
41+
format(self.selector, self.max_wait_time))
42+
3043
def __finder_strategies(self):
3144
return {'xpath': By.XPATH, 'css': By.CSS_SELECTOR, 'id': By.ID, 'class_name': By.CLASS_NAME,
3245
'link_text': By.LINK_TEXT, 'name': By.NAME}
3346

47+
def __xpath_strategies(self):
48+
return {'match_xpath': '{}','exact_text': '//*[text()="{}"]',
49+
'normalize_text': '//*[text()[normalize-space()="{}"]]'}
50+
51+
def __css_strategies(self):
52+
return {'match_css': '{}', 'class_name': '.{}', 'id': '#{}'}
53+
3454
def __find_element(self):
3555
start_time = get_current_time()
3656
while timer() - start_time < self.max_wait_time:
3757
for strategy_name, method in self.__finder_strategies().items():
3858
try:
39-
element: we = self.browser.find_element(method, self.selector)
40-
return element
59+
if strategy_name == 'xpath':
60+
element: we = self.__find_by_xpath()
61+
if element:
62+
return element
63+
elif strategy_name == 'css':
64+
element: we = self.__find_by_css_selector()
65+
if element:
66+
return element
67+
if not self.exact_selector:
68+
element: we = self.browser.find_element(method, self.selector)
69+
return element
4170
except:
4271
pass
4372
return None
4473

74+
def __find_by_xpath(self):
75+
for finder_name, xpath_strategy in self.__xpath_strategies().items():
76+
try:
77+
if self.exact_selector:
78+
return self.browser.find_element_by_xpath(self.selector)
79+
return self.browser.find_element_by_xpath(xpath_strategy.format(self.selector))
80+
except NoSuchElementException:
81+
pass
82+
return None
83+
84+
def __find_by_css_selector(self):
85+
for finder_name, css_stragegy in self.__css_strategies().items():
86+
try:
87+
if self.exact_selector:
88+
return self.browser.find_element_by_css_selector(self.selector)
89+
return self.browser.find_element_by_css_selector(css_stragegy.format(self.selector))
90+
except NoSuchElementException:
91+
pass
92+
return None
93+
4594
def is_displayed(self):
4695
return self.element.is_displayed()
4796

@@ -55,11 +104,14 @@ def click(self):
55104
return False
56105

57106
def input_text(self, text):
58-
try:
59-
self.element.send_keys(text)
60-
return True
61-
except Exception as e:
62-
self.logger.error('Not possible to send text {} to element with selector {}'.format(
63-
text, self.selector), e)
64-
return False
107+
for _ in range(5):
108+
try:
109+
self.element.send_keys(text)
110+
return True
111+
except Exception as e:
112+
self.logger.error('Not possible to send text {} to element with selector {}'.format(
113+
text, self.selector), e)
114+
self.logger.debug("Waiting 1 second until element is interactable")
115+
time.sleep(1)
116+
return False
65117

samuranium/__init__.py

+55-11
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
1+
import datetime
2+
import os
3+
14
from selenium import webdriver
25
from selenium.webdriver.common.keys import Keys
36
from webdriver_manager.chrome import ChromeDriverManager
47
from webdriver_manager.firefox import GeckoDriverManager
58
from samuranium.WebElement import WebElement
9+
from samuranium.mobile import Mobile
10+
from samuranium.utils.config import Config
611
from samuranium.utils.logger import Logger
712
from samuranium.reporter import Reporter
13+
from samuranium.utils.paths import REPORT_SCREENSHOTS_PATH
14+
815

916
class Samuranium:
1017
"""
1118
Main class for Samuranium
1219
1320
This is called when 'import Samuranium from samuraniun'
1421
"""
15-
def __init__(self, selected_browser='chrome'):
22+
23+
def __init__(self, context=None, selected_browser=None):
1624
"""
1725
Initialization function
1826
Args:
1927
selected_browser: a string expecting browser name. Ex: 'chrome', 'firefox'
2028
"""
21-
self.selected_browser = selected_browser
29+
self.context = context
30+
self.config = Config(context)
31+
self.selected_browser = selected_browser if selected_browser else self.config.browser
2232
self.browser = self.__set_browser()
2333
self.logger = Logger()
2434
self.reporter = Reporter()
2535
self.selected_element = None
36+
if self.context:
37+
context.samuranium = self
38+
context.browser = self.browser
2639

2740
def get_browser(self):
2841
"""
@@ -41,7 +54,11 @@ def __set_browser(self):
4154
Returns: Webdriver element
4255
4356
"""
44-
return {'chrome': self.__get_chrome, 'firefox': self.__get_firefox}.get(self.selected_browser)()
57+
return {'chrome': self.__get_chrome, 'firefox': self.__get_firefox,
58+
'android-browser': self.__get_android_browser}.get(self.selected_browser)()
59+
60+
def __get_android_browser(self):
61+
return Mobile(self).get_driver()
4562

4663
def __get_chrome(self):
4764
"""
@@ -84,25 +101,28 @@ def wait_for_element(self, selector):
84101
Returns: Boolean for element.is_displayed()
85102
86103
"""
87-
self.selected_element = WebElement(self.browser, selector)
88-
if not self.selected_element:
89-
raise Exception("Element not found") # Todo handle this exception properly :D
104+
self.selected_element = WebElement(self, selector)
105+
self.selected_element.ensure_element_exists()
90106
return self.selected_element.is_displayed()
91107

92-
def find_element(self, selector):
108+
def find_element(self, selector, exact_selector=False):
93109
"""
94110
Finds an element by the given selector
95111
96112
Sets the element to self.selected_element so you can use it before action methods like click_on_element
113+
114+
Warnings:
115+
Triggers a NoSuchElementException if not found
97116
Args:
98117
selector: Any valid selector
99118
100119
Returns: WebElement
101120
"""
102-
self.selected_element: WebElement = WebElement(self.browser, selector)
121+
self.selected_element: WebElement = WebElement(self, selector, exact_selector=exact_selector)
122+
self.selected_element.ensure_element_exists()
103123
return self.selected_element
104124

105-
def click_on_element(self, selector=None):
125+
def click_on_element(self, selector=None, exact_selector=False):
106126
"""
107127
Clicks on an element by a given selector
108128
If no selector is passed, it will try to click on self.selected_element, set by find_element for instance
@@ -111,7 +131,7 @@ def click_on_element(self, selector=None):
111131
selector: Any valid selector
112132
"""
113133
if selector:
114-
self.selected_element = WebElement(self.browser, selector)
134+
self.find_element(selector, exact_selector)
115135
self.selected_element.click()
116136

117137
def input_text_on_element(self, text, selector=None):
@@ -123,7 +143,7 @@ def input_text_on_element(self, text, selector=None):
123143
selector: (optional) Any valid selector.
124144
"""
125145
if selector:
126-
self.selected_element = WebElement(self.browser, selector)
146+
self.find_element(selector)
127147
self.selected_element.input_text(text)
128148

129149
def press_enter_key(self):
@@ -153,9 +173,33 @@ def press_backspace_key(self, times=1):
153173
for _ in range(0, times):
154174
self.selected_element.input_text(Keys.BACKSPACE)
155175

176+
def save_screenshot(self, name=None, output_folder=None):
177+
if not output_folder:
178+
output_folder = REPORT_SCREENSHOTS_PATH
179+
if not name:
180+
name = 'randomvalue.png'
181+
if not name.endswith('.png'):
182+
name = '{}.png'.format(name)
183+
screenshot_path = os.path.join(output_folder, name)
184+
os.makedirs(output_folder, exist_ok=True)
185+
self.browser.save_screenshot(screenshot_path)
186+
187+
def save_error_screenshot(self, step):
188+
output_folder = REPORT_SCREENSHOTS_PATH
189+
os.makedirs(output_folder, exist_ok=True)
190+
screenshot_name = '{}.png'.format(str(datetime.datetime.now().strftime("%Y%m%d_%H-%M-%S")))
191+
screenshot_path = os.path.join(output_folder, screenshot_name)
192+
self.browser.save_screenshot(screenshot_path)
193+
step.screenshot = screenshot_path
194+
195+
156196
def tear_down(self):
157197
"""
158198
Closes the browser and sets self.browser = None
159199
"""
200+
try:
201+
os.environ.pop('VERSION')
202+
except Exception:
203+
pass
160204
self.browser.quit()
161205
self.browser = None

samuranium/__main__.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from samuranium.runner import samuranium_runner
2+
3+
if __name__ == '__main__':
4+
samuranium_runner()
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import os
2+
import time
3+
4+
import requests
5+
6+
from samuranium.utils.general import get_random_port, run_command
7+
from webdriver_manager.chrome import ChromeDriverManager
8+
9+
10+
class AppiumServerManager:
11+
def __init__(self, samuranium):
12+
self.config = samuranium.config
13+
self.appium_command = 'appium'
14+
self.port = None
15+
self.logs_port = None
16+
self.process = None
17+
self.pid = None
18+
self.chromedriver_version = self.config.get_property('mobile', 'chromedriver')
19+
20+
21+
def is_running(self):
22+
try:
23+
_status_request = requests.get('http://127.0.0.1:{}/wd/hub/status'.format(self.port))
24+
return _status_request.status_code == 200 and _status_request.json().get('status') == 0
25+
except:
26+
return False
27+
28+
@property
29+
def chromedriver_path(self):
30+
if self.chromedriver_version:
31+
os.environ['VERSION'] = self.chromedriver_version
32+
return ChromeDriverManager().install()
33+
return None
34+
35+
def _wait_until_ready(self):
36+
max_wait_time = 0
37+
while not self.is_running() and max_wait_time <= 30:
38+
time.sleep(5)
39+
print("Server started")
40+
41+
def start(self):
42+
self.port = get_random_port()
43+
self.logs_port = get_random_port()
44+
command = '{} -p {} -G 0.0.0.0:{} '.format(self.appium_command, self.port, self.logs_port)
45+
if self.chromedriver_path:
46+
command = '{} --chromedriver-executable {}'.format(command, self.chromedriver_path)
47+
self.process = run_command(command)
48+
self.pid = self.process.pid
49+
self._wait_until_ready()
50+
51+
def stop(self):
52+
self.process.kill()
53+
self.process = None
54+
self.pid = None

0 commit comments

Comments
 (0)