Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/scripts/pinned_browsers.py
4502 views
1
#!/usr/bin/env python
2
3
import hashlib
4
import json
5
import os
6
import sys
7
from pathlib import Path
8
9
import urllib3
10
from packaging.version import parse
11
12
# Find the current stable versions of each browser we
13
# support and the sha256 of these. That's useful for
14
# updating `//common:repositories.bzl`
15
16
http = urllib3.PoolManager()
17
18
19
def calculate_hash(url):
20
print(f"Calculate hash for {url}", file=sys.stderr)
21
h = hashlib.sha256()
22
r = http.request("GET", url, preload_content=False)
23
for b in iter(lambda: r.read(4096), b""):
24
h.update(b)
25
return h.hexdigest()
26
27
28
def get_chrome_info_for_channel(channel):
29
r = http.request(
30
"GET",
31
f"https://chromiumdash.appspot.com/fetch_releases?channel={channel}&num=1&platform=Mac,Linux",
32
)
33
all_versions = json.loads(r.data)
34
# use the same milestone for all chrome releases, so pick the lowest
35
milestones = [version["milestone"] for version in all_versions if version["milestone"]]
36
if not milestones:
37
raise ValueError(f"No Chrome versions with milestones found for channel '{channel}'")
38
milestone = min(milestones)
39
r = http.request(
40
"GET",
41
"https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json",
42
)
43
versions = json.loads(r.data)["versions"]
44
45
return sorted(
46
filter(lambda v: v["version"].split(".")[0] == str(milestone), versions),
47
key=lambda v: parse(v["version"]),
48
)[-1]
49
50
51
def chromedriver(selected_version, workspace_prefix=""):
52
content = ""
53
54
drivers = selected_version["downloads"]["chromedriver"]
55
56
url = next((d["url"] for d in drivers if d["platform"] == "linux64"), None)
57
if url is None:
58
raise ValueError("No chromedriver download found for platform 'linux64'")
59
sha = calculate_hash(url)
60
61
content += f""" http_archive(
62
name = "linux_{workspace_prefix}chromedriver",
63
url = "{url}",
64
sha256 = "{sha}",
65
strip_prefix = "chromedriver-linux64",
66
build_file_content = \"\"\"
67
load("@aspect_rules_js//js:defs.bzl", "js_library")
68
package(default_visibility = ["//visibility:public"])
69
70
exports_files(["chromedriver"])
71
72
js_library(
73
name = "chromedriver-js",
74
data = ["chromedriver"],
75
)
76
\"\"\",
77
)
78
"""
79
80
url = next((d["url"] for d in drivers if d["platform"] == "mac-arm64"), None)
81
if url is None:
82
raise ValueError("No chromedriver download found for platform 'mac-arm64'")
83
sha = calculate_hash(url)
84
85
content += f"""
86
http_archive(
87
name = "mac_{workspace_prefix}chromedriver",
88
url = "{url}",
89
sha256 = "{sha}",
90
strip_prefix = "chromedriver-mac-arm64",
91
build_file_content = \"\"\"
92
load("@aspect_rules_js//js:defs.bzl", "js_library")
93
package(default_visibility = ["//visibility:public"])
94
95
exports_files(["chromedriver"])
96
97
js_library(
98
name = "chromedriver-js",
99
data = ["chromedriver"],
100
)
101
\"\"\",
102
)
103
"""
104
105
return content
106
107
108
def chrome(selected_version, workspace_prefix=""):
109
content = ""
110
chrome_downloads = selected_version["downloads"]["chrome"]
111
112
url = next((d["url"] for d in chrome_downloads if d["platform"] == "linux64"), None)
113
if url is None:
114
raise ValueError("No Chrome download found for platform 'linux64'")
115
sha = calculate_hash(url)
116
117
content += f"""
118
http_archive(
119
name = "linux_{workspace_prefix}chrome",
120
url = "{url}",
121
sha256 = "{sha}",
122
build_file_content = \"\"\"
123
load("@aspect_rules_js//js:defs.bzl", "js_library")
124
package(default_visibility = ["//visibility:public"])
125
126
filegroup(
127
name = "files",
128
srcs = glob(["**/*"]),
129
)
130
131
exports_files(["chrome-linux64/chrome"])
132
133
js_library(
134
name = "chrome-js",
135
data = [":files"],
136
)
137
\"\"\",
138
)
139
"""
140
141
url = next((d["url"] for d in chrome_downloads if d["platform"] == "mac-arm64"), None)
142
if url is None:
143
raise ValueError("No Chrome download found for platform 'mac-arm64'")
144
sha = calculate_hash(url) # Calculate SHA for Mac chrome
145
146
content += f""" http_archive(
147
name = "mac_{workspace_prefix}chrome",
148
url = "{url}",
149
sha256 = "{sha}",
150
strip_prefix = "chrome-mac-arm64",
151
patch_cmds = [
152
"mv 'Google Chrome for Testing.app' Chrome.app",
153
"mv 'Chrome.app/Contents/MacOS/Google Chrome for Testing' Chrome.app/Contents/MacOS/Chrome",
154
],
155
build_file_content = \"\"\"
156
load("@aspect_rules_js//js:defs.bzl", "js_library")
157
package(default_visibility = ["//visibility:public"])
158
159
exports_files(["Chrome.app"])
160
161
js_library(
162
name = "chrome-js",
163
data = glob(["Chrome.app/**/*"]),
164
)
165
\"\"\",
166
)
167
"""
168
169
return content
170
171
172
def convert_keys_to_lowercase(obj):
173
if isinstance(obj, dict):
174
return {k.lower(): convert_keys_to_lowercase(v) for k, v in obj.items()}
175
elif isinstance(obj, list):
176
return [convert_keys_to_lowercase(i) for i in obj]
177
else:
178
return obj
179
180
181
def case_insensitive_json_loads(json_str):
182
data = json.loads(json_str)
183
return convert_keys_to_lowercase(data)
184
185
186
def get_edge_versions(platform):
187
"""Fetch all available Edge browser versions for a platform from enterprise API."""
188
r = http.request("GET", "https://edgeupdates.microsoft.com/api/products?view=enterprise")
189
all_data = case_insensitive_json_loads(r.data)
190
191
platform_name = "MacOS" if platform == "mac" else "Linux"
192
artifact_name = "pkg" if platform == "mac" else "deb"
193
194
versions = []
195
for data in all_data:
196
if data.get("product") != "Stable":
197
continue
198
for release in data["releases"]:
199
if release.get("platform") != platform_name:
200
continue
201
for artifact in release["artifacts"]:
202
if artifact["artifactname"] == artifact_name:
203
versions.append(
204
{
205
"url": artifact["location"],
206
"hash": artifact["hash"],
207
"version": release["productversion"],
208
}
209
)
210
return versions
211
212
213
def get_edgedriver_version(major, platform):
214
"""Get the latest EdgeDriver version for a given major version and platform."""
215
platform_suffix = "LINUX" if platform == "linux" else "MACOS"
216
r = http.request(
217
"GET",
218
f"https://msedgedriver.microsoft.com/LATEST_RELEASE_{major}_{platform_suffix}",
219
)
220
if r.status != 200:
221
return None
222
return r.data.decode("utf-16").strip()
223
224
225
def get_edgedriver_url(version, platform):
226
"""Get EdgeDriver download URL if it exists."""
227
if platform == "linux":
228
url = f"https://msedgedriver.microsoft.com/{version}/edgedriver_linux64.zip"
229
else:
230
url = f"https://msedgedriver.microsoft.com/{version}/edgedriver_mac64_m1.zip"
231
r = http.request("HEAD", url)
232
return url if r.status == 200 else None
233
234
235
def find_matching_edge_version(platform):
236
"""Find the latest Edge version where both browser and driver are available."""
237
browsers = get_edge_versions(platform)
238
239
# Sort by version descending (newest first)
240
browsers.sort(key=lambda x: parse(x["version"]), reverse=True)
241
242
for browser in browsers:
243
major = browser["version"].split(".")[0]
244
driver_version = get_edgedriver_version(major, platform)
245
if not driver_version:
246
print(f" No driver for {platform} major version {major}", file=sys.stderr)
247
continue
248
249
driver_url = get_edgedriver_url(driver_version, platform)
250
if not driver_url:
251
print(
252
f" Driver {driver_version} not downloadable for {platform}",
253
file=sys.stderr,
254
)
255
continue
256
257
print(
258
f" Found match: browser={browser['version']}, driver={driver_version}",
259
file=sys.stderr,
260
)
261
return {
262
"browser": browser,
263
"driver_version": driver_version,
264
"driver_url": driver_url,
265
}
266
267
return None
268
269
270
def mac_edge_browser_content(browser_url, browser_hash, browser_version):
271
"""Generate Bazel content for Mac Edge browser."""
272
return f"""
273
pkg_archive(
274
name = "mac_edge",
275
url = "{browser_url}",
276
sha256 = "{browser_hash.lower()}",
277
move = {{
278
"MicrosoftEdge-{browser_version}.pkg/Payload/Microsoft Edge.app": "Edge.app",
279
}},
280
build_file_content = \"\"\"
281
load("@aspect_rules_js//js:defs.bzl", "js_library")
282
package(default_visibility = ["//visibility:public"])
283
284
exports_files(["Edge.app"])
285
286
js_library(
287
name = "edge-js",
288
data = glob(["Edge.app/**/*"], allow_empty = True),
289
)
290
\"\"\",
291
)
292
"""
293
294
295
def linux_edge_browser_content(browser_url, browser_hash):
296
"""Generate Bazel content for Linux Edge browser."""
297
return f"""
298
deb_archive(
299
name = "linux_edge",
300
url = "{browser_url}",
301
sha256 = "{browser_hash.lower()}",
302
build_file_content = \"\"\"
303
load("@aspect_rules_js//js:defs.bzl", "js_library")
304
package(default_visibility = ["//visibility:public"])
305
306
filegroup(
307
name = "files",
308
srcs = glob(["**/*"]),
309
)
310
311
exports_files(["opt/microsoft/msedge/microsoft-edge"])
312
313
js_library(
314
name = "edge-js",
315
data = [":files"],
316
)
317
\"\"\",
318
)
319
"""
320
321
322
def edge_and_edgedriver():
323
"""Fetch Edge browser and EdgeDriver, ensuring versions are compatible."""
324
matches = {}
325
326
for platform in ["mac", "linux"]:
327
print(f"Finding matching Edge version for {platform}...", file=sys.stderr)
328
match = find_matching_edge_version(platform)
329
if match:
330
matches[platform] = match
331
else:
332
print(
333
f"Warning: No matching Edge browser/driver found for {platform}",
334
file=sys.stderr,
335
)
336
337
content = ""
338
339
# Output browsers first: mac, then linux
340
if "mac" in matches:
341
browser = matches["mac"]["browser"]
342
content += mac_edge_browser_content(browser["url"], browser["hash"], browser["version"])
343
344
if "linux" in matches:
345
browser = matches["linux"]["browser"]
346
content += linux_edge_browser_content(browser["url"], browser["hash"])
347
348
# Output drivers: linux, then mac
349
if "linux" in matches:
350
content += edgedriver_content("linux_edgedriver", matches["linux"]["driver_url"])
351
352
if "mac" in matches:
353
content += edgedriver_content("mac_edgedriver", matches["mac"]["driver_url"])
354
355
return content
356
357
358
def edgedriver_content(name, driver_url):
359
"""Generate Bazel content for EdgeDriver."""
360
driver_sha = calculate_hash(driver_url)
361
return f"""
362
http_archive(
363
name = "{name}",
364
url = "{driver_url}",
365
sha256 = "{driver_sha}",
366
build_file_content = \"\"\"
367
load("@aspect_rules_js//js:defs.bzl", "js_library")
368
package(default_visibility = ["//visibility:public"])
369
370
exports_files(["msedgedriver"])
371
372
js_library(
373
name = "msedgedriver-js",
374
data = ["msedgedriver"],
375
)
376
\"\"\",
377
)
378
"""
379
380
381
def geckodriver():
382
content = ""
383
384
r = http.request("GET", "https://api.github.com/repos/mozilla/geckodriver/releases/latest")
385
for a in json.loads(r.data)["assets"]:
386
if a["name"].endswith("-linux64.tar.gz"):
387
url = a["browser_download_url"]
388
sha = calculate_hash(url)
389
content = (
390
content
391
+ f""" http_archive(
392
name = "linux_geckodriver",
393
url = "{url}",
394
sha256 = "{sha}",
395
build_file_content = \"\"\"
396
load("@aspect_rules_js//js:defs.bzl", "js_library")
397
package(default_visibility = ["//visibility:public"])
398
399
exports_files(["geckodriver"])
400
401
js_library(
402
name = "geckodriver-js",
403
data = ["geckodriver"],
404
)
405
\"\"\",
406
)
407
"""
408
)
409
410
if a["name"].endswith("-macos-aarch64.tar.gz"):
411
url = a["browser_download_url"]
412
sha = calculate_hash(url)
413
content = (
414
content
415
+ f"""
416
http_archive(
417
name = "mac_geckodriver",
418
url = "{url}",
419
sha256 = "{sha}",
420
build_file_content = \"\"\"
421
load("@aspect_rules_js//js:defs.bzl", "js_library")
422
package(default_visibility = ["//visibility:public"])
423
424
exports_files(["geckodriver"])
425
426
js_library(
427
name = "geckodriver-js",
428
data = ["geckodriver"],
429
)
430
\"\"\",
431
)
432
"""
433
)
434
return content
435
436
437
def firefox():
438
firefox_versions = json.loads(firefox_version_data())
439
440
latest_firefox = firefox_versions["LATEST_FIREFOX_VERSION"]
441
sha_linux = calculate_hash(firefox_linux(latest_firefox))
442
sha_mac = calculate_hash(firefox_mac(latest_firefox))
443
content = print_firefox(latest_firefox, "", sha_linux, sha_mac)
444
445
beta_firefox = firefox_versions["LATEST_FIREFOX_RELEASED_DEVEL_VERSION"]
446
if latest_firefox != beta_firefox:
447
sha_linux = calculate_hash(firefox_linux(beta_firefox))
448
sha_mac = calculate_hash(firefox_mac(beta_firefox))
449
return content + print_firefox(beta_firefox, "beta_", sha_linux, sha_mac)
450
451
452
def firefox_version_data():
453
versions = http.request("GET", "https://product-details.mozilla.org/1.0/firefox_versions.json")
454
return versions.data
455
456
457
def firefox_linux(version):
458
if int(version.split(".")[0]) < 135:
459
return f"https://ftp.mozilla.org/pub/firefox/releases/{version}/linux-x86_64/en-US/firefox-{version}.tar.bz2"
460
else:
461
return f"https://ftp.mozilla.org/pub/firefox/releases/{version}/linux-x86_64/en-US/firefox-{version}.tar.xz"
462
463
464
def firefox_mac(version):
465
return f"https://ftp.mozilla.org/pub/firefox/releases/{version}/mac/en-US/Firefox%20{version}.dmg"
466
467
468
def print_firefox(version, workspace_name, sha_linux, sha_mac):
469
content = ""
470
471
content = (
472
content
473
+ f""" http_archive(
474
name = "linux_{workspace_name}firefox",
475
url = "{firefox_linux(version)}",
476
sha256 = "{sha_linux}",
477
build_file_content = \"\"\"
478
load("@aspect_rules_js//js:defs.bzl", "js_library")
479
package(default_visibility = ["//visibility:public"])
480
481
filegroup(
482
name = "files",
483
srcs = glob(["**/*"]),
484
)
485
486
exports_files(["firefox/firefox"])
487
488
js_library(
489
name = "firefox-js",
490
data = [":files"],
491
)
492
\"\"\",
493
)
494
495
"""
496
)
497
498
content = (
499
content
500
+ f""" dmg_archive(
501
name = "mac_{workspace_name}firefox",
502
url = "{firefox_mac(version)}",
503
sha256 = "{sha_mac}",
504
build_file_content = \"\"\"
505
load("@aspect_rules_js//js:defs.bzl", "js_library")
506
package(default_visibility = ["//visibility:public"])
507
508
exports_files(["Firefox.app"])
509
510
js_library(
511
name = "firefox-js",
512
data = glob(["Firefox.app/**/*"], allow_empty = True),
513
)
514
\"\"\",
515
)
516
517
"""
518
)
519
520
return content
521
522
523
if __name__ == "__main__":
524
content = """# This file has been generated using `bazel run scripts:pinned_browsers`
525
526
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
527
load("//common/private:deb_archive.bzl", "deb_archive")
528
load("//common/private:dmg_archive.bzl", "dmg_archive")
529
load("//common/private:drivers.bzl", "local_drivers")
530
load("//common/private:pkg_archive.bzl", "pkg_archive")
531
532
def pin_browsers():
533
local_drivers(name = "local_drivers")
534
535
"""
536
content = content + firefox()
537
content = content + geckodriver()
538
content = content + edge_and_edgedriver()
539
540
# Stable Chrome
541
stable_chrome_info = get_chrome_info_for_channel(channel="Stable")
542
content = content + chrome(stable_chrome_info, workspace_prefix="")
543
content = content + chromedriver(stable_chrome_info, workspace_prefix="")
544
545
# Beta Chrome
546
beta_chrome_info = get_chrome_info_for_channel(channel="Beta")
547
content = content + chrome(beta_chrome_info, workspace_prefix="beta_")
548
content = content + chromedriver(beta_chrome_info, workspace_prefix="beta_")
549
550
content += """
551
def _pin_browsers_extension_impl(_ctx):
552
pin_browsers()
553
554
pin_browsers_extension = module_extension(
555
implementation = _pin_browsers_extension_impl,
556
)
557
"""
558
559
current_script_dir = Path(os.path.realpath(__file__)).parent
560
target_file_path = current_script_dir.parent / "common/repositories.bzl"
561
562
with open(target_file_path, "w") as file:
563
file.write(content)
564
565