Skip to content

Commit d00fba0

Browse files
authored
Merge pull request #4175 from seleniumbase/base-chromium-support
Base Chromium support
2 parents 8fb610f + a283dfa commit d00fba0

File tree

22 files changed

+376
-23
lines changed

22 files changed

+376
-23
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ msedgedriver.exe
8282
operadriver.exe
8383
uc_driver.exe
8484

85+
# Chromium Zip Files
86+
chrome-mac.zip
87+
chrome-linux.zip
88+
chrome-win.zip
89+
90+
# Chromium folders
91+
chrome-mac
92+
chrome-linux
93+
chrome-win
94+
8595
# Chrome for Testing Zip Files
8696
chrome-mac-arm64.zip
8797
chrome-mac-x64.zip

help_docs/customizing_test_runs.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,20 @@ With the `SB()` and `Driver()` formats, the binary location is set via the `bina
508508

509509
--------
510510

511+
🎛️ To use the special `Chromium` binary:
512+
513+
```zsh
514+
sbase get chromium
515+
```
516+
517+
Then, run scripts with `--use-chromium` / `use_chromium=True`:
518+
519+
```zsh
520+
pytest --use-chromium -n8 --dashboard --html=report.html -v --rs --headless
521+
```
522+
523+
--------
524+
511525
🎛️ To use the special `Chrome for Testing` binary:
512526

513527
```zsh
@@ -721,6 +735,7 @@ sjw=None # Shortcut / Duplicate of "skip_js_waits".
721735
wfa=None # Shortcut / Duplicate of "wait_for_angularjs".
722736
cft=None # Use "Chrome for Testing"
723737
chs=None # Use "Chrome-Headless-Shell"
738+
use_chromium=None # Use base "Chromium"
724739
save_screenshot=None # Save a screenshot at the end of each test.
725740
no_screenshot=None # No screenshots saved unless tests directly ask it.
726741
page_load_strategy=None # Set Chrome PLS to "normal", "eager", or "none".
@@ -816,6 +831,7 @@ server=None # Shortcut / Duplicate of "servername".
816831
guest=None # Shortcut / Duplicate of "guest_mode".
817832
wire=None # Shortcut / Duplicate of "use_wire".
818833
pls=None # Shortcut / Duplicate of "page_load_strategy".
834+
use_chromium=None # Use base "Chromium"
819835
cft=None # Use "Chrome for Testing"
820836
chs=None # Use "Chrome-Headless-Shell"
821837
```

mkdocs_build/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Minimum Python version: 3.10 (for generating docs only)
33

44
regex>=2025.11.3
5-
pymdown-extensions>=10.19
5+
pymdown-extensions>=10.19.1
66
pipdeptree>=2.30.0
77
python-dateutil>=2.8.2
88
Markdown==3.10

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ packages = [
4747
"seleniumbase.drivers.brave_drivers",
4848
"seleniumbase.drivers.comet_drivers",
4949
"seleniumbase.drivers.atlas_drivers",
50+
"seleniumbase.drivers.chromium_drivers",
5051
"seleniumbase.extensions",
5152
"seleniumbase.fixtures",
5253
"seleniumbase.js_code",

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ certifi>=2025.11.12
88
exceptiongroup>=1.3.1
99
websockets>=15.0.1
1010
filelock~=3.19.1;python_version<"3.10"
11-
filelock>=3.20.0;python_version>="3.10"
11+
filelock>=3.20.1;python_version>="3.10"
1212
fasteners>=0.20
1313
mycdp>=1.3.2
1414
pynose>=1.5.5

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.45.2"
2+
__version__ = "4.45.3"

seleniumbase/behave/behave_sb.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,12 @@ def get_configured_sb(context):
494494
sb.binary_location = binary_location
495495
sb_config.binary_location = binary_location
496496
continue
497+
# Handle: -D use-chromium
498+
if low_key in ["use-chromium"] and not sb_config.binary_location:
499+
binary_location = "_chromium_"
500+
sb.binary_location = binary_location
501+
sb_config.binary_location = binary_location
502+
continue
497503
# Handle: -D cft
498504
if low_key in ["cft"] and not sb_config.binary_location:
499505
binary_location = "cft"

seleniumbase/console_scripts/ReadMe.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ sbase get chromedriver 114.0.5735.90
6868
sbase get chromedriver stable
6969
sbase get chromedriver beta
7070
sbase get chromedriver -p
71+
sbase get chromium
7172
sbase get cft 131
7273
sbase get chs
7374
```

