Clear the Safari cache on iOS devices

There is no existing function to clear the cache of the Safari browser, but you can create a script that mimics the behavior of a real user. The script includes the following steps: 

  1. On the iOS device, open the Settings app.
  2. Scroll down and find Safari.
  3. Click Safari.
  4. Scroll down and click Clear History and Website Data.

The following script is Appium 2-compliant and has been tested on iPhones and iPads running versions of iOS 14, 15, 16, 17, and 18.

Copy
package remote.pureAppium;

import com.perfecto.reportium.client.ReportiumClient;
import com.perfecto.reportium.client.ReportiumClientFactory;
import com.perfecto.reportium.model.Job;
import com.perfecto.reportium.model.PerfectoExecutionContext;
import com.perfecto.reportium.model.Project;
import com.perfecto.reportium.test.TestContext;
import com.perfecto.reportium.test.result.TestResultFactory;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.ITestResult;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.Duration;
import java.util.HashMap;

import static java.lang.Integer.parseInt;

public class IOS_Clear_Safari_Cache {

  IOSDriver driver;
  int iosMajorVersion;
  static ReportiumClient reportiumClient;
  String cloudName = "demo";

  @BeforeClass
  public void setUp() throws Exception {
    var options = new XCUITestOptions()
//        .setDeviceName("00008101-00051D820C03001E")
         ;

    var perfectoOptions = new HashMap<>();
//    perfectoOptions.put("model", ".*iPad.*");
//    perfectoOptions.put("platformVersion", "18.*");
    perfectoOptions.put("securityToken", "");
    options.setCapability("perfecto:options", perfectoOptions);

    driver = new IOSDriver(
        new URL("https://" + cloudName + ".perfectomobile.com/nexperience/perfectomobile/wd/hub"), options
    );

    var perfectoExecutionContext = new PerfectoExecutionContext.PerfectoExecutionContextBuilder()
        .withProject(new Project("Perfecto.Support IOS tests", "1.0"))
        .withJob(new Job("Clear Safari Cache", 3))
        .withWebDriver(driver)
        .build();
    reportiumClient = new ReportiumClientFactory().createPerfectoReportiumClient(perfectoExecutionContext);

    reportiumClient.testStart("Clear Safari Cache on iOS", new TestContext());
  }


  @AfterMethod
  public void testEnded(ITestResult result) {
    if (result.getStatus() == ITestResult.FAILURE) {
      reportiumClient.testStop(TestResultFactory.createFailure(result.getThrowable()));
    }
    else {
      reportiumClient.testStop(TestResultFactory.createSuccess());
    }
  }

  @AfterClass
  public void tearDown() throws URISyntaxException, IOException {
    driver.quit();
//    java.awt.Desktop.getDesktop().browse(new URI(reportiumClient.getReportUrl()));
  }

  @Test
  public void clearSafariCache() throws Exception {

    WebElement element;
    iosMajorVersion = getIosVersion(driver);
    var params = new HashMap<>();
    params.put("identifier", "com.apple.Preferences");
    driver.executeScript("mobile:application:open", params);
    driver.executeScript("mobile:application:close", params);
    driver.executeScript("mobile:application:open", params);

    driver.context("NATIVE_APP");

    if (iosMajorVersion >= 18) {
      element = findAppsContainerButton(driver);
      element.click();
    }

    element = findSafariContainerButton(driver);
    element.click();

    element = findClearCacheButton(driver);
    if (!element.isEnabled()) {
      System.out.println("'Clear History and Website Data' button is disabled, which means that cache is already cleared! Exiting");
      return;
    }
    element.click();

    if (iosMajorVersion >= 17) {
      clearCacheInPopup(driver);
    }
    else {
      driver.findElement(AppiumBy.xpath("//XCUIElementTypeButton[starts-with(@label,'Clear')]"))
          .click();

      // Close Tabs button was added in iOS 15.x so it might be missing on some devices
      try {
        driver.findElement(AppiumBy.xpath("//XCUIElementTypeButton[@label=\"Close Tabs\"]"))
            .click();
      } catch (Exception ex) {
      }
    }

    Thread.sleep(500);

    if (driver.findElement(AppiumBy.xpath("//*[@name=\"Clear History and Website Data\"]")).isEnabled()) {
      throw new Exception("Clear Safari cache result not expected. Clear History and Website Data button is still enabled");
    }
  }

  private int getIosVersion(IOSDriver driver) {
    var versionString = getDeviceInfo(driver, "osVersion");
    var parts = versionString.split("\\.");
    return parts.length > 1 ? parseInt(parts[0]) : parseInt(versionString);
  }

  private WebElement findAppsContainerButton(IOSDriver driver) throws Exception {
    int current = 1;
    int maxRetries = 7;
    var exception =  new Exception(String.format("Apps element not found after %s retries ", maxRetries));
    var params = new HashMap<>();
    params.put("start", "10%,90%");
    params.put("end", "10%,20%");
    params.put("duration", "1");

    while (current <= maxRetries) {
      driver.executeScript("mobile:touch:swipe", params);
      Thread.sleep(500);

      try {
        return driver.findElement(AppiumBy.xpath("//*[@label=\"Apps\"]"));
      } catch (Exception ex) {
        exception = ex;
      }
      current++;
    }
    throw exception;
  }

  public static String getDeviceInfo(IOSDriver driver, String deviceProperty){
    var params = new HashMap<>();
    params.put("property", deviceProperty);
    return (String) driver.executeScript("mobile:device:info", params);
  }

  public WebElement findSafariContainerButton(IOSDriver driver) throws Exception {
    var params = new HashMap<>();
    // On iPad devices the Settings app is split in 2 columns
    // On iOS >= 18 Safari Settings is under the new App section, so on iPad devices
    // if iOS >= 18 we are already within the App section so swipe on the right hand side
    // If iOS <= 17 and lower, we are still in the main Settings container, so swipe on the left hand side
    var x = iosMajorVersion >= 18 ? "90%" : "10%";
    params.put("start", x + ",90%");
    params.put("end", x + ",30%");
    params.put("duration", "2");
    int currentRetry = 1;
    int maxRetries = 7;
    var exception = new Exception(String.format("Safari settings element not found after %s retries ", maxRetries));

    while (currentRetry <= maxRetries) {

      driver.executeScript("mobile:touch:swipe", params);
      Thread.sleep(500);

      try {
        return driver.findElement(AppiumBy.xpath("//*[@value='Safari' and @visible='true']"));
      } catch (Exception ex) {
        exception = ex;
      }
      currentRetry++;
    }
    throw exception;
  }

  private static WebElement findClearCacheButton(IOSDriver driver) throws Exception {
    int current = 1;
    int maxRetries = 3;
    var exception = new Exception(String.format("'Clear History and Website Data' element not found after %s retries ", maxRetries));
    var params = new HashMap<>();
    params.put("start", "90%,90%");
    params.put("end", "90%,20%");
    params.put("duration", "1");

    while (current <= maxRetries) {

      driver.executeScript("mobile:touch:swipe", params);
      Thread.sleep(500);

      try {
        return driver.findElement(AppiumBy.xpath("//*[@name=\"Clear History and Website Data\"]"));
      } catch (Exception ex) {
        exception = ex;
      }
      current++;
    }
    throw exception;
  }


  private static void clearCacheInPopup(IOSDriver driver) throws InterruptedException {
    var wait = new WebDriverWait(driver, Duration.ofSeconds(5));

    var element = wait.until(ExpectedConditions.elementToBeClickable(AppiumBy.xpath("//*[@label=\"All history\"]")));
    element.click();

    Thread.sleep(500);

    element = driver.findElement(AppiumBy.id("CloseAllTabsSwitch"));
    if (element.getAttribute("value").equals("0"))
        element.click();

    Thread.sleep(500);
    driver.findElement(AppiumBy.id("ClearHistoryButton"))
        .click();
  }
}