View Javadoc

1   package com.atlassian.selenium.visualcomparison;
2   
3   import com.atlassian.selenium.visualcomparison.utils.BoundingBox;
4   import com.atlassian.selenium.visualcomparison.utils.PageDifference;
5   import com.atlassian.selenium.visualcomparison.utils.PageElementInfo;
6   import com.atlassian.selenium.visualcomparison.utils.ScreenResolution;
7   import com.atlassian.selenium.visualcomparison.utils.Screenshot;
8   import com.atlassian.selenium.visualcomparison.utils.ScreenshotDiff;
9   import junit.framework.Assert;
10  
11  import javax.imageio.ImageIO;
12  import java.awt.*;
13  import java.io.File;
14  import java.io.FileFilter;
15  import java.io.IOException;
16  import java.util.ArrayList;
17  import java.util.Collections;
18  import java.util.List;
19  import java.util.Map;
20  
21  public class VisualComparer
22  {
23      private ScreenResolution[] resolutions = new ScreenResolution[]
24              {
25                      new ScreenResolution(1280, 1024)
26              };
27      private VisualComparableClient client;
28      private boolean refreshAfterResize = false;
29      private boolean reportingEnabled = false;
30      private String reportOutputPath;
31      private String imageSubDirName = "report_images";
32      private String tempPath = System.getProperty("java.io.tmpdir");
33      private Map<String, String> uiStringReplacements = null;
34      private long waitforJQueryTimeout = 0;
35      private List<BoundingBox> ignoreAreas = null;
36      private boolean ignoreSingleLineDiffs = false;
37  
38      public long getWaitforJQueryTimeout() {
39          return waitforJQueryTimeout;
40      }
41  
42      public void setWaitforJQueryTimeout(long waitforJQueryTimeout) {
43          this.waitforJQueryTimeout = waitforJQueryTimeout;
44      }
45  
46      public VisualComparer(VisualComparableClient client)
47      {
48          this.client = client;
49      }
50  
51      public void setScreenResolutions(ScreenResolution[] resolutions)
52      {
53          this.resolutions = resolutions;
54      }
55  
56      public ScreenResolution[] getScreenResolutions()
57      {
58          return this.resolutions;
59      }
60  
61      public void setRefreshAfterResize(boolean refreshAfterResize)
62      {
63          this.refreshAfterResize = refreshAfterResize;
64      }
65  
66      public boolean getRefreshAfterResize()
67      {
68          return this.refreshAfterResize;
69      }
70  
71      public void setUIStringReplacements(Map<String,String> uiStringReplacements)
72      {
73          this.uiStringReplacements = uiStringReplacements;
74      }
75  
76      public Map<String,String> getUIStringReplacements()
77      {
78          return this.uiStringReplacements;
79      }
80  
81      public void enableReportGeneration(String reportOutputPath)
82      {
83          this.reportingEnabled = true;
84          this.reportOutputPath = reportOutputPath;
85          
86          File file = new File(reportOutputPath + "/" + imageSubDirName);
87          file.mkdirs();
88      }
89  
90      public void disableReportGeneration()
91      {
92          this.reportingEnabled = false;
93      }
94  
95      public void setTempPath(String tempPath)
96      {
97          File file = new File(tempPath);
98          file.mkdirs();
99          this.tempPath = tempPath;
100     }
101 
102     public String getTempPath()
103     {
104         return this.tempPath;
105     }
106 
107 
108     public List<BoundingBox> getIgnoreAreas()
109     {
110         return ignoreAreas;
111     }
112 
113     public boolean getIgnoreSingleLineDiffs()
114     {
115         return ignoreSingleLineDiffs;
116     }
117 
118     public void setIgnoreSingleLineDiffs(boolean ignoreSingleLineDiffs)
119     {
120         this.ignoreSingleLineDiffs = ignoreSingleLineDiffs;
121     }
122 
123     public void setIgnoreAreas(List<BoundingBox> ignoreAreas)
124     {
125         this.ignoreAreas = ignoreAreas;
126     }
127 
128     public void assertUIMatches(String id, String baselineImagePath)
129     {
130         try
131         {
132             Assert.assertTrue("Screenshots were not equal", uiMatches(id, baselineImagePath));
133         }
134         catch (Exception e)
135         {
136             throw new RuntimeException(e);
137         }
138     }
139 
140     public boolean uiMatches(final String id, final String baselineImagePath) throws Exception
141     {
142         ArrayList<Screenshot> currentScreenshots = takeScreenshots(id);
143         ArrayList<Screenshot> baselineScreenshots = loadBaselineScreenshots(id, baselineImagePath);
144         return compareScreenshots(baselineScreenshots, currentScreenshots);
145     }
146 
147     public ArrayList<Screenshot> takeScreenshots(final String id) throws IOException
148     {
149         // Capture a series of screenshots in all the valid screen resolutions.
150         ArrayList<Screenshot> screenshots = new ArrayList<Screenshot>();
151         for (ScreenResolution res : resolutions)
152         {
153             client.resizeScreen(res, refreshAfterResize);
154             if (waitforJQueryTimeout > 0)
155             {
156                 if (!client.waitForJQuery (waitforJQueryTimeout))
157                 {
158                     Assert.fail("Timed out while waiting for jQuery to complete");
159                 }
160             }
161             if (uiStringReplacements != null)
162             {
163                 // Remove strings from the UI that we are expecting will change
164                 // (such as the build number in the JIRA footer)
165                 for (String key : uiStringReplacements.keySet())
166                 {
167                     replaceUIHtml(key, uiStringReplacements.get(key));
168                 }
169             }
170             screenshots.add(new Screenshot(client, id, tempPath, res));
171         }
172         Collections.sort(screenshots);
173         return screenshots;
174     }
175 
176     public ArrayList<Screenshot> loadBaselineScreenshots(final String id, final String baselineImagePath) throws IOException
177     {
178         File screenshotDir = new File(baselineImagePath);
179         File[] screenshotFiles = screenshotDir.listFiles(
180                 new FileFilter()
181                 {
182                     public boolean accept(File file) { return file.getName().startsWith(id);}
183                 });
184 
185         ArrayList<Screenshot> screenshots = new ArrayList<Screenshot>();
186         for (File screenshotFile : screenshotFiles)
187         {
188             screenshots.add(new Screenshot(screenshotFile));
189         }
190         Collections.sort(screenshots);
191         return screenshots;
192     }
193 
194     protected void replaceUIHtml(String id, String newContent)
195     {
196         final String script = "var content, el = window.document.getElementById('" + id + "');" +
197                 "if (el) { content = el.innerHTML; el.innerHTML = \"" + newContent + "\"; } content;";
198         final Object result = client.execute(script);
199         final String value = String.valueOf(result);
200     }
201 
202     public boolean compareScreenshots(ArrayList<Screenshot> oldScreenshots, ArrayList<Screenshot> newScreenshots)
203             throws Exception
204     {
205         if (oldScreenshots.size() != newScreenshots.size())
206         {
207             if (oldScreenshots.size() == 0)
208             {
209                 if (reportingEnabled)
210                 {
211                     String imageOutputDir = ScreenshotDiff.getImageOutputDir(reportOutputPath, imageSubDirName);
212                     for (Screenshot newScreenshot : newScreenshots)
213                     {
214                         // Copy the new image to the output directory.
215                         ImageIO.write(newScreenshot.getImage(), "png", new File(imageOutputDir + newScreenshot.getFileName()));
216                     }
217                 }
218                 throw new IllegalArgumentException("There were new screenshots, but no baseline images. Is this a new test?"
219                     + " If reporting is enabled, the new screenshots will be output in the report.");
220             }
221             else
222             {
223                 throw new IllegalArgumentException("Incorrect number of images."
224                         + " There were " + oldScreenshots.size() + " baseline images,"
225                         + " but only " + newScreenshots.size() + " new images.");
226             }
227         }
228 
229         boolean matches = true;
230         for (int i = 0; i < oldScreenshots.size(); i++)
231         {
232             ScreenshotDiff diff = getScreenshotDiff(oldScreenshots.get(i), newScreenshots.get(i));
233 
234             for (PageDifference difference : diff.getDifferences())
235             {
236                 BoundingBox box = difference.getBoundingBox();
237                 int x = new Double(Math.floor(box.getLeft() + box.getWidth() / 2)).intValue();
238                 int y = new Double(Math.floor(box.getTop() + box.getHeight() / 2)).intValue();
239                 ScreenElement thing = client.getElementAtPoint(x, y);
240                 PageElementInfo info = new PageElementInfo();
241                 info.htmlContent = thing.getHtml();
242                 info.position = new Point(x,y);
243                 difference.addPageElement(info);
244             }
245 
246             if (reportingEnabled)
247             {
248                 diff.writeDiffReport(reportOutputPath, imageSubDirName);
249             }
250             matches = !diff.hasDifferences() && matches;
251         }
252 
253         return matches;
254     }
255 
256     public ScreenshotDiff getScreenshotDiff(Screenshot oldScreenshot, Screenshot newScreenshot) throws Exception
257     {
258         return oldScreenshot.getDiff(newScreenshot, ignoreAreas, ignoreSingleLineDiffs);
259     }
260 
261 
262 }