Как программно настроить расширение Chrome через Selenium WebDriver

Мне нужно установить и настроить расширение в Chrome для изменения всех заголовков запросов во время выполнения теста Selenium. Я смог следовать примеру из этого статья поддержки в Saucelabs, в которой показано, как сделать это для Firefox локально, но не знаете, как это сделать для Chrome.

Документация ChromeDriver для расширений касается только их установки, а не настройки.

Вопросы

  • Может ли кто-нибудь указать мне на некоторые документы, которые объясняют, как это можно сделать, или опубликовать пример здесь?
  • Как будут обновляться настройки?
  • Как узнать, какие свойства настроек доступны для того или иного расширения?
  • Существуют ли какие-либо различия между локальным и удаленным выполнением, поскольку это одна из проблем, с которыми я столкнулся при использовании Метод Firefox?

План состоит в том, чтобы запустить это против SauceLabs. Попытался бы использовать расширение Chrome ModHeader для установки значений заголовка. нужный.

ИЗМЕНИТЬ 1

Пытался установить версию расширения MODHeader для Chrome, но столкнулся с аналогичными проблемами. Удалось установить расширение локально, но при удаленном выполнении вижу ошибку.

private static IWebDriver GetRemoteDriver(string browser)
{

    ChromeOptions options = new ChromeOptions();
    options.AddExtensions("Tools/Chrome_ModHeader
[1.968][INFO]: RESPONSE InitSession unknown error: cannot parse capability: chromeOptions
from unknown error: unrecognized chrome option: Arguments
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

comment
Быстрый вопрос, чтобы подтвердить мое понимание: вам нужно делать это с помощью расширения, а не, скажем, прокси-сервера Browsermob, через который вы можете передавать весь трафик Selenium и переписывать большинство аспектов запроса/ответа? Я бы определенно попытался избежать создания чего-либо специфичного для браузера.   -  person Andrew Regan    schedule 07.02.2016
comment
Вам также нужно использовать ModHeader? API WebRequest (developer.chrome.com/extensions/webRequest) не сложен, поэтому вероятно, намного проще развернуть собственное специальное расширение, которому вы можете отправлять свои собственные сообщения, чем пытаться контролировать существующее.   -  person Andrew Regan    schedule 07.02.2016
comment
Спасибо за комментарии @AndrewRegan. Беглый взгляд на Browsermob, в документах ничего не говорится об использовании этого в среде C#, так что мне это не подходит, но может быть хорошим вариантом для других. Предложение WebRequest API похоже на то, что вам нужно создать расширение Chrome, чтобы получить эту функциональность, что является более сложным, чем я хотел представить в качестве решения. У меня работает метод Firefox, указанный выше, который был самым простым подходом. Что-то подобное должно быть легко сделано в Chrome, но мне не удалось найти документы или примеры того, как это сделать.   -  person Jerry    schedule 09.02.2016
comment
Я упоминаю Browsermob, потому что именно его я использовал для перезаписи req/resp x-browser (например, для обхода предупреждений Basic Auth). У него есть REST API, поэтому аспект Java не должен иметь значения, но я уверен, что есть эквиваленты на основе C#. Просто нужно убедиться, что прокси доступен с серверов Saucelab. Для меня было критически важно не иметь каких-либо компонентов, специфичных для браузера, но я понимаю, если вы хотите сосредоточиться на Chrome, пока у вас работает FF.   -  person Andrew Regan    schedule 09.02.2016
comment
Будет ли прокси-сервер Apache работать для вашей цели? stackoverflow .com/questions/154441/ . Похоже на то, что вы делаете с BrowserMob, но может быть проще, в зависимости от вашего варианта использования.   -  person djangofan    schedule 12.02.2016
comment
проверьте, присутствует ли Tools/Chrome_ModHeader_2_0_6.crx на удаленной машине. Проблема может заключаться в том, что файл недоступен на вашем удаленном компьютере и, следовательно, не может быть установлен. Локально он работает, потому что он доступен в вашей локальной системе.   -  person Prasanta Biswas    schedule 26.02.2018


Ответы (4)


Поскольку вы упомянули, что проблема в основном на удаленке, и я заметил, что вы используете 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.
person Rain9333    schedule 31.05.2016
comment
Я связался с этой статьей в своем вопросе и упомянул, что мне удалось заставить ее работать в Firefox локально. В другой моей ссылке мне удалось заставить ее работать в Firefox удаленно. Эта проблема связана с удаленной установкой расширения в Chrome. - person Jerry; 31.05.2016

Расширение в Chrome имеет постоянный уникальный идентификатор.

Вы можете использовать веб-драйвер selenium, чтобы перейти к chrome-extension://<EXTENSION_UUIF>/options.html, здесь options.html — это страница настроек, которую вы определили.

Затем выполните фрагмент скрипта, чтобы изменить настройки, хранящиеся в chrome.storage.local.

person Xiaoming    schedule 08.05.2018

Мне удалось установить расширение на браузер Chrome в Saucelabs следующим образом:

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);
person Boni García    schedule 15.06.2016

Я нашел решение этой проблемы. Он работает для моего Selenium GRID с удаленными браузерами Chrome. Первым делом я стал хранить распакованное расширение ModHeader(версия 1.2.4) в ресурсах своего проекта. это выглядит так

введите здесь описание изображения

Если мне нужно изменить заголовок в Chrome, я делаю следующие шаги:

1) распаковать папку с расширением из ресурсов во временную папку

2) установить ключи и значения заголовка в header.json

3) запаковать это расширение в zip-файл с помощью Java

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

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;
}
person Seploid    schedule 08.07.2016