Wednesday, 3 July 2013

Coding something simple.... or not! Taking a screenshot on error using Selenium WebDriver

I recently wrote a little function that takes a screenshot at the end of a test if it has errored. What sounded very simple at the start turned out to be quite a bit of work, and quite a few lines of code to handle certain scenarios! It's now over 50 lines of code!

I'll start with what I had at the beginning, this was to simply take a screenshot in the working directory, we are using SpecFlow and Selenium to run the tests, so we are going to check if the ScenarioContext.Current.TestError isn't null, if it is, then using Selenium, take a screenshot (note the below code is a simplified version of what I had at the beginning).

[AfterScenario]        public static void TakeScreenShotOnError()        {
            if (ScenarioContext.Current.TestError == null) return;
            var screenshotDriver = Driver as ITakesScreenshot;            if (screenshotDriver != null)            {                var screenshot = screenshotDriver.GetScreenshot();
                string fileName = createFileName(ErrorScreenshotDirectory);
                screenshot.SaveAsFile(Path.Combine(ErrorScreenshotDirectory, fileName + ".png"), ImageFormat.Png);
 }
So if you follow the code through, it will run after a scenario has been executed (SpecFlow) and if there isn't an error then it will do nothing, if there is then it will initiate the ITakesScreenshot, and use that to take a screenshot. There are a number of methods inside this piece of code, but I will go through them in more detail as we go on the journey to create the functionality and make it robust.

This worked fine, but I noticed that not every error was being taken, I was racking my brains, and thought it coudl possibly be 2 things, the file name could be containing illegal characters (this createFileName took the ScenarioContext.Current.ScenarioInfo.Title as the file name, so there was a high chance that this contained illegal filename characters, and sure enough, upon further investigation some tests did have illegal characters. 

So I googled for a method that would remove illegal characters from a string, and sure enough, Google delivered! With a bit of modifications I got the below code to work:


        private static string CleanFileName(string fileName)        {            return Path.GetInvalidFileNameChars().Aggregate(fileName,                                                            (current, c) => current.Replace(c.ToString(), string.Empty));        }


This was placed inside the createFileName method, and took the ScenarioContext.Current.ScenarioInfo.Title as a parameter and removed any illegal characters. 

The other issue that it might have been was that the file name could have been greater than the maximum allowed in Windows, so whilst it could have been either one of these things, I thought it would be best to guard against both, as either one could come up again. 

This was created by passing in the whole working directory and the clean file name, and if it was greater than 255 characters it would trim it so that it wasn't longer anymore. The following code achieved this:


        private static string truncateString(string value, int maxLength)        {            return value.Length <= maxLength ? value : value.Substring(0, maxLength);        }


This would return the new filename with the end truncated if at all necessary. 

This made my code far more robust, and I like to think that being a tester I am more aware of possible issues.

I thought this was good to go, however, upon further investigation, if we have a scenario that has multiple examples, then it would only create 1 screenshot, I needed a method that if the file exists, then append a numerical value to the end of the filename, so as to have multiple screenshots for multiple scenario examples. This was pretty simple to achieve, though it meant I would have to reduce the maximum value of a filename by one, to accomodate adding an integer to the end of the filename:


        private static string incrementFileName(string workingDirectory, string fileName)        {            int i = 1;            do            {                i++;            } while (File.Exists(Path.Combine(workingDirectory,fileName+i+".png")));            fileName = fileName + i;            return fileName;        }

I won't bore you by going through the code :)

We are almost there, however my code originally was only creating the directory on the test error if it didn't exist, and you'd end up with multiple test run screenshots in there if you ran it locally (on the build server it's fine as each build got it's own drop folder to store stuff in). To get around this I would create or empty the directory (depending if it exists or not) at the start of the test run, to get rid of having a folder full of multiple screenshots.

        [BeforeTestRun]
        public static void SetupScreenShotDirectoryBeforeTestRun()
        {
            var workingDirectory = Directory.GetCurrentDirectory();
            var screenshotDirectory = Path.Combine(workingDirectory, "errorScreenshots");
            var fileInfo = new DirectoryInfo(screenshotDirectory);
            if (Directory.Exists(screenshotDirectory))
            {
                foreach (FileInfo file in fileInfo.GetFiles())
                {
                    file.Delete();
                }
                foreach (DirectoryInfo dir in fileInfo.GetDirectories())
                {
                    dir.Delete(true);
                }
            }
            if (!Directory.Exists(screenshotDirectory))
            {
                Directory.CreateDirectory(screenshotDirectory);
            }

            ErrorScreenshotDirectory = screenshotDirectory;

        }

This would empty the folder if it already exists, if not then it creates it in the working directory at the start of each test run.

As a nice to have, I also added in some code that will put the error message at the top of the image, this means that you can cycle through the images and see the error message without having to compare it to a test run. I found that .net has the ability to do this, and found the below code on google:


        private static void AppendErrorMessageToImage(string filePNG)        {            Bitmap bitmap = null;            using (var stream = File.OpenRead(filePNG))            {                bitmap = (Bitmap)Image.FromStream(stream);            }
            using (bitmap)            using (var graphics = Graphics.FromImage(bitmap))            using (var font = new Font("Arial", 20, FontStyle.Regular))            {                graphics.DrawString(ScenarioContext.Current.TestError.Message, font, Brushes.Red, 0, 0);
                bitmap.Save(filePNG);            }                    }
With a bit of modification I got it to grab the ScenarioContext.Current.TestError.Message and print it to the top of the image. 


So there you have it, what started out as something simple, (just like most things), quickly became something much bigger, when I thought about it from a QA perspective, maybe a developer would have noticed these things too, but I like to think that by coming from a testing background I am more open to finding bugs in my code!  However I'm sure you'll agree it's made it a lot more robust and has the ability to handle pretty much anything that the tests can throw at it (I hope!).


9 comments:

  1. The information shared was very much useful My sincere Thanks for sharing this post Please Continue to share this kind of post
    Software Testing Training in Chennai

    ReplyDelete
    Replies
    1. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.. I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging. If anyone wants to become a .Net developer learn from Dot Net Training in Chennai. or learn thru ASP.NET Essential Training Online . Nowadays Dot Net has tons of job opportunities on various vertical industry.

      Delete
  2. nice blog has been shared by you. before i read this blog i didn't have any knowledge about this but now i got some knowledge. so keep on sharing such kind of an interesting blogs.
    software testing training in chennai

    ReplyDelete
  3. I wondered upon your blog and wanted to say that I have really enjoyed reading your blog posts. Thanks for share to our vision..
    Selenium Training in Chennai | Software Testing Training in Chennai

    ReplyDelete
  4. I love this post.We share give the now amazing post.I understand all fantastic info.We will have shared learning it amazing post.Learn Python Online | Python Online Training

    ReplyDelete
  5. nice blog has been shared by you. before i read this blog i didn't have any knowledge about this but now i got some knowledge. so keep on sharing such kind of an interesting blogs.
    selenium training in bangalore

    ReplyDelete
  6. Interesting and informative article.. very useful to me.. thanks for sharing your wonderful ideas.. please keep on updating..
    Best BE | B.Tech Project Center in Chennai | ME | M.Tech Project Center in Chennai | Best IT Project Center in Chennai

    ReplyDelete
  7. Hi, am a big follower of your blog. I am really happy to found such a helpful and fascinating post that is written in well manner. Thanks for sharing such an informative post. keep update your blog.
    Java Training Institute in Chennai | DotNet Training Institute in Chennai | Web Designing Training Institute in Chennai

    ReplyDelete