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


101

在启动Selenium客户端之前,是否有人知道Selenium(最好是WebDriver)是否能够与已经运行的浏览器进行通信并通过其运行?

我的意思是,如果Selenium能够在不使用Selenium Server的情况下与浏览器进行通讯(例如,可能是手动启动的Internet Explorer)。

Answers:


34

这是一个非常古老的功能请求:允许webdriver附加到正在运行的浏览器。因此正式不支持。

但是,有一些工作代码声称支持此功能:https : //web.archive.org/web/20171214043703/http : //tarunlalwani.com/post/reusing-existing-browser-session-selenium-java/


非常感谢,因为在该链接中我找到了一个可以执行此操作的类,但是不幸的是,我无法在IE(仅在Firefox)中使用该解决方案。我将启动一个常规的IEDriver,并使用中间件与其他过程进行通讯。如果您有一个想法,为什么该类无法在IE上运行,我将不胜感激。谢谢。
Angel Romero

罗伯特,现在是2018年。你能更新你的答案吗?
MasterJoe

万一有人需要,我已经尝试并测试了一些Java代码以使硒使用现有的浏览器会话-stackoverflow.com/a/51145789/6648326
MasterJoe

53

这是重复的答案**重新连接到python selenium中的驱动程序**这适用于所有驱动程序和Java api。

  1. 打开一个驱动程序
driver = webdriver.Firefox()  #python
  1. 从驱动程序对象提取到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'
  1. 使用这两个参数连接到您的驱动程序。
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")

1
这正是我想要的。谢谢。
milso

6
它对我有用,除了每次都出现一个重复的虚拟浏览器。
帕维尔·弗拉索夫

我也得到了虚拟窗口,这没什么大不了的,但是在调试过程中很烦人。关于如何摆脱的任何想法?
史蒂夫·冈

1
+1。我的工作方式是避免两因素身份验证登录,但是存在重复的虚拟浏览器。我可以忍受这一点。
山姆

如果您需要关闭虚拟浏览器窗口,只需driver.close()在更新会话ID之前先调用即可。
Amr Awad

23

此代码段成功允许重用现有浏览器实例,但又避免了生成重复的浏览器。在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/')

2
有没有一种方法可以通过自动化查找现有的会话ID和执行者URL?就我而言,另一个应用程序打开了一个浏览器会话,我想使用它。您能否建议一下,如何找到该浏览器的会话ID?
Sun Shine

可能在脚本启动时可以将executor_command的url和会话ID转储到文件中,并在您想再次挂钩浏览器会话时从文件中读取它。
SK Venkat

@SKVenkat如何获取chrome窗口的会话ID,我使用pywinauto打开了它,现在想在其上运行selenuim,是否有python方式获取chrome标签的会话ID
Tayyab Nasir

@TayyabNasir,请注意上面的答案。注释掉的第五行是# session_id = driver.session_id使用python selenium api检索Chrome窗口的会话ID的方式。我猜想chrome会话中的每个标签都没有唯一的ID。
SK Venkat

3
@SK我想要手动打开的Chrome窗口的会话ID,我没有使用硒打开该窗口
Tayyab Nasir

12

有可能的。但是您需要稍微修改一下,这里有一个代码您需要做的是运行独立服务器并“修补” 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;
        }
    }
}

4
基于这个出色的解决方案,我写了一篇完整的博客文章,其中讨论了如何连接到已打开的chrome浏览器实例。该博客文章还附有完整的源代码。binaryclips.com/2015/08/25/...
joinsaad

4

硒似乎没有正式支持此功能。但是,塔伦·拉尔瓦尼(Tarun Lalwani)已经创建了可工作的Java代码来提供该功能。请参阅-http://tarunlalwani.com/post/reusing-existing-browser-session-selenium-java/

这是从上面的链接复制的工作示例代码:

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。为了获得这些详细信息,我们可以使用硒创建一个浏览器会话,打开所需的页面,然后最终运行实际的测试脚本。

我不知道是否有一种方法来获取不是由硒创建的会话的会话信息。

这是会话信息的示例:

远程服务器地址:http:// localhost:24266。每个会话的端口号不同。会话ID:534c7b561aacdd6dc319f60fed27d9d6。


“我不知道是否有一种方法可以获取不是由硒创建的会话的会话信息。” 这实际上是我已经尝试了几天的问题了……还没有成功
slesh

@slesh-我建议您为此提出一个新问题,如果没有引起足够的重视,也许可以提供100分。
MasterJoe

感谢您提及塔伦·拉尔瓦尼(Tarun Lalwani)的工作。在他的页面和您的答案之间,我能够弄清楚。导入本来很好,还有一些注释可以解释某些声明的目的。但是所有,非常有帮助。
Tihamer

4

受埃里克(Eric)的回答启发,这是我针对硒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))

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 '16

1
在内部,仅startSession(...)方法将初始化功能对象。功能对象是许多方法(例如takeScreenshot,executeScript等)所必需的。但是通过startSession,您将不得不创建一个新的会话。此重载会跳过新会话的创建,但仍会导致功能对象初始化。
亚尼尔(Yanir)'16

花花公子,不要用==比较字符串
Norill Tempest

3

JavaScript解决方案:

我已使用此功能成功附加到现有浏览器会话

webdriver.WebDriver.attachToSession(executor, session_id);

文档可以在这里找到。


3
这不是4.0.0版本!
googamanga

1

我在python中找到了解决方案,我修改了发现的PersistenBrowser类上的webdriver类。

https://github.com/axelPalmerin/personal/commit/fabddb38a39f378aa113b0cb8d33391d5f91dca5

替换webdriver模块/usr/local/lib/python2.7/dist-packages/selenium/webdriver/remote/webdriver.py

j 使用:

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

runDriver = sys.argv[1]
sessionId = sys.argv[2]

def setBrowser():
    if eval(runDriver):
        webdriver = w.Remote(command_executor='http://localhost:4444/wd/hub',
                     desired_capabilities=DesiredCapabilities.CHROME,
                     )
    else:
        webdriver = w.Remote(command_executor='http://localhost:4444/wd/hub',
                             desired_capabilities=DesiredCapabilities.CHROME,
                             session_id=sessionId)

    url = webdriver.command_executor._url
    session_id = webdriver.session_id
    print url
    print session_id
    return webdriver


-1

使用JavaScriptselenium-webdriver客户端,这非常简单:

首先,请确保您正在运行WebDriver服务器。例如,下载ChromeDriver,然后运行chromedriver --port=9515

第二,像这样创建驱动程序:

var driver = new webdriver.Builder()
   .withCapabilities(webdriver.Capabilities.chrome())
   .usingServer('http://localhost:9515')  // <- this
   .build();

这是一个完整的示例:

var webdriver = require('selenium-webdriver');

var driver = new webdriver.Builder()
   .withCapabilities(webdriver.Capabilities.chrome())
   .usingServer('http://localhost:9515')
   .build();

driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.getTitle().then(function(title) {
   console.log(title);
 });

driver.quit();

4
它不使用现有的浏览器会话。它将创建一个新的chromedriver会话并打开一个新的浏览器窗口。而且getAllWindowHandles()将不会显示您的旧浏览器窗口的句柄。
Dzenly

更新:有 seleniumhq.github.io/selenium/docs/api/javascript/module/…允许连接到现有打开的浏览器窗口。
Dzenly
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.