seleniumbase/console_scripts/sb_install.py

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
sbase get chromedriver stable
2020
sbase get chromedriver beta
2121
sbase get chromedriver -p
22+
sbase get chromium
2223
sbase get cft 131
2324
sbase get chs
2425
Output:
@@ -46,16 +47,21 @@
4647
from seleniumbase import drivers # webdriver storage folder for SeleniumBase
4748
from seleniumbase.drivers import cft_drivers # chrome-for-testing
4849
from seleniumbase.drivers import chs_drivers # chrome-headless-shell
50+
from seleniumbase.drivers import chromium_drivers # base chromium
4951

5052
urllib3.disable_warnings()
5153
ARCH = platform.architecture()[0]
5254
IS_ARM_MAC = shared_utils.is_arm_mac()
5355
IS_MAC = shared_utils.is_mac()
56+
IS_ARM_LINUX = shared_utils.is_arm_linux()
5457
IS_LINUX = shared_utils.is_linux()
5558
IS_WINDOWS = shared_utils.is_windows()
5659
DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))
5760
DRIVER_DIR_CFT = os.path.dirname(os.path.realpath(cft_drivers.__file__))
5861
DRIVER_DIR_CHS = os.path.dirname(os.path.realpath(chs_drivers.__file__))
62+
DRIVER_DIR_CHROMIUM = os.path.dirname(
63+
os.path.realpath(chromium_drivers.__file__)
64+
)
5965
LOCAL_PATH = "/usr/local/bin/" # On Mac and Linux systems
6066
DEFAULT_CHROMEDRIVER_VERSION = "114.0.5735.90" # (If can't find LATEST_STABLE)
6167
DEFAULT_GECKODRIVER_VERSION = "v0.36.0"
@@ -203,6 +209,59 @@ def get_cft_latest_version_from_milestone(milestone):
203209
return url_request.json()["milestones"][milestone]["version"]
204210

205211

