How to migrate from Appium 1.x.x to Appium 2.0.

Elizabeth Grillo
11 min readFeb 8, 2024

--

A step-by-step guide

If you need a step-by-step help with the migration from Appium 1.x to Appium 2.0, especially if you don’t have much experience, this article might be for you. Why? Because I understand how stressful technical changes can be, and I also recognize how a step-by-step guide could make the process easier.

For me, the problems with the old WebDriver started after upgrading my Mac to Sonoma and Xcode to 17.4. These issues forced me to perform an unplanned upgrade from Appium 1.22.3 to Appium 2.0. Don’t get me wrong; I knew I was going to do this at some point, but it would have been nice to choose the ‘when’ and plan accordingly. You must know that there are several changes within Appium 2.0. After the upgrade, you’ll need to refactor your scripts for sure.

Let's get started! 😊

This was my environment before the upgrade:

  • macOS Sonoma — 14.3
  • Previous Appium installed: 1.22.3
  • Previous Java-client installed: 7.0.0
  • Previous Selenium-Java installed: 3.141.59
  • Also, my project is a Maven project.

— —

First things first: Node JS and NPM

Node JS: If you have Appium 1.x installed, you also have Node JS installed.

For reference, it’s expected to have ^14.17.0 || ^16.13.0 || >=18.0.0. If you want to check your version, open the Terminal and run:

node -v

This will tell you the current default versions.

You could also have more than one version installed! You can check the list of your Node JS versions installed by running this command:

nvm list

In my case, I already had 3 versions installed. I simply marked the newest version to be the one I want to use by default, by running this command:

nvm use [version] -> in my case ->  $nvm use v19.7.0

In case you need to download the latest version, here’s the official website: https://nodejs.org/en/download

NPM: it’s expected to have version ≥ v8. If you want to check your version, run:

npm -v

If you need to update it, run this command:

npm install -g npm@latest

Then, check your version again to ensure everything is fine.

Java Client

The required version is ≥8. For this, you need to edit your pom.xml file in your Maven project. You should change the version of this dependency:

<!-- https://mvnrepository.com/artifact/io.appium/java-client -->
<dependency>
<groupId>io.appium</groupId>
<artifactId>java-client</artifactId>
<version>8.6.0</version>
</dependency>

You can go to that Maven URL and check the latest version available.

— —

Note: When I made this change, some other Java problems came up:

  1. I had to download Java SE Development Kit 11.0.2 from the official Oracle website: https://www.oracle.com/ar/java/technologies/javase/jdk11-archive-downloads.html
  2. Modified the maven.compiler.source and maven.compiler.target to 11 in the pom.xml file of the project:
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

3. On IntelliJ, I had to go to file -> project Structure -> SDK and select 11. If it’s not in the menu, you can just download it from there. I downloaded the Amazon Corretto version, and it’s working fine.

4) And lastly, I had to edit my JAVA_HOME. To know what your current JAVA_HOME is, you can run: echo $JAVA_HOME

lizzygrillo@Lizzies-MacBook-Pro-2 ~ % echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk-11.0.21.jdk/Contents/Home

It’s important for you to know that there are newer versions of Java SE Development Kit. I just picked version 11 because it is currently the most commonly used version, but it’s should work with newer versions too.

Upgrade your Selenium version

The new Java Client 8 requires a new Selenium version. This change is related to strict W3C compliant.

When using Selenium 3.x.x, the JSON wire protocol served as the mediator between the client libraries and the web drivers. However, this protocol is no longer supported after upgrading your Java Client to v8.

Selenium 4.x.x strictly supports W3C WebDriver standard’s new protocol.

Go to your project’s pom.xml file and change this dependency:

<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.8.1</version>
</dependency>

Now, let’s take care of Appium

If you already have the Appium 1.x installed, the first thing you need to do is to uninstall it before installing the new 2.0. You can do this by running:

npm uninstall -g appium

Then you need to check that it really got uninstalled. Run:

appium -v

It should bring up nothing.

Problem you might encounter: If appium -v brings up a version, it means that it’s still installed somewhere in your system. You can do the following:

which appium

This will throw you the path to the Appium folder still present in your system. Go and delete it manually. Then run appium -v again until it shows nothing.

The installation of Appium 2.0

You could install Appium 2.0, each driver, and plugins separately. However, the Appium-Installer tool makes the installation pretty easy. So, if you are going to use this installer, let’s install it first 😊

npm install appium-installer -g

Then, open it:

appium-installer

Follow this installer. It’s pretty straightforward. Just move up, down, and press enter to select each element you want to install. You will be able to install Appium 2.0 (Appium Server), the drivers, and the plugins you need. Additionally, you can set up environments for Android and iOS in case you automate native apps.

Drivers: When I installed the drivers, I selected npm as the option to install them (it’s going to ask this before installing). Pick another method if it’s convenient for you. Otherwise, npm works fine.

In my case, I only installed these ones:

- uiautomator2@2.44.0 
- xcuitest@5.15.1.

This is because I automate Android and iOS native apps on simulators and real devices. I don’t need any other drivers besides these, but pay attention to what you need because there are more drivers available:

- mac2 
- espresso
- safari
- gecko
- chromium

After the installation, if you want to check what drivers you have, just run:

appium driver list

Plugins: This is one of the new features within Appium 2. In my case, I installed the following:

For me, there are 2 plugins that I considered important to install: element-wait and gestures.

— —

I’d like to talk about this a little bit more, so I’m going to add a special section here. There are new things within the Appium Java Client 8. Waiters and Gestures are part of the changes.

  • Waiters: We used to use java.util.concurrent.TimeUnit class, and we passed the time as an int:
import java.util.concurrent.TimeUnit;

driver.manage().timeouts().implicitlyWait(500, TimeUnit.SECONDS);

Now, we are going to use another class: java.time.Duration class and its methods. Here is an example:

WaitersPage class:

import java.time.Duration;

public static boolean explicitWaitForElement(By locator, int duration){
try {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(duration));
wait.until(visibilityOfElementLocated(locator));
return true;
} catch (TimeoutException e) {
System.out.println("Throw Time out. The element wasn't visible");
return false;
}
}

---

@Test class:
WaitersPage.explicitWaitForElement([element], 60);
  • New gestures: The TouchAction and MultiTouchAction classes have been deprecated (and the support of these actions will be removed from future Appium versions).

You can check out this video to better understand the new gestures: https://www.youtube.com/watch?v=oAJ7jwMNFVU&ab_channel=ConfEngine

Just to give you an idea of the new gestures you’ll need to use, take a look at this scrollDown():

public void scrollDownAppium2() {
//We first need to calculate the starting position. For our example, we will use the middle of the screen:
int startX = driver.manage().window().getSize().getWidth() / 2;
int startY = (int) (driver.manage().window().getSize().getHeight() * 0.8);

//we also need to calculate the ending position of the scrollDown/swipe.
//Since we will perform a vertical swipe we will only calculate the vertical coordinate.
//We will set it to be 25% of the screen height from the top
int endX = startX;
int endY = (int) (driver.manage().window().getSize().getHeight() * 0.50);

//Now we need our pointer input that will represent a touch input device:
PointerInput finger1 = new PointerInput (PointerInput.Kind.TOUCH, "finger1");

//We will call our sequence object scrollDown and associate it with the finger input device:
Sequence sequence = new Sequence(finger1, 1)
//moving the pointer to the starting position
.addAction(finger1.createPointerMove (Duration.ZERO, PointerInput.Origin.viewport(), startX, startY))
//simulating a touch-down event on the screen
.addAction (finger1.createPointerDown (PointerInput.MouseButton. LEFT.asArg()))
//.addAction(new Pause(finger1, Duration.ofMillis(100)))
//moving the pointer from the starting position to the ending position for 100 milliseconds
.addAction(finger1.createPointerMove (Duration.ofMillis(100), PointerInput.Origin.viewport(), endX, endY))
//performing a touch-up event
.addAction(finger1.createPointerUp (PointerInput.MouseButton.LEFT.asArg()));
driver.perform(Collections.singletonList(sequence));
print("Scrolled down");
}

As you can see, it’s al little bit complicated. But with the plugin, this could be a little bit simpler. Take a look:

public void scrollUntilElementPlugin(By viewLocator, String locator) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(60));

//viewLocator is the screen where the element is present
RemoteWebElement scrollView = (RemoteWebElement) wait.until(presenceOfElementLocated(viewLocator));

//locator is the element you want to find
driver.executeScript("gesture: scrollElementIntoView", ImmutableMap.of("scrollableView", scrollView.getId(),
"strategy", "id",
"selector", locator,
"percentage", 50,
"direction", "up",
"maxCount", 5));
}

Notes: viewLocator is the screen where the element is located.

You could also have a generic scrollDown() without looking for a particular element:

public void scrollDownPlugin(By viewLocator) {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(60));
RemoteWebElement scrollView = (RemoteWebElement) wait.until(presenceOfElementLocated(viewLocator));

driver.executeScript("gesture: swipe", ImmutableMap.of("elementId", scrollView.getId(),
"percentage", 50,
"direction", "up"));
}

Note: I personally use all methods, the one from Appium and the one from the plugin.

Turn on the server

Once Appium is installed, let’s turn it on. So, the old appium command is still the one you need to run. But if you also installed plugins, you need to include this in the command every time you turn on the Appium server:

You don’t want any installed plugin activated:

appium

Or, choose some or all installed plugins to be activated in this session.

appium --use-plugins=element-wait,gestures

Anyway, after turning it on, you’ll get something like this:

As you can see, it lists the drivers and plugins I have installed as well as the plugins that are going to be ‘active’ for this session. There’s no way to make the plugins permanently active. You’ll need to specify which ones you want ‘active’ every session.

Now, let’s talk about capabilities and your Base.class

