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