com.googlecode.fightinglayoutbugs.FightingLayoutBugs 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.helpers.DebugHelper;
import com.googlecode.fightinglayoutbugs.helpers.ImageHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.*;
import org.apache.log.LogKit;
import org.apache.log.Priority;
import org.apache.log4j.LogManager;
import javax.annotation.Nonnull;
import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Finds different layout bugs in a web page by executing several
* {@link LayoutBugDetector}s. By default the following detectors
* are enabled:
* - {@link DetectInvalidImageUrls}
* - {@link DetectTextNearOrOverlappingHorizontalEdge}
* - {@link DetectTextNearOrOverlappingVerticalEdge}
* - {@link DetectTextWithTooLowContrast}
*
*/
public class FightingLayoutBugs extends AbstractLayoutBugDetector {
private static final Log LOG = LogFactory.getLog(FightingLayoutBugs.class);
private List _runAfterAnalysis = new ArrayList();
private TextDetector _textDetector;
private EdgeDetector _edgeDetector;
private final List _detectors = new ArrayList();
private boolean _debugMode;
/**
* Registers the following detectors:
* - {@link DetectInvalidImageUrls}
* - {@link DetectTextNearOrOverlappingHorizontalEdge}
* - {@link DetectTextNearOrOverlappingVerticalEdge}
* - {@link DetectTextWithTooLowContrast}
*
*/
public FightingLayoutBugs() {
this(
new DetectInvalidImageUrls(),
new DetectTextNearOrOverlappingHorizontalEdge(),
new DetectTextNearOrOverlappingVerticalEdge(),
new DetectTextWithTooLowContrast()
);
}
public FightingLayoutBugs(LayoutBugDetector... detectors) {
for (LayoutBugDetector d : detectors) {
enable(d);
}
}
/**
* Sets the {@link TextDetector} to use.
*/
public void setTextDetector(TextDetector textDetector) {
_textDetector = textDetector;
}
/**
* Sets the {@link EdgeDetector} to use.
*/
public void setEdgeDetector(EdgeDetector edgeDetector) {
_edgeDetector = edgeDetector;
}
/**
* Call this method to enable the debug mode, which produces
* more log output and several screenshots of intermediate
* analysis results.
*/
public void enableDebugMode() {
_debugMode = true;
}
/**
* Adds the given detector to the set of detectors, which will be executed,
* when {@link #findLayoutBugsIn(WebPage) findLayoutBugsIn(...)} is called.
* If there is already a detector of the same class registered, it will be
* replaced by the given detector.
*/
public void enable(LayoutBugDetector detector) {
if (detector == null) {
throw new IllegalArgumentException("Method parameter newDetector must not be null.");
}
disable(detector.getClass());
_detectors.add(detector);
}
/**
* Removes all detectors of the given class from the set of detectors, which will be executed,
* when {@link #findLayoutBugsIn(WebPage) findLayoutBugsIn(...)} is called.
*/
public void disable(Class extends LayoutBugDetector> detectorClass) {
if (detectorClass == null) {
throw new IllegalArgumentException("Method parameter detectorClass must not be null.");
}
Iterator i = _detectors.iterator();
while (i.hasNext()) {
LayoutBugDetector detector = i.next();
if (detector.getClass().isAssignableFrom(detectorClass) || detectorClass.isAssignableFrom(detector.getClass())) {
i.remove();
}
}
}
/**
* Call this method to gain access to one of the {@link LayoutBugDetector}s
* for calling setter methods on it.
*/
public D configure(Class detectorClass) {
if (detectorClass == null) {
throw new IllegalArgumentException("Method parameter detectorClass must not be null.");
}
for (LayoutBugDetector detector : _detectors) {
if (detectorClass.isAssignableFrom(detector.getClass())) {
// noinspection unchecked
return (D) detector;
}
}
throw new IllegalArgumentException("There is no detector of class " + detectorClass.getName());
}
/**
* Runs all registered {@link LayoutBugDetector}s. Before you call this method, you might:
* - register new detectors via {@link #enable},
* - remove unwanted detectors via {@link #disable},
* - configure a registered detector via {@link #configure},
* - configure the {@link TextDetector} to be used via {@link #setTextDetector},
* - configure the {@link EdgeDetector} to be used via {@link #setEdgeDetector}.
*
*/
public Collection findLayoutBugsIn(@Nonnull WebPage webPage) {
if (webPage == null) {
throw new IllegalArgumentException("Method parameter webPage must not be null.");
}
if (_debugMode) {
setLogLevelToDebug();
registerDebugListener();
}
try {
TextDetector textDetector = (_textDetector == null ? new AnimationAwareTextDetector() : _textDetector);
EdgeDetector edgeDetector = (_edgeDetector == null ? new SimpleEdgeDetector() : _edgeDetector);
try {
LOG.debug("Analyzing " + webPage.getUrl() + " ...");
webPage.setTextDetector(textDetector);
webPage.setEdgeDetector(edgeDetector);
final Collection result = new ArrayList();
for (LayoutBugDetector detector : _detectors) {
detector.setScreenshotDir(screenshotDir);
LOG.debug("Running " + detector.getClass().getSimpleName() + " ...");
result.addAll(detector.findLayoutBugsIn(webPage));
}
if (!result.isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("Detected layout bug(s) on ").append(webPage.getUrl()).append("\n");
if (_debugMode) {
sb.append("If there is a false positive, please send an email to [email protected] with the following information:\n");
sb.append(" - your test code,\n");
sb.append(" - all logged output, and\n");
sb.append(" - all screenshot files located in: ").append(screenshotDir);
sb.append(" (You might want to pack those into an zip archive)\n");
sb.append("TextDetector: ").append(textDetector.getClass().getName()).append("\n");
sb.append("EdgeDetector: ").append(edgeDetector.getClass().getName()).append("\n");
sb.append(DebugHelper.getDiagnosticInfo(webPage.getDriver()));
} else {
sb.append("If you call FightingLayoutBugs.enableDebugMode() before you call FightingLayoutBugs.findLayoutBugsIn(...) you can get more information.");
}
LOG.info(sb.toString());
}
return result;
} catch (RuntimeException e) {
String url = null;
try {
url = webPage.getUrl().toString();
} catch (Exception ignored) {}
StringBuilder sb = new StringBuilder();
sb.append("Failed to analyze ").append(url == null ? "given WebPage" : url).append(" -- ").append(e.toString()).append("\n");
if (_debugMode) {
sb.append("If you want support (or want to support FLB) you can send an email to [email protected] with the following information:\n");
sb.append(" - your test code,\n");
sb.append(" - all logged output, and\n");
sb.append(" - all screenshot files located in: ").append(screenshotDir);
sb.append(" (You might want to pack those into an zip archive)\n");
sb.append("TextDetector: ").append(textDetector.getClass().getName()).append("\n");
sb.append("EdgeDetector: ").append(edgeDetector.getClass().getName()).append("\n");
sb.append(DebugHelper.getDiagnosticInfo(webPage.getDriver()));
} else {
sb.append("If you call FightingLayoutBugs.enableDebugMode() before you call FightingLayoutBugs.findLayoutBugsIn(...) you can get more information.");
}
String errorMessage = sb.toString();
LOG.error(errorMessage);
throw new RuntimeException(errorMessage, e);
}
} finally {
for (Runnable runnable : _runAfterAnalysis) {
try {
runnable.run();
} catch (RuntimeException e) {
LOG.warn(runnable + " failed.", e);
}
}
}
}
private void setLogLevelToDebug() {
String name = FightingLayoutBugs.class.getPackage().getName();
final Log log = LogFactory.getLog(name);
if (log instanceof Jdk14Logger || (log instanceof AvalonLogger && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.Jdk14Logger)) {
final Logger logger = Logger.getLogger(name);
final Level originalLevel = logger.getLevel();
logger.setLevel(Level.FINE);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
logger.setLevel(originalLevel);
}});
enableDebugOutputToConsole(logger);
} else if (log instanceof Log4JLogger || (log instanceof AvalonLogger && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.Log4JLogger)) {
final org.apache.log4j.Logger logger = LogManager.getLogger(name);
final org.apache.log4j.Level originalLevel = logger.getLevel();
logger.setLevel(org.apache.log4j.Level.DEBUG);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
logger.setLevel(originalLevel);
}});
} else if (log instanceof LogKitLogger || (log instanceof AvalonLogger && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.LogKitLogger)) {
final org.apache.log.Logger logger = LogKit.getLoggerFor(name);
final Priority originalLevel = logger.getPriority();
logger.setPriority(Priority.DEBUG);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
logger.setPriority(originalLevel);
}});
} else if (log instanceof SimpleLog) {
final SimpleLog simpleLog = (SimpleLog) log;
final int originalLevel = simpleLog.getLevel();
simpleLog.setLevel(SimpleLog.LOG_LEVEL_DEBUG);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
simpleLog.setLevel(originalLevel);
}});
}
}
private void enableDebugOutputToConsole(Logger logger) {
do {
for (final Handler handler : logger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
final Level originalConsoleLogLevel = handler.getLevel();
handler.setLevel(Level.FINE);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
handler.setLevel(originalConsoleLogLevel);
}});
}
}
} while (logger.getUseParentHandlers() && (logger = logger.getParent()) != null);
}
private void registerDebugListener() {
final AtomicInteger i = new AtomicInteger(0);
final NumberFormat nf = new DecimalFormat("00");
final File screenshotDir = this.screenshotDir;
final Visualization.Listener debugListener = new Visualization.Listener() {
@Override
public void algorithmStepFinished(String algorithm, String stepDescription, int[][] tempResult) {
File pngFile = new File(screenshotDir, nf.format(i.incrementAndGet()) + "_" + algorithm + ".png");
ImageHelper.pixelsToPngFile(tempResult, pngFile);
LOG.debug(pngFile.getName() + " -- " + stepDescription);
}
@Override
public void algorithmFinished(String algorithm, String stepDescription, int[][] result) {
File pngFile = new File(screenshotDir, nf.format(i.incrementAndGet()) + "_" + algorithm + ".png");
ImageHelper.pixelsToPngFile(result, pngFile);
LOG.debug(pngFile.getName() + " -- " + stepDescription);
}
};
Visualization.registerListener(debugListener);
_runAfterAnalysis.add(new Runnable() { @Override public void run() {
Visualization.unregisterListener(debugListener);
}});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy