e2e-tests, android: added infrastructure for running e2e tests, added test send/receive in chat, added request password tests, updated gitignore to exclude python related files.

This commit is contained in:
Anton Danchenko 2017-08-28 13:02:20 +03:00 committed by Oskar Thorén
parent f07eff4ab1
commit e69d998071
24 changed files with 502 additions and 406 deletions

4
.gitignore vendored
View File

@ -79,3 +79,7 @@ status-dev-cli
#ios
ios/Pods
ios/StatusIm.xcworkspace
#python
*.pyc
*.cache

View File

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: io.appium:java-client:5.0.0-BETA7" level="project" />
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.0" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.4" level="project" />
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.9" level="project" />
<orderEntry type="library" name="Maven: cglib:cglib:3.2.4" level="project" />
<orderEntry type="library" name="Maven: org.ow2.asm:asm:5.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.ant:ant:1.9.6" level="project" />
<orderEntry type="library" name="Maven: org.apache.ant:ant-launcher:1.9.6" level="project" />
<orderEntry type="library" name="Maven: commons-validator:commons-validator:1.5.1" level="project" />
<orderEntry type="library" name="Maven: commons-beanutils:commons-beanutils:1.9.2" level="project" />
<orderEntry type="library" name="Maven: commons-digester:commons-digester:1.8.1" level="project" />
<orderEntry type="library" name="Maven: commons-collections:commons-collections:3.2.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.5" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:4.3.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:4.3.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:4.3.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:4.3.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:4.3.5.RELEASE" level="project" />
<orderEntry type="library" name="Maven: org.aspectj:aspectjweaver:1.8.10" level="project" />
<orderEntry type="library" name="Maven: org.openpnp:opencv:3.2.0-1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-java:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-chrome-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-remote-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-api:3.3.1" level="project" />
<orderEntry type="library" name="Maven: cglib:cglib-nodep:3.2.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-exec:1.3" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:21.0" level="project" />
<orderEntry type="library" name="Maven: net.java.dev.jna:jna-platform:4.1.0" level="project" />
<orderEntry type="library" name="Maven: net.java.dev.jna:jna:4.1.0" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-edge-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-firefox-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-ie-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-opera-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-safari-driver:3.3.1" level="project" />
<orderEntry type="library" name="Maven: com.codeborne:phantomjsdriver:1.4.0" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:htmlunit-driver:2.24" level="project" />
<orderEntry type="library" name="Maven: org.seleniumhq.selenium:selenium-support:3.3.1" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />
<orderEntry type="library" name="Maven: net.sourceforge.htmlunit:htmlunit:2.24" level="project" />
<orderEntry type="library" name="Maven: xalan:xalan:2.7.2" level="project" />
<orderEntry type="library" name="Maven: xalan:serializer:2.7.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpmime:4.5.2" level="project" />
<orderEntry type="library" name="Maven: net.sourceforge.htmlunit:htmlunit-core-js:2.23" level="project" />
<orderEntry type="library" name="Maven: net.sourceforge.htmlunit:neko-htmlunit:2.24" level="project" />
<orderEntry type="library" name="Maven: xerces:xercesImpl:2.11.0" level="project" />
<orderEntry type="library" name="Maven: xml-apis:xml-apis:1.4.01" level="project" />
<orderEntry type="library" name="Maven: net.sourceforge.cssparser:cssparser:0.9.21" level="project" />
<orderEntry type="library" name="Maven: org.w3c.css:sac:1.3" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-client:9.2.20.v20161216" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.2.20.v20161216" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.2.20.v20161216" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-common:9.2.20.v20161216" level="project" />
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-api:9.2.20.v20161216" level="project" />
<orderEntry type="library" name="Maven: junit:junit:4.12" level="project" />
<orderEntry type="library" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />
<orderEntry type="library" name="Maven: com.saucelabs:sauce_junit:2.1.23" level="project" />
<orderEntry type="library" name="Maven: com.saucelabs:sauce_java_common:2.1.23" level="project" />
<orderEntry type="library" name="Maven: com.saucelabs:saucerest:1.0.32" level="project" />
<orderEntry type="library" name="Maven: com.googlecode.json-simple:json-simple:1.1.1" level="project" />
<orderEntry type="library" name="Maven: org.json:json:20090211" level="project" />
</component>
</module>

8
test/appium/Jenkinsfile vendored Normal file
View File

@ -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' }
}
}

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>im.status</groupId>
<artifactId>AppiumTests</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>5.0.0-BETA7</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.saucelabs</groupId>
<artifactId>sauce_junit</artifactId>
<version>2.1.23</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<parallel>methods</parallel>
<threadCount>4</threadCount>
</configuration>
</plugin>
</plugins>
</build>
</project>

3
test/appium/pytest.ini Normal file
View File

@ -0,0 +1,3 @@
[pytest]
norecursedirs = .git views
addopts = -s -v --junitxml=result.xml --tb=short

View File

@ -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

View File

@ -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); }
}

View File

@ -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<WebElement> driver;
protected WebDriverWait wait;
public AbstractScreen(AppiumDriver<WebElement> 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);
}
}

View File

@ -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<WebElement> 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;
}
}
}

View File

@ -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<WebElement> 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;
}
}

View File

@ -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();
}
}

View File

@ -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<SELF, DRIVER extends AppiumDriver> {
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<AndroidDriverBuilder, AndroidDriver> {
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<WebElement>(endpoint, capabilities);
}
}
public SELF withEndpoint(URL endpoint) {
this.endpoint = endpoint;
return (SELF) this;
}
public abstract DRIVER build();
}

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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')

View File

@ -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"])

View File

View File

@ -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()

View File

@ -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)

View File

@ -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)

39
test/appium/views/home.py Normal file
View File

@ -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)

View File

@ -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)