com.htmlhifive.pitalium.core.rules.AssertionView.java Source code

Java tutorial

Introduction

Here is the source code for com.htmlhifive.pitalium.core.rules.AssertionView.java

Source

/*
 * Copyright (C) 2015-2016 NS Solutions Corporation
 *
 * 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.htmlhifive.pitalium.core.rules;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.htmlhifive.pitalium.common.exception.TestRuntimeException;
import com.htmlhifive.pitalium.common.util.JSONUtils;
import com.htmlhifive.pitalium.core.config.ExecMode;
import com.htmlhifive.pitalium.core.config.PtlTestConfig;
import com.htmlhifive.pitalium.core.io.PersistMetadata;
import com.htmlhifive.pitalium.core.io.Persister;
import com.htmlhifive.pitalium.core.model.CompareTarget;
import com.htmlhifive.pitalium.core.model.DomSelector;
import com.htmlhifive.pitalium.core.model.ExecResult;
import com.htmlhifive.pitalium.core.model.IndexDomSelector;
import com.htmlhifive.pitalium.core.model.ScreenArea;
import com.htmlhifive.pitalium.core.model.ScreenAreaResult;
import com.htmlhifive.pitalium.core.model.ScreenshotArgument;
import com.htmlhifive.pitalium.core.model.ScreenshotResult;
import com.htmlhifive.pitalium.core.model.SelectorType;
import com.htmlhifive.pitalium.core.model.TargetResult;
import com.htmlhifive.pitalium.core.result.TestResultManager;
import com.htmlhifive.pitalium.core.selenium.PtlCapabilities;
import com.htmlhifive.pitalium.core.selenium.PtlWebDriver;
import com.htmlhifive.pitalium.core.selenium.PtlWebDriverFactory;
import com.htmlhifive.pitalium.core.selenium.PtlWebDriverManager;
import com.htmlhifive.pitalium.image.model.DiffPoints;
import com.htmlhifive.pitalium.image.model.RectangleArea;
import com.htmlhifive.pitalium.image.model.ScreenshotImage;
import com.htmlhifive.pitalium.image.util.ImageUtils;

/**
 * ?assert????<br/>
 * ???????&#064;Rule??<br/>
 * <ul>
 * <li>??ID?</li>
 * <li>WebDriver?quit</li>
 * </ul>
 * {@link com.htmlhifive.pitalium.core.PtlTestBase}????????????????
 */
public class AssertionView extends TestWatcher {

    private static final Logger LOG = LoggerFactory.getLogger(AssertionView.class);

    private static final Function<ScreenAreaResult, Rectangle> SCREEN_AREA_RESULT_TO_RECTANGLE_FUNCTION = new Function<ScreenAreaResult, Rectangle>() {
        @Override
        public Rectangle apply(ScreenAreaResult input) {
            return input.getRectangle().toRectangle();
        }
    };

    /**
     * {@link PtlWebDriver}?<br>
     * WebDriver????????quit???
     */
    protected PtlWebDriverManager.WebDriverContainer webDriverContainer;
    /**
     * ??WebDriver
     */
    protected PtlWebDriver driver;

    private final Set<String> screenshotIds = new HashSet<String>();

    private Description description;
    private String className;
    private String methodName;
    private String currentId;

    private PtlCapabilities capabilities;

    private final List<ScreenshotResult> results = new ArrayList<ScreenshotResult>();
    private final List<AssertionError> verifyErrors = new ArrayList<AssertionError>();

    //<editor-fold desc="Watcher methods">

    @Override
    protected void starting(Description desc) {
        LOG.info("[Testcase start] (name: {})", desc.getDisplayName());

        description = desc;
        className = getClassName(desc);
        methodName = getMethodName(desc);

        currentId = TestResultManager.getInstance().getCurrentId();
    }

    @Override
    protected void failed(Throwable e, Description desc) {
        if (e instanceof AssertionError) {
            LOG.info("[Testcase failed] (assertion error)", e);
        } else if (e instanceof WebDriverException) {
            LOG.error("[Testcase failed] (selenium error)", e);
        } else if (e instanceof TestRuntimeException) {
            LOG.error("[Testcase failed] (pitalium runtime error)", e);
        } else {
            LOG.error("[Testcase failed] (unhandled error)", e);
        }

        TestResultManager.getInstance().cancelUpdateExpectedId(className);
    }

    @Override
    protected void succeeded(Description desc) {
        // ????ExpectedId
        if (verifyErrors.isEmpty()) {
            LOG.info("[Testcase succeeded]");
            TestResultManager.getInstance().updateExpectedId(className, methodName);
            return;
        }

        LOG.info("[Testcase failed] (verified {} errors)", verifyErrors.size());
        TestResultManager.getInstance().cancelUpdateExpectedId(className);
        String errors = StringUtils
                .join(FluentIterable.from(verifyErrors).transform(new Function<AssertionError, String>() {
                    @Override
                    public String apply(AssertionError error) {
                        return error.getMessage();
                    }
                }).filter(new Predicate<String>() {
                    @Override
                    public boolean apply(String message) {
                        return !Strings.isNullOrEmpty(message);
                    }
                }), "\n");
        throw new AssertionError(String.format(Locale.US, "Verified %d errors: %s", verifyErrors.size(), errors));
    }

    @Override
    protected void finished(Description desc) {
        LOG.info("[Testcase finished] (name: {})", desc.getDisplayName());

        if (webDriverContainer != null) {
            webDriverContainer.quit();
            driver = null;
        }

        if (results.isEmpty()) {
            return;
        }

        synchronized (AssertionView.class) {
            TestResultManager resultManager = TestResultManager.getInstance();
            for (ScreenshotResult result : results) {
                resultManager.addScreenshotResult(className, result);
            }
        }
    }

    //</editor-fold>

    /**
     * {@link org.openqa.selenium.Capabilities}???{@link PtlWebDriver}??????
     * 
     * @param cap 
     * @return ??WebDriver
     */
    public PtlWebDriver createDriver(PtlCapabilities cap) {
        if (driver != null) {
            if (!capabilities.equals(cap)) {
                throw new TestRuntimeException("Capabilities not match");
            }
            return driver;
        }

        capabilities = cap;
        webDriverContainer = PtlWebDriverManager.getInstance().getWebDriver(description.getTestClass(), cap,
                new Supplier<WebDriver>() {
                    @Override
                    public PtlWebDriver get() {
                        return PtlWebDriverFactory.getInstance(capabilities).getDriver();
                    }
                });

        driver = webDriverContainer.get();
        LOG.debug("[Get WebDriver] Use session ({})", driver);
        return driver;
    }

