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

com.applitools.eyes.selenium.capture.DomCapture Maven / Gradle / Ivy

package com.applitools.eyes.selenium.capture;

import com.applitools.connectivity.ServerConnector;
import com.applitools.eyes.*;
import com.applitools.eyes.positioning.PositionMemento;
import com.applitools.eyes.positioning.PositionProvider;
import com.applitools.eyes.selenium.SeleniumEyes;
import com.applitools.eyes.selenium.frames.FrameChain;
import com.applitools.eyes.selenium.wrappers.EyesTargetLocator;
import com.applitools.eyes.selenium.wrappers.EyesSeleniumDriver;
import com.applitools.utils.EfficientStringReplace;
import com.applitools.utils.GeneralUtils;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.css.ECSSVersion;
import com.helger.css.decl.CSSImportRule;
import com.helger.css.decl.CSSStyleRule;
import com.helger.css.decl.CascadingStyleSheet;
import com.helger.css.decl.ICSSTopLevelRule;
import com.helger.css.reader.CSSReader;
import com.helger.css.writer.CSSWriterSettings;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.*;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

public class DomCapture {
    private static String CAPTURE_FRAME_SCRIPT;
    private static String CAPTURE_FRAME_SCRIPT_FOR_IE;

    private final Phaser cssPhaser = new Phaser(); // Phaser for syncing all callbacks on a single Frame

