seleniumbase 4.34.7__py3-none-any.whl → 4.36.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. seleniumbase/__init__.py +2 -0
  2. seleniumbase/__version__.py +1 -1
  3. seleniumbase/config/proxy_list.py +2 -2
  4. seleniumbase/console_scripts/sb_mkdir.py +3 -0
  5. seleniumbase/core/browser_launcher.py +160 -38
  6. seleniumbase/core/detect_b_ver.py +12 -13
  7. seleniumbase/core/proxy_helper.py +7 -5
  8. seleniumbase/core/sb_cdp.py +335 -93
  9. seleniumbase/core/sb_driver.py +7 -0
  10. seleniumbase/extensions/ad_block.zip +0 -0
  11. seleniumbase/extensions/disable_csp.zip +0 -0
  12. seleniumbase/fixtures/base_case.py +104 -19
  13. seleniumbase/fixtures/js_utils.py +33 -9
  14. seleniumbase/plugins/base_plugin.py +12 -15
  15. seleniumbase/plugins/driver_manager.py +19 -2
  16. seleniumbase/plugins/pytest_plugin.py +3 -0
  17. seleniumbase/plugins/sb_manager.py +18 -1
  18. seleniumbase/plugins/selenium_plugin.py +1 -0
  19. seleniumbase/undetected/__init__.py +38 -17
  20. seleniumbase/undetected/cdp_driver/__init__.py +2 -0
  21. seleniumbase/undetected/cdp_driver/browser.py +16 -10
  22. seleniumbase/undetected/cdp_driver/cdp_util.py +143 -8
  23. seleniumbase/undetected/cdp_driver/config.py +10 -0
  24. seleniumbase/undetected/cdp_driver/connection.py +1 -1
  25. seleniumbase/undetected/cdp_driver/tab.py +41 -4
  26. seleniumbase/undetected/webelement.py +3 -2
  27. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/METADATA +30 -28
  28. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/RECORD +32 -32
  29. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/WHEEL +1 -1
  30. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/entry_points.txt +0 -0
  31. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info/licenses}/LICENSE +0 -0
  32. {seleniumbase-4.34.7.dist-info → seleniumbase-4.36.2.dist-info}/top_level.txt +0 -0
@@ -452,14 +452,24 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
452
452
  self.service.start()
453
453
  with suppress(Exception):
454
454
  self.start_session()
455
+ time.sleep(0.0075)
455
456
  with suppress(Exception):
456
- if self.current_url.startswith("chrome-extension://"):
457
- self.close()
458
- if self.service.is_connectable():
459
- self.stop_client()
460
- self.service.stop()
461
- self.service.start()
462
- self.start_session()
457
+ for window_handle in self.window_handles:
458
+ self.switch_to.window(window_handle)
459
+ if self.current_url.startswith(
460
+ "chrome-extension://"
461
+ ):
462
+ # https://issues.chromium.org/issues/396611138
463
+ # (Uncomment below when resolved)
464
+ # self.close()
465
+ if self.service.is_connectable():
466
+ self.stop_client()
467
+ self.service.stop()
468
+ self.service.start()
469
+ self.start_session()
470
+ time.sleep(0.003)
471
+ with suppress(Exception):
472
+ self.switch_to.window(self.window_handles[-1])
463
473
  self._is_connected = True
464
474
 
465
475
  def disconnect(self):
@@ -469,6 +479,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
469
479
  with suppress(Exception):
470
480
  if self.service.is_connectable():
471
481
  self.stop_client()
482
+ time.sleep(0.003)
472
483
  self.service.stop()
473
484
  self._is_connected = False
474
485
 
@@ -480,14 +491,24 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
480
491
  self.service.start()
481
492
  with suppress(Exception):
482
493
  self.start_session()
494
+ time.sleep(0.0075)
483
495
  with suppress(Exception):
484
- if self.current_url.startswith("chrome-extension://"):
485
- self.close()
486
- if self.service.is_connectable():
487
- self.stop_client()
488
- self.service.stop()
489
- self.service.start()
490
- self.start_session()
496
+ for window_handle in self.window_handles:
497
+ self.switch_to.window(window_handle)
498
+ if self.current_url.startswith(
499
+ "chrome-extension://"
500
+ ):
501
+ # https://issues.chromium.org/issues/396611138
502
+ # (Uncomment below when resolved)
503
+ # self.close()
504
+ if self.service.is_connectable():
505
+ self.stop_client()
506
+ self.service.stop()
507
+ self.service.start()
508
+ self.start_session()
509
+ time.sleep(0.003)
510
+ with suppress(Exception):
511
+ self.switch_to.window(self.window_handles[-1])
491
512
  self._is_connected = True
492
513
 
493
514
  def start_session(self, capabilities=None):
@@ -576,14 +597,14 @@ def find_chrome_executable():
576
597
  if IS_POSIX:
577
598
  for item in os.environ.get("PATH").split(os.pathsep):
578
599
  for subitem in (
579
- "chromium",
580
600
  "google-chrome",
581
- "chromium-browser",
582
- "chrome",
583
601
  "google-chrome-stable",
584
602
  "google-chrome-beta",
585
603
  "google-chrome-dev",
586
604
  "google-chrome-unstable",
605
+ "chrome",
606
+ "chromium",
607
+ "chromium-browser",
587
608
  ):
588
609
  candidates.add(os.sep.join((item, subitem)))
589
610
  if "darwin" in sys.platform:
@@ -1 +1,3 @@
1
1
  from seleniumbase.undetected.cdp_driver import cdp_util # noqa
2
+ from seleniumbase.undetected.cdp_driver.cdp_util import start_async # noqa
3
+ from seleniumbase.undetected.cdp_driver.cdp_util import start_sync # noqa
@@ -15,6 +15,7 @@ import urllib.parse
15
15
  import urllib.request
16
16
  import warnings
17
17
  from collections import defaultdict
18
+ from seleniumbase import config as sb_config
18
19
  from typing import List, Set, Tuple, Union
19
20
  import mycdp as cdp
20
21
  from . import cdp_util as util
@@ -265,6 +266,8 @@ class Browser:
265
266
  :param new_window: Open new window
266
267
  :return: Page
267
268
  """
269
+ if url and ":" not in url:
270
+ url = "https://" + url
268
271
  if new_tab or new_window:
269
272
  # Create new target using the browser session.
270
273
  target_id = await self.connection.send(
@@ -285,6 +288,9 @@ class Browser:
285
288
  filter(lambda item: item.type_ == "page", self.targets)
286
289
  )
287
290
  # Use the tab to navigate to new url
291
+ if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
292
+ await connection.send(cdp.page.navigate("about:blank"))
293
+ await connection.set_locale(sb_config._cdp_locale)
288
294
  frame_id, loader_id, *_ = await connection.send(
289
295
  cdp.page.navigate(url)
290
296
  )
@@ -565,10 +571,11 @@ class Browser:
565
571
  # asyncio.get_running_loop().create_task(
566
572
  # self.connection.send(cdp.browser.close())
567
573
  # )
568
- asyncio.get_event_loop().create_task(self.connection.aclose())
569
- logger.debug(
570
- "Closed the connection using get_event_loop().create_task()"
571
- )
574
+ if self.connection:
575
+ asyncio.get_event_loop().create_task(self.connection.aclose())
576
+ logger.debug(
577
+ "Closed connection using get_event_loop().create_task()"
578
+ )
572
579
  except RuntimeError:
573
580
  if self.connection:
574
581
  try:
@@ -653,7 +660,7 @@ class CookieJar:
653
660
  break
654
661
  else:
655
662
  connection = self._browser.connection
656
- cookies = await connection.send(cdp.storage.get_cookies())
663
+ cookies = await connection.send(cdp.network.get_cookies())
657
664
  if requests_cookie_format:
658
665
  import requests.cookies
659
666
 
@@ -683,8 +690,7 @@ class CookieJar:
683
690
  break
684
691
  else:
685
692
  connection = self._browser.connection
686
- cookies = await connection.send(cdp.storage.get_cookies())
687
- await connection.send(cdp.storage.set_cookies(cookies))
693
+ await connection.send(cdp.network.set_cookies(cookies))
688
694
 
689
695
  async def save(self, file: PathLike = ".session.dat", pattern: str = ".*"):
690
696
  """
@@ -711,7 +717,7 @@ class CookieJar:
711
717
  break
712
718
  else:
713
719
  connection = self._browser.connection
714
- cookies = await connection.send(cdp.storage.get_cookies())
720
+ cookies = await connection.send(cdp.network.get_cookies())
715
721
  # if not connection:
716
722
  # return
717
723
  # if not connection.websocket:
@@ -769,7 +775,7 @@ class CookieJar:
769
775
  cookie.value,
770
776
  )
771
777
  break
772
- await connection.send(cdp.storage.set_cookies(included_cookies))
778
+ await connection.send(cdp.network.set_cookies(included_cookies))
773
779
 
774
780
  async def clear(self):
775
781
  """
@@ -784,7 +790,7 @@ class CookieJar:
784
790
  break
785
791
  else:
786
792
  connection = self._browser.connection
787
- cookies = await connection.send(cdp.storage.get_cookies())
793
+ cookies = await connection.send(cdp.network.get_cookies())
788
794
  if cookies:
789
795
  await connection.send(cdp.storage.clear_cookies())
790
796
 
@@ -4,12 +4,15 @@ import asyncio
4
4
  import fasteners
5
5
  import logging
6
6
  import os
7
+ import sys
7
8
  import time
8
9
  import types
9
10
  import typing
10
11
  from contextlib import suppress
11
12
  from seleniumbase import config as sb_config
12
13
  from seleniumbase.config import settings
14
+ from seleniumbase.core import detect_b_ver
15
+ from seleniumbase.core import proxy_helper
13
16
  from seleniumbase.fixtures import constants
14
17
  from seleniumbase.fixtures import shared_utils
15
18
  from typing import Optional, List, Union, Callable
@@ -22,6 +25,7 @@ import mycdp as cdp
22
25
 
23
26
  logger = logging.getLogger(__name__)
24
27
  IS_LINUX = shared_utils.is_linux()
28
+ PROXY_DIR_LOCK = proxy_helper.PROXY_DIR_LOCK
25
29
  T = typing.TypeVar("T")
26
30
 
27
31
 
@@ -138,6 +142,85 @@ def __activate_virtual_display_as_needed(
138
142
  __activate_standard_virtual_display()
139
143
 
140
144
 
145
+ def __set_proxy_filenames():
146
+ DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER
147
+ for num in range(1000):
148
+ PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir_%s" % num)
149
+ if os.path.exists(PROXY_DIR_PATH):
150
+ continue
151
+ proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
152
+ return
153
+ # Exceeded upper bound. Use Defaults:
154
+ PROXY_DIR_PATH = os.path.join(DOWNLOADS_DIR, "proxy_ext_dir")
155
+ proxy_helper.PROXY_DIR_PATH = PROXY_DIR_PATH
156
+
157
+
158
+ def __add_chrome_ext_dir(extension_dir, dir_path):
159
+ # Add dir_path to the existing extension_dir
160
+ option_exists = False
161
+ if extension_dir:
162
+ option_exists = True
163
+ extension_dir = "%s,%s" % (
164
+ extension_dir, os.path.realpath(dir_path)
165
+ )
166
+ if not option_exists:
167
+ extension_dir = os.path.realpath(dir_path)
168
+ return extension_dir
169
+
170
+
171
+ def __add_chrome_proxy_extension(
172
+ extension_dir,
173
+ proxy_string,
174
+ proxy_user,
175
+ proxy_pass,
176
+ proxy_bypass_list=None,
177
+ multi_proxy=False,
178
+ ):
179
+ """Implementation of https://stackoverflow.com/a/35293284/7058266
180
+ for https://stackoverflow.com/q/12848327/7058266
181
+ (Run Selenium on a proxy server that requires authentication.)"""
182
+ args = " ".join(sys.argv)
183
+ bypass_list = proxy_bypass_list
184
+ if (
185
+ not ("-n" in sys.argv or " -n=" in args or args == "-c")
186
+ and not multi_proxy
187
+ ):
188
+ # Single-threaded
189
+ proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
190
+ with proxy_dir_lock:
191
+ proxy_helper.create_proxy_ext(
192
+ proxy_string,
193
+ proxy_user,
194
+ proxy_pass,
195
+ bypass_list,
196
+ zip_it=False,
197
+ )
198
+ proxy_dir_path = proxy_helper.PROXY_DIR_PATH
199
+ extension_dir = __add_chrome_ext_dir(
200
+ extension_dir, proxy_dir_path
201
+ )
202
+ else:
203
+ # Multi-threaded
204
+ proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
205
+ with proxy_dir_lock:
206
+ with suppress(Exception):
207
+ shared_utils.make_writable(PROXY_DIR_LOCK)
208
+ if multi_proxy:
209
+ __set_proxy_filenames()
210
+ if not os.path.exists(proxy_helper.PROXY_DIR_PATH):
211
+ proxy_helper.create_proxy_ext(
212
+ proxy_string,
213
+ proxy_user,
214
+ proxy_pass,
215
+ bypass_list,
216
+ zip_it=False,
217
+ )
218
+ extension_dir = __add_chrome_ext_dir(
219
+ extension_dir, proxy_helper.PROXY_DIR_PATH
220
+ )
221
+ return extension_dir
222
+
223
+
141
224
  async def start(
142
225
  config: Optional[Config] = None,
143
226
  *,
@@ -149,12 +232,14 @@ async def start(
149
232
  browser_args: Optional[List[str]] = None,
150
233
  xvfb_metrics: Optional[List[str]] = None, # "Width,Height" for Linux
151
234
  sandbox: Optional[bool] = True,
152
- lang: Optional[str] = None,
153
- host: Optional[str] = None,
154
- port: Optional[int] = None,
235
+ lang: Optional[str] = None, # Set the Language Locale Code
236
+ host: Optional[str] = None, # Chrome remote-debugging-host
237
+ port: Optional[int] = None, # Chrome remote-debugging-port
155
238
  xvfb: Optional[int] = None, # Use a special virtual display on Linux
156
239
  headed: Optional[bool] = None, # Override default Xvfb mode on Linux
157
240
  expert: Optional[bool] = None, # Open up closed Shadow-root elements
241
+ proxy: Optional[str] = None, # "host:port" or "user:pass@host:port"
242
+ extension_dir: Optional[str] = None, # Chrome extension directory
158
243
  **kwargs: Optional[dict],
159
244
  ) -> Browser:
160
245
  """
@@ -199,6 +284,18 @@ async def start(
199
284
  if IS_LINUX and not headless and not headed and not xvfb:
200
285
  xvfb = True # The default setting on Linux
201
286
  __activate_virtual_display_as_needed(headless, headed, xvfb, xvfb_metrics)
287
+ if proxy and "@" in str(proxy):
288
+ user_with_pass = proxy.split("@")[0]
289
+ if ":" in user_with_pass:
290
+ proxy_user = user_with_pass.split(":")[0]
291
+ proxy_pass = user_with_pass.split(":")[1]
292
+ proxy_string = proxy.split("@")[1]
293
+ extension_dir = __add_chrome_proxy_extension(
294
+ extension_dir,
295
+ proxy_string,
296
+ proxy_user,
297
+ proxy_pass,
298
+ )
202
299
  if not config:
203
300
  config = Config(
204
301
  user_data_dir,
@@ -212,13 +309,27 @@ async def start(
212
309
  host=host,
213
310
  port=port,
214
311
  expert=expert,
312
+ proxy=proxy,
313
+ extension_dir=extension_dir,
215
314
  **kwargs,
216
315
  )
316
+ driver = None
217
317
  try:
218
- return await Browser.create(config)
318
+ driver = await Browser.create(config)
219
319
  except Exception:
220
320
  time.sleep(0.15)
221
- return await Browser.create(config)
321
+ driver = await Browser.create(config)
322
+ if proxy and "@" in str(proxy):
323
+ time.sleep(0.15)
324
+ if lang:
325
+ sb_config._cdp_locale = lang
326
+ elif "locale" in kwargs:
327
+ sb_config._cdp_locale = kwargs["locale"]
328
+ elif "locale_code" in kwargs:
329
+ sb_config._cdp_locale = kwargs["locale_code"]
330
+ else:
331
+ sb_config._cdp_locale = None
332
+ return driver
222
333
 
223
334
 
224
335
  async def start_async(*args, **kwargs) -> Browser:
@@ -226,7 +337,15 @@ async def start_async(*args, **kwargs) -> Browser:
226
337
  binary_location = None
227
338
  if "browser_executable_path" in kwargs:
228
339
  binary_location = kwargs["browser_executable_path"]
229
- if shared_utils.is_chrome_130_or_newer(binary_location):
340
+ else:
341
+ binary_location = detect_b_ver.get_binary_location("google-chrome")
342
+ if binary_location and not os.path.exists(binary_location):
343
+ binary_location = None
344
+ if (
345
+ shared_utils.is_chrome_130_or_newer(binary_location)
346
+ and "user_data_dir" in kwargs
347
+ and kwargs["user_data_dir"]
348
+ ):
230
349
  if "headless" in kwargs:
231
350
  headless = kwargs["headless"]
232
351
  decoy_args = kwargs
@@ -241,12 +360,28 @@ async def start_async(*args, **kwargs) -> Browser:
241
360
 
242
361
 
243
362
  def start_sync(*args, **kwargs) -> Browser:
244
- loop = asyncio.get_event_loop()
363
+ loop = None
364
+ if (
365
+ "loop" in kwargs
366
+ and kwargs["loop"]
367
+ and hasattr(kwargs["loop"], "create_task")
368
+ ):
369
+ loop = kwargs["loop"]
370
+ else:
371
+ loop = asyncio.new_event_loop()
245
372
  headless = False
246
373
  binary_location = None
247
374
  if "browser_executable_path" in kwargs:
248
375
  binary_location = kwargs["browser_executable_path"]
249
- if shared_utils.is_chrome_130_or_newer(binary_location):
376
+ else:
377
+ binary_location = detect_b_ver.get_binary_location("google-chrome")
378
+ if binary_location and not os.path.exists(binary_location):
379
+ binary_location = None
380
+ if (
381
+ shared_utils.is_chrome_130_or_newer(binary_location)
382
+ and "user_data_dir" in kwargs
383
+ and kwargs["user_data_dir"]
384
+ ):
250
385
  if "headless" in kwargs:
251
386
  headless = kwargs["headless"]
252
387
  decoy_args = kwargs
@@ -40,6 +40,8 @@ class Config:
40
40
  host: str = AUTO,
41
41
  port: int = AUTO,
42
42
  expert: bool = AUTO,
43
+ proxy: Optional[str] = None,
44
+ extension_dir: Optional[str] = None,
43
45
  **kwargs: dict,
44
46
  ):
45
47
  """
@@ -91,6 +93,8 @@ class Config:
91
93
  self.host = host
92
94
  self.port = port
93
95
  self.expert = expert
96
+ self.proxy = proxy
97
+ self.extension_dir = extension_dir
94
98
  self._extensions = []
95
99
  # When using posix-ish operating system and running as root,
96
100
  # you must use no_sandbox=True
@@ -195,6 +199,12 @@ class Config:
195
199
  "--disable-web-security",
196
200
  "--disable-site-isolation-trials",
197
201
  ]
202
+ if self.proxy:
203
+ args.append("--proxy-server=%s" % self.proxy.split("@")[-1])
204
+ args.append("--ignore-certificate-errors")
205
+ args.append("--ignore-ssl-errors=yes")
206
+ if self.extension_dir:
207
+ args.append("--load-extension=%s" % self.extension_dir)
198
208
  if self._browser_args:
199
209
  args.extend([arg for arg in self._browser_args if arg not in args])
200
210
  if self.headless:
@@ -382,7 +382,7 @@ class Connection(metaclass=CantTouchThis):
382
382
  async def send(
383
383
  self,
384
384
  cdp_obj: Generator[dict[str, Any], dict[str, Any], Any],
385
- _is_update=False,
385
+ _is_update=True,
386
386
  ) -> Any:
387
387
  """
388
388
  Send a protocol command.
@@ -1,7 +1,10 @@
1
1
  from __future__ import annotations
2
2
  import asyncio
3
+ import base64
4
+ import datetime
3
5
  import logging
4
6
  import pathlib
7
+ import urllib.parse
5
8
  import warnings
6
9
  from typing import Dict, List, Union, Optional, Tuple
7
10
  from . import browser as cdp_browser
@@ -852,6 +855,7 @@ class Tab(Connection):
852
855
  await self.send(
853
856
  cdp.target.close_target(target_id=self.target.target_id)
854
857
  )
858
+ await asyncio.sleep(0.1)
855
859
 
856
860
  async def get_window(self) -> Tuple[
857
861
  cdp.browser.WindowID, cdp.browser.Bounds
@@ -896,6 +900,10 @@ class Tab(Connection):
896
900
  """
897
901
  return await self.set_window_state(left, top, width, height)
898
902
 
903
+ async def set_window_rect(self, left=0, top=0, width=1280, height=1024):
904
+ """Same as set_window_size(). Uses a different naming convention."""
905
+ return await self.set_window_state(left, top, width, height)
906
+
899
907
  async def activate(self):
900
908
  """Active this target (Eg: tab, window, page)"""
901
909
  await self.send(cdp.target.activate_target(self.target.target_id))
@@ -1128,9 +1136,6 @@ class Tab(Connection):
1128
1136
  :return: The path/filename of the saved screenshot.
1129
1137
  :rtype: str
1130
1138
  """
1131
- import urllib.parse
1132
- import datetime
1133
-
1134
1139
  await self.sleep() # Update the target's URL
1135
1140
  path = None
1136
1141
  if format.lower() in ["jpg", "jpeg"]:
@@ -1161,8 +1166,40 @@ class Tab(Connection):
1161
1166
  "Most possible cause is the page "
1162
1167
  "has not finished loading yet."
1163
1168
  )
1164
- import base64
1169
+ data_bytes = base64.b64decode(data)
1170
+ if not path:
1171
+ raise RuntimeError("Invalid filename or path: '%s'" % filename)
1172
+ path.write_bytes(data_bytes)
1173
+ return str(path)
1165
1174
 
1175
+ async def print_to_pdf(
1176
+ self,
1177
+ filename: Optional[PathLike] = "auto",
1178
+ ) -> str:
1179
+ """
1180
+ Saves a webpage as a PDF.
1181
+ :param filename: uses this as the save path
1182
+ :type filename: PathLike
1183
+ :return: The path/filename of the saved screenshot.
1184
+ :rtype: str
1185
+ """
1186
+ await self.sleep() # Update the target's URL
1187
+ path = None
1188
+ ext = ".pdf"
1189
+ if not filename or filename == "auto":
1190
+ parsed = urllib.parse.urlparse(self.target.url)
1191
+ parts = parsed.path.split("/")
1192
+ last_part = parts[-1]
1193
+ last_part = last_part.rsplit("?", 1)[0]
1194
+ dt_str = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
1195
+ candidate = f"{parsed.hostname}__{last_part}_{dt_str}"
1196
+ path = pathlib.Path(candidate + ext) # noqa
1197
+ else:
1198
+ path = pathlib.Path(filename)
1199
+ path.parent.mkdir(parents=True, exist_ok=True)
1200
+ data, _ = await self.send(cdp.page.print_to_pdf())
1201
+ if not data:
1202
+ raise ProtocolException("Could not save PDF.")
1166
1203
  data_bytes = base64.b64decode(data)
1167
1204
  if not path:
1168
1205
  raise RuntimeError("Invalid filename or path: '%s'" % filename)
@@ -26,10 +26,11 @@ class WebElement(selenium.webdriver.remote.webelement.WebElement):
26
26
  driver.js_click(selector, by=by, timeout=1)
27
27
  else:
28
28
  super().click()
29
+ driver = self._parent
29
30
  if not reconnect_time:
30
- self._parent.reconnect(0.5)
31
+ driver.reconnect(0.5)
31
32
  else:
32
- self._parent.reconnect(reconnect_time)
33
+ driver.reconnect(reconnect_time)
33
34
 
34
35
  def uc_reconnect(self, reconnect_time=None):
35
36
  if not reconnect_time: