seleniumbase 4.44.2__py3-none-any.whl → 4.45.10__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 (42) hide show
  1. seleniumbase/__version__.py +1 -1
  2. seleniumbase/behave/behave_sb.py +14 -0
  3. seleniumbase/console_scripts/run.py +1 -0
  4. seleniumbase/console_scripts/sb_install.py +142 -11
  5. seleniumbase/console_scripts/sb_mkdir.py +76 -0
  6. seleniumbase/console_scripts/sb_mkrec.py +25 -0
  7. seleniumbase/console_scripts/sb_recorder.py +40 -3
  8. seleniumbase/core/browser_launcher.py +279 -117
  9. seleniumbase/core/detect_b_ver.py +8 -6
  10. seleniumbase/core/log_helper.py +11 -16
  11. seleniumbase/core/mysql.py +1 -1
  12. seleniumbase/core/report_helper.py +3 -7
  13. seleniumbase/core/sb_cdp.py +275 -78
  14. seleniumbase/core/sb_driver.py +36 -5
  15. seleniumbase/core/session_helper.py +2 -4
  16. seleniumbase/drivers/chromium_drivers/__init__.py +0 -0
  17. seleniumbase/fixtures/base_case.py +251 -201
  18. seleniumbase/fixtures/constants.py +1 -0
  19. seleniumbase/fixtures/js_utils.py +52 -14
  20. seleniumbase/fixtures/page_actions.py +18 -7
  21. seleniumbase/fixtures/page_utils.py +4 -2
  22. seleniumbase/fixtures/shared_utils.py +2 -4
  23. seleniumbase/masterqa/master_qa.py +16 -2
  24. seleniumbase/plugins/base_plugin.py +8 -0
  25. seleniumbase/plugins/driver_manager.py +15 -5
  26. seleniumbase/plugins/pytest_plugin.py +43 -57
  27. seleniumbase/plugins/sb_manager.py +23 -19
  28. seleniumbase/plugins/selenium_plugin.py +20 -13
  29. seleniumbase/undetected/__init__.py +11 -10
  30. seleniumbase/undetected/cdp.py +1 -12
  31. seleniumbase/undetected/cdp_driver/browser.py +330 -128
  32. seleniumbase/undetected/cdp_driver/cdp_util.py +48 -14
  33. seleniumbase/undetected/cdp_driver/config.py +78 -11
  34. seleniumbase/undetected/cdp_driver/connection.py +15 -43
  35. seleniumbase/undetected/cdp_driver/element.py +37 -22
  36. seleniumbase/undetected/cdp_driver/tab.py +414 -39
  37. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/METADATA +140 -152
  38. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/RECORD +42 -41
  39. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/licenses/LICENSE +1 -1
  40. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/WHEEL +0 -0
  41. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/entry_points.txt +0 -0
  42. {seleniumbase-4.44.2.dist-info → seleniumbase-4.45.10.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
  import asyncio
4
4
  import atexit
5
+ import fasteners
5
6
  import http.cookiejar
6
7
  import json
7
8
  import logging
@@ -15,7 +16,10 @@ import urllib.parse
15
16
  import urllib.request
16
17
  import warnings
17
18
  from collections import defaultdict
19
+ from contextlib import suppress
18
20
  from seleniumbase import config as sb_config
21
+ from seleniumbase.fixtures import constants
22
+ from seleniumbase.fixtures import shared_utils
19
23
  from typing import List, Optional, Set, Tuple, Union
20
24
  import mycdp as cdp
21
25
  from . import cdp_util as util
@@ -34,7 +38,7 @@ def get_registered_instances():
34
38
  def deconstruct_browser():
35
39
  for _ in __registered__instances__:
36
40
  if not _.stopped:
37
- _.stop()
41
+ _.stop(deconstruct=True)
38
42
  for attempt in range(5):
39
43
  try:
40
44
  if _.config and not _.config.uses_custom_data_dir:
@@ -251,6 +255,53 @@ class Browser:
251
255
  )
252
256
  self.targets.remove(current_tab)
253
257
 
