Heroku throwing R14 (memory) errors when running Selenium tests - java

I've been trying to get Selenium tests setup on Heroku. However, I've observed that when even a small test runs, Heroku throws R14 errors due to memory consumption.
I started trying to run these tests using free dynos and have upgraded up to the Standard 2X dynos and still see this issue. I can't imagine that these tests require more than 1GB of RAM to run. I suppose it's possible but I was interested in hearing other people's experiences with this and to see if anyone ran into similar issues and had solutions.
Locally the tests are super speedy and run fine. I know on the Heroku side that the buildpacks need to dynamically download chrome and the driver each time the tests are run so a little slowness there is to be expected.
A couple notes about my process:
I'm using the Gradle wrapper to run the test script
I'm running the Selenium test via the jUnit framework
I'm using the heroku/google-chrome and heroku/chromedriver buildpacks
Here's my setup so you can see if I'm doing anything strange...
SeleniumConfig.kt
class SeleniumConfig {
val driver: WebDriver
init {
val options = ChromeOptions()
val driverFile = System.getenv("CHROME_DRIVER_PATH")
val binary = System.getenv("GOOGLE_CHROME_BIN")
options.apply {
addArguments("--enable-javascript")
addArguments("--start-maximized")
addArguments("--incognito")
addArguments("--headless")
addArguments("--disable-gpu")
addArguments("--no-sandbox")
}
val service = ChromeDriverService.Builder()
.usingDriverExecutable(File(driverFile))
.build()
binary?.let { options.setBinary(it) }
driver = ChromeDriver(service, options)
driver.manage().timeouts().implicitlyWait(60, TimeUnit.SECONDS)
}
}
BaseSeleniumTest.kt
open class BaseSeleniumTest(private val path: String) {
companion object {
const val DEFAULT_WAIT = 5000L
}
protected val config = SeleniumConfig()
protected val driver: WebDriver
get() = config.driver
#BeforeEach
fun setUp() {
driver.get("https://<mysite>$path")
implicitWait()
}
#AfterEach
fun tearDown() {
driver.close()
driver.quit()
}
fun implicitWait() {
driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT, TimeUnit.MILLISECONDS)
}
}
SigninTest.kt
class SigninTest : BaseSeleniumTest("/signin") {
#Test
fun `Succeed signing in`() {
val username = driver.findElement(By.name("email"))
val password = driver.findElement(By.name("password"))
username.sendKeys("test#test.com")
password.sendKeys("password")
username.submit()
implicitWait()
}
#Test
fun `Fail with invalid username`() {
val username = driver.findElement(By.name("email"))
val password = driver.findElement(By.name("password"))
username.sendKeys("wrong")
password.sendKeys("password")
username.submit()
implicitWait()
assertEquals("Invalid email or password",
driver.findElement(By.cssSelector(".form-error")).text)
}
#Test
fun `Fail with invalid password`() {
val username = driver.findElement(By.name("email"))
val password = driver.findElement(By.name("password"))
username.sendKeys("test#test.com")
password.sendKeys("wrong")
username.submit()
implicitWait()
assertEquals("Invalid email or password",
driver.findElement(By.cssSelector(".form-error")).text)
}
}
Any help that anyone could give me would be appreciated!

Related

OutOfMemoryError while trying to download large file using Retrofit #Streaming annotation

I'm downloading files using retrofit client, but when there is large file (200 MB) it throws java.lang.OutOfMemoryError:
I have #Streaming annotation also and this is my download service method
#Streaming
#GET("{path}")
suspend fun downloadFile(#Path("path") path: String): Response<ResponseBody>`
Here is invoke code snippet
suspend fun downloadFile(remotePath: String): FileDownloadResponse {
try {
val response = api.downloadFile(remotePath)
if (response.isSuccessful) {
FileDownloadResponse.Success(response.body()!!)
} else {
FileDownloadResponse.Fail()
}
} catch (e: Exception) {
e.printStakTrace()
FileDownloadResponse.Fail(throwable = e)
}
}
val response = remoteRepositroy.downloadFile(remotePath)
val writeResult = response.body.writeResponseBodyToDisk()
Retrofit version = 2.6.0
Coroutine version = 1.3.0-M1
I have fixed it by changing HttpLogingInterceptor log level from BODY to HEADERS
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS
})
Seems strange bug fix, but it works
Try using download manager for downloading large files
You can find the complete sample here by #CommonsWare
https://github.com/commonsguy/cw-android/tree/master/Internet/Download

How do I fix this error after assigning driver object to "new AndroidDriver<WebElement>(url, caps)" from multiple instances in a thread?

I've created a little script for testing a few elements in my app with Appium. I've set-up my project accordingly, tried to run it with only one device and Appium server, works like a charm. If I run it again with multiple devices and respectful servers, many problems appear. Some devices do not run the app at all, some devices run the app but not the script while only one random device out of all of them runs the script.
I am running the command-line Appium version like this:
> appium -p XXXX -bp XXX --session-override
...where -p defines the server's port while -bp defines the bootstrap port. They are both different for each server.
The way my code works is that I've set up a class object for each device I define in my main code so that all of them can get tested simultaneously via separate threads for each of them.
Using breakpoints, I've found out that this line in my code has caused the following error:
An unknown server-side error occurred while processing the command. Original error: io.appium.uiautomator2.common.exceptions.UiAutomator2Exception: Error in building: Ill-formed XML document (multiple root elements detected). Try changing the 'normalizeTagNames' driver setting to 'true' in order to workaround the problem.
This is what caused it:
driver = new AndroidDriver<WebElement>(url, capabilities);
Main.java
public class Main {
private static String[][] testArgs = {
{"Device-1", "my-udid-01", "8.0.0", "http://0.0.0.0:5000/wd/hub"},
{"Device-2", "my-udid-02", "9", "http://0.0.0.0:5001/wd/hub"},
{"Device-3", "my-udid-03", "9", "http://0.0.0.0:5002/wd/hub"}
};
public static void main(String[] args) {
for(final String[] testArgs : testArgs) {
new Thread(new Runnable() {
public void run() {
new TestScript(testArgs);
}
}).start();
}
// If this function stops, then all threads will stop working. I chose to end the script
// at my will by stopping the code.
while(true){}
}
}
TestScript.java
class TestScript {
private RemoteWebDriver driver;
TestScript(String[] args) {
try {
String deviceName = args[0];
DesiredCapabilities caps1 = new DesiredCapabilities();
caps1.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
caps1.setCapability(MobileCapabilityType.UDID, args[1]);
caps1.setCapability(MobileCapabilityType.PLATFORM_VERSION, args[2]);
caps1.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
caps1.setCapability(MobileCapabilityType.APP, "my-app.apk");
caps1.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UIAutomator2");
// This is what triggers the error
driver = new AndroidDriver<WebElement>(new URL(args[3]), caps1);
run(deviceName);
}
catch(Exception ex) {
driver = null;
ex.printStackTrace();
}
}
private void run(String deviceName) {
//... The testing goes here and does not cause any problem...
}
}

Having trouble with SkipException, testNG, what am I doing wrong?

I am very new to selenium UI automation and I am trying my hands on with a simple application. I am using java with testNG. I need to integrate this test with CI and the test environment url will be different with every deployment. I am passing this as a parameter, difference between test and prod environment is that in the test, there won't be any login screen hence there is no need for authentication but my test verifies login and a login method. I need to be able to skip this test based on the URL supplied. Here is my code and the problem is testNG always suggests that the test was skipped but I can see it executing the login method. Please help me correct or understand what mistake I am committing.
public class Login {
WebDriver driver;
//Parameter - test environment URL
String url = System.getProperty("environment");
#Test (priority = 1)
public void verifyLogin() {
//Skip verifyLogin test in non prod environments
System.setProperty("webdriver.chrome.driver", "C://chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get(url);
if (url=="www.google.com")
//If url matches production url then execute the test, if url doesn't match production URL then skip login test as there won't be any authentication/login in non prod
{
LoginPage login = new LoginPage(driver);
login.signIn();
Assert.assertEquals(driver.getTitle(), "Application Name", "Login failed,");
String title = driver.getTitle();
System.out.println("Page title is " + title);
driver.close();
}
else if (url!="www.google.com"){
throw new SkipException("Skipping this test");
}
}
#Test(priority = 2)
public void userKey() {
System.setProperty("webdriver.chrome.driver", "C://chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get(url);
//If URL equals prod then call the login method to be able to login to the app else just execute the userKey method without having the need to login
if (url=="www.google.com");
{
LoginPage login = new LoginPage(driver);
login.signIn();
}
AccountManagementPage userKey = new AccountManagementPage(driver);
userKey.key();
driver.close();
}
}
The very exact use case is well explained here without throwing SkipException.
The idea is to create a custom annotation for the methods to be skipped & read the methods using IMethodInterceptor - decide to execute or not behind the scenes.
Updated for the question in the comment section:
You do not have to worry about 'TestEnvironment' or 'TestParameters' class.Just directly use the production check logic here.
Predicate<IMethodInstance> isTestExecutingOnProduction = (tip) -> {
return system.getProperty("env").
.equals("<production check here>");
};

Keeping Selenium browser active

I'm using Selenium HtmlUnitDriver and wondering is it possible somehow to keep the state of this driver.
I mean that to test something on the page I have to load the driver -> load and add cookies -> go through the login page -> get required page.
It takes too much time to do it everytime.
Is there something like a 'server state' or maybe I need to serialize and save-load the driver?
Thank you.
Since I've managed to solve my question, I'll leave this here:
1. I took selenium-server-standalone and run it with -browserSessionReuse -timeout 3600 -browserTimeout 600 to keep my session alive.
2. Made my class:
public class MyRemoteWebDriver extends RemoteWebDriver {
....
#Override
protected void startSession(Capabilities desiredCapabilities, Capabilities requiredCapabilities) {
String sid = loadSessionID("SID_NAME");
if (sid != null) {
super.startSession(desiredCapabilities, requiredCapabilities);
log.info("Old SID: " + sid);
setSessionId(sid);
try {
getCurrentUrl();
log.info("Old url: " + getCurrentUrl());
} catch (WebDriverException e) {
sid = null;
}
}
if (sid == null) {
super.startSession(desiredCapabilities, requiredCapabilities);
saveSessionID(getSessionId().toString());
log.info("New SID: " + getSessionId().toString());
}
}
}
So, this way I can store this SessionId in the db and re-use it.
You can try to use a singleton webdriver instead of creating one for each test.
Something like that:
class SingletonWebdriver {
private static Webdriver driver;
public static Webdriver getDriver() {
if(driver == null) {
createDriver();
}
return driver;
}
}
And then you may call getDriver to retrieve the same driver, but in many cases it's a good practice to make each test in a diferent session.

Running integration tests for a spring-boot REST service using gradle

I am currently trying to setup integration test framework for a REST service which is built on:
Spring-boot
Gradle
Jetty
I was able to use spring-boot integration test framework along with spring-boot junit runner to bring up the app context and run the tests successfully.
The next thing that I was trying to do was to have a gradle task which will do the following:
Build the jar(not war)
Start jetty and deploy the jar
Run a set of test-cases against this jar.
Stop jetty
=> I tried using the 'jetty' plugin. But it does not seem to be supporting jar files.
=> I then tried using the JavaExec task to run the jar and then run the tests, but then I couldn't find a straight-forward way to stop the jar process after the tests are done.
=> The same issue with the Exec type task.
So, I have two questions regarding this:
Is there a way to achieve the above said form of integration testing using gradle.
Is this way of integration-testing recommended or is there a better way of doing it?
Any thoughts and insights are much appreciated.
Thanks,
There are different ways to achieve what you want. The approach I helped with at a client relied on the /shutdown URL provided by Spring Boot Actuator. Important If you use this approach, be sure to either disable or secure the /shutdown endpoint for production.
Within the build file you have two tasks:
task startWebApp(type: StartApp) {
dependsOn 'assemble'
jarFile = jar.archivePath
port = 8080
appContext = "MyApp"
}
task stopWebApp(type: StopApp) {
urlPath = "${startWebApp.baseUrl}/shutdown"
}
You should make sure that your integration tests depend on the startWebApp tasks and they should be finalised by the stop task. So something like this:
integTest.dependsOn "startWebApp"
integTest.finalizedBy "stopWebApp"
Of course, you need to create the custom task implementations too:
class StartApp extends DefaultTask {
static enum Status { UP, DOWN, TIMED_OUT }
#InputFile
File jarFile
#Input
int port = 8080
#Input
String appContext = ""
String getBaseUrl() {
return "http://localhost:${port}" + (appContext ? '/' + appContext : '')
}
#TaskAction
def startApp() {
logger.info "Starting server"
logger.debug "Application jar file: " + jarFile
def args = ["java",
"-Dspring.profiles.active=dev",
"-jar",
jarFile.path]
def pb = new ProcessBuilder(args)
pb.redirectErrorStream(true)
final process = pb.start()
final output = new StringBuffer()
process.consumeProcessOutputStream(output)
def status = Status.TIMED_OUT
for (i in 0..20) {
Thread.sleep(3000)
if (hasServerExited(process)) {
status = Status.DOWN
break
}
try {
status = checkServerStatus()
break
}
catch (ex) {
logger.debug "Error accessing app health URL: " + ex.message
}
}
if (status == Status.TIMED_OUT) process.destroy()
if (status != Status.UP) {
logger.info "Server output"
logger.info "-------------"
logger.info output.toString()
throw new RuntimeException("Server failed to start up. Status: ${status}")
}
}
protected Status checkServerStatus() {
URL url = new URL("$baseUrl/health")
logger.info("Health Check --> ${url}")
HttpURLConnection connection = url.openConnection()
connection.readTimeout = 300
def obj = new JsonSlurper().parse(
connection.inputStream,
connection.contentEncoding ?: "UTF-8")
connection.inputStream.close()
return obj.status == "UP" ? Status.UP : Status.DOWN
}
protected boolean hasServerExited(Process process) {
try {
process.exitValue()
return true
} catch (IllegalThreadStateException ex) {
return false
}
}
}
Note that it's important to start the server on a thread, otherwise the task never ends. The task to stop the server is more straightforward:
class StopApp extends DefaultTask {
#Input
String urlPath
#TaskAction
def stopApp(){
def url = new URL(urlPath)
def connection = url.openConnection()
connection.requestMethod = "POST"
connection.doOutput = true
connection.outputStream.close()
connection.inputStream.close()
}
}
It basically sends an empty POST to the /shutdown URL to stop the running server.

Categories