2017-07-19 37 views
7

我知道這是不可能的before,但現在有以下更新:以整版的屏幕截圖在Chrome與硒

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

這似乎是使用Chrome瀏覽器開發工具成爲可能。

現在可以在java中使用硒了嗎?

+0

AFAIK這是可能的在'硒'+'PhantomJS' – Andersson

+2

是的,但我需要Chrome驅動程序 –

+0

如果您使用Java,它看起來像有人已經創建了一個很好的庫來爲你做這種事情。也許給這個嘗試https://github.com/yandex-qatools/ashot/ – stewartm

回答

2

要做到這一點在Java中使用Selenium Webdriver需要一些工作。正如Florent B.所暗示的,我們需要改變默認ChromeDriver使用的一些類來完成這項工作。首先我們需要一個新的DriverCommandExecutor它增加了新的Chrome命令:

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) 
     ); 
    } 
} 

之後,我們需要創建一個新的ChromeDriver類,那麼將用這個東西。我們需要創建一個類,因爲原來沒有構造,讓我們代替命令執行......因此,新的階級變爲:

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所示,但現在在Java中與硒API:

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

Hi @SiKing,我添加了導入。但請注意,今天我得到的Chrome 61將刪除原始示例中使用的forceViewport調用。我添加了一個替代方案,它似乎適用於takeScreenshot()的代碼。 – fjalvingh

+0

Emulation.resetViewport」似乎並沒有在Chrome 61(是在鉻60 OK)。工作是否有其他選擇嗎? –

10

是的,它有可能從Chrome v59開始就採用Selenium的全屏截圖。該瀏覽器的驅動程序有兩個新的端點直接調用API DevTools:

/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) 

,和代碼,以一個完整的頁面截圖:

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']) 

與Java:

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 –