1 package com.atlassian.webdriver.visualcomparison;
2
3 import com.atlassian.selenium.visualcomparison.ScreenElement;
4 import com.atlassian.selenium.visualcomparison.v2.BrowserEngine;
5 import com.atlassian.selenium.visualcomparison.v2.settings.Resolution;
6 import com.atlassian.webdriver.debug.WebDriverDebug;
7 import org.openqa.selenium.*;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10
11 import javax.annotation.Nonnull;
12 import javax.annotation.Nullable;
13 import javax.inject.Inject;
14 import java.io.File;
15 import java.util.Collections;
16 import java.util.List;
17
18 import static com.atlassian.webdriver.utils.JavaScriptUtils.execute;
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static java.lang.String.format;
21
22
23
24
25 public final class WebDriverBrowserEngine implements BrowserEngine
26 {
27 private static final Logger log = LoggerFactory.getLogger(WebDriverBrowserEngine.class);
28
29 private final WebDriver webDriver;
30
31 @Inject
32 public WebDriverBrowserEngine(@Nonnull WebDriver webDriver)
33 {
34 this.webDriver = checkNotNull(webDriver, "webDriver");
35 }
36
37 @SuppressWarnings("ConstantConditions")
38 @Nonnull
39 @Override
40 public ScreenElement getElementAt(int x, int y)
41 {
42 try
43 {
44 ElementByPoint elementByPoint = new ElementByPoint(x, y).calculate();
45
46 executeScript(Object.class, format("window.scrollTo(%d,%d)",
47 elementByPoint.scrollTo.getX(), elementByPoint.scrollTo.getY()));
48 final WebElement element = webDriver.findElement(byPoint(elementByPoint.elementCoordinates));
49 return new SimpleScreenElement(executeScript(String.class,
50 "var d = document.createElement('div'); d.appendChild(arguments[0].cloneNode(true)); return d.innerHTML;",
51 element));
52 }
53 catch (Exception e)
54 {
55 log.error(format("Error while getting screen element at %d,%d", x, y), e);
56 return new SimpleScreenElement("");
57 }
58 }
59
60 @Nonnull
61 @Override
62 public BrowserEngine resizeTo(@Nonnull Resolution resolution)
63 {
64 setSize(resolution);
65
66 setSize(resolution);
67 return this;
68 }
69
70 @Nonnull
71 @Override
72 public BrowserEngine captureScreenshotTo(@Nonnull File file)
73 {
74 new WebDriverDebug(webDriver).takeScreenshotTo(file);
75 return this;
76 }
77
78 @Nonnull
79 @Override
80 public BrowserEngine reloadPage()
81 {
82 webDriver.navigate().refresh();
83 return this;
84 }
85
86 @Nullable
87 @Override
88 public <T> T executeScript(@Nonnull Class<T> returnType, @Nonnull String script, @Nonnull Object... args)
89 {
90 return execute(returnType, script, webDriver, args);
91 }
92
93 private By byPoint(final Point coordinates)
94 {
95 return new By()
96 {
97 @Override
98 public List<WebElement> findElements(SearchContext context)
99 {
100 final String findByPoint = format("return document.elementFromPoint(%d,%d);", coordinates.getX(),
101 coordinates.getY());
102 final WebElement element = execute(WebElement.class, findByPoint, webDriver);
103 return element != null ? Collections.singletonList(element) : Collections.<WebElement>emptyList();
104 }
105
106 @Override
107 public String toString()
108 {
109 return format("By point in viewport %d,%d", coordinates.getX(), coordinates.getY());
110 }
111 };
112 }
113
114 @SuppressWarnings("ConstantConditions")
115 private Dimension getDimensionFor(String selector)
116 {
117 long x = executeScript(Long.class, "return jQuery(" + selector + ").width();");
118 long y = executeScript(Long.class, "return jQuery(" + selector + ").height();");
119 return new Dimension((int) x, (int) y);
120 }
121
122 private void setSize(Resolution resolution)
123 {
124 webDriver.manage().window().setSize(new Dimension(resolution.getWidth(), resolution.getHeight()));
125 }
126
127 private final class ElementByPoint
128 {
129 private final int x;
130 private final int y;
131
132 private Dimension viewport;
133 private Dimension document;
134
135 Point scrollTo;
136 Point elementCoordinates;
137
138 private ElementByPoint(int x, int y)
139 {
140 this.x = x;
141 this.y = y;
142 }
143
144 ElementByPoint calculate()
145 {
146 this.document = getDimensionFor("document");
147 this.viewport = getDimensionFor("window");
148 calculateScroll();
149 calculateCoordinates();
150 return this;
151 }
152
153 private void calculateScroll()
154 {
155 int deltaX = Math.max(0, document.getWidth() - viewport.getWidth());
156 int scrollX = Math.min(x, deltaX);
157 int deltaY = Math.max(0, document.getHeight() - viewport.getHeight());
158 int scrollY = Math.min(y, deltaY);
159 scrollTo = new Point(scrollX, scrollY);
160 }
161
162 private void calculateCoordinates()
163 {
164 int relativeX = x - scrollTo.getX();
165 int relativeY = y - scrollTo.getY();
166 elementCoordinates = new Point(relativeX, relativeY);
167 }
168 }
169
170 private static final class SimpleScreenElement implements ScreenElement
171 {
172
173 private final String html;
174
175 private SimpleScreenElement(@Nonnull String html)
176 {
177 this.html = html;
178 }
179
180 @Override
181 @Nonnull
182 public String getHtml()
183 {
184 return html;
185 }
186 }
187
188 }