Your data. Anywhere you go.

New Relic for iOS or Android


Download on the App Store    Android App on Google play


New Relic Insights App for iOS


Download on the App Store


Learn more

Close icon

Relic Solution - Scripted Browser Error Handling and Alerting on Step Failures


#1

Overview

In a Scripted Browser monitor there maybe some steps in the journey that you don’t want the monitor to alert on, such as checking for the existence of a modal overlay for a campaign, so you can close it, and then resume checking other critical elements. Or sometimes, if a step fails, there maybe some extra steps you want to take before failing the monitor, like adding a custom attribute to your SyntheticCheck event.

This is where onRejected handler functions for the .then() method comes in handy. Think of them as a callback (errback) function but for unresolved promises / errors. I find this is sometimes under utilized, but it can add some versatility to your script and your alerting.

onRejected and .catch() handlers

One may already be familiar with using the onFulfilled handler in the promise .then() method. This is how we ordinarily control asynchrounous actions within the webdriver library. To illustrate, in the example below we wait for the browser to fetch the url then we check for the existence of a link on the page:

$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.waitForAndFindElement($driver.By.linkText('Sign Up'), 5000);
})

Typically if the waitForAndFindElement method hits the specified timeout to locate the provided element in the DOM, the promise returned from this method is rejected and an error is thrown to end and fail the monitor check. If our method above is not able to find the element, and the promise is rejected we can handle this situation with the .then() method’s optional second parameter onRejected handler.

The syntax is as such:

promise.then((value) => {
  // fulfillment
}, (err) => {
  // rejection
});
 
$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.findElement($driver.By.linkText('Sign Up')).then(null, (err) => {
      console.log('Sign up link not found');
      throw err
  })
})

You may have noticed that we branched off of our original promise chain. If we had put the onRejected handler on our original chain like so:

$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.findElement($driver.By.linkText('Sign Up'))
})
.then(() => {
    //next step
    console.log('Sign up link found');
}, (err) => {
      console.log('Sign up link not found');
      throw err
})

This thrown error / rejected promise will actually get passed down the entire chain and get ‘swallowed’ up or get handled by the last error handler on the chain. An alternative syntax to handle rejected promises is using .catch(), which is like using the onRejected handler but can help our syntax look a tad more readable when we are branching like this.

//OR with .catch()
 
$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.findElement($driver.By.linkText('Sign Up')).catch((err) => {
      console.log('Sign up link not found');
      throw err
  })
})

Now lets say the page we are monitoring occasionally has an overlay modal that pops up and gets in our way of finding and interacting with other critical elements we are monitoring. If it doesn’t exist, cool, but we don’t want to alert on not finding it and move on to the next step. If it does exist we’ll otherwise close it.

$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.findElement($driver.By.linkText('Sign Up')).catch((err) => {
      console.log('Sign up link not found');
      throw err
  })
})
.then(() => {
  console.log(`step2: looking for campaign overlay`)
  return $browser.waitForAndFindElement($driver.By.css('div.campaign-overlay'), 5000).then(() => {
    return $browser.findElement($driver.By.css('button.close')).click().catch((err) => {
            console.log('modal close button could not be clicked')
             throw err
    })
  }, (err) => {
    console.log('div.campaign-overlay not found');
  })
})
.then(() => {
    console.log(`step3: verifying h1 content`);
    return $browser.waitForAndFindElement($driver.By.css('h1'), 5000).catch((err) => {
      console.log('h1 not found');
      throw err
    })
})
.then((element) => {
  return element.getText()
})
.then((text) => {
    if(text == 'New Relic'){
      return
    } else {
      throw new Error("Expected h1 text to be New Relic")
    }
})

Adding a custom attribute when promises fail

Knowing this, lets say we want to track in Insights what steps are failing in our scripted monitor, we can add a custom attribute to our SyntheticCheck event from our onRejected handler first, then throw our error.

The syntax for this is:

$util.insights.set('key','value');

Adding this to our example above:

$browser.get('https://newrelic.com')
.then(() => {
  console.log('step1: looking for signup button');
  return $browser.findElement($driver.By.linkText('Sign Up')).catch((err) => {
      console.log('Sign up link not found');
      //attach step1 custom attribute to our SyntheticCheck event
      $util.insights.set('step1', 'FAILED');
      throw err
  })
})
.then(() => {
  console.log(`step2: looking for campaign overlay`)
  return $browser.waitForAndFindElement($driver.By.css('div.campaign-overlay'), 5000).then(() => {
    return $browser.findElement($driver.By.css('button.close')).click().catch((err) => {
            console.log('modal close button could not be clicked')
            $util.insights.set('step2', 'FAILED')
             throw err
    })
  }, (err) => {
    console.log('div.campaign-overlay not found');
    $util.insights.set('step2', 'FAILED')
  })
})
.then(() => {
    console.log(`step3: verifying h1 content`);
    return $browser.waitForAndFindElement($driver.By.css('h1'), 5000).catch((err) => {
      console.log('h1 not found');
      $util.insights.set('step3', 'FAILED')
      throw err
    })
})
.then((element) => {
  return element.getText()
})
.then((text) => {
    if(text == 'New Relic'){
      return
    } else {
      $util.insights.set('step3', 'FAILED')
      throw new Error("Expected h1 text to be New Relic")
    }
})

Alerting on failed steps

Now our Scripted Browser monitor is producing SyntheticCheck events that are decorated with custom attributes associated with particular steps in our journey, when those steps fail. Not only can we now query insights on the occurrence of these attributes, but with NRQL conditions we can also monitor and alert on specific steps:

Here is an example condition:

Some Caveats

  • When a Synthetic check is running, it is listening to errors not just from webdriver, but also from the runtime which includes the browser as triggers to end the check. The above wont catch all errors, only unresolved webdriver promises. For example, if there is an error in the request triggered by $browser.get(), like DNS resolution, we will catch the Chrome error first and end the job before webdriver creates the rejected promise.

  • Take care with making sure your .catch or onRejected handlers are not on the main promise chain, and thoroughly test that your steps will throw an error correctly if failed. Rejected promise fall through and error swallowing can get weird.


Add Event listener or log output