Hash files are different after taking same screen shot with Selenium - c#

I'm trying to learn about automated tests, using Selenium. I'm working alone, and so only have the docs, Google and then you guys.
Using Selenium in VS-2105, I save a screen shot of my website as an image to a file location, and then stop debugging at that point. This file then becomes the 'expected' result.
I then comment that line out, run it again, taking a screen shot but saving to a different location. The files, although of the same size, have different hash values.
To my eyes they are identical.
Is there something wrong with my approach?
This is the code which I run to create my 'master'
_webDriver.Navigate().GoToUrl(url);
var accept = _webDriver.SwitchTo().Alert();
accept.Accept();
IWebElement menu = _webDriver.FindElement(By.Id("link"));
menu.Click();
System.Threading.Thread.Sleep(500);
var screenshot = _webDriver.GetScreenshot();
var fileName = "expandMenuInPlan.png";
var origFile = _testImagesPersistentPath + fileName;
screenshot.SaveAsFile(origFile, OpenQA.Selenium.ScreenshotImageFormat.Png);
And this is the code I use to compare
_webDriver.Navigate().GoToUrl(url);
var accept = _webDriver.SwitchTo().Alert();
accept.Accept();
IWebElement menu = _webDriver.FindElement(By.Id("link"));
menu.Click();
System.Threading.Thread.Sleep(500);
var screenshot = _webDriver.GetScreenshot();
var fileName = "expandMenuInPlan.png";
var origFile = _testImagesPersistentPath + fileName;
//screenshot.SaveAsFile(origFile, OpenQA.Selenium.ScreenshotImageFormat.Png); COMMENTED OUT
//The above is identical
var newFile = _testImagesTempForTestRunPath + fileName;
screenshot.SaveAsFile(newFile, OpenQA.Selenium.ScreenshotImageFormat.Png);
string hashOrig = GetBytes(origFile);
string hashNew = GetBytes(newFile);
if (hashOrig != hashNew)
{
SaveFailedImage(origFile, newFile, fileName);
}
And the GetBytes method
private string GetBytes(string file)
{
using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider())
{
var img = new Bitmap(file);
ImageConverter converter = new ImageConverter();
var bytes = (byte[])converter.ConvertTo(img, typeof(byte[]));
return Convert.ToBase64String(sha1.ComputeHash(bytes));
}
}
Is using screenshots in this manner just not reliable or is there something wrong with my code?

Hashing a image file and comparing is never the right approach. Even a single pixel off will change the hash. So you don't want to to make your tests brittle
You can use the C# method ImageComparer.Compare Method (Image, Image, ColorDifference, Image)
https://msdn.microsoft.com/en-in/library/hh191601.aspx?f=255&MSPPError=-2147217396
Or you can use external tools like https://applitools.com/ which allow you to do compare images which are smart in nature
PS: I have no association with applitools and it is just one example, there might be other services available as well.

Related

fo-Dicom - How do I extract image frames from the DicomFile

I need to extract all image frames from a DICOM SC using fo-DICOM. I have a test app that extracts and displays the images, which works fine. However, I need to save the individual images to a database, and am running into problems.
I have the following code so far:
public void SetImages(DicomFile dicom, ThumbResults results)
{
var images = new DicomImage(dicom.Dataset);
for(var count = 0; count < images.NumberOfFrames; count++)
{
var image = images.RenderImage(count).As<Bitmap>();
using(var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
results.Images.Add(Convert.ToBase64String(stream.ToArray()));
}
}
}
I get a DicomImagingException, "Cannot cast to 'Bitmap'" on images.RenderImage. It works in my test code, when I call PictureBox.Image = _image.RenderImage(count).As<Bitmap>(); so I figure RenderImage must be specifically for rendering (as the name implies).
How should I go about extracting individual frames to a string that will be saved to the database?
In case someone else runs into this problem, the issue was the original code was in .NET Framework, but the new code was in .NET Core. In Core, the ImageManager does not use the WindowsImageManager by default, so you need to set it manually.
ImageManager.SetImplementation(WinFormsImageManager.Instance);

Copy a visio page to a new document

