如何通过Selenium WebDriver编程方式配置Chrome扩展程序

15

我需要在Selenium测试执行期间安装和配置Chrome扩展程序来修改所有请求标头。我已经能够按照Saucelabs的这篇支持文章的示例为Firefox本地安装和配置扩展程序,但不确定如何为Chrome进行操作。

extensions 的 ChromeDriver 文档仅介绍了如何安装它们,而没有介绍如何配置。

问题:

  • 有人可以指导我如何完成此操作或在此处发布示例的文档吗?
  • 设置将如何更新?
  • 如何查找任何给定扩展程序可用的设置属性?
  • 是否存在本地执行和远程执行之间的差异,因为这是我在Firefox 方法中遇到的问题之一?

计划在SauceLabs上运行此操作。 会尝试使用ModHeader Chrome扩展程序来设置所需的标头值。

编辑1

尝试安装MODHeader扩展程序的Chrome版本,但遇到类似的问题。 能够在本地安装扩展程序,但在远程执行中看到错误。

private static IWebDriver GetRemoteDriver(string browser)
{

    ChromeOptions options = new ChromeOptions();
    options.AddExtensions("Tools/Chrome_ModHeader_2_0_6.crx");

    DesiredCapabilities capabilities = DesiredCapabilities.Chrome();
    capabilities.SetCapability(ChromeOptions.Capability, options);


    capabilities.SetCapability("name", buildContext);
    capabilities.SetCapability(CapabilityType.BrowserName, "Chrome");
    capabilities.SetCapability(CapabilityType.Version, "");
    capabilities.SetCapability(CapabilityType.Platform, "Windows 10");
    capabilities.SetCapability("screen-resolution", "1280x1024");
    capabilities.SetCapability("username", "SaucelabsUserName");
    capabilities.SetCapability("accessKey", "SaucelabsAccessKey");
    capabilities.SetCapability("build", "BuildNumber");
    capabilities.SetCapability("seleniumVersion", "2.50.1");


    return new RemoteWebDriver(new Uri("http://ondemand.saucelabs.com/wd/hub"), capabilities);
}

SauceLabs日志中显示的错误为

[1.968][INFO]: RESPONSE InitSession unknown error: cannot parse capability: chromeOptions
from unknown error: unrecognized chrome option: Arguments

快速确认一下我的理解:您必须使用扩展程序而不是 Browsermob 代理来完成这个任务吗?通过代理,您可以将所有 Selenium 流量传输并重写请求/响应的大部分方面。我本来会尝试避免创建任何特定于浏览器的东西。 - Andrew Regan
1
你是否需要使用ModHeader? WebRequest API(https://developer.chrome.com/extensions/webRequest)并不复杂,因此部署一个定制的扩展程序可能比尝试控制现有的扩展程序更容易 - 你可以向其中发送自己的消息。 - Andrew Regan
感谢@AndrewRegan的评论。快速查看Browsermob,文档中没有提到在C#环境中使用它,所以这对我来说不可行,但可能是其他人的好选择。 WebRequest API建议似乎需要创建一个Chrome扩展程序才能获得此功能,这比我想要引入的解决方案更复杂。我已经成功地使用了上面链接的Firefox方法,这是最简单的方法。在Chrome中做类似的事情应该很容易,但我找不到如何做到这一点的文档或示例。 - Jerry
我提到Browsermob是因为我用它来进行跨浏览器的请求/响应重写(例如绕过基本身份验证警报)。它有一个REST API,所以Java方面不应该成问题,但我敢打赌也有基于C#的等效物。只需要确保代理服务器可以从Saucelab服务器访问即可。对我来说,关键是没有任何特定于浏览器的组件,但如果您想专注于Chrome而您似乎已经在使用FF,我也能理解。 - Andrew Regan
一个 Apache 代理是否适合您的目的?类似于您在 BrowserMob 中所做的操作,但根据您的用例可能更容易。https://dev59.com/anVC5IYBdhLWcg3w4Vb6 - djangofan
检查远程机器上是否存在 Tools/Chrome_ModHeader_2_0_6.crx。这可能是因为该文件在您的远程机器上不可用,因此安装失败。本地可以工作,因为它在您的本地系统中可用。 - Prasanta Biswas
6个回答

2

Chrome扩展具有恒定的唯一标识符。

您可以使用Selenium Web Driver导航到chrome-extension://<EXTENSION_UUIF>/options.html,这里的options.html是您定义的首选项页面。

然后执行一段脚本片段来更改存储在chrome.storage.local中的设置。


1
    public void AddHeaderChrome()
    {
    ChromeOptions  options = new ChromeOptions();
    options.addExtensions(new File("C:\\Downloads\\ModHeader_v2.0.9.crx"));
     DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer();

    capabilities.setCapability(CapabilityType.options);
    // launch the browser
    WebDriver driver = new ChromeDriver(options);
    String HeadersName[]=new String[10];
    String HeadersValue[]=new String[10];;
    int length;
    if(ConfigDetails.HeadersName.contains(","))
    {
    HeadersName=ConfigDetails.HeadersName.split(",");
    HeadersValue=ConfigDetails.HeadersValue.split(",");
    length=HeadersName.length;
    }
    else
    {
       HeadersName[0]=ConfigDetails.HeadersName; 
       HeadersValue[0]=ConfigDetails.HeadersValue;
       length=1;
    }   
    int field_no=1;
    for(int i=0;i<length;i++)
    {
    driver.get("chrome-extension://idgpnmonknjnojddfkpgkljpfnnfcklj/popup.html");
    driver.findElement(By.xpath("//input[@id='fl-input-"+field_no+"']")).sendKeys(HeadersName[i]);
    driver.findElement(By.xpath("//input[@id='fl-input-"+(field_no+1)+"']")).sendKeys(HeadersValue[i]);
    field_no+=2
    }

1

既然您提到问题主要在远程方面,我注意到您正在使用SauceLabs,您是否查看过他们的这篇文章?

https://support.saucelabs.com/customer/en/portal/articles/2200902-creating-custom-firefox-profiles-and-chrome-instances-for-your-automated-testing

Installing an Firefox Extension such as Modify Headers(You would need download the .xpi file on your machine first):

DesiredCapabilities caps = new DesiredCapabilities();
FirefoxProfile profile = new FirefoxProfile();
profile.addExtension(new File("path\of\Modify Headers xpi file"));
profile.setPreference("general.useragent.override", "UA-STRING");
profile.setPreference("extensions.modify_headers.currentVersion", "0.7.1.1-signed");
profile.setPreference("modifyheaders.headers.count", 1);
profile.setPreference("modifyheaders.headers.action0", "Add");
profile.setPreference("modifyheaders.headers.name0", "X-Forwarded-For");
profile.setPreference("modifyheaders.headers.value0", "161.76.79.1");
profile.setPreference("modifyheaders.headers.enabled0", true);
profile.setPreference("modifyheaders.config.active", true);
profile.setPreference("modifyheaders.config.alwaysOn", true);
profile.setPreference("modifyheaders.config.start", true);
caps.setCapability(FirefoxDriver.PROFILE, profile);

NOTE: If you trying to do the same using C#, you would need to use the ToBase64String() method.

我在我的问题中链接了那篇文章,并提到我能够在本地的Firefox中使其工作。在我的另一个链接中,我成功地在远程Firefox中让它工作了。这个问题涉及到在Chrome远程安装扩展程序的问题。 - Jerry

0
我成功地在Saucelabs上的Chrome浏览器中安装了一个扩展,步骤如下:

ChromeOptions options = new ChromeOptions();
options.addExtensions(new File("/path/to/myextrension.crx"));
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
capabilities.setBrowserName(DesiredCapabilities.chrome().getBrowserName());

// Rest of capabilities config (version, platform, name, ...)

WebDriver driver = new RemoteWebDriver(new URL("http://saucelabs-url/wd/hub"), capabilities);

0
我找到了这个问题的解决方案。它适用于我的Selenium GRID和远程Chrome浏览器。 首先,我将未打包的ModHeader(版本1.2.4)扩展程序存储在我的项目资源中。它看起来是这样的:

enter image description here

如果我需要在Chrome中修改标题,我会执行以下步骤:
1)将资源中的扩展文件夹解压缩到临时文件夹中
2)在header.json中设置标题键和值
3)使用Java将此扩展打包为zip文件
4)将zip文件添加到ChromeOptions中
public static IDriver getDriverWithCustomHeader(List<HeaderElement> headerList) {
    Logger.info(StringUtils.buildString("Create new instance of Driver with header."));
    IDriver driver;
    DesiredCapabilities capabilities;
    switch (GlobalConfig.getInstance().getDriverType()) {
        case CHROME:
            // define path to resources
            String unpackedExtensionPath = FileUtils.getResourcePath("chrome_extension", true);
            // setting  headers for extension in unpackaged kind
            FileUtils.writeToJson(StringUtils.buildString(unpackedExtensionPath, File.separator, "header.json"), headerList);
            // packing prepared extension to ZIP with crx extension
            String crxExtensionPath = ZipUtils.packZipWithNameOfFolder(unpackedExtensionPath, "crx");
            // creating capability based on packed extension
            capabilities = CapabilityFactory.getChromeCapabilitiesWithExtension(crxExtensionPath);
            driver = new AppiumDriver(GlobalConfig.getInstance().getHost(), GlobalConfig.getInstance().getPort(),
                    capabilities);
            break;
        default:
            throw new CommonTestRuntimeException("Unsupported Driver Type for changing head args.");
    }

    drivers.add(driver);
    if (defaultDriver != null) {
        closeDefaultDriver();
    }

    defaultDriver.set(driver);
    return driver;
}