    //<editor-fold desc="assertView">

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param screenshotId ?ID
     */
    public void assertView(String screenshotId) {
        assertView(null, screenshotId);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param message {@link AssertionError}?
     * @param screenshotId ?ID
     */
    public void assertView(String message, String screenshotId) {
        assertView(message, screenshotId, asList(new CompareTarget(ScreenArea.of(SelectorType.TAG_NAME, "body"))),
                null);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param screenshotId ?ID
     * @param compareTargets ???
     */
    public void assertView(String screenshotId, CompareTarget[] compareTargets) {
        assertView(screenshotId, compareTargets, null);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param screenshotId ?ID
     * @param compareTargets ???
     */
    public void assertView(String screenshotId, List<CompareTarget> compareTargets) {
        assertView(null, screenshotId, compareTargets, null);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param message {@link AssertionError}?
     * @param screenshotId ?ID
     * @param compareTargets ???
     */
    public void assertView(String message, String screenshotId, List<CompareTarget> compareTargets) {
        assertView(message, screenshotId, compareTargets, null);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param screenshotId ?ID
     * @param compareTargets ???
     * @param hiddenElementsSelectors ????DOM??
     */
    public void assertView(String screenshotId, CompareTarget[] compareTargets,
            DomSelector[] hiddenElementsSelectors) {
        assertView(null, screenshotId, asList(compareTargets), asList(hiddenElementsSelectors));
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param screenshotId ?ID
     * @param compareTargets ???
     * @param hiddenElementsSelectors ????DOM??
     */
    public void assertView(String screenshotId, List<CompareTarget> compareTargets,
            List<DomSelector> hiddenElementsSelectors) {
        assertView(null, screenshotId, compareTargets, hiddenElementsSelectors);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param arg ?????
     */
    public void assertView(ScreenshotArgument arg) {
        assertView(null, arg);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param message {@link AssertionError}?
     * @param arg ?????
     */
    public void assertView(String message, ScreenshotArgument arg) {
        assertView(message, arg.getScreenshotId(), arg.getTargets(), arg.getHiddenElementSelectors());
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param message {@link AssertionError}?
     * @param screenshotId ?ID
     * @param compareTargets ???
     * @param hiddenElementsSelectors ????DOM??
     */
    public void assertView(String message, String screenshotId, List<CompareTarget> compareTargets,
            List<DomSelector> hiddenElementsSelectors) {
        if (driver == null) {
            throw new TestRuntimeException("Driver is not initialized");
        }

        if (Strings.isNullOrEmpty(screenshotId)) {
            LOG.error("ScreenshotId cannot be null or empty");
            throw new TestRuntimeException("ScreenshotId cannot be null or empty");
        }

        // Check screenshotId
        if (screenshotIds.contains(screenshotId)) {
            LOG.error("Duplicate screenshotId ({})", screenshotId);
            throw new TestRuntimeException("Duplicate screenshotId");
        }

        ExecMode execMode = PtlTestConfig.getInstance().getEnvironment().getExecMode();
        LOG.info("[AssertView start] (ssid: {}, Mode: {})", screenshotId, execMode);
        LOG.trace(
                "[AssertView start] message: {}, screenshotId: {}, compareTargets: {}, hiddenElementSelectors: {}",
                message, screenshotId, compareTargets, hiddenElementsSelectors);

        List<CompareTarget> targets;
        if (compareTargets == null || compareTargets.isEmpty()) {
            targets = new ArrayList<CompareTarget>();
            targets.add(new CompareTarget());
        } else {
            targets = compareTargets;
        }

        screenshotIds.add(screenshotId);

        // ?
        ScreenshotResult captureResult = takeCaptureAndPersistImage(screenshotId, targets, hiddenElementsSelectors);
        List<TargetResult> targetResults = captureResult.getTargetResults();
        saveTargetResults(screenshotId, targetResults);

        ValidateResult validateResult = validateTargetResults(targetResults, targets);

        // Expected mode
        if (!execMode.isRunTest()) {
            ScreenshotResult screenshotResult = getScreenshotResultForExpectedMode(screenshotId, targetResults,
                    validateResult);
            results.add(screenshotResult);

            if (!validateResult.isValid()) {
                LOG.info("[AssertView failed] (validation error, ssid: {})", screenshotId);
                throw new AssertionError("Invalid selector found");
            }

            LOG.info("[AssertView finished] (ssid: {})", screenshotId);
            return;
        }

        // RunTest mode
        LOG.info("[AssertView comparison start] (ssid: {})", screenshotId);

        String expectedId = TestResultManager.getInstance().getExpectedId(className, methodName);
        PersistMetadata expectedMetadata = new PersistMetadata(expectedId, className, methodName, screenshotId,
                capabilities);
        List<TargetResult> expectedTargetResults = TestResultManager.getInstance().getPersister()
                .loadTargetResults(expectedMetadata);

        ScreenshotResult screenshotResult = compareTargetResults(screenshotId, expectedId, targetResults,
                expectedTargetResults, validateResult);
        results.add(screenshotResult);

        if (!validateResult.isValid()) {
            LOG.info("[AssertView failed] (validation error, ssid: {})", screenshotId);
            throw new AssertionError("Invalid selector found");
        }
        if (!screenshotResult.getResult().isSuccess()) {
            LOG.info("[AssertView failed] (ssid: {})", screenshotId);
            throw Strings.isNullOrEmpty(message) ? new AssertionError() : new AssertionError(message);
        }

        LOG.info("[AssertView finished] (ssid: {})", screenshotId);
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????<br />
     * <br />
     * {@link #assertView(ScreenshotArgument)}?????{@code RUN_TEST}?????????????
     * 
     * @param arg ?????
     */
    public void verifyView(ScreenshotArgument arg) {
        try {
            LOG.info("[VerifyView start] (ssid: {})", arg.getScreenshotId());
            assertView(arg);
        } catch (AssertionError e) {
            LOG.info("[VerifyView failed] (ssid: {})", arg.getScreenshotId());
            verifyErrors.add(e);
        }
    }

    /**
     * ???????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}??
     * ?????????? ? {@link com.htmlhifive.pitalium.core.config.ExecMode#RUN_TEST}???
     * {@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????<br />
     * <br />
     * {@link #assertView(ScreenshotArgument)}?????{@code RUN_TEST}?????????????
     * 
     * @param message {@link AssertionError}?
     * @param arg ?????
     */
    public void verifyView(String message, ScreenshotArgument arg) {
        try {
            LOG.info("[VerifyView start] (ssid: {})", arg.getScreenshotId());
            assertView(message, arg);
        } catch (AssertionError e) {
            LOG.info("[VerifyView failed] (ssid: {})", arg.getScreenshotId());
            verifyErrors.add(e);
        }
    }

    /**
     * ??????
     * 
     * @param targetResults ?
     * @param compareTargets ??{@link CompareTarget}
     * @return ??
     */
    private ValidateResult validateTargetResults(List<TargetResult> targetResults,
            List<CompareTarget> compareTargets) {
        return new ValidateResult(validateTargetElementHasSize(targetResults),
                validateDomSelectorTargetExists(targetResults, compareTargets));
    }

    /**
     * CompareTarget???????????????????
     * 
     * @param targetResults ??{@link CompareTarget}
     * @return ??????????
     */
    private List<IndexDomSelector> validateTargetElementHasSize(List<TargetResult> targetResults) {
        List<IndexDomSelector> selectors = new ArrayList<IndexDomSelector>();
        for (TargetResult targetResult : targetResults) {
            RectangleArea area = targetResult.getTarget().getRectangle();
            IndexDomSelector selector = targetResult.getTarget().getSelector();
            if (selector != null && (area.getWidth() == 0d || area.getHeight() == 0d)) {
                selectors.add(selector);
                LOG.error("[Validation error] element has empty size. ({})", selector);
            }
        }

        return selectors;
    }

    /**
     * CompareTarget??????????????
     * 
     * @param targetResults ?
     * @param compareTargets ??{@link CompareTarget}
     * @return ????????
     */
    private List<DomSelector> validateDomSelectorTargetExists(List<TargetResult> targetResults,
            List<CompareTarget> compareTargets) {
        Set<DomSelector> selectors = new HashSet<DomSelector>(targetResults.size() * 2);
        for (TargetResult targetResult : targetResults) {
            DomSelector selector = targetResult.getTarget().getScreenArea().getSelector();
            if (selector != null) {
                selectors.add(selector);
            }
        }

        List<DomSelector> invalidSelectors = new ArrayList<DomSelector>();
        for (CompareTarget compareTarget : compareTargets) {
            DomSelector selector = compareTarget.getCompareArea().getSelector();
            if (selector == null) {
                continue;
            }

            if (!selectors.contains(selector)) {
                invalidSelectors.add(selector);
                LOG.error("[Validation error] no element found. ({})", selector);
            }
        }

        return invalidSelectors;
    }

    /**
     * 1??ID???????
     * 
     * @param screenshotId ID
     * @param targetResults ??
     */
    private void saveTargetResults(String screenshotId, List<TargetResult> targetResults) {
        PersistMetadata currentMetadata = new PersistMetadata(currentId, className, methodName, screenshotId,
                capabilities);
        List<TargetResult> processes = new ArrayList<TargetResult>(targetResults.size());
        for (TargetResult target : targetResults) {
            processes.add(new TargetResult(target.getTarget(), target.getExcludes(), target.getImage()));
        }

        TestResultManager.getInstance().getPersister().saveTargetResults(currentMetadata, processes);
    }

    /**
     * ????????
     * 
     * @param screenshotId ?ID
     * @param compareTargets ???
     * @param hiddenElementsSelectors ????DOM??
     * @return ??{@link ScreenshotResult}
     */
    private ScreenshotResult takeCaptureAndPersistImage(String screenshotId, List<CompareTarget> compareTargets,
            List<DomSelector> hiddenElementsSelectors) {
        ScreenshotResult screenshotResult = driver.takeScreenshot(screenshotId, compareTargets,
                hiddenElementsSelectors);
        LOG.trace("(takeCaptureAndPersistImage) (ssid: {}) result: {}", screenshotId, screenshotResult);

        // Persist all screenshots
        Persister persister = TestResultManager.getInstance().getPersister();
        ScreenshotImage entireScreenshotImage = screenshotResult.getEntireScreenshotImage();
        if (entireScreenshotImage.isImageCached()) {
            persister.saveScreenshot(
                    new PersistMetadata(currentId, className, methodName, screenshotId, null, null, capabilities),
                    entireScreenshotImage.get());
        }

        for (TargetResult targetResult : screenshotResult.getTargetResults()) {
            ScreenshotImage image = targetResult.getImage();
            if (!image.isImageCached()) {
                LOG.debug("(takeCaptureAndPersistImage) Screenshot image was not captured. ({})",
                        targetResult.getTarget());
                continue;
            }

            ScreenAreaResult target = targetResult.getTarget();
            PersistMetadata metadata;
            if (target.getSelector() == null) {
                metadata = new PersistMetadata(currentId, className, methodName, screenshotId, null,
                        target.getRectangle(), capabilities);
            } else {
                metadata = new PersistMetadata(currentId, className, methodName, screenshotId, target.getSelector(),
                        null, capabilities);
            }

            persister.saveScreenshot(metadata, image.get());
        }

        return screenshotResult;
    }

    /**
     * Expected?ScreenshotResult????
     * 
     * @param screenshotId ID
     * @param targetResults ?
     * @param validateResult ??
     * @return ??{@link ScreenshotResult}
     */
    private ScreenshotResult getScreenshotResultForExpectedMode(String screenshotId,
            List<TargetResult> targetResults, ValidateResult validateResult) {
        List<TargetResult> processes = new ArrayList<TargetResult>(targetResults.size());
        for (TargetResult target : targetResults) {
            processes.add(new TargetResult(null, target.getTarget(), target.getExcludes(), target.isMoveTarget(),
                    target.getHiddenElementSelectors()));
        }

        for (DomSelector selector : validateResult.noElementSelectors) {
            processes.add(new TargetResult(null, new ScreenAreaResult(null, null, new ScreenArea(selector)), null,
                    null, null));
        }

        ExecResult result = validateResult.isValid() ? null : ExecResult.FAILURE;
        return new ScreenshotResult(screenshotId, result, null, processes, className, methodName,
                capabilities.asMap(), null);
    }

    /**
     * Actual?ScreenshotResult????
     * 
     * @param screenshotId ID
     * @param expectedId ?ID
     * @param currents ??
     * @param expects ?
     * @param validateResult ??
     * @return ????{@link ScreenshotResult}
     */
    private ScreenshotResult compareTargetResults(String screenshotId, String expectedId,
            List<TargetResult> currents, List<TargetResult> expects, ValidateResult validateResult) {
        boolean assertFail = !validateResult.isValid();

        List<TargetResult> processes = new ArrayList<TargetResult>(currents.size());
        for (final TargetResult current : currents) {
            // Target element's area was nothing
            IndexDomSelector selector = current.getTarget().getSelector();
            if (selector != null && validateResult.noAreaElementSelectors.contains(selector)) {
                processes.add(new TargetResult(ExecResult.FAILURE, current.getTarget(), current.getExcludes(),
                        current.isMoveTarget(), current.getHiddenElementSelectors()));
                LOG.debug("[Comparison skipped] ({})", current.getTarget());
                continue;
            }

            TargetResult expected;
            try {
                expected = Iterators.find(expects.iterator(), new Predicate<TargetResult>() {
                    @Override
                    public boolean apply(TargetResult input) {
                        return current.getTarget().areaEquals(input.getTarget());
                    }
                });
            } catch (NoSuchElementException e) {
                LOG.error("[Comparison failed] No element found for target ({}).", current.getTarget());
                processes.add(new TargetResult(null, current.getTarget(), current.getExcludes(),
                        current.isMoveTarget(), current.getHiddenElementSelectors()));
                assertFail = true;

                continue;
            }

            // ?
            LOG.debug("[Comparison start] ({})", current);
            ImageRectanglePair currentImage = prepareScreenshotImageForCompare(current);
            ImageRectanglePair expectedImage = prepareScreenshotImageForCompare(expected);
            DiffPoints compareResult = ImageUtils.compare(currentImage.image, currentImage.rectangle,
                    expectedImage.image, expectedImage.rectangle, current.getOptions());
            assertFail |= compareResult.isFailed();
            if (compareResult.isFailed()) {
                LOG.error("[Comparison failed] ({})", current);
            } else {
                LOG.debug("[Comparison success] ({})", current);
            }

            processes.add(new TargetResult(compareResult.isSucceeded() ? ExecResult.SUCCESS : ExecResult.FAILURE,
                    current.getTarget(), current.getExcludes(), current.isMoveTarget(),
                    current.getHiddenElementSelectors()));

            // ?Fail????????
            if (compareResult.isFailed()) {
                LOG.debug("[Create diff image] ({})", current);
                BufferedImage diffImage = ImageUtils.getDiffImage(expectedImage.image, currentImage.image,
                        compareResult);

                // Metadata????
                ScreenAreaResult target = current.getTarget();
                PersistMetadata metadata;
                if (target.getSelector() == null) {
                    metadata = new PersistMetadata(currentId, className, methodName, screenshotId, null,
                            target.getRectangle(), capabilities);
                } else {
                    metadata = new PersistMetadata(currentId, className, methodName, screenshotId,
                            target.getSelector(), null, capabilities);
                }
                TestResultManager.getInstance().getPersister().saveDiffImage(metadata, diffImage);
            }
        }

        for (DomSelector selector : validateResult.noElementSelectors) {
            processes.add(new TargetResult(null, new ScreenAreaResult(null, null, new ScreenArea(selector)), null,
                    null, null));
        }

        return new ScreenshotResult(screenshotId, assertFail ? ExecResult.FAILURE : ExecResult.SUCCESS, expectedId,
                processes, className, methodName, capabilities.asMap(), null);
    }

    /**
     * ???????????????
     * 
     * @param target ?
     * @return ????
     */
    private ImageRectanglePair prepareScreenshotImageForCompare(TargetResult target) {
        BufferedImage image = target.getImage().get();

        // Mask
        List<ScreenAreaResult> excludes = target.getExcludes();
        if (!excludes.isEmpty()) {
            List<Rectangle> maskAreas = Lists.transform(excludes, SCREEN_AREA_RESULT_TO_RECTANGLE_FUNCTION);
            image = ImageUtils.getMaskedImage(image, maskAreas);
        }

        return new ImageRectanglePair(image, target.getTarget().getRectangle().toRectangle());
    }

    //</editor-fold>

    //<editor-fold desc="assertScreenshot">

    /**
     * ??????????????????????diff??????????
     * 
     * @param screenshotResult {@link PtlWebDriver#takeScreenshot(String) takeScreenshot}??????
     */
    public void assertScreenshot(ScreenshotResult screenshotResult) {
        assertScreenshot(null, screenshotResult);
    }

    /**
     * ??????????????????????diff??????????
     * 
     * @param message ??
     * @param screenshotResult {@link PtlWebDriver#takeScreenshot(String) takeScreenshot}??????
     */
    public void assertScreenshot(String message, ScreenshotResult screenshotResult) {
        ExecMode execMode = PtlTestConfig.getInstance().getEnvironment().getExecMode();
        String screenshotId = screenshotResult.getScreenshotId();
        if (!execMode.isRunTest()) {
            LOG.debug("[AssertScreenshot skipped] (ssid: {}, mode: {})", screenshotId, execMode);
            return;
        }

        String expectedId = TestResultManager.getInstance().getExpectedId(className, methodName);
        PersistMetadata expectedMetadata = new PersistMetadata(expectedId, className, methodName, screenshotId,
                capabilities);
        List<TargetResult> expectedTargetResults = TestResultManager.getInstance().getPersister()
                .loadTargetResults(expectedMetadata);

        LOG.info("[AssertScreenshot comparison start] (ssid: {})", screenshotId);
        ScreenshotResult result = compareTargetResults(screenshotId, expectedId,
                screenshotResult.getTargetResults(), expectedTargetResults, new ValidateResult());

        if (!result.getResult().isSuccess()) {
            LOG.info("[AssertScreenshot failed] (ssid: {})", screenshotId);
            throw Strings.isNullOrEmpty(message) ? new AssertionError() : new AssertionError(message);
        }

        LOG.info("[AssertScreenshot succeeded] (ssid: {})", screenshotId);
    }

    /**
     * ??????????????????????diff????????<br />
     * <br />
     * {@link #assertScreenshot(ScreenshotResult)}?????????????????
     * 
     * @param screenshotResult {@link PtlWebDriver#takeScreenshot(String) takeScreenshot}??????
     */
    public void verifyScreenshot(ScreenshotResult screenshotResult) {
        try {
            LOG.info("[VerifyScreenshot start] (ssid: {})", screenshotResult.getExpectedId());
            assertScreenshot(screenshotResult);
        } catch (AssertionError e) {
            LOG.info("[VerifyScreenshot failed] (ssid: {})", screenshotResult.getScreenshotId());
            verifyErrors.add(e);
        }
    }

    /**
     * ??????????????????????diff????????<br />
     * <br />
     * {@link #assertScreenshot(ScreenshotResult)}?????????????????
     * 
     * @param message ??
     * @param screenshotResult {@link PtlWebDriver#takeScreenshot(String) takeScreenshot}??????
     */
    public void verifyScreenshot(String message, ScreenshotResult screenshotResult) {
        try {
            LOG.info("[VerifyScreenshot start] (ssid: {})", screenshotResult.getExpectedId());
            assertScreenshot(message, screenshotResult);
        } catch (AssertionError e) {
            LOG.info("[VerifyScreenshot failed] (ssid: {})", screenshotResult.getScreenshotId());
            verifyErrors.add(e);
        }
    }

    //</editor-fold>

    //<editor-fold desc="assertExists">

    /**
     * ??????????????????<br />
     * ?????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param image ???
     */
    public void assertExist(BufferedImage image) {
        assertExist(null, image);
    }

    /**
     * ??????????????????<br />
     * ?????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????
     * 
     * @param message {@link AssertionError}?
     * @param image ???
     */
    public void assertExist(String message, BufferedImage image) {
        ExecMode execMode = PtlTestConfig.getInstance().getEnvironment().getExecMode();
        if (!execMode.isRunTest()) {
            LOG.debug("[AssertExist skipped] (mode: {})", execMode);
            return;
        }

        // Capture body
        LOG.debug("[AssertExist capture start]");
        ScreenshotImage screenshot = driver.takeScreenshot("assertExists").getTargetResults().get(0).getImage();
        BufferedImage entireScreenshotImage = screenshot.get();
        LOG.debug("[AssertExist capture finished]");

        if (ImageUtils.isContained(entireScreenshotImage, image)) {
            LOG.info("[AssertExist succeeded]");
            return;
        }

        LOG.info("[AssertExist failed]");
        throw Strings.isNullOrEmpty(message) ? new AssertionError() : new AssertionError(message);
    }

    /**
     * ??????????????????<br />
     * ?????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????<br />
     * <br />
     * {@link #assertExist(BufferedImage)}?????????????????
     * 
     * @param image ???
     */
    public void verifyExists(BufferedImage image) {
        try {
            LOG.info("[VerifyExist start]");
            assertExist(image);
        } catch (AssertionError e) {
            LOG.info("[VerifyExist failed]");
            verifyErrors.add(e);
        }
    }

    /**
     * ??????????????????<br />
     * ?????{@link com.htmlhifive.pitalium.core.config.ExecMode#SET_EXPECTED}???????<br />
     * <br />
     * {@link #assertExist(BufferedImage)}?????????????????
     * 
     * @param message {@link AssertionError}?
     * @param image ???
     */
    public void verifyExists(String message, BufferedImage image) {
        try {
            LOG.info("[VerifyExist start]");
            assertExist(message, image);
        } catch (AssertionError e) {
            LOG.info("[VerifyExist failed]");
            verifyErrors.add(e);
        }
    }

    //</editor-fold>

    /**
     * ???????
     * 
     * @param description ??Description
     * @return ??
     */
    private static String getClassName(Description description) {
        return description.getTestClass().getSimpleName();
    }

    /**
     * ???????
     * 
     * @param description ??Description
     * @return ??
     */
    private static String getMethodName(Description description) {
        return description.getMethodName().split("\\[")[0];
    }

    /**
     * ?List?????
     * 
     * @param <T> ???
     * @param array ??
     * @return ?List
     */
    @SafeVarargs
    private static <T> List<T> asList(T... array) {
        if (array == null || array.length == 0) {
            return Collections.emptyList();
        } else if (array.length == 1) {
            return Collections.singletonList(array[0]);
        } else {
            return Arrays.asList(array);
        }
    }

    /**
     * ??????
     */
    private static class ImageRectanglePair {
        private final BufferedImage image;
        private final Rectangle rectangle;

        /**
         * ??????????
         * 
         * @param image ?
         * @param rectangle 
         */
        public ImageRectanglePair(BufferedImage image, Rectangle rectangle) {
            this.image = image;
            this.rectangle = rectangle;
        }
    }

    /**
     * ?????
     */
    private static class ValidateResult {
        /**
         * ????????
         */
        @JsonInclude
        private final Collection<IndexDomSelector> noAreaElementSelectors;
        /**
         * ??????
         */
        @JsonInclude
        private final Collection<DomSelector> noElementSelectors;

        /**
         * ???????
         */
        public ValidateResult() {
            this(new ArrayList<IndexDomSelector>(), new ArrayList<DomSelector>());
        }

        /**
         * ????????????????<br>
         * ??????
         * 
         * @param noAreaElementSelectors ????????
         * @param noElementSelectors ??????
         */
        public ValidateResult(Collection<IndexDomSelector> noAreaElementSelectors,
                Collection<DomSelector> noElementSelectors) {
            this.noAreaElementSelectors = noAreaElementSelectors;
            this.noElementSelectors = noElementSelectors;
        }

        /**
         * ????Valid???????
         * 
         * @return Valid?true?Invalid?false
         */
        public boolean isValid() {
            return noAreaElementSelectors.isEmpty() && noElementSelectors.isEmpty();
        }

        @Override
        public String toString() {
            return "ValidateResult: " + JSONUtils.toString(this);
        }
    }

}