com.googlecode.fightinglayoutbugs.WebPage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fighting-layout-bugs Show documentation
Show all versions of fighting-layout-bugs Show documentation
A library for automatic detection of layout bugs in web pages
The newest version!
/*
* Copyright 2009-2012 Michael Tamm
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.fightinglayoutbugs;
import com.googlecode.fightinglayoutbugs.ScreenshotCache.Condition;
import com.googlecode.fightinglayoutbugs.helpers.RectangularRegion;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import static com.googlecode.fightinglayoutbugs.ScreenshotCache.Condition.UNMODIFIED;
import static com.googlecode.fightinglayoutbugs.helpers.StringHelper.asString;
/**
* Represents a web page. This class was created to improve
* performance when several {@link LayoutBugDetector}s
* analyze the same page -- it caches as much information
* as possible. Furthermore it stops all JavaScript and
* CSS animations to prevent false alarms.
*/
public class WebPage {
private static final Log LOG = LogFactory.getLog(WebPage.class);
private final WebDriver _driver;
private final ScreenshotCache _screenshotCache;
private TextDetector _textDetector;
private EdgeDetector _edgeDetector;
private URL _url;
private SoftReference _html;
private SoftReference _textPixels;
private SoftReference _horizontalEdges;
private SoftReference _verticalEdges;
/**
* Side effects:
* - will inject jQuery into the current page (if not already present)
* - will stop all JavaScript animations
* - will stop all CSS animations
* - will disable all CSS transitions
*
*/
public WebPage(WebDriver driver) {
_driver = driver;
_screenshotCache = new ScreenshotCache(this);
injectJQueryIfNotPresent();
stopJavaScriptAnimations();
stopCssAnimationsAndDisableCssTransistions();
}
private void injectJQueryIfNotPresent() {
// Check if jQuery is present ...
if ("undefined".equals(executeJavaScript("return typeof jQuery"))) {
String jquery = readResource("jquery-1.7.2.min.js");
executeJavaScript(jquery);
// Check if jQuery was successfully injected ...
if (!"1.7.2".equals(executeJavaScript("return jQuery.fn.jquery"))) {
throw new RuntimeException("Failed to inject jQuery.");
}
}
}
private void stopJavaScriptAnimations() {
executeJavaScript(
"var noop = function() {};\n" +
"var i;\n" +
"var n = window.setTimeout(noop, 1);\n" +
"for (i = 0; i <= n; ++i) window.clearTimeout(i);\n" +
"window.setTimeout = noop;\n" +
"n = window.setInterval(noop, 1);\n" +
"for (i = 0; i <= n; ++i) window.clearInterval(i);\n" +
"window.setInterval = noop;\n"
);
}
private void stopCssAnimationsAndDisableCssTransistions() {
executeJavaScript(
"jQuery('*').each(function() {\n" +
" var $x = jQuery(this);\n" +
" var prefixes = ['', '-webkit-', '-moz-', '-ms-', '-o-'];\n" +
" var i;\n" +
" for (i = 0; i < prefixes.length; ++i) {\n" +
" $x.css(prefixes[i] + 'animation-play-state', 'paused');\n" +
" $x.css(prefixes[i] + 'transition-property', 'none');\n" +
" }" +
"}).size();" // ... the trailing ".size()" will reduce the size of the response
);
}
public WebDriver getDriver() {
return _driver;
}
/**
* Sets the detector for {@link #getTextPixels()},
* default is the {@link AnimationAwareTextDetector}.
*/
public void setTextDetector(TextDetector textDetector) {
if (_textPixels != null) {
throw new IllegalStateException("getTextPixels() was already called.");
}
_textDetector = textDetector;
}
/**
* Sets the detector for {@link #getHorizontalEdges()} and {@link #getVerticalEdges()},
* default is the {@link SimpleEdgeDetector}.
*/
public void setEdgeDetector(EdgeDetector edgeDetector) {
if (_horizontalEdges != null) {
throw new IllegalStateException("getHorizontalEdges() was already called.");
}
if (_verticalEdges != null) {
throw new IllegalStateException("getVerticalEdges() was already called.");
}
_edgeDetector = edgeDetector;
}
/**
* Returns the URL of this web page.
*/
public URL getUrl() {
if (_url == null) {
String urlAsString = _driver.getCurrentUrl();
try {
_url = new URL(urlAsString);
} catch (MalformedURLException e) {
throw new RuntimeException("Could not convert " + asString(urlAsString) + " into an URL.", e);
}
}
return _url;
}
/**
* Returns the source HTML of this web page.
*/
@Nonnull
public String getHtml() {
String html = (_html == null ? null : _html.get());
if (html == null) {
html = _driver.getPageSource();
_html = new SoftReference(html);
}
return html;
}
public Screenshot getScreenshot() {
return getScreenshot(UNMODIFIED);
}
public Screenshot getScreenshot(Condition condition) {
return _screenshotCache.getScreenshot(condition);
}
/**
* Bypasses the cache and always takes a screenshot.
*/
public Screenshot takeScreenshot() {
return takeScreenshot(UNMODIFIED);
}
/**
* Bypasses the cache and always takes a screenshot.
*/
public Screenshot takeScreenshot(Condition condition) {
return _screenshotCache.takeScreenshot(condition);
}
/**
* Returns a two dimensional array a, whereby a[x][y] is true
* if the pixel with the coordinates x,y in a {@link #getScreenshot screenshot} of this web page
* belongs to displayed text, otherwise a[x][y] is false.
*/
public boolean[][] getTextPixels() {
boolean[][] textPixels;
if (_textPixels == null) {
if (_textDetector == null) {
_textDetector = new AnimationAwareTextDetector();
}
textPixels = _textDetector.detectTextPixelsIn(this);
_textPixels = new SoftReference(textPixels);
} else {
textPixels = _textPixels.get();
if (textPixels == null) {
LOG.warn("Cached result of text detection was garbage collected, running text detection again -- give the JVM more heap memory to speed up layout bug detection.");
_textPixels = null;
return getTextPixels();
}
}
return textPixels;
}
@edu.umd.cs.findbugs.annotations.SuppressWarnings("SBSC")
public Collection getRectangularRegionsCoveredBy(Collection jQuerySelectors) {
if (jQuerySelectors.isEmpty()) {
return Collections.emptySet();
}
// 1.) Assemble JavaScript to select elements ...
Iterator i = jQuerySelectors.iterator();
String js = "jQuery('" + i.next().replace("'", "\\'");
while (i.hasNext()) {
js += "').add('" + i.next().replace("'", "\\'");
}
js += "').filter(':visible')";
// 2.) Assemble JavaScript function to fill an array with rectangular region of each selected element ...
js = "function() { " +
"var a = new Array(); " +
js + ".each(function(i, e) { " +
"var j = jQuery(e); " +
"var o = j.offset(); " +
"a.push({ top: o.top, left: o.left, width: j.width(), height: j.height() }); " +
"}); " +
"return a; " +
"}";
// 3.) Execute JavaScript function ...
@SuppressWarnings("unchecked")
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy