We've already seen how to capture browser logs for iOS; the same can be done for Android! The technique is largely similar but varies in some respects. As a reminder of why we might want to capture logs and errors from a browser, we could:
And of course, just as in the iOS world, we're not limited to capturing console logs; we could make sure any JavaScript errors that are thrown get attached to the console as well, so we track those too (using a little hack like window.onerror = console.error.bind(console)
or similar).
But let's skip to the action. With iOS there was some complexity where we used a non-standard safariConsole
log type, and had to parse some JSON to get the actual data, with Android we can take advantage of the fact that Chrome automation happens via Chromedriver, which supports the full WebDriver protocol all on its own. Given that, we can simply make a request for the browser
log type:
driver.manage().logs().get("browser");
This returns a list of LogEntry
objects, and for each one we can retrieve its message (and, say, print it to the terminal):
for (LogEntry entry : driver.manage().logs().get("browser")) {
System.out.println(entry.getMessage());
}
That's all there is to it, in terms of the test code itself. However, we do need to use a special loggingPrefs
capability to convince Chromedriver to turn on capturing of the browser logs for us. In it simplest form, the loggingPrefs
capability is an object specifying log types and log levels. For our purposes it ends up looking like:
{"loggingPrefs": {"browser": "ALL"}}
But in Java-client-world, we can build up this little structure using classes and constants instead! That way our IDE can guide us to the correct structure without having to remember strings like "loggingPrefs":
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
capabilities.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
With this capability set, browser
log retrieval will work, regardless of whether you're automating Chrome itself or a hybrid app. The code sample for this edition is basically the same as for the iOS equivalent, so I added a hybrid test just to show that it works equally well with hybrid apps (though of course we have to switch into the web context before making the call to get the browser logs). Here's the full example (which you can also view on GitHub):
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy;
import io.appium.java_client.android.AndroidDriver;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.annotation.Nullable;
import org.junit.After;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.logging.LogEntry;
import org.openqa.selenium.logging.LogType;
import org.openqa.selenium.logging.LoggingPreferences;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class Edition038_Android_Web_Console {
private AndroidDriver driver;
private String APP_ANDROID = "https://github.com/cloudgrey-io/the-app/releases/download/v1.7.1/TheApp-v1.7.1.apk";
private static By hybridScreen = MobileBy.AccessibilityId("Webview Demo");
private static By urlInput = MobileBy.AccessibilityId("urlInput");
@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}
@Nullable
private String getWebContext(AppiumDriver driver) {
ArrayList<String> contexts = new ArrayList(driver.getContextHandles());
for (String context : contexts) {
if (!context.equals("NATIVE_APP")) {
return context;
}
}
return null;
}
@Test
public void testLogging_Chrome() throws MalformedURLException {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("browserName", "Chrome");
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
capabilities.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), capabilities);
loggingRoutine(driver);
}
@Test
public void testLogging_Hybrid() throws MalformedURLException {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("app", APP_ANDROID);
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.BROWSER, Level.ALL);
capabilities.setCapability(CapabilityType.LOGGING_PREFS, logPrefs);
driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), capabilities);
WebDriverWait wait = new WebDriverWait(driver, 10);
// get to webview screen and enter webview mode
wait.until(ExpectedConditions.presenceOfElementLocated(hybridScreen)).click();
wait.until(ExpectedConditions.presenceOfElementLocated(urlInput));
driver.context(getWebContext(driver));
// now we can run the same routine as for the browser
loggingRoutine(driver);
}
public void loggingRoutine(AndroidDriver driver) {
driver.get("https://appiumpro.com/test");
driver.executeScript("window.onerror=console.error.bind(console)");
driver.executeScript("console.log('foo.');");
driver.executeScript("console.warn('bar?');");
driver.findElementById("jsErrLink").click();
for (LogEntry entry : driver.manage().logs().get("browser")) {
System.out.println(entry.getMessage());
}
}
}
(NB: you'll need to make sure you're using Appium 1.9.2-beta or greater, since before this, the loggingPrefs
capability was not passed correctly to Chromedriver).