文件工具类

public static String getResourcePath(String resourceName, boolean isDir) {
    String jarFileName = new File(FileUtils.class.getClassLoader().getResource(resourceName).getPath()).getAbsolutePath()
            .replaceAll("(!|file:\\\\)", "");
    if (!(jarFileName.contains(".jar"))) {
        return getResourcePath(resourceName);
    }
    if (isDir) {
        return getDirPath(resourceName);
    }
    return getFilePath(resourceName);
}

private static String getResourcePath(String resourceName) {
    String resourcePath = FileUtils.class.getClassLoader().getResource(resourceName).getPath();
    if (platformIsWindows()) {
        resourcePath = resourcePath.substring(1);
    }
    return resourcePath;
}

private static boolean platformIsWindows() {
    boolean platformIsWindows = (File.separatorChar == '\\') ? true : false;
    return platformIsWindows;
}

private static String getDirPath(String dirName) {
    JarFile jarFile = null;
    //check created or no tmp directory
    //and if the directory created already we return "it + dirName"
    //else we create tmp directory and copy target resources
    if (directoryPath.get() == null) {
        //set directory path for each thread
        directoryPath.set(Files.createTempDir().getAbsolutePath());
    }
    //copying resources
    if (!new File(directoryPath.get() + File.separator + dirName.replaceAll("/", "")).exists()) {
        try {
            List<JarEntry> dirEntries = new ArrayList<JarEntry>();
            File directory = null;
            String jarFileName = new File(FileUtils.class.getClassLoader().getResource(dirName).getPath()).getParent()
                    .replaceAll("(!|file:\\\\)", "").replaceAll("(!|file:)", "");
            jarFile = new JarFile(URLDecoder.decode(jarFileName, "UTF-8"));
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = entries.nextElement();
                if (jarEntry.getName().startsWith(dirName)) {
                    if (jarEntry.getName().replaceAll("/", "").equals(dirName.replaceAll("/", ""))) {
                        directory = new File(directoryPath.get() + File.separator + dirName.replaceAll("/", ""));
                        directory.mkdirs();
                    } else
                        dirEntries.add(jarEntry);
                }
            }
            if (directory == null) {
                throw new CommonTestRuntimeException(StringUtils.buildString("There is no directory ", dirName,
                        "in the jar file"));
            }
            for (JarEntry dirEntry : dirEntries) {
                if (!dirEntry.isDirectory()) {
                    File dirFile = new File(directory.getParent() + File.separator + dirEntry.getName());
                    dirFile.createNewFile();
                    convertStreamToFile(dirEntry.getName(), dirFile);
                } else {
                    File dirFile = new File(directory.getParent() + File.separator + dirEntry.getName());
                    dirFile.mkdirs();
                }
            }
            return directory.getAbsolutePath();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                jarFile.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        throw new CommonTestRuntimeException("There are problems in creation files in directory " + directoryPath);
    } else {
        return directoryPath.get() + File.separator + dirName.replaceAll("/", "");
    }
}

