Skip to content

Commit 4153f72

Browse files
[py] Add the ability to use Options classes on Safari
1 parent 0b2ab18 commit 4153f72

File tree

3 files changed

+199
-6
lines changed

3 files changed

+199
-6
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
from typing import Union
18+
import warnings
19+
from selenium.common.exceptions import InvalidArgumentException
20+
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
21+
from selenium.webdriver.common.proxy import Proxy
22+
from selenium.webdriver.common.options import ArgOptions
23+
24+
25+
class Log(object):
26+
def __init__(self):
27+
self.level = None
28+
29+
def to_capabilities(self) -> dict:
30+
if self.level:
31+
return {"log": {"level": self.level}}
32+
return {}
33+
34+
35+
class Options(ArgOptions):
36+
KEY = "webkit:safariOptions"
37+
38+
def __init__(self):
39+
super(Options, self).__init__()
40+
self._binary_location = None
41+
self._preferences: dict = {}
42+
self._proxy = None
43+
self.log = Log()
44+
45+
@property
46+
def binary_location(self):
47+
"""
48+
:Returns: The location of the browser binary otherwise an empty string
49+
"""
50+
return self._binary_location
51+
52+
@binary_location.setter
53+
def binary_location(self, value):
54+
"""
55+
Allows you to set the browser binary to launch
56+
57+
:Args:
58+
- value : path to the browser binary
59+
"""
60+
self._binary_location = value
61+
62+
@property
63+
def accept_insecure_certs(self) -> bool:
64+
return self._caps.get('acceptInsecureCerts')
65+
66+
@accept_insecure_certs.setter
67+
def accept_insecure_certs(self, value: bool):
68+
self._caps['acceptInsecureCerts'] = value
69+
70+
@property
71+
def page_load_strategy(self) -> str:
72+
return self._caps["pageLoadStrategy"]
73+
74+
@page_load_strategy.setter
75+
def page_load_strategy(self, strategy: str):
76+
if strategy in ["normal", "eager", "none"]:
77+
self.set_capability("pageLoadStrategy", strategy)
78+
else:
79+
raise ValueError("Strategy can only be one of the following: normal, eager, none")
80+
81+
def to_capabilities(self) -> dict:
82+
"""Marshals the options to an desired capabilities object.
83+
"""
84+
# This intentionally looks at the internal properties
85+
# so if a binary or profile has _not_ been set,
86+
# it will defer to geckodriver to find the system Firefox
87+
# and generate a fresh profile.
88+
caps = self._caps
89+
opts = {}
90+
91+
if self._arguments:
92+
opts["args"] = self._arguments
93+
if self._binary_location:
94+
opts["binary"] = self._binary_location
95+
opts.update(self.log.to_capabilities())
96+
97+
if opts:
98+
caps[Options.KEY] = opts
99+
100+
return caps
101+
102+
@property
103+
def default_capabilities(self) -> dict:
104+
return DesiredCapabilities.SAFARI.copy()

py/selenium/webdriver/safari/webdriver.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,34 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17-
from selenium.common.exceptions import WebDriverException
1817

1918
try:
2019
import http.client as http_client
2120
except ImportError:
2221
import httplib as http_client
2322

23+
import warnings
24+
25+
from selenium.common.exceptions import WebDriverException
2426
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
2527
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
28+
from .options import Options
2629
from .service import Service
2730
from .remote_connection import SafariRemoteConnection
2831

32+
DEFAULT_EXECUTABLE_PATH = "/usr/bin/safaridriver"
33+
DEFAULT_SAFARI_CAPS = DesiredCapabilities.SAFARI.copy()
34+
2935

3036
class WebDriver(RemoteWebDriver):
3137
"""
3238
Controls the SafariDriver and allows you to drive the browser.
3339
3440
"""
3541

36-
def __init__(self, port=0, executable_path="/usr/bin/safaridriver", reuse_service=False,
37-
desired_capabilities=DesiredCapabilities.SAFARI, quiet=False,
38-
keep_alive=True, service_args=None):
42+
def __init__(self, port=0, executable_path=DEFAULT_EXECUTABLE_PATH, reuse_service=False,
43+
desired_capabilities=DEFAULT_SAFARI_CAPS, quiet=False,
44+
keep_alive=True, service_args=None, options: Options = None, service: Service = None):
3945
"""
4046
4147
Creates a new Safari driver instance and launches or finds a running safaridriver service.
@@ -47,12 +53,38 @@ def __init__(self, port=0, executable_path="/usr/bin/safaridriver", reuse_servic
4753
- desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Safari switches).
4854
- quiet - If True, the driver's stdout and stderr is suppressed.
4955
- keep_alive - Whether to configure SafariRemoteConnection to use
50-
HTTP keep-alive. Defaults to False.
56+
HTTP keep-alive. Defaults to True.
5157
- service_args : List of args to pass to the safaridriver service
5258
"""
59+
if port == 0:
60+
warnings.warn("port has been deprecated, please set it via the service class",
61+
DeprecationWarning, stacklevel=2)
62+
63+
if executable_path != DEFAULT_EXECUTABLE_PATH:
64+
warnings.warn("executable_path has been deprecated, please use the Options class to set it",
65+
DeprecationWarning, stacklevel=2)
66+
if not reuse_service:
67+
warnings.warn("reuse_service has been deprecated, please use the Service class to set it",
68+
DeprecationWarning, stacklevel=2)
69+
if desired_capabilities != DEFAULT_SAFARI_CAPS:
70+
warnings.warn("desired_capabilities has been deprecated, please use the Options class to set it",
71+
DeprecationWarning, stacklevel=2)
72+
if not quiet:
73+
warnings.warn("quiet has been deprecated, please use the Service class to set it",
74+
DeprecationWarning, stacklevel=2)
75+
if not keep_alive:
76+
warnings.warn("keep_alive has been deprecated, please use the Service class to set it",
77+
DeprecationWarning, stacklevel=2)
78+
79+
if service_args:
80+
warnings.warn("service_args has been deprecated, please use the Service class to set it",
81+
DeprecationWarning, stacklevel=2)
5382

5483
self._reuse_service = reuse_service
55-
self.service = Service(executable_path, port=port, quiet=quiet, service_args=service_args)
84+
if service:
85+
self.service = service
86+
else:
87+
self.service = Service(executable_path, port=port, quiet=quiet, service_args=service_args)
5688
if not reuse_service:
5789
self.service.start()
5890

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
18+
import pytest
19+
20+
from selenium.webdriver.safari.options import Options
21+
22+
23+
@pytest.fixture
24+
def options():
25+
return Options()
26+
27+
28+
def test_set_binary_location(options):
29+
options.binary_location = '/foo/bar'
30+
assert options._binary_location == '/foo/bar'
31+
32+
33+
def test_get_binary_location(options):
34+
options._binary_location = '/foo/bar'
35+
assert options.binary_location == '/foo/bar'
36+
37+
38+
def test_creates_capabilities(options):
39+
options._arguments = ['foo']
40+
options._binary_location = '/bar'
41+
caps = options.to_capabilities()
42+
opts = caps.get(Options.KEY)
43+
assert opts
44+
assert 'foo' in opts['args']
45+
assert opts['binary'] == '/bar'
46+
47+
48+
def test_starts_with_default_capabilities(options):
49+
from selenium.webdriver import DesiredCapabilities
50+
caps = DesiredCapabilities.SAFARI.copy()
51+
caps.update({"pageLoadStrategy": "normal"})
52+
assert options._caps == caps
53+
54+
55+
def test_is_a_baseoptions(options):
56+
from selenium.webdriver.common.options import BaseOptions
57+
assert isinstance(options, BaseOptions)

0 commit comments

Comments
 (0)