package com.smash.revolance.ui.explorer; /* * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Revolance-UI-Explorer * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Copyright (C) 2012 - 2013 RevoLance * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ import com.smash.revolance.ui.model.application.Application; import com.smash.revolance.ui.model.element.api.Element; import com.smash.revolance.ui.model.element.api.ElementBean; import com.smash.revolance.ui.model.helper.*; import com.smash.revolance.ui.model.page.api.Page; import com.smash.revolance.ui.model.page.api.PageBean; import com.smash.revolance.ui.model.sitemap.SiteMap; import com.smash.revolance.ui.model.user.User; import org.apache.log4j.*; import org.openqa.selenium.Dimension; import org.openqa.selenium.WebDriver; import java.io.File; import java.io.IOException; import java.util.*; /** * User: wsmash * Date: 02/06/13 * Time: 12:35 */ public class UserExplorer extends Thread { private Logger log = Logger.getLogger(UserExplorer.class); private int timeoutInSec; private User user; private File reportFile; public UserExplorer(User user, WriterAppender log, File reportFile, int timeoutInSec) { this.user = user; this.log.addAppender(log); this.user.setLogger(this.log); this.reportFile = reportFile; this.timeoutInSec = timeoutInSec; } public UserExplorer(User user, File reportFolder, int timeoutInSec) throws IOException { this(user, new FileAppender(new SimpleLayout(), File.createTempFile("exploration-" + UUID.randomUUID().toString(), ".log").getAbsolutePath()), reportFolder, timeoutInSec); } public File doContentReport() { try { JsonHelper.getInstance().map( getReportFile(), getSiteMap() ); getLogger().log(Level.INFO, "Report has been generated with id: " + getReportFile().getName()); } catch (IOException e) { e.printStackTrace(); getLogger().log(Level.ERROR, "Unable to generate report!"); } return getReportFile(); } public File getReportFile() { return reportFile; } public Logger getLogger() { return log; } public void run() { try { this._explore(); } catch (Exception e) { getLogger().log(Level.ERROR, "Exploration aborted. Reason: " + e.getMessage()); getLogger().log(Level.ERROR, e ); } finally { user.setExplorationDone(true); try { user.stopBot(); doContentReport(); } catch (Exception e) { getLogger().log(Level.ERROR, e); } } } public void explore() { start(); if(timeoutInSec == 0) { return; // We don't want to monitor the duration of the exploration } else { long mark = System.currentTimeMillis(); while( (System.currentTimeMillis()-mark) < timeoutInSec*1000 ) { try { sleep(10000); //10s of sleep } catch (InterruptedException e) { } if(user.isExplorationDone()) { return; } } user.setExplorationDone(true); } } private User _explore() throws Exception { long start = System.currentTimeMillis(); Page home = new Page( user, user.getHome() ); user.goTo( home ).awaitLoaded(); new PageParser(home).parse(); // Just for the home page if ( !home.hasBeenExplored() ) { if ( !home.isExternal() && !home.isBroken() ) { // This call will recursively: // parse & handleUserAccout logic & explore // of all the pages accessible from home explore( home ); home.setExplored( true ); } else if ( home.isBroken() ) { home.getSource().setBroken( true ); } } user.setExplorationDone( true ); user.stopBot(); long duration = System.currentTimeMillis() - start; user.getLogger().log(Level.INFO, "Exploration [Done] [Duration " + duration / 60000 + "mn]"); return user; } private void _explore(Page page) throws Exception { doRecursiveExploration(page); doContentReport(); } private void doRecursiveExploration(Page page) throws Exception { String title = page.getTitle(); user.getLogger().log(Level.INFO, "Exploring page: " + title); if ( page.isOriginal() ) { exploreOriginal( page ); user.getLogger().log(Level.INFO, "Exploring page: " + title + " [Done]"); } else { exploreVariant( page ); user.getLogger().log(Level.INFO, "Exploring variant [Done]"); } } private void exploreVariant(Page page) throws Exception { if ( !page.getContent().isEmpty() ) { user.getLogger().log(Level.INFO, "New content has been found in this variant. Exploration will begin..."); _explore( page ); user.getLogger().log(Level.INFO, "New content has been found in this variant. Exploration [Done]"); } else { user.getLogger().log(Level.INFO, "No variations have been found. Deleting variant and associated content..."); page.delete(); // Remove itself user.getLogger().log(Level.INFO, "No variations have been found. Deletion [Done]"); } } public void explore(Page page) throws Exception { new PageParser(page).parse(); handleUserAccountLogic( page ); if ( !page.hasBeenExplored() && !page.isBroken() && !page.isExternal() ) { List<Element> clickables = page.getClickableContent(); Collections.sort( clickables ); for ( Element clickable : clickables ) { if ( !clickable.hasBeenClicked() && clickable.isClickable() && !clickable.isDisabled() && BotHelper.rightDomain(user, clickable.getHref()) ) { // Checking if some content is left to be explored on the target page (if already being explored) if ( !getSiteMap().hasBeenExplored( UrlHelper.removeHash(clickable.getHref()) ) && !UrlHelper.areEquivalent( UrlHelper.removeHash( clickable.getHref() ), UrlHelper.removeHash( page.getUrl() ) ) && !getSourceContent( page ).contentEquals( clickable.getContent() ) ) { String oldWindowHandle = getBrowser().getWindowHandle(); int windowCount = getBrowser().getWindowHandles().size(); if ( clickable.click() ) { if ( getBrowser().getWindowHandles().size() > windowCount ) { exploreNewWindowChanges( page, clickable, oldWindowHandle ); } else { exploreChanges( page, clickable ); } } } else { // Either the target page has already been explored or we're already exploring the page clickable.setClicked( true ); } } } } } private String getSourceContent(Page page) { Element source = page.getSource(); if ( source == null ) { return ""; } else { return source.getContent(); } } private boolean isUnexploredContentLeadingToThisPage(Page page, Element newClickElement) throws Exception { ElementBean target = getSiteMap().getFirstUnexploredElement( newClickElement.getUrl() ); if ( target == null ) { return false; } else { String currentUrl = UrlHelper.removeHash( page.getUrl() ); String targetUrl = UrlHelper.removeHash( target.getPage().getUrl() ); return UrlHelper.areEquivalent( currentUrl, targetUrl ); } } private void exploreNewWindowChanges(Page prevPage, Element clickable, String oldWindowHandle) throws Exception { WebDriver oldBot = swithToNewWindow( oldWindowHandle ); exploreChanges( prevPage, clickable ); // Closing the new window after exploration has completed WebDriver oldOldBot = user.setBrowser( oldBot ); oldOldBot.close(); getBrowser().switchTo().window( oldWindowHandle ); } private WebDriver swithToNewWindow(String oldWindowHandle) throws Exception { WebDriver oldBot; Set<String> handles = user.getBrowser().getWindowHandles(); String newHandle = ""; for ( String handle : handles ) { if ( !handle.contentEquals( oldWindowHandle ) ) { newHandle = handle; break; } } oldBot = user.setBrowser( getBrowser().switchTo().window( newHandle ) ); UserHelper.handleAlert( user ); // Adapting the size of the new window Dimension size = new Dimension( user.getBrowserWidth(), user.getBrowserHeight() ); getBrowser().manage().window().setSize( size ); return oldBot; } private void exploreChanges(Page page, Element clickable) throws Exception { String currentUrl = getBrowser().getCurrentUrl(); if ( !UrlHelper.areEquivalent( currentUrl, page.getUrl() ) ) { handleUrlChange( page, clickable, currentUrl ); } /* else // url are equivalent (without hash) so we've to check if this is a dynamic change in the content { if ( user.isPageScreenshotEnabled() ) { handleDynamicChange( page, clickable ); } else { clickable.setDisabled( true ); // nothing seems to have changed } } */ } private void handleDynamicChange(Page page, Element source) throws Exception { String id = UUID.randomUUID().toString(); String caption = BotHelper.pageContentHasChanged( user.getBot(), page.getCaption() ); if ( !caption.contentEquals( page.getCaption() ) ) { // Create new page and inject data to speed up the computations // Checking if there is already a variant with that checksum Page variant = page.findVariantByCaption( caption ); // otherwise we've to create one from scratch if ( variant == null ) { variant = page.findVariantBySource( source.getBean() ); } if ( variant == null ) { variant = _buildVariant( page, source, false ); variant.setId( id ); variant.setImage( ImageHelper.decodeToImage( caption ) ); page.addVariant( variant ); // Parse the new content. new PageParser( variant ).parse(); removeBaseContent( variant ); if ( user.wantsToExploreVariants() ) { explore( variant ); } } source.setDisabled( false ); } } private void removeBaseContent(Page page) throws Exception { List<Element> content = page.getContent(); List<Element> contentToRemove = new ArrayList<Element>(); for ( Element element : content ) { if ( page.getOriginal().getBean().contains( element.getBean() ) ) { contentToRemove.add( element ); } } content.removeAll( contentToRemove ); page.setContent( content ); } private void handleUrlChange(Page prevPage, Element clickable, String currentUrl) throws Exception { if ( UrlHelper.containsHash( currentUrl ) ) { String hash = UrlHelper.getHash( currentUrl ); Page variant = getVariant( prevPage, clickable, hash, true ).getInstance(); variant.setScrollX( String.valueOf( (Long) user.getBot().runJS( "return window.scrollX" ) ) ); variant.setScrollY( String.valueOf( (Long) user.getBot().runJS( "return window.scrollY" ) ) ); prevPage.addVariant( variant ); } else if(BotHelper.rightDomain( prevPage.getUser(), currentUrl))// Real change of page { Page page = getSiteMap().getPage( currentUrl, true ).getInstance(); if ( !page.hasBeenExplored() && !page.isExternal()) { page.setSource( clickable ); explore( page ); } } } private void exploreOriginal(Page page) throws Exception { if ( !page.isBroken() ) { user.getLogger().log(Level.TRACE, "Exploring the original version of the page: " + page.getTitle() ); explore( page ); } } private void handleUserAccountLogic(Page page) throws Exception { Application application = getApplication(); if ( application != null && application.isLoginPage(page) ) { getApplication().login(user, page); } } /** * Retrieve a variant given the source element of the page. Do not generate it if not found. * * @param source * @return * @throws Exception */ public Page getVariant(Page prevPage, ElementBean source) throws Exception { return getVariant( prevPage, source, false ); } /** * Retrieve a variant given the source element of the page * Build the variant if not existing. * * @param source * @return * @throws Exception */ public Page getVariant(Page prevPage, ElementBean source, boolean generate) throws Exception { Page page = prevPage.findVariantBySource( source ); if ( page != null ) { return page; } else { if ( generate ) { return _buildVariant( prevPage, source.getInstance(), user.isPageScreenshotEnabled() ); } else { return null; } } } public PageBean getVariant(Page prevPage, Element source, String hash, boolean generate) throws Exception { PageBean page = prevPage.findVariantByHash( hash ); if ( page == null ) { if ( generate ) { page = buildHashVariant( prevPage, source, hash ); } } return page; } private Page _buildVariant(Page page, Element source, boolean takeScreenshot) throws Exception { Page variant = new Page( user, page.getUrl() ); variant.setOriginal( page.getOriginal() ); variant.setSource( source ); if ( takeScreenshot ) { new PageParser( variant ).takeScreenShot(); } return variant; } private PageBean buildHashVariant(Page prevPage, Element source, String hash) throws Exception { Page variant = _buildVariant( prevPage, source, user.isPageScreenshotEnabled() ); variant.setUrl( variant.getUrl() + "#" + hash ); return variant.getBean(); } private SiteMap getSiteMap() { return user.getSiteMap(); } private Application getApplication() { return user.getApplication(); } private WebDriver getBrowser() throws Exception { return user.getBrowser(); } }