Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Jenkins 2.0 Pipelines and BrowserStack

DZone's Guide to

Jenkins 2.0 Pipelines and BrowserStack

Jan Molan looks at how a Jenkins 2.0 Pipeline can be integrated with BrowserStack.

· DevOps Zone
Free Resource

The DevOps Zone is brought to you in partnership with Sonatype Nexus. The Nexus Suite helps scale your DevOps delivery with continuous component intelligence integrated into development tools, including Eclipse, IntelliJ, Jenkins, Bamboo, SonarQube and more. Schedule a demo today

Serenity BDD is typically used to drive automated tests via a web interface of the application, so today we’ll look at how a Jenkins 2.0 Pipeline can be integrated with BrowserStack, a popular cloud-based provider of cross-browser testing tools such as browsers and mobile devices.

The Big Picture

Typically, the system under test is not publicly accessible and can’t be reached from outside your organization network (i.e., from BrowserStack).

To work around this limitation, BrowserStack provides their users with a command line tool that establishes a local tunnel, allowing BrowserStack to securely connect back to your system.

What this means is that in order to use BrowserStack, we need to:

  1. get the BrowserStack client and establish a secure connection,
  2. execute the tests using BrowserStack browsers or mobile devices, and
  3. shut down the BrowserStack client.This means that we’ll probably need a Pipeline definition that looks more or less like this:
node {  // Prepare the BrowserStackLocal client [...]
  // Start the connection [...]
  // Execute tests [...]
  // Stop the connection [...]
}

Sounds easy, right? Well, the devil’s in the details.

“First, You Get the Client”

We’ll need to get a BrowserStackLocal client suitable for the operating system of our Jenkins build agent. You can find the list of available clients in BrowserStack documentation.

Assuming that we use a 64bit Linux, we’ll need a client available at the following URL:

https://www.browserstack.com/browserstack-local/BrowserStackLocal-linux-x64.zip

PRO TIP: “linux-x64” indicates a binary suitable for a 64bit version of Linux. If your operating system of choice differs, replace that with “linux-ia32” for a 32bit Linux, “win-32” for Windows or “darwin-x64” for Mac OsX.

Once we have downloaded the client, we'll need to unzip it and make it executable, which looks like this when expressed with the Pipeline DSL

node {
  // Prepare the BrowserStackLocal client [...]
  def type = 'linux-x64'
  sh "curl -sS https://www.browserstack.com/browserstack-local/BrowserStackLocal-${type}.zip > /var/tmp/BrowserStackLocal.zip"
  sh "unzip -o /var/tmp/BrowserStackLocal.zip -d /var/tmp"
  sh "chmod +x /var/tmp/BrowserStackLocal"
  // [...]
}

Note that we're storing the BrowswerStackLocal client under /var/tmp/ so that it can be reused by other projects, but you can put it wherever you like.

Pro tip: to avoid having to call the native "unzip" command, install the Pipeline Utility Steps Plugin in and use the "unzip" step instead. Thanks, Alex Soto!

Next, let’s add a guard to avoid downloading the BrowserStackLocal client every single time we execute the Jenkins project:

node {
  
  // Prepare the BrowserStackLocal client
  
  if (! fileExists("/var/tmp/BrowserStackLocal")) {
  
    def type = 'linux-x64'
    sh "curl -sS https://www.browserstack.com/browserstacklocal/BrowserStackLocal-${type}.zip > /var/tmp/BrowserStackLocal.zip"
    sh "unzip -o /var/tmp/BrowserStackLocal.zip -d /var/tmp"
    sh "chmod +x /var/tmp/BrowserStackLocal"
  }
  // [...]
}

Now that we have the client available on the build agent, we can move onto establishing a secure connection with BrowserStack.

Get Connected

When you set up your BrowserStack account, you should have received an “access key”, which you’ll need to pass as an argument to the BrowserStackLocal.

Pro tip: if you don’t remember what your automation key is, you can find it in the settings section of your BrowserStack account.

If the access key is “42MyAcc3sK3yV4lu3”, you’d start the client like this:

$> /var/tmp/BrowserStackLocal 42MyAcc3sK3yV4lu3 -onlyAutomate

There’s one problem, though: BrowserStackLocal needs to run as a background process, so simply executing the above command from the pipeline won’t work and will make our Jenkins build to hang.

Let’s improve it a bit:

$> nohup /var/tmp/BrowserStackLocal 42MyAcc3sK3yV4lu3 -onlyAutomate
> /var/tmp/browserstack.log 2>&1

This looks slightly better on a surface, as we’re using nohup to start a background process, but sadly, that won’t work either.

In order to keep your build environment clean, the merciless Jenkins ProcessTreeKiller kills any background processes we spawn during the build...

…unless we politely ask it to leave them alone:

$> BUILD_ID=dontKillMe nohup /var/tmp/BrowserStackLocal
42MyAcc3sK3yV4lu3 -onlyAutomate > /var/tmp/browserstack.log 2>&1

Executing the above line starts a BrowserStackLocal connection.

We’re not quite done yet, though. How do we kill the “nohup-ed” daemon when we finish the build?

A simple way to do it would be to remember the process id of the daemon we spawned:

$> BUILD_ID=dontKillMe nohup /var/tmp/BrowserStackLocal
42MyAcc3sK3yV4lu3 -onlyAutomate > /var/tmp/browserstack.log 2>&
1 & echo \$! > /var/tmp/browserstack.pid

and then kill it using its process id:

$> kill `cat /var/tmp/browserstack.pid`

The Taming of the Demon

The above two scripts look as follows when added to our pipeline definition:

node {
  
  // Prepare the BrowserStackLocal client [...]
  // Start the connection
  sh "BUILD_ID=dontKillMe nohup /var/tmp/BrowserStackLocal 42MyAcc3sK3yV4lu3 -onlyAutomate > /var/tmp/browserstack.log 2>&1 & echo \$! > /var/tmp/browserstack.pid"
  // Execute tests [...]
  // Stop the connection
  sh "kill `cat /var/tmp/browserstack.pid`"
}

Again, this might seem fine on the surface, but what happens when the tests fail? We'd still want to retain control over the demon we spawned and shut it down properly when we're done with the build. 

Let's improve the pipeline definition further:

node {
  
  // Prepare the BrowserStackLocal client [...]
  // Start the connection [...]
  try {
    // Execute tests [...]
  } finally {
    
    // Stop the connection [...]
  }
}

The addition of the try/finally clause ensures that we shut down the connection no matter what happens during the test execution phase.

It’s the Final Pipeline!

Well, almost. So far our pipeline definition looks like this:

node {
  // ----------------------------------------
  // Prepare the BrowserStackLocal client
  // ----------------------------------------
  def type = 'linux-x64'
  if (! fileExists("/var/tmp/BrowserStackLocal")) {
    sh "curl -sS https://www.browserstack.com/browserstack-local/BrowserStackLocal-${type}.zip > /var/tmp/BrowserStackLocal.zip"
    sh "unzip -o /var/tmp/BrowserStackLocal.zip -d /var/tmp"
    sh "chmod +x /var/tmp/BrowserStackLocal"
  }
  // ----------------------------------------
  // Start the connection
  // ----------------------------------------
  sh "BUILD_ID=dontKillMe nohup /var/tmp/BrowserStackLocal 42MyAcc3sK3yV4lu3 -onlyAutomate > /var/tmp/browserstack.log 2>&1 & echo \$! > /var/tmp/browserstack.pid"
  try {
    // Execute tests [...]
  } finally { 
    // ----------------------------------------
    // Stop the connection
    // ----------------------------------------
    sh "kill `cat /var/tmp/browserstack.pid`"
  }
}

The above code works, but is too low-level and quite far from being readable, reusable, or easy to maintain.

Pro tip: the beauty of the Pipeline DSL using Groovy under the hood is that we can use Groovy to extend the DSL and define our own pipeline steps!

Wrap It Up, Scotty!

Wouldn’t it be nicer and more expressive for our pipeline to look like this?

node {
  with_browser_stack 'linux-x64', {
    // Execute tests [...]
  }
}

Turns out that it’s actually quite easy to do. All we need to do is to wrap up our code into a custom function:

node {
  with_browser_stack 'linux-x64', {
    // Execute tests [...]
  }
}
// ----------------------------------------------
def with_browser_stack(type, actions) {
  // Prepare the BrowserStackLocal client
  if (! fileExists("/var/tmp/BrowserStackLocal")) {
    
    sh "curl -sS https://www.browserstack.com/browserstack-local/BrowserStackLocal-${type}.zip > /var/tmp/BrowserStackLocal.zip"
    sh "unzip -o /var/tmp/BrowserStackLocal.zip -d /var/tmp"
    sh "chmod +x /var/tmp/BrowserStackLocal"
  }
  // Start the connection
  sh "BUILD_ID=dontKillMe nohup /var/tmp/BrowserStackLocal 42MyAcc3sK3yV4lu3 -onlyAutomate > /var/tmp/browserstack.log 2>&1 & echo \$! > /var/tmp/browserstack.pid"
  try {
    actions()
  }
  finally {
    // Stop the connection
    sh "kill `cat /var/tmp/browserstack.pid`"
  }
}

Serenity Now!

Now, to integrate our Serenity BDD tests with this setup, it's enough to add the following to the serenity.conf file:

browserstack {
  url           = "http://<user>:<key>@hub.browserstack.com/wd/hub"  
  browser       = "chrome"
  os            = "Windows"
  os_version    = "7"

  local   = true

  project = "My BrowserStack Project"
}

And we're done!

Taking It Further

What we ended up with is a nice and reusable function that could be extracted into a separate file and loaded into our pipeline definition if needed. One thing that could be still improved is for the access key to be loaded either from an environmental variable or a build parameter rather than being hard-coded.

If you’d like to see examples of how to achieve that, please leave a comment below!

The DevOps Zone is brought to you in partnership with Sonatype Nexus. Use the Nexus Suite to automate your software supply chain and ensure you're using the highest quality open source components at every step of the development lifecycle. Get Nexus today

Topics:
serenity ,bdd ,browserstack

Published at DZone with permission of Jan Molak, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}