How to programmatically configure Chrome extension through Selenium WebDriver
Asked Answered
G

6

15

I need to install and configure an extension in Chrome to modify all request headers during Selenium test execution. I've been able to follow an example from this support article in Saucelabs showing how to do this for Firefox locally, but not sure how to do so for Chrome.

The ChromeDriver documentation for extensions only goes into installing them, not configuring.

Questions

  • Can someone point me to some docs which explain how this can be accomplished or post an example here?
  • How would settings be updated?
  • How to find out what settings properties are available for any given extension?
  • Are there any differences between local and remote execution since that's one of the issues I've encountered with the Firefox method?

Plan is to run this against SauceLabs. Would try to use the ModHeader chrome extension to set the header values needed.

EDIT 1

Tried installing the Chrome version of the MODHeader extension, but running into similar problems. Able to get the extension installed locally, but in remote executions see an error.

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

Error displayed in SauceLabs logs is

[1.968][INFO]: RESPONSE InitSession unknown error: cannot parse capability: chromeOptions
from unknown error: unrecognized chrome option: Arguments
Gilmagilman answered 27/1, 2016 at 1:40 Comment(6)
Quick question to confirm my understanding: do you have to do this using an extension rather than, say, a Browsermob proxy, through which you can pipe all Selenium traffic and rewrite most aspects of the request/response? I'd have def tried to avoid creating anything browser-specific.Recover
Also do you need to use ModHeader? WebRequest API (developer.chrome.com/extensions/webRequest) isn't complex, so it's probably a lot easier to deploy a custom, dedicated extension - to which you can send your own messages - than try to control an existing one.Recover
Thanks for the comments @AndrewRegan. Quick look at Browsermob, docs don't mention anything about using this in a C# environment, so that's a no go for me, but may be a good option for others. The WebRequest API suggestion seems like you have to create a chrome extension to get this functionality, which is more complexity than I wanted to introduce as a solution. I got the Firefox method linked above working, which was the simplest approach. Something similar should be straight forward to do in Chrome, but I was not able to find docs or examples on how do this.Gilmagilman
I mention Browsermob because that's what I've used for x-browser req/resp rewriting (e.g. bypassing Basic Auth alerts). It has a REST API so Java aspect shouldn't matter, but I bet there are C#-based equivalents. Just need to ensure proxy is reachable from the Saucelab servers. For me, it was critical not to have any browser-specific components, but I understand if you want to concentrate on Chrome while you seem to have FF working.Recover
Would a apache proxy work for your purpose? #154941 . Similar to what you'd do with BrowserMob but maybe easier, depending on your use-case.Memorialist
check if Tools/Chrome_ModHeader_2_0_6.crx is present on the remote machine. This might be a problem that the file is not available in your remote machine and thus failing to install. Locally it is working because it is available in your local system.Jasmin
A
2

An extension on Chrome has a constant unique ID.

You can use selenium web driver navigate to chrome-extension://<EXTENSION_UUIF>/options.html, here options.html is the preference page you defined.

Then execute a snippet of script to change the settings that stored in chrome.storage.local.

Aspasia answered 8/5, 2018 at 2:39 Comment(0)
B
1

Since you mention that the problem is mainly on remote and I notice you are using SauceLabs, have you check this article from them?

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.
Bertina answered 31/5, 2016 at 11:9 Comment(1)
I linked to that article in my question and mentioned that I was able to get it working in Firefox locally. In my other link, I managed to get it working in Firefox remotely also. This issue deals with getting an extension installed in Chrome remotely.Gilmagilman
M
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
    }
Magistral answered 18/11, 2017 at 16:42 Comment(0)
C
0

I managed to install an extension on a Chrome browser in Saucelabs as follows:

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);
Corcovado answered 15/6, 2016 at 10:28 Comment(0)
D
0

I found solution of this problem. It works for my Selenium GRID with remote Chrome browsers. First of all I was become to store unpacked ModHeader(version 1.2.4) extension in resources of my project. It looks so

enter image description here

If I need to modify header in Chrome I do following steps:

1) unzip folder with extension from resources into temporary folder

2) set header keys and values in header.json

3) pack this extension to zip-file using Java

4) add zip-file to 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;
}

FileUtils

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;
}
Dipole answered 8/7, 2016 at 14:26 Comment(0)
M
-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);

}

Magistral answered 18/11, 2017 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.