From 9e78f09c43ccf5328f49ae3af5cc49c8b71fe838 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Jul 2026 00:46:09 -0400 Subject: [PATCH 1/5] Update scroll timing in CDP Mode --- seleniumbase/core/sb_cdp.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/seleniumbase/core/sb_cdp.py b/seleniumbase/core/sb_cdp.py index 8b12c42b4f3..c6c7cc25daa 100644 --- a/seleniumbase/core/sb_cdp.py +++ b/seleniumbase/core/sb_cdp.py @@ -3446,33 +3446,33 @@ def js_scroll_into_view(self, selector): def scroll_into_view(self, selector): self.find_element(selector).scroll_into_view() - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_to_y(self, y): y = int(y) js_code = "window.scrollTo(0, %s);" % y with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_by_y(self, y): y = int(y) js_code = "window.scrollBy(0, %s);" % y with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_to_top(self): js_code = "window.scrollTo(0, 0);" with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_to_bottom(self): js_code = "window.scrollTo(0, 10000);" with suppress(Exception): self.loop.run_until_complete(self.page.evaluate(js_code)) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_up(self, amount=25): """Scrolls up as a percentage of the page.""" @@ -3481,7 +3481,7 @@ def scroll_up(self, amount=25): except Exception: amount = self.get_window_size()["height"] * amount / 100 self.execute_script("window.scrollBy(0, -%s);" % amount) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def scroll_down(self, amount=25): """Scrolls down as a percentage of the page.""" @@ -3490,7 +3490,7 @@ def scroll_down(self, amount=25): except Exception: amount = self.get_window_size()["height"] * amount / 100 self.execute_script("window.scrollBy(0, %s);" % amount) - self.loop.run_until_complete(self.page.wait(0.1)) + self.loop.run_until_complete(self.page.wait(0.05)) def save_page_source(self, name, folder=None): from seleniumbase.core import log_helper From 11f026db660b6fad00f390b8f94fa9e4f6624b7f Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Jul 2026 00:46:32 -0400 Subject: [PATCH 2/5] Update dashboard layout --- seleniumbase/fixtures/base_case.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index cb1e258028e..c87ca8479bf 100644 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -16856,15 +16856,15 @@ def __process_dashboard(self, has_exception, init=False): '' '' '' - 'Failed: %s ' + 'Failed: %s ' '' - 'Skipped: %s ' + 'Skipped: %s ' '' - 'Passed: %s ' + 'Passed: %s ' '' - 'Untested: %s ' + 'Untested: %s ' '' - 'Total: %s' + ' Total: %s' "" "

" ' Date: Wed, 8 Jul 2026 00:47:10 -0400 Subject: [PATCH 3/5] Refresh Python dependencies --- requirements.txt | 6 +++--- setup.py | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 300a2c65c66..f4ddd7bdac6 100755 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ pip>=26.0.1;python_version<"3.10" pip>=26.1.2;python_version>="3.10" packaging>=26.2 setuptools~=70.2;python_version<"3.10" -setuptools>=82.0.1;python_version>="3.10" +setuptools>=83.0.0;python_version>="3.10" wheel>=0.47.0 attrs>=26.1.0 certifi>=2026.6.17 @@ -10,7 +10,7 @@ exceptiongroup>=1.3.1 websockets~=15.0.1;python_version<"3.10" websockets>=16.0;python_version>="3.10" filelock~=3.19.1;python_version<"3.10" -filelock>=3.29.5;python_version>="3.10" +filelock>=3.29.6;python_version>="3.10" fasteners>=0.20 mycdp>=1.4.0 pynose>=1.5.5 @@ -31,7 +31,7 @@ pyreadline3>=3.5.4;platform_system=="Windows" tabcompleter>=1.4.1 pdbp>=1.8.2 idna>=3.18 -charset-normalizer>=3.4.7,<4 +charset-normalizer>=3.4.9,<4 urllib3>=1.26.20,<2;python_version<"3.10" urllib3>=2.7.0,<3;python_version>="3.10" requests~=2.32.5;python_version<"3.10" diff --git a/setup.py b/setup.py index d2668d8ff0b..1456d12c911 100755 --- a/setup.py +++ b/setup.py @@ -166,7 +166,7 @@ 'pip>=26.1.2;python_version>="3.10"', 'packaging>=26.2', 'setuptools~=70.2;python_version<"3.10"', # Newer ones had issues - 'setuptools>=82.0.1;python_version>="3.10"', + 'setuptools>=83.0.0;python_version>="3.10"', 'wheel>=0.47.0', 'attrs>=26.1.0', 'certifi>=2026.6.17', @@ -174,7 +174,7 @@ 'websockets~=15.0.1;python_version<"3.10"', 'websockets>=16.0;python_version>="3.10"', 'filelock~=3.19.1;python_version<"3.10"', - 'filelock>=3.29.5;python_version>="3.10"', + 'filelock>=3.29.6;python_version>="3.10"', 'fasteners>=0.20', 'mycdp>=1.4.0', 'pynose>=1.5.5', @@ -195,7 +195,7 @@ 'tabcompleter>=1.4.1', 'pdbp>=1.8.2', 'idna>=3.18', - 'charset-normalizer>=3.4.7,<4', + 'charset-normalizer>=3.4.9,<4', 'urllib3>=1.26.20,<2;python_version<"3.10"', 'urllib3>=2.7.0,<3;python_version>="3.10"', 'requests~=2.32.5;python_version<"3.10"', @@ -275,7 +275,8 @@ 'pdfminer.six==20251107;python_version<"3.10"', 'pdfminer.six==20260107;python_version>="3.10"', 'cryptography==49.0.0', - 'cffi==2.0.0', + 'cffi==2.0.0;python_version<"3.10"', + 'cffi==2.1.0;python_version>="3.10"', 'pycparser==2.23;python_version<"3.10"', 'pycparser==3.0;python_version>="3.10"', ], From 2c0be3b8875fdb5fc3401937ddf2c633e108ed9a Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Jul 2026 00:57:45 -0400 Subject: [PATCH 4/5] Update CDP Mode examples --- examples/cdp_mode/playwright/raw_etsy_sync.py | 31 +++++++++++++++++++ examples/cdp_mode/raw_cdp_etsy.py | 29 +++++++++++++++++ examples/cdp_mode/raw_cdp_nordstrom.py | 4 +-- examples/cdp_mode/raw_cdp_zillow.py | 5 ++- examples/cdp_mode/raw_indeed_jobs.py | 16 ++++++++++ examples/cdp_mode/raw_nordstrom.py | 4 +-- examples/cdp_mode/raw_ralphlauren.py | 22 +++++++------ 7 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 examples/cdp_mode/playwright/raw_etsy_sync.py create mode 100644 examples/cdp_mode/raw_cdp_etsy.py create mode 100644 examples/cdp_mode/raw_indeed_jobs.py diff --git a/examples/cdp_mode/playwright/raw_etsy_sync.py b/examples/cdp_mode/playwright/raw_etsy_sync.py new file mode 100644 index 00000000000..6705ac4b31a --- /dev/null +++ b/examples/cdp_mode/playwright/raw_etsy_sync.py @@ -0,0 +1,31 @@ +from playwright.sync_api import sync_playwright +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(ad_block=True) +sb.goto("https://www.etsy.com/") +sb.sleep(1) +search = "FIFA Keychains" +required_text = "keychain" +sb.type('input[data-id="search-query"]', search) +sb.sleep(1) +sb.click('button[aria-label="Search"]') +sb.sleep(2) +sb.click_if_visible('button[aria-label="Ok"]') +endpoint_url = sb.get_endpoint_url() + +with sync_playwright() as p: + browser = p.chromium.connect_over_cdp(endpoint_url) + page = browser.contexts[0].pages[0] + items = page.locator("div.v2-listing-card__info") + num = 0 + for i in range(items.count()): + title = items.nth(i).locator("h3.v2-listing-card__title").inner_text() + price = items.nth(i).locator("div.n-listing-card__price").inner_text() + if required_text.lower() in title.lower(): + num += 1 + title = " ".join(title.split()).strip() + price = price.replace("Sale Price", "").strip().split("\n")[0] + print(f"* <====== {num} ======>") + print(title) + print(price.strip().split("\n")[0]) + print(f"*** {num} total items found!") diff --git a/examples/cdp_mode/raw_cdp_etsy.py b/examples/cdp_mode/raw_cdp_etsy.py new file mode 100644 index 00000000000..0d35d9bbc3a --- /dev/null +++ b/examples/cdp_mode/raw_cdp_etsy.py @@ -0,0 +1,29 @@ +from seleniumbase import sb_cdp + +sb = sb_cdp.Chrome(ad_block=True) +sb.goto("https://www.etsy.com/") +sb.sleep(1) +search = "FIFA Keychains" +required_text = "keychain" +sb.type('input[data-id="search-query"]', search) +sb.sleep(1) +sb.click('button[aria-label="Search"]') +sb.sleep(2) +sb.click_if_visible('button[aria-label="Ok"]') +soup = sb.get_beautiful_soup() +items = soup.select("div.v2-listing-card__info") +num = 0 +for item in items: + title_element = item.select_one("h3.v2-listing-card__title") + price_element = item.select_one("div.n-listing-card__price") + if title_element and price_element: + title = title_element.get_text() + price = price_element.get_text() + if required_text.lower() in title.lower(): + num += 1 + title = " ".join(title.split()).strip() + price = price.replace("Sale Price", "").strip().split("\n")[0] + print(f"* <====== {num} ======>") + print(title) + print(price.strip().split("\n")[0]) +print(f"*** {num} total items found!") diff --git a/examples/cdp_mode/raw_cdp_nordstrom.py b/examples/cdp_mode/raw_cdp_nordstrom.py index ab7e90562af..3a91121f197 100644 --- a/examples/cdp_mode/raw_cdp_nordstrom.py +++ b/examples/cdp_mode/raw_cdp_nordstrom.py @@ -8,9 +8,6 @@ search = "cocktail dresses for women teal" sb.press_keys("input#keyword-search-input", search + "\n") sb.sleep(2.2) -for i in range(17): - sb.scroll_down(16) - sb.sleep(0.14) print('*** Nordstrom Search for "%s":' % search) unique_item_text = [] items = sb.find_elements("article") @@ -23,4 +20,5 @@ if price: price_text = price.text print("* %s (%s)" % (description.text, price_text)) + sb.scroll_down(16) sb.quit() diff --git a/examples/cdp_mode/raw_cdp_zillow.py b/examples/cdp_mode/raw_cdp_zillow.py index 512cae5439e..a87c83106a6 100644 --- a/examples/cdp_mode/raw_cdp_zillow.py +++ b/examples/cdp_mode/raw_cdp_zillow.py @@ -14,11 +14,10 @@ items = sb.find_visible_elements('[data-testid="property-card"]') print('*** %s:' % search) for i, item in enumerate(items): - print("<----- %s ----->" % (i + 1)) - print(item.query_selector('[data-testid*="price"]').text) + print(f"* <====== {i + 1} ======>") print(item.query_selector('[data-testid*="details"]').text) print(item.query_selector('[data-testid*="address"]').text) + print(item.query_selector('[data-testid*="price"]').text) item.scroll_into_view() -print("<-------------->") sb.sleep(2) sb.quit() diff --git a/examples/cdp_mode/raw_indeed_jobs.py b/examples/cdp_mode/raw_indeed_jobs.py new file mode 100644 index 00000000000..f56fcb09189 --- /dev/null +++ b/examples/cdp_mode/raw_indeed_jobs.py @@ -0,0 +1,16 @@ +from seleniumbase import SB + +with SB(uc=True, test=True, use_chromium=True, incognito=True) as sb: + sb.goto("https://www.indeed.com/?from=gnav-compui") + sb.sleep(0.6) + search = "AI Engineer" + sb.type('input[name="q"]', search) + sb.click('button[type="submit"]') + sb.sleep(3.6) + items = sb.find_visible_elements('[data-testid*="jobcard"]') + print('*** %s:' % search) + for i, item in enumerate(items): + print(f"* <====== {i + 1} ======>") + print(item.text) + item.scroll_into_view() + print(f"*** {len(items)} total items found!") diff --git a/examples/cdp_mode/raw_nordstrom.py b/examples/cdp_mode/raw_nordstrom.py index 354231b5073..e5e7bdce6cc 100644 --- a/examples/cdp_mode/raw_nordstrom.py +++ b/examples/cdp_mode/raw_nordstrom.py @@ -9,9 +9,6 @@ search = "cocktail dresses for women teal" sb.press_keys("input#keyword-search-input", search + "\n") sb.sleep(2.2) - for i in range(17): - sb.scroll_down(16) - sb.sleep(0.14) print('*** Nordstrom Search for "%s":' % search) unique_item_text = [] items = sb.find_elements("article") @@ -24,3 +21,4 @@ if price: price_text = price.text print("* %s (%s)" % (description.text, price_text)) + sb.scroll_down(16) diff --git a/examples/cdp_mode/raw_ralphlauren.py b/examples/cdp_mode/raw_ralphlauren.py index cdc791f970d..913b897d9b2 100644 --- a/examples/cdp_mode/raw_ralphlauren.py +++ b/examples/cdp_mode/raw_ralphlauren.py @@ -9,13 +9,17 @@ sb.sleep(2.2) print("*** Ralph Lauren Search for Dresses:") unique_item_text = [] - items = sb.find_elements('div.product-data') + soup = sb.get_beautiful_soup() + items = soup.select('div.product-data') for item in items: - description = item.query_selector("a.name-link") - if description and description.text not in unique_item_text: - unique_item_text.append(description.text) - print("* " + description.text) - price = item.query_selector('span[title="Price"]') - if price: - print(" (" + price.text.replace(" ", " ") + ")") - item.scroll_into_view() + description_element = item.select_one("a.name-link") + if description_element: + description_text = description_element.get_text(strip=True) + if description_text not in unique_item_text: + unique_item_text.append(description_text) + print(f"* {description_text}") + price_element = item.select_one('span[title="Price"]') + if price_element: + price_text = " ".join(price_element.get_text().split()) + print(f" ({price_text})") + sb.scroll_down(20) From 970a7ece29e52af6eca1c3e0aad3ac28e1e513b0 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Wed, 8 Jul 2026 00:58:01 -0400 Subject: [PATCH 5/5] Version 4.50.6 --- seleniumbase/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py index 0049e708595..9159d1b7954 100755 --- a/seleniumbase/__version__.py +++ b/seleniumbase/__version__.py @@ -1,2 +1,2 @@ # seleniumbase package -__version__ = "4.50.5" +__version__ = "4.50.6"