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..d7d22f6d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,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..9cf0264af 100644
--- a/src/main/java/io/appium/java_client/MobileBy.java
+++ b/src/main/java/io/appium/java_client/MobileBy.java
@@ -71,7 +71,15 @@ public static By AccessibilityId(final String id) {
return new ByAccessibilityId(id);
}
-
+
+ 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;
@@ -96,6 +104,29 @@ public List findElements(SearchContext context) {
return "By.IosUIAutomation: " + automationText;
}
}
+ 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..a8ec97019 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,9 @@
public class IOSDriver
extends AppiumDriver
implements IOSDeviceActionShortcuts, GetsNamedTextField,
- FindsByIosUIAutomation {
+ FindsByIosUIAutomation, FindsByIosNsPredicate{
private static final String IOS_PLATFORM = MobilePlatform.IOS;
-
+
/**
* @param remoteAddress is the address
* of remotely/locally started Appium server
@@ -67,6 +68,8 @@ public IOSDriver(URL remoteAddress, Capabilities desiredCapabilities) {
JsonToIOSElementConverter.class);
}
+ }
+
/**
* @param remoteAddress is the address
* of remotely/locally started Appium server
@@ -247,6 +250,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..3ebce8433
--- /dev/null
+++ b/src/test/java/io/appium/java_client/ios/XCUIDriverTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import java.io.File;
+import java.io.IOException;
+
+import net.lingala.zip4j.exception.ZipException;
+import net.lingala.zip4j.core.ZipFile;
+import org.apache.commons.io.FileUtils;
+
+import io.appium.java_client.remote.HideKeyboardStrategy;
+import io.appium.java_client.remote.IOSMobileCapabilityType;
+import io.appium.java_client.remote.MobileCapabilityType;
+import io.appium.java_client.service.local.AppiumDriverLocalService;
+
+import org.junit.Test;
+
+import io.appium.java_client.MobileBy;
+import io.appium.java_client.MobileElement;
+
+import org.junit.*;
+import org.openqa.selenium.Point;
+import org.openqa.selenium.ScreenOrientation;
+import org.openqa.selenium.html5.Location;
+
+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() {
+ if (driver != null) {
+ driver.quit();
+ }
+ if (service != null) {
+ service.stop();
+ }
+ try {
+ FileUtils.deleteDirectory(new File(SOURCE + "/UICatalog.app"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ //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\""));
+ }
+
+}