258
+ def get_rd_host(self):
259
+ return self.config.host
260
+
261
+ def get_rd_port(self):
262
+ return self.config.port
263
+
264
+ def get_rd_url(self):
265
+ host = self.config.host
266
+ port = self.config.port
267
+ return f"http://{host}:{port}"
268
+
269
+ def get_endpoint_url(self):
270
+ return self.get_rd_url()
271
+
272
+ def get_port(self):
273
+ return self.get_rd_port()
274
+
275
+ async def set_auth(self, username, password, tab):
276
+ async def auth_challenge_handler(event: cdp.fetch.AuthRequired):
277
+ await tab.send(
278
+ cdp.fetch.continue_with_auth(
279
+ request_id=event.request_id,
280
+ auth_challenge_response=cdp.fetch.AuthChallengeResponse(
281
+ response="ProvideCredentials",
282
+ username=username,
283
+ password=password,
284
+ ),
285
+ )
286
+ )
287
+
288
+ async def req_paused(event: cdp.fetch.RequestPaused):
289
+ await tab.send(
290
+ cdp.fetch.continue_request(request_id=event.request_id)
291
+ )
292
+
293
+ tab.add_handler(
294
+ cdp.fetch.RequestPaused,
295
+ lambda event: asyncio.create_task(req_paused(event)),
296
+ )
297
+
298
+ tab.add_handler(
299
+ cdp.fetch.AuthRequired,
300
+ lambda event: asyncio.create_task(auth_challenge_handler(event)),
301
+ )
302
+
303
+ await tab.send(cdp.fetch.enable(handle_auth_requests=True))
304
+
254
305
  async def get(
255
306
  self,
256
307
  url="about:blank",
@@ -266,6 +317,7 @@ class Browser:
266
317
  :param new_window: Open new window
267
318
  :return: Page
268
319
  """
320
+ await asyncio.sleep(0.005)
269
321
  if url and ":" not in url:
270
322
  url = "https://" + url
271
323
  if new_tab or new_window:
@@ -277,114 +329,210 @@ class Browser:
277
329
  )
278
330
  connection: tab.Tab = next(
279
331
  filter(
280
- lambda item: item.type_ == "page" and item.target_id == target_id, # noqa
332
+ lambda item: (
333
+ item.type_ == "page" and item.target_id == target_id
334
+ ),
281
335
  self.targets,
282
336
  )
283
337
  )
284
338
  connection.browser = self
285
339
  else:
286
- # First tab from browser.tabs
287
- connection: tab.Tab = next(
288
- filter(lambda item: item.type_ == "page", self.targets)
340
+ try:
341
+ # Most recently opened tab
342
+ connection = self.targets[-1]
343
+ await connection.sleep(0.005)
344
+ except Exception:
345
+ # First tab from browser.tabs
346
+ connection: tab.Tab = next(
347
+ filter(lambda item: item.type_ == "page", self.targets)
348
+ )
349
+ await connection.sleep(0.005)
350
+ _cdp_timezone = None
351
+ _cdp_user_agent = ""
352
+ _cdp_locale = None
353
+ _cdp_platform = None
354
+ _cdp_disable_csp = None
355
+ _cdp_geolocation = None
356
+ _cdp_mobile_mode = None
357
+ _cdp_recorder = None
358
+ _cdp_ad_block = None
359
+ if getattr(sb_config, "_cdp_timezone", None):
360
+ _cdp_timezone = sb_config._cdp_timezone
361
+ if getattr(sb_config, "_cdp_user_agent", None):
362
+ _cdp_user_agent = sb_config._cdp_user_agent
363
+ if getattr(sb_config, "_cdp_locale", None):
364
+ _cdp_locale = sb_config._cdp_locale
365
+ if getattr(sb_config, "_cdp_platform", None):
366
+ _cdp_platform = sb_config._cdp_platform
367
+ if getattr(sb_config, "_cdp_geolocation", None):
368
+ _cdp_geolocation = sb_config._cdp_geolocation
369
+ if getattr(sb_config, "_cdp_mobile_mode", None):
370
+ _cdp_mobile_mode = sb_config._cdp_mobile_mode
371
+ if getattr(sb_config, "ad_block_on", None):
372
+ _cdp_ad_block = sb_config.ad_block_on
373
+ if getattr(sb_config, "disable_csp", None):
374
+ _cdp_disable_csp = sb_config.disable_csp
375
+ if "timezone" in kwargs:
376
+ _cdp_timezone = kwargs["timezone"]
377
+ elif "tzone" in kwargs:
378
+ _cdp_timezone = kwargs["tzone"]
379
+ if "user_agent" in kwargs:
380
+ _cdp_user_agent = kwargs["user_agent"]
381
+ elif "agent" in kwargs:
382
+ _cdp_user_agent = kwargs["agent"]
383
+ if "locale" in kwargs:
384
+ _cdp_locale = kwargs["locale"]
385
+ elif "lang" in kwargs:
386
+ _cdp_locale = kwargs["lang"]
387
+ elif "locale_code" in kwargs:
388
+ _cdp_locale = kwargs["locale_code"]
389
+ if "platform" in kwargs:
390
+ _cdp_platform = kwargs["platform"]
391
+ elif "plat" in kwargs:
392
+ _cdp_platform = kwargs["plat"]
393
+ if "disable_csp" in kwargs:
394
+ _cdp_disable_csp = kwargs["disable_csp"]
395
+ if "geolocation" in kwargs:
396
+ _cdp_geolocation = kwargs["geolocation"]
397
+ elif "geoloc" in kwargs:
398
+ _cdp_geolocation = kwargs["geoloc"]
399
+ if "ad_block" in kwargs:
400
+ _cdp_ad_block = kwargs["ad_block"]
401
+ if "mobile" in kwargs:
402
+ _cdp_mobile_mode = kwargs["mobile"]
403
+ if "recorder" in kwargs:
404
+ _cdp_recorder = kwargs["recorder"]
405
+ await connection.sleep(0.01)
406
+ await connection.send(cdp.network.enable())
407
+ await connection.sleep(0.01)
408
+ if _cdp_timezone:
409
+ await connection.set_timezone(_cdp_timezone)
410
+ if _cdp_locale:
411
+ await connection.set_locale(_cdp_locale)
412
+ if _cdp_user_agent or _cdp_locale or _cdp_platform:
413
+ await connection.set_user_agent(
414
+ user_agent=_cdp_user_agent,
415
+ accept_language=_cdp_locale,
416
+ platform=_cdp_platform,
289
417
  )
290
- _cdp_timezone = None
291
- _cdp_user_agent = ""
292
- _cdp_locale = None
293
- _cdp_platform = None
294
- _cdp_geolocation = None
295
- _cdp_recorder = None
296
- if (
297
- hasattr(sb_config, "_cdp_timezone") and sb_config._cdp_timezone
298
- ):
299
- _cdp_timezone = sb_config._cdp_timezone
300
- if (
301
- hasattr(sb_config, "_cdp_user_agent")
302
- and sb_config._cdp_user_agent
303
- ):
304
- _cdp_user_agent = sb_config._cdp_user_agent
305
- if hasattr(sb_config, "_cdp_locale") and sb_config._cdp_locale:
306
- _cdp_locale = sb_config._cdp_locale
307
- if hasattr(sb_config, "_cdp_platform") and sb_config._cdp_platform:
308
- _cdp_platform = sb_config._cdp_platform
418
+ if _cdp_ad_block:
419
+ await connection.send(cdp.network.set_blocked_urls(
420
+ urls=[
421
+ "*.cloudflareinsights.com*",
422
+ "*.googlesyndication.com*",
423
+ "*.googletagmanager.com*",
424
+ "*.google-analytics.com*",
425
+ "*.amazon-adsystem.com*",
426
+ "*.adsafeprotected.com*",
427
+ "*.ads.linkedin.com*",
428
+ "*.casalemedia.com*",
429
+ "*.doubleclick.net*",
430
+ "*.admanmedia.com*",
431
+ "*.quantserve.com*",
432
+ "*.fastclick.net*",
433
+ "*.snigelweb.com*",
434
+ "*.bidswitch.net*",
435
+ "*.360yield.com*",
436
+ "*.adthrive.com*",
437
+ "*.pubmatic.com*",
438
+ "*.id5-sync.com*",
439
+ "*.dotomi.com*",
440
+ "*.adsrvr.org*",
441
+ "*.atmtd.com*",
442
+ "*.liadm.com*",
443
+ "*.loopme.me*",
444
+ "*.adnxs.com*",
445
+ "*.openx.net*",
446
+ "*.tapad.com*",
447
+ "*.3lift.com*",
448
+ "*.turn.com*",
449
+ "*.2mdn.net*",
450
+ "*.cpx.to*",
451
+ "*.ad.gt*",
452
+ ]
453
+ ))
454
+ if _cdp_geolocation:
455
+ await connection.set_geolocation(_cdp_geolocation)
456
+ if _cdp_disable_csp:
457
+ await connection.send(cdp.page.set_bypass_csp(enabled=True))
458
+ if _cdp_mobile_mode:
459
+ await connection.send(
460
+ cdp.emulation.set_device_metrics_override(
461
+ width=412, height=732, device_scale_factor=3, mobile=True
462
+ )
463
+ )
464
+ # (The code below is for the Chrome 142 extension fix)
465
+ if (
466
+ getattr(sb_config, "_cdp_proxy", None)
467
+ and "@" in sb_config._cdp_proxy
468
+ and "auth" not in kwargs
469
+ ):
470
+ username_and_password = sb_config._cdp_proxy.split("@")[0]
471
+ proxy_user = username_and_password.split(":")[0]
472
+ proxy_pass = username_and_password.split(":")[1]
309
473
  if (
310
- hasattr(sb_config, "_cdp_geolocation")
311
- and sb_config._cdp_geolocation
474
+ hasattr(self.main_tab, "_last_auth")
475
+ and self.main_tab._last_auth == username_and_password
312
476
  ):
313
- _cdp_geolocation = sb_config._cdp_geolocation
314
- if "timezone" in kwargs:
315
- _cdp_timezone = kwargs["timezone"]
316
- elif "tzone" in kwargs:
317
- _cdp_timezone = kwargs["tzone"]
318
- if "user_agent" in kwargs:
319
- _cdp_user_agent = kwargs["user_agent"]
320
- elif "agent" in kwargs:
321
- _cdp_user_agent = kwargs["agent"]
322
- if "locale" in kwargs:
323
- _cdp_locale = kwargs["locale"]
324
- elif "lang" in kwargs:
325
- _cdp_locale = kwargs["lang"]
326
- elif "locale_code" in kwargs:
327
- _cdp_locale = kwargs["locale_code"]
328
- if "platform" in kwargs:
329
- _cdp_platform = kwargs["platform"]
330
- elif "plat" in kwargs:
331
- _cdp_platform = kwargs["plat"]
332
- if "geolocation" in kwargs:
333
- _cdp_geolocation = kwargs["geolocation"]
334
- elif "geoloc" in kwargs:
335
- _cdp_geolocation = kwargs["geoloc"]
336
- if "recorder" in kwargs:
337
- _cdp_recorder = kwargs["recorder"]
338
- if _cdp_timezone:
339
- await connection.send(cdp.page.navigate("about:blank"))
340
- await connection.set_timezone(_cdp_timezone)
341
- if _cdp_locale:
342
- await connection.set_locale(_cdp_locale)
343
- if _cdp_user_agent or _cdp_locale or _cdp_platform:
344
- await connection.send(cdp.page.navigate("about:blank"))
345
- await connection.set_user_agent(
346
- user_agent=_cdp_user_agent,
347
- accept_language=_cdp_locale,
348
- platform=_cdp_platform,
349
- )
350
- if _cdp_geolocation:
351
- await connection.send(cdp.page.navigate("about:blank"))
352
- await connection.set_geolocation(_cdp_geolocation)
353
- # This part isn't needed now, but may be needed later
354
- """
477
+ pass # Auth was already set
478
+ else:
479
+ self.main_tab._last_auth = username_and_password
480
+ await self.set_auth(proxy_user, proxy_pass, self.tabs[0])
481
+ time.sleep(0.25)
482
+ if "auth" in kwargs and kwargs["auth"] and ":" in kwargs["auth"]:
483
+ username_and_password = kwargs["auth"]
484
+ proxy_user = username_and_password.split(":")[0]
485
+ proxy_pass = username_and_password.split(":")[1]
355
486
  if (
356
- hasattr(sb_config, "_cdp_proxy")
357
- and "@" in sb_config._cdp_proxy
358
- and sb_config._cdp_proxy
359
- and "auth" not in kwargs
487
+ hasattr(self.main_tab, "_last_auth")
488
+ and self.main_tab._last_auth == username_and_password
360
489
  ):
361
- username_and_password = sb_config._cdp_proxy.split("@")[0]
362
- proxy_user = username_and_password.split(":")[0]
363
- proxy_pass = username_and_password.split(":")[1]
364
- await connection.set_auth(proxy_user, proxy_pass, self.tabs[0])
490
+ pass # Auth was already set
491
+ else:
492
+ self.main_tab._last_auth = username_and_password
493
+ await self.set_auth(proxy_user, proxy_pass, self.tabs[0])
365
494
  time.sleep(0.25)
366
- """
367
- if "auth" in kwargs and kwargs["auth"] and ":" in kwargs["auth"]:
368
- username_and_password = kwargs["auth"]
369
- proxy_user = username_and_password.split(":")[0]
370
- proxy_pass = username_and_password.split(":")[1]
371
- await connection.set_auth(proxy_user, proxy_pass, self.tabs[0])
372
- time.sleep(0.22)
373
- await connection.sleep(0.05)
374
- frame_id, loader_id, *_ = await connection.send(
375
- cdp.page.navigate(url)
495
+ await connection.sleep(0.15)
496
+ frame_id, loader_id, *_ = await connection.send(
497
+ cdp.page.navigate(url)
498
+ )
499
+ major_browser_version = None
500
+ try:
501
+ major_browser_version = (
502
+ int(self.info["Browser"].split("/")[-1].split(".")[0])
376
503
  )
377
- if _cdp_recorder:
378
- pass # (The code below was for the Chrome 137 extension fix)
379
- '''from seleniumbase.js_code.recorder_js import recorder_js
380
- recorder_code = (
381
- """window.onload = function() { %s };""" % recorder_js
504
+ except Exception:
505
+ pass
506
+ if (
507
+ _cdp_recorder
508
+ and (
509
+ not hasattr(sb_config, "browser")
510
+ or (
511
+ sb_config.browser == "chrome"
512
+ and (
513
+ not major_browser_version
514
+ or major_browser_version >= 142
515
+ )
382
516
  )
383
- await connection.send(cdp.runtime.evaluate(recorder_code))'''
384
- # Update the frame_id on the tab
385
- connection.frame_id = frame_id
386
- connection.browser = self
517
+ )
518
+ ):
519
+ # (The code below is for the Chrome 142 extension fix)
520
+ from seleniumbase.js_code.recorder_js import recorder_js
521
+ recorder_code = (
522
+ """window.onload = function() { %s };""" % recorder_js
523
+ )
524
+ await connection.send(
525
+ cdp.page.add_script_to_evaluate_on_new_document(recorder_code)
526
+ )
527
+ await connection.sleep(0.25)
528
+ await self.wait(0.05)
529
+ await connection.send(cdp.runtime.evaluate(recorder_js))
530
+ # Update the frame_id on the tab
531
+ connection.frame_id = frame_id
532
+ connection.browser = self
533
+ # Give settings time to take effect
387
534
  await connection.sleep(0.25)
535
+ await self.wait(0.05)
388
536
  return connection
389
537
 
390
538
  async def start(self=None) -> Browser:
@@ -436,7 +584,7 @@ class Browser:
436
584
  ) # noqa
437
585
  exe = self.config.browser_executable_path
438
586
  params = self.config()
439
- logger.info(
587
+ logger.debug(
440
588
  "Starting\n\texecutable :%s\n\narguments:\n%s",
441
589
  exe,
442
590
  "\n\t".join(params),
@@ -470,20 +618,29 @@ class Browser:
470
618
  else:
471
619
  break
472
620
  if not self.info:
473
- raise Exception(
474
- (
475
- """
476
- --------------------------------
477
- Failed to connect to the browser
478
- --------------------------------
479
- """
621
+ chromium = "Chromium"
622
+ if getattr(sb_config, "_cdp_browser", None):
623
+ chromium = sb_config._cdp_browser
624
+ chromium = chromium[0].upper() + chromium[1:]
625
+ message = "Failed to connect to the browser"
626
+ if not exe or not os.path.exists(exe):
627
+ message = (
628
+ "%s executable not found. Is it installed?" % chromium
480
629
  )
630
+ dash_len = len(message)
631
+ dashes = "-" * dash_len
632
+ raise Exception(
633
+ """
634
+ %s
635
+ %s
636
+ %s
637
+ """ % (dashes, message, dashes)
481
638
  )
482
639
  self.connection = Connection(
483
- self.info.webSocketDebuggerUrl, _owner=self
640
+ self.info.webSocketDebuggerUrl, browser=self
484
641
  )
485
642
  if self.config.autodiscover_targets:
486
- logger.info("Enabling autodiscover targets")
643
+ logger.debug("Enabling autodiscover targets")
487
644
  self.connection.handlers[cdp.target.TargetInfoChanged] = [
488
645
  self._handle_target_update
489
646
  ]
@@ -579,8 +736,11 @@ class Browser:
579
736
  try:
580
737
  import mss
581
738
  except Exception:
582
- from seleniumbase.fixtures import shared_utils
583
- shared_utils.pip_install("mss")
739
+ pip_find_lock = fasteners.InterProcessLock(
740
+ constants.PipInstall.FINDLOCK
741
+ )
742
+ with pip_find_lock: # Prevent issues with multiple processes
743
+ shared_utils.pip_install("mss")
584
744
  import mss
585
745
  m = mss.mss()
586
746
  screen, screen_width, screen_height = 3 * (None,)
@@ -654,7 +814,7 @@ class Browser:
654
814
  f"/{t.target_id}"
655
815
  ),
656
816
  target=t,
657
- _owner=self,
817
+ browser=self,
658
818
  )
659
819
  )
660
820
  await asyncio.sleep(0)
@@ -689,11 +849,17 @@ class Browser:
689
849
  else:
690
850
  del self._i
691
851
 
692
- def stop(self):
852
+ def stop(self, deconstruct=False):
853
+ if (
854
+ not hasattr(sb_config, "_closed_connection_ids")
855
+ or not isinstance(sb_config._closed_connection_ids, list)
856
+ ):
857
+ sb_config._closed_connection_ids = []
858
+ connection_id = None
859
+ with suppress(Exception):
860
+ connection_id = self.connection.websocket.id.hex
861
+ close_success = False
693
862
  try:
694
- # asyncio.get_running_loop().create_task(
695
- # self.connection.send(cdp.browser.close())
696
- # )
697
863
  if self.connection:
698
864
  asyncio.get_event_loop().create_task(self.connection.aclose())
699
865
  logger.debug(
@@ -702,23 +868,26 @@ class Browser:
702
868
  except RuntimeError:
703
869
  if self.connection:
704
870
  try:
705
- # asyncio.run(self.connection.send(cdp.browser.close()))
706
871
  asyncio.run(self.connection.aclose())
707
872
  logger.debug("Closed the connection using asyncio.run()")
708
873
  except Exception:
709
874
  pass
710
875
  for _ in range(3):
711
876
  try:
712
- self._process.terminate()
713
- logger.info(
714
- "Terminated browser with pid %d successfully."
715
- % self._process.pid
716
- )
717
- break
877
+ if connection_id not in sb_config._closed_connection_ids:
878
+ self._process.terminate()
879
+ logger.debug(
880
+ "Terminated browser with pid %d successfully."
881
+ % self._process.pid
882
+ )
883
+ if connection_id:
884
+ sb_config._closed_connection_ids.append(connection_id)
885
+ close_success = True
886
+ break
718
887
  except (Exception,):
719
888
  try:
720
889
  self._process.kill()
721
- logger.info(
890
+ logger.debug(
722
891
  "Killed browser with pid %d successfully."
723
892
  % self._process.pid
724
893
  )
@@ -727,14 +896,14 @@ class Browser:
727
896
  try:
728
897
  if hasattr(self, "browser_process_pid"):
729
898
  os.kill(self._process_pid, 15)
730
- logger.info(
899
+ logger.debug(
731
900
  "Killed browser with pid %d "
732
901
  "using signal 15 successfully."
733
902
  % self._process.pid
734
903
  )
735
904
  break
736
905
  except (TypeError,):
737
- logger.info("typerror", exc_info=True)
906
+ logger.info("TypeError", exc_info=True)
738
907
  pass
739
908
  except (PermissionError,):
740
909
  logger.info(
@@ -743,12 +912,45 @@ class Browser:
743
912
  )
744
913
  pass
745
914
  except (ProcessLookupError,):
746
- logger.info("Process lookup failure!")
915
+ logger.info("ProcessLookupError")
747
916
  pass
748
917
  except (Exception,):
749
918
  raise
750
919
  self._process = None
751
920
  self._process_pid = None
921
+ if (
922
+ hasattr(sb_config, "_xvfb_users")
923
+ and isinstance(sb_config._xvfb_users, int)
924
+ and close_success
925
+ and hasattr(sb_config, "_virtual_display")
926
+ and sb_config._virtual_display
927
+ ):
928
+ sb_config._xvfb_users -= 1
929
+ if sb_config._xvfb_users < 0:
930
+ sb_config._xvfb_users = 0
931
+ if (
932
+ shared_utils.is_linux()
933
+ and (
934
+ hasattr(sb_config, "_virtual_display")
935
+ and sb_config._virtual_display
936
+ and hasattr(sb_config._virtual_display, "stop")
937
+ )
938
+ and sb_config._xvfb_users == 0
939
+ ):
940
+ try:
941
+ sb_config._virtual_display.stop()
942
+ sb_config._virtual_display = None
943
+ sb_config.headless_active = False
944
+ except AttributeError:
945
+ pass
946
+ except Exception:
947
+ pass
948
+ if (
949
+ deconstruct
950
+ and connection_id
951
+ and connection_id in sb_config._closed_connection_ids
952
+ ):
953
+ sb_config._closed_connection_ids.remove(connection_id)
752
954
 
753
955
  def quit(self):
754
956
  self.stop()
@@ -780,7 +982,7 @@ class CookieJar:
780
982
  """
781
983
  connection = None
782
984
  for _tab in self._browser.tabs:
783
- if hasattr(_tab, "closed") and _tab.closed:
985
+ if getattr(_tab, "closed", None):
784
986
  continue
785
987
  connection = _tab
786
988
  break
@@ -810,7 +1012,7 @@ class CookieJar:
810
1012
  """
811
1013
  connection = None
812
1014
  for _tab in self._browser.tabs:
813
- if hasattr(_tab, "closed") and _tab.closed:
1015
+ if getattr(_tab, "closed", None):
814
1016
  continue
815
1017
  connection = _tab
816
1018
  break
@@ -837,7 +1039,7 @@ class CookieJar:
837
1039
  save_path = pathlib.Path(file).resolve()
838
1040
  connection = None
839
1041
  for _tab in self._browser.tabs:
840
- if hasattr(_tab, "closed") and _tab.closed:
1042
+ if getattr(_tab, "closed", None):
841
1043
  continue
842
1044
  connection = _tab
843
1045
  break
@@ -883,7 +1085,7 @@ class CookieJar:
883
1085
  included_cookies = []
884
1086
  connection = None
885
1087
  for _tab in self._browser.tabs:
886
- if hasattr(_tab, "closed") and _tab.closed:
1088
+ if getattr(_tab, "closed", None):
887
1089
  continue
888
1090
  connection = _tab
889
1091
  break
@@ -906,7 +1108,7 @@ class CookieJar:
906
1108
  """
907
1109
  connection = None
908
1110
  for _tab in self._browser.tabs:
909
- if hasattr(_tab, "closed") and _tab.closed:
1111
+ if getattr(_tab, "closed", None):
910
1112
  continue
911
1113
  connection = _tab
912
1114
  break