Selenium能够与现有的浏览器会话进行交互吗?

179

请问有人知道 Selenium(最好是 WebDriver)是否能够与已经运行的浏览器通信并进行操作吗?

我的意思是,Selenium是否能够在不使用Selenium服务器(例如手动启动的Internet Explorer)的情况下与浏览器通信。

15个回答

92

这是一个重复的答案

重新连接Python Selenium中的驱动程序。适用于所有驱动程序和Java API。

  1. 打开一个驱动程序
driver = webdriver.Firefox()  #python

从驱动对象中提取session_id和_url。
url = driver.command_executor._url       #"http://127.0.0.1:60622/hub"
session_id = driver.session_id            #'4e167f26-dc1d-4f51-a207-f761eaf73c31'

使用这两个参数连接到您的驱动程序。
driver = webdriver.Remote(command_executor=url,desired_capabilities={})
driver.close()   # this prevents the dummy browser
driver.session_id = session_id

现在您已经重新连接到您的驱动程序。

driver.get("http://www.mrsmart.in")

8
除了每次会弹出一个重复的虚拟浏览器之外,它对我很有效。 - Pavel Vlasov
2
如果您需要关闭虚拟浏览器窗口,在更新会话 ID 之前只需调用 driver.close() 即可。 - Amr Awad
14
selenium.common.exceptions.SessionNotCreatedException: Message: Session is already started 的翻译是:Selenium常见异常错误:会话已经启动。 - Cerin
2
我尝试了这个解决方案,但它不起作用,我收到一个错误信息,指出selenium.common.exceptions.SessionNotCreatedException:Message: Session is already started。 - Vishwanath Heddoori
1
我不理解这个答案——如果我打开了多个Chrome浏览器,Selenium怎么知道要连接哪一个? - Moran Reznik
显示剩余4条评论

53

非常感谢,因为在那个链接中我找到了一个可以实现这个功能的类,但不幸的是我不能在IE中使用这个解决方案(只能在Firefox中使用)。我将启动一个常规的IEDriver,并使用中间件从其他进程与其通信。如果您知道为什么该类在IE上无法工作,我将不胜感激。谢谢。 - Angel Romero
罗伯特,现在已经是2018年了。你能否更新一下你的回答? - MasterJoe
如果有人需要的话,我已经尝试并测试了一些Java代码,以使Selenium使用现有的浏览器会话-https://dev59.com/xmsy5IYBdhLWcg3wvgn7#51145789。 - MasterJoe
@AngelRomero 选择一个新答案怎么样?自从提出这个问题以来,许多功能已经被开发出来。 - Mugen
以防万一有人先找到这篇文章,它是不起作用的。但是这个对我起了作用:https://dev59.com/qKzla4cB1Zd3GeqPA7Q1 - Frank Buss

48

这段代码成功地允许重用现有的浏览器实例,并避免引发重复的浏览器。来源于Tarun Lalwani博客。

from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver

# executor_url = driver.command_executor._url
# session_id = driver.session_id

def attach_to_session(executor_url, session_id):
    original_execute = WebDriver.execute
    def new_command_execute(self, command, params=None):
        if command == "newSession":
            # Mock the response
            return {'success': 0, 'value': None, 'sessionId': session_id}
        else:
            return original_execute(self, command, params)
    # Patch the function before creating the driver object
    WebDriver.execute = new_command_execute
    driver = webdriver.Remote(command_executor=executor_url, desired_capabilities={})
    driver.session_id = session_id
    # Replace the patched function with original function
    WebDriver.execute = original_execute
    return driver

bro = attach_to_session('http://127.0.0.1:64092', '8de24f3bfbec01ba0d82a7946df1d1c3')
bro.get('http://ya.ru/')

3
有没有一种方法可以通过自动化程序找到现有的会话 ID 和执行器 URL? 在我的情况下,另一个应用程序打开了一个浏览器会话,我想使用它。您能否推荐如何查找该浏览器会话的会话 ID? - Sun Shine
1
@TayyabNasir,请注意上面的答案。第五行被注释掉的# session_id = driver.session_id是使用Python Selenium API检索Chrome窗口的会话ID的方法。我猜每个Chrome会话中的选项卡没有唯一的ID。 - S.K. Venkat
17
@S.K. 我想要手动打开的 Chrome 窗口的会话 ID,我没有使用 Selenium 打开该窗口。 - Tayyab Nasir
2
driver.service.service_url 也适用于URL,并且不需要访问受保护的字段。 - Spencer Connaughton
这个运行得非常出色! - Kira
显示剩余2条评论

17

这里了解到,如果浏览器是手动打开的,则可以使用远程调试:

  1. Start chrome with

    chrome --remote-debugging-port=9222
    

或使用可选配置文件

chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenium\ChromeProfile"
  1. 接着: Java:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
    
//Change chrome driver path accordingly
System.setProperty("webdriver.chrome.driver", "C:\\selenium\\chromedriver.exe");
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("debuggerAddress", "127.0.0.1:9222");
WebDriver driver = new ChromeDriver(options);
System.out.println(driver.getTitle());

Python:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
   
chrome_options = Options()
chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
#Change chrome driver path accordingly
chrome_driver = "C:\chromedriver.exe"
driver = webdriver.Chrome(chrome_driver, chrome_options=chrome_options)
print driver.title

3
被低估了!解决方法简单易行。我最初遇到的问题是Selenium打开了另一个浏览器窗口。我使用了驱动程序管理器代替chrome_driver,但忘记使用chrome_options的第二个参数。 - Ashark
1
Ashark,你能提供更多关于你是如何做到的信息吗? - vlatko606

14

是可以的。但你需要做一些“黑科技”,这里有一段代码。 你需要运行一个独立的服务器并对 RemoteWebDriver 进行“打补丁”。

public class CustomRemoteWebDriver : RemoteWebDriver
{
    public static bool newSession;
    public static string capPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "tmp", "sessionCap");
    public static string sessiodIdPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles", "tmp", "sessionid");

    public CustomRemoteWebDriver(Uri remoteAddress) 
        : base(remoteAddress, new DesiredCapabilities())
    {
    }

    protected override Response Execute(DriverCommand driverCommandToExecute, Dictionary<string, object> parameters)
    {
        if (driverCommandToExecute == DriverCommand.NewSession)
        {
            if (!newSession)
            {
                var capText = File.ReadAllText(capPath);
                var sidText = File.ReadAllText(sessiodIdPath);

                var cap = JsonConvert.DeserializeObject<Dictionary<string, object>>(capText);
                return new Response
                {
                    SessionId = sidText,
                    Value = cap
                };
            }
            else
            {
                var response = base.Execute(driverCommandToExecute, parameters);
                var dictionary = (Dictionary<string, object>) response.Value;
                File.WriteAllText(capPath, JsonConvert.SerializeObject(dictionary));
                File.WriteAllText(sessiodIdPath, response.SessionId);
                return response;
            }
        }
        else
        {
            var response = base.Execute(driverCommandToExecute, parameters);
            return response;
        }
    }
}

6
根据这个优秀的解决方案,我写了一篇完整的博客文章,讨论如何连接到已经打开的 Chrome 浏览器实例。博客文章中还附有完整的源代码。http://binaryclips.com/2015/08/25/selenium-webdriver-in-c-how-to-use-the-existing-window-of-chrome-browser/ - joinsaad
基于这个优秀的解决方案,这是适用于Python 3的版本:https://github.com/kassi-blk/webdriver-transfer-driver - Kassi

7
受Eric答案的启发,我给出了适用于selenium 3.7.0的解决此问题的方案。与此处http://tarunlalwani.com/post/reusing-existing-browser-session-selenium/的解决方案相比,其优势在于每次连接到现有会话时不会出现空白浏览器窗口。
import warnings

from selenium.common.exceptions import WebDriverException
from selenium.webdriver.remote.errorhandler import ErrorHandler
from selenium.webdriver.remote.file_detector import LocalFileDetector
from selenium.webdriver.remote.mobile import Mobile
from selenium.webdriver.remote.remote_connection import RemoteConnection
from selenium.webdriver.remote.switch_to import SwitchTo
from selenium.webdriver.remote.webdriver import WebDriver


# This webdriver can directly attach to an existing session.
class AttachableWebDriver(WebDriver):
    def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
                 desired_capabilities=None, browser_profile=None, proxy=None,
                 keep_alive=False, file_detector=None, session_id=None):
        """
        Create a new driver that will issue commands using the wire protocol.

        :Args:
         - command_executor - Either a string representing URL of the remote server or a custom
             remote_connection.RemoteConnection object. Defaults to 'http://127.0.0.1:4444/wd/hub'.
         - desired_capabilities - A dictionary of capabilities to request when
             starting the browser session. Required parameter.
         - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object.
             Only used if Firefox is requested. Optional.
         - proxy - A selenium.webdriver.common.proxy.Proxy object. The browser session will
             be started with given proxy settings, if possible. Optional.
         - keep_alive - Whether to configure remote_connection.RemoteConnection to use
             HTTP keep-alive. Defaults to False.
         - file_detector - Pass custom file detector object during instantiation. If None,
             then default LocalFileDetector() will be used.
        """
        if desired_capabilities is None:
            raise WebDriverException("Desired Capabilities can't be None")
        if not isinstance(desired_capabilities, dict):
            raise WebDriverException("Desired Capabilities must be a dictionary")
        if proxy is not None:
            warnings.warn("Please use FirefoxOptions to set proxy",
                          DeprecationWarning)
            proxy.add_to_capabilities(desired_capabilities)
        self.command_executor = command_executor
        if type(self.command_executor) is bytes or isinstance(self.command_executor, str):
            self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)

        self.command_executor._commands['GET_SESSION'] = ('GET', '/session/$sessionId')  # added

        self._is_remote = True
        self.session_id = session_id  # added
        self.capabilities = {}
        self.error_handler = ErrorHandler()
        self.start_client()
        if browser_profile is not None:
            warnings.warn("Please use FirefoxOptions to set browser profile",
                          DeprecationWarning)

        if session_id:
            self.connect_to_session(desired_capabilities)  # added
        else:
            self.start_session(desired_capabilities, browser_profile)

        self._switch_to = SwitchTo(self)
        self._mobile = Mobile(self)
        self.file_detector = file_detector or LocalFileDetector()

        self.w3c = True  # added hardcoded

    def connect_to_session(self, desired_capabilities):
        response = self.execute('GET_SESSION', {
            'desiredCapabilities': desired_capabilities,
            'sessionId': self.session_id,
        })
        # self.session_id = response['sessionId']
        self.capabilities = response['value']

如何使用:

if use_existing_session:
    browser = AttachableWebDriver(command_executor=('http://%s:4444/wd/hub' % ip),
                                  desired_capabilities=(DesiredCapabilities.INTERNETEXPLORER),
                                  session_id=session_id)
    self.logger.info("Using existing browser with session id {}".format(session_id))
else:
    browser = AttachableWebDriver(command_executor=('http://%s:4444/wd/hub' % ip),
                                  desired_capabilities=(DesiredCapabilities.INTERNETEXPLORER))
    self.logger.info('New session_id  : {}'.format(browser.session_id))

4

看起来这个功能在selenium中没有得到官方支持。但是,Tarun Lalwani已经创建了可行的Java代码来提供该功能。请参考 - http://tarunlalwani.com/post/reusing-existing-browser-session-selenium-java/

下面是可行的示例代码,从上面的链接中复制而来:

import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.*;
import org.openqa.selenium.remote.http.W3CHttpCommandCodec;
import org.openqa.selenium.remote.http.W3CHttpResponseCodec;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Collections;

