diff --git a/docs/Installing-xcuitest-driver.md b/docs/Installing-xcuitest-driver.md new file mode 100644 index 000000000..9f3f7d158 --- /dev/null +++ b/docs/Installing-xcuitest-driver.md @@ -0,0 +1,19 @@ +## Setting Up XCUITest +--- + +**Install Appium XCUITest Driver** + +Clone Appium XCUITest Driver repo https://github.com/appium/appium-xcuitest-driver + +``` +git clone --recursive https://github.com/appium/appium-xcuitest-driver.git +cd appium-xcuitest-driver +npm install +``` + +Set your appium path to the appropriate location (Where you cloned appium-xcuitest-driver) and you can +start the driver by doing: + +``` +node . +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 74915299b..6aa789404 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + 4.0.0 io.appium @@ -63,6 +64,16 @@ commons-lang3 3.4 + + net.lingala.zip4j + zip4j + 1.3.2 + + + commons-io + commons-io + 2.1 + jar java-client diff --git a/src/main/java/io/appium/java_client/FindsByIosNsPredicate.java b/src/main/java/io/appium/java_client/FindsByIosNsPredicate.java new file mode 100644 index 000000000..3772421c6 --- /dev/null +++ b/src/main/java/io/appium/java_client/FindsByIosNsPredicate.java @@ -0,0 +1,11 @@ +package io.appium.java_client; + +import org.openqa.selenium.WebElement; + +import java.util.List; + +public interface FindsByIosNsPredicate { + T findElementByIosNsPredicate(String using); + + List findElementsByIosNsPredicate(String using); +} diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index e885fd830..69d97f6c3 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -72,6 +72,19 @@ public static By AccessibilityId(final String id) { return new ByAccessibilityId(id); } + /** + * This locator strategy is available in XCUITest Driver mode + * @param iOSNsPredicateString is an an iOS NsPredicate String + * @return an instance of {@link io.appium.java_client.MobileBy.ByIosNsPredicate} + */ + public static By IosNsPredicateString(final String iOSNsPredicateString) { + if (iOSNsPredicateString == null) { + throw new IllegalArgumentException("Must supply an iOS NsPredicate String"); + } + + return new ByIosNsPredicate(iOSNsPredicateString); + } + public static class ByIosUIAutomation extends By implements Serializable { private final String automationText; @@ -97,6 +110,30 @@ public List findElements(SearchContext context) { } } + public static class ByIosNsPredicate extends By implements Serializable { + + private final String automationText; + + public ByIosNsPredicate(String uiautomationText) { + automationText = uiautomationText; + } + + @SuppressWarnings("unchecked") + @Override public List findElements(SearchContext context) { + return (List) ((FindsByIosNsPredicate) context) + .findElementsByIosNsPredicate(automationText); + } + + @Override public WebElement findElement(SearchContext context) { + return ((FindsByIosNsPredicate) context) + .findElementByIosNsPredicate(automationText); + } + + @Override public String toString() { + return "By.IosNsPredicate: " + automationText; + } + } + public static class ByAndroidUIAutomator extends By implements Serializable { diff --git a/src/main/java/io/appium/java_client/ios/IOSDriver.java b/src/main/java/io/appium/java_client/ios/IOSDriver.java index abc0ba774..57fda00e3 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -24,6 +24,7 @@ import io.appium.java_client.AppiumDriver; import io.appium.java_client.FindsByAccessibilityId; +import io.appium.java_client.FindsByIosNsPredicate; import io.appium.java_client.FindsByIosUIAutomation; import io.appium.java_client.ScrollsTo; import io.appium.java_client.ios.internal.JsonToIOSElementConverter; @@ -53,9 +54,10 @@ public class IOSDriver extends AppiumDriver implements IOSDeviceActionShortcuts, GetsNamedTextField, - FindsByIosUIAutomation { - private static final String IOS_PLATFORM = MobilePlatform.IOS; + FindsByIosUIAutomation, FindsByIosNsPredicate { + private static final String IOS_PLATFORM = MobilePlatform.IOS; + /** * @param remoteAddress is the address * of remotely/locally started Appium server @@ -247,6 +249,28 @@ public List findElementsByIosUIAutomation(String using) throws WebDriverException { return (List) findElements("-ios uiautomation", using); } + + /** + * @throws org.openqa.selenium.WebDriverException + * This method is not applicable with browser/webview UI. + */ + @SuppressWarnings("unchecked") + @Override + public T findElementByIosNsPredicate(String using) + throws WebDriverException { + return (T) findElement("-ios predicate string", using); + } + + /** + * @throws org.openqa.selenium.WebDriverException + * This method is not applicable with browser/webview UI. + */ + @SuppressWarnings("unchecked") + @Override + public List findElementsByIosNsPredicate(String using) + throws WebDriverException { + return (List) findElements("-ios predicate string", using); + } /** * Lock the device (bring it to the lock screen) for a given number of diff --git a/src/main/java/io/appium/java_client/ios/IOSElement.java b/src/main/java/io/appium/java_client/ios/IOSElement.java index 0d522bb6f..d8845985e 100644 --- a/src/main/java/io/appium/java_client/ios/IOSElement.java +++ b/src/main/java/io/appium/java_client/ios/IOSElement.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMap; +import io.appium.java_client.FindsByIosNsPredicate; import io.appium.java_client.FindsByIosUIAutomation; import io.appium.java_client.MobileCommand; import io.appium.java_client.MobileElement; @@ -30,8 +31,8 @@ import java.util.List; public class IOSElement extends MobileElement - implements FindsByIosUIAutomation, ScrollsTo { - + implements FindsByIosUIAutomation, ScrollsTo, + FindsByIosNsPredicate { /** * @throws WebDriverException * This method is not applicable with browser/webview UI. @@ -42,7 +43,8 @@ public class IOSElement extends MobileElement } /** - * @throws WebDriverException This method is not applicable with browser/webview UI. + * @throws WebDriverException + * This method is not applicable with browser/webview UI. */ @Override public List findElementsByIosUIAutomation(String using) throws WebDriverException { @@ -53,6 +55,39 @@ public class IOSElement extends MobileElement } return result; } + + /** + * @throws org.openqa.selenium.WebDriverException + * This method is not applicable with browser/webview UI. + */ + @Override public MobileElement findElementByIosNsPredicate(String using) + throws WebDriverException { + return (IOSElement) findElement("-ios predicate string", using); + } + + /** + * @throws WebDriverException This method is not applicable with browser/webview UI. + */ + @Override public List findElementsByIosNsPredicate(String using) + throws WebDriverException { + List result = new ArrayList(); + List found = findElements("-ios predicate string", using); + for (WebElement e : found) { + result.add((IOSElement) e); + } + return result; + } + + /** + * Scroll to the element whose 'text' attribute contains the input text. + * Scrolling happens within this element + * + * @param text input text contained in text attribute + */ + @Override public MobileElement scrollTo(String text) { + return (IOSElement) findElementByIosUIAutomation( + ".scrollToElementWithPredicate(\"name CONTAINS '" + text + "'\")"); + } /** * Scroll to the element whose 'text' attribute contains the input text. diff --git a/src/test/java/io/appium/java_client/ios/XCUIDriverTest.java b/src/test/java/io/appium/java_client/ios/XCUIDriverTest.java new file mode 100644 index 000000000..022ee7843 --- /dev/null +++ b/src/test/java/io/appium/java_client/ios/XCUIDriverTest.java @@ -0,0 +1,112 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.ios; + +import static org.junit.Assert.assertTrue; + +import io.appium.java_client.MobileBy; +import io.appium.java_client.MobileElement; +import io.appium.java_client.remote.IOSMobileCapabilityType; +import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import net.lingala.zip4j.core.ZipFile; +import net.lingala.zip4j.exception.ZipException; +import org.apache.commons.io.FileUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openqa.selenium.remote.DesiredCapabilities; + +import java.io.File; +import java.io.IOException; + +public class XCUIDriverTest { + + private static final String SOURCE = "src/test/java/io/appium/java_client/"; + private static AppiumDriverLocalService service; + protected static IOSDriver driver; + + /** + * initialization. + */ + @BeforeClass + public static void beforeClass() throws Exception { + service = AppiumDriverLocalService.buildDefaultService(); + service.start(); + + if (service == null || !service.isRunning()) { + throw new RuntimeException("An appium server node is not started!"); + } + + String source = SOURCE + "UICatalog.app.zip"; + + try { + ZipFile zipFile = new ZipFile(source); + zipFile.extractAll(SOURCE); + } catch (ZipException e) { + String msg = "Could not extract file"; + throw new ZipException(msg, e); + } + + File appDir = new File(SOURCE); + File app = new File(appDir, "UICatalog.app"); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); + capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "9.3"); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone 6"); + //sometimes environment has performance problems + capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000); + capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + driver = new IOSDriver<>(service.getUrl(), capabilities); + } + + /** + * finishing. + */ + @AfterClass + public static void afterClass() throws IOException { + if (driver != null) { + driver.quit(); + } + if (service != null) { + service.stop(); + } + try { + FileUtils.deleteDirectory(new File(SOURCE + "/UICatalog.app")); + } catch (IOException e) { + throw e; + } + + } + + //TODO There is no ability to check this function usibg simulators. + // When CI will have been set up then this test will be returned + public void getDeviceTimeTest() { + String time = driver.getDeviceTime(); + assertTrue(time.length() == 28); + } + + /** + * Verifies UICatalog element is present in view. + */ + @Test + public void getiOSElementByPredicate() { + //Needs to run on the XCUITest ios Driver (https://github.com/appium/appium-xcuitest-driver.git). + driver.findElement(MobileBy.IosNsPredicateString("identifier == \"UICatalog\"")); + } + +}