212+
def get_chromium_channel_revision(platform_code, channel):
213+
"""Snapshots only exist for revisions where a build occurred.
214+
Therefore, not all found revisions will lead to snapshots."""
215+
platform_key = None
216+
if platform_code in ["Mac_Arm", "Mac"]:
217+
platform_key = "Mac"
218+
elif platform_code in ["Linux_x64"]:
219+
platform_key = "Linux"
220+
elif platform_code in ["Win_x64"]:
221+
platform_key = "Windows"
222+
elif platform_code in ["Win"]:
223+
platform_key = "Win32"
224+
channel_key = None
225+
if channel.lower() == "stable":
226+
channel_key = "Stable"
227+
elif channel.lower() == "beta":
228+
channel_key = "Beta"
229+
elif channel.lower() == "dev":
230+
channel_key = "Dev"
231+
elif channel.lower() == "canary":
232+
channel_key = "Canary"
233+
base_url = "https://chromiumdash.appspot.com/fetch_releases"
234+
url = f"{base_url}?channel={channel_key}&platform={platform_key}&num=1"
235+
url_request = requests_get_with_retry(url)
236+
data = None
237+
if url_request.ok:
238+
data = url_request.text
239+
else:
240+
raise Exception("Could not determine Chromium revision!")
241+
if data:
242+
try:
243+
import ast
244+
245+
result = ast.literal_eval(data)
246+
revision = result[0]["chromium_main_branch_position"]
247+
return str(revision)
248+
except Exception:
249+
return get_latest_chromedriver_version(platform_code)
250+
else:
251+
return get_latest_chromedriver_version(platform_code)
252+
253+
254+
def get_chromium_latest_revision(platform_code):
255+
base_url = "https://storage.googleapis.com/chromium-browser-snapshots"
256+
url = f"{base_url}/{platform_code}/LAST_CHANGE"
257+
url_request = requests_get_with_retry(url)
258+
if url_request.ok:
259+
latest_revision = url_request.text
260+
else:
261+
raise Exception("Could not determine latest Chromium revision!")
262+
return latest_revision
263+
264+
206265
def get_latest_chromedriver_version(channel="Stable"):
207266
try:
208267
if getattr(sb_config, "cft_lkgv_json", None):
@@ -274,6 +333,11 @@ def main(override=None, intel_for_uc=None, force_uc=None):
274333
elif override.startswith("iedriver "):
275334
extra = override.split("iedriver ")[1]
276335
sys.argv = ["seleniumbase", "get", "iedriver", extra]
336+
elif override == "chromium":
337+
sys.argv = ["seleniumbase", "get", "chromium"]
338+
elif override.startswith("chromium "):
339+
extra = override.split("chromium ")[1]
340+
sys.argv = ["seleniumbase", "get", "chromium", extra]
277341
elif override == "cft":
278342
sys.argv = ["seleniumbase", "get", "cft"]
279343
elif override.startswith("cft "):
@@ -316,6 +380,8 @@ def main(override=None, intel_for_uc=None, force_uc=None):
316380
downloads_folder = DRIVER_DIR_CFT
317381
elif override == "chs" or name == "chs":
318382
downloads_folder = DRIVER_DIR_CHS
383+
elif override == "chromium":
384+
downloads_folder = DRIVER_DIR_CHROMIUM
319385
expected_contents = None
320386
platform_code = None
321387
copy_to_path = False
@@ -643,6 +709,31 @@ def main(override=None, intel_for_uc=None, force_uc=None):
643709
"https://storage.googleapis.com/chrome-for-testing-public/"
644710
"%s/%s/%s" % (use_version, platform_code, file_name)
645711
)
712+
elif name == "chromium":
713+
if IS_MAC:
714+
if IS_ARM_MAC:
715+
platform_code = "Mac_Arm"
716+
else:
717+
platform_code = "Mac"
718+
file_name = "chrome-mac.zip"
719+
elif IS_LINUX:
720+
platform_code = "Linux_x64"
721+
file_name = "chrome-linux.zip"
722+
elif IS_WINDOWS:
723+
if "64" in ARCH:
724+
platform_code = "Win_x64"
725+
else:
726+
platform_code = "Win"
727+
file_name = "chrome-win.zip"
728+
revision = get_chromium_latest_revision(platform_code)
729+
msg = c2 + "Chromium revision to download" + cr
730+
p_version = c3 + revision + cr
731+
log_d("\n*** %s = %s" % (msg, p_version))
732+
download_url = (
733+
"https://storage.googleapis.com/chromium-browser-snapshots/"
734+
"%s/%s/%s" % (platform_code, revision, file_name)
735+
)
736+
downloads_folder = DRIVER_DIR_CHROMIUM
646737
elif name == "chrome-headless-shell" or name == "chs":
647738
set_version = None
648739
found_version = None
@@ -1003,12 +1094,12 @@ def main(override=None, intel_for_uc=None, force_uc=None):
10031094
remote_file = requests_get_with_retry(download_url)
10041095
with open(file_path, "wb") as file:
10051096
file.write(remote_file.content)
1006-
log_d("%sDownload Complete!%s\n" % (c1, cr))
10071097

