Skip to content

Commit 5a23445

Browse files
authored
Merge pull request #2882 from seleniumbase/uc-mode-updates-and-refactoring
UC Mode updates and refactoring
2 parents 0fb6618 + 8681140 commit 5a23445

File tree

14 files changed

+125
-133
lines changed

14 files changed

+125
-133
lines changed

Dockerfile

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
# SeleniumBase Docker Image
22
FROM ubuntu:22.04
3+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
4+
5+
#======================
6+
# Locale Configuration
7+
#======================
8+
RUN apt-get update
9+
RUN apt-get install -y --no-install-recommends tzdata locales
10+
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
11+
ENV LANG en_US.UTF-8
12+
ENV LANGUAGE en_US:en
13+
ENV LC_ALL en_US.UTF-8
14+
ENV TZ="America/New_York"
15+
16+
#======================
17+
# Install Common Fonts
18+
#======================
19+
RUN apt-get update
20+
RUN apt-get install -y \
21+
fonts-liberation \
22+
fonts-open-sans \
23+
fonts-mononoki \
24+
fonts-roboto \
25+
fonts-lato
326

427
#============================
528
# Install Linux Dependencies
629
#============================
7-
RUN apt-get update && apt-get install -y \
8-
fonts-liberation \
30+
RUN apt-get update
31+
RUN apt-get install -y \
932
libasound2 \
1033
libatk-bridge2.0-0 \
1134
libatk1.0-0 \
@@ -17,60 +40,57 @@ RUN apt-get update && apt-get install -y \
1740
libgtk-3-0 \
1841
libnspr4 \
1942
libnss3 \
43+
libu2f-udev \
44+
libvulkan1 \
2045
libwayland-client0 \
2146
libxcomposite1 \
2247
libxdamage1 \
2348
libxfixes3 \
2449
libxkbcommon0 \
25-
libxrandr2 \
26-
libu2f-udev \
27-
libvulkan1 \
28-
xdg-utils
50+
libxrandr2
51+
52+
#==========================
53+
# Install useful utilities
54+
#==========================
55+
RUN apt-get update
56+
RUN apt-get install -y xdg-utils
2957

3058
#=================================
3159
# Install Bash Command Line Tools
3260
#=================================
61+
RUN apt-get update
3362
RUN apt-get -qy --no-install-recommends install \
3463
curl \
3564
sudo \
3665
unzip \
3766
vim \
3867
wget \
39-
xvfb \
40-
&& rm -rf /var/lib/apt/lists/*
68+
xvfb
4169

4270
#================
4371
# Install Chrome
4472
#================
45-
RUN curl -LO https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
46-
RUN apt-get install -y ./google-chrome-stable_current_amd64.deb
73+
RUN apt-get update
74+
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
75+
RUN dpkg -i google-chrome-stable_current_amd64.deb
76+
RUN apt-get -fy --no-install-recommends install
4777
RUN rm google-chrome-stable_current_amd64.deb
4878

4979
#================
5080
# Install Python
5181
#================
52-
RUN apt-get update -y
53-
RUN apt-get install -y python3 python3-pip python3-setuptools python3-dev
82+
RUN apt-get update
83+
RUN apt-get install -y python3 python3-pip python3-setuptools python3-dev python3-tk
5484
RUN alias python=python3
5585
RUN echo "alias python=python3" >> ~/.bashrc
5686
RUN apt-get -qy --no-install-recommends install python3.10
5787
RUN rm /usr/bin/python3
5888
RUN ln -s python3.10 /usr/bin/python3
5989

60-
#=============================================
61-
# Allow Special Characters in Python Programs
62-
#=============================================
63-
RUN export PYTHONIOENCODING=utf8
64-
RUN echo "export PYTHONIOENCODING=utf8" >> ~/.bashrc
65-
66-
#===========================
67-
# Configure Virtual Display
68-
#===========================
69-
RUN set -e
70-
RUN echo "Starting X virtual framebuffer (Xvfb) in background..."
71-
RUN Xvfb -ac :99 -screen 0 1280x1024x16 > /dev/null 2>&1 &
72-
RUN export DISPLAY=:99
73-
RUN exec "$@"
90+
#===============
91+
# Cleanup Lists
92+
#===============
93+
RUN rm -rf /var/lib/apt/lists/*
7494

7595
#=====================
7696
# Set up SeleniumBase
@@ -89,6 +109,7 @@ RUN find . -name '*.pyc' -delete
89109
RUN pip install --upgrade pip setuptools wheel
90110
RUN cd /SeleniumBase && ls && pip install -r requirements.txt --upgrade
91111
RUN cd /SeleniumBase && pip install .
112+
RUN pip install pyautogui
92113

93114
#=======================
94115
# Download chromedriver

examples/raw_cdp_logging.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
try:
66
url = "seleniumbase.io/apps/turnstile"
77
driver.uc_open_with_reconnect(url, 2)
8-
driver.switch_to_frame("iframe")
9-
driver.uc_click("span")
8+
driver.uc_gui_handle_cf()
109
driver.sleep(3)
1110
pprint(driver.get_log("performance"))
1211
finally:

examples/raw_form_turnstile.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
with SB(uc=True, test=True) as sb:
44
url = "seleniumbase.io/apps/form_turnstile"
5-
sb.driver.uc_open_with_reconnect(url, 2)
5+
sb.uc_open_with_reconnect(url, 2)
66
sb.press_keys("#name", "SeleniumBase")
77
sb.press_keys("#email", "test@test.test")
88
sb.press_keys("#phone", "1-555-555-5555")
@@ -12,8 +12,8 @@
1212
sb.click('span:contains("9:00 PM")')
1313
sb.highlight_click('input[value="AR"] + span')
1414
sb.click('input[value="cc"] + span')
15-
sb.switch_to_frame("iframe")
16-
sb.driver.uc_click("span")
15+
sb.scroll_to("iframe")
16+
sb.uc_gui_handle_cf()
1717
sb.highlight("img#captcha-success", timeout=3)
1818
sb.highlight_click('button:contains("Request & Pay")')
1919
sb.highlight("img#submit-success")

examples/raw_nopecha.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
from seleniumbase import SB
22

33
with SB(uc=True, test=True) as sb:
4-
sb.driver.uc_open_with_reconnect("nopecha.com/demo/turnstile", 4)
5-
if sb.is_element_visible("#example-container0 iframe"):
6-
sb.switch_to_frame("#example-container0 iframe")
7-
if not sb.is_element_visible("circle.success-circle"):
8-
sb.driver.uc_click("span", reconnect_time=3)
9-
sb.switch_to_frame("#example-container0 iframe")
10-
sb.switch_to_default_content()
11-
12-
sb.switch_to_frame("#example-container5 iframe")
13-
sb.driver.uc_click("span", reconnect_time=2.5)
14-
sb.switch_to_frame("#example-container5 iframe")
15-
sb.assert_element("svg#success-icon", timeout=3)
16-
sb.switch_to_parent_frame()
4+
sb.uc_open_with_disconnect("nopecha.com/demo/turnstile", 3.5)
5+
sb.uc_gui_press_keys("\t\t ")
6+
sb.sleep(3.5)
7+
sb.connect()
8+
sb.uc_gui_handle_cf("#example-container5 iframe")
179

1810
if sb.is_element_visible("#example-container0 iframe"):
1911
sb.switch_to_frame("#example-container0 iframe")

examples/raw_turnstile.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
11
from seleniumbase import SB
22

3-
4-
def open_the_turnstile_page(sb):
3+
with SB(uc=True, test=True) as sb:
54
url = "seleniumbase.io/apps/turnstile"
6-
sb.driver.uc_open_with_reconnect(url, reconnect_time=2)
7-
8-
9-
def click_turnstile_and_verify(sb):
10-
sb.driver.switch_to_frame("iframe")
11-
sb.driver.uc_click("span")
5+
sb.uc_open_with_reconnect(url, reconnect_time=2)
6+
sb.uc_gui_handle_cf()
127
sb.assert_element("img#captcha-success", timeout=3)
13-
14-
15-
with SB(uc=True, test=True) as sb:
16-
open_the_turnstile_page(sb)
17-
try:
18-
click_turnstile_and_verify(sb)
19-
except Exception:
20-
open_the_turnstile_page(sb)
21-
click_turnstile_and_verify(sb)
228
sb.set_messenger_theme(location="top_left")
239
sb.post_message("SeleniumBase wasn't detected", duration=3)

examples/uc_cdp_events.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@ def add_cdp_listener(self):
1313
)
1414

1515
def click_turnstile_and_verify(sb):
16-
sb.switch_to_frame("iframe")
17-
sb.driver.uc_click("span")
16+
sb.uc_gui_handle_cf()
1817
sb.assert_element("img#captcha-success", timeout=3)
1918
sb.highlight("img#captcha-success", loops=8)
2019

2120
def test_display_cdp_events(self):
2221
if not (self.undetectable and self.uc_cdp_events):
2322
self.get_new_driver(undetectable=True, uc_cdp_events=True)
2423
url = "seleniumbase.io/apps/turnstile"
25-
self.driver.uc_open_with_reconnect(url, 2)
24+
self.uc_open_with_reconnect(url, 2)
2625
self.add_cdp_listener()
2726
self.click_turnstile_and_verify()
2827
self.sleep(1)

help_docs/uc_mode.md

Lines changed: 32 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
* Automatically changing user agents to prevent detection.
2222
* Automatically setting various chromium args as needed.
23-
* Has special methods. Eg. `driver.uc_click(selector)`
23+
* Has special `uc_*()` methods.
2424

2525
👤 Here's an example with the <b><code translate="no">Driver</code></b> manager:
2626

@@ -67,22 +67,11 @@ with SB(uc=True, test=True) as sb:
6767
```python
6868
from seleniumbase import SB
6969

70-
def open_the_turnstile_page(sb):
70+
with SB(uc=True, test=True) as sb:
7171
url = "seleniumbase.io/apps/turnstile"
72-
sb.driver.uc_open_with_reconnect(url, reconnect_time=2)
73-
74-
def click_turnstile_and_verify(sb):
75-
sb.switch_to_frame("iframe")
76-
sb.driver.uc_click("span")
72+
sb.uc_open_with_reconnect(url, reconnect_time=2)
73+
sb.uc_gui_handle_cf()
7774
sb.assert_element("img#captcha-success", timeout=3)
78-
79-
with SB(uc=True, test=True) as sb:
80-
open_the_turnstile_page(sb)
81-
try:
82-
click_turnstile_and_verify(sb)
83-
except Exception:
84-
open_the_turnstile_page(sb)
85-
click_turnstile_and_verify(sb)
8675
sb.set_messenger_theme(location="top_left")
8776
sb.post_message("SeleniumBase wasn't detected", duration=3)
8877
```
@@ -129,6 +118,27 @@ with SB(uc=True, test=True, ad_block_on=True) as sb:
129118

130119
<img src="https://seleniumbase.github.io/other/ttm_bypass.png" title="SeleniumBase" width="540">
131120

121+
👤 <b>On Linux</b>, use `sb.uc_gui_handle_cf()` to handle Cloudflare Turnstiles:
122+
123+
```python
124+
from seleniumbase import SB
125+
126+
with SB(uc=True, test=True) as sb:
127+
url = "https://www.virtualmanager.com/en/login"
128+
sb.uc_open_with_reconnect(url, 4)
129+
print(sb.get_page_title())
130+
sb.uc_gui_handle_cf() # Ready if needed!
131+
print(sb.get_page_title())
132+
sb.assert_element('input[name*="email"]')
133+
sb.assert_element('input[name*="login"]')
134+
sb.set_messenger_theme(location="bottom_center")
135+
sb.post_message("SeleniumBase wasn't detected!")
136+
```
137+
138+
<a href="https://github.com/mdmintz/undetected-testing/actions/runs/9637461606/job/26576722411"><img width="540" alt="uc_gui_handle_cf on Linux" src="https://github.com/seleniumbase/SeleniumBase/assets/6788579/6aceb2a3-2a32-4521-b30a-f79446d2ce28"></a>
139+
140+
The 2nd `print()` should output "Virtual Manager", which means that the automation successfully passed the Turnstile.
141+
132142
--------
133143

134144
👤 In <b translate="no">UC Mode</b>, <code translate="no">driver.get(url)</code> has been modified from its original version: If anti-bot services are detected from a <code translate="no">requests.get(url)</code> call that's made before navigating to the website, then <code translate="no">driver.uc_open_with_reconnect(url)</code> will be used instead. To open a URL normally in <b translate="no">UC Mode</b>, use <code translate="no">driver.default_get(url)</code>.
@@ -144,6 +154,7 @@ with SB(uc=True, test=True, ad_block_on=True) as sb:
144154
<img src="https://seleniumbase.github.io/other/pixelscan.jpg" title="SeleniumBase" width="540">
145155

146156
### 👤 Here are some UC Mode examples that bypass CAPTCHAs when clicking is required:
157+
* [SeleniumBase/examples/raw_pyautogui.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_pyautogui.py)
147158
* [SeleniumBase/examples/raw_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_turnstile.py)
148159
* [SeleniumBase/examples/raw_form_turnstile.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/raw_form_turnstile.py)
149160
* [SeleniumBase/examples/uc_cdp_events.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/uc_cdp_events.py)
@@ -214,11 +225,6 @@ driver.reconnect("breakpoint")
214225

215226
(Note that while the special <b><code translate="no">UC Mode</code></b> breakpoint is active, you can't use <b><code translate="no">Selenium</code></b> commands in the browser, and the browser can't detect <b><code translate="no">Selenium</code></b>.)
216227

217-
👤 The two main causes of getting detected in <b translate="no">UC Mode</b> (which are both easily handled) are:
218-
219-
<li>Timing. (<b translate="no">UC Mode</b> methods let you customize default values that aren't good enough for your environment.)</li>
220-
<li>Not using <b><code translate="no">driver.uc_click(selector)</code></b> when you need to remain undetected while clicking something.</li>
221-
222228
👤 On Linux, you may need to use `driver.uc_gui_handle_cf()` to successfully bypass a Cloudflare CAPTCHA. If there's more than one iframe on that website (and Cloudflare isn't the first one) then put the CSS Selector of that iframe as the first arg to `driver.uc_gui_handle_cf()`. This method uses `pyautogui`. In order for `pyautogui` to focus on the correct element, use `xvfb=True` / `--xvfb` to activate a special virtual display on Linux.
223229

224230
👤 To find out if <b translate="no">UC Mode</b> will work at all on a specific site (before adjusting for timing), load your site with the following script:
@@ -268,46 +274,15 @@ with ThreadPoolExecutor(max_workers=len(urls)) as executor:
268274

269275
--------
270276

271-
👥 <b>Double Duty:</b> Here's an example of handling two CAPTCHAs on one page:
272-
273-
<img src="https://seleniumbase.github.io/other/nopecha.png" title="SeleniumBase" align="center" width="630">
274-
275-
```python
276-
from seleniumbase import SB
277-
278-
with SB(uc=True, test=True) as sb:
279-
sb.driver.uc_open_with_reconnect("nopecha.com/demo/turnstile", 3.4)
280-
if sb.is_element_visible("#example-container0 iframe"):
281-
sb.switch_to_frame("#example-container0 iframe")
282-
if not sb.is_element_visible("circle.success-circle"):
283-
sb.driver.uc_click("span", reconnect_time=3)
284-
sb.switch_to_frame("#example-container0 iframe")
285-
sb.switch_to_default_content()
286-
287-
sb.switch_to_frame("#example-container5 iframe")
288-
sb.driver.uc_click("span", reconnect_time=2.5)
289-
sb.switch_to_frame("#example-container5 iframe")
290-
sb.assert_element("svg#success-icon", timeout=3)
291-
sb.switch_to_parent_frame()
292-
293-
if sb.is_element_visible("#example-container0 iframe"):
294-
sb.switch_to_frame("#example-container0 iframe")
295-
sb.assert_element("circle.success-circle")
296-
sb.switch_to_parent_frame()
297-
298-
sb.set_messenger_theme(location="top_center")
299-
sb.post_message("SeleniumBase wasn't detected!", duration=3)
300-
```
301-
302-
--------
303-
304277
👤 <b>What makes UC Mode work?</b>
305278

306279
Here are the 3 primary things that <b translate="no">UC Mode</b> does to make bots appear human:
307280

281+
<ul>
308282
<li>Modifies <b><code translate="no">chromedriver</code></b> to rename <b translate="no">Chrome DevTools Console</b> variables.</li>
309283
<li>Launches <b translate="no">Chrome</b> browsers before attaching <b><code translate="no">chromedriver</code></b> to them.</li>
310284
<li>Disconnects <b><code translate="no">chromedriver</code></b> from <b translate="no">Chrome</b> during stealthy actions.</li>
285+
</ul>
311286

312287
For example, if the <b translate="no">Chrome DevTools Console</b> variables aren't renamed, you can expect to find them easily when using <b><code translate="no">selenium</code></b> for browser automation:
313288

@@ -321,13 +296,17 @@ While <b><code translate="no">chromedriver</code></b> is connected to <b transla
321296

322297
Links to those <a href="https://github.com/SeleniumHQ/selenium">raw <b>Selenium</b></a> method definitions have been provided for reference (but you don't need to call those methods directly):
323298

299+
<ul>
324300
<li><b><code translate="no"><a href="https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/common/service.py#L135">driver.service.stop()</a></code></b></li>
325301
<li><b><code translate="no"><a href="https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/common/service.py#L91">driver.service.start()</a></code></b></li>
326302
<li><b><code translate="no"><a href="https://github.com/SeleniumHQ/selenium/blob/9c6ccdbf40356284fad342f70fbdc0afefd27bd3/py/selenium/webdriver/remote/webdriver.py#L284">driver.start_session(capabilities)</a></code></b></li>
303+
</ul>
327304

328305
Also note that <b><code translate="no">chromedriver</code></b> isn't detectable in a browser tab if it never touches that tab. Here's a JS command that lets you open a URL in a new tab (from your current tab):
329306

307+
<ul>
330308
<li><b><code translate="no">window.open("URL");</code></b> --> (Info: <a href="https://www.w3schools.com/jsref/met_win_open.asp" target="_blank">W3Schools</a>)</li>
309+
</ul>
331310

332311
The above JS method is used within <b translate="no"><code>SeleniumBase</code></b> <b translate="no">UC Mode</b> methods for opening URLs in a stealthy way. Since some websites try to detect if your browser is a bot on the initial page load, this allows you to bypass detection in those situations. After a few seconds (customizable), <b translate="no">UC Mode</b> tells <b><code translate="no">chromedriver</code></b> to connect to that tab so that automated commands can now be issued. At that point, <b><code translate="no">chromedriver</code></b> could be detected if websites are looking for it (but generally websites only look for it during specific events, such as page loads, form submissions, and button clicks).
333312

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
pip>=24.0;python_version<"3.8"
2-
pip>=24.1;python_version>="3.8"
2+
pip>=24.1.1;python_version>="3.8"
33
packaging>=24.0;python_version<"3.8"
44
packaging>=24.1;python_version>="3.8"
55
setuptools>=68.0.0;python_version<"3.8"
6-
setuptools>=70.1.0;python_version>="3.8"
6+
setuptools>=70.1.1;python_version>="3.8"
77
wheel>=0.42.0;python_version<"3.8"
88
wheel>=0.43.0;python_version>="3.8"
99
attrs>=23.2.0

seleniumbase/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# seleniumbase package
2-
__version__ = "4.28.0"
2+
__version__ = "4.28.1"

0 commit comments

Comments
 (0)