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:
parent
f07eff4ab1
commit
e69d998071
|
@ -79,3 +79,7 @@ status-dev-cli
|
|||
#ios
|
||||
ios/Pods
|
||||
ios/StatusIm.xcworkspace
|
||||
|
||||
#python
|
||||
*.pyc
|
||||
*.cache
|
||||
|
|
|
@ -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>
|
|
@ -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' }
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
[pytest]
|
||||
norecursedirs = .git views
|
||||
addopts = -s -v --junitxml=result.xml --tb=short
|
|
@ -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
|
|
@ -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); }
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
|
@ -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")
|
|
@ -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')
|
|
@ -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"])
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue