selenium
selenium copied to clipboard
As a Firefox extension developer, I would like to install a temporary extension from a folder
Edit: 2020-06-11 – ⚠️ This initial comment is not correct. I misunderstood the GeckoDriver API. I was looking at stale source code. 😓 With new knowledge and some guidance from the GeckoDriver team, I've proposed an approach to handle the "install addon" command from FirefoxDriver consistently for each language. See my long comment below.
--
🐛 Bug Report
We can't install a temporary extension in Firefox using the JavaScript or DotNet bindings. The implementation is inconsistent across the languages. The GeckoDriver API expects a path
as a String but we're sending an addon
. See mozilla/geckodriver - marionette.rs.
These two bindings need to be fixed:
Language | Implementation | Authors / CC |
---|---|---|
JavaScript | javascript/node/selenium-webdriver/firefox.js#L637-L657 | @kvetko, @jleyba |
DotNet | dotnet/src/webdriver/Firefox/FirefoxDriver.cs#L213-L227 | @jimevans |
These seem to be OK:
To Reproduce
Try to install an extension without building an .xpi or .zip file using dotnet and javascript bindings.
TODO - Script
Expected behavior
I expect to be able to install a temporary extension in Firefox:
- From the JavaScript library using
installAddon
- From the DotNet library using
InstallAddOn
- Note 1: This method is not in those docs?!
- Note 2:
InstallAddOnFromFile
is also made redundant
Test script or set of commands reproducing this issue
TODO - Include in example repo.
The steps to reproduce are:
- Create a minimal extension
-
extension\manifest.json
{ "name": "Uh oh!", "description" : "Print a message to the console you may not see", "version": "0.1", "manifest_version": 2, "content_scripts": [ { "matches": ["https://*.github.com/*"], "js": ["index.js"] } ] }
-
extension\index.js
console.log('We did it! ⭐️');
-
extension\manifest.json
- Start Firefox and add an extension
-
javascript/example.js
const { Builder, By } = require('selenium-webdriver'); const { Options } = require('selenium-webdriver/firefox'); (async () => { const options = new Options() .setPreference('extensions.htmlaboutaddons.recommendations.enabled', false); const builder = new Builder() .forBrowser('firefox') .setFirefoxOptions(options); const driver = builder.build(); const isTemporaryAddon = true; driver.installAddon('../extension/', isTemporaryAddon); await driver.get('https://github.com/SeleniumHQ/selenium'); // Expected: A message to be printed on the console // Expected: The temporary extension appears in about:debugging > This Firefox > Temporary Extensions })();
-
javascript/example.js
- Install selenium-webdriver
echo {} > package.json npm install --save-dev selenium-webdriver
- Run
node javascript/example.js
Environment
OS: macOS Browser: Firefox Browser version: 71.0 Browser Driver version: GeckoDriver for the last 2+ years Language Bindings version: Latest Selenium Grid version (if applicable): Not applicable
Questions
- Would you welcome a PR?
- Do you want the issue or PR split by language?
Please can we have a 2 separate PRs for each language. I will try get them merged ASAP
Will do. Thanks for the quick reply David.
Hey @AutomatedTester, before I submit any code changes I wanted to check-in with a bit of a pivot. I've had a good chat with some of the Mozilla devs on chat.mozilla.org from the interop and Add-ons channels.
Background
FirefoxDriver will dispatch a command to install an addon. GeckoDriver supports two variants of this command:
- { addon: string, temporary: Optional
} - { path: string, temporary: Optional
The "addon" is the Base64 encoded contents of a .xpi or .zip packed addon.
The "path" is a file path to a .xpi or .zip file. It can also be the path
to an unpacked directory when:
- The
temporary
is set - Firefox is configured to allow temporary extensions, either by changing the configuration or using the nightly or dev builds
What did I get wrong in the issue description?
- GeckoDriver still supports both types of "install addon" command messages. I was looking at stale source code. See
AddonInstallParameters
in https://searchfox.org/mozilla-central/source/testing/geckodriver/src/command.rs. - We don't want to move away from "addon" at all. @whimboo has explained that when we're handling .xpi or .zip files, it is best to use the "addon" message. This allows GeckoDriver and the browser to be on separate machines. Otherwise, we can use the "path" message.
Proposed Changes
We'll want every language to use the same behaviour. When asked to install an addon, we should check the file type of the argument. When we have an .xpi or .zip file, we should send the command using the "addon"-type message. Otherwise, we'll use the "path".
To save space in the table below, I'll call it "🍍".
Language | Current Behaviour | Proposed Changes |
---|---|---|
.NET | See FirefoxDriver.cs
|
|
Java | See FirefoxDriver.java
|
|
JavaScript | See firefox.js
|
|
Python | See firefox/webdriver.py
|
|
Ruby | See firefox/bridge.rb
|
|
We can also update the API docs to include a note that Firefox needs to be configured to allow temporary
extensions.
--
Actions
- [ ] Sound good, @AutomatedTester?
- [ ] Do you want this cleaned up as a new issue?
I'd like to suggest a different approach. Instead of checking the file extension or type, the API could allow the caller to specify whether the given path is local or remote. If local, then the code should read the file and send it to the server using the addon
parameter. If remote, the code should send the path to the server using the path
parameter.
I think this would be more flexible and give the caller more control over the behavior.
Without temporary flag we can't add unsigned extensions
Is there any update about this issue? Thanks.
Java fix is here - 932cbf96 I have a PR for .NET #10093
I'm working to get everything implemented with addon
instead of path
for Selenium 4.2 so we don't have to duplicate Local File Detector implementations for using with Remote drivers. There's not much downside to just encoding it even when on a local machine, even if geckodriver doesn't need it. JavaScript & .NET were already doing this, but Java support and Ruby support are in trunk, and I have a PR for Python: #10092
That makes sense with addon
and both PRs look great. Thank you Titus!
As the issue title says, I aim to install a temporary extension from a folder using Firefox. Using the Java bindings, this was already possible in Chrome using --load-extension
as follows:
Path extension = Paths
.get(ClassLoader.getSystemResource("web-extension").toURI());
ChromeOptions options = new ChromeOptions();
options.addArguments(
"--load-extension=" + extension.toAbsolutePath().toString());
ChromeDriver driver = new ChromeDriver(options);
According to the release notes, this is also possible with Firefox in version 4.1.1, recently released. I tried to use the new feature in one of my tests as follows:
Path extension = Paths
.get(ClassLoader.getSystemResource("web-extension").toURI());
FirefoxDriver driver = new FirefoxDriver();
driver.installExtension(extension, true);
But I get the following exception:
org.openqa.selenium.InvalidArgumentException: ...\target\test-classes\web-extension is an invalid path
Build info: version: '4.1.1', revision: 'e8fcc2cecf'
System info: host: 'DESKTOP-VPVQ23J', ip: '192.168.56.1', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_251'
Driver info: driver.version: FirefoxDriver
at org.openqa.selenium.firefox.AddHasExtensions$1.installExtension(AddHasExtensions.java:82)
at org.openqa.selenium.firefox.FirefoxDriver.installExtension(FirefoxDriver.java:276)
at io.github.bonigarcia.webdriver.jupiter.ch05.caps.extensions.LoadExtensionFirefoxJupiterTest.setup(LoadExtensionFirefoxJupiterTest.java:46)
...
Caused by: java.nio.file.AccessDeniedException: ...\test-classes\web-extension
at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(Unknown Source)
at java.nio.file.Files.newByteChannel(Unknown Source)
at java.nio.file.Files.newByteChannel(Unknown Source)
at java.nio.file.Files.readAllBytes(Unknown Source)
at org.openqa.selenium.firefox.AddHasExtensions$1.installExtension(AddHasExtensions.java:80)
... 63 more
It seems that it is not possible to give the folder in which the extension (under development) is located. If I use the packaged extension (as a zip), I managed to make it work, but I would like to use the unpackaged folder.
Is it not possible to use an unpackaged extension in Firefox (like in Chrome with --load-extension
)?
First, in Chrome it would be easier to use the provided extension
key instead of an argument, which is accessed via addExtensions()
Right now, all the languages are just working with zipped files not directories. 4.1.1 works if you zipped the directory yourself and used that file as the argument.
I think an argument can be made that this should be Selenium's responsibility instead of the user's responsibility, but since this isn't something that blocks a user from accessing the temporary extension functionality, I think it belongs in a separate feature request.
I think I found a problem in the current implementation of installExtension()
for FirefoxDriver
in Java. I use a method to zip the extension used in Firefox (see example). When the extension contains folders with assets (e.g., icons, JavaScript libraries, CSS, etc., for example this), these assets are not loaded by the extension in the browser controlled with Selenium.
I have just discovered that there was no problem in the implementation of installExtension()
as I said in my previous post. The problem was in the method WebDriverManager.zipFolder()
I used to compress the extension. Thanks to PR by @nickgaya, I managed to solve the issue, and now the assets within folders are correctly loaded in my tests. Thanks a lot, @nickgaya!
This was merged, will be available in 4.5
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.