Java tutorial
/* * 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.selenium; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javax.imageio.ImageIO; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.openqa.selenium.Capabilities; import org.openqa.selenium.OutputType; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.internal.JsonToWebElementConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.io.Files; import com.htmlhifive.pitalium.common.exception.TestRuntimeException; import com.htmlhifive.pitalium.core.config.EnvironmentConfig; import com.htmlhifive.pitalium.core.model.CompareTarget; import com.htmlhifive.pitalium.core.model.DomSelector; 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.ScreenAreaWrapper; import com.htmlhifive.pitalium.core.model.ScreenshotArgument; import com.htmlhifive.pitalium.core.model.ScreenshotParams; 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.image.model.RectangleArea; import com.htmlhifive.pitalium.image.model.ScreenshotImage; import com.htmlhifive.pitalium.image.util.ImageUtils; /** * WebDriver?{@link org.openqa.selenium.remote.RemoteWebDriver} * ?????Web?????????????<br/> * ?????WebDriver????????? */ public abstract class PtlWebDriver extends RemoteWebDriver { /** * ?????1 */ public static final double DEFAULT_SCREENSHOT_SCALE = 1d; //@formatter:off // CHECKSTYLE:OFF private static final String[] SCRIPTS_SCROLL_TOP = { "document.documentElement.scrollTop", "document.body.scrollTop" }; private static final String[] SCRIPTS_SCROLL_LEFT = { "document.documentElement.scrollLeft", "document.body.scrollLeft" }; private static final String SCRIPT_GET_DEFAULT_DOCUMENT_OVERFLOW = "return {\"overflow\": document.documentElement.style.overflow};"; private static final String SCRIPT_SET_DOCUMENT_OVERFLOW = "document.documentElement.style.overflow = arguments[0];"; private static final String SCRIPT_GET_DEFAULT_BODY_STYLE = "return {" + " \"position\": document.body.style.position ? document.body.style.position : null," + " \"top\": document.body.style.top ? document.body.style.top : null," + " \"left\": document.body.style.left ? document.body.style.left : null," + " \"width\": document.body.style.width ? document.body.style.width : null," + " \"scrollWidth\": document.body.scrollWidth};"; private static final String SCRIPT_MOVE_BODY = "document.body.style.position = arguments[0];" + "document.body.style.top = arguments[1];" + "document.body.style.left = arguments[2];"; private static final String GET_WINDOW_WIDTH_SCRIPT = "if (typeof window.innerWidth != 'undefined') {" + " return window.innerWidth;" + "} else if (typeof document.documentElement != 'undefined' && typeof document.documentElement.clientWidth != 'undefined' && document.documentElement.clientWidth != 0) {" + " return document.documentElement.clientWidth;" + "} else {" + " return document.getElementsByTagName('body')[0].clientWidth;" + "}"; private static final String GET_WINDOW_HEIGHT_SCRIPT = "if (typeof window.innerHeight != 'undefined') {" + " return window.innerHeight;" + "} else if (typeof document.documentElement != 'undefined' && typeof document.documentElement.clientHeight != 'undefined' && document.documentElement.clientHeight != 0) {" + " return document.documentElement.clientHeight;" + "} else {" + " return document.getElementsByTagName('body')[0].clientHeight;" + "}"; private static final String GET_SCROLL_WIDTH_SCRIPT = "return arguments[0].scrollWidth"; private static final String GET_SCROLL_HEIGHT_SCRIPT = "return arguments[0].scrollHeight"; private static final String GET_BODY_LEFT_SCRIPT = "var _bodyLeft = arguments[0].getBoundingClientRect().left; return _bodyLeft;"; private static final String GET_BODY_TOP_SCRIPT = "var _bodyTop = arguments[0].getBoundingClientRect().top; return _bodyTop;"; private static final long SCROLL_WAIT_MS = 100L; // CHECKSTYLE:ON //@formatter:on private static final Map<Capabilities, AtomicInteger> DEBUG_SCREENSHOT_COUNTS = new HashMap<Capabilities, AtomicInteger>(); protected final Logger LOG = LoggerFactory.getLogger(getClass()); private final PtlCapabilities capabilities; private String baseUrl; private double scale = DEFAULT_SCREENSHOT_SCALE; private EnvironmentConfig environmentConfig; /** * * * @param remoteAddress RemoteWebDriverServer? * @param capabilities Capability */ protected PtlWebDriver(URL remoteAddress, PtlCapabilities capabilities) { super(remoteAddress, capabilities); this.capabilities = capabilities; // JsonToWebElementConverter??????findElement???RemoteWebElement?? setElementConverter(new JsonToPtlWebElementConverter(this)); } /** * ?URL???? * * @see com.htmlhifive.pitalium.core.config.TestAppConfig#baseUrl * @return ?URL */ protected String getBaseUrl() { return baseUrl; } /** * ?URL??? * * @see com.htmlhifive.pitalium.core.config.TestAppConfig#baseUrl * @param baseUrl ?URL */ protected void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } /** * ??? * * @param environmentConfig */ protected final void setEnvironmentConfig(EnvironmentConfig environmentConfig) { this.environmentConfig = environmentConfig; } /** * ??URL?????url?http://???https://???????????????{@link #baseUrl} + url????? * * @param url ????URL */ @Override public void get(String url) { String targetUrl = UrlUtils.getTargetUrl(baseUrl, url); LOG.debug("[Get] ({}, base: {}, url: {})", targetUrl, baseUrl, url); super.get(targetUrl); } /** * Capability???? * * @return Capability */ @Override public PtlCapabilities getCapabilities() { return capabilities; } @Override public <X> X getScreenshotAs(OutputType<X> outputType) throws WebDriverException { X screenshot = super.getScreenshotAs(outputType); if (environmentConfig != null && environmentConfig.isDebug()) { exportDebugScreenshot(screenshot); } return screenshot; } private void exportDebugScreenshot(Object screenshot) { File imageFile; if (screenshot instanceof String) { imageFile = OutputType.FILE.convertFromBase64Png((String) screenshot); } else if (screenshot instanceof byte[]) { imageFile = OutputType.FILE.convertFromPngBytes((byte[]) screenshot); } else if (screenshot instanceof File) { imageFile = (File) screenshot; } else { LOG.warn("Unknown OutputType: \"{}\"", screenshot.getClass().getName()); return; } // Filename -> logs/screenshots/firefox/43/0000.png // FIXME: 2016/01/04 ???? String filename; File dir; synchronized (DEBUG_SCREENSHOT_COUNTS) { AtomicInteger counter = DEBUG_SCREENSHOT_COUNTS.get(capabilities); if (counter == null) { counter = new AtomicInteger(); DEBUG_SCREENSHOT_COUNTS.put(capabilities, counter); } int count = counter.getAndIncrement(); filename = String.format(Locale.US, "%04d.png", count); dir = new File("logs", "screenshots"); dir = new File(dir, capabilities.getBrowserName()); if (!Strings.isNullOrEmpty(capabilities.getVersion())) { dir = new File(dir, capabilities.getVersion()); } // First time => delete old files if (count == 0 && dir.exists()) { try { FileUtils.deleteDirectory(dir); } catch (IOException e) { LOG.warn("Cannot delete debug screenshot directory \"" + dir.getAbsolutePath() + "\"", e); return; } } if (!dir.exists() && !dir.mkdirs()) { LOG.warn("Debug screenshot persist error. Cannot make directory \"{}\"", dir.getAbsolutePath()); return; } } try { Files.copy(imageFile, new File(dir, filename)); } catch (IOException e) { LOG.warn("Debug screenshot persist error", e); } } /** * ??????????? * * @return ???true????false */ protected boolean canMoveTarget() { return true; } /** * body??????????????? * * @return ???????true????????false */ protected boolean canHideBodyScrollbar() { return true; } /** * ???????????????? * * @return ???????true????????false */ protected boolean canHideElementScrollbar() { return true; } /** * ??????????????? * * @return ?????true??????false */ protected boolean isHideElementsRequired() { return true; } /** * ???????????? * * @return ?????true????????false */ protected boolean canResizeElement() { return true; } /** * @see org.openqa.selenium.JavascriptExecutor#executeScript(String, Object...) * @param script ?JavaScript * @param params ?? * @param <T> ?? * @return ? */ @SuppressWarnings("unchecked") public <T> T executeJavaScript(String script, Object... params) { return (T) executeScript(script, params); } @Override public Object executeScript(String script, Object... args) { Object result = super.executeScript(script, args); LOG.trace("[Execute script] [{}] (args: {}, result: {})", script, args, result); return result; } //<editor-fold desc="takeScreenshot"> /** * ????? * * @param screenshotId ID * @return ? */ public ScreenshotResult takeScreenshot(String screenshotId) { return takeScreenshot(screenshotId, Collections.singletonList(new CompareTarget())); } /** * ???? * * @param screenshotId ID * @param compareTargets ?? * @return ? */ public ScreenshotResult takeScreenshot(String screenshotId, CompareTarget[] compareTargets) { return takeScreenshot(screenshotId, Arrays.asList(compareTargets)); } /** * ???? * * @param screenshotId ID * @param compareTargets ?? * @return ? */ public ScreenshotResult takeScreenshot(String screenshotId, List<CompareTarget> compareTargets) { return takeScreenshot(screenshotId, compareTargets, new ArrayList<DomSelector>()); } /** * ??????????? * * @param screenshotId ID * @param compareTargets ?? * @param hiddenElementsSelectors ????? * @return ? */ public ScreenshotResult takeScreenshot(String screenshotId, CompareTarget[] compareTargets, DomSelector[] hiddenElementsSelectors) { return takeScreenshot(screenshotId, Arrays.asList(compareTargets), Arrays.asList(hiddenElementsSelectors)); } /** * ??????? * * @param arg ? * @return ? */ public ScreenshotResult takeScreenshot(ScreenshotArgument arg) { return takeScreenshot(arg.getScreenshotId(), arg.getTargets(), arg.getHiddenElementSelectors()); } /** * ??????????? * * @param screenshotId ID * @param compareTargets ?? * @param hiddenElementSelectors ????? * @return ? */ public ScreenshotResult takeScreenshot(String screenshotId, List<CompareTarget> compareTargets, List<DomSelector> hiddenElementSelectors) { List<CompareTarget> cTarget; // CompareTargets????BODY?? if (compareTargets == null || compareTargets.isEmpty()) { cTarget = new ArrayList<CompareTarget>(1); cTarget.add(new CompareTarget()); } else { cTarget = compareTargets; } LOG.debug("[TakeScreenshot start] (ssid: {})", screenshotId); List<PtlWebElement> hiddenElements = findElementsByDomSelectors(hiddenElementSelectors); LOG.trace("[TakeScreenshot] compareTargets: {}, hiddenElementSelectors: {}, hiddenElements: {}", compareTargets, hiddenElementSelectors, hiddenElements); // CompareTarget => ScreenshotParams List<Pair<CompareTarget, ScreenshotParams>> moveTargetParams = new ArrayList<Pair<CompareTarget, ScreenshotParams>>(); List<Pair<CompareTarget, ScreenshotParams>> nonMoveNoScrollTargetParams = new ArrayList<Pair<CompareTarget, ScreenshotParams>>(); List<Pair<CompareTarget, ScreenshotParams>> nonMoveScrollTargetParams = new ArrayList<Pair<CompareTarget, ScreenshotParams>>(); for (CompareTarget compareTarget : cTarget) { List<ScreenAreaWrapper> targets = ScreenAreaWrapper.fromArea(compareTarget.getCompareArea(), this, null); for (int i = 0; i < targets.size(); i++) { ScreenAreaWrapper target = targets.get(i); List<ScreenAreaWrapper> excludes = new ArrayList<ScreenAreaWrapper>(); for (ScreenArea exclude : compareTarget.getExcludes()) { excludes.addAll(target.getChildWrapper(exclude)); } Pair<CompareTarget, ScreenshotParams> pair = Pair.of(compareTarget, new ScreenshotParams(target, excludes, hiddenElements, compareTarget.isMoveTarget(), compareTarget.isScrollTarget(), i)); if (isMoveTargetRequired(pair.getRight())) { LOG.trace("[TakeScreenshot] MoveTarget: {} (index: {})", target.getParent(), i); moveTargetParams.add(pair); } else if (isScrollTargetRequired(pair.getRight())) { LOG.trace("[TakeScreenshot] Non-move scroll target: {} (index: {})", target.getParent(), i); nonMoveScrollTargetParams.add(pair); } else { LOG.trace("[TakeScreenshot] Non-move non-scroll target: {} (index: {})", target.getParent(), i); nonMoveNoScrollTargetParams.add(pair); } } } // ?? ScreenshotParams[] additionalParams = extractScreenshotParams(nonMoveNoScrollTargetParams); // ???? ScreenshotParams entireScreenshotParams = new ScreenshotParams( ScreenAreaWrapper.fromArea(ScreenArea.of(SelectorType.TAG_NAME, "body"), this, null).get(0), new ArrayList<ScreenAreaWrapper>(), hiddenElements, false, false, 0); LOG.debug("[TakeScreenshot (entire screenshot start)]"); TargetResult entireScreenshotResult = getTargetResult(new CompareTarget(), hiddenElementSelectors, entireScreenshotParams, additionalParams); LOG.debug("[TakeScreenshot (entire screenshot finished)]"); ScreenshotImage entireScreenshotImage = entireScreenshotResult.getImage(); List<TargetResult> screenshotResults = new ArrayList<TargetResult>(); // move??????? if (nonMoveNoScrollTargetParams.isEmpty()) { LOG.debug("[TakeScreenshot (NO non-move non-scroll screenshots)]"); } else { for (Pair<CompareTarget, ScreenshotParams> pair : nonMoveNoScrollTargetParams) { LOG.debug("[TakeScreenshot (non-move non-scroll screenshots start)]"); screenshotResults.add(getTargetResult(pair.getLeft(), hiddenElementSelectors, pair.getRight(), entireScreenshotImage)); LOG.debug("[TakeScreenshot (non-move non-scroll screenshots finished)]"); } } // move?????? if (nonMoveScrollTargetParams.isEmpty()) { LOG.debug("[TakeScreenshot (NO non-move scroll screenshots)]"); } else { LOG.debug("[TakeScreenshot (non-move scroll screenshots start)]"); List<TargetResult> nonMoveScreenshots = takeNonMoveScreenshots(entireScreenshotResult, hiddenElementSelectors, entireScreenshotParams, nonMoveScrollTargetParams); screenshotResults.addAll(nonMoveScreenshots); LOG.debug("[TakeScreenshot (non-move scroll screenshots finished)]"); } // move??? if (moveTargetParams.isEmpty()) { LOG.debug("[TakeScreenshot (NO move screenshots)]"); } else { LOG.debug("[TakeScreenshot (move screenshots start)]"); for (Pair<CompareTarget, ScreenshotParams> pair : moveTargetParams) { TargetResult moveScreenshot = takeMoveScreenshots(pair.getLeft(), hiddenElementSelectors, pair.getRight()); screenshotResults.add(moveScreenshot); } LOG.debug("[TakeScreenshot (move screenshots finished)]"); } return new ScreenshotResult(screenshotId, screenshotResults, entireScreenshotImage); } /** * isMove?false?????? * * @param entireScreenshotResult ????TargetResult * @param hiddenElementSelectors ?????? * @param targetParams ? * @param entireScreenshotParams ? * @return ??TargetResult */ protected List<TargetResult> takeNonMoveScreenshots(TargetResult entireScreenshotResult, List<DomSelector> hiddenElementSelectors, ScreenshotParams entireScreenshotParams, List<Pair<CompareTarget, ScreenshotParams>> targetParams) { LOG.trace( "[TakeNonMoveScrollScreenshots] entireScreenshotResult: {}, hiddenElementSelectors: {}, targetParams: {}, entireScreenshotParams: {}", entireScreenshotResult, hiddenElementSelectors, targetParams, entireScreenshotParams); String[][] overflowStatus = new String[targetParams.size()][2]; String[] resizeStatus = new String[targetParams.size()]; int[] partialScrollNums = new int[targetParams.size()]; int maxPartialScrollNum = 0; for (int i = 0; i < targetParams.size(); i++) { Pair<CompareTarget, ScreenshotParams> pair = targetParams.get(i); PtlWebElement targetElement = pair.getRight().getTarget().getElement(); // ?hidden?? // ???????? if (canHideElementScrollbar()) { overflowStatus[i] = targetElement.getOverflowStatus(); targetElement.hideScrollBar(); } // ???????? // ?????? if (canResizeElement()) { resizeStatus[i] = targetElement.getResizeStatus(); targetElement.setNoResizable(); } // ?? partialScrollNums[i] = targetElement.getScrollNum(); if (maxPartialScrollNum < partialScrollNums[i]) { maxPartialScrollNum = partialScrollNums[i]; } LOG.trace("[TakeNonMoveScrollScreenshots] Partial scroll {} times. ({})", partialScrollNums[i], pair.getLeft().getCompareArea()); // ????? if (partialScrollNums[i] > 0) { try { targetElement.scrollTo(0, 0); } catch (InterruptedException e) { throw new TestRuntimeException(e); } } } LOG.trace("[TakeNonMoveScrollScreenshots] overflow: {}, resize: {}", overflowStatus, resizeStatus); // ???? List<BufferedImage> screenshots = takeNonMoveScrollTargetScreenshots(hiddenElementSelectors, entireScreenshotParams, targetParams, maxPartialScrollNum, partialScrollNums); List<TargetResult> nonMoveNoScrollTargetResults = new ArrayList<TargetResult>(); for (int i = 0; i < targetParams.size(); i++) { Pair<CompareTarget, ScreenshotParams> pair = targetParams.get(i); // ?????? RectangleArea targetPosition = pair.getRight().getTarget().getArea(); pair.getRight().getTarget().setArea(new RectangleArea(targetPosition.getX(), targetPosition.getY(), targetPosition.getWidth(), screenshots.get(i).getHeight())); LOG.trace("[TakeNonMoveScrollScreenshots] update target position {}", pair.getRight().getTarget().getArea()); // ?? ScreenAreaResult targetAreaResult = createScreenAreaResult(pair.getRight().getTarget(), pair.getRight().getIndex()); List<ScreenAreaResult> excludes = Lists.transform(pair.getRight().getExcludes(), new Function<ScreenAreaWrapper, ScreenAreaResult>() { @Override public ScreenAreaResult apply(ScreenAreaWrapper input) { return createScreenAreaResult(input, null); } }); TargetResult tResult = new TargetResult(null, targetAreaResult, excludes, isMoveTargetRequired(pair.getRight()), hiddenElementSelectors, new ScreenshotImage(screenshots.get(i)), pair.getLeft().getOptions()); nonMoveNoScrollTargetResults.add(tResult); } // ??? if (canHideElementScrollbar()) { for (int i = 0; i < targetParams.size(); i++) { PtlWebElement el = targetParams.get(i).getRight().getTarget().getElement(); el.setOverflowStatus(overflowStatus[i][0], overflowStatus[i][1]); } } // ?? if (canResizeElement()) { for (int i = 0; i < targetParams.size(); i++) { PtlWebElement el = targetParams.get(i).getRight().getTarget().getElement(); if (resizeStatus[i] != null) { el.setResizeStatus(resizeStatus[i]); } } } return nonMoveNoScrollTargetResults; } /** * isMove?true????? * * @param target ? * @param hiddenElementSelectors ?????? * @param params ? * @return ?TargetResult */ protected TargetResult takeMoveScreenshots(CompareTarget target, List<DomSelector> hiddenElementSelectors, ScreenshotParams params) { LOG.trace("[TakeMoveScreenshot] target: {}, hiddenElementSelectors: {}, params: {}", target, hiddenElementSelectors, params); // ???? if (!target.isScrollTarget()) { LOG.trace("[TakeMoveScreenshot] partial scroll is not required for ({})", target.getCompareArea()); return getTargetResult(target, hiddenElementSelectors, params); } // ??? PtlWebElement el = params.getTarget().getElement(); // ???????? String[] overflowStatus = null; if (canHideElementScrollbar()) { String[] statuses = el.getOverflowStatus(); overflowStatus = Arrays.copyOf(statuses, statuses.length); // ???? el.hideScrollBar(); } String resizeStatus = null; if (canResizeElement()) { // ??????? resizeStatus = el.getResizeStatus(); // ???? el.setNoResizable(); } // ???? BufferedImage screenshot = takeMoveScrollTargetScreenshot(target, hiddenElementSelectors, params); // ?????? RectangleArea targetPosition = params.getTarget().getArea(); params.getTarget().setArea(new RectangleArea(targetPosition.getX(), targetPosition.getY(), targetPosition.getWidth(), screenshot.getHeight())); LOG.trace("[TakeMoveScreenshots] update target position {}", params.getTarget().getArea()); // TargetResult for target area ScreenAreaResult targetAreaResult = createScreenAreaResult(params.getTarget(), params.getIndex()); // TargetResult for exclude areas List<ScreenAreaResult> excludes = Lists.transform(params.getExcludes(), new Function<ScreenAreaWrapper, ScreenAreaResult>() { @Override public ScreenAreaResult apply(ScreenAreaWrapper input) { return createScreenAreaResult(input, null); } }); // ???? if (canHideElementScrollbar()) { el.setOverflowStatus(overflowStatus[0], overflowStatus[1]); } // ??? if (canResizeElement() && resizeStatus != null) { el.setResizeStatus(resizeStatus); } return new TargetResult(null, targetAreaResult, excludes, params.isMoveTarget(), hiddenElementSelectors, new ScreenshotImage(screenshot), target.getOptions()); } /** * ????????????? * * @param hiddenElementSelectors ?????? * @param entireScreenshotParams ???? * @param targetParams ? * @param maxPartialScrollNum ?? * @param partialScrollNums ??? * @return ?? */ private List<BufferedImage> takeNonMoveScrollTargetScreenshots(List<DomSelector> hiddenElementSelectors, ScreenshotParams entireScreenshotParams, List<Pair<CompareTarget, ScreenshotParams>> targetParams, int maxPartialScrollNum, int[] partialScrollNums) { LOG.debug("[TakeNonMoveScrollScreenshots (capture start)] maximum partial scroll times: {}", maxPartialScrollNum); List<List<BufferedImage>> allTargetScreenshots = new ArrayList<List<BufferedImage>>(); int targetSize = targetParams.size(); for (int i = 0; i < targetSize; i++) { allTargetScreenshots.add(new ArrayList<BufferedImage>()); } long[] partialScrollAmounts = new long[targetSize]; long[] partialScrollTops = new long[targetSize]; // ?? ScreenshotParams[] additionalParams = extractScreenshotParams(targetParams); // ??target?????????target ? for (int i = 0; i <= maxPartialScrollNum; i++) { // TargetResult entireResult = getTargetResult(new CompareTarget(), hiddenElementSelectors, entireScreenshotParams, additionalParams); ScreenshotImage entireScreenshotImage = entireResult.getImage(); LOG.trace("[TakeNonMoveScrollScreenshots (captured)] ({}) TargetResult: {}", entireScreenshotImage, entireResult); // scale??? if (i == 0) { scale = calcScale(getCurrentPageWidth(), entireScreenshotImage.get().getWidth()); LOG.trace("[TakeNonMoveScrollScreenshots] scale: {}", scale); } // ?target?? for (int j = 0; j < targetSize; j++) { Pair<CompareTarget, ScreenshotParams> pair = targetParams.get(j); PtlWebElement targetElement = pair.getRight().getTarget().getElement(); // ????????????? if (i <= partialScrollNums[j]) { // ?????????? TargetResult targetPartResult = getTargetResult(pair.getLeft(), hiddenElementSelectors, pair.getRight(), entireScreenshotImage); allTargetScreenshots.get(j).add(targetPartResult.getImage().get()); } // ?????????????? if (i < partialScrollNums[j]) { try { long currentScrollAmount = targetElement.scrollNext(); if (currentScrollAmount == 0) { partialScrollNums[j] = i; } else { partialScrollAmounts[j] = currentScrollAmount; } LOG.debug("[TakeNonMoveScrollScreenshots] Partial scroll amount({}): {}", j, currentScrollAmount); } catch (InterruptedException e) { throw new TestRuntimeException(e); } } else if (i != 0) { // ???????????? partialScrollTops[j] = Math.round(targetElement.getCurrentScrollTop()); } } } LOG.debug("[TakeNonMoveScrollScreenshots (capture finished)]"); // ????border? trimNonMoveBorder(allTargetScreenshots, targetParams, scale); // ????padding? trimNonMovePadding(allTargetScreenshots, targetParams); LOG.debug("[TakeNonMoveScrollScreenshots (image processing start)]"); // ??????trim for (int i = 0; i < allTargetScreenshots.size(); i++) { List<BufferedImage> targetScreenshots = allTargetScreenshots.get(i); PtlWebElement targetElement = targetParams.get(i).getRight().getTarget().getElement(); trimBottomImage(targetScreenshots, partialScrollAmounts[i], targetElement, scale); } List<BufferedImage> screenshots = new ArrayList<>(); for (int i = 0; i < targetSize; i++) { Pair<CompareTarget, ScreenshotParams> pair = targetParams.get(i); // Exclude? for (ScreenAreaWrapper wrapper : pair.getRight().getExcludes()) { wrapper.setArea(wrapper.getArea().move(0, partialScrollTops[i] * scale)); } // ???? List<BufferedImage> targetScreenshots = allTargetScreenshots.get(i); screenshots.add(ImageUtils.verticalMerge(targetScreenshots)); } LOG.debug("[TakeNonMoveScrollScreenshots (image processing finished)]"); return screenshots; } /** * ????????????? * * @param target ? * @param hiddenElementSelectors ????? * @param params ? * @return ?? */ private BufferedImage takeMoveScrollTargetScreenshot(CompareTarget target, List<DomSelector> hiddenElementSelectors, ScreenshotParams params) { PtlWebElement el = params.getTarget().getElement(); // ??? long clientHeight = el.getClientHeight(); LOG.debug("[TakeMoveScreenshot (capture start)] clientHeight: {}", clientHeight); double captureTop = 0d; long scrollTop = -1L; long currentScrollAmount = -1; List<Double> allCaptureTop = new ArrayList<Double>(); List<BufferedImage> images = new ArrayList<BufferedImage>(); try { // ?? el.scrollTo(0d, 0d); // ?? long currentScrollTop = Math.round(el.getCurrentScrollTop()); // ??????? long scrollNum = el.getScrollNum(); int currentScrollNum = 0; while (scrollTop != currentScrollTop && currentScrollNum <= scrollNum) { if (currentScrollAmount < 0) { currentScrollAmount = 0; } else { currentScrollAmount = currentScrollTop - scrollTop; } scrollTop = currentScrollTop; LOG.trace("[TakeMoveScreenshot] scrollTop: {}, scrollAmount: {}", scrollTop, currentScrollAmount); // ?? BufferedImage image = getTargetResult(target, hiddenElementSelectors, params).getImage().get(); allCaptureTop.add(captureTop); // scale??? if (currentScrollNum == 0) { scale = calcScale(el.getDoubleValueRect().getWidth(), image.getWidth()); LOG.trace("[TakeMoveScreenshot] scale: {}", scale); } // ?? images.add(image); // ??? double scrollIncrement = 0; scrollIncrement = clientHeight; captureTop += scrollIncrement; LOG.debug("[TakeMoveScreenshot] Scroll increment: {}", scrollIncrement); // ???? el.scrollNext(); currentScrollNum++; // ?? currentScrollTop = Math.round(el.getCurrentScrollTop()); } } catch (InterruptedException e) { throw new TestRuntimeException(e); } LOG.debug("[TakeMoveScreenshot (capture finished)]"); // border????? trimMoveBorder(el, images, scale); // padding????? trimMovePadding(el, images); // ?? trimBottomImage(images, currentScrollAmount, el, scale); // Exclude? for (ScreenAreaWrapper wrapper : params.getExcludes()) { wrapper.setArea(wrapper.getArea().move(0, scrollTop * scale)); } LOG.debug("[TakeMoveScreenshot (image processing start)]"); BufferedImage screenshot = ImageUtils.verticalMerge(images); LOG.debug("[TakeMoveScreenshot (image processing finished)]"); return screenshot; } /** * ????{@link ScreenshotParams}??????????? * * @param params ? * @return {@link ScreenshotParams}?? */ private ScreenshotParams[] extractScreenshotParams(List<Pair<CompareTarget, ScreenshotParams>> params) { int size = params.size(); ScreenshotParams[] extractedParams = new ScreenshotParams[size]; for (int i = 0; i < size; i++) { extractedParams[i] = params.get(i).getRight(); } return extractedParams; } /** * ???????????<br> * ???????? * * @param images ?? * @param lastScrollAmount ?? * @param el ?? * @param currentScale ?? */ protected void trimBottomImage(List<BufferedImage> images, long lastScrollAmount, PtlWebElement el, double currentScale) { LOG.trace("(trimBottomImage) lastScrollAmount: {}, el: {}, currentScroll: {}", lastScrollAmount, el, currentScale); int size = images.size(); // ??1??????????? if (size <= 1) { return; } BufferedImage lastImage = images.get(size - 1); int trimTop = calcTrimTop(lastImage.getHeight(), lastScrollAmount, el, currentScale); LOG.trace("(trimBottomImage) trim: {}", trimTop); if (trimTop > 0 && trimTop < lastImage.getHeight()) { images.set(size - 1, ImageUtils.trim(lastImage, trimTop, 0, 0, 0)); } } /** * ??????????<br> * ???????? * * @param images ?? * @param lastScrollAmount ?? * @param el ?? * @param currentScale ?? */ protected void trimRightImage(List<BufferedImage> images, long lastScrollAmount, PtlWebElement el, double currentScale) { LOG.trace("(trimRightImage) lastScrollAmount: {}, el: {}, currentScroll: {}", lastScrollAmount, el, currentScale); int size = images.size(); // ??1??????????? if (size <= 1) { return; } BufferedImage lastImage = images.get(size - 1); int trimLeft = calcTrimLeft(lastImage.getWidth(), lastScrollAmount, el, currentScale); LOG.trace("(trimRightImage) trim: {}", trimLeft); if (trimLeft > 0 && trimLeft < lastImage.getWidth()) { images.set(size - 1, ImageUtils.trim(lastImage, 0, trimLeft, 0, 0)); } } /** * ???????? * * @param el ?? * @param image ? * @param num ?? * @param size ? * @return ???BufferedImage */ protected BufferedImage trimTargetBorder(WebElement el, BufferedImage image, int num, int size, double currentScale) { LOG.trace("(trimTargetBorder) el: {}; image[w: {}, h: {}], num: {}, size: {}", el, image.getWidth(), image.getHeight(), num, size); WebElementBorderWidth targetBorder = ((PtlWebElement) el).getBorderWidth(); int trimTop = 0; int trimBottom = 0; if (size > 1) { if (num <= 0) { trimBottom = (int) Math.round(targetBorder.getBottom() * currentScale); } else if (num >= size - 1) { trimTop = (int) Math.round(targetBorder.getTop() * currentScale); } else { trimBottom = (int) Math.round(targetBorder.getBottom() * currentScale); trimTop = (int) Math.round(targetBorder.getTop() * currentScale); } } LOG.trace("(trimTargetBorder) top: {}, bottom: {}", trimTop, trimBottom); return ImageUtils.trim(image, trimTop, 0, trimBottom, 0); } /** * ?????Padding???<br> * * * @param el ?? * @param image ? * @param num ??? * @param size ? * @return Padding???BufferedImage */ protected BufferedImage trimTargetPadding(WebElement el, BufferedImage image, int num, int size) { LOG.trace("(trimTargetPadding) el: {}, image[w: {}, h: {}], num: {}, size: {}", el, image.getWidth(), image.getHeight(), num, size); WebElementPadding targetPadding = ((PtlWebElement) el).getPadding(); int trimTop = 0; int trimBottom = 0; if (size > 1) { if (num <= 0) { trimBottom = (int) Math.round(targetPadding.getBottom() * scale); } else if (num >= size - 1) { trimTop = (int) Math.round(targetPadding.getTop() * scale); } else { trimBottom = (int) Math.round(targetPadding.getBottom() * scale); trimTop = (int) Math.round(targetPadding.getTop() * scale); } } LOG.trace("(trimTargetPadding) top: {}, bottom: {}", trimTop, trimBottom); return ImageUtils.trim(image, trimTop, 0, trimBottom, 0); } /** * isMove?false???????border????? * * @param allTargetScreenshots ?? * @param targetParams ? */ protected void trimNonMoveBorder(List<List<BufferedImage>> allTargetScreenshots, List<Pair<CompareTarget, ScreenshotParams>> targetParams, double currentScale) { LOG.trace("[Trim non-move elements' border]"); for (int i = 0; i < allTargetScreenshots.size(); i++) { PtlWebElement targetElement = targetParams.get(i).getRight().getTarget().getElement(); // ???border? if (!targetElement.isBody() && targetParams.get(i).getLeft().isScrollTarget()) { List<BufferedImage> targetScreenshots = allTargetScreenshots.get(i); for (int j = 0; j < targetScreenshots.size(); j++) { targetScreenshots.set(j, trimTargetBorder(targetElement, targetScreenshots.get(j), j, targetScreenshots.size(), currentScale)); } } } } /** * isMove?true???????border????? * * @param el ?? * @param images ?? */ protected void trimMoveBorder(WebElement el, List<BufferedImage> images, double currentScale) { LOG.trace("[Trim move element's border]"); for (int i = 0; i < images.size(); i++) { images.set(i, trimTargetBorder(el, images.get(i), i, images.size(), currentScale)); } } /** * isMove?false???????padding????? * * @param allTargetScreenshots ?? * @param targetParams ? */ protected void trimNonMovePadding(List<List<BufferedImage>> allTargetScreenshots, List<Pair<CompareTarget, ScreenshotParams>> targetParams) { LOG.trace("[Trim non-move elements' padding]"); } /** * isMove?true???????padding????? * * @param el ?? * @param images ?? */ protected void trimMovePadding(WebElement el, List<BufferedImage> images) { LOG.trace("[Trim move element's padding]"); } /** * ?????????????????? * * @param captureTop ??? * @param captureLeft ??? * @param windowHeight viewport??? * @param windowWidth viewport?? * @param currentScale ?? * @param img ? * @return ????? */ protected BufferedImage trimOverlap(double captureTop, double captureLeft, long windowHeight, long windowWidth, double currentScale, BufferedImage img) { LOG.trace("(TrimOverlap) image[w: {}, h:{}], top: {}, left: {}, windowWidth: {}, windowHeight: {}", img.getWidth(), img.getHeight(), captureTop, captureLeft, windowWidth, windowHeight); BufferedImage image = img; // ?????????????????? long calculatedRightValue = Math.round((captureLeft + windowWidth) * currentScale); long actualRightValue = Math.round(captureLeft * currentScale) + img.getWidth(); int trimWidth = calculatedRightValue < actualRightValue ? (int) (actualRightValue - calculatedRightValue) : 0; // ???????????????? long calculatedBottomValue = Math.round((captureTop + windowHeight) * currentScale); long actualBottomValue = Math.round(captureTop * currentScale) + img.getHeight(); int trimHeight = calculatedBottomValue < actualBottomValue ? (int) (actualBottomValue - calculatedBottomValue) : 0; // ????????????? LOG.trace("(TrimOverlap) right(calc: {}, actual: {}), bottom(calc: {}, actual: {})", calculatedRightValue, actualRightValue, calculatedBottomValue, actualBottomValue); if (trimWidth > 0 || trimHeight > 0) { image = image.getSubimage(0, 0, image.getWidth() - trimWidth, image.getHeight() - trimHeight); } return image; } /** * ??????{@link TargetResult}??????? * * @param compareTarget ?? * @param hiddenElementSelectors ??????? * @param params ???? * @param additionalParams ??{@code params} * ????????????????????? * @return ? */ protected TargetResult getTargetResult(CompareTarget compareTarget, List<DomSelector> hiddenElementSelectors, ScreenshotParams params, ScreenshotParams... additionalParams) { ScreenshotImage image = getScreenshotImage(params, additionalParams); // TargetResult for target area ScreenAreaResult targetAreaResult = createScreenAreaResult(params.getTarget(), params.getIndex()); // TargetResult for exclude areas List<ScreenAreaResult> excludes = Lists.transform(params.getExcludes(), new Function<ScreenAreaWrapper, ScreenAreaResult>() { @Override public ScreenAreaResult apply(ScreenAreaWrapper input) { return createScreenAreaResult(input, null); } }); return new TargetResult(null, targetAreaResult, excludes, isMoveTargetRequired(params), hiddenElementSelectors, image, compareTarget.getOptions()); } /** * ???????{@link TargetResult}?????? * * @param compareTarget ? * @param hiddenElementSelectors ??????? * @param params ???? * @param image ? * @return {@link TargetResult} */ protected TargetResult getTargetResult(CompareTarget compareTarget, List<DomSelector> hiddenElementSelectors, ScreenshotParams params, ScreenshotImage image) { BufferedImage bi = image.get(); ScreenshotImage targetImage; RectangleArea targetArea = params.getTarget().getArea(); if (targetArea.getX() != 0d || targetArea.getY() != 0d || targetArea.getWidth() != bi.getWidth() || targetArea.getHeight() != bi.getHeight()) { if (targetArea.getWidth() == 0d || targetArea.getHeight() == 0d) { targetImage = new ScreenshotImage(); } else { // Crop image and reset elements' area targetImage = new ScreenshotImage(cropScreenshotImage(bi, params)); } } else { targetImage = image; } // TargetResult for target area ScreenAreaResult targetAreaResult = createScreenAreaResult(params.getTarget(), params.getIndex()); // TargetResult for exclude areas List<ScreenAreaResult> excludes = Lists.transform(params.getExcludes(), new Function<ScreenAreaWrapper, ScreenAreaResult>() { @Override public ScreenAreaResult apply(ScreenAreaWrapper input) { return createScreenAreaResult(input, null); } }); return new TargetResult(null, targetAreaResult, excludes, isMoveTargetRequired(params), hiddenElementSelectors, targetImage, compareTarget.getOptions()); } /** * {@link ScreenAreaWrapper}?{@link ScreenAreaResult}???? * * @param target ??ScreenAreaWrapper * @param index ???? * @return {@link ScreenAreaResult} */ protected ScreenAreaResult createScreenAreaResult(ScreenAreaWrapper target, Integer index) { DomSelector selector = target.getSelector(); // Rectangle if (selector == null) { return new ScreenAreaResult(null, target.getArea(), target.getParent()); } // DOM return new ScreenAreaResult(new IndexDomSelector(selector, index), target.getArea(), target.getParent()); } /** * ??{@link ScreenshotImage}??????? * * @param params * @param additionalParams ??{@code params} * ????????????????????? * @return ?? */ protected ScreenshotImage getScreenshotImage(ScreenshotParams params, ScreenshotParams... additionalParams) { LOG.trace("[GetScreenshotImage start]"); Object documentOverflow = null; // Hide scrollbar if (canHideBodyScrollbar()) { // Backup default overflow value Map<String, Object> object = executeJavaScript(SCRIPT_GET_DEFAULT_DOCUMENT_OVERFLOW); documentOverflow = object.get("overflow"); LOG.trace("[GetScreenshotImage] Hide scrollbar. origin: {}", documentOverflow); executeScript(SCRIPT_SET_DOCUMENT_OVERFLOW, "hidden"); } updateScreenWrapperStatus(0d, 0d, params, additionalParams); params.updateInitialArea(); // Check target element size RectangleArea area = params.getTarget().getArea().floor(); LOG.trace("[GetScreenshotImage] target area size: {}", area); if (area.getWidth() == 0d || area.getHeight() == 0d) { LOG.debug("[GetScreenshotImage] Target element is empty. (target: {}, index: {})", params.getTarget().getParent(), params.getIndex()); if (canHideBodyScrollbar()) { executeScript(SCRIPT_SET_DOCUMENT_OVERFLOW, documentOverflow); } return new ScreenshotImage(); } if (isHideElementsRequired()) { for (PtlWebElement element : params.getHiddenElements()) { element.hide(); } } // Do not move if the target is "body" element. LOG.debug("[GetScreenshotImage (capture start)]"); BufferedImage fullScreenshot; if (isMoveTargetRequired(params)) { fullScreenshot = getScreenshotInternal(params); } else { fullScreenshot = getScreenshotInternalWithoutMoving(params, additionalParams); } LOG.debug("[GetScreenshotImage (capture finished)] w: {}, h: {}", fullScreenshot.getWidth(), fullScreenshot.getHeight()); if (isHideElementsRequired()) { for (PtlWebElement element : params.getHiddenElements()) { element.show(); } } // Restore scrollbar if (canHideBodyScrollbar()) { executeScript(SCRIPT_SET_DOCUMENT_OVERFLOW, documentOverflow); } // Crop screenshot BufferedImage targetImage = cropScreenshotImage(fullScreenshot, params); LOG.trace("[GetScreenshotImage finished]"); // MEMO Driver?????????????????? return new ScreenshotImage(targetImage); } /** * ???????????????? * * @param image ? * @param params * @return ??? */ private BufferedImage cropScreenshotImage(BufferedImage image, ScreenshotParams params) { RectangleArea targetArea = params.getTarget().getArea(); RectangleArea floorTargetArea = targetArea.round(); LOG.debug("[CropScreenshot] image[w: {}, h: {}], target: {} ({})", image.getWidth(), image.getHeight(), floorTargetArea, targetArea); // Don't crop image when the target element is "body" if (params.getTarget().isBody()) { int width = image.getWidth(); if (width < floorTargetArea.getX() + floorTargetArea.getWidth()) { width -= (int) floorTargetArea.getX(); } else { width = (int) floorTargetArea.getWidth(); } int height = image.getHeight(); if (height < floorTargetArea.getY() + floorTargetArea.getHeight()) { height -= (int) floorTargetArea.getY(); } else { height = (int) floorTargetArea.getHeight(); } params.getTarget() .setArea(new RectangleArea(floorTargetArea.getX(), floorTargetArea.getY(), width, height)); LOG.trace("[CropScreenshot] Did not crop image (element is body). Image area: {}", params.getTarget().getArea()); return image; } // (width + x) ? (height + y) ???????? int maxCropWidth = (int) Math.min(floorTargetArea.getX() + floorTargetArea.getWidth(), image.getWidth()); int maxCropHeight = (int) Math.min(floorTargetArea.getY() + floorTargetArea.getHeight(), image.getHeight()); LOG.trace("[CropScreenshot] ({})", new RectangleArea((int) floorTargetArea.getX(), (int) floorTargetArea.getY(), maxCropWidth - (int) floorTargetArea.getX(), maxCropHeight - (int) floorTargetArea.getY())); BufferedImage targetImage = image.getSubimage((int) floorTargetArea.getX(), (int) floorTargetArea.getY(), maxCropWidth - (int) floorTargetArea.getX(), maxCropHeight - (int) floorTargetArea.getY()); double deltaX = -floorTargetArea.getX(); double deltaY = -floorTargetArea.getY(); params.getTarget().setArea(new RectangleArea(0d, 0d, targetImage.getWidth(), targetImage.getHeight())); LOG.trace("[CropScreenshot] new area: {}", params.getTarget().getArea()); LOG.trace("[CropScreenshot] Move excludes. (deltaX: {}, deltaY: {})", deltaX, deltaY); for (ScreenAreaWrapper wrapper : params.getExcludes()) { wrapper.setArea(wrapper.getArea().move(deltaX, deltaY)); } return targetImage; } /** * ???????????? * * @param params * @return ???????true */ protected boolean isMoveTargetRequired(ScreenshotParams params) { return params.isMoveTarget() && !params.getTarget().isBody() && canMoveTarget(); } /** * ?????????? * * @param params * @return ???????true */ protected boolean isScrollTargetRequired(ScreenshotParams params) { return params.isScrollTarget() && !params.getTarget().isBody(); } /** * ?????? * * @param params * @param additionalParams * @return ?? */ protected BufferedImage getScreenshotInternalWithoutMoving(ScreenshotParams params, ScreenshotParams... additionalParams) { BufferedImage image = getMinimumScreenshot(params); updateScreenWrapperStatus(0d, 0d, params, additionalParams); return image; } /** * ?0, 0)?????? * * @param params * @param additionalParams * @return ?? */ protected BufferedImage getScreenshotInternal(ScreenshotParams params, ScreenshotParams... additionalParams) { LOG.debug("[GetMoveScreenshot] target: {}; index: {}", params.getTarget().getParent(), params.getIndex()); // Backup default body style values Map<String, Object> originalStyle = executeJavaScript(SCRIPT_GET_DEFAULT_BODY_STYLE); LOG.trace("[GetMoveScreenshot] Original style: {}", originalStyle); // Set body width executeScript("document.body.style.width = arguments[0]", originalStyle.get("scrollWidth") + "px"); executeScript(SCRIPT_MOVE_BODY, "absolute", "", ""); // Get target element position ScreenAreaWrapper target = params.getTarget(); target.updatePosition(getScreenshotScale()); RectangleArea moveAmount = target.getArea(); // Move body position LOG.debug("[GetMoveScreenshot] Move body (amount = x: {}, y: {})", -moveAmount.getX(), -moveAmount.getY()); executeScript(SCRIPT_MOVE_BODY, "absolute", String.format("%spx", -moveAmount.getY()), String.format("%spx", -moveAmount.getX())); BufferedImage image = getMinimumScreenshot(params); updateScreenWrapperStatus(moveAmount.getX(), moveAmount.getY(), params, additionalParams); // Restore body width String width = originalStyle.get("width") == null ? "" : originalStyle.get("width").toString(); executeScript("document.body.style.width = arguments[0]", width); // Restore body position String pos = originalStyle.get("position") == null ? "" : originalStyle.get("position").toString(); String top = originalStyle.get("top") == null ? "" : originalStyle.get("top").toString(); String left = originalStyle.get("left") == null ? "" : originalStyle.get("left").toString(); LOG.debug("[GetMoveScreenshot] Restore body position. (width: {}, position: {}, top: {}, left: {})", width, pos, top, left); executeScript(SCRIPT_MOVE_BODY, pos, top, left); return image; } /** * {@link DomSelector}????????? * * @param selectors ????? * @return ????? */ protected List<PtlWebElement> findElementsByDomSelectors(List<DomSelector> selectors) { List<PtlWebElement> elements = new ArrayList<PtlWebElement>(); if (selectors == null || selectors.isEmpty()) { return elements; } for (DomSelector selector : selectors) { for (WebElement element : selector.getType().findElements(this, selector.getValue())) { elements.add((PtlWebElement) element); } } return elements; } /** * {@code params}?{@code additionalParams}????????? * * @param moveX x??? * @param moveY y??? * @param params ? * @param additionalParams ? */ private void updateScreenWrapperStatus(double moveX, double moveY, ScreenshotParams params, ScreenshotParams... additionalParams) { // Scroll to top try { scrollTo(0d, 0d); } catch (InterruptedException e) { throw new TestRuntimeException(e); } double currentScale = getScreenshotScale(); LOG.debug("(UpdateScreenWrapperStatus) moveX: {}, moveY: {}, scale: {}", moveX, moveY, currentScale); updateScreenWrapperStatus(moveX, moveY, currentScale, params); for (ScreenshotParams p : additionalParams) { updateScreenWrapperStatus(moveX, moveY, currentScale, p); } } /** * {@code params}?{@code additionalParams}????????? * * @param moveX x??? * @param moveY y??? * @param currentScale * @param params ? */ private void updateScreenWrapperStatus(double moveX, double moveY, double currentScale, ScreenshotParams params) { LOG.trace("(UpdateScreenWrapperStatus) moveX: {}, moveY: {}, scale: {} => {}", moveX, moveY, currentScale, params.getTarget().getParent()); params.getTarget().updatePosition(currentScale, moveX, moveY); for (ScreenAreaWrapper wrapper : params.getExcludes()) { wrapper.updatePosition(currentScale, moveX, moveY); } } /** * ?viewport????? * * @return PC???1.0 */ protected double getScreenshotScale() { return DEFAULT_SCREENSHOT_SCALE; } /** * ????{@link BufferedImage}?????? * * @return ?? */ public BufferedImage getEntirePageScreenshot() { return getScreenshotAsBufferedImage(); } /** * ?????????{@link BufferedImage}??????<br> * * @param params * @return ?? */ protected BufferedImage getMinimumScreenshot(ScreenshotParams params) { return getEntirePageScreenshot(); } /** * ??{@link BufferedImage}??????? * * @return ?? */ protected final BufferedImage getScreenshotAsBufferedImage() { try { byte[] data = getScreenshotAs(OutputType.BYTES); return ImageIO.read(new ByteArrayInputStream(data)); } catch (IOException e) { throw new TestRuntimeException("Screenshot capture error", e); } } //</editor-fold> //<editor-fold desc="getWidth/Height"> /** * ??????? * * @return ?ypx */ public double getCurrentScrollTop() { double max = 0d; for (String value : SCRIPTS_SCROLL_TOP) { try { double current = Double.parseDouble(executeScript("return " + value).toString()); max = Math.max(max, current); } catch (Exception e) { LOG.debug("(GetCurrentScrollTop) unexpected error", e); } } LOG.trace("(GetCurrentScrollTop) [{}]", max); return max; } /** * ??????? * * @return ?xpx */ public double getCurrentScrollLeft() { double max = 0d; for (String value : SCRIPTS_SCROLL_LEFT) { try { double current = Double.parseDouble(executeScript("return " + value).toString()); max = Math.max(max, current); } catch (Exception e) { LOG.debug("(GetCurrentScrollLeft) unexpected error", e); } } LOG.trace("(GetCurrentScrollLeft) [{}]", max); return max; } /** * ?????? * * @return ??px */ public long getWindowWidth() { return executeJavaScript(GET_WINDOW_WIDTH_SCRIPT); } /** * ??????? * * @return ???px */ public long getWindowHeight() { return executeJavaScript(GET_WINDOW_HEIGHT_SCRIPT); } /** * ?scrollWidth???? * * @return scrollWidthpx */ public long getScrollWidth() { PtlWebElement bodyElement = (PtlWebElement) findElementByTagName("body"); return executeJavaScript(GET_SCROLL_WIDTH_SCRIPT, bodyElement); } /** * ?scrollHeight???? * * @return scrollHeightpx */ public long getScrollHeight() { PtlWebElement bodyElement = (PtlWebElement) findElementByTagName("body"); return executeJavaScript(GET_SCROLL_HEIGHT_SCRIPT, bodyElement); } /** * ????? * * @return */ public long getScrollNum() { double clientHeight = getWindowHeight(); double scrollHeight = getScrollHeight() + 1; if (clientHeight >= scrollHeight) { return 0; } return (int) (Math.ceil(scrollHeight / clientHeight)) - 1; } /** * ??????? * * @return ?px */ public long getCurrentPageWidth() { // ???? double scrollTop = getCurrentScrollTop(); double scrollLeft = getCurrentScrollLeft(); double pageWidth = 0; try { // body?? PtlWebElement bodyElement = (PtlWebElement) findElementByTagName("body"); scrollTo(0d, 0d); Number bodyLeft = executeJavaScript(GET_BODY_LEFT_SCRIPT, bodyElement); // ??????body?? long scrollWidth = getScrollWidth(); WebElementMargin margin = bodyElement.getMargin(); // ? + ???+1??? double totalWidth = scrollWidth + margin.getLeft() + margin.getRight() + 1; scrollTo(totalWidth, 0d); Number relativeBodyLeft = executeJavaScript(GET_BODY_LEFT_SCRIPT, bodyElement); // left?????? LOG.trace("(GetCurrentPageWidth) relativeBodyLeft: {}, bodyLeft: {}, margin: {}", relativeBodyLeft, bodyLeft, margin.getLeft()); pageWidth = -relativeBodyLeft.doubleValue() + bodyLeft.doubleValue() + getWindowWidth(); // ??? scrollTo(scrollLeft, scrollTop); } catch (InterruptedException e) { throw new TestRuntimeException(e); } long result = Math.round(pageWidth); LOG.trace("(GetCurrentPageWidth) [{}] ({})", result, pageWidth); return result; } /** * ???????? * * @return ??px */ public long getCurrentPageHeight() { // ???? double scrollTop = getCurrentScrollTop(); double scrollLeft = getCurrentScrollLeft(); double pageHeight = 0; try { // body?? PtlWebElement bodyElement = (PtlWebElement) findElementByTagName("body"); scrollTo(0d, 0d); Number bodyTop = executeJavaScript(GET_BODY_TOP_SCRIPT, bodyElement); // ?????body?? long scrollHeight = getScrollHeight(); WebElementMargin margin = bodyElement.getMargin(); // ?? + ???+1??? double totalHeight = scrollHeight + margin.getTop() + margin.getBottom() + 1; scrollTo(0d, totalHeight); Number relativeBodyTop = executeJavaScript(GET_BODY_TOP_SCRIPT, bodyElement); // top??????? LOG.trace("(GetCurrentPageHeight) relativeBodyTop: {}, bodyTop: {}, margin: {}", relativeBodyTop, bodyTop, margin.getTop()); pageHeight = -relativeBodyTop.doubleValue() + bodyTop.doubleValue() + getWindowHeight(); // ??? scrollTo(scrollLeft, scrollTop); } catch (InterruptedException e) { throw new TestRuntimeException(e); } long result = Math.round(pageHeight); LOG.trace("(GetCurrentPageHeight) [{}] ({})", result, pageHeight); return result; } /** * ???????? * * @param x ?xpx * @param y ?ypx * @throws InterruptedException ????? */ public void scrollTo(double x, double y) throws InterruptedException { executeScript("window.scrollTo(arguments[0], arguments[1])", x, y); Thread.sleep(SCROLL_WAIT_MS); } /** * viewport?????? * * @param windowWidth viewport?? * @param imageWidth ? * @return PC???1 */ protected double calcScale(double windowWidth, double imageWidth) { return DEFAULT_SCREENSHOT_SCALE; } /** * ?????????????? * * @param imageHeight ??? * @param scrollAmount ?? * @param targetElement * @param currentScale ?? * @return trim? */ protected int calcTrimTop(int imageHeight, long scrollAmount, PtlWebElement targetElement, double currentScale) { int borderWidth = 0; if (!targetElement.isBody()) { WebElementBorderWidth border = targetElement.getBorderWidth(); borderWidth = (int) Math.round(border.getTop()); } int trimTop = imageHeight - (int) Math.round(scrollAmount * currentScale) - (int) Math.round(borderWidth * currentScale); LOG.trace("(CalcTrimTop) imageHeight: {}, scrollAmount: {}, element: {} => {}", imageHeight, scrollAmount, targetElement, trimTop); return trimTop; } /** * ????????????? * * @param imageWidth ?? * @param scrollAmount ?? * @param targetElement null??? * @param currentScale ?? * @return trim? */ protected int calcTrimLeft(int imageWidth, long scrollAmount, PtlWebElement targetElement, double currentScale) { int borderWidth = 0; if (!targetElement.isBody()) { WebElementBorderWidth border = targetElement.getBorderWidth(); borderWidth = (int) Math.round(border.getLeft()); } int trimLeft = imageWidth - (int) Math.round(scrollAmount * currentScale) - (int) Math.round(borderWidth * currentScale); return trimLeft; } /** * driver??WebElement???? * * @return WebElement */ protected abstract PtlWebElement newPtlWebElement(); //</editor-fold> //<editor-fold desc="JsonToPtlWebElementConverter"> /** * JSONWebElement??? */ static class JsonToPtlWebElementConverter extends JsonToWebElementConverter { private final PtlWebDriver driver; /** * * * @param driver WebDriver */ JsonToPtlWebElementConverter(PtlWebDriver driver) { super(driver); this.driver = driver; } @Override protected PtlWebElement newRemoteWebElement() { PtlWebElement element = driver.newPtlWebElement(); element.setParent(driver); return element; } } //</editor-fold> }