public class TestClass {
    public static RemoteWebDriver createDriverFromSession(final SessionId sessionId, URL command_executor){
        CommandExecutor executor = new HttpCommandExecutor(command_executor) {

            @Override
            public Response execute(Command command) throws IOException {
                Response response = null;
                if (command.getName() == "newSession") {
                    response = new Response();
                    response.setSessionId(sessionId.toString());
                    response.setStatus(0);
                    response.setValue(Collections.<String, String>emptyMap());

                    try {
                        Field commandCodec = null;
                        commandCodec = this.getClass().getSuperclass().getDeclaredField("commandCodec");
                        commandCodec.setAccessible(true);
                        commandCodec.set(this, new W3CHttpCommandCodec());

                        Field responseCodec = null;
                        responseCodec = this.getClass().getSuperclass().getDeclaredField("responseCodec");
                        responseCodec.setAccessible(true);
                        responseCodec.set(this, new W3CHttpResponseCodec());
                    } catch (NoSuchFieldException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                } else {
                    response = super.execute(command);
                }
                return response;
            }
        };

        return new RemoteWebDriver(executor, new DesiredCapabilities());
    }

    public static void main(String [] args) {

        ChromeDriver driver = new ChromeDriver();
        HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
        URL url = executor.getAddressOfRemoteServer();
        SessionId session_id = driver.getSessionId();


        RemoteWebDriver driver2 = createDriverFromSession(session_id, url);
        driver2.get("http://tarunlalwani.com");
    }
}

您的测试需要从现有浏览器会话中创建一个RemoteWebDriver。要创建该驱动程序,您只需要知道“会话信息”,即运行浏览器的服务器(在我们的情况下是本地)的地址和浏览器会话ID。为了获取这些详细信息,我们可以使用Selenium创建一个浏览器会话,打开所需页面,最后运行实际的测试脚本。
我不知道是否有一种方法可以获取未由Selenium创建的会话的会话信息。
以下是会话信息示例:
远程服务器地址:http://localhost:24266。每个会话的端口号都不同。 会话ID:534c7b561aacdd6dc319f60fed27d9d6。

1
我不知道是否有一种方法可以获取未由Selenium创建的会话的会话信息。这实际上是我已经尝试了几天的问题...但目前还没有成功。 - slesh
@slesh - 我建议你为此创建一个新问题,如果它没有得到足够的关注,可以提供100个你的积分。 - MasterJoe
1
感谢您提供Tarun Lalwani的参考资料。在他的页面和您的回答之间,我能够弄清楚它的工作原理。导入语句会很好,还有一些解释某些语句目的的注释。但总体来说,非常有帮助。 - Tihamer

3

到目前为止,所有的解决方案都缺乏某些功能。这是我的解决方案:

public class AttachedWebDriver extends RemoteWebDriver {

    public AttachedWebDriver(URL url, String sessionId) {
        super();
        setSessionId(sessionId);
        setCommandExecutor(new HttpCommandExecutor(url) {
            @Override
            public Response execute(Command command) throws IOException {
                if (command.getName() != "newSession") {
                    return super.execute(command);
                }
                return super.execute(new Command(getSessionId(), "getCapabilities"));
            }
        });
        startSession(new DesiredCapabilities());
    }
}

这个功能是什么(其他功能缺失了什么)? - jalanb
1
在内部,只有startSession(...)方法将初始化capabilities对象。 许多方法(如takeScreenshot、executeScript等)都需要capabilities对象。但是通过startSession,您将不得不创建一个新的会话。 这个重载跳过了创建新会话的步骤,但仍然导致capabilities对象的初始化。 - Yanir
2
兄弟,不要用 == 比较字符串。 - Norill Tempest

3

Javascript解决方案:

我成功地使用这个函数附加到现有的浏览器会话。

webdriver.WebDriver.attachToSession(executor, session_id);

你可以在 这里 找到相关的文档。


4
这在4.0.0版本中不存在! - googamanga

1
使用Chrome内置的远程调试功能。打开带有远程调试端口的Chrome。我在OS X上完成了此操作:
sudo nohup /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 &

告诉Selenium使用远程调试端口:
from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument('--remote-debugging-port=9222')
driver = webdriver.Chrome("./chromedriver", chrome_options=options)

使用Selenium 14时,你应该使用driver = webdriver.Chrome(options=options) - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接