All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.fightinglayoutbugs.WebPage Maven / Gradle / Ivy

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> list = (List>) executeJavaScript("return (" + js + ")()"); // 4.) Convert JavaScript return value to Java return value ... if (list.isEmpty()) { return Collections.emptySet(); } Collection result = new ArrayList(list.size()); for (Map map : list) { double left = map.get("left").doubleValue(); double width = map.get("width").doubleValue(); double top = map.get("top").doubleValue(); double height = map.get("height").doubleValue(); if (height > 0 && width > 0) { int x1 = (int) left; int y1 = (int) top; int x2 = (int) Math.round(left + width - 0.5000001); int y2 = (int) Math.round(top + height - 0.5000001); if (x2 >= 0 && y2 >= 0) { if (x1 < 0) { x1 = 0; } if (y1 < 0) { y1 = 0; } if (x1 <= x2 && y1 <= y2) { result.add(new RectangularRegion(x1, y1, x2, y2)); } } } } return result; } /** * 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 a horizontal edge, otherwise a[x][y] is false. */ public boolean[][] getHorizontalEdges() { boolean[][] horizontalEdges; if (_horizontalEdges == null) { if (_edgeDetector == null) { _edgeDetector = new SimpleEdgeDetector(); } horizontalEdges = _edgeDetector.detectHorizontalEdgesIn(this); _horizontalEdges = new SoftReference(horizontalEdges); } else { horizontalEdges = _textPixels.get(); if (horizontalEdges == null) { LOG.warn("Cached result of horizontal edge detection was garbage collected, running horizontal edge detection again -- give the JVM more heap memory to speed up layout bug detection."); _horizontalEdges = null; return getHorizontalEdges(); } } return horizontalEdges; } /** * 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 a vertical edge, otherwise a[x][y] is false. */ public boolean[][] getVerticalEdges() { boolean[][] verticalEdges; if (_verticalEdges == null) { if (_edgeDetector == null) { _edgeDetector = new SimpleEdgeDetector(); } verticalEdges = _edgeDetector.detectVerticalEdgesIn(this); _verticalEdges = new SoftReference(verticalEdges); } else { verticalEdges = _textPixels.get(); if (verticalEdges == null) { LOG.warn("Cached result of vertical edge detection was garbage collected, running vertical edge detection again -- give the JVM more heap memory to speed up layout bug detection."); _verticalEdges = null; return getVerticalEdges(); } } return verticalEdges; } /** * Returns all elements on this web page for the given find criteria. */ public List findElements(By by) { return _driver.findElements(by); } /** * Executes the given JavaScript in the context of this web page. */ protected Object executeJavaScript(String javaScript, Object... arguments) { if (_driver instanceof JavascriptExecutor) { return ((JavascriptExecutor) _driver).executeScript(javaScript, arguments); } else { throw new UnsupportedOperationException("Can't execute JavaScript via " + _driver.getClass().getName()); } } protected String readResource(String resourceFileName) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); try { InputStream in = getClass().getResourceAsStream(resourceFileName); try { try { IOUtils.copy(in, buf); } catch (IOException e) { throw new RuntimeException("Could not read " + resourceFileName, e); } } finally { IOUtils.closeQuietly(in); } } finally { IOUtils.closeQuietly(buf); } try { return new String(buf.toByteArray(), "UTF-8"); } catch (UnsupportedEncodingException e) { // Should never happen throw new RuntimeException(e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy