capiumbrowser 1.2.0__tar.gz → 1.2.2__tar.gz

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 (46) hide show
  1. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/MANIFEST.in +3 -0
  2. {capiumbrowser-1.2.0/capiumbrowser.egg-info → capiumbrowser-1.2.2}/PKG-INFO +44 -6
  3. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/README.md +43 -5
  4. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/_version.py +7 -6
  5. capiumbrowser-1.2.2/capiumbrowser/android-phones.json +99 -0
  6. capiumbrowser-1.2.2/capiumbrowser/browser-brands.json +47 -0
  7. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/browser.py +98 -17
  8. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/browser_async.py +100 -7
  9. capiumbrowser-1.2.2/capiumbrowser/channels.json +40 -0
  10. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/config.py +37 -10
  11. capiumbrowser-1.2.2/capiumbrowser/desktop.py +81 -0
  12. capiumbrowser-1.2.2/capiumbrowser/ios-phones.json +19 -0
  13. capiumbrowser-1.2.2/capiumbrowser/mobile.py +113 -0
  14. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2/capiumbrowser.egg-info}/PKG-INFO +44 -6
  15. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser.egg-info/SOURCES.txt +8 -0
  16. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/pyproject.toml +2 -2
  17. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_config_args.py +5 -2
  18. capiumbrowser-1.2.2/tests/test_desktop_brands.py +62 -0
  19. capiumbrowser-1.2.2/tests/test_ios_profiles.py +48 -0
  20. capiumbrowser-1.2.2/tests/test_mobile_profiles.py +157 -0
  21. capiumbrowser-1.2.0/capiumbrowser/channels.json +0 -36
  22. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/LICENSE +0 -0
  23. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/__init__.py +0 -0
  24. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/__main__.py +0 -0
  25. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/_driver.py +0 -0
  26. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/errors.py +0 -0
  27. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/human/__init__.py +0 -0
  28. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/human/behavior.py +0 -0
  29. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/licensing/__init__.py +0 -0
  30. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/licensing/client.py +0 -0
  31. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/licensing/download.py +0 -0
  32. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/licensing/readiness.py +0 -0
  33. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/network/__init__.py +0 -0
  34. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser/network/proxy.py +0 -0
  35. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser.egg-info/dependency_links.txt +0 -0
  36. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser.egg-info/entry_points.txt +0 -0
  37. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser.egg-info/requires.txt +0 -0
  38. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/capiumbrowser.egg-info/top_level.txt +0 -0
  39. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/setup.cfg +0 -0
  40. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_channels_manifest.py +0 -0
  41. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_download_contract.py +0 -0
  42. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_geoip_flags.py +0 -0
  43. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_launch_viewport.py +0 -0
  44. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_license_readiness.py +0 -0
  45. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_proxy.py +0 -0
  46. {capiumbrowser-1.2.0 → capiumbrowser-1.2.2}/tests/test_version_tables_consistent.py +0 -0
@@ -1,5 +1,8 @@
1
1
  include README.md
2
2
  include LICENSE
3
3
  include capiumbrowser/channels.json
4
+ include capiumbrowser/android-phones.json
5
+ include capiumbrowser/ios-phones.json
6
+ include capiumbrowser/browser-brands.json
4
7
  global-exclude __pycache__/*
5
8
  global-exclude *.py[cod]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capiumbrowser
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: Drop-in stealth browser SDK on top of Playwright (Capium Chromium).
5
5
  Author: Capzy
6
6
  License: Capzy capiumbrowser SDK License
@@ -128,6 +128,7 @@ browser.close() # also stops the Playwright driver — no leake
128
128
  - [Install](#install) · [Why Capium](#why-capium) · [Licensing & tiers](#licensing)
129
129
  - [What a persona reads](#what-a-persona-reads) · [Comparison](#comparison) · [How it works](#how-it-works)
130
130
  - [API](#api) · [Parameters](#parameters) · [Personas & seeds](#personas--seeds) · [Fingerprint switches](#fingerprint-switches)
131
+ - [Browser and mobile profiles](#browser-and-mobile-profiles)
131
132
  - [Human behavior](#human-behavior) · [Proxies & geo](#proxies--geo) · [Fonts](#fonts)
132
133
  - [Framework integrations](#framework-integrations) · [Deployment](#deployment) · [Platforms](#platforms)
133
134
  - [CLI](#cli--environment) · [Troubleshooting](#troubleshooting) · [FAQ](#faq) · [Security](#security) · [License](#license)
@@ -395,7 +396,9 @@ and `launch_persistent_context` forward everything to `launch`.
395
396
  | Parameter | Type | Default | Description |
396
397
  |---|---|---|---|
397
398
  | `seed` | `int` \| `None` \| `"off"` | `None` | Persona seed → a complete coherent device. `None` = fresh random seed each launch. The range selects the OS family (see [Personas & seeds](#personas--seeds)). `"off"`/`"false"`/`"none"` = **passthrough** (host's real values; debugging). |
398
- | `platform` | `str` | `"windows"` | Spoofed OS: `"windows"` \| `"macos"` \| `"linux"`. Sets `--fingerprint-platform` and picks the seed range if you didn't. |
399
+ | `platform` | `str` | `"windows"` | OS profile: `"windows"` \| `"macos"` \| `"linux"` \| `"android"` \| `"ios"`. Mobile profiles are opt-in. |
400
+ | `browser_brand` | `str` | `"chrome"` | Desktop profile: `chrome`, `edge`, `opera`, `vivaldi`. |
401
+ | `mobile_device` | `str` | seed/default | Phone catalog ID; requires `android` or `ios`. |
399
402
  | `headless` | `bool` | `False` | Headed is recommended (reads cleaner). On a server, run headed under Xvfb. |
400
403
  | `stealth_args` | `bool` | `True` | Apply the default coherent fingerprint flags. Set `False` to build the whole command line yourself via `args`. |
401
404
  | `args` | `list[str]` \| `None` | `None` | Extra raw Chrome flags appended verbatim (see [Fingerprint switches](#fingerprint-switches)). |
@@ -496,6 +499,42 @@ Full runnable version (personas, persistent profiles, a fleet, proxy+geo, full c
496
499
 
497
500
  ---
498
501
 
502
+ ## Browser and mobile profiles
503
+
504
+ CapiumBrowser 1.2.1 supports desktop Chrome, Edge, Opera and Vivaldi identity
505
+ profiles, plus opt-in Android and iPhone profiles on Windows x64, macOS arm64
506
+ and Linux x64. Chrome remains the default.
507
+
508
+ | Profile | Python | Node.js |
509
+ | --- | --- | --- |
510
+ | Desktop browser | `browser_brand="edge"` (also `chrome`, `opera`, `vivaldi`) | `browserBrand: 'edge'` |
511
+ | Android phone | `platform="android", mobile_device="pixel-9"` | `platform: 'android', mobileDevice: 'pixel-9'` |
512
+ | iPhone 16 | `platform="ios", mobile_device="iphone-16"` | `platform: 'ios', mobileDevice: 'iphone-16'` |
513
+
514
+ Android includes Pixel 7/8/9 families and Samsung Galaxy A55. Omit the Android
515
+ device to retain the original seed selection; iOS defaults to iPhone 16. Mobile
516
+ profiles set the mobile layout and identity while leaving desktop defaults intact.
517
+ These are experimental desktop emulation profiles: they do not install Android
518
+ or iOS, reproduce WebKit, or certify physical-phone fingerprints. Android Chrome
519
+ and Chrome iOS are the supported mobile browser profiles; arbitrary app/WebView
520
+ UAs are outside these presets.
521
+
522
+ Desktop profiles use verified browser/Chromium version pairs: Edge 155/155,
523
+ Opera 137/153 and Vivaldi 8.2/152. Vivaldi follows its usual Chrome branding.
524
+ Capium's engine remains Chromium 155; vendor-specific features are not emulated.
525
+ A version catalog is not an older browser engine. Native compatibility checks
526
+ reject binaries that do not implement the selected profile.
527
+
528
+ ```python
529
+ from capiumbrowser import launch_context
530
+
531
+ browser, context, page = launch_context(
532
+ seed=200123, platform="ios", mobile_device="iphone-16")
533
+ # Android: platform="android", mobile_device="pixel-9"
534
+ # Desktop Edge: platform="windows", browser_brand="edge"
535
+ browser.close()
536
+ ```
537
+
499
538
  ## Fingerprint switches
500
539
 
501
540
  The SDK sets the essentials from `seed`/`platform`. Everything below is available for precise control —
@@ -725,13 +764,12 @@ launches **headed**, which reads cleaner than headless.
725
764
  | Linux arm64 | `linux-arm64` | Declared in the manifest; not yet published or covered by the three-host audit |
726
765
 
727
766
  The SDK auto-detects your host and downloads the matching tag; macOS Intel and Windows ARM are **not**
728
- published and raise a clear error. The **1.2.0** manifest selects **Chromium 155.0.8059.39, revision 1** on the three
729
- supported hosts. This release is prepared as a draft; downloads become available when the browser
730
- artifacts are published. Linux arm64 remains unpublished.
767
+ published and raise a clear error. The **1.2.1** manifest selects **Chromium 155.0.8059.39, revision 2** on the three
768
+ supported hosts. Browser downloads are available with a valid license. Linux arm64 remains unpublished.
731
769
 
732
770
  ---
733
771
 
734
- The 1.2.0 launch APIs wait for the packaged browser's licensing decision before
772
+ The launch APIs wait for the packaged browser's licensing decision before
735
773
  returning a handle. Invalid keys raise a typed error during launch.
736
774
 
737
775
  ## CLI & environment
@@ -65,6 +65,7 @@ browser.close() # also stops the Playwright driver — no leake
65
65
  - [Install](#install) · [Why Capium](#why-capium) · [Licensing & tiers](#licensing)
66
66
  - [What a persona reads](#what-a-persona-reads) · [Comparison](#comparison) · [How it works](#how-it-works)
67
67
  - [API](#api) · [Parameters](#parameters) · [Personas & seeds](#personas--seeds) · [Fingerprint switches](#fingerprint-switches)
68
+ - [Browser and mobile profiles](#browser-and-mobile-profiles)
68
69
  - [Human behavior](#human-behavior) · [Proxies & geo](#proxies--geo) · [Fonts](#fonts)
69
70
  - [Framework integrations](#framework-integrations) · [Deployment](#deployment) · [Platforms](#platforms)
70
71
  - [CLI](#cli--environment) · [Troubleshooting](#troubleshooting) · [FAQ](#faq) · [Security](#security) · [License](#license)
@@ -332,7 +333,9 @@ and `launch_persistent_context` forward everything to `launch`.
332
333
  | Parameter | Type | Default | Description |
333
334
  |---|---|---|---|
334
335
  | `seed` | `int` \| `None` \| `"off"` | `None` | Persona seed → a complete coherent device. `None` = fresh random seed each launch. The range selects the OS family (see [Personas & seeds](#personas--seeds)). `"off"`/`"false"`/`"none"` = **passthrough** (host's real values; debugging). |
335
- | `platform` | `str` | `"windows"` | Spoofed OS: `"windows"` \| `"macos"` \| `"linux"`. Sets `--fingerprint-platform` and picks the seed range if you didn't. |
336
+ | `platform` | `str` | `"windows"` | OS profile: `"windows"` \| `"macos"` \| `"linux"` \| `"android"` \| `"ios"`. Mobile profiles are opt-in. |
337
+ | `browser_brand` | `str` | `"chrome"` | Desktop profile: `chrome`, `edge`, `opera`, `vivaldi`. |
338
+ | `mobile_device` | `str` | seed/default | Phone catalog ID; requires `android` or `ios`. |
336
339
  | `headless` | `bool` | `False` | Headed is recommended (reads cleaner). On a server, run headed under Xvfb. |
337
340
  | `stealth_args` | `bool` | `True` | Apply the default coherent fingerprint flags. Set `False` to build the whole command line yourself via `args`. |
338
341
  | `args` | `list[str]` \| `None` | `None` | Extra raw Chrome flags appended verbatim (see [Fingerprint switches](#fingerprint-switches)). |
@@ -433,6 +436,42 @@ Full runnable version (personas, persistent profiles, a fleet, proxy+geo, full c
433
436
 
434
437
  ---
435
438
 
439
+ ## Browser and mobile profiles
440
+
441
+ CapiumBrowser 1.2.1 supports desktop Chrome, Edge, Opera and Vivaldi identity
442
+ profiles, plus opt-in Android and iPhone profiles on Windows x64, macOS arm64
443
+ and Linux x64. Chrome remains the default.
444
+
445
+ | Profile | Python | Node.js |
446
+ | --- | --- | --- |
447
+ | Desktop browser | `browser_brand="edge"` (also `chrome`, `opera`, `vivaldi`) | `browserBrand: 'edge'` |
448
+ | Android phone | `platform="android", mobile_device="pixel-9"` | `platform: 'android', mobileDevice: 'pixel-9'` |
449
+ | iPhone 16 | `platform="ios", mobile_device="iphone-16"` | `platform: 'ios', mobileDevice: 'iphone-16'` |
450
+
451
+ Android includes Pixel 7/8/9 families and Samsung Galaxy A55. Omit the Android
452
+ device to retain the original seed selection; iOS defaults to iPhone 16. Mobile
453
+ profiles set the mobile layout and identity while leaving desktop defaults intact.
454
+ These are experimental desktop emulation profiles: they do not install Android
455
+ or iOS, reproduce WebKit, or certify physical-phone fingerprints. Android Chrome
456
+ and Chrome iOS are the supported mobile browser profiles; arbitrary app/WebView
457
+ UAs are outside these presets.
458
+
459
+ Desktop profiles use verified browser/Chromium version pairs: Edge 155/155,
460
+ Opera 137/153 and Vivaldi 8.2/152. Vivaldi follows its usual Chrome branding.
461
+ Capium's engine remains Chromium 155; vendor-specific features are not emulated.
462
+ A version catalog is not an older browser engine. Native compatibility checks
463
+ reject binaries that do not implement the selected profile.
464
+
465
+ ```python
466
+ from capiumbrowser import launch_context
467
+
468
+ browser, context, page = launch_context(
469
+ seed=200123, platform="ios", mobile_device="iphone-16")
470
+ # Android: platform="android", mobile_device="pixel-9"
471
+ # Desktop Edge: platform="windows", browser_brand="edge"
472
+ browser.close()
473
+ ```
474
+
436
475
  ## Fingerprint switches
437
476
 
438
477
  The SDK sets the essentials from `seed`/`platform`. Everything below is available for precise control —
@@ -662,13 +701,12 @@ launches **headed**, which reads cleaner than headless.
662
701
  | Linux arm64 | `linux-arm64` | Declared in the manifest; not yet published or covered by the three-host audit |
663
702
 
664
703
  The SDK auto-detects your host and downloads the matching tag; macOS Intel and Windows ARM are **not**
665
- published and raise a clear error. The **1.2.0** manifest selects **Chromium 155.0.8059.39, revision 1** on the three
666
- supported hosts. This release is prepared as a draft; downloads become available when the browser
667
- artifacts are published. Linux arm64 remains unpublished.
704
+ published and raise a clear error. The **1.2.1** manifest selects **Chromium 155.0.8059.39, revision 2** on the three
705
+ supported hosts. Browser downloads are available with a valid license. Linux arm64 remains unpublished.
668
706
 
669
707
  ---
670
708
 
671
- The 1.2.0 launch APIs wait for the packaged browser's licensing decision before
709
+ The launch APIs wait for the packaged browser's licensing decision before
672
710
  returning a handle. Invalid keys raise a typed error during launch.
673
711
 
674
712
  ## CLI & environment
@@ -17,17 +17,18 @@ import json
17
17
  import os
18
18
  import platform as _platform
19
19
 
20
- __version__ = "1.2.0"
20
+ __version__ = "1.2.2"
21
21
 
22
22
  # Used only if channels.json can't be read (corrupt/removed install). ensure_binary() raises a
23
23
  # clear error long before the "unsupported platform" case matters; this keeps lookups total.
24
+ # Reported version per platform: Windows .85, macOS/Linux .84 (engine 155.0.8059.84 on all).
24
25
  _FALLBACK_VERSIONS = {
25
- "windows-x64": "155.0.8059.39", # Chromium 155 release candidate on all supported hosts
26
- "macos-arm64": "155.0.8059.39",
27
- "linux-x64": "155.0.8059.39",
28
- "linux-arm64": "155.0.8059.39", # declared; not yet published (see channels.json)
26
+ "windows-x64": "155.0.8059.85",
27
+ "macos-arm64": "155.0.8059.84",
28
+ "linux-x64": "155.0.8059.84",
29
+ "linux-arm64": "155.0.8059.84", # declared; not yet published (see channels.json)
29
30
  }
30
- _DEFAULT_BINARY_VERSION = "155.0.8059.39"
31
+ _DEFAULT_BINARY_VERSION = "155.0.8059.84"
31
32
 
32
33
 
33
34
  def _load_stable():
@@ -0,0 +1,99 @@
1
+ {
2
+ "schema_version": 2,
3
+ "minimum_release_year": 2022,
4
+ "browser": "chrome",
5
+ "status": "experimental-emulation",
6
+ "gpu_policy": "host",
7
+ "hardware_policy": "host",
8
+ "physical_device_validation": "pending",
9
+ "default_profile_ids": [
10
+ "pixel-7",
11
+ "pixel-8",
12
+ "pixel-8-pro",
13
+ "pixel-8a",
14
+ "pixel-9"
15
+ ],
16
+ "sources": [
17
+ "https://support.google.com/pixelphone/answer/7158570?hl=en",
18
+ "https://news.samsung.com/global/samsung-galaxy-a55-5g-and-galaxy-a35-5g-awesome-innovations-and-security-engineered-for-everyone",
19
+ "https://www.samsung.com/ie/business/smartphones/galaxy-a/galaxy-a55-5g-awesome-navy-256gb-sm-a556bzkceub/",
20
+ "https://chromium.googlesource.com/devtools/devtools-frontend/+/cd7bfad9bba968ac23ba67e538f9e1d499991d25/front_end/models/emulation/EmulatedDevices.ts"
21
+ ],
22
+ "profiles": [
23
+ {
24
+ "id": "pixel-7",
25
+ "model": "Pixel 7",
26
+ "release_year": 2022,
27
+ "platform_version": "13",
28
+ "width": 412,
29
+ "height": 915,
30
+ "dpr": 2.625
31
+ },
32
+ {
33
+ "id": "pixel-8",
34
+ "model": "Pixel 8",
35
+ "release_year": 2023,
36
+ "platform_version": "14",
37
+ "width": 412,
38
+ "height": 915,
39
+ "dpr": 2.625
40
+ },
41
+ {
42
+ "id": "pixel-8-pro",
43
+ "model": "Pixel 8 Pro",
44
+ "release_year": 2023,
45
+ "platform_version": "14",
46
+ "width": 448,
47
+ "height": 997,
48
+ "dpr": 3.0
49
+ },
50
+ {
51
+ "id": "pixel-8a",
52
+ "model": "Pixel 8a",
53
+ "release_year": 2024,
54
+ "platform_version": "14",
55
+ "width": 412,
56
+ "height": 915,
57
+ "dpr": 2.625
58
+ },
59
+ {
60
+ "id": "pixel-9",
61
+ "model": "Pixel 9",
62
+ "release_year": 2024,
63
+ "platform_version": "14",
64
+ "width": 412,
65
+ "height": 924,
66
+ "dpr": 2.625
67
+ },
68
+ {
69
+ "id": "samsung-galaxy-a55",
70
+ "model": "SM-A556B",
71
+ "display_name": "Samsung Galaxy A55",
72
+ "release_year": 2024,
73
+ "platform_version": "14",
74
+ "width": 360,
75
+ "height": 800,
76
+ "dpr": 2.25,
77
+ "geometry_status": "devtools-emulation-only",
78
+ "geometry_note": "These Chromium preset dimensions/DPR do not directly map to the published 1080x2340 panel; physical Chrome capture required."
79
+ },
80
+ {
81
+ "id": "pixel-9-pro",
82
+ "model": "Pixel 9 Pro",
83
+ "release_year": 2024,
84
+ "platform_version": "14",
85
+ "width": 427,
86
+ "height": 952,
87
+ "dpr": 3.0
88
+ },
89
+ {
90
+ "id": "pixel-9-pro-xl",
91
+ "model": "Pixel 9 Pro XL",
92
+ "release_year": 2024,
93
+ "platform_version": "14",
94
+ "width": 448,
95
+ "height": 997,
96
+ "dpr": 3.0
97
+ }
98
+ ]
99
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "schema_version": 1,
3
+ "verified_on": "2026-10-10",
4
+ "engine_version": "155.0.8059.39",
5
+ "scope": "Desktop UA and Client Hints profiles; does not install vendor engines or features.",
6
+ "profiles": [
7
+ {
8
+ "brand": "edge",
9
+ "product_version": "155.0.4283.45",
10
+ "chromium_version": "155.0.8059.40",
11
+ "hint_brand": "Microsoft Edge",
12
+ "ua_suffix": " Edg/",
13
+ "ua_version": "155.0.0.0",
14
+ "sources": [
15
+ "https://edgeupdates.microsoft.com/api/products?view=enterprise"
16
+ ],
17
+ "measurement": "Stock Edge 155 Windows and Linux owned-origin captures"
18
+ },
19
+ {
20
+ "brand": "opera",
21
+ "product_version": "137.0.6036.39",
22
+ "chromium_version": "153.0.8010.55",
23
+ "hint_brand": "Opera",
24
+ "ua_suffix": " OPR/",
25
+ "ua_version": "137.0.0.0",
26
+ "sources": [
27
+ "https://blogs.opera.com/desktop/changelog-for-137/",
28
+ "https://blogs.opera.com/desktop/2026/10/opera-137-stable/"
29
+ ],
30
+ "measurement": "Stock Opera 137 Linux owned-origin capture"
31
+ },
32
+ {
33
+ "brand": "vivaldi",
34
+ "product_version": "8.2.4133.84",
35
+ "chromium_version": "152.0.7977.160",
36
+ "hint_brand": "Google Chrome",
37
+ "ua_suffix": "",
38
+ "ua_version": "",
39
+ "sources": [
40
+ "https://vivaldi.com/blog/minor-update-nine-8-2/",
41
+ "https://vivaldi.com/blog/desktop/",
42
+ "https://help.vivaldi.com/developers/web/vivaldi-user-agent-and-client-hints-user-agent/"
43
+ ],
44
+ "measurement": "Stock Vivaldi 8.2 Linux owned-origin capture; default branding only, partner-site exceptions excluded"
45
+ }
46
+ ]
47
+ }
@@ -117,13 +117,13 @@ def _has_screen_override(extra):
117
117
 
118
118
 
119
119
  def _build_args(seed, platform, stealth_args, timezone, locale, extension_paths, extra,
120
- headless=False):
120
+ headless=False, mobile_device=None, browser_brand=None):
121
121
  args = []
122
122
  if stealth_args:
123
- args += config.get_default_stealth_args(seed, platform)
123
+ args += config.get_default_stealth_args(seed, platform, mobile_device=mobile_device, browser_brand=browser_brand)
124
124
  # Keep the engine's seeded screen. Use initial window bounds only when
125
125
  # the caller has not provided screen/window flags.
126
- if headless and not _has_screen_override(extra):
126
+ if headless and platform not in ("android", "ios") and not _has_screen_override(extra):
127
127
  args += config.get_headless_default_args()
128
128
  if timezone:
129
129
  args.append("--timezone=%s" % timezone) # capium in-binary tz spoof
@@ -171,6 +171,7 @@ def _fit_window_to_screen(context):
171
171
  (works for persistent contexts, which have no browser() object). This keeps screen~=viewport in
172
172
  headless/container (avoids the browserscan 'screen != viewport' VM tell) AND stays coherent in
173
173
  headed (Chrome clamps to the real display -> a normal viewport<screen window). Best-effort."""
174
+ s = None
174
175
  try:
175
176
  page = context.pages[0] if context.pages else None
176
177
  if not page:
@@ -185,6 +186,12 @@ def _fit_window_to_screen(context):
185
186
  "left": 0, "top": 0, "width": aw, "height": ah, "windowState": "normal"}})
186
187
  except Exception:
187
188
  pass
189
+ finally:
190
+ if s is not None:
191
+ try:
192
+ s.detach()
193
+ except Exception:
194
+ pass
188
195
 
189
196
 
190
197
  def _wrap_close(obj, pw):
@@ -209,10 +216,13 @@ def _wrap_close(obj, pw):
209
216
  return obj
210
217
 
211
218
 
212
- def _apply_viewport_defaults(browser, asynchronous=False):
219
+ def _apply_viewport_defaults(browser, asynchronous=False, mobile_seed=None, mobile_device=None, mobile_platform="android"):
213
220
  """Match the other launch APIs: use the window unless the caller chooses a viewport."""
214
221
  def decorate(original):
215
222
  def options(kwargs):
223
+ if mobile_seed is not None:
224
+ from .mobile import context_options
225
+ return context_options(mobile_seed, kwargs, mobile_device, mobile_platform)
216
226
  if 'viewport' not in kwargs and 'no_viewport' not in kwargs:
217
227
  kwargs = dict(kwargs, no_viewport=True)
218
228
  return kwargs
@@ -234,13 +244,29 @@ def _apply_viewport_defaults(browser, asynchronous=False):
234
244
  return browser
235
245
 
236
246
 
247
+ def _require_mobile_engine(context, mobile_device=None, seed=0, platform="android"):
248
+ """Refuse incompatible binaries before a mobile case can visit a site."""
249
+ page = context.new_page()
250
+ try:
251
+ from .mobile import mobile_profile, engine_probe
252
+ expected = mobile_profile(seed, mobile_device, platform)
253
+ page.route("**/*", lambda route: route.fulfill(status=200, content_type="text/html", body="<!doctype html>"))
254
+ page.goto("https://capium-mobile-preview.invalid/", wait_until="domcontentloaded")
255
+ supported = page.evaluate(engine_probe(platform), expected)
256
+ if not supported:
257
+ raise ValueError("This binary does not support the requested mobile profile/device; use Capium 1.2.1 revision 2 or later")
258
+ finally:
259
+ page.close()
260
+
261
+
237
262
  def launch(seed=None, platform="windows", headless=False, proxy=None, geoip=None, args=None,
238
263
  stealth_args=True, timezone=None, locale=None, extension_paths=None, binary=None,
239
264
  license_key=None, license_server=None, license_through_proxy=False,
240
- license_preflight=True, driver=None, **kwargs):
265
+ license_preflight=True, driver=None, mobile_device=None, browser_brand=None, **kwargs):
241
266
  """Launch a Capium Browser (non-persistent; Playwright manages a temp profile).
242
267
 
243
268
  seed/platform : identity. stealth_args=False drops the default fingerprint flags.
269
+ mobile_device : Android/iPhone catalog ID; requires the matching native release.
244
270
  proxy : "dataimpulse" | "http://user:pass@host:port" | "socks5://host:port" | dict.
245
271
  geoip : geo coherence, resolved inside the binary. None (default) = OFF: no lookup
246
272
  runs, even with a proxy (a proxy alone no longer triggers it, so proxied
@@ -263,6 +289,10 @@ def launch(seed=None, platform="windows", headless=False, proxy=None, geoip=None
263
289
  kwargs : forwarded to Playwright's chromium.launch (e.g. slow_mo=...).
264
290
  Returns a Browser whose .close() also stops Playwright.
265
291
  """
292
+ from .mobile import validate_launch
293
+ from . import desktop
294
+ desktop.validate_launch(browser_brand, platform, binary, args, stealth_args)
295
+ validate_launch(platform, binary, args, stealth_args, mobile_device)
266
296
  if license_preflight:
267
297
  _license.preflight(license_key, license_server)
268
298
  key, _ = _license.effective(license_key, license_server)
@@ -272,7 +302,7 @@ def launch(seed=None, platform="windows", headless=False, proxy=None, geoip=None
272
302
  display = os.environ.get("DISPLAY")
273
303
  proxy_kwargs, proxy_args = _proxy_and_geo_args(proxy, binpath, geoip, display)
274
304
  launch_args = proxy_args + _build_args(
275
- seed, platform, stealth_args, timezone, locale, extension_paths, args, headless)
305
+ seed, platform, stealth_args, timezone, locale, extension_paths, args, headless, mobile_device, browser_brand)
276
306
  if license_through_proxy:
277
307
  launch_args.append("--license-through-proxy")
278
308
  env = _license.child_env(license_key, license_server)
@@ -304,7 +334,30 @@ def launch(seed=None, platform="windows", headless=False, proxy=None, geoip=None
304
334
  raise
305
335
  _clear_status_file(status_path)
306
336
  browser._capium_seed = seed
307
- return _wrap_close(_apply_viewport_defaults(browser), pw)
337
+ browser._capium_mobile = platform in ("android", "ios") and stealth_args
338
+ browser = _wrap_close(_apply_viewport_defaults(
339
+ browser, mobile_seed=seed if browser._capium_mobile else None, mobile_device=mobile_device, mobile_platform=platform), pw)
340
+ if browser._capium_mobile:
341
+ try:
342
+ context = browser.new_context()
343
+ try:
344
+ _require_mobile_engine(context, mobile_device, seed, platform)
345
+ finally:
346
+ context.close()
347
+ except Exception:
348
+ browser.close()
349
+ raise
350
+ if desktop.normalize_brand(browser_brand) != "chrome":
351
+ try:
352
+ context = browser.new_context()
353
+ try:
354
+ desktop.require_engine(context, browser_brand)
355
+ finally:
356
+ context.close()
357
+ except Exception:
358
+ browser.close()
359
+ raise
360
+ return browser
308
361
 
309
362
 
310
363
  def launch_persistent_context(user_data_dir, seed=None, platform="windows", headless=False,
@@ -312,11 +365,15 @@ def launch_persistent_context(user_data_dir, seed=None, platform="windows", head
312
365
  timezone=None, locale=None, extension_paths=None, binary=None,
313
366
  license_key=None, license_server=None,
314
367
  license_through_proxy=False,
315
- license_preflight=True, driver=None, **kwargs):
368
+ license_preflight=True, driver=None, mobile_device=None, browser_brand=None, **kwargs):
316
369
  """Launch a persistent context (cookies/localStorage/state persist in user_data_dir).
317
370
 
318
371
  Same options as launch(); returns a BrowserContext whose .close() also stops Playwright.
319
372
  """
373
+ from .mobile import context_options, validate_launch
374
+ from . import desktop
375
+ desktop.validate_launch(browser_brand, platform, binary, args, stealth_args)
376
+ validate_launch(platform, binary, args, stealth_args, mobile_device)
320
377
  if license_preflight:
321
378
  _license.preflight(license_key, license_server)
322
379
  key, _ = _license.effective(license_key, license_server)
@@ -326,14 +383,16 @@ def launch_persistent_context(user_data_dir, seed=None, platform="windows", head
326
383
  display = os.environ.get("DISPLAY")
327
384
  proxy_kwargs, proxy_args = _proxy_and_geo_args(proxy, binpath, geoip, display)
328
385
  launch_args = proxy_args + _build_args(
329
- seed, platform, stealth_args, timezone, locale, extension_paths, args, headless)
386
+ seed, platform, stealth_args, timezone, locale, extension_paths, args, headless, mobile_device, browser_brand)
330
387
  if license_through_proxy:
331
388
  launch_args.append("--license-through-proxy")
332
389
  env = _license.child_env(license_key, license_server)
333
390
  status_path = _new_status_file(env)
334
391
  # Default to the page filling the window (see launch_context) unless the caller
335
392
  # pinned a viewport themselves.
336
- if "viewport" not in kwargs and "no_viewport" not in kwargs:
393
+ if platform in ("android", "ios") and stealth_args:
394
+ kwargs = context_options(seed, kwargs, mobile_device, platform)
395
+ elif "viewport" not in kwargs and "no_viewport" not in kwargs:
337
396
  kwargs["no_viewport"] = True
338
397
  # Strip Playwright's automation + forced-software-WebGL defaults;
339
398
  # the caller can override by passing ignore_default_args explicitly.
@@ -363,11 +422,29 @@ def launch_persistent_context(user_data_dir, seed=None, platform="windows", head
363
422
  raise
364
423
  _clear_status_file(status_path)
365
424
  ctx._capium_seed = seed
366
- # Headed persistent launches fit the window to the selected screen. Headless
367
- # keeps its startup bounds (800x600 by default) independent of the seeded screen;
368
- # callers can choose larger window bounds explicitly.
369
- if not headless:
425
+ # Fit every headed desktop brand before its engine check returns the context.
426
+ # Mobile profiles retain their catalog viewport and startup bounds.
427
+ # Size the window to the seed's screen for desktop personas -- in HEADLESS too, so the
428
+ # viewport is a coherent per-seed maximized size (e.g. 1920x1080 screen -> ~1904x911)
429
+ # instead of the tiny startup window. Mobile keeps its device viewport.
430
+ if not (platform in ("android", "ios") and stealth_args):
370
431
  _fit_window_to_screen(ctx)
432
+ if desktop.normalize_brand(browser_brand) != "chrome":
433
+ ctx = _wrap_close(ctx, pw)
434
+ try:
435
+ desktop.require_engine(ctx, browser_brand)
436
+ except Exception:
437
+ ctx.close()
438
+ raise
439
+ return ctx
440
+ if platform in ("android", "ios") and stealth_args:
441
+ ctx = _wrap_close(ctx, pw)
442
+ try:
443
+ _require_mobile_engine(ctx, mobile_device, seed, platform)
444
+ except Exception:
445
+ ctx.close()
446
+ raise
447
+ return ctx
371
448
  return _wrap_close(ctx, pw)
372
449
 
373
450
 
@@ -383,13 +460,17 @@ def launch_context(seed=None, platform="windows", headless=False, url=None,
383
460
  # no_viewport: let the page fill the real window (fit_window sizes the window to
384
461
  # the persona's screen). A fixed 1280x720 emulated viewport would leave the page
385
462
  # smaller than the window AND mismatch window.screen — an incoherent fingerprint.
386
- context = browser.contexts[0] if browser.contexts else browser.new_context(no_viewport=True)
463
+ context = browser.contexts[0] if browser.contexts else browser.new_context(
464
+ **({} if getattr(browser, "_capium_mobile", False) else {"no_viewport": True}))
387
465
  if humanize:
388
466
  from . import human as _human
389
467
  _human.humanize(context, preset=human_preset)
390
468
  page = context.pages[0] if context.pages else context.new_page()
391
- if not headless:
392
- fit_window(browser, context, page) # modest window <= spoofed screen
469
+ if not getattr(browser, "_capium_mobile", False):
470
+ if headless:
471
+ _fit_window_to_screen(context) # per-seed screen fit (coherent viewport, not the tiny startup window)
472
+ else:
473
+ fit_window(browser, context, page) # modest window <= spoofed screen
393
474
  if url:
394
475
  page.goto(url, wait_until="domcontentloaded", timeout=60000)
395
476
  return browser, context, page