View Javadoc

1   package com.atlassian.selenium.visualcomparison.v2;
2   
3   import com.atlassian.annotations.ExperimentalApi;
4   import com.atlassian.selenium.visualcomparison.ScreenElement;
5   import com.atlassian.selenium.visualcomparison.VisualComparableClient;
6   import com.atlassian.selenium.visualcomparison.VisualComparer;
7   import com.atlassian.selenium.visualcomparison.utils.BoundingBox;
8   import com.atlassian.selenium.visualcomparison.utils.ScreenResolution;
9   import com.atlassian.selenium.visualcomparison.v2.settings.PagePart;
10  import com.atlassian.selenium.visualcomparison.v2.settings.Replacement;
11  import com.atlassian.selenium.visualcomparison.v2.settings.Resolution;
12  import com.google.common.base.Function;
13  import com.google.common.collect.ImmutableList;
14  import com.google.common.collect.ImmutableMap;
15  
16  import javax.annotation.Nonnull;
17  import javax.annotation.Nullable;
18  import javax.annotation.concurrent.Immutable;
19  import java.io.File;
20  import java.util.List;
21  import java.util.Map;
22  
23  import static com.atlassian.selenium.visualcomparison.v2.ComparisonSettings.emptySettings;
24  import static com.google.common.base.Preconditions.checkNotNull;
25  import static com.google.common.base.Preconditions.checkState;
26  import static com.google.common.collect.Iterables.toArray;
27  import static com.google.common.collect.Iterables.transform;
28  
29  /**
30   * Current default implementation of {@link Comparer}. This implementation delegates the visual comparison requests to
31   * the {@link com.atlassian.selenium.visualcomparison.VisualComparer old visual comparison library}, transforming the
32   * request, the settings and the results in the process.
33   *
34   * <p/>
35   * {@link BrowserEngine} is used as the underlying SPI for the browser automation framework and
36   * {@link ComparisonSettings} as the vehicle for configuring the visual comparison, both at the
37   * {@link #DefaultComparer(BrowserEngine, ComparisonSettings) instance} and at the
38   * {@link #compare(String, ComparisonSettings) single comparison} level.
39   *
40   * <p/>
41   * NOTE: this implementation is likely to change in the future to migrate away from the old library. Depending on
42   * implementation details of this class is highly discouraged.
43   *
44   * @since 2.3
45   */
46  @ExperimentalApi
47  @Immutable
48  public final class DefaultComparer implements Comparer
49  {
50      private final BrowserEngine engine;
51      private final ComparisonSettings settings;
52  
53      public DefaultComparer(@Nonnull BrowserEngine engine, ComparisonSettings settings) {
54          this.engine = checkNotNull(engine, "engine");
55          this.settings = checkNotNull(settings, "settings");
56      }
57  
58      @Override
59      public void compare(@Nonnull String id)
60      {
61          compare(id, emptySettings());
62      }
63  
64      @Override
65      public void compare(@Nonnull String id, @Nonnull ComparisonSettings extraSettings)
66      {
67          checkNotNull(id, "id");
68          checkNotNull(extraSettings, "settings");
69          final ComparisonSettings effectiveSettings = settings.merge(extraSettings);
70          validateSettings(effectiveSettings);
71          VisualComparer comparer = getComparer(effectiveSettings);
72          try
73          {
74              if (!comparer.uiMatches(id, effectiveSettings.getBaselineDirectory().getAbsolutePath()))
75              {
76                  String message = "Screenshots did not match the baseline in "
77                          + effectiveSettings.getBaselineDirectory().getAbsolutePath() + ".";
78                  if (effectiveSettings.isReportingEnabled())
79                  {
80                      message += " Check reports in " + effectiveSettings.getReportingDirectory().getAbsolutePath()
81                              + " for more details.";
82                  }
83                  throw new VisualComparisonFailedException(id, message);
84              }
85          }
86          catch (Exception e)
87          {
88              if (e instanceof VisualComparisonFailedException)
89              {
90                  throw (VisualComparisonFailedException) e;
91              }
92              else
93              {
94                  throw new VisualComparisonFailedException(id, "Error when performing comparison", e);
95              }
96          }
97      }
98  
99      @SuppressWarnings("ConstantConditions")
100     private void validateSettings(ComparisonSettings effectiveSettings)
101     {
102         checkState(effectiveSettings.getBaselineDirectory() != null, "Baseline directory must be provided");
103         if (!effectiveSettings.getBaselineDirectory().isDirectory())
104         {
105             checkState(effectiveSettings.getBaselineDirectory().mkdirs(), "Unable to create baseline directory "
106                     + effectiveSettings.getBaselineDirectory().getAbsolutePath());
107         }
108         if (effectiveSettings.isReportingEnabled() && !effectiveSettings.getReportingDirectory().isDirectory())
109         {
110             checkState(effectiveSettings.getReportingDirectory().mkdirs(), "Unable to create reporting directory "
111                                 + effectiveSettings.getReportingDirectory().getAbsolutePath());
112         }
113     }
114 
115     private VisualComparer getComparer(ComparisonSettings effectiveSettings)
116     {
117         VisualComparer comparer = new VisualComparer(new BrowserEngineComparableClient());
118         comparer.setScreenResolutions(getResolutions(effectiveSettings));
119         if (effectiveSettings.isReportingEnabled())
120         {
121             comparer.enableReportGeneration(effectiveSettings.getReportingDirectory().getAbsolutePath());
122         }
123         comparer.setIgnoreSingleLineDiffs(effectiveSettings.isIgnoreSingleLineDifferences());
124         comparer.setRefreshAfterResize(effectiveSettings.isRefreshAfterResize());
125         comparer.setIgnoreAreas(getIgnoreAreas(effectiveSettings));
126         comparer.setUIStringReplacements(getReplacements(effectiveSettings));
127         comparer.setWaitforJQueryTimeout(5000);
128         return comparer;
129     }
130 
131     private ScreenResolution[] getResolutions(ComparisonSettings effectiveSettings)
132     {
133         return toArray(transform(effectiveSettings.getResolutions(), new Function<Resolution, ScreenResolution>()
134         {
135             @Nullable
136             @Override
137             public ScreenResolution apply(Resolution input)
138             {
139                 return new ScreenResolution(input.getWidth(), input.getHeight());
140             }
141         }), ScreenResolution.class);
142     }
143 
144     private List<BoundingBox> getIgnoreAreas(ComparisonSettings effectiveSettings)
145     {
146         return ImmutableList.copyOf(transform(effectiveSettings.getIgnoredParts(), new Function<PagePart, BoundingBox>()
147         {
148             @Nullable
149             @Override
150             public BoundingBox apply(PagePart input)
151             {
152                 return new BoundingBox(input.getLeft(), input.getTop(), input.getRight(), input.getBottom());
153             }
154         }));
155     }
156 
157     private Map<String, String> getReplacements(ComparisonSettings effectiveSettings)
158     {
159         ImmutableMap.Builder<String,String> builder = ImmutableMap.builder();
160         for (Replacement replacement : effectiveSettings.getReplacements())
161         {
162             builder.put(replacement.getElementId(), replacement.getHtml());
163         }
164         return builder.build();
165     }
166 
167     private final class BrowserEngineComparableClient implements VisualComparableClient
168     {
169 
170         @Override
171         public void captureEntirePageScreenshot(String filePath)
172         {
173             engine.captureScreenshotTo(new File(filePath));
174         }
175 
176         @Override
177         public ScreenElement getElementAtPoint(int x, int y)
178         {
179             return engine.getElementAt(x, y);
180         }
181 
182         @Override
183         public void evaluate(String command)
184         {
185             execute(command);
186         }
187 
188         @Override
189         public Object execute(String command, Object... arguments)
190         {
191             return engine.executeScript(Object.class, command, arguments);
192         }
193 
194         @Override
195         public boolean resizeScreen(ScreenResolution resolution, boolean refreshAfterResize)
196         {
197             engine.resizeTo(new Resolution((int) resolution.getWidth(), (int) resolution.getHeight()));
198             if (refreshAfterResize)
199             {
200                 refreshAndWait();
201             }
202             return true;
203         }
204 
205         @Override
206         public void refreshAndWait()
207         {
208             engine.reloadPage();
209         }
210 
211         @Override
212         public boolean waitForJQuery(long waitTimeMillis)
213         {
214             Long jQueryActive = null;
215             try
216             {
217                 while (jQueryActive == null || jQueryActive != 0)
218                 {
219                     jQueryActive = engine.executeScript(Long.class, "return window.jQuery.active");
220                     Thread.sleep(100);
221                 }
222             }
223             catch (InterruptedException e)
224             {
225                 return false;
226             }
227             return true;
228         }
229     }
230 }