Now that we have everything in place and the Appium 2 server up, we want to run our projects, right? Well, let me tell you that there are also changes in the capabilities that we need to fix before starting to work.

I’m going to describe the capabilities needed to automate Android and iOS apps.

  1. The first thing is that we don’t use DesiredCapabilities anymore. Now we use Options. This is the class we need to import for each platform:
//For iOS
import io.appium.java_client.ios.options.XCUITestOptions;

//For Android
import io.appium.java_client.android.options.UiAutomator2Options;

This class already includes some data. This means that we are not going to specify all the capabilities we used to do.

-For iOS, there’s no need to clarify these two anymore since they are already included in the XCUITestOptions class (you can access this class and check it for yourself):

caps.setCapability(MobileCapabilityType.PLATFORM_NAME, "IOS");
caps.setCapability("automationName", "XCUITest");

So, your Base class will resemble something like this:

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.testng.annotations.BeforeSuite;

public class Base {

public static IOSDriver driver;

//APP
public static final String APP_LOCATION = "appPath";

@BeforeSuite
public static void setUp() {

XCUITestOptions options = new XCUITestOptions();
options.setDeviceName("iPhone 14");
options.setPlatformVersion("16.4");
options.setApp(APP_LOCATION);

//--Appium server
URL url = new URL("http://127.0.0.1:4723");
driver = new IOSDriver(url, options);

}


@AfterSuite
(...)
}

You can add more capabilities if needed.

-Similarly for Android, there’s no need to specify these two anymore because they are already defined in the UiAutomator2Optionsclass:

capabilities.setCapability("platformName", "Android");
capabilities.setCapability("automationName","UiAutomator2");

So, your Base class will resemble something like this:

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;

import org.testng.annotations.BeforeSuite;
import java.io.File;
import java.net.URL;

public class Base {

public static AndroidDriver driver;

//APP
final String APKPATH = "appPath";
File app = new File(APKPATH);

@BeforeSuite
public void setUp() {

UiAutomator2Options options = new UiAutomator2Options();
options.setDeviceName("Pixel 2");
options.setApp(app.getAbsolutePath());

//Appium server
driver = new AndroidDriver(new URL("http://127.0.0.1:4723"), options);
}


@AfterSuite
(...)
}

You can add more capabilities if needed.

2) Another thing you might already noticed is the change in the Appium Server URL.

We don’t point to http://127.0.0.1:4723/wd/hub anymore. This is the new URL:

http://127.0.0.1:4723/

Without the wd/hub.

3) There are some methods that have been deprecated and we can’t use them anymore:

resetApp()
launchApp()
closeApp()

You can replace them with the following methods:

terminateApp("bundleID");
removeApp("bundleID");
installApp("appPath");
activateApp("bundleID");

Checking the official Appium discussions page (the link is at the bottom of this article), I found these useful definitions to understand which ones I’d need to use for each case:

installApp(): Installs the app from the given path or URL to the device under test. Works similarly to the app capability with the difference that the app installation could be performed mid-session. Useful to test app upgrades.

removeApp(): Uninstalls the given app identifier from the device under test mid-session. In combination with installApp, it might be useful to reset app data (iOS does not support any other ways to do that).

terminateApp(): Stops the given app on the device under test if it was running before. Could be useful in combination with activateApp to verify app restart.

activateApp(): Puts the app to the foreground if it was minimized or starts a new instance if it was not running. Useful to test minimize/restore scenarios or switching between apps.

And lastly, let’s talk about locators

Take into consideration the following changes:

  1. MobileBy has been deprecated in Appium 2. It has been replaced by the AppiumBy class. For example, before Appium 2 we could find this element following this strategy:
protected By text = MobileBy.AccessibilityId("text");

Now, we need to find it using AppiumBy:

protected By text = AppiumBy.accessibilityId("text");

2. MobileElement classes (AndroidElement and iOSElement) have also been deprecated. You can use the WebElement class instead.

3. All the findElementsBy[something] were also removed. Now the new format is: findElement[s](By. or AppiumBy.). For example, before:

driver.findElementByClassName("className");
driver.findElementsByClassName("className");

Now:

driver.findElement(By.className("className"));
driver.findElements(By.className("className"));

Conclusion

I’m still learning the new features of Appium 2.

The installation was quite easy because the Appium Installer makes it easier.

I found the new gestures challenging to deal with until I got used to them. It’s important to create your own methods to make them easy to use.

I’m glad that we don’t need as many capabilities anymore.

As for waiters, I managed to create my own and they work fine.

Regarding the deprecated methods, I had to learn how to replace them, and that was it.

In general, I perceive that Appium 2 is faster than the previous version.

Final disclaimer: It’s important for you to know that I’m senior mobile QA, but I’m not senior in automation. I don’t claim to cover everything with this article. If you can’t find the right answer here, I encourage you to keep searching the web. Everything I’ve posted here is based on my own steps, learning, and research.

Important links:

--

--