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
|
||||||
ios/Pods
|
ios/Pods
|
||||||
ios/StatusIm.xcworkspace
|
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