在編程中處理adb命令—App自動化測試與框架實戰(10)
- 2019 年 12 月 12 日
- 筆記
顧翔老師開發的bugreport2script開源了,希望大家多提建議。文件在https://github.com/xianggu625/bug2testscript,
主文件是:zentao.py 。bugreport是禪道,script是python3+selenium 3,按照規則在禪道上書寫的bugreport可由zentao.py程式生成py測試腳本。
來源:http://www.51testing.com
11.13 處理拖動
拖動就是將一個對象從一個位置拖到另外一個位置,可以簡化桌面操作,如程式碼清單11-18所示。
程式碼清單11-18拖動
public void drag(By startElement_by, By endElement_by){TouchAction act = new TouchAction(driver) ;//定位元素的原位置MobileElement startElement = (MobileElement) driver.findElement(startElement_by);//定位元素要移動到的目標位置MobileElement endElement = (MobileElement) driver.findElement(endElement_by) ;//執行元素的移動操作act.press(startElement).perform();act.moveTo(endElement).release().perform();} |
|---|
11.14 處理截圖
Appium可以通過使用getScreenshotAs截取整個頁面作為圖片,在測試過程中幫助我們直觀地定位錯誤,如程式碼清單11-19所示。
程式碼清單11-19截圖操作
WebElement RegisterPage=driver.findElement(By.name("startUserRegistration"));FilescreenShot=driver.getScreenshotAs(OutputType.FILE);Filelocation=newFile("screenshots");String screenShotName=location.getAbsolutePath()+File.separator+"testCalculator.png";try{System.out.println("save screenshop");FileUtils.copyFile(screenShot,new File(screenShotName));}catch(IOException e){System.out.println("save screenshop fail");e.printStackTrace();}finally{System.out.println("save screenshop finish");} |
|---|
受到設備存儲容量的限制,我們可以考慮擴展這個功能,使得它可以截取頁面上某一個元素。要截取頁面上的username編輯框,程式碼如程式碼清單11-20所示。
程式碼清單11-20截取指定元素
WebElement username = driver.findElementById("inputUsername");username.sendKeys("bree");getElementShotSaveAs(username);Assert.assertEquals("liming", username.getText());public void getElementShotSaveAs(WebElement element) throws IOException{File screenShot=driver.getScreenshotAs(OutputType.FILE);BufferedImage img = ImageIO.read(screenShot);int width = element.getSize().width;int height = element.getSize().height;Rectangle rect = new Rectangle(width,height);Point p = element.getLocation();BufferedImage dest = img.getSubimage(p.x, p.y, rect.width, rect.height);ImageIO.write(dest, "png",screenShot);} |
|---|
由於自動化測試是無人值守的,因此可以利用TestNG監聽器來實現監聽功能。當測試處於某種狀態的時候執行錯誤截圖,如測試失敗時的截圖。這裡採用testListenerAdapter方法,每次測試失敗的時候,都會重寫該方法。
新建兩個類,一個用作監聽器,另外一個用於寫測試程式碼。
1.監聽器
監聽器是一些預定義的Java介面。用戶創建這些介面的實現類,並把它們加入TestNG中,TestNG 便會在測試運行的不同時刻調用這些類中的介面方法。這裡使用ITestListener監聽器,實現其方法為onTestFailure在測試失敗的時候,保存控制項的截圖,如程式碼清單11-21所示。
程式碼清單11-21監聽器
packageappiumsample;importjava.io.File;importjava.io.IOException;importio.appium.java_client.AppiumDriver;importorg.apache.commons.io.FileUtils;importorg.openqa.selenium.OutputType;importorg.testng.ITestResult;importorg.testng.TestListenerAdapter;publicclassScreenshotListenerextendsTestListenerAdapter{@OverridepublicvoidonTestFailure(ITestResulttr){AppiumDriverdriver=Screenshot.getDriver();Filelocation=newFile("screenshots");StringscreenShotName=location.getAbsolutePath()+File.separator+tr.getMethod().getMethodName()+".png";//使用ItestResult獲取失敗方法名FilescreenShot=driver.getScreenshotAs(OutputType.FILE);try{FileUtils.copyFile(screenShot,newFile(screenShotName));}catch(IOExceptione){e.printStackTrace();}}} |
|---|
2.測試程式碼
通過使用"@Listeners"注釋,可以直接在 Java 源程式碼中添加 TestNG 監聽器,如程式碼清單11-22所示。
程式碼清單11-22測試程式碼
package appiumsample;import io.appium.java_client.android.AndroidDriver;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;import org.openqa.selenium.WebElement;import org.openqa.selenium.remote.DesiredCapabilities;import org.testng.Assert;import org.testng.annotations.AfterClass;import org.testng.annotations.BeforeClass;import org.testng.annotations.Listeners;import org.testng.annotations.Test;@Listeners({ScreenshotListener.class})public class Screenshot {private static AndroidDriver driver;@BeforeClasspublic void setup() throws MalformedURLException {//App地址String apppath = "F:\selendroid-test-app-0.15.0_debug.apk";//配置AndroidDriverDesiredCapabilities capabilities = new DesiredCapabilities();capabilities.setCapability("deviceName", "Lenovo A788t");//真機的名稱為Lenovo A788tcapabilities.setCapability("platformVersion", "4.3");//作業系統版本為4.3capabilities.setCapability("platformName", "Android");//作業系統名稱為Androidcapabilities.setCapability("udid", "00a10399");//使用的真機為Android平台capabilities.setCapability("app", apppath);//確定待測Appcapabilities.setCapability("appPackage", "io.selendroid.testapp");//待測App包capabilities.setCapability("appActivity", ".HomeScreenActivity");//待測App主Activity名capabilities.setCapability("automationName", "selendroid");driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"),capabilities);// WebDriverWait wait = new WebDriverWait(driver,10);}@SuppressWarnings("deprecation")@Testpublic void testExample() throws IOException {WebElement username = driver.findElementById("inputUsername");username.sendKeys("bree");Assert.assertEquals("liming", username.getText());}public static AndroidDriver getDriver(){return driver;}@AfterClasspublic void tearDown(){}} |
|---|
11.15 隱式等待
在運行測試時,測試可能並不總是以相同的速度響應,例如,可能在幾秒後進度條到100%時,按鈕才會變成可單擊的狀態。這裡介紹不同的方法進行同步測試。
隱式等待有兩種方法,即implicitlyWait和sleep。需要注意的是,一旦設置了隱式等待,則它存在整個driver對象實例的生命周期中。在下例中,設置全局等待時間是30s,這是最長的等待時間。
最直接的方式是設置固定的等待時間。
Thread.sleep(30000)
對於固定等待時間的元素,可以用sleep進行簡單的封裝來實現等待指定的時間,如程式碼清單11-23所示。
程式碼清單11-23用sleep實現等待
@Test(description = "sleep簡單封裝")private boolean testisElementPresent(By by) throws InterruptedException {try {//設置等待時間Thread.sleep(1000);//查找元素driver.findElement(by);//若找到元素,返回truereturn true;} catch (NoSuchElementException e) {//若找不到元素,返回falsereturn false;}} |
|---|
也可以利用sleep封裝一個計時器,完成等待操作,如程式碼清單11-24所示。
程式碼清單11-24通過sleep計時器實現隱式等待
@Test(description = "sleep封裝")public static void testwaitTimer( int units, int mills ) {DecimalFormat df = new DecimalFormat("###.##");double totalSeconds = ((double)units*mills)/1000;System.out.print("Explicit pause for " + df.format(totalSeconds) + " seconds dividedby " + units + " units of time: ");try {Thread.currentThread();int x = 0;while( x < units ) {Thread.sleep( mills );System.out.print("." );x = x + 1;}System.out.print('n');} catch ( InterruptedException ex ) {ex.printStackTrace();}} |
|---|
隱式等待方式(implicitlyWait)是指在嘗試發現某個元素的時候,如果沒能立刻發現,就等待固定長度的時間。默認設置是0s,如程式碼清單11-25所示。
程式碼清單11-25implicitlyWait實現隱式等待
@Test(description = "測試顯示等待")public void testImplicitlyWait(){//識別"美食"圖標MobileElement meishiElement = (MobileElement) driver.findElement(By. xpath("//android.support.v7.widget.RecyclerView/android.widget.RelativeLayout[1]/android.widget.ImageView"));//單擊"美食"圖標,跳轉到"美食"介面meishiElement.click();//設置全局等待時間最大為30sdriver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);//查找"深***"try{//查找"深***"driver.findElement(By.xpath("//android.widget.TextView[@text='深***']"));}catch(NoSuchElementException e){//如果控制項沒有找到,則測試用例執行失敗Assert.fail("沒有找到控制項");}} |
|---|
11.16 顯示等待方法
在自動化測試的過程中,很多窗體內的數據,需要等待一會兒,才能載入完數據,才能出現一些元素,Driver才能操作這些元素。另外,做一些操作,本身可能也需要等待一會兒才有數據顯示。
不管是否載入完成,隱式等待都會等待特定的時間,它會讓一個正常響應的應用的測試變慢,增加了整個測試執行的時間。比如有些控制項可能數據較多,需要較長時間才可以載入完成,但是其他控制項載入很快,把它們都設置成固定等待時間,將會造成大量時間的浪費。因此,合理地設置時間等待是非常必要的。
Appium中提供了AppiumFluentWait來實現顯示等待。AppiumFluentWait繼承自FluentWait。這個類能支援一直等待知道特定的條件出現,使用AppiumFluentWait可以設置最大等待時間、等待的頻率等,如程式碼清單11-26所示。
程式碼清單11-26顯示等待
@Test(description = "測試FluentWait")public void testFluent(){//識別美團圖標MobileElement meituan = (MobileElement) driver.findElement(By.xpath("// android.support.v7.widget.RecyclerView/android.widget.RelativeLayout[1]/android.widget.ImageView"));//創建AppiumFluentWait對象new AppiumFluentWait<MobileElement>(meituan)//最長等待時間為10s.withTimeout(10, TimeUnit.SECONDS)//每隔100ms判斷一次元素的文本值是否為"深***".pollingEvery(100,TimeUnit.MILLISECONDS).until(new Function<MobileElement,Boolean>(){@Overridepublic Boolean apply(MobileElement element) {return element.getText().endsWith("深***");}});} |
|---|
AppiumFluentWait的until的參數可以是Predicate,也可以是Function。這裡使用的是Function。因為Function的返回值種類較多,可以為Object或者Boolean類型,而Predicate只能返回Boolean類型。
11.17 在編程中處理adb命令
在對App進行性能測試時,如獲取CPU資訊的命令為adb shell dumpsys cpuinfo packagename。在selendroid-test-app-0.15.0.apk實例中,要獲取CPU的性能指標,編寫的程式碼如程式碼清單11-27所示。
程式碼清單11-27獲取CPU的性能指標
public static void GetCpu(String packageName) throws IOException {Runtime runtime = Runtime.getRuntime();Process proc = runtime.exec("adb shell dumpsys cpuinfo $"+packageName);try {if (proc.waitFor() != 0) {System.err.println("exit value = " + proc.exitValue());}BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream()));String line = null;String totalCpu = null;String userCpu = null;String kernalCpu = null;while ((line = in.readLine()) != null) {if(line.contains(packageName)){System.out.println(line);totalCpu = line.split("%")[0].trim();userCpu = line.substring(line.indexOf(":")+1,line.indexOf("% user")).trim();kernalCpu = line.substring(line.indexOf("+")+1, line.indexOf("% kernel")).trim();System.out.printf("totalCpu的值為:%s%n", totalCpu);System.out.printf("userCpu的值為:%s%n", userCpu);System.out.printf("kernalCpu的值為:%s%n", kernalCpu);}}} catch (InterruptedException e) {System.err.println(e);}finally{try {proc.destroy();} catch (Exception e2) {}}} |
|---|
輸出結果如圖11-7所示。

圖11-7 CPU性能指標
在實際的測試過程中可以多次調用上述程式碼,以獲取不同階段的CPU值。其他性能指標的獲取方法類似。
11.18 區分WebElement、MobileElement、AndroidElement和iOSElement
在Appium自動化測試中,可能有些初學者會對獲取控制項元素對象的類型存在疑惑,不知道在什麼情況下使用什麼類型。下面將介紹控制項元素對象類型的區別。
" WebElement可以使用所有的Selenium命令。
" MobileElement屬於Appium,繼承自WebElement,但是又增加了一些Appium特有的功能(如Touch手勢)。
" AndroidElement和iOSElement實現了WebElement介面方法,並增加了一些Android和iOS特有的功能(如findByAndroidUiAutomation)。
根據待測手機作業系統平台,可以選擇不同的應用,或者根據是否跨平台進行選擇。
11.19 區分RemoteWebDriver、AppiumDriver、AndroidDriver和iOSDriver
在Appium自動化測試中,可能有些初學者會對創建什麼類型的驅動產生困惑,本節將介紹各個驅動類型的區別。
" RemoteWebDriver:這個驅動來自於Selenium,可以使執行測試的機器和發送測試命令的機器獨立開來,中間存在網路請求。Appium是基於客戶端/伺服器的,所有RemoteWebDriver可以直接初始化會話。但是一般不建議使用,Appium提供了其他驅動,可能在使用上更加方便。
" AppiumDriver:繼承自RemoteWebDriver,但是增加了一些特有的功能(如上下文切換)。
" AndroidDriver:繼承自AppiumDriver,但是增加了一些特有的功能,如openNtificutions方法,只有在Android設備或者Android模擬器上才使用這個驅動。
" iOSDriver:繼承自AppiumDriver,但是增加了一些特有的功能,只有在iOS設備或者iOS模擬器上才使用這個驅動。
在實際的使用場景中,根據手機作業系統不同,建議直接使用AndroidDriver或者iOSDriver。
11.20 在程式碼中啟動伺服器
在Appium測試執行時,需要手動啟動Appium伺服器。在一些並行測試場景下,要啟動多個Appium伺服器,如果在程式碼中未使用driver.quit關閉伺服器,或者存在其他一些異常,就會出現會話無法創建的情況。Appium官網提供了AppiumDriverLocalService來完成Appium伺服器的啟動和關閉。這一節講述如何設置Appium伺服器的啟動和關閉,可以根據項目要求進行集成。
使用AppiumDriverLocalService的前提條件有以下兩個。
" 安裝Node.js 7以上版本。
" 通過npm安裝Appium伺服器。
具體的操作如下。
(1)如果沒有指定參數,實現方式如程式碼清單11-28所示。
程式碼清單11-28 未指定參數
import io.appium.java_client.service.local.AppiumDriverLocalService;...AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();service.start();...service.stop(); |
|---|
本地環境中可能會在這一步報錯。
AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();
這個問題在UNIX/Linux下面比較常見,可能是因為使用的node.js實例與環境變數設置的實例不是同一個,也有可能是Appium node服務導致的(Appium.js版本小於等於1.4.16,Main.js版本大於等於1.5.0)。在這種情況下,建議用戶設置NODE_BINARY_PATH(Windows作業系統下指node.exe所在路徑,Linux/Mac OS下指node所在路徑)和APPIUM_BINARY_PATH(Appium.js和Main.js的執行路徑)到環境變數中,也可以在程式中指定。
//appium.node.js.exec.pathSystem.setProperty(AppiumServiceBuilder.NODE_PATH ,"the path to the desired node.js executable");System.setProperty(AppiumServiceBuilder.APPIUM_PATH ,"the path to the desired appium.js or main.js");AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); |
|---|
(2)指定參數,如程式碼清單11-29所示。
程式碼清單11-29 指定參數
import io.appium.java_client.service.local.AppiumDriverLocalService;import io.appium.java_client.service.local.AppiumServiceBuilder;import io.appium.java_client.service.local.flags.GeneralServerFlag;...AppiumDriverLocalService service = AppiumDriverLocalService.buildService(new AppiumServiceBuilder().withArgument(GeneralServerFlag.TEMP_DIRECTORY,"The_path_to_the_temporary_directory"));或者import io.appium.java_client.service.local.AppiumDriverLocalService;import io.appium.java_client.service.local.AppiumServiceBuilder;import io.appium.java_client.service.local.flags.GeneralServerFlag;...AppiumDriverLocalService service = new AppiumServiceBuilder().withArgument(GeneralServerFlag.TEMP_DIRECTORY,"The_path_to_the_temporary_directory").build(); |
|---|
需要導入以下3個包。
io.appium.java_client.service.local.flags.GeneralServerFlagio.appium.java_client.service.local.flags.AndroidServerFlagio.appium.java_client.service.local.flags.iOSServerFlag |
|---|
(3)定義參數。
在有些情況下可能需要使用一些特殊的埠(指定埠)。
new AppiumServiceBuilder().usingPort(4000);
或者使用那些未使用的埠。
new AppiumServiceBuilder().usingAnyFreePort();
使用其他的IP地址。
new AppiumServiceBuilder().withIPAddress("127.0.0.1");
確定日誌文件。
import java.io.File;...new AppiumServiceBuilder().withLogFile(logFile); |
|---|
Node.js執行路徑,如程式碼清單11-30所示。
程式碼清單11-30 Node.js執行路徑
import java.io.File;...new AppiumServiceBuilder().usingDriverExecutable(nodeJSExecutable); |
|---|
Main.js執行路徑,如程式碼清單11-31所示。
程式碼清單11-31 Main.js執行路徑
import java.io.File;...//appium.js is the full or relative path to//the appium.js (v<=1.4.16) or maim.js (v>=1.5.0)new AppiumServiceBuilder().withAppiumJS(new File(appiumJS)); |
|---|
確定伺服器端的Desired Capabilities,如程式碼清單11-32所示。
程式碼清單11-32 確定伺服器端的Desired Capabilities
DesiredCapabilities serverCapabilities = new DesiredCapabilities();...//the capability fillingAppiumServiceBuilder builder = new AppiumServiceBuilder().withCapabilities(serverCapabilities);AppiumDriverLocalService service = builder.build();service.start();...service.stop(); |
|---|
11.21 PageFactory註解
第8章中使用了Page Object和PageFactory兩種設計模式。這一節將詳細闡述Appium官方關於Page Object和PageFactory的使用,並通過實例加深對它們的認識,以便在實際使用中對這些概念不會產生疑惑並能靈活地根據需求進行設置。更複雜的使用場景參考官方文檔。
(1)如程式碼清單11-33所示,默認設置為WebElement或WebElement 數組,注釋方式使用FindBy,元素類型為WebElement。
程式碼清單11-33 FindBy實例
import org.openqa.selenium.WebElement;import org.openqa.selenium.support.FindBy;...@FindBy(someStrategy) //for browser or web view html UI//also for mobile native applications when other locator strategies are not definedWebElement someElement;@FindBy(someStrategy) //for browser or web view html UI//also for mobile native applications when other locator strategies are not definedList<WebElement> someElements; |
|---|
(2)指定具體的定位策略。如程式碼清單11-34所示,根據Desired Capability中設置的automationName自動化測試引擎的值,針對移動原生應用(Native App),分別使用"@ AndroidFindBy""@ SelendroidFindBy"和"@ iOSFindBy"進行註解,元素類型為AndroidElement、RemoteWebElement以及IOSElement。
程式碼清單11-34 指定具體的定位策略
import io.appium.java_client.android.AndroidElement;import org.openqa.selenium.remote.RemoteWebElement;import io.appium.java_client.pagefactory.*;import io.appium.java_client.ios.IOSElement;@AndroidFindBy(someStrategy) //for Android UI when Android UI automator is usedAndroidElement someElement;@AndroidFindBy(someStrategy) //for Android UI when Android UI automator is usedList<AndroidElement> someElements;@SelendroidFindBy(someStrategy) //for Android UI when Selendroid automation is usedRemoteWebElement someElement;@SelendroidFindBy(someStrategy) //for Android UI when Selendroid automation is usedList<RemoteWebElement> someElements;@iOSFindBy(someStrategy) //for iOS native UIIOSElement someElement;@iOSFindBy(someStrategy) //for iOS native UIList<IOSElement> someElements; |
|---|
(3)跨平台的原生App測試實例,如程式碼清單11-35所示。針對原生App,使用"@AndroidFindBy"和"@iOSFindBy"同時進行註解。元素的類型為MobileElement。
程式碼清單11-35 跨平台的原生App測試實例
import io.appium.java_client.MobileElement;import io.appium.java_client.pagefactory.*;@AndroidFindBy(someStrategy)@iOSFindBy(someStrategy)MobileElement someElement;@AndroidFindBy(someStrategy) //for the crossplatform mobile native@iOSFindBy(someStrategy) //testingList<MobileElement> someElements; |
|---|
(4)全平台的測試實例,如程式碼清單11-36所示。其中使用"@FindBy""@AndroidFindBy"以及"@iOSFindBy"同時進行註解。元素的類型為RemoteWebElement。
程式碼清單11-36 全平台的測試實例
import org.openqa.selenium.remote.RemoteWebElement;import io.appium.java_client.pagefactory.*;import org.openqa.selenium.support.FindBy;//the fully cross platform examle@FindBy(someStrategy) //for browser or web view html UI@AndroidFindBy(someStrategy) //for Android native UI@iOSFindBy(someStrategy) //for iOS native UIRemoteWebElement someElement;//the fully cross platform examle@FindBy(someStrategy)@AndroidFindBy(someStrategy) //for Android native UI@iOSFindBy(someStrategy) //for iOS native UIList<RemoteWebElement> someElements; |
|---|
(5)用Chained或者Possible定位方式。
" Chained定位方式。使用"@FindBys""@AndroidFindBys"和"@iOSFindBy"進行註解。元素內容通過多種定位方法找到。FindBys相當於在多種定位方式中取交集,如"@FindBys({@FindBy(someStrategy1)""@FindBy(someStrategy2)})"相當於首先根據someStrategy1找到對應元素,然後在這些元素中通過someStrategy2再次查找元素,這類似於driver.findelement(someStrategy1). findelement(someStrategy2),如程式碼清單11-37和程式碼清單11-38所示。
程式碼清單11-37 Chained定位方式之一
import org.openqa.selenium.remote.RemoteWebElement; import io.appium.java_client.pagefactory.*; import org.openqa.selenium.support.FindBys; import org.openqa.selenium.support.FindBy; @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2) RemoteWebElement someElement; @FindBys({@FindBy(someStrategy1), @FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2) List<RemoteWebElement> someElements;程式碼清單11-38 Chained定位方式之二
importorg.openqa.selenium.remote.RemoteWebElement; importio.appium.java_client.pagefactory.*; importorg.openqa.selenium.support.FindBys; importorg.openqa.selenium.support.FindBy; importstaticio.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN; @HowToUseLocators(androidAutomation=CHAIN,iOSAutomation=CHAIN) @FindBys({@FindBy(someStrategy1),@FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1)@AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1)@iOSFindBy(someStrategy2) RemoteWebElementsomeElement; @HowToUseLocators(androidAutomation=CHAIN,iOSAutomation=CHAIN) @FindBys({@FindBy(someStrategy1),@FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1)@AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1)@iOSFindBy(someStrategy2) List<RemoteWebElement>someElements;" Possible定位方式。如程式碼清單11-39所示,這種定位方式指使用"@FindAll""@AndroidFindAll"和"@iOSFindAll"進行註解。FindAll相當於在多種定位方式中取並集,如"@FindAll{@FindBy(someStrategy1)","@FindBy(someStrategy2)})"相當於取到所有符合someStrategy1和someStrategy2的元素。
程式碼清單11-39 Possible定位方式
import org.openqa.selenium.remote.RemoteWebElement; import io.appium.java_client.pagefactory.*; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindByAll; import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE; @HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE) @FindAll{@FindBy(someStrategy1), @FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2) RemoteWebElement someElement; @HowToUseLocators(androidAutomation = ALL_POSSIBLE, iOSAutomation = ALL_POSSIBLE) @FindAll({@FindBy(someStrategy1), @FindBy(someStrategy2)}) @AndroidFindBy(someStrategy1) @AndroidFindBy(someStrategy2) @iOSFindBy(someStrategy1) @iOSFindBy(someStrategy2) List<RemoteWebElement> someElements;星雲測試
http://www.teststars.cc
奇林軟體
http://www.kylinpet.com
聯合通測
http://www.quicktesting.net