private static String getFilePath(String fileName) {
    try {
        String[] fileType = fileName.split("\\.");
        int typeIndex = fileType.length;
        File file = File.createTempFile(StringUtils.generateRandomString("temp"),
                StringUtils.buildString(".", fileType[typeIndex - 1]));
        file.deleteOnExit();
        convertStreamToFile(fileName, file);
        return file.getAbsolutePath();
    } catch (IOException e) {
        e.printStackTrace();
    }
    throw new CommonTestRuntimeException("Impossible to get file path");
}

private static void convertStreamToFile(String resourceFileName, File file) throws IOException {
    try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resourceFileName);
         BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF8"));
         FileOutputStream fos = new FileOutputStream(file);
         OutputStreamWriter fileOutputStreamWriter = new OutputStreamWriter(fos, "UTF8");
         BufferedWriter fileWriter = new BufferedWriter(fileOutputStreamWriter);
    ) {
        String line = null;
        while ((line = reader.readLine()) != null) {
            fileWriter.write(line + "\n");
        }
    }
}

public static void writeToJson(String jsonFilePath, Object object) {
    try {
        Gson gson = new Gson();
        FileWriter fileWriter = new FileWriter(jsonFilePath);
        fileWriter.write(gson.toJson(object));
        fileWriter.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

ZipUtils

public static String packZipWithNameOfFolder(String folder, String extension) {
    String outZipPath = folder + "." + extension;
    try {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outZipPath))) {
            File file = new File(folder);
            doZip(file, zos);
        }
    } catch (IOException e) {
        throw new CommonTestRuntimeException("Fail of packaging of folder. ", e);
    }
    return outZipPath;
}

private static void doZip(File dir, ZipOutputStream out) throws IOException {
    for (File f: dir.listFiles()) {
        if (f.isDirectory()) {
            doZip(f, out);
        } else {
            out.putNextEntry(new ZipEntry(f.getName()));
            try (FileInputStream in = new FileInputStream(f)) {
                write(in, out);
            }
        }
    }
}

private static void write (InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int len;
    while ((len = in.read(buffer)) >= 0) {
        out.write(buffer, 0, len);
    }
}

CapabilityFactory.getChromeCapabilitiesWithExtension(...)

public static DesiredCapabilities getChromeCapabilitiesWithExtension(String crxExtensionPath) {
    DesiredCapabilities chromeCapabilities = getChromeCapabilities();
    Logger.info("Extension path: " + crxExtensionPath);
    ChromeOptions options = new ChromeOptions();
    options.addExtensions(new File(crxExtensionPath));
    options.addArguments("--start-maximized");
    chromeCapabilities.setCapability(ChromeOptions.CAPABILITY, options);
    return chromeCapabilities;
}

-1
 public void AddHeaderFirefox(FirefoxProfile profile)
 {
 String directory = System.getProperty("user.dir");
 FirefoxProfile profile = new FirefoxProfile(); 
 try
 {
 profile.addExtension(new File(directory+"/modify-headers-0.7.1.1.xpi"));
 }
 catch(IOException e)
 {
  System.out.println(e);
 }
 String HeadersName[]=new String[10];
 String HeadersValue[]=new String[10];

 if(ConfigDetails.HeadersName.contains(",") && ConfigDetails.HeadersValue.contains(","))
 {
 HeadersName=ConfigDetails.HeadersName.split(",");
 HeadersValue=ConfigDetails.HeadersValue.split(",");
 length=HeadersName.length;
               }

 You have to parametrise the header USING Split function of java to set 
 multiple headers.

 for(int i=0;i<length;i++)
 {
 profile.setPreference("modifyheaders.headers.count",i+1);
 profile.setPreference("modifyheaders.headers.action"+i, "Add");
 profile.setPreference("modifyheaders.headers.name"+i,HeadersName[i]);
 profile.setPreference("modifyheaders.headers.value"+i,HeadersValue[i]);
 profile.setPreference("modifyheaders.headers.enabled"+i, true);
 profile.setPreference("modifyheaders.config.active", true);
 profile.setPreference("modifyheaders.config.alwaysOn", true);

}


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