diff --git a/.gitignore b/.gitignore
index cc3ade1664..4c2015b620 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,3 +79,7 @@ status-dev-cli
#ios
ios/Pods
ios/StatusIm.xcworkspace
+
+#python
+*.pyc
+*.cache
diff --git a/test/appium/AppiumTests.iml b/test/appium/AppiumTests.iml
deleted file mode 100644
index 4d44f624cf..0000000000
--- a/test/appium/AppiumTests.iml
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/appium/Jenkinsfile b/test/appium/Jenkinsfile
new file mode 100644
index 0000000000..f864bf45a9
--- /dev/null
+++ b/test/appium/Jenkinsfile
@@ -0,0 +1,8 @@
+node {sauce('6269510b-13f3-4019-b156-c2c835f3a408') {
+ try {sh '/usr/local/bin/python3 -m pytest -m sanity -n5 --apk apkUrl --build ${JOB_NAME}__${BUILD_NUMBER}'
+ }
+ finally {
+ saucePublisher()
+ junit testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']], testResults: '*.xml' }
+ }
+}
diff --git a/test/appium/pom.xml b/test/appium/pom.xml
deleted file mode 100644
index 2680f725e2..0000000000
--- a/test/appium/pom.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
- 4.0.0
-
- im.status
- AppiumTests
- 1.0-SNAPSHOT
-
-
-
- io.appium
- java-client
- 5.0.0-BETA7
-
-
- org.seleniumhq.selenium
- selenium-java
- 3.3.1
-
-
- junit
- junit
- 4.12
-
-
-
- com.saucelabs
- sauce_junit
- 2.1.23
-
-
-
-
-
-
- maven-compiler-plugin
- 3.6.1
-
-
- 1.8
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.20
-
- methods
- 4
-
-
-
-
-
\ No newline at end of file
diff --git a/test/appium/pytest.ini b/test/appium/pytest.ini
new file mode 100644
index 0000000000..f6ee4716ba
--- /dev/null
+++ b/test/appium/pytest.ini
@@ -0,0 +1,3 @@
+[pytest]
+norecursedirs = .git views
+addopts = -s -v --junitxml=result.xml --tb=short
diff --git a/test/appium/requirements.txt b/test/appium/requirements.txt
new file mode 100644
index 0000000000..3579f46760
--- /dev/null
+++ b/test/appium/requirements.txt
@@ -0,0 +1,22 @@
+aiohttp==2.2.3
+allpairspy==2.3.0
+apipkg==1.4
+Appium-Python-Client==0.24
+async-timeout==1.2.1
+asyncio==3.4.3
+certifi==2017.7.27.1
+chardet==3.0.4
+execnet==1.4.1
+idna==2.5
+lxml==3.8.0
+multidict==3.1.3
+namedlist==1.7
+py==1.4.34
+pytest==3.1.3
+pytest-xdist==1.18.2
+requests==2.18.3
+sauceclient==1.0.0
+selenium==2.53.6
+six==1.10.0
+urllib3==1.22
+yarl==0.12.0
diff --git a/test/appium/src/test/java/app/StatusApp.java b/test/appium/src/test/java/app/StatusApp.java
deleted file mode 100644
index 2586ac952d..0000000000
--- a/test/appium/src/test/java/app/StatusApp.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package app;
-
-import io.appium.java_client.AppiumDriver;
-import screens.ChatScreen;
-
-
-public class StatusApp {
-
- private final AppiumDriver driver;
-
- public StatusApp(AppiumDriver driver) {
- this.driver = driver;
- }
-
- public ChatScreen ChatScreen() { return new ChatScreen(driver); }
-
-}
diff --git a/test/appium/src/test/java/screens/AbstractScreen.java b/test/appium/src/test/java/screens/AbstractScreen.java
deleted file mode 100644
index 1c853b0d24..0000000000
--- a/test/appium/src/test/java/screens/AbstractScreen.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package screens;
-
-import io.appium.java_client.AppiumDriver;
-import io.appium.java_client.MobileElement;
-import io.appium.java_client.pagefactory.AppiumFieldDecorator;
-import org.openqa.selenium.By;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.PageFactory;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.openqa.selenium.support.ui.WebDriverWait;
-
-public class AbstractScreen {
-
- protected final AppiumDriver driver;
- protected WebDriverWait wait;
-
-
- public AbstractScreen(AppiumDriver driver) {
- this.driver = driver;
- wait = new WebDriverWait(driver,45);
- PageFactory.initElements(new AppiumFieldDecorator(driver), this);
- }
-
- public MobileElement findElementWithTimeout(By by, int timeOutInSeconds) {
- return (MobileElement)(new WebDriverWait(driver, timeOutInSeconds)).until(ExpectedConditions.presenceOfElementLocated(by));
- }
-
- protected void takeScreenShot(){
- driver.getScreenshotAs(OutputType.BASE64);
- }
-}
diff --git a/test/appium/src/test/java/screens/ChatScreen.java b/test/appium/src/test/java/screens/ChatScreen.java
deleted file mode 100644
index fd57729729..0000000000
--- a/test/appium/src/test/java/screens/ChatScreen.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package screens;
-
-import io.appium.java_client.AppiumDriver;
-import io.appium.java_client.pagefactory.AndroidFindBy;
-import org.openqa.selenium.By;
-import org.openqa.selenium.InvalidSelectorException;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.ui.ExpectedConditions;
-import org.springframework.util.Assert;
-
-
-public class ChatScreen extends AbstractScreen {
-
- public ChatScreen(AppiumDriver driver){
- super(driver);
- }
-
-
- @AndroidFindBy(id="android:id/button1")
- public WebElement btnContinue;
-
- @AndroidFindBy(accessibility="request-password")
- public WebElement requestPassword;
-
- @AndroidFindBy(accessibility="chat-message-input")
- public WebElement inputChatMessage;
-
- @AndroidFindBy(accessibility="chat-send-button")
- public WebElement btnSend;
-
- @AndroidFindBy(accessibility="request-phone")
- public WebElement btnRequestPhone;
-
- @AndroidFindBy(accessibility="chat-cancel-response-button")
- public WebElement btnCancelPasswordRequest;
-
- public void continueForRootedDevice()
- {
- if (isElementPresent(By.id("android:id/button1"))) {
- btnContinue.click();
- }
- }
-
- public void createPassword(String password){
- requestPassword.click();
- for (int i=0; i<2; i++){
- inputChatMessage.sendKeys(password);
- btnSend.click();
- }
- }
-
- public void verifyPasswordIsSet(){
- //button "tap to enter phone number" is shown
- wait.until(ExpectedConditions.elementToBeClickable(btnRequestPhone));
-
- //screen contains text "Phew that was hard" TODO: replace hardcoded string "Find a bug" check
- Assert.isTrue(driver.getPageSource().contains("Find a bug"),"Text Phew that was hard is not found on screen");
- }
-
- public void verifyPasswordRequestIsVisible(){
- //button "tap to enter phone number" is shown
- wait.until(ExpectedConditions.elementToBeClickable(requestPassword));
- //screen contains text "Phew that was hard" TODO: replace hardcoded string "Welcome to Status " check
- Assert.isTrue(driver.getPageSource().contains("Welcome to Status"),"Welcome to Status is not found on screen");
- }
-
-
- public boolean isElementPresent(By locator) {
- try {
- return driver.findElements(locator).size() > 0;
- } catch (InvalidSelectorException ex) {
- throw ex;
- }
- }
-}
\ No newline at end of file
diff --git a/test/appium/src/test/java/tests/AbstractTest.java b/test/appium/src/test/java/tests/AbstractTest.java
deleted file mode 100644
index a708907aaf..0000000000
--- a/test/appium/src/test/java/tests/AbstractTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package tests;
-
-import app.StatusApp;
-import com.saucelabs.common.SauceOnDemandSessionIdProvider;
-import io.appium.java_client.android.AndroidDriver;
-import org.junit.*;
-import org.junit.rules.TestName;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.remote.RemoteWebDriver;
-import utility.AppiumDriverBuilder;
-import java.net.MalformedURLException;
-import java.net.URL;
-import com.saucelabs.junit.SauceOnDemandTestWatcher;
-import com.saucelabs.common.SauceOnDemandAuthentication;
-
-public abstract class AbstractTest implements SauceOnDemandSessionIdProvider {
-
- private AndroidDriver driver;
- protected StatusApp app;
-
- public static final String USERNAME = System.getenv("SAUCE_USERNAME");
- public static final String ACCESS_KEY = System.getenv("SAUCE_ACCESS_KEY");
- public static final String URL = "https://" + USERNAME + ":" + ACCESS_KEY + "@ondemand.saucelabs.com:443/wd/hub";
- // public String buildTag = System.getenv("JOB_NAME") + "__" + System.getenv("BUILD_NUMBER");
-
- protected String sessionId;
-
- public SauceOnDemandAuthentication authentication = new SauceOnDemandAuthentication(USERNAME, ACCESS_KEY);
-
- @Rule
- public SauceOnDemandTestWatcher resultReportingTestWatcher = new SauceOnDemandTestWatcher(this, authentication);
-
- @Rule
- public TestName name = new TestName() {
- public String getMethodName() {
- return String.format("%s", super.getMethodName());
- }
- };
-
-
- @Before
- public void SetupRemote() throws MalformedURLException {
- driver = AppiumDriverBuilder.forAndroid()
- .withEndpoint(new URL(URL))
- .build();
-
- app = new StatusApp(driver);
- this.sessionId = (((RemoteWebDriver) driver).getSessionId()).toString();
- }
-
- @After
- public void teardown(){
- //close the app
- driver.quit();
- }
-
- @Override
- public String getSessionId() {
- return sessionId;
- }
-
-}
diff --git a/test/appium/src/test/java/tests/InitialRunTest.java b/test/appium/src/test/java/tests/InitialRunTest.java
deleted file mode 100644
index ae5a0318ae..0000000000
--- a/test/appium/src/test/java/tests/InitialRunTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package tests;
-
-
-import org.junit.*;
-
-
-
-public class InitialRunTest extends AbstractTest {
-
-
- @Test
- public void canRunAppTest()
- {
- app.ChatScreen().continueForRootedDevice();
- app.ChatScreen().verifyPasswordRequestIsVisible();
- }
-
- @Test
- public void canCreatePasswordTest()
- {
-
- app.ChatScreen().continueForRootedDevice();
- app.ChatScreen().createPassword("password");
- app.ChatScreen().verifyPasswordIsSet();
- }
-
-
-}
\ No newline at end of file
diff --git a/test/appium/src/test/java/utility/AppiumDriverBuilder.java b/test/appium/src/test/java/utility/AppiumDriverBuilder.java
deleted file mode 100644
index ed42a704fa..0000000000
--- a/test/appium/src/test/java/utility/AppiumDriverBuilder.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package utility;
-
-
-import io.appium.java_client.AppiumDriver;
-import io.appium.java_client.android.AndroidDriver;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.remote.DesiredCapabilities;
-
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.sql.Timestamp;
-
-
-public abstract class AppiumDriverBuilder {
-
- public String jobName = System.getenv("JOB_NAME");
- public String buildNumber = System.getenv("BUILD_NUMBER");
- protected URL endpoint;
- DesiredCapabilities capabilities = new DesiredCapabilities();
-
- public static AndroidDriverBuilder forAndroid() throws MalformedURLException {
- return new AndroidDriverBuilder();
- }
-
- public static class AndroidDriverBuilder extends AppiumDriverBuilder {
-
-
-
- public AndroidDriver build() {
- capabilities.setCapability("appiumVersion", "1.6.3");
- capabilities.setCapability("deviceName","Samsung Galaxy S4 Emulator");
- capabilities.setCapability("deviceOrientation", "portrait");
- capabilities.setCapability("browserName", "");
- capabilities.setCapability("platformVersion","4.4");
- capabilities.setCapability("platformName","Android");
-
- jobName = (null == jobName) ? "Local run " : jobName;
- buildNumber = (null == buildNumber) ? new Timestamp(System.currentTimeMillis()).toString() : buildNumber;
- capabilities.setCapability("build", jobName + "__" + buildNumber);
-
- //read url to apk file from Jenkins property or from maven parameter
- // example of maven run: mvn -DapkUrl=http://artifacts.status.im:8081/artifactory/nightlies-local/im.status.ethereum-baebbe.apk test
- capabilities.setCapability("app", System.getProperty("apkUrl"));
- return new AndroidDriver(endpoint, capabilities);
- }
- }
-
- public SELF withEndpoint(URL endpoint) {
- this.endpoint = endpoint;
-
- return (SELF) this;
- }
-
- public abstract DRIVER build();
-
-}
-
diff --git a/test/appium/tests/__init__.py b/test/appium/tests/__init__.py
new file mode 100644
index 0000000000..63727a34d8
--- /dev/null
+++ b/test/appium/tests/__init__.py
@@ -0,0 +1,20 @@
+import asyncio
+
+
+@asyncio.coroutine
+def start_threads(amount, func, *args):
+ features = dict()
+ loop = asyncio.get_event_loop()
+ for i in range(amount):
+ features['feature_' + str(i)] = loop.run_in_executor(None, func, *args)
+ for k in features:
+ features[k] = yield from features[k]
+ return (features[k] for k in features)
+
+
+class TestData(object):
+
+ def __init__(self):
+ self.name = None
+
+tests_data = TestData()
diff --git a/test/appium/tests/basetestcase.py b/test/appium/tests/basetestcase.py
new file mode 100644
index 0000000000..2bbf643046
--- /dev/null
+++ b/test/appium/tests/basetestcase.py
@@ -0,0 +1,107 @@
+import pytest
+import sys
+from tests import *
+from os import environ
+from appium import webdriver
+from abc import ABCMeta, \
+ abstractmethod
+import hmac
+from hashlib import md5
+
+
+class AbstractTestCase:
+
+ __metaclass__ = ABCMeta
+
+ @property
+ def sauce_access_key(self):
+ return environ.get('SAUCE_ACCESS_KEY')
+
+ @property
+ def sauce_username(self):
+ return environ.get('SAUCE_USERNAME')
+
+ @property
+ def executor_sauce_lab(self):
+ return 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (self.sauce_username, self.sauce_access_key)
+
+ @property
+ def capabilities_sauce_lab(self):
+
+ desired_caps = dict()
+ desired_caps['platformName'] = 'Android'
+ desired_caps['appiumVersion'] = '1.6.5'
+ desired_caps['platformVersion'] = '6.0'
+ desired_caps['deviceName'] = 'Android GoogleAPI Emulator'
+ desired_caps['app'] = pytest.config.getoption('apk')
+ desired_caps['browserName'] = ''
+ desired_caps['deviceOrientation'] = "portrait"
+ desired_caps['name'] = tests_data.name
+ desired_caps['build'] = pytest.config.getoption('build')
+ desired_caps['idleTimeout'] = 500
+ return desired_caps
+
+ def get_public_url(self, driver):
+ token = hmac.new(bytes(self.sauce_username + ":" + self.sauce_access_key, 'latin-1'),
+ bytes(driver.session_id, 'latin-1'), md5).hexdigest()
+ return "https://saucelabs.com/jobs/%s?auth=%s" % (driver.session_id, token)
+
+ def print_sauce_lab_info(self, driver):
+ sys.stdout = sys.stderr
+ print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id,
+ pytest.config.getoption('build')))
+ print(self.get_public_url(driver))
+
+ @property
+ def executor_local(self):
+ return 'http://localhost:4723/wd/hub'
+
+ @property
+ def capabilities_local(self):
+ desired_caps = dict()
+ desired_caps['deviceName'] = 'takoe'
+ desired_caps['platformName'] = 'Android'
+ desired_caps['appiumVersion'] = '1.6.5'
+ desired_caps['platformVersion'] = '6.0'
+ desired_caps['app'] = pytest.config.getoption('apk')
+ return desired_caps
+
+ @abstractmethod
+ def setup_method(self, method):
+ raise NotImplementedError('Should be overridden from a child class')
+
+ @abstractmethod
+ def teardown_method(self, method):
+ raise NotImplementedError('Should be overridden from a child class')
+
+
+class SingleDeviceTestCase(AbstractTestCase):
+
+ def setup_method(self, method):
+ self.driver = webdriver.Remote(self.executor_sauce_lab,
+ self.capabilities_sauce_lab)
+ self.driver.implicitly_wait(10)
+
+ def teardown_method(self, method):
+ self.print_sauce_lab_info(self.driver)
+ self.driver.quit()
+
+
+class MultiplyDeviceTestCase(AbstractTestCase):
+
+ def setup_method(self, method):
+
+ loop = asyncio.get_event_loop()
+ self.driver_1, \
+ self.driver_2 = loop.run_until_complete(start_threads(2,
+ webdriver.Remote,
+ self.executor_sauce_lab,
+ self.capabilities_sauce_lab))
+ loop.close()
+ for driver in self.driver_1, self.driver_2:
+ driver.implicitly_wait(10)
+
+ def teardown_method(self, method):
+ for driver in self.driver_1, self.driver_2:
+ self.print_sauce_lab_info(driver)
+ driver.quit()
diff --git a/test/appium/tests/conftest.py b/test/appium/tests/conftest.py
new file mode 100644
index 0000000000..888c476806
--- /dev/null
+++ b/test/appium/tests/conftest.py
@@ -0,0 +1,12 @@
+from tests import tests_data
+import time
+
+
+def pytest_addoption(parser):
+ parser.addoption("--build", action="store", default='build_' + time.strftime('%Y_%m_%d_%H_%M'),
+ help="Specify build name")
+ parser.addoption('--apk', action='store', default=None, help='Please provide url or local path to apk')
+
+
+def pytest_runtest_setup(item):
+ tests_data.name = item.name
diff --git a/test/appium/tests/preconditions.py b/test/appium/tests/preconditions.py
new file mode 100644
index 0000000000..2b7aaeb52c
--- /dev/null
+++ b/test/appium/tests/preconditions.py
@@ -0,0 +1,10 @@
+
+
+def set_chat_for_users_from_scratch(*args):
+ for view in args:
+ view.request_password_icon.click()
+ view.type_message_edit_box.send_keys("qwerty1234")
+ view.confirm()
+ view.type_message_edit_box.send_keys("qwerty1234")
+ view.confirm()
+ view.find_text("Tap here to enter your phone number & I\'ll find your friends")
diff --git a/test/appium/tests/test_chats.py b/test/appium/tests/test_chats.py
new file mode 100644
index 0000000000..f41a560a30
--- /dev/null
+++ b/test/appium/tests/test_chats.py
@@ -0,0 +1,33 @@
+import pytest
+from tests.basetestcase import MultiplyDeviceTestCase
+from tests.preconditions import set_chat_for_users_from_scratch
+from views.home import HomeView
+
+
+@pytest.mark.sanity
+class TestMultiplyDevices(MultiplyDeviceTestCase):
+
+ def test_private_chat(self):
+
+ device_1, device_2 = HomeView(self.driver_1), HomeView(self.driver_2)
+ set_chat_for_users_from_scratch(device_1, device_2)
+
+ device_1.back_button.click()
+ chats_d1 = device_1.get_chats()
+ chats_d1.profile_button.click()
+ profile_d1 = chats_d1.profile_icon.click()
+ key = profile_d1.public_key_text.get_key()
+
+ device_2.back_button.click()
+ chats_d2 = device_2.get_chats()
+ chats_d2.plus_button.click()
+ chats_d2.add_new_contact.click()
+ chats_d2.public_key_edit_box.send_keys(key)
+ chats_d2.confirm()
+ chats_d2.confirm_public_key_button.click()
+
+ chats_d2.chat_message_input.send_keys('SOMETHING')
+ chats_d2.send_message_button.click()
+
+ profile_d1.back_button.click()
+ profile_d1.find_text('SOMETHING')
diff --git a/test/appium/tests/test_sanity.py b/test/appium/tests/test_sanity.py
new file mode 100644
index 0000000000..10f307b127
--- /dev/null
+++ b/test/appium/tests/test_sanity.py
@@ -0,0 +1,28 @@
+import pytest
+from tests.basetestcase import SingleDeviceTestCase
+from views.home import HomeView
+
+
+@pytest.mark.sanity
+class TestSanity(SingleDeviceTestCase):
+
+ @pytest.mark.parametrize("verification", ["short", "mismatch", "valid"])
+ def test_password(self, verification):
+
+ verifications = {"short": {"input": "qwe1",
+ "outcome":
+ "Password should be not less then 6 symbols."},
+ "mismatch": {"input": "mismatch1234",
+ "outcome":
+ "Password confirmation doesn\'t match password."},
+ "valid": {"input": "qwerty1234",
+ "outcome":
+ "Tap here to enter your phone number & I\'ll find your friends"}}
+ home = HomeView(self.driver)
+ home.request_password_icon.click()
+ home.type_message_edit_box.send_keys(verifications[verification]["input"])
+ home.confirm()
+ if 'short' not in verification:
+ home.type_message_edit_box.send_keys(verifications["valid"]["input"])
+ home.confirm()
+ home.find_text(verifications[verification]["outcome"])
diff --git a/test/appium/views/__init__.py b/test/appium/views/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/appium/views/base_element.py b/test/appium/views/base_element.py
new file mode 100644
index 0000000000..519dfaee4a
--- /dev/null
+++ b/test/appium/views/base_element.py
@@ -0,0 +1,75 @@
+from selenium.webdriver.common.by import By
+from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from selenium.webdriver.support.wait import WebDriverWait
+from selenium.webdriver.support import expected_conditions
+import pytest
+
+
+class BaseElement(object):
+
+ class Locator(object):
+
+ def __init__(self, by, value):
+ self.by = by
+ self.value = value
+
+ @classmethod
+ def xpath_selector(locator, value):
+ return locator(By.XPATH, value)
+
+ def __str__(self, *args, **kwargs):
+ return "%s:%s" % (self.by, self.value)
+
+ def __init__(self, driver):
+ self.driver = driver
+ self.locator = None
+
+ @property
+ def name(self):
+ return self.__class__.__name__
+
+ def navigate(self):
+ return None
+
+ def find_element(self):
+ try:
+ return self.wait_for_element()
+ except (NoSuchElementException, TimeoutException):
+ pytest.fail("'%s' not found by %s '%s'" % (
+ self.name, self.locator.by, self.locator.value), pytrace=False)
+
+ def wait_for_element(self, seconds=30):
+ return WebDriverWait(self.driver, seconds)\
+ .until(expected_conditions.presence_of_element_located((self.locator.by, self.locator.value)))
+
+
+class BaseEditBox(BaseElement):
+
+ def __init__(self, driver):
+ super(BaseEditBox, self).__init__(driver)
+ self.driver = driver
+
+ def send_keys(self, value):
+ self.find_element().send_keys(value)
+
+
+class BaseText(BaseElement):
+
+ def __init__(self, driver):
+ super(BaseText, self).__init__(driver)
+ self.driver = driver
+
+ @property
+ def text(self):
+ return self.find_element().text
+
+
+class BaseButton(BaseElement):
+
+ def __init__(self, driver):
+ super(BaseButton, self).__init__(driver)
+ self.driver = driver
+
+ def click(self):
+ self.find_element().click()
+ return self.navigate()
diff --git a/test/appium/views/base_view.py b/test/appium/views/base_view.py
new file mode 100644
index 0000000000..5fa6f8e486
--- /dev/null
+++ b/test/appium/views/base_view.py
@@ -0,0 +1,27 @@
+from views.base_element import BaseElement, BaseButton
+
+
+class BackButton(BaseButton):
+
+ def __init__(self, driver):
+ super(BackButton, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector("//*[@content-desc='toolbar-back-button']")
+
+
+class BaseViewObject(object):
+
+ def __init__(self, driver):
+ self.driver = driver
+ self.back_button = BackButton(self.driver)
+
+ def confirm(self):
+ self.driver.keyevent(66)
+
+ def find_text(self, text):
+ element = BaseElement(self.driver)
+ element.locator = element.Locator.xpath_selector('//*[@text="' + text + '"]')
+ return element.wait_for_element(30)
+
+ def get_chats(self):
+ from views.chats import ChatsViewObject
+ return ChatsViewObject(self.driver)
diff --git a/test/appium/views/chats.py b/test/appium/views/chats.py
new file mode 100644
index 0000000000..103d7f9962
--- /dev/null
+++ b/test/appium/views/chats.py
@@ -0,0 +1,89 @@
+from views.base_view import BaseViewObject
+from views.base_element import *
+
+
+class ProfileButton(BaseButton):
+
+ def __init__(self, driver):
+ super(ProfileButton, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector(
+ "//android.support.v4.view.ViewPager//android.view.ViewGroup[1]/android.widget.ImageView")
+
+
+class ProfileIcon(BaseButton):
+
+ def __init__(self, driver):
+ super(ProfileIcon, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector(
+ "//android.widget.EditText/../android.view.ViewGroup")
+
+ def navigate(self):
+ from views.profile import ProfileViewObject
+ return ProfileViewObject(self.driver)
+
+
+class PlusButton(BaseButton):
+
+ def __init__(self, driver):
+ super(PlusButton, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector(
+ "//android.widget.TextView[@text='+']")
+
+
+class AddNewContactButton(BaseButton):
+
+ def __init__(self, driver):
+ super(AddNewContactButton, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector(
+ "//android.widget.TextView[@text='Add new contact']")
+
+
+class PublicKeyEditBox(BaseEditBox):
+
+ def __init__(self, driver):
+ super(PublicKeyEditBox, self).__init__(driver)
+ self.locator = \
+ self.Locator.xpath_selector("//android.widget.EditText[@NAF='true']")
+
+
+class ConfirmPublicKeyButton(BaseButton):
+
+ def __init__(self, driver):
+ super(ConfirmPublicKeyButton, self).__init__(driver)
+ self.locator = \
+ self.Locator.xpath_selector("//android.widget.TextView[@text='Add new contact']"
+ "/following-sibling::android.view.ViewGroup/"
+ "android.widget.ImageView")
+
+
+class ChatMessageInput(BaseEditBox):
+
+ def __init__(self, driver):
+ super(ChatMessageInput, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector("//*[@content-desc='chat-message-input']")
+
+
+class SendMessageButton(BaseButton):
+
+ def __init__(self, driver):
+ super(SendMessageButton, self).__init__(driver)
+ self.locator = \
+ self.Locator.xpath_selector("//android.widget.FrameLayout//"
+ "android.view.ViewGroup[3]//"
+ "android.view.ViewGroup[2]//android.widget.ImageView")
+
+
+class ChatsViewObject(BaseViewObject):
+
+ def __init__(self, driver):
+ super(ChatsViewObject, self).__init__(driver)
+ self.driver = driver
+
+ self.profile_button = ProfileButton(self.driver)
+ self.profile_icon = ProfileIcon(self.driver)
+ self.plus_button = PlusButton(self.driver)
+ self.add_new_contact = AddNewContactButton(self.driver)
+ self.public_key_edit_box = PublicKeyEditBox(self.driver)
+ self.confirm_public_key_button = ConfirmPublicKeyButton(self.driver)
+ self.chat_message_input = ChatMessageInput(self.driver)
+ self.send_message_button = SendMessageButton(self.driver)
diff --git a/test/appium/views/home.py b/test/appium/views/home.py
new file mode 100644
index 0000000000..554948b24a
--- /dev/null
+++ b/test/appium/views/home.py
@@ -0,0 +1,39 @@
+from views.base_view import BaseViewObject
+from views.base_element import *
+
+
+class ContinueButton(BaseButton):
+
+ def __init__(self, driver):
+ super(ContinueButton, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector("//*[@text='Continue']")
+
+
+class TypeMessageEditBox(BaseEditBox):
+
+ def __init__(self, driver):
+ super(TypeMessageEditBox, self).__init__(driver)
+ self.locator = \
+ self.Locator.xpath_selector("//android.widget.EditText[@content-desc!='chat-message-input']")
+
+
+class RequestPasswordIcon(BaseButton):
+
+ def __init__(self, driver):
+ super(RequestPasswordIcon, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector("//*[@content-desc='request-password']")
+
+
+class HomeView(BaseViewObject):
+
+ def __init__(self, driver):
+ super(HomeView, self).__init__(driver)
+ self.continue_button = ContinueButton(driver)
+
+ try:
+ self.continue_button.click()
+ except Exception:
+ pass
+
+ self.type_message_edit_box = TypeMessageEditBox(driver)
+ self.request_password_icon = RequestPasswordIcon(driver)
diff --git a/test/appium/views/profile.py b/test/appium/views/profile.py
new file mode 100644
index 0000000000..7b12840a60
--- /dev/null
+++ b/test/appium/views/profile.py
@@ -0,0 +1,25 @@
+from views.base_view import BaseViewObject
+from views.base_element import *
+
+
+class PublicKeyText(BaseText):
+
+ def __init__(self, driver):
+ super(PublicKeyText, self).__init__(driver)
+ self.locator = self.Locator.xpath_selector("//android.widget.TextView")
+
+ def get_key(self):
+ texts = self.driver.find_elements(self.locator.by, self.locator.value)
+ for i in texts:
+ if i.text.startswith('0x04'):
+ return i.text
+ pytest.fail("Public key wasn't found!")
+
+
+class ProfileViewObject(BaseViewObject):
+
+ def __init__(self, driver):
+ super(ProfileViewObject, self).__init__(driver)
+ self.driver = driver
+
+ self.public_key_text = PublicKeyText(self.driver)