Skip to content

Commit 1804f48

Browse files
committed
[py] add initial support for selenium manager
1 parent 0ce65b1 commit 1804f48

File tree

4 files changed

+117
-37
lines changed

4 files changed

+117
-37
lines changed

.github/workflows/ci-python.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,8 @@ jobs:
142142
uses: actions/setup-java@v1
143143
with:
144144
java-version: '11'
145-
- name: Setup Chrome and ChromeDriver
146-
uses: ./.github/actions/setup-chrome
145+
- name: Setup Chrome
146+
uses: browser-actions/setup-chrome@latest
147147
- name: Start XVFB
148148
run: Xvfb :99 &
149149
- name: Start Fluxbox
@@ -185,8 +185,11 @@ jobs:
185185
uses: actions/setup-java@v1
186186
with:
187187
java-version: '11'
188-
- name: Setup Firefox and GeckoDriver
189-
uses: ./.github/actions/setup-firefox
188+
- name: Setup Firefox
189+
uses: abhi1693/setup-browser@v0.3.4
190+
with:
191+
browser: firefox
192+
version: latest
190193
- name: Start XVFB
191194
run: Xvfb :99 &
192195
- name: Start Fluxbox
@@ -227,8 +230,11 @@ jobs:
227230
uses: actions/setup-java@v1
228231
with:
229232
java-version: '11'
230-
- name: Setup Firefox and GeckoDriver
231-
uses: ./.github/actions/setup-firefox
233+
- name: Setup Firefox
234+
uses: abhi1693/setup-browser@v0.3.4
235+
with:
236+
browser: firefox
237+
version: latest
232238
- name: Start Xvfb
233239
run: Xvfb :99 &
234240
- name: Start Fluxbox

py/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ TEST_DEPS = [
4545
requirement("zipp"),
4646
]
4747

48+
copy_file(
49+
name = "manager-macos",
50+
src = "//common/manager:macos/selenium-manager",
51+
out = "selenium/webdriver/common/macos/selenium-manager",
52+
)
53+
54+
copy_file(
55+
name = "manager-linux",
56+
src = "//common/manager:linux/selenium-manager",
57+
out = "selenium/webdriver/common/linux/selenium-manager",
58+
)
59+
60+
copy_file(
61+
name = "manager-windows",
62+
src = "//common/manager:windows/selenium-manager.exe",
63+
out = "selenium/webdriver/common/windows/selenium-manager.exe",
64+
)
65+
4866
copy_file(
4967
name = "get-attribute",
5068
src = "//javascript/webdriver/atoms:get-attribute.js",
@@ -106,6 +124,9 @@ py_library(
106124
":get-attribute",
107125
":is-displayed",
108126
":mutation-listener",
127+
":manager-linux",
128+
":manager-macos",
129+
":manager-windows",
109130
] + [":create-cdp-srcs-" + n for n in BROWSER_VERSIONS],
110131
imports = ["."],
111132
visibility = ["//visibility:public"],
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
import logging
18+
import os
19+
import subprocess
20+
21+
logger = logging.getLogger(__name__)
22+
23+
24+
class SeleniumManager:
25+
26+
@classmethod
27+
def driver_location(cls, browser: str) -> str:
28+
path = os.path.dirname(__file__) + "/macos/selenium-manager"
29+
args = [path, '--browser', browser]
30+
logger.info("executing selenium manager with: " + ' '.join(args))
31+
result = subprocess.run(args, stdout=subprocess.PIPE)
32+
command = result.stdout.decode('utf-8').split('\t')[-1].strip()
33+
logger.info("using driver at: " + command)
34+
return command

py/selenium/webdriver/common/service.py

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@
3232
from selenium.common.exceptions import WebDriverException
3333
from selenium.types import SubprocessStdAlias
3434
from selenium.webdriver.common import utils
35+
from selenium.webdriver.common.selenium_manager import SeleniumManager
3536

36-
log = logging.getLogger(__name__)
37+
logger = logging.getLogger(__name__)
3738

3839

3940
_HAS_NATIVE_DEVNULL = True
@@ -87,35 +88,21 @@ def start(self) -> None:
8788
or when it can't connect to the service
8889
"""
8990
try:
90-
cmd = [self.path]
91-
cmd.extend(self.command_line_args())
92-
self.process = subprocess.Popen(
93-
cmd,
94-
env=self.env,
95-
close_fds=system() != "Windows",
96-
stdout=self.log_file,
97-
stderr=self.log_file,
98-
stdin=PIPE,
99-
creationflags=self.creation_flags,
100-
)
101-
log.debug(f"Started executable: `{self.path}` in a child process with pid: {self.process.pid}")
102-
except TypeError:
103-
raise
104-
except OSError as err:
105-
if err.errno == errno.ENOENT:
106-
raise WebDriverException(
107-
f"'{os.path.basename(self.path)}' executable needs to be in PATH. {self.start_error_message}"
108-
)
109-
elif err.errno == errno.EACCES:
110-
raise WebDriverException(
111-
f"'{os.path.basename(self.path)}' executable may have wrong permissions. {self.start_error_message}"
112-
)
113-
else:
114-
raise
115-
except Exception as e:
116-
raise WebDriverException(
117-
f"The executable {os.path.basename(self.path)} needs to be available in the path. {self.start_error_message}\n{str(e)}"
118-
)
91+
self._start_process(self.path)
92+
except WebDriverException as err:
93+
if "executable needs to be in PATH" in err.msg:
94+
logger.info("driver not found in PATH, trying Selenium Manager")
95+
browser = self.__class__.__module__.split('.')[-2]
96+
try:
97+
path = SeleniumManager.driver_location(browser)
98+
except OSError as new_err:
99+
if new_err.errno == errno.ENOENT:
100+
raise err
101+
else:
102+
raise new_err
103+
104+
self._start_process(path)
105+
119106
count = 0
120107
while True:
121108
self.assert_process_still_running()
@@ -183,11 +170,43 @@ def _terminate_process(self) -> None:
183170
# Todo: only SIGKILL if necessary; the process may be cleanly exited by now.
184171
self.process.kill()
185172
except OSError:
186-
log.error("Error terminating service process.", exc_info=True)
173+
logger.error("Error terminating service process.", exc_info=True)
187174

188175
def __del__(self) -> None:
189176
# `subprocess.Popen` doesn't send signal on `__del__`;
190177
# so we attempt to close the launched process when `__del__`
191178
# is triggered.
192179
with contextlib.suppress(Exception):
193180
self.stop()
181+
182+
def _start_process(self, path: str):
183+
try:
184+
cmd = [path]
185+
cmd.extend(self.command_line_args())
186+
self.process = subprocess.Popen(
187+
cmd,
188+
env=self.env,
189+
close_fds=system() != "Windows",
190+
stdout=self.log_file,
191+
stderr=self.log_file,
192+
stdin=PIPE,
193+
creationflags=self.creation_flags,
194+
)
195+
logger.debug(f"Started executable: `{self.path}` in a child process with pid: {self.process.pid}")
196+
except TypeError:
197+
raise
198+
except OSError as err:
199+
if err.errno == errno.ENOENT:
200+
raise WebDriverException(
201+
f"'{os.path.basename(self.path)}' executable needs to be in PATH. {self.start_error_message}"
202+
)
203+
elif err.errno == errno.EACCES:
204+
raise WebDriverException(
205+
f"'{os.path.basename(self.path)}' executable may have wrong permissions. {self.start_error_message}"
206+
)
207+
else:
208+
raise
209+
except Exception as e:
210+
raise WebDriverException(
211+
f"The executable {os.path.basename(self.path)} needs to be available in the path. {self.start_error_message}\n{str(e)}"
212+
)

0 commit comments

Comments
 (0)