Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Adding a LaunchApp command to Chrome specific webdriver client. #168

Open
wants to merge 16 commits into from

8 participants

@richardrb

Adding a LaunchApp command for the Chrome specific Java/Python webdriver clients, which is supported by chromedriver 2.9 and above. Many of our users use the client code here, and this will make it easier for Chrome app developers be able to test their apps using chromedriver.

@AutomatedTester

From the Python side this looks ok but I wonder if the command file should be a third party dependency since this is specific to the way that Chrome works.

Your thoughts @jleyba ?

...org/openqa/selenium/chrome/ChromeCommandExecutor.java
@@ -38,18 +43,33 @@
private final ChromeDriverService service;
+ private final static Map<String, CommandInfo> chromeCommandsNameToUrl = ImmutableMap.of(
@jleyba Owner
jleyba added a note
  • Correct modifier order is static final
  • Use CHROME_COMMAND_NAME_TO_URL for the name as this is an immutable class constant.
  • Move this above the definition of the service field.

Done for the points above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...org/openqa/selenium/chrome/ChromeCommandExecutor.java
((18 lines not shown))
this.service = service;
}
/**
+ * Creates a new ChromeCommandExecutor which will communicate with the chromedriver as configured
+ * by the given {@code service}.
+ *
+ * @param service The ChromeDriverService to send commands to.
+ */
+ public ChromeCommandExecutor(URL serviceUrl) {
@jleyba Owner
jleyba added a note

Why did you add this constructor?

I added this constructor to support the case where the chromedriver server is running independently of the client, and we don't want to manage it here in client code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...c/org/openqa/selenium/chrome/ChromeDriverCommand.java
((8 lines not shown))
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package org.openqa.selenium.chrome;
+
+import org.openqa.selenium.remote.DriverCommand;
+
+public interface ChromeDriverCommand extends DriverCommand {
+ String LAUNCH_APP = "launchApp";
@jleyba Owner
jleyba added a note

I know this is how DriverCommand is defined, but it's an anti-pattern we shouldn't use. Please change this to

final class ChromeDriverCommand {
  private ChromeDriverCommand(){}

  static final String LAUNCH_APP = "chromium.launchApp";
}

Done, but I still need to reference the strings in DriverCommand, so I have this class implement it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@richardrb

@AutomatedTester I'd prefer to keep it there, it looks other browsers have specific implementations so it would be consistent with that (ie, firefox, opera, etc... directories). If you feel strongly about it I can put it over in third_party.

@jleyba Thanks for the review! I made changes based on your comments, please take another look when you have a chance.

...c/org/openqa/selenium/chrome/ChromeDriverCommand.java
@@ -0,0 +1,26 @@
+/*
+Copyright 2014 Selenium committers
@lukeis Owner
lukeis added a note

the copyright should just be:
Copyright 2014 Software Freedom Conservancy

(remove the Selenium committers and Google reference)

Done. For here and the other files added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@lukeis
Owner

I don't necessarily agree with putting it in third party, almost makes more sense for chromedriver to release another python package to pypi for users to also grab, since this falls under the section of browser specific extensions to the protocol. @shs96c input appreciated.

@richardrb

Hmm, I think it would be really helpful to end users to have this available in Selenium over having to download another package, in many cases users probably won't know this is available. There's already a hacky way to launch apps through the UI, so including this in Selenium will encourage users to do something better.

@AutomatedTester
@alanpca

@richardrb do you have any example specs which make use of this?

@bayandin

Any updates? @AutomatedTester?
I want to add PerformanceLog feature and I want to do it the same way.

@AutomatedTester
@andreastt andreastt commented on the diff
py/selenium/webdriver/remote/webdriver.py
@@ -62,7 +63,7 @@ def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
proxy.add_to_capabilities(desired_capabilities)
self.command_executor = command_executor
if type(self.command_executor) is bytes or type(self.command_executor) is str:
- self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
+ self.command_executor = remote_connection_client(command_executor, keep_alive=keep_alive)
@andreastt Owner

I can't see that the symbol remote_connection_client is defined here.

@sevaseva Owner

One of the commits in the PR added remote_connection_client parameter to this constructor. However that change is unnecessary and everything in this file needs to be reverted. (I'll add comments in chrome/webdriver.py explaining how)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@andreastt andreastt commented on the diff
py/selenium/webdriver/chrome/command.py
((6 lines not shown))
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from selenium.webdriver.remote.command import Command
+
+class ChromeCommand(Command):
+ """
+ Defines Chrome specific commands while including standard WebDriver ones.
+ """
+ LAUNCH_APP = "launchApp"
@andreastt Owner

Enums isn't really a thing in Python. Please just embed the "launchApp" string in chrome/webdriver.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@andreastt
Owner

@richardrb, this change looks mostly good to me. Can you make the few changes requested and rebase? Sorry it's taken so long to get around to.

@AutomatedTester, I can't load the link you posted. If it's dead, should we close this?

@AutomatedTester

As discussed in https://groups.google.com/forum/m/#!topic/selenium-developers/-GS1wyTH2aI we are going to close this and this will need to be released separately by the Chromium Project

p.s. I don't necessarily agree with this so suggest bringing it up in that email thread.

@sevaseva
Owner

@shs96c @lukeis @andreastt @AutomatedTester

Some 20 days ago there was a post that suggested that how to implement vendor specific commands in the client bindings and where to put those client bindings' code are two separate questions. It didn't generate a lot of stir, fair enough.

If there is anyone who is actively against reopening and merging [1] this PR or against the proposal in the post (which is basically the same thing), can you please reply there or here?

While a decision about the place for vendor specific client code is being made in the other thread I see no reason to block changes in that code.

In fact, IEDriver and FirefoxDriver, ChromeDriver and possibly other vendor specific client side changes went in just fine recently. (phantomjs specific #475 almost made it in, too)

[1] After fixing issues including @andreastt suggestions and reverting unnecessary changes in core files webdriver/remote/webdriver.py and selenium/remote/HttpCommandExecutor.java.

@sevaseva
Owner

Well, maybe there's no one who is actively again this anymore. Let's reopen, make last adjustments and merge this.

@richardrb , can you please

If any of these changes you don't know how or don't have time to do, let me know and I may do them; I'd make sure this change would still go in attributed to you even if I need to make adjustments before pushing.

@sevaseva sevaseva reopened this
@sevaseva sevaseva commented on the diff
py/selenium/webdriver/chrome/webdriver.py
@@ -61,6 +63,7 @@ def __init__(self, executable_path="chromedriver", port=0,
try:
RemoteWebDriver.__init__(self,
command_executor=self.service.service_url,
+ remote_connection_client=ChromeRemoteConnection,
@sevaseva Owner

remote_connection_client parameter is unnecessary, it should not be passed here and reverted from remote/webdriver.py.

What you need to do is to pass an instance of ChromeRemoteConnection as a value of command_executor parameter.

See the current latest revision for an updated documentation that should make this less confusing:
https://github.com/SeleniumHQ/selenium/blob/a19f07aec61d491fa4839c002d65d03347fc21e8/py/selenium/webdriver/remote/webdriver.py#L64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@sevaseva sevaseva commented on the diff
...org/openqa/selenium/chrome/ChromeCommandExecutor.java
@@ -36,20 +41,35 @@
*/
class ChromeCommandExecutor extends HttpCommandExecutor {
+ private static final Map<String, CommandInfo> CHROME_COMMANDS_NAME_TO_URL = ImmutableMap.of(
+ ChromeDriverCommand.LAUNCH_APP,
+ post("/session/:sessionId/chromium/launch_app"));
@sevaseva Owner

Please simply use constructor, new CommandInfo(), instead of method post()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@sevaseva sevaseva commented on the diff
...c/org/openqa/selenium/remote/HttpCommandExecutor.java
@@ -496,15 +496,15 @@ private Response createResponse(HttpResponse httpResponse, HttpContext context,
return response;
}
- private static CommandInfo get(String url) {
+ protected static CommandInfo get(String url) {
@sevaseva Owner

I suggest we revert all changes in this file. They are not necessary. Other classes that already create instances of CommandInfo outside of this class simply use CommandInfo constructor(s), we could keep doing the same here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@sevaseva sevaseva commented on the diff
...c/org/openqa/selenium/chrome/ChromeDriverCommand.java
((6 lines not shown))
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package org.openqa.selenium.chrome;
+
+import org.openqa.selenium.remote.DriverCommand;
+
+final class ChromeDriverCommand implements DriverCommand {
@sevaseva Owner

I don't really see how this class/enum helps anything, especially while it is is declared with package-private access. I suggest reverting this class and simply using string literal "chromium.launchApp" in one place where it's used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
32 java/client/src/org/openqa/selenium/chrome/ChromeCommandExecutor.java
@@ -19,15 +19,20 @@
package org.openqa.selenium.chrome;
import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.WebDriverException;
+import org.openqa.selenium.chrome.ChromeDriverCommand;
import org.openqa.selenium.remote.Command;
-import org.openqa.selenium.remote.DriverCommand;
+import org.openqa.selenium.remote.CommandInfo;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.Response;
import java.io.IOException;
import java.net.ConnectException;
+import java.net.URL;
+import java.util.Map;
+import java.util.HashMap;
/**
* A specialized {@link HttpCommandExecutor} that will use a {@link ChromeDriverService} that lives
@@ -36,20 +41,35 @@
*/
class ChromeCommandExecutor extends HttpCommandExecutor {
+ private static final Map<String, CommandInfo> CHROME_COMMANDS_NAME_TO_URL = ImmutableMap.of(
+ ChromeDriverCommand.LAUNCH_APP,
+ post("/session/:sessionId/chromium/launch_app"));
@sevaseva Owner

Please simply use constructor, new CommandInfo(), instead of method post()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
private final ChromeDriverService service;
/**
* Creates a new ChromeCommandExecutor which will communicate with the chromedriver as configured
* by the given {@code service}.
- *
+ *
* @param service The ChromeDriverService to send commands to.
*/
public ChromeCommandExecutor(ChromeDriverService service) {
- super(service.getUrl());
+ super(CHROME_COMMANDS_NAME_TO_URL, service.getUrl());
this.service = service;
}
/**
+ * Creates a new ChromeCommandExecutor which will communicate with the chromedriver as configured
+ * by the given {@code service}.
+ *
+ * @param serviceUrl The URL of the service to communicate with.
+ */
+ public ChromeCommandExecutor(URL serviceUrl) {
+ super(CHROME_COMMANDS_NAME_TO_URL, serviceUrl);
+ this.service = null;
+ }
+
+ /**
* Sends the {@code command} to the chromedriver server for execution. The server will be started
* if requesting a new session. Likewise, if terminating a session, the server will be shutdown
* once a response is received.
@@ -60,7 +80,8 @@ public ChromeCommandExecutor(ChromeDriverService service) {
*/
@Override
public Response execute(Command command) throws IOException {
- if (DriverCommand.NEW_SESSION.equals(command.getName())) {
+ if (ChromeDriverCommand.NEW_SESSION.equals(command.getName()) &&
+ this.service != null) {
service.start();
}
@@ -76,7 +97,8 @@ public Response execute(Command command) throws IOException {
Throwables.propagateIfPossible(t);
throw new WebDriverException(t);
} finally {
- if (DriverCommand.QUIT.equals(command.getName())) {
+ if (ChromeDriverCommand.QUIT.equals(command.getName()) &&
+ this.service != null) {
service.stop();
}
}
View
21 java/client/src/org/openqa/selenium/chrome/ChromeDriver.java
@@ -18,10 +18,16 @@
package org.openqa.selenium.chrome;
+import com.google.common.collect.ImmutableMap;
+
+import java.net.URL;
+
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
+import org.openqa.selenium.chrome.ChromeDriverCommand;
+import org.openqa.selenium.chrome.ChromeCommandExecutor;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.RemoteWebDriver;
@@ -160,6 +166,17 @@ public ChromeDriver(ChromeDriverService service, Capabilities capabilities) {
super(new DriverCommandExecutor(service), capabilities);
}
+ /**
+ * Creates a new ChromeDriver instance. The {@code service} will be started along with the
+ * driver, and shutdown upon calling {@link #quit()}.
+ *
+ * @param service The service to use.
+ * @param capabilities The capabilities required from the ChromeDriver.
+ */
+ public ChromeDriver(URL serviceUrl, Capabilities capabilities) {
+ super(new ChromeCommandExecutor(serviceUrl), capabilities);
+ }
+
@Override
public void setFileDetector(FileDetector detector) {
throw new WebDriverException(
@@ -174,6 +191,10 @@ public void setFileDetector(FileDetector detector) {
return target.convertFromBase64Png(base64);
}
+ public void launchApp(String id) {
+ execute(ChromeDriverCommand.LAUNCH_APP, ImmutableMap.of("id", id));
+ }
+
@Override
protected void startSession(Capabilities desiredCapabilities,
Capabilities requiredCapabilities) {
View
25 java/client/src/org/openqa/selenium/chrome/ChromeDriverCommand.java
@@ -0,0 +1,25 @@
+/*
+Copyright 2014 Software Freedom Conservancy
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package org.openqa.selenium.chrome;
+
+import org.openqa.selenium.remote.DriverCommand;
+
+final class ChromeDriverCommand implements DriverCommand {
@sevaseva Owner

I don't really see how this class/enum helps anything, especially while it is is declared with package-private access. I suggest reverting this class and simply using string literal "chromium.launchApp" in one place where it's used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ private ChromeDriverCommand(){}
+
+ static final String LAUNCH_APP = "chromium.launchApp";
+}
View
6 java/client/src/org/openqa/selenium/remote/HttpCommandExecutor.java
@@ -496,15 +496,15 @@ private Response createResponse(HttpResponse httpResponse, HttpContext context,
return response;
}
- private static CommandInfo get(String url) {
+ protected static CommandInfo get(String url) {
@sevaseva Owner

I suggest we revert all changes in this file. They are not necessary. Other classes that already create instances of CommandInfo outside of this class simply use CommandInfo constructor(s), we could keep doing the same here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
return new CommandInfo(url, HttpVerb.GET);
}
- private static CommandInfo post(String url) {
+ protected static CommandInfo post(String url) {
return new CommandInfo(url, HttpVerb.POST);
}
- private static CommandInfo delete(String url) {
+ protected static CommandInfo delete(String url) {
return new CommandInfo(url, HttpVerb.DELETE);
}
View
21 py/selenium/webdriver/chrome/command.py
@@ -0,0 +1,21 @@
+# Copyright 2014 Software Freedom Conservancy
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from selenium.webdriver.remote.command import Command
+
+class ChromeCommand(Command):
+ """
+ Defines Chrome specific commands while including standard WebDriver ones.
+ """
+ LAUNCH_APP = "launchApp"
@andreastt Owner

Enums isn't really a thing in Python. Please just embed the "launchApp" string in chrome/webdriver.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
23 py/selenium/webdriver/chrome/remote_connection.py
@@ -0,0 +1,23 @@
+# Copyright 2014 Software Freedom Conservancy
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from selenium.webdriver.remote.remote_connection import RemoteConnection
+from selenium.webdriver.chrome.command import ChromeCommand
+
+class ChromeRemoteConnection(RemoteConnection):
+
+ def __init__(self, remote_server_addr, keep_alive=False):
+ RemoteConnection.__init__(self, remote_server_addr, keep_alive)
+ self._commands[ChromeCommand.LAUNCH_APP] =\
+ ('POST', '/session/$sessionId/chromium/launch_app')
View
10 py/selenium/webdriver/chrome/webdriver.py
@@ -15,6 +15,8 @@
# limitations under the License.
import base64
+from command import ChromeCommand
+from remote_connection import ChromeRemoteConnection
from selenium.webdriver.remote.command import Command
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
from selenium.common.exceptions import WebDriverException
@@ -61,6 +63,7 @@ def __init__(self, executable_path="chromedriver", port=0,
try:
RemoteWebDriver.__init__(self,
command_executor=self.service.service_url,
+ remote_connection_client=ChromeRemoteConnection,
@sevaseva Owner

remote_connection_client parameter is unnecessary, it should not be passed here and reverted from remote/webdriver.py.

What you need to do is to pass an instance of ChromeRemoteConnection as a value of command_executor parameter.

See the current latest revision for an updated documentation that should make this less confusing:
https://github.com/SeleniumHQ/selenium/blob/a19f07aec61d491fa4839c002d65d03347fc21e8/py/selenium/webdriver/remote/webdriver.py#L64

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
desired_capabilities=desired_capabilities,
keep_alive=True)
except:
@@ -68,6 +71,13 @@ def __init__(self, executable_path="chromedriver", port=0,
raise
self._is_remote = False
+ def launch_app(self, app_id):
+ """
+ Launches an app with the specified id
+ """
+ return self.execute(ChromeCommand.LAUNCH_APP,
+ {'id': app_id})
+
def quit(self):
"""
Closes the browser and shuts down the ChromeDriver executable
View
5 py/selenium/webdriver/remote/webdriver.py
@@ -45,7 +45,8 @@ class WebDriver(object):
"""
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
- desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False):
+ desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False,
+ remote_connection_client=RemoteConnection):
"""
Create a new driver that will issue commands using the wire protocol.
@@ -62,7 +63,7 @@ def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
proxy.add_to_capabilities(desired_capabilities)
self.command_executor = command_executor
if type(self.command_executor) is bytes or type(self.command_executor) is str:
- self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive)
+ self.command_executor = remote_connection_client(command_executor, keep_alive=keep_alive)
@andreastt Owner

I can't see that the symbol remote_connection_client is defined here.

@sevaseva Owner

One of the commits in the PR added remote_connection_client parameter to this constructor. However that change is unnecessary and everything in this file needs to be reverted. (I'll add comments in chrome/webdriver.py explaining how)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
self._is_remote = True
self.session_id = None
self.capabilities = {}
Something went wrong with that request. Please try again.