Интересная темка завелась
Приведу свой пример, может кому-то пригодиться, или что-то дельное подскажите…
На мобильном (Appium) у меня реализовано немного криво, через ApplicationManager:
DriverSetup
public class DriverSetup {
private static final Logger logger = LogManager.getLogger(DriverSetup.class);
private static DriverSetup _instance = null;
private AndroidDriver driver;
private WebDriverWait wait;
private URL serverUrl;
public int EXPLICIT_WAIT_TIME;
public int DEFAULT_WAIT_TIME;
public int IMPLICIT_WAIT_TIME;
public String WAIT_ACTIVITY;
public String APPIUM_PORT;
public String NEW_COMMAND_TIMEOUT;
public String DEVICE_READY_TIMEOUT;
public String DEVICE_NAME;
public String BROWSER_NAME;
public String PLATFORM_VERSION;
public String PLATFORM_NAME;
public String APP_PKG;
public String APP_ACTIVITY;
public String AUTOMATION_INSTRUMENTATION;
public String APPLICATION_NAME;
public String PERFORMANCE_LOGGING;
private DesiredCapabilities capabilities = new DesiredCapabilities();
private Properties prop = new Properties();
public DriverSetup() {
this.loadConfigProp();
this.setCapabilities();
try {
serverUrl = new URL("http://127.0.0.1:" + APPIUM_PORT + "/wd/hub");
} catch (MalformedURLException e) {
e.printStackTrace();
logger.error(e);
}
driver = new AndroidDriver(serverUrl, capabilities);
//EXPLICIT_WAIT_TIME
wait = (WebDriverWait) new WebDriverWait(driver, DEFAULT_WAIT_TIME)
.withMessage("Element was not found")
.ignoring(MoveTargetOutOfBoundsException.class);
//driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
// IMPLICIT_WAIT_TIME
driver.manage().timeouts().implicitlyWait(IMPLICIT_WAIT_TIME, TimeUnit.SECONDS);
}
public static DriverSetup getInstance() {
if (_instance == null) {
_instance = new DriverSetup();
}
return _instance;
}
public AndroidDriver getDriver() {
return driver;
}
public WebDriverWait getWait() {
return wait;
}
public void loadConfigProp() {
EXPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("explicitWait"));
DEFAULT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("defaultWait"));
IMPLICIT_WAIT_TIME = Integer.parseInt(PropertyLoader.loadProperty("implicitWait"));
WAIT_ACTIVITY = PropertyLoader.loadProperty("waitActivity");
APPIUM_PORT = PropertyLoader.loadProperty("appiumServerPort");
NEW_COMMAND_TIMEOUT = PropertyLoader.loadProperty("newCommandTimeout");
DEVICE_READY_TIMEOUT = PropertyLoader.loadProperty("deviceReadyTimeout");
DEVICE_NAME = PropertyLoader.loadProperty("deviceName");
BROWSER_NAME = PropertyLoader.loadProperty("browserName");
PLATFORM_VERSION = PropertyLoader.loadProperty("platformVersion");
PLATFORM_NAME = PropertyLoader.loadProperty("platformName");
APP_PKG = PropertyLoader.loadProperty("applicationPackage");
APP_ACTIVITY = PropertyLoader.loadProperty("applicationActivity");
AUTOMATION_INSTRUMENTATION = PropertyLoader.loadProperty("automationInstumentation");
APPLICATION_NAME = PropertyLoader.loadProperty("applicationPath");
//PERFORMANCE_LOGGING = PropertyLoader.loadProperty("enablePerformanceLogging");
}
public void setCapabilities() {
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, DEVICE_NAME);
capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, BROWSER_NAME);
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, PLATFORM_VERSION);
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, PLATFORM_NAME);
capabilities.setCapability(MobileCapabilityType.APP_PACKAGE, APP_PKG);
if (prop.containsKey("waitActivity")) {
capabilities.setCapability(MobileCapabilityType.APP_WAIT_ACTIVITY, WAIT_ACTIVITY);
} else {
capabilities.setCapability(MobileCapabilityType.APP_ACTIVITY, APP_ACTIVITY);
}
// if (!APPLICATION_NAME.isEmpty()) {
// capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AUTOMATION_INSTRUMENTATION);
// capabilities.setCapability(MobileCapabilityType.APP, new File(
// ClassLoader.getSystemResource(APPLICATION_NAME).getFile()).getAbsolutePath());
// }
capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, NEW_COMMAND_TIMEOUT);
capabilities.setCapability(MobileCapabilityType.DEVICE_READY_TIMEOUT, DEVICE_READY_TIMEOUT);
//capabilities.setCapability("unicodeKeyboard", true);
}
}
пока эксперементирую с вейтами и наверное стоит переделать на ENUM переменные и избавиться от такого loadConfigProp
ApplicationManager - тут у меня скрины инициализируются и сам драйвер с вейтом…
public class ApplicationManager {
public AndroidDriver driver;
public WebDriverWait wait;
// Helpers
private NavigationHelper navigationHelper;
private DeviceHelper deviceHelper;
// Screens
private SomeScreen someScreen;
public ApplicationManager() {
driver = DriverSetup.getInstance().getDriver();
wait = DriverSetup.getInstance().getWait();
}
/***************
* Helpers
***************/
public NavigationHelper getNavigationHelper() {
if (navigationHelper == null) {
navigationHelper = new NavigationHelper(this);
}
return navigationHelper;
}
public DeviceHelper getDeviceHelper() {
if (deviceHelper == null) {
deviceHelper = new DeviceHelper(this);
}
return deviceHelper;
}
/***************
* Screens
***************/
public SomeScreen getSomeScreen() {
if (someScreen == null) {
someScreen = new someScreen(this);
}
return somScreen;
}
/***************
* Others
***************/
public void stop() {
if (driver != null) {
driver.quit();
}
}
}
BaseScreen
public abstract class BaseScreen<T extends BaseScreen<T>> {
private static final Logger logger = LogManager.getLogger(BaseScreen.class);
protected ApplicationManager manager;
protected AndroidDriver driver;
protected WebDriverWait wait;
public BaseScreen(ApplicationManager manager) {
this.manager = manager;
this.driver = manager.driver;
this.wait = manager.wait;
}
public void loadScreen() {
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
@AndroidFindBy(className = "android.webkit.WebView")
private WebElement androidWebView;
@SuppressWarnings("unchecked")
public T then() {
return (T) this;
}
@SuppressWarnings("unchecked")
public T with() {
return (T) this;
}
@Step("Tap by element {0}")
public BaseScreen tap(WebElement element) {
element.click();
return (T) this;
}
@Step("Tap with hold by element {0}")
public T longTap(WebElement element, int longPressTime) {
TouchAction action = new TouchAction(driver);
action.longPress(element, longPressTime).release().perform();
return (T) this;
}
@Step("Type text {1} in field {0}")
public T typeTextInField(WebElement element, String text) {
element.clear();
element.sendKeys(text);
hideKeyboard();
return (T) this;
}
@Step("Select checkBox \"{0}\"")
public T selectCheckBox(WebElement element) {
if (!element.isSelected()) {
element.click();
}
return (T) this;
}
public T goToAppMainScreen() {
driver.navigate().back();
return (T) this;
}
@Step("Hide keyboard")
public T hideKeyboard() {
Process p = null;
try {
p = Runtime.getRuntime().exec("adb shell dumpsys input_method | grep mInputShown");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String outputText = "";
while ((outputText = in.readLine()) != null) {
if (!outputText.trim().equals("")) {
String keyboardProperties[] = outputText.split(" ");
String keyValue[] = keyboardProperties[keyboardProperties.length - 1].split("=");
String softkeyboardpresenseValue = keyValue[keyValue.length - 1];
if (softkeyboardpresenseValue.equalsIgnoreCase("false")) {
logger.info("Keyboard already closed");
} else {
driver.hideKeyboard();
logger.info("Keyboard closed");
}
}
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return (T) this;
}
@Step("Rotate device screen: LANDSCAPE / PORTRAIT")
protected T rotateScreen() {
driver.rotate(ScreenOrientation.LANDSCAPE);
return (T) this;
}
@Step("Switch to webview")
protected T switchToWebView() {
//wait.until(ExpectedConditions.visibilityOf(androidWebView));
Set<String> contextSet = driver.getContextHandles();
for (String contextName : contextSet) {
if (!contextName.contains("NATIVE_APP")) {
driver.context(contextName);
break;
}
}
return (T) this;
}
@Step("Take screenshot")
protected T takeScreenShot(String fileName) {
File file = new File(fileName + ".png");
File tmpFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
try {
FileUtils.copyFile(tmpFile, file);
} catch (IOException e) {
e.printStackTrace();
}
return (T) this;
}
}
Собственно вот сам скрин:
public class SomeScreen extends BaseScreen<SomeScreen> {
@AndroidFindBy(uiAutomator = "new UiSelector().text(\"Write Message\")")
private WebElement writeMessageButton;
public SomeScreen(ApplicationManager manager) {
super(manager);
loadScreen();
}
// методы тут идут по работе с элементами
public SomeScreen sendMessages(String message) {
tap(this.writeMessageButton);
typeTextInField(this.sendMessagesField, message);
tap(this.sendMessagesButton);
return this;
}
}
вот собственно мой тест:
public class SomeTest extends BaseTest {
@Test(enabled = true, groups = "some groups", priority = 10)
@Title("title")
public void someTestName() {
// --------------------- Test Data ----------------------//
// --------------------- Test Case ----------------------//
app.getSomeScreen()
.sendMessages("qwerty")
.goToAppMainScreen();
}
}
конечно тетс сократил, там у меня еще контейнеры, в которых логика состредаточена для разных девайсов и разных имен и разных сообщений, но для примера будет понятно…
BaseTest
//@Listeners({ScreenshotListener.class, TestListener.class})
@Listeners({TestListener.class})
public class BaseTest {
private static final Logger logger = LogManager.getLogger(BaseTest.class);
protected ApplicationManager app;
@BeforeSuite(alwaysRun = true)
public void setUp() {
app = new ApplicationManager();
}
@AfterSuite(alwaysRun = true)
public static void createAllureProperties() {
AllureProperties.create();
}
@AfterSuite(alwaysRun = true)
public void tearDown() {
//app.stop();
}
}
Начал писать все это когда еще ничерта не понимал, и только начинал разбираться в автоматизации, поэтому не очень здорово тут все получилось, сейчас вот переписываю так, чтобы все ожидания и методы с UI элементами - перенести в отдельные классы.
Это так у меня под мобилку для Android, фреймворк такой, сейчас уже понял что не так, чуть опыта поднабрался, переписываю… На вебе там все посложнее будет, там у меня реализована многопоточность и куча браузеров и запуск под разными ОС…
При написании фреймворка в автоматизации для себя выделил такие вещи:
- весь код должен переиспользоваться так, чтобы в пейджах были только элементы и работа с ними
- при чтении тестов все должно быть предельно понятно, что происходит
- драйвер и ожидания не должны быть в тестах и не должны быть в пейджах
- все методы касающиеся элемнтов - должны находиться в своем классе
- все методы по ожиданиям - должны находится в своем классе
- фреймворк должен писаться так, чтобы я сначала проектировал тест, а из него уже генерировал методы. Проще придумать тест, каким он должен быть, а затем уже писать методы для теста
- везде должны соблюдаться уровни доступа