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
@@ -4,9 +4,15 @@ import base64
4
4
  import datetime
5
5
  import logging
6
6
  import pathlib
7
+ import re
7
8
  import urllib.parse
8
9
  import warnings
10
+ from contextlib import suppress
11
+ from filelock import FileLock
9
12
  from seleniumbase import config as sb_config
13
+ from seleniumbase.fixtures import constants
14
+ from seleniumbase.fixtures import js_utils
15
+ from seleniumbase.fixtures import shared_utils
10
16
  from typing import Dict, List, Union, Optional, Tuple
11
17
  from . import browser as cdp_browser
12
18
  from . import element
@@ -244,23 +250,7 @@ class Tab(Connection):
244
250
  Raise timeout exception when after this many seconds nothing is found.
245
251
  :type timeout: float,int
246
252
  """
247
- loop = asyncio.get_running_loop()
248
- start_time = loop.time()
249
- selector = selector.strip()
250
- item = None
251
- try:
252
- item = await self.query_selector(selector)
253
- except (Exception, TypeError):
254
- pass
255
- while not item:
256
- await self
257
- item = await self.query_selector(selector)
258
- if loop.time() - start_time > timeout:
259
- raise asyncio.TimeoutError(
260
- "Time ran out while waiting for: {%s}" % selector
261
- )
262
- await self.sleep(0.5)
263
- return item
253
+ return await self.wait_for(selector=selector, timeout=timeout)
264
254
 
265
255
  async def find_all(
266
256
  self,
@@ -358,13 +348,19 @@ class Tab(Connection):
358
348
  if new_window and not new_tab:
359
349
  new_tab = True
360
350
  if new_tab:
361
- if hasattr(sb_config, "incognito") and sb_config.incognito:
351
+ if (
352
+ getattr(sb_config, "incognito", None)
353
+ or (
354
+ getattr(sb_config, "_cdp_browser", None)
355
+ in ["comet", "atlas"]
356
+ )
357
+ ):
362
358
  return await self.browser.get(
363
359
  url, new_tab=False, new_window=True, **kwargs
364
360
  )
365
361
  else:
366
362
  return await self.browser.get(
367
- url, new_tab, new_window, **kwargs
363
+ url, new_tab=True, new_window=False, **kwargs
368
364
  )
369
365
  else:
370
366
  if not kwargs:
@@ -375,9 +371,12 @@ class Tab(Connection):
375
371
  return self
376
372
  else:
377
373
  return await self.browser.get(
378
- url, new_tab, new_window, **kwargs
374
+ url, new_tab=False, new_window=False, **kwargs
379
375
  )
380
376
 
377
+ async def open(self, url="about:blank"):
378
+ return await self.get(url=url)
379
+
381
380
  async def query_selector_all(
382
381
  self,
383
382
  selector: str,
@@ -705,10 +704,12 @@ class Tab(Connection):
705
704
  raise ProtocolException(errors)
706
705
  if remote_object:
707
706
  if return_by_value:
708
- if remote_object.value:
707
+ if remote_object.value is not None:
709
708
  return remote_object.value
710
709
  else:
711
- return remote_object, errors
710
+ if remote_object.deep_serialized_value is not None:
711
+ return remote_object.deep_serialized_value.value
712
+ return None
712
713
 
713
714
  async def js_dumps(
714
715
  self, obj_name: str, return_by_value: Optional[bool] = True
@@ -899,7 +900,10 @@ class Tab(Connection):
899
900
  """Gets the current page source content (html)"""
900
901
  doc: cdp.dom.Node = await self.send(cdp.dom.get_document(-1, True))
901
902
  return await self.send(
902
- cdp.dom.get_outer_html(backend_node_id=doc.backend_node_id)
903
+ cdp.dom.get_outer_html(
904
+ backend_node_id=doc.backend_node_id,
905
+ include_shadow_dom=True,
906
+ )
903
907
  )
904
908
 
905
909
  async def maximize(self):
@@ -1075,7 +1079,7 @@ class Tab(Connection):
1075
1079
  raise asyncio.TimeoutError(
1076
1080
  "Time ran out while waiting for: {%s}" % selector
1077
1081
  )
1078
- await self.sleep(0.5)
1082
+ await self.sleep(0.068)
1079
1083
  return item
1080
1084
  if text:
1081
1085
  item = await self.find_element_by_text(text)
@@ -1085,9 +1089,42 @@ class Tab(Connection):
1085
1089
  raise asyncio.TimeoutError(
1086
1090
  "Time ran out while waiting for: {%s}" % text
1087
1091
  )
1088
- await self.sleep(0.5)
1092
+ await self.sleep(0.068)
1089
1093
  return item
1090
1094
 
1095
+ async def set_attributes(self, selector, attribute, value):
1096
+ """This method uses JavaScript to set/update a common attribute.
1097
+ All matching selectors from querySelectorAll() are used.
1098
+ Example => (Make all links on a website redirect to Google):
1099
+ self.set_attributes("a", "href", "https://google.com")"""
1100
+ attribute = re.escape(attribute)
1101
+ attribute = js_utils.escape_quotes_if_needed(attribute)
1102
+ value = re.escape(value)
1103
+ value = js_utils.escape_quotes_if_needed(value)
1104
+ if selector.startswith(("/", "./", "(")):
1105
+ with suppress(Exception):
1106
+ selector = js_utils.convert_to_css_selector(selector, "xpath")
1107
+ css_selector = selector
1108
+ css_selector = re.escape(css_selector) # Add "\\" to special chars
1109
+ css_selector = js_utils.escape_quotes_if_needed(css_selector)
1110
+ js_code = (
1111
+ """var $elements = document.querySelectorAll('%s');
1112
+ var index = 0, length = $elements.length;
1113
+ for(; index < length; index++){
1114
+ $elements[index].setAttribute('%s','%s');}""" % (
1115
+ css_selector,
1116
+ attribute,
1117
+ value,
1118
+ )
1119
+ )
1120
+ with suppress(Exception):
1121
+ await self.evaluate(js_code)
1122
+
1123
+ async def internalize_links(self):
1124
+ """All `target="_blank"` links become `target="_self"`.
1125
+ This prevents those links from opening in a new tab."""
1126
+ await self.set_attributes('[target="_blank"]', "target", "_self")
1127
+
1091
1128
  async def download_file(
1092
1129
  self, url: str, filename: Optional[PathLike] = None
1093
1130
  ):
@@ -1287,19 +1324,355 @@ class Tab(Connection):
1287
1324
  res.append(abs_url)
1288
1325
  return res
1289
1326
 
1290
- async def verify_cf(self):
1291
- """(An attempt)"""
1292
- checkbox = None
1293
- checkbox_sibling = await self.wait_for(text="verify you are human")
1294
- if checkbox_sibling:
1295
- parent = checkbox_sibling.parent
1296
- while parent:
1297
- checkbox = await parent.query_selector("input[type=checkbox]")
1298
- if checkbox:
1299
- break
1300
- parent = parent.parent
1301
- await checkbox.mouse_move()
1302
- await checkbox.mouse_click()
1327
+ async def get_html(self):
1328
+ element = await self.find("html", timeout=1)
1329
+ return await element.get_html_async()
1330
+
1331
+ async def get_page_source(self):
1332
+ return await self.get_html()
1333
+
1334
+ async def is_element_present(self, selector):
1335
+ try:
1336
+ await self.select(selector, timeout=0.01)
1337
+ return True
1338
+ except Exception:
1339
+ return False
1340
+
1341
+ async def is_element_visible(self, selector):
1342
+ try:
1343
+ element = await self.select(selector, timeout=0.01)
1344
+ except Exception:
1345
+ return False
1346
+ if not element:
1347
+ return False
1348
+ try:
1349
+ position = await element.get_position_async()
1350
+ return (position.width != 0 or position.height != 0)
1351
+ except Exception:
1352
+ return False
1353
+
1354
+ async def __on_a_cf_turnstile_page(self, source=None):
1355
+ if not source or len(source) < 400:
1356
+ await self.sleep(0.22)
1357
+ source = await self.get_html()
1358
+ if (
1359
+ (
1360
+ 'data-callback="onCaptchaSuccess"' in source
1361
+ and 'title="reCAPTCHA"' not in source
1362
+ and 'id="recaptcha-token"' not in source
1363
+ )
1364
+ or "/challenge-platform/h/b/" in source
1365
+ or 'id="challenge-widget-' in source
1366
+ or "challenges.cloudf" in source
1367
+ or "cf-turnstile-" in source
1368
+ ):
1369
+ return True
1370
+ return False
1371
+
1372
+ async def __on_a_g_recaptcha_page(self, *args, **kwargs):
1373
+ await self.sleep(0.4) # reCAPTCHA may need a moment to appear
1374
+ source = await self.get_html()
1375
+ if (
1376
+ (
1377
+ 'id="recaptcha-token"' in source
1378
+ or 'title="reCAPTCHA"' in source
1379
+ )
1380
+ and await self.is_element_present('iframe[title="reCAPTCHA"]')
1381
+ ):
1382
+ await self.sleep(0.1)
1383
+ return True
1384
+ elif "com/recaptcha/api.js" in source:
1385
+ await self.sleep(1.6) # Still loading
1386
+ return True
1387
+ return False
1388
+
1389
+ async def __gui_click_recaptcha(self):
1390
+ selector = None
1391
+ if await self.is_element_present('iframe[title="reCAPTCHA"]'):
1392
+ selector = 'iframe[title="reCAPTCHA"]'
1393
+ else:
1394
+ return
1395
+ await self.sleep(0.5)
1396
+ with suppress(Exception):
1397
+ element_rect = await self.get_gui_element_rect(selector, timeout=1)
1398
+ e_x = element_rect["x"]
1399
+ e_y = element_rect["y"]
1400
+ x_offset = 26
1401
+ y_offset = 35
1402
+ if await asyncio.to_thread(shared_utils.is_windows):
1403
+ x_offset = 29
1404
+ x = e_x + x_offset
1405
+ y = e_y + y_offset
1406
+ sb_config._saved_cf_x_y = (x, y) # For debugging later
1407
+ await self.sleep(0.11)
1408
+ gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
1409
+ with await asyncio.to_thread(gui_lock.acquire):
1410
+ await self.bring_to_front()
1411
+ await self.sleep(0.05)
1412
+ await self.click_with_offset(
1413
+ selector, x_offset, y_offset, timeout=1
1414
+ )
1415
+ await self.sleep(0.22)
1416
+
1417
+ async def get_element_rect(self, selector, timeout=5):
1418
+ element = await self.select(selector, timeout=timeout)
1419
+ coordinates = None
1420
+ if ":contains(" in selector:
1421
+ position = await element.get_position_async()
1422
+ x = position.x
1423
+ y = position.y
1424
+ width = position.width
1425
+ height = position.height
1426
+ coordinates = {"x": x, "y": y, "width": width, "height": height}
1427
+ else:
1428
+ coordinates = await self.js_dumps(
1429
+ """document.querySelector('%s').getBoundingClientRect()"""
1430
+ % js_utils.escape_quotes_if_needed(re.escape(selector))
1431
+ )
1432
+ return coordinates
1433
+
1434
+ async def get_window_rect(self):
1435
+ coordinates = {}
1436
+ innerWidth = await self.evaluate("window.innerWidth")
1437
+ innerHeight = await self.evaluate("window.innerHeight")
1438
+ outerWidth = await self.evaluate("window.outerWidth")
1439
+ outerHeight = await self.evaluate("window.outerHeight")
1440
+ pageXOffset = await self.evaluate("window.pageXOffset")
1441
+ pageYOffset = await self.evaluate("window.pageYOffset")
1442
+ scrollX = await self.evaluate("window.scrollX")
1443
+ scrollY = await self.evaluate("window.scrollY")
1444
+ screenLeft = await self.evaluate("window.screenLeft")
1445
+ screenTop = await self.evaluate("window.screenTop")
1446
+ x = await self.evaluate("window.screenX")
1447
+ y = await self.evaluate("window.screenY")
1448
+ coordinates["innerWidth"] = innerWidth
1449
+ coordinates["innerHeight"] = innerHeight
1450
+ coordinates["outerWidth"] = outerWidth
1451
+ coordinates["outerHeight"] = outerHeight
1452
+ coordinates["width"] = outerWidth
1453
+ coordinates["height"] = outerHeight
1454
+ coordinates["pageXOffset"] = pageXOffset if pageXOffset else 0
1455
+ coordinates["pageYOffset"] = pageYOffset if pageYOffset else 0
1456
+ coordinates["scrollX"] = scrollX if scrollX else 0
1457
+ coordinates["scrollY"] = scrollY if scrollY else 0
1458
+ coordinates["screenLeft"] = screenLeft if screenLeft else 0
1459
+ coordinates["screenTop"] = screenTop if screenTop else 0
1460
+ coordinates["x"] = x if x else 0
1461
+ coordinates["y"] = y if y else 0
1462
+ return coordinates
1463
+
1464
+ async def get_gui_element_rect(self, selector, timeout=5):
1465
+ """(Coordinates are relative to the screen. Not the window.)"""
1466
+ element_rect = await self.get_element_rect(selector, timeout=timeout)
1467
+ e_width = element_rect["width"]
1468
+ e_height = element_rect["height"]
1469
+ window_rect = await self.get_window_rect()
1470
+ w_bottom_y = window_rect["y"] + window_rect["height"]
1471
+ viewport_height = window_rect["innerHeight"]
1472
+ x = window_rect["x"] + element_rect["x"]
1473
+ y = w_bottom_y - viewport_height + element_rect["y"]
1474
+ y_scroll_offset = window_rect["pageYOffset"]
1475
+ if (
1476
+ hasattr(sb_config, "_cdp_browser")
1477
+ and sb_config._cdp_browser == "opera"
1478
+ ):
1479
+ # Handle special case where Opera side panel shifts coordinates
1480
+ x_offset = window_rect["outerWidth"] - window_rect["innerWidth"]
1481
+ if x_offset > 56:
1482
+ x_offset = 56
1483
+ elif x_offset < 22:
1484
+ x_offset = 0
1485
+ x = x + x_offset
1486
+ y = y - y_scroll_offset
1487
+ x = x + window_rect["scrollX"]
1488
+ y = y + window_rect["scrollY"]
1489
+ return ({"height": e_height, "width": e_width, "x": x, "y": y})
1490
+
1491
+ async def get_title(self):
1492
+ return await self.evaluate("document.title")
1493
+
1494
+ async def get_current_url(self):
1495
+ return await self.evaluate("window.location.href")
1496
+
1497
+ async def send_keys(self, selector, text, timeout=5):
1498
+ element = await self.find(selector, timeout=timeout)
1499
+ await element.send_keys_async(text)
1500
+
1501
+ async def type(self, selector, text, timeout=5):
1502
+ await self.send_keys(selector, text, timeout=timeout)
1503
+
1504
+ async def click(self, selector, timeout=5):
1505
+ element = await self.find(selector, timeout=timeout)
1506
+ await element.click_async()
1507
+
1508
+ async def click_with_offset(self, selector, x, y, center=False, timeout=5):
1509
+ element = await self.find(selector, timeout=timeout)
1510
+ await element.scroll_into_view_async()
1511
+ await element.mouse_click_with_offset_async(x=x, y=y, center=center)
1512
+
1513
+ async def solve_captcha(self):
1514
+ await self.sleep(0.11)
1515
+ source = await self.get_html()
1516
+ if await self.__on_a_cf_turnstile_page(source):
1517
+ pass
1518
+ elif await self.__on_a_g_recaptcha_page(source):
1519
+ await self.__gui_click_recaptcha()
1520
+ return
1521
+ else:
1522
+ return
1523
+ selector = None
1524
+ if await self.is_element_present('[class="cf-turnstile"]'):
1525
+ selector = '[class="cf-turnstile"]'
1526
+ elif await self.is_element_present("#challenge-form div > div"):
1527
+ selector = "#challenge-form div > div"
1528
+ elif await self.is_element_present('[style="display: grid;"] div div'):
1529
+ selector = '[style="display: grid;"] div div'
1530
+ elif await self.is_element_present("[class*=spacer] + div div"):
1531
+ selector = '[class*=spacer] + div div'
1532
+ elif await self.is_element_present(".spacer div:not([class])"):
1533
+ selector = ".spacer div:not([class])"
1534
+ elif await self.is_element_present('[data-testid*="challenge-"] div'):
1535
+ selector = '[data-testid*="challenge-"] div'
1536
+ elif await self.is_element_present(
1537
+ "div#turnstile-widget div:not([class])"
1538
+ ):
1539
+ selector = "div#turnstile-widget div:not([class])"
1540
+ elif await self.is_element_present("ngx-turnstile div:not([class])"):
1541
+ selector = "ngx-turnstile div:not([class])"
1542
+ elif await self.is_element_present(
1543
+ 'form div:not([class]):has(input[name*="cf-turn"])'
1544
+ ):
1545
+ selector = 'form div:not([class]):has(input[name*="cf-turn"])'
1546
+ elif await self.is_element_present("form div:not(:has(*))"):
1547
+ selector = "form div:not(:has(*))"
1548
+ elif await self.is_element_present(
1549
+ "body > div#check > div:not([class])"
1550
+ ):
1551
+ selector = "body > div#check > div:not([class])"
1552
+ elif await self.is_element_present(".cf-turnstile-wrapper"):
1553
+ selector = ".cf-turnstile-wrapper"
1554
+ elif await self.is_element_present(
1555
+ '[id*="turnstile"] div:not([class])'
1556
+ ):
1557
+ selector = '[id*="turnstile"] div:not([class])'
1558
+ elif await self.is_element_present(
1559
+ '[class*="turnstile"] div:not([class])'
1560
+ ):
1561
+ selector = '[class*="turnstile"] div:not([class])'
1562
+ elif await self.is_element_present(
1563
+ '[data-callback="onCaptchaSuccess"]'
1564
+ ):
1565
+ selector = '[data-callback="onCaptchaSuccess"]'
1566
+ elif await self.is_element_present(
1567
+ "div:not([class]) > div:not([class])"
1568
+ ):
1569
+ selector = "div:not([class]) > div:not([class])"
1570
+ else:
1571
+ return
1572
+ if not selector:
1573
+ return
1574
+ if (
1575
+ await self.is_element_present("form")
1576
+ and (
1577
+ await self.is_element_present('form[class*="center"]')
1578
+ or await self.is_element_present('form[class*="right"]')
1579
+ or await self.is_element_present('form div[class*="center"]')
1580
+ or await self.is_element_present('form div[class*="right"]')
1581
+ )
1582
+ ):
1583
+ script = (
1584
+ """var $elements = document.querySelectorAll(
1585
+ 'form[class], form div[class]');
1586
+ var index = 0, length = $elements.length;
1587
+ for(; index < length; index++){
1588
+ the_class = $elements[index].getAttribute('class');
1589
+ new_class = the_class.replaceAll('center', 'left');
1590
+ new_class = new_class.replaceAll('right', 'left');
1591
+ $elements[index].setAttribute('class', new_class);}"""
1592
+ )
1593
+ with suppress(Exception):
1594
+ await self.evaluate(script)
1595
+ elif (
1596
+ await self.is_element_present("form")
1597
+ and (
1598
+ await self.is_element_present('form div[style*="center"]')
1599
+ or await self.is_element_present('form div[style*="right"]')
1600
+ )
1601
+ ):
1602
+ script = (
1603
+ """var $elements = document.querySelectorAll(
1604
+ 'form[style], form div[style]');
1605
+ var index = 0, length = $elements.length;
1606
+ for(; index < length; index++){
1607
+ the_style = $elements[index].getAttribute('style');
1608
+ new_style = the_style.replaceAll('center', 'left');
1609
+ new_style = new_style.replaceAll('right', 'left');
1610
+ $elements[index].setAttribute('style', new_style);}"""
1611
+ )
1612
+ with suppress(Exception):
1613
+ await self.evaluate(script)
1614
+ elif (
1615
+ await self.is_element_present(
1616
+ 'form [id*="turnstile"] div:not([class])'
1617
+ )
1618
+ or await self.is_element_present(
1619
+ 'form [class*="turnstile"] div:not([class])'
1620
+ )
1621
+ ):
1622
+ script = (
1623
+ """var $elements = document.querySelectorAll(
1624
+ 'form [id*="turnstile"]');
1625
+ var index = 0, length = $elements.length;
1626
+ for(; index < length; index++){
1627
+ $elements[index].setAttribute('align', 'left');}
1628
+ var $elements = document.querySelectorAll(
1629
+ 'form [class*="turnstile"]');
1630
+ var index = 0, length = $elements.length;
1631
+ for(; index < length; index++){
1632
+ $elements[index].setAttribute('align', 'left');}"""
1633
+ )
1634
+ with suppress(Exception):
1635
+ await self.evaluate(script)
1636
+ elif (
1637
+ await self.is_element_present(
1638
+ '[style*="text-align: center;"] div:not([class])'
1639
+ )
1640
+ ):
1641
+ script = (
1642
+ """var $elements = document.querySelectorAll(
1643
+ '[style*="text-align: center;"]');
1644
+ var index = 0, length = $elements.length;
1645
+ for(; index < length; index++){
1646
+ the_style = $elements[index].getAttribute('style');
1647
+ new_style = the_style.replaceAll('center', 'left');
1648
+ $elements[index].setAttribute('style', new_style);}"""
1649
+ )
1650
+ with suppress(Exception):
1651
+ await self.evaluate(script)
1652
+ with suppress(Exception):
1653
+ await self.sleep(0.05)
1654
+ element_rect = await self.get_gui_element_rect(selector, timeout=1)
1655
+ e_x = element_rect["x"]
1656
+ e_y = element_rect["y"]
1657
+ x_offset = 32
1658
+ y_offset = 32
1659
+ if await asyncio.to_thread(shared_utils.is_windows):
1660
+ y_offset = 28
1661
+ x = e_x + x_offset
1662
+ y = e_y + y_offset
1663
+ sb_config._saved_cf_x_y = (x, y) # For debugging later
1664
+ await self.sleep(0.11)
1665
+ gui_lock = FileLock(constants.MultiBrowser.PYAUTOGUILOCK)
1666
+ with await asyncio.to_thread(gui_lock.acquire):
1667
+ await self.bring_to_front()
1668
+ await self.sleep(0.05)
1669
+ await self.click_with_offset(
1670
+ selector, x_offset, y_offset, timeout=1
1671
+ )
1672
+ await self.sleep(0.22)
1673
+
1674
+ async def click_captcha(self):
1675
+ await self.solve_captcha()
1303
1676
 
1304
1677
  async def get_document(self):
1305
1678
  return await self.send(cdp.dom.get_document())
@@ -1365,7 +1738,9 @@ class Tab(Connection):
1365
1738
  :param selector: css selector string
1366
1739
  :type selector: str
1367
1740
  """
1368
- return self.wait_for(text, selector, timeout)
1741
+ return self.wait_for(
1742
+ selector=selector, text=text, timeout=timeout
1743
+ )
1369
1744
 
1370
1745
  def __eq__(self, other: Tab):
1371
1746
  try: