2017-07-19 22 views
7

とChromeのフルページのスクリーンショットを取り、今は次のアップデートで:私はこれが<a href="https://bugs.chromium.org/p/chromedriver/issues/detail?id=294" rel="noreferrer">before</a>ことはできませんでした知っているセレン

https://developers.google.com/web/updates/2017/04/devtools-release-notes#screenshots

これはクロムのdevのツールを使用して可能であるように思われます。

現在、Javaでセレンを使用することはできますか?

+0

AFAIKこれは 'セレン' + 'PhantomJS'でも可能です。 – Andersson

+2

はい、Chromeのドライバが必要です –

+0

Javaを使用している場合、誰かがこのようなことをする良いライブラリを作成したようです。おそらくこれを試してみてください。https://github.com/yandex-qatools/ashot/ – stewartm

答えて

2

これを行うにはJavaのSelenium Webdriverは少しの作業を必要とします。Florent B.が暗示しているように、デフォルトのChromeDriverがこの作業を行うために使用するクラスをいくつか変更する必要があります。私たちは、この事を使用する新しいChromeDriverクラスを作成する必要があります。その後

import com.google.common.collect.ImmutableMap; 
import org.openqa.selenium.remote.CommandInfo; 
import org.openqa.selenium.remote.http.HttpMethod; 
import org.openqa.selenium.remote.service.DriverCommandExecutor; 
import org.openqa.selenium.remote.service.DriverService; 

public class MyChromeDriverCommandExecutor extends DriverCommandExecutor { 
    private static final ImmutableMap<String, CommandInfo> CHROME_COMMAND_NAME_TO_URL; 

    public MyChromeDriverCommandExecutor(DriverService service) { 
     super(service, CHROME_COMMAND_NAME_TO_URL); 
    } 

    static { 
     CHROME_COMMAND_NAME_TO_URL = ImmutableMap.of("launchApp", new CommandInfo("/session/:sessionId/chromium/launch_app", HttpMethod.POST) 
     , "sendCommandWithResult", new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST) 
     ); 
    } 
} 

:まず、新しいクロームコマンドを追加する新しいDriverCommandExecutorを作成する必要があります。私たちは、元のは、私たちがコマンド実行を置き換えることができますコンストラクタを持っていないため、クラスを作成する必要があります...だから新しいクラスは次のようになります。

import com.google.common.collect.ImmutableMap; 
import org.openqa.selenium.Capabilities; 
import org.openqa.selenium.WebDriverException; 
import org.openqa.selenium.chrome.ChromeDriverService; 
import org.openqa.selenium.html5.LocalStorage; 
import org.openqa.selenium.html5.Location; 
import org.openqa.selenium.html5.LocationContext; 
import org.openqa.selenium.html5.SessionStorage; 
import org.openqa.selenium.html5.WebStorage; 
import org.openqa.selenium.interactions.HasTouchScreen; 
import org.openqa.selenium.interactions.TouchScreen; 
import org.openqa.selenium.mobile.NetworkConnection; 
import org.openqa.selenium.remote.FileDetector; 
import org.openqa.selenium.remote.RemoteTouchScreen; 
import org.openqa.selenium.remote.RemoteWebDriver; 
import org.openqa.selenium.remote.html5.RemoteLocationContext; 
import org.openqa.selenium.remote.html5.RemoteWebStorage; 
import org.openqa.selenium.remote.mobile.RemoteNetworkConnection; 

public class MyChromeDriver extends RemoteWebDriver implements LocationContext, WebStorage, HasTouchScreen, NetworkConnection { 
    private RemoteLocationContext locationContext; 
    private RemoteWebStorage webStorage; 
    private TouchScreen touchScreen; 
    private RemoteNetworkConnection networkConnection; 

    //public MyChromeDriver() { 
    // this(ChromeDriverService.createDefaultService(), new ChromeOptions()); 
    //} 
    // 
    //public MyChromeDriver(ChromeDriverService service) { 
    // this(service, new ChromeOptions()); 
    //} 

    public MyChromeDriver(Capabilities capabilities) { 
     this(ChromeDriverService.createDefaultService(), capabilities); 
    } 

    //public MyChromeDriver(ChromeOptions options) { 
    // this(ChromeDriverService.createDefaultService(), options); 
    //} 