    static {
        try {
            CAPTURE_FRAME_SCRIPT = GeneralUtils.readToEnd(DomCapture.class.getResourceAsStream("/captureDomAndPoll.js"));
            CAPTURE_FRAME_SCRIPT += "return __captureDomAndPoll();";
            CAPTURE_FRAME_SCRIPT_FOR_IE = GeneralUtils.readToEnd(DomCapture.class.getResourceAsStream("/captureDomAndPollForIE.js"));
            CAPTURE_FRAME_SCRIPT_FOR_IE += "return __captureDomAndPollForIE();";
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static long DOM_EXTRACTION_TIMEOUT = 5 * 60 * 1000;

    private static ServerConnector mServerConnector = null;
    private EyesSeleniumDriver driver;
    private final Logger logger;
    private String cssStartToken;
    private String cssEndToken;
    private Map cssData = Collections.synchronizedMap(new HashMap());
    private boolean shouldWaitForPhaser = false;
    private AtomicBoolean isCheckTimerTimedOut = new AtomicBoolean(false);
    private Timer timer;
    private final UserAgent userAgent;

    public DomCapture(SeleniumEyes eyes) {
        mServerConnector = eyes.getServerConnector();
        logger = eyes.getLogger();
        driver = (EyesSeleniumDriver) eyes.getDriver();
        userAgent = eyes.getUserAgent();
    }


    public String getFullWindowDom() {
        long currentTimeMillis = System.currentTimeMillis();
        String domJson = getDom();
        String timeDiff = String.valueOf(System.currentTimeMillis() - currentTimeMillis);
        logger.verbose("getting th DOM took " + timeDiff + " ms");
        return domJson;
    }

    public String getFullWindowDom(PositionProvider positionProvider) {
        PositionMemento originalPosition = positionProvider.getState();
        positionProvider.setPosition(Location.ZERO);
        String domJson = getDom();
        positionProvider.restoreState(originalPosition);
        return domJson;
    }

    private String getDom() {
        FrameChain originalFC = driver.getFrameChain().clone();

        String dom = getFrameDom();

        if (originalFC != null) {
            ((EyesTargetLocator) driver.switchTo()).frames(originalFC);
        }

        try {

            if (shouldWaitForPhaser) {
                cssPhaser.awaitAdvanceInterruptibly(0, 30, TimeUnit.SECONDS);
            }

        } catch (InterruptedException | TimeoutException e) {
            GeneralUtils.logExceptionStackTrace(logger, e);
        }
        String inlaidString = EfficientStringReplace.efficientStringReplace(cssStartToken, cssEndToken, dom, cssData);
        return inlaidString;
    }

    private String getFrameDom() {
        logger.verbose("Trying to get DOM from driver");
        timer = new Timer(true);
        timer.schedule(new TimeoutTask(), DOM_EXTRACTION_TIMEOUT);
        try {
            isCheckTimerTimedOut.set(false);
            String resultAsString;
            ScriptResponse.Status status = null;
            ScriptResponse scriptResponse = null;
            do {
                if (userAgent.isInternetExplorer()) {
                    resultAsString = (String) this.driver.executeScript(CAPTURE_FRAME_SCRIPT_FOR_IE);
                } else {
                    resultAsString = (String) this.driver.executeScript(CAPTURE_FRAME_SCRIPT);
                }

                try {
                    scriptResponse = GeneralUtils.parseJsonToObject(resultAsString, ScriptResponse.class);
                    status = scriptResponse.getStatus();
                } catch (IOException e) {
                    GeneralUtils.logExceptionStackTrace(logger, e);
                }
                Thread.sleep(200);

            } while (status == ScriptResponse.Status.WIP && !isCheckTimerTimedOut.get());
            timer.cancel();

            if (status == ScriptResponse.Status.ERROR) {
                throw new EyesException("DomCapture Error: " + scriptResponse.getError());
            }

            if (isCheckTimerTimedOut.get()) {
                throw new EyesException("DomCapture Timed out");
            }
            String executeScripString = scriptResponse.getValue();

            List missingCssList = new ArrayList<>();
            List missingFramesList = new ArrayList<>();
            List data = new ArrayList<>();

            Separators separators = parseScriptResult(executeScripString, missingCssList, missingFramesList, data);
            cssStartToken = separators.cssStartToken;
            cssEndToken = separators.cssEndToken;

            fetchCssFiles(missingCssList);

            Map framesData = null;
            try {
                framesData = recurseFrames(missingFramesList);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //noinspection UnnecessaryLocalVariable
            String inlaidString = EfficientStringReplace.efficientStringReplace(
                    separators.iframeStartToken, separators.iframeEndToken, data.get(0), framesData);
            return inlaidString;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            timer.cancel();
        }
        return "";
    }

    private Map recurseFrames(List missingFramesList) {
        Map framesData = new HashMap<>();
        EyesTargetLocator switchTo = (EyesTargetLocator) driver.switchTo();

        FrameChain fc = driver.getFrameChain().clone();
        for (String missingFrameLine : missingFramesList) {
            logger.verbose("Switching to frame line :" + missingFrameLine);
            String originLocation = (String) driver.executeScript("return document.location.href");
            try {
                String[] missingFrameXpaths = missingFrameLine.split(",");
                for (String missingFrameXpath : missingFrameXpaths) {
                    logger.verbose("switching to specific frame : " + missingFrameXpath);
                    WebElement frame = driver.findElement(By.xpath(missingFrameXpath));
                    logger.verbose("Switched to frame(" + missingFrameXpath + ") with src(" + frame.getAttribute("src") + ")");
                    switchTo.frame(frame);
                }
                String locationAfterSwitch = (String) driver.executeScript("return document.location.href");
                if (locationAfterSwitch.equals(originLocation)) {
                    logger.verbose("Switching to frame failed");
                    framesData.put(missingFrameLine, "");
                    continue;
                }
                String result = getFrameDom();
                framesData.put(missingFrameLine, result);
            } catch (Exception e) {
                GeneralUtils.logExceptionStackTrace(logger, e);
                framesData.put(missingFrameLine, "");
            }
            switchTo.frames(fc);
        }

        return framesData;
    }

    private void fetchCssFiles(List missingCssList) {
        for (final String missingCssUrl : missingCssList) {
            if (missingCssUrl.startsWith("blob:") || missingCssUrl.startsWith("data:")) {
                logger.log("Found blob url continuing - " + missingCssUrl);
                continue;
            }
            if (missingCssUrl.isEmpty()) continue;
            try {
                logger.verbose("DomCapture.fetchCssFiles() downloading");
                final CssTreeNode cssTreeNode = new CssTreeNode(new URL(missingCssUrl));
                downloadCss(cssTreeNode, new TaskListener() {
                    @Override
                    public void onComplete(String taskResponse) {
                        try {
                            logger.verbose("DomCapture.onDownloadComplete  - finished");
                            parseCSS(cssTreeNode, taskResponse);
                            if (cssTreeNode.allImportRules != null && !cssTreeNode.allImportRules.isEmpty()) {
                                cssTreeNode.downloadNodeCss();
                            }
                            cssData.put(missingCssUrl, EfficientStringReplace.CleanForJSON(cssTreeNode.calcCss()));
                        } catch (Throwable e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onFail() {
                        logger.verbose("DomCapture.onDownloadFailed");
                        cssData.put(missingCssUrl, "");
                    }
                });

            } catch (Throwable e) {
                GeneralUtils.logExceptionStackTrace(logger, e);
            }
        }
    }

    private Separators parseScriptResult(String scriptResult, List missingCssList, List missingFramesList, List data) {
        String[] lines = scriptResult.split("\\r?\\n");
        Separators separators = null;
        try {
            separators = GeneralUtils.parseJsonToObject(lines[0], Separators.class);

            ArrayList> blocks = new ArrayList>();
            blocks.add(missingCssList);
            blocks.add(missingFramesList);
            blocks.add(data);
            int blockIndex = 0;
            int lineIndex = 1;
            do {
                String str = lines[lineIndex++];
                if (separators.separator.equals(str)) {
                    blockIndex++;
                } else {
                    blocks.get(blockIndex).add(str);
                }
            } while (lineIndex < lines.length);
            logger.verbose("missing css count: " + missingCssList.size());
            logger.verbose("missing frames count: " + missingFramesList.size());
        } catch (IOException e) {
            e.printStackTrace();
        }
        shouldWaitForPhaser |= !missingCssList.isEmpty();
        return separators;
    }

    class CssTreeNode {
        URL url;
        String css = "";
        StringBuilder sb = new StringBuilder();
        List decedents = new ArrayList<>();
        ICommonsList allImportRules;
        ICommonsList styleRules;


        public CssTreeNode(URL url) {
            this.url = url;
        }

        String calcCss() {
            sb.append(css);
            if (decedents != null) {
                for (CssTreeNode decedent : decedents) {
                    sb.append(decedent.calcCss());
                }
            }

            if (styleRules != null) {
                for (CSSStyleRule styleRule : styleRules) {
                    sb.append(styleRule.getAsCSSString(new CSSWriterSettings()));
                }
            }

            return sb.toString();
        }

        public void setCss(String css) {
            this.css = css;
        }

        void downloadNodeCss() {
            if (allImportRules != null) {
                for (CSSImportRule importRule : allImportRules) {
                    final CssTreeNode cssTreeNode;
                    try {
                        cssTreeNode = new CssTreeNode(new URL(importRule.getLocation().getURI()));
                        String uri = importRule.getLocation().getURI();
                        cssTreeNode.setUrl(uri);
                        downloadCss(cssTreeNode, new TaskListener() {
                            @Override
                            public void onComplete(String taskResponse) {
                                parseCSS(cssTreeNode, EfficientStringReplace.CleanForJSON(taskResponse));
                                if (!cssTreeNode.allImportRules.isEmpty()) {
                                    cssTreeNode.downloadNodeCss();

                                }
                            }

                            @Override
                            public void onFail() {
                                logger.verbose("Download Failed");
                            }
                        });
                        decedents.add(cssTreeNode);
                    } catch (Throwable e) {
                        GeneralUtils.logExceptionStackTrace(logger, e);
                    }

                }
            }
        }

        public void setUrl(String url) {

            boolean absolute = false;
            try {
                url = URLEncoder.encode(url, "UTF-8");
                absolute = new URI(url).isAbsolute();
                this.url = absolute ? new URL(url) : new URL(url);
            } catch (UnsupportedEncodingException | URISyntaxException | MalformedURLException e) {
                GeneralUtils.logExceptionStackTrace(logger, e);
            }
        }


        public void setAllImportRules(ICommonsList allImportRules) {
            this.allImportRules = allImportRules;
        }

        public void setAllStyleRules(ICommonsList allStyleRules) {
            this.styleRules = allStyleRules;
        }


    }

    private void downloadCss(final CssTreeNode node, final TaskListener listener) {
        cssPhaser.register();
        logger.verbose("Given URL to download: " + node.url);
        mServerConnector.downloadString(node.url, new TaskListener() {
            @Override
            public void onComplete(String taskResponse) {
                try {
                    logger.verbose("Download Complete");
                    node.setCss(taskResponse);
                    listener.onComplete(taskResponse);

                } catch (Throwable e) {
                    GeneralUtils.logExceptionStackTrace(logger, e);
                } finally {
                    cssPhaser.arriveAndDeregister();
                    logger.verbose("cssPhaser.arriveAndDeregister(); " + node.url);
                    logger.verbose("current missing - " + cssPhaser.getUnarrivedParties());
                }
            }

            @Override
            public void onFail() {
                listener.onComplete("");
                cssPhaser.arriveAndDeregister();
                logger.verbose("Download Failed");
                logger.verbose("cssPhaser.arriveAndDeregister(); " + node.url);
                logger.verbose("current missing  - " + cssPhaser.getUnarrivedParties());
            }
        });
    }

    private void parseCSS(CssTreeNode node, String css) {
        if (css == null) {
            return;
        }

        final CascadingStyleSheet cascadingStyleSheet = CSSReader.readFromString(css, ECSSVersion.CSS30);
        if (cascadingStyleSheet == null) {
            return;
        }

        ICommonsList allRules = cascadingStyleSheet.getAllRules();
        if (allRules.isEmpty()) {
            return;
        }

        ICommonsList allImportRules = cascadingStyleSheet.getAllImportRules();
        node.setAllImportRules(allImportRules);
        node.setAllStyleRules(cascadingStyleSheet.getAllStyleRules());
    }

    private class TimeoutTask extends TimerTask {
        @Override
        public void run() {
            logger.verbose("Check Timer timeout.");
            isCheckTimerTimedOut.set(true);
        }
    }

}







© 2015 - 2025 Weber Informatics LLC | Privacy Policy