10081098
if file_name.endswith(".zip"):
10091099
zip_file_path = file_path
10101100
zip_ref = zipfile.ZipFile(zip_file_path, "r")
10111101
contents = zip_ref.namelist()
1102+
log_d("%sDownload Complete!%s\n" % (c1, cr))
10121103
if (
10131104
len(contents) >= 1
10141105
and name in ["chromedriver", "uc_driver", "geckodriver"]
@@ -1249,6 +1340,48 @@ def main(override=None, intel_for_uc=None, force_uc=None):
12491340
"Chrome for Testing was saved inside:\n%s%s\n%s\n"
12501341
% (pr_base_path, pr_sep, pr_folder_name)
12511342
)
1343+
elif name == "chromium":
1344+
# Zip file is valid. Proceed.
1345+
driver_path = None
1346+
driver_file = None
1347+
base_path = os.sep.join(zip_file_path.split(os.sep)[:-1])
1348+
folder_name = contents[0].split("/")[0]
1349+
folder_path = os.path.join(base_path, folder_name)
1350+
if IS_MAC or IS_LINUX:
1351+
if (
1352+
"chromium" in folder_path
1353+
and "drivers" in folder_path
1354+
and os.path.exists(folder_path)
1355+
):
1356+
shutil.rmtree(folder_path)
1357+
subprocess.run(
1358+
["unzip", zip_file_path, "-d", downloads_folder]
1359+
)
1360+
elif IS_WINDOWS:
1361+
subprocess.run(
1362+
[
1363+
"powershell",
1364+
"Expand-Archive",
1365+
"-Path",
1366+
zip_file_path,
1367+
"-DestinationPath",
1368+
downloads_folder,
1369+
"-Force",
1370+
]
1371+
)
1372+
else:
1373+
zip_ref.extractall(downloads_folder)
1374+
zip_ref.close()
1375+
with suppress(Exception):
1376+
os.remove(zip_file_path)
1377+
log_d("%sUnzip Complete!%s\n" % (c2, cr))
1378+
pr_base_path = c3 + base_path + cr
1379+
pr_sep = c3 + os.sep + cr
1380+
pr_folder_name = c3 + folder_name + cr
1381+
log_d(
1382+
"Chromium was saved inside:\n%s%s\n%s\n"
1383+
% (pr_base_path, pr_sep, pr_folder_name)
1384+
)
12521385
elif name == "chrome-headless-shell" or name == "chs":
12531386
# Zip file is valid. Proceed.
12541387
driver_path = None
@@ -1300,6 +1433,7 @@ def main(override=None, intel_for_uc=None, force_uc=None):
13001433
tar = tarfile.open(file_path)
13011434
contents = tar.getnames()
13021435
if len(contents) == 1:
1436+
log_d("%sDownload Complete!%s\n" % (c1, cr))
13031437
for f_name in contents:
13041438
# Remove existing version if exists
13051439
new_file = os.path.join(downloads_folder, str(f_name))
@@ -1337,6 +1471,7 @@ def main(override=None, intel_for_uc=None, force_uc=None):
13371471
else:
13381472
# Not a .zip file or a .tar.gz file. Just a direct download.
13391473
if "Driver" in file_name or "driver" in file_name:
1474+
log_d("%sDownload Complete!%s\n" % (c1, cr))
13401475
log_d("Making [%s] executable ..." % file_name)
13411476
make_executable(file_path)
13421477
log_d("%s[%s] is now ready for use!%s" % (c1, file_name, cr))

seleniumbase/console_scripts/sb_mkdir.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,12 @@ def main():
246246
data.append("msedgedriver.exe")
247247
data.append("operadriver.exe")
248248
data.append("uc_driver.exe")
249+
data.append("chrome-mac.zip")
250+
data.append("chrome-linux.zip")
251+
data.append("chrome-win.zip")
252+
data.append("chrome-mac")
253+
data.append("chrome-linux")
254+
data.append("chrome-win")
249255
data.append("chrome-mac-arm64.zip")
250256
data.append("chrome-mac-x64.zip")
251257
data.append("chrome-linux64.zip")

0 commit comments

Comments
 (0)