    public MyChromeDriver(ChromeDriverService service, Capabilities capabilities) { 
     super(new MyChromeDriverCommandExecutor(service), capabilities); 
     this.locationContext = new RemoteLocationContext(this.getExecuteMethod()); 
     this.webStorage = new RemoteWebStorage(this.getExecuteMethod()); 
     this.touchScreen = new RemoteTouchScreen(this.getExecuteMethod()); 
     this.networkConnection = new RemoteNetworkConnection(this.getExecuteMethod()); 
    } 

    @Override 
    public void setFileDetector(FileDetector detector) { 
     throw new WebDriverException("Setting the file detector only works on remote webdriver instances obtained via RemoteWebDriver"); 
    } 

    @Override 
    public LocalStorage getLocalStorage() { 
     return this.webStorage.getLocalStorage(); 
    } 

    @Override 
    public SessionStorage getSessionStorage() { 
     return this.webStorage.getSessionStorage(); 
    } 

    @Override 
    public Location location() { 
     return this.locationContext.location(); 
    } 

    @Override 
    public void setLocation(Location location) { 
     this.locationContext.setLocation(location); 
    } 

    @Override 
    public TouchScreen getTouch() { 
     return this.touchScreen; 
    } 

    @Override 
    public ConnectionType getNetworkConnection() { 
     return this.networkConnection.getNetworkConnection(); 
    } 

    @Override 
    public ConnectionType setNetworkConnection(ConnectionType type) { 
     return this.networkConnection.setNetworkConnection(type); 
    } 

    public void launchApp(String id) { 
     this.execute("launchApp", ImmutableMap.of("id", id)); 
    } 
} 

これは、元のクラスのコピーがほとんどですが、いくつかのコンストラクタが無効になっています(なぜなら、必要なコードの一部がパッケージプライベートであるからです)。これらのコンストラクタが必要な場合は、クラスorg.openqa.selenium.chromeにクラスを配置する必要があります。フロランBで示したように、あなたが、必要なコードを呼び出すことができますこれらの変更により

が、今セレンAPIとJavaで:

import com.google.common.collect.ImmutableMap; 
import org.openqa.selenium.remote.Command; 
import org.openqa.selenium.remote.Response; 

import javax.annotation.Nonnull; 
import javax.annotation.Nullable; 
import javax.imageio.ImageIO; 
import java.awt.image.BufferedImage; 
import java.io.ByteArrayInputStream; 
import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.util.Base64; 
import java.util.HashMap; 
import java.util.Map; 

public class ChromeExtender { 
    @Nonnull 
    private MyChromeDriver m_wd; 

    public ChromeExtender(@Nonnull MyChromeDriver wd) { 
     m_wd = wd; 
    } 

    public void takeScreenshot(@Nonnull File output) throws Exception { 
     Object visibleSize = evaluate("({x:0,y:0,width:window.innerWidth,height:window.innerHeight})"); 
     Long visibleW = jsonValue(visibleSize, "result.value.width", Long.class); 
     Long visibleH = jsonValue(visibleSize, "result.value.height", Long.class); 

     Object contentSize = send("Page.getLayoutMetrics", new HashMap<>()); 
     Long cw = jsonValue(contentSize, "contentSize.width", Long.class); 
     Long ch = jsonValue(contentSize, "contentSize.height", Long.class); 

     /* 
     * In chrome 61, delivered one day after I wrote this comment, the method forceViewport was removed. 
     * I commented it out here with the if(false), and hopefully wrote a working alternative in the else 8-/ 
     */ 
     if(false) { 
      send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch)); 
      send("Emulation.forceViewport", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "scale", Long.valueOf(1))); 
     } else { 
      send("Emulation.setDeviceMetricsOverride", 
       ImmutableMap.of("width", cw, "height", ch, "deviceScaleFactor", Long.valueOf(1), "mobile", Boolean.FALSE, "fitWindow", Boolean.FALSE) 
      ); 
      send("Emulation.setVisibleSize", ImmutableMap.of("width", cw, "height", ch)); 
     } 

     Object value = send("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", Boolean.TRUE)); 

     // Since chrome 61 this call has disappeared too; it does not seem to be necessary anymore with the new code. 
     // send("Emulation.resetViewport", ImmutableMap.of()); 
     send("Emulation.setVisibleSize", ImmutableMap.of("x", Long.valueOf(0), "y", Long.valueOf(0), "width", visibleW, "height", visibleH)); 

     String image = jsonValue(value, "data", String.class); 
     byte[] bytes = Base64.getDecoder().decode(image); 

     try(FileOutputStream fos = new FileOutputStream(output)) { 
      fos.write(bytes); 
     } 
    } 

    @Nonnull 
    private Object evaluate(@Nonnull String script) throws IOException { 
     Map<String, Object> param = new HashMap<>(); 
     param.put("returnByValue", Boolean.TRUE); 
     param.put("expression", script); 

     return send("Runtime.evaluate", param); 
    } 

    @Nonnull 
    private Object send(@Nonnull String cmd, @Nonnull Map<String, Object> params) throws IOException { 
     Map<String, Object> exe = ImmutableMap.of("cmd", cmd, "params", params); 
     Command xc = new Command(m_wd.getSessionId(), "sendCommandWithResult", exe); 
     Response response = m_wd.getCommandExecutor().execute(xc); 

     Object value = response.getValue(); 
     if(response.getStatus() == null || response.getStatus().intValue() != 0) { 
      //System.out.println("resp: " + response); 
      throw new MyChromeDriverException("Command '" + cmd + "' failed: " + value); 
     } 
     if(null == value) 
      throw new MyChromeDriverException("Null response value to command '" + cmd + "'"); 
     //System.out.println("resp: " + value); 
     return value; 
    } 

    @Nullable 
    static private <T> T jsonValue(@Nonnull Object map, @Nonnull String path, @Nonnull Class<T> type) { 
     String[] segs = path.split("\\."); 
     Object current = map; 
     for(String name: segs) { 
      Map<String, Object> cm = (Map<String, Object>) current; 
      Object o = cm.get(name); 
      if(null == o) 
       return null; 
      current = o; 
     } 
     return (T) current; 
    } 
} 

これは、指定されたとおり、あなたがコマンドを使用することができますし、作成しますその内部にpng形式の画像を持つファイル。もちろん、ImageIO.read()をバイトで使用してBufferedImageを直接作成することもできます。

+0

素晴らしい仕事!!! ChromeExtenderクラスのインポートを追加できますか?私はそれらのうちのいくつかを推測しました。 – SiKing

+0

こんにちは@SiKing、私はインポートを追加しました。しかし、私が今日入手したChrome 61は、元の例で使用されていたforceViewport呼び出しを削除します。私はtakeScreenshot()のコードで私のために働くように思われる代替案を追加しました。 – fjalvingh

+0

Emulation.resetViewport "がクロム61で機能していないようです(クロム60でOKでした)。 –

10

はい、Chrome v59以降、Seleniumでフルページのスクリーンショットを撮ることができます。クロームドライバが直接デベロッパーツールAPIを呼び出すために2つの新しいエンドポイントを持っています

/session/:sessionId/chromium/send_command_and_get_result 
/session/:sessionId/chromium/send_command 

あなたは根本的な執行に直接それらを送信する必要がありますので、セレンのAPIは、これらのコマンドを実装していません。それは簡単ではありませんが、DevToolsとまったく同じ結果を出すことは可能です。

ここでのpythonは、ローカルまたはリモートインスタンス上で作業して例を示します

from selenium import webdriver 
import json, base64 

capabilities = { 
    'browserName': 'chrome', 
    'chromeOptions': { 
    'useAutomationExtension': False, 
    'args': ['--disable-infobars'] 
    } 
} 

driver = webdriver.Chrome(desired_capabilities=capabilities) 
driver.get("https://stackoverflow.com/questions") 

png = chrome_takeFullScreenshot(driver) 

with open(r"C:\downloads\screenshot.png", 'wb') as f: 
    f.write(png) 

、および全ページのスクリーンショット取るためのコード:Javaを使用し

def chrome_takeFullScreenshot(driver) : 

    def send(cmd, params): 
    resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id 
    url = driver.command_executor._url + resource 
    body = json.dumps({'cmd':cmd, 'params': params}) 
    response = driver.command_executor._request('POST', url, body) 
    return response.get('value') 

    def evaluate(script): 
    response = send('Runtime.evaluate', {'returnByValue': True, 'expression': script}) 
    return response['result']['value'] 

    metrics = evaluate(\ 
    "({" + \ 
     "width: Math.max(window.innerWidth, document.body.scrollWidth, document.documentElement.scrollWidth)|0," + \ 
     "height: Math.max(innerHeight, document.body.scrollHeight, document.documentElement.scrollHeight)|0," + \ 
     "deviceScaleFactor: window.devicePixelRatio || 1," + \ 
     "mobile: typeof window.orientation !== 'undefined'" + \ 
    "})") 
    send('Emulation.setDeviceMetricsOverride', metrics) 
    screenshot = send('Page.captureScreenshot', {'format': 'png', 'fromSurface': True}) 
    send('Emulation.clearDeviceMetricsOverride', {}) 

    return base64.b64decode(screenshot['data']) 

public static void main(String[] args) throws Exception { 

    ChromeOptions options = new ChromeOptions(); 
    options.setExperimentalOption("useAutomationExtension", false); 
    options.addArguments("disable-infobars"); 

    ChromeDriverEx driver = new ChromeDriverEx(options); 

    driver.get("https://stackoverflow.com/questions"); 
    File file = driver.getFullScreenshotAs(OutputType.FILE); 
} 
import java.lang.reflect.Method; 
import java.util.Map; 
import com.google.common.collect.ImmutableMap; 
import org.openqa.selenium.OutputType; 
import org.openqa.selenium.chrome.ChromeDriver; 
import org.openqa.selenium.chrome.ChromeDriverService; 
import org.openqa.selenium.chrome.ChromeOptions; 
import org.openqa.selenium.remote.CommandInfo; 
import org.openqa.selenium.remote.HttpCommandExecutor; 
import org.openqa.selenium.remote.http.HttpMethod; 


public class ChromeDriverEx extends ChromeDriver { 

    public ChromeDriverEx() throws Exception { 
     this(new ChromeOptions()); 
    } 

    public ChromeDriverEx(ChromeOptions options) throws Exception { 
     this(ChromeDriverService.createDefaultService(), options); 
    } 

    public ChromeDriverEx(ChromeDriverService service, ChromeOptions options) throws Exception { 
     super(service, options); 
     CommandInfo cmd = new CommandInfo("/session/:sessionId/chromium/send_command_and_get_result", HttpMethod.POST); 
     Method defineCommand = HttpCommandExecutor.class.getDeclaredMethod("defineCommand", String.class, CommandInfo.class); 
     defineCommand.setAccessible(true); 
     defineCommand.invoke(super.getCommandExecutor(), "sendCommand", cmd); 
    } 

    public <X> X getFullScreenshotAs(OutputType<X> outputType) throws Exception { 
     Object metrics = sendEvaluate(
      "({" + 
      "width: Math.max(window.innerWidth,document.body.scrollWidth,document.documentElement.scrollWidth)|0," + 
      "height: Math.max(window.innerHeight,document.body.scrollHeight,document.documentElement.scrollHeight)|0," + 
      "deviceScaleFactor: window.devicePixelRatio || 1," + 
      "mobile: typeof window.orientation !== 'undefined'" + 
      "})"); 
     sendCommand("Emulation.setDeviceMetricsOverride", metrics); 
     Object result = sendCommand("Page.captureScreenshot", ImmutableMap.of("format", "png", "fromSurface", true)); 
     sendCommand("Emulation.clearDeviceMetricsOverride", ImmutableMap.of()); 
     String base64EncodedPng = (String)((Map<String, ?>)result).get("data"); 
     return outputType.convertFromBase64Png(base64EncodedPng); 
    } 

    protected Object sendCommand(String cmd, Object params) { 
     return execute("sendCommand", ImmutableMap.of("cmd", cmd, "params", params)).getValue(); 
    } 

    protected Object sendEvaluate(String script) { 
     Object response = sendCommand("Runtime.evaluate", ImmutableMap.of("returnByValue", true, "expression", script)); 
     Object result = ((Map<String, ?>)response).get("result"); 
     return ((Map<String, ?>)result).get("value"); 
    } 
} 
+1

どのようにJavaでそれを行うためのヒント? –

+2

'HttpCommandExecutor'を拡張し、新しいエンドポイントを使うために' defineCommand'を呼び出してみてください。拡張された 'HttpCommandExecutor'で新しい RemoteWebDriverをインスタンス化します。 –

+1

https://github.com/SeleniumHQ/selenium/blob/master/java/client/src/org/openqa/selenium/chrome/ChromeDriverCommandExecutor.java –

関連する問題

 関連する問題