What I want to accomplish:
I want to copy the active page in my Visio application to a new document and save it (and make it a byte[] for the db), I am already doing this but in a slightly "wrong" way as there is too much interaction with the Visio application.
Method to copy page to byte array:
private static byte[] VisioPageToBytes()
{
//Make a new invisible app to dump the shapes in
var app = new InvisibleApp();
Page page = MainForm.IVisioApplication.ActivePage;
app.AlertResponse = 2;
//Selact all shapes and copy, then deselect
MainForm.IVisioApplication.ActiveWindow.SelectAll();
MainForm.IVisioApplication.ActiveWindow.Selection.Copy();
MainForm.IVisioApplication.ActiveWindow.DeselectAll();
//Add empty document to invisible app and dump shapes
app.Documents.Add( string.Empty );
app.ActivePage.Paste();
//Save document and convert to byte[]
app.ActiveDocument.SaveAs( Application.UserAppDataPath + #"/LastStored.vsd" );
app.ActiveDocument.Close();
app.Quit();
app.AlertResponse = 0;
var bytes = File.ReadAllBytes( Application.UserAppDataPath + #"/LastStored.vsd" );
Clipboard.Clear();
return bytes;
}
Why it's wrong:
This code makes selections in the visio page and has to open an invisible window to store the page. I'm looking for a way with less interaction with the Visio application (as its unstable). The opening of the 2nd (invisible) Visio application occasionally makes my main Visio application crash.
I would like to do something like:
Page page = MainForm.IVisioApplication.ActivePage;
Document doc;
doc.Pages.Add( page ); //Pages.Add has no parameters so this doesn't work
doc.SaveAs(Application.UserAppDataPath + #"/LastStored.vsd");
If this is not possible in a way with less interaction (by "building" the document), please comment to let me know.
TL;DR;
I wan't to make a new Visio document without opening Visio and copy (the content of) 1 page to it.
If you want to create a copy page then you might find the Duplicate method on Page handy, but by the sounds of it just save the existing doc should work:
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var sourcePage = vApp.ActivePage;
var sourcePageNameU = sourcePage.NameU;
var vDoc = sourcePage.Document;
vDoc.Save(); //to retain original
var origFileName = vDoc.FullName;
var newFileName = Path.Combine(vDoc.Path, $"LastStored{Path.GetExtension(origFileName)}");
vDoc.SaveAs(newFileName);
//Remove all other pages
for (short i = vDoc.Pages.Count; i > 0; i--)
{
if (vDoc.Pages[i].NameU != sourcePageNameU)
{
vDoc.Pages[i].Delete(0);
}
}
//Save single page state
vDoc.Save();
//Close copy and reopen original
vDoc.Close();
vDoc = vApp.Documents.Open(origFileName);
}
GetRunningVisio is my extension method for using with LinqPad:
http://visualsignals.typepad.co.uk/vislog/2015/12/getting-started-with-c-in-linqpad-with-visio.html
...but you've already got a reference to your app so you can use that instead.
Update based on comments:
Ok, so how about this modification of your original code? Note that I'm creating a new Selection object from the page but not changing the Window one, so this shouldn't interfere with what the user sees or change the source doc at all.
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var sourcePage = vApp.ActivePage;
var sourceDoc = sourcePage.Document;
var vSel = sourcePage.CreateSelection(Visio.VisSelectionTypes.visSelTypeAll);
vSel.Copy(Visio.VisCutCopyPasteCodes.visCopyPasteNoTranslate);
var copyDoc = vApp.Documents.AddEx(string.Empty,
Visio.VisMeasurementSystem.visMSDefault,
(int)Visio.VisOpenSaveArgs.visAddHidden);
copyDoc.Pages[1].Paste(Visio.VisCutCopyPasteCodes.visCopyPasteNoTranslate);
var origFileName = sourceDoc.FullName;
var newFileName = Path.Combine(sourceDoc.Path, $"LastStored{Path.GetExtension(origFileName)}");
copyDoc.SaveAs(newFileName);
copyDoc.Close();
}
Note that this will only create a default page so you might want to include copying over page cells such as PageWidth, PageHeight, PageScale and DrawingScale etc. prior to pasting.

C# How to remove XPKeywords (PropertyItem 0x9c9e) from an image

I was having trouble editing or removing keywords from a photograph. The following works to replace the keywords successfully:
...
string s_keywords = "tag1;tag2;tag3";
PropertyItem item_keyword = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
item_keyword.Id = 0x9c9e; // XPKeywords
item_keyword.Type = 1;
item_keyword.Value = System.Text.Encoding.Unicode.GetBytes(s_keywords + "\0");
item_keyword.Len = item_keyword.Value.Length;
image.SetPropertyItem(item_keyword);
...
Note that my experiments show that image.RemovePropertyItem(0x9c9e); seems to have no effect on the saved image. Instead use the above code with s_keywords = "";
Don't do it this way: The following code works to remove the keywords, but results in the jpeg being re-encoded and loosing some quality (the image file goes from about 4MB to < 2MB and I can see some slight visual differences):
...
Image image_copy = new Bitmap(image);
foreach (var pi in image.PropertyItems)
{
if (pi.Id != 0x9c9e) image_copy.SetPropertyItem(pi);
}
image.Dispose();
image = (Image)image_copy.Clone();
...
I'm was having similar issues with setting the XPTitle - setting the propertyItem 0x9c9b seemed to have no effect in the saved image, instead I had to open the file as a BitmapFrame, extract the BitmapMetadata and use the Title property, then build a new jpeg using JpegBitmapEncoder - thus re-encoding and loosing image quality.
...
BitmapFrame bf_title = BitmapFrame.Create(new Uri(tmp_file, UriKind.Relative));
BitmapMetadata bmd_title = (BitmapMetadata)bf_title.Metadata.Clone();
bmd_title.Title = new_title;
BitmapFrame bf_new = BitmapFrame.Create(bf_title, bf_title.Thumbnail, bmd_title, bf_title.ColorContexts);
JpegBitmapEncoder je = new JpegBitmapEncoder();
je.Frames.Add(bf_new);
FileStream fs = new FileStream(tmp_file, FileMode.Create);
je.Save(fs);
fs.Close();
...
See my answer below for the correct way to change the title.
This was driving me crazy, I hope this can help someone else...
Note that the above keywords code now works perfectly.
I'm answering this because I could not find sample code to do this when I searched - so hopefully someone else will find this useful.
To change the title use the following code. The problem was that there are two tags that affect how windows shows the title - one of which (0x010e) takes priority over the XPTitle (0xc9b) tag...
...
string new_value = "New title for the image";
PropertyItem item_title = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
item_title.Id = 0x9c9b; // XPTitle 0x9c9b
item_title.Type = 1;
item_title.Value = System.Text.Encoding.Unicode.GetBytes(new_value + "\0");
item_title.Len = item_title.Value.Length;
image.SetPropertyItem(item_title);
PropertyItem item_title2 = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
item_title2.Id = 0x010e; // ImageDescription 0x010e
item_title2.Type = 2;
item_title2.Value = System.Text.Encoding.UTF8.GetBytes(new_value + "\0");
item_title2.Len = item_title2.Value.Length;
image.SetPropertyItem(item_title2);
image.Save("new_filename.jpg", ImageFormat.Jpeg)
image.Dispose();
...
Note - Another potential issue, you need to save the image to a new location. You can then dispose of the image and copy it into the original location if desired.

A generic error occurred in GDI+ (screenshot SaveAsFile, ExternalException)

Hello I tried looking through questions that may have my answer but none of them did.
I am doing web automation testing and trying to take a screenshot of when an error occurs and save it to a file in another folder.
My program has a 30 second timeout when searching for elements on the page. If after 30 seconds no element is found, it takes a SS, reloads the page, and tries again.
The first screenshot works fine. But on the second run through, it tries to save another screenshot to the folder and I get this incredibly vague error that is seemingly caused by a hundred different things, so I'm not quite sure what the problem is.
Here's my code:
public void takeScreenShot()
{
string ssPath = _persistencePath += "\\Errors";
string currTime = DateTime.Now.ToString(#"MMM-ddd-d-HH.mm");
Screenshot ss = ((ITakesScreenshot)_driver).GetScreenshot();
try
{
ss.SaveAsFile(ssPath + "\\ERROR-" + currTime + ".png", System.Drawing.Imaging.ImageFormat.Png);
ssCount = 0;
}
catch (System.Runtime.InteropServices.ExternalException)
{
ssCount++;
//error occurs here
ss.SaveAsFile(ssPath + "\\ERROR-" + currTime + "(" + ssCount + ")" + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
I initially thought the issue was that it was trying to save a file of the same name, because if the error happens during the same minute then the file name is the same. So that's why I added that catch block, in an attempt to change the name if it occurs in the same minute. But that didn't fix it.
Again I tried searching all over and couldn't find an answer. Any help is greatly appreciated.
Well, if anyone's curious I solved it. Turns out I'm just an idiot.
string ssPath = _persistencePath += "\\Errors";
this line was appending another \Errors to the target path on the second run though. thus invalidating the path, because \Errors\Errors didn't exist.
Thanks to everyone who commented/tried to help!
The Screenshot class doesn't dispose of the image resource properly, so it may be a bug in the Selenium framework. A work-around is to do it yourself:
public void takeScreenShot()
{
string ssPath = System.IO.Path.Combine(_persistencePath, #"\Errors");
string currTime = DateTime.Now.ToString(#"MMM-ddd-d-HH.mm");
string fileName = System.IO.Path.Combine(ssPath, #"\ERROR-" + currTime + ".png");
Screenshot ss = ((ITakesScreenshot)_driver).GetScreenshot();
using (MemoryStream ms = new MemoryStream(ss.AsByteArray))
using (Image screenShotImage = Image.FromStream(ms))
{
Bitmap cp = new Bitmap(screenShotImage);
cp.Save(filename, ImageFormat.Png);
cp.Dispose();
}
}
No assurance that this will fix it, but at least you can be reasonable assured that the resources are disposed of properly.
Note requires you to reference System.Drawing.dll in the project.
Edit another workaround posted.
Finally i did it!
check out this, just write completed PATHNAME,
pathname+filename+extension: #"C:\folderWithPermission\test.bmp"
JUST TAKE FOLDER WITH WRITE PERMISSIONS, was like o.0
here's the method
public DriverExecutor CaptureScreen(string filename)
{
Screenshot shot = this.myRemoteWebDriver.GetScreenshot();
MemoryStream ms;
Image imgShot;
Bitmap bitmap;
try
{
using (ms = new MemoryStream(shot.AsByteArray))
using (imgShot = Image.FromStream(ms))
using (bitmap = new Bitmap(imgShot))
{
bitmap.Save(filename, ImageFormat.Bmp);
}
}catch(Exception err){}
return this;
}
NOTE: im asssuming what remoteDriver was rightly instanced and encapsulated on a own object , i hope this help you all ^^

Deleting a bitmap from local directory

Alright it has come to this. I searched this website among many others and no one can seem to give me a straight answer so I'm going to try just asking outright. Been on this issue for about a solid 3 days and I can't afford to waste any more time on it.
Goal: The app I am building is in WPF and is going to be used as a bug tracker for a project my design team and I will be undertaking soon. Since we are going to be building a game in C++ most of the errors that occur will have a visual element to them so I inlcuded functionality to provide an image of the error in question when the user adds a bug to the list. I then take that image and save it to a local directory (for testing). Now the image path in the Error object points to a path that leads to the local directory. This functionality has been tested and works fine. My problem showes up when I want to delete a bug from the list. I am getting that very infamous "IO Exception" saying that the image I want to delete is being used by another process.
So Far: At first I tried very elegant solutions, but as with all things you get to a point where you just want to see if you can get the thing to even work at all. So I am at the point where most of the code I am using is experimental and radical. So please when looking at it note that the code being used is out of desperation, so any "simple" solutions have probably already been tried (I did research this a lot becuase I hate having to do this). Things i can think of off the top of my head are the obsurd amount of disposes and forced garbage collections being called so please to not comment on the negative nature of this practice, I am well aware :).
The Code
Saving image to local directory
public void OnBrowseClick()
{
Microsoft.Win32.OpenFileDialog openBox = new Microsoft.Win32.OpenFileDialog();
// Show dialog box to user and grab output
Nullable<bool> result = openBox.ShowDialog();
if (result == true)
{
// Create temp variable to hold local path string
string localPath = Directory.GetCurrentDirectory();
// Grab the extension of the specified file path
string extension = openBox.FileName.Substring(openBox.FileName.LastIndexOf("\\"));
// Add extension to local path
localPath += extension;
// Create local copy of image at given file path (being ridiculous at this point)
using (Stream stream = new FileStream(openBox.FileName, FileMode.Open, FileAccess.ReadWrite))
{
using (Bitmap bmp = LoadImage(stream))
{
using (Bitmap temp = (Bitmap)bmp.Clone())
{
temp.Save(localPath);
temp.Dispose();
}
bmp.Dispose();
}
stream.Dispose();
}
// Set the URL in the image text box (UI stuff)
LocalError.ImagePath = localPath;
}
}
The following is the LoadImage function that is used in the function above
private Bitmap LoadImage(Stream stream)
{
Bitmap retval = null;
using (Bitmap bitmap = new Bitmap(stream))
{
retval = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);
using (Graphics gdi = Graphics.FromImage(retval))
{
gdi.DrawImageUnscaled(bitmap, 0, 0);
gdi.Flush();
gdi.Dispose();
bitmap.Dispose();
}
}
// Garbage collection here to be safe
GC.WaitForPendingFinalizers();
GC.Collect();
return retval;
}
And finally we come to where I try to delete the image
public void OnDeleteClick()
{
// Ask user to make sure they want to delete selected item(s)
MessageBoxResult result = MessageBox.Show("Are you sure you want to delete selected item(s) from the list?",
"Delete", MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
for( int i = 0; i < Parent.ErrorListControl.ErrorDataGrid.SelectedItems.Count; ++i)
{
// Get path to image
string path = (Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i] as Error).ImagePath;
// Even tried calling garbage collection here!!!!!
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
File.Delete(path);
// Remove the error from the error list
Parent.ErrorListVM.ErrorList.Remove((Error)Parent.ErrorListControl.ErrorDataGrid.SelectedItems[i]);
// Decrement counter because we altered the list while in a loop
i--;
}
}
}
Notes: If anyone would like me to explain anything further or if you need to know something I left out please just ask I will get back to you ASAP! Any suggestions are helpful at this point I have absolutley no idea what I am doing wrong. I generally only program in a C++ environment so I tend to manage my own memory this whole "garbage collection" thing is really throwing a wrench in our project! (Off topic note: I do not know why I am not getting any color highlighting so I apologize to anyone who takes the time to read this).
Here's a simple way to do what you want. In this example, I'm using Path.GetTempFileName() to generate a random file name in the local user's temp directory. If you don't need to persist the files then it's a good place to store them temporarily. Also, the user could theoretically import two files with the same name. So you want to use some kind of random filename generation or other mechanism to avoid conflicts.
private void browseButton_Click(object sender, RoutedEventArgs e)
{
var openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog(this) == true)
{
using (Bitmap originalImage = new Bitmap(openFileDialog.FileName))
{
string tempFileName = System.IO.Path.GetTempFileName();
originalImage.Save(tempFileName);
// LocalError.LocalPath
LocalPath = tempFileName;
}
}
}
private void deleteButton_Click(object sender, RoutedEventArgs e)
{
if (File.Exists(LocalPath))
{
File.Delete(LocalPath);
}
}
Although a simple File.Copy should suffice as long as you have the right paths, I was just providing a solution that matched your question.
EDIT:
Actually the current directory does not seem to be changed by the OpenFileDialog. I could swear that it did at some point. So I don't think this is your problem. Regardless, this code still works for me and you shouldn't require anything more complicated than this.
EDIT #2:
It seems the lock is actually caused by the image being databound to the view and presumably locked by the BitmapSource. You should be able to create it without locking the file. Generally, this is slower so don't do it this way unless you need to be able to modify or delete the file.
bitmapSource = new BitmapImage();
bitmapSource.BeginInit();
bitmapSource.CacheOption = BitmapCacheOption.OnLoad;
bitmapSource.CreateOption = BitmapCreateOptions.IgnoreImageCache;
bitmapSource.UriSource = new Uri(ImagePath, UriKind.Absolute);
bitmapSource.EndInit();
Since your LoadImage method does simple copy of the image, why not use File.Copy(source, dest) and avoid all the bitmaps, drawings, etc? Your goal might be to modify local bitmap after it's created, but it can still be done after copy.
Also, when using the using block, explicit .Dispose() is not required, as using block does it for you:
using (var obj = new SomeDisposableObject())
{
// code here
// obj.Dispose(); <-- not needed, since...
} // ...at this point obj.Dispose is called automatically.

Categories