onyxweb 0.2.0__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.
- onyxweb-0.2.0/.cargo/config.toml +3 -0
- onyxweb-0.2.0/.github/workflows/ci.yml +142 -0
- onyxweb-0.2.0/.github/workflows/publish.yml +235 -0
- onyxweb-0.2.0/.gitignore +33 -0
- onyxweb-0.2.0/.python-version +1 -0
- onyxweb-0.2.0/BENCHMARKS.md +106 -0
- onyxweb-0.2.0/Cargo.lock +1979 -0
- onyxweb-0.2.0/Cargo.toml +47 -0
- onyxweb-0.2.0/LICENSE +28 -0
- onyxweb-0.2.0/PKG-INFO +352 -0
- onyxweb-0.2.0/README.md +321 -0
- onyxweb-0.2.0/pyproject.toml +131 -0
- onyxweb-0.2.0/python/onyxweb/__init__.py +1509 -0
- onyxweb-0.2.0/python/onyxweb/__main__.py +377 -0
- onyxweb-0.2.0/python/onyxweb/_download_chrome.py +268 -0
- onyxweb-0.2.0/python/onyxweb/_logging.py +73 -0
- onyxweb-0.2.0/python/onyxweb/_onyxweb.pyi +160 -0
- onyxweb-0.2.0/python/onyxweb/config.py +709 -0
- onyxweb-0.2.0/python/onyxweb/presets/__init__.py +33 -0
- onyxweb-0.2.0/python/onyxweb/presets/full/__init__.py +17 -0
- onyxweb-0.2.0/python/onyxweb/presets/full/stealth.py +33 -0
- onyxweb-0.2.0/python/onyxweb/presets/shell/__init__.py +17 -0
- onyxweb-0.2.0/python/onyxweb/presets/shell/archival.py +31 -0
- onyxweb-0.2.0/python/onyxweb/presets/shell/recon.py +41 -0
- onyxweb-0.2.0/python/onyxweb/presets/shell/stealth.py +286 -0
- onyxweb-0.2.0/python/onyxweb/py.typed +0 -0
- onyxweb-0.2.0/rust-toolchain.toml +3 -0
- onyxweb-0.2.0/src/chrome.rs +141 -0
- onyxweb-0.2.0/src/client.rs +758 -0
- onyxweb-0.2.0/src/config.rs +1010 -0
- onyxweb-0.2.0/src/dom.rs +361 -0
- onyxweb-0.2.0/src/engine.rs +1026 -0
- onyxweb-0.2.0/src/error.rs +144 -0
- onyxweb-0.2.0/src/hash.rs +57 -0
- onyxweb-0.2.0/src/lib.rs +86 -0
- onyxweb-0.2.0/src/pool.rs +1046 -0
- onyxweb-0.2.0/src/result.rs +178 -0
- onyxweb-0.2.0/src/runtime.rs +25 -0
- onyxweb-0.2.0/tests/conftest.py +32 -0
- onyxweb-0.2.0/tests/test_actions.py +501 -0
- onyxweb-0.2.0/tests/test_anti_bot.py +419 -0
- onyxweb-0.2.0/tests/test_async_concurrency.py +81 -0
- onyxweb-0.2.0/tests/test_async_module_level.py +46 -0
- onyxweb-0.2.0/tests/test_async_smoke.py +103 -0
- onyxweb-0.2.0/tests/test_batch.py +75 -0
- onyxweb-0.2.0/tests/test_batch_errors.py +99 -0
- onyxweb-0.2.0/tests/test_cli.py +228 -0
- onyxweb-0.2.0/tests/test_config.py +361 -0
- onyxweb-0.2.0/tests/test_console_capture.py +268 -0
- onyxweb-0.2.0/tests/test_dialog_handling.py +145 -0
- onyxweb-0.2.0/tests/test_dom.py +145 -0
- onyxweb-0.2.0/tests/test_domino_smoke.py +247 -0
- onyxweb-0.2.0/tests/test_emulation.py +54 -0
- onyxweb-0.2.0/tests/test_error_context.py +82 -0
- onyxweb-0.2.0/tests/test_fetch.py +132 -0
- onyxweb-0.2.0/tests/test_gauntlet.py +250 -0
- onyxweb-0.2.0/tests/test_image_format.py +62 -0
- onyxweb-0.2.0/tests/test_per_fetch_blocking.py +202 -0
- onyxweb-0.2.0/tests/test_per_fetch_headers_no_leak.py +80 -0
- onyxweb-0.2.0/tests/test_per_fetch_headers_validate.py +82 -0
- onyxweb-0.2.0/tests/test_per_fetch_nav_blocking.py +252 -0
- onyxweb-0.2.0/tests/test_per_fetch_referer.py +116 -0
- onyxweb-0.2.0/tests/test_per_fetch_scripts.py +178 -0
- onyxweb-0.2.0/tests/test_pool_integrity.py +108 -0
- onyxweb-0.2.0/tests/test_post_load_results.py +136 -0
- onyxweb-0.2.0/tests/test_post_load_scripts.py +320 -0
- onyxweb-0.2.0/tests/test_post_load_settle.py +105 -0
- onyxweb-0.2.0/tests/test_proxy.py +227 -0
- onyxweb-0.2.0/tests/test_response_async_edge.py +94 -0
- onyxweb-0.2.0/tests/test_response_cert.py +34 -0
- onyxweb-0.2.0/tests/test_response_hashes.py +72 -0
- onyxweb-0.2.0/tests/test_response_headers.py +125 -0
- onyxweb-0.2.0/tests/test_response_metadata.py +55 -0
- onyxweb-0.2.0/tests/test_response_redirects.py +68 -0
- onyxweb-0.2.0/tests/test_runtime_config.py +154 -0
- onyxweb-0.2.0/tests/test_same_doc_nav.py +132 -0
- onyxweb-0.2.0/tests/test_smoke.py +50 -0
- onyxweb-0.2.0/tests/test_stealth.py +325 -0
- onyxweb-0.2.0/tests/test_threading.py +80 -0
- onyxweb-0.2.0/tests/test_wait_until.py +106 -0
- onyxweb-0.2.0/tests/urls_bench_big.txt +50 -0
- onyxweb-0.2.0/uv.lock +886 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Cross-platform wheel matrix. Wheels are uploaded as artifacts — no auto-
|
|
2
|
+
# publish. Trigger: push to master, PRs, and manual workflow_dispatch.
|
|
3
|
+
#
|
|
4
|
+
# Platforms target what `onyxweb-download-chrome` can stage a binary for:
|
|
5
|
+
# linux x86_64 / aarch64, darwin x86_64 / arm64, windows x86_64. Adding a
|
|
6
|
+
# platform requires both a matrix entry here AND a PLATFORMS row in
|
|
7
|
+
# python/onyxweb/_download_chrome.py.
|
|
8
|
+
|
|
9
|
+
name: CI
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [master]
|
|
14
|
+
pull_request:
|
|
15
|
+
workflow_dispatch:
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
lint:
|
|
22
|
+
# Static gates: ruff, mypy strict, cargo fmt, cargo clippy. Fast — runs
|
|
23
|
+
# on every PR / push to master alongside the wheel-build matrix.
|
|
24
|
+
runs-on: ubuntu-22.04
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v3
|
|
28
|
+
- name: Sync dev deps (builds Rust ext via cache-keys)
|
|
29
|
+
run: uv sync --group dev
|
|
30
|
+
- name: Ruff
|
|
31
|
+
run: uv run ruff check python/ tests/
|
|
32
|
+
- name: Mypy (strict)
|
|
33
|
+
run: uv run mypy python/onyxweb/ tests/
|
|
34
|
+
- name: Cargo fmt
|
|
35
|
+
run: cargo fmt --all -- --check
|
|
36
|
+
- name: Cargo clippy
|
|
37
|
+
run: cargo clippy --all-targets -- -D warnings
|
|
38
|
+
|
|
39
|
+
linux:
|
|
40
|
+
runs-on: ubuntu-22.04
|
|
41
|
+
strategy:
|
|
42
|
+
fail-fast: false
|
|
43
|
+
matrix:
|
|
44
|
+
target: [x86_64, aarch64]
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- uses: actions/setup-python@v5
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.11"
|
|
50
|
+
- name: Build wheel (manylinux_2_28)
|
|
51
|
+
uses: PyO3/maturin-action@v1
|
|
52
|
+
with:
|
|
53
|
+
target: ${{ matrix.target }}
|
|
54
|
+
args: --release --out dist --find-interpreter
|
|
55
|
+
manylinux: "2_28"
|
|
56
|
+
sccache: true
|
|
57
|
+
- uses: actions/upload-artifact@v4
|
|
58
|
+
with:
|
|
59
|
+
name: wheels-linux-${{ matrix.target }}
|
|
60
|
+
path: dist
|
|
61
|
+
|
|
62
|
+
macos:
|
|
63
|
+
runs-on: ${{ matrix.runner }}
|
|
64
|
+
strategy:
|
|
65
|
+
fail-fast: false
|
|
66
|
+
matrix:
|
|
67
|
+
include:
|
|
68
|
+
- runner: macos-latest
|
|
69
|
+
target: aarch64
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- uses: actions/setup-python@v5
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.11"
|
|
75
|
+
- name: Build wheel
|
|
76
|
+
uses: PyO3/maturin-action@v1
|
|
77
|
+
with:
|
|
78
|
+
target: ${{ matrix.target }}
|
|
79
|
+
args: --release --out dist --find-interpreter
|
|
80
|
+
sccache: true
|
|
81
|
+
- uses: actions/upload-artifact@v4
|
|
82
|
+
with:
|
|
83
|
+
name: wheels-macos-${{ matrix.target }}
|
|
84
|
+
path: dist
|
|
85
|
+
|
|
86
|
+
windows:
|
|
87
|
+
runs-on: windows-latest
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/checkout@v4
|
|
90
|
+
- uses: actions/setup-python@v5
|
|
91
|
+
with:
|
|
92
|
+
python-version: "3.11"
|
|
93
|
+
architecture: x64
|
|
94
|
+
- name: Build wheel
|
|
95
|
+
uses: PyO3/maturin-action@v1
|
|
96
|
+
with:
|
|
97
|
+
target: x64
|
|
98
|
+
args: --release --out dist --find-interpreter
|
|
99
|
+
sccache: true
|
|
100
|
+
- uses: actions/upload-artifact@v4
|
|
101
|
+
with:
|
|
102
|
+
name: wheels-windows-x86_64
|
|
103
|
+
path: dist
|
|
104
|
+
|
|
105
|
+
sdist:
|
|
106
|
+
runs-on: ubuntu-latest
|
|
107
|
+
steps:
|
|
108
|
+
- uses: actions/checkout@v4
|
|
109
|
+
- uses: PyO3/maturin-action@v1
|
|
110
|
+
with:
|
|
111
|
+
command: sdist
|
|
112
|
+
args: --out dist
|
|
113
|
+
- uses: actions/upload-artifact@v4
|
|
114
|
+
with:
|
|
115
|
+
name: sdist
|
|
116
|
+
path: dist
|
|
117
|
+
|
|
118
|
+
test:
|
|
119
|
+
# Smoke-test the linux x86_64 wheel end-to-end: install into a fresh venv,
|
|
120
|
+
# fetch chrome-headless-shell via the new `onyxweb --install` path, run
|
|
121
|
+
# the default pytest suite. Skips the benchmark tests (-m 'not benchmark'
|
|
122
|
+
# from pyproject addopts).
|
|
123
|
+
needs: [linux]
|
|
124
|
+
runs-on: ubuntu-22.04
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: actions/setup-python@v5
|
|
128
|
+
with:
|
|
129
|
+
python-version: "3.11"
|
|
130
|
+
- uses: actions/download-artifact@v4
|
|
131
|
+
with:
|
|
132
|
+
name: wheels-linux-x86_64
|
|
133
|
+
path: dist
|
|
134
|
+
- name: Install wheel + dev deps
|
|
135
|
+
run: |
|
|
136
|
+
python -m pip install --upgrade pip
|
|
137
|
+
python -m pip install onyxweb --find-links dist --force-reinstall
|
|
138
|
+
python -m pip install --group dev
|
|
139
|
+
- name: Fetch chrome-headless-shell
|
|
140
|
+
run: onyxweb --install
|
|
141
|
+
- name: Run default test suite
|
|
142
|
+
run: pytest tests/
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Manual publish to (Test)PyPI via Trusted Publishing (OIDC). Builds the full
|
|
2
|
+
# wheel matrix + sdist, smoke-tests the release wheel, then publishes to the
|
|
3
|
+
# chosen index. Run it from the Actions tab ("Run workflow" -> pick testpypi
|
|
4
|
+
# (default) or pypi). Requires a matching GitHub Environment (testpypi / pypi)
|
|
5
|
+
# and a PyPI trusted publisher.
|
|
6
|
+
#
|
|
7
|
+
# testpypi runs publish a unique dev version (BASE.dev<run_number>) so retries
|
|
8
|
+
# never collide with PyPI's immutable filenames; pypi runs publish the
|
|
9
|
+
# pyproject version as-is and tag the commit.
|
|
10
|
+
name: Publish
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
inputs:
|
|
15
|
+
repository:
|
|
16
|
+
description: "Index to publish to"
|
|
17
|
+
type: choice
|
|
18
|
+
options: [testpypi, pypi]
|
|
19
|
+
default: testpypi
|
|
20
|
+
|
|
21
|
+
# Never run two publishes to the same index at once, and never cancel one
|
|
22
|
+
# mid-upload (a half-finished upload is exactly what we want to avoid).
|
|
23
|
+
concurrency:
|
|
24
|
+
group: publish-${{ inputs.repository }}
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
version:
|
|
32
|
+
# Single source of the published version. For testpypi it is made unique per
|
|
33
|
+
# run so re-publishing never hits PyPI's immutable-filename wall. Only
|
|
34
|
+
# pyproject.toml carries it (maturin reads the wheel version from [project]);
|
|
35
|
+
# Cargo.toml stays valid semver. Hand the file to every build job as an
|
|
36
|
+
# artifact so the rewrite happens once, with no per-platform shell.
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
outputs:
|
|
39
|
+
version: ${{ steps.calc.outputs.version }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
- id: calc
|
|
43
|
+
run: |
|
|
44
|
+
BASE=$(python3 -c "import re;print(re.search(r'(?m)^version = \"([^\"]+)\"', open('pyproject.toml').read()).group(1))")
|
|
45
|
+
if [ "${{ inputs.repository }}" = "testpypi" ]; then
|
|
46
|
+
VERSION="${BASE}.dev${{ github.run_number }}"
|
|
47
|
+
else
|
|
48
|
+
VERSION="$BASE"
|
|
49
|
+
fi
|
|
50
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
51
|
+
echo "Publishing $VERSION to ${{ inputs.repository }}"
|
|
52
|
+
- name: Pin version into pyproject.toml
|
|
53
|
+
env:
|
|
54
|
+
V: ${{ steps.calc.outputs.version }}
|
|
55
|
+
run: |
|
|
56
|
+
python3 - <<'EOF'
|
|
57
|
+
import os, re
|
|
58
|
+
v = os.environ["V"]
|
|
59
|
+
s = open("pyproject.toml").read()
|
|
60
|
+
s = re.sub(r'(?m)^version = "[^"]+"', f'version = "{v}"', s, count=1)
|
|
61
|
+
open("pyproject.toml", "w").write(s)
|
|
62
|
+
EOF
|
|
63
|
+
- uses: actions/upload-artifact@v4
|
|
64
|
+
with:
|
|
65
|
+
name: version-files
|
|
66
|
+
path: pyproject.toml
|
|
67
|
+
|
|
68
|
+
linux:
|
|
69
|
+
needs: version
|
|
70
|
+
runs-on: ubuntu-22.04
|
|
71
|
+
strategy:
|
|
72
|
+
fail-fast: false
|
|
73
|
+
matrix:
|
|
74
|
+
target: [x86_64, aarch64]
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
- uses: actions/download-artifact@v4
|
|
78
|
+
with:
|
|
79
|
+
name: version-files
|
|
80
|
+
path: .
|
|
81
|
+
- name: Build wheel (manylinux_2_28)
|
|
82
|
+
uses: PyO3/maturin-action@v1
|
|
83
|
+
with:
|
|
84
|
+
target: ${{ matrix.target }}
|
|
85
|
+
manylinux: "2_28"
|
|
86
|
+
args: --release --out dist -i 3.10 3.11 3.12 3.13
|
|
87
|
+
sccache: true
|
|
88
|
+
- uses: actions/upload-artifact@v4
|
|
89
|
+
with:
|
|
90
|
+
name: wheels-linux-${{ matrix.target }}
|
|
91
|
+
path: dist
|
|
92
|
+
|
|
93
|
+
macos:
|
|
94
|
+
# One job + --find-interpreter builds all versions. A python-version matrix
|
|
95
|
+
# would duplicate wheel filenames across artifacts and corrupt the publish
|
|
96
|
+
# merge (PyPI then rejects them as "Trailing data").
|
|
97
|
+
needs: version
|
|
98
|
+
runs-on: macos-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
- uses: actions/download-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: version-files
|
|
104
|
+
path: .
|
|
105
|
+
- uses: actions/setup-python@v5
|
|
106
|
+
with:
|
|
107
|
+
python-version: "3.x"
|
|
108
|
+
- uses: PyO3/maturin-action@v1
|
|
109
|
+
with:
|
|
110
|
+
target: aarch64
|
|
111
|
+
args: --release --out dist --find-interpreter
|
|
112
|
+
sccache: true
|
|
113
|
+
- uses: actions/upload-artifact@v4
|
|
114
|
+
with:
|
|
115
|
+
name: wheels-macos-aarch64
|
|
116
|
+
path: dist
|
|
117
|
+
|
|
118
|
+
windows:
|
|
119
|
+
# One job (see macos): a python-version matrix duplicates wheels across
|
|
120
|
+
# artifacts and corrupts the publish merge.
|
|
121
|
+
needs: version
|
|
122
|
+
runs-on: windows-latest
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
- uses: actions/download-artifact@v4
|
|
126
|
+
with:
|
|
127
|
+
name: version-files
|
|
128
|
+
path: .
|
|
129
|
+
- uses: actions/setup-python@v5
|
|
130
|
+
with:
|
|
131
|
+
python-version: "3.x"
|
|
132
|
+
architecture: x64
|
|
133
|
+
- uses: PyO3/maturin-action@v1
|
|
134
|
+
with:
|
|
135
|
+
target: x64
|
|
136
|
+
args: --release --out dist --find-interpreter
|
|
137
|
+
sccache: true
|
|
138
|
+
- uses: actions/upload-artifact@v4
|
|
139
|
+
with:
|
|
140
|
+
name: wheels-windows-x86_64
|
|
141
|
+
path: dist
|
|
142
|
+
|
|
143
|
+
sdist:
|
|
144
|
+
needs: version
|
|
145
|
+
runs-on: ubuntu-latest
|
|
146
|
+
steps:
|
|
147
|
+
- uses: actions/checkout@v4
|
|
148
|
+
- uses: actions/download-artifact@v4
|
|
149
|
+
with:
|
|
150
|
+
name: version-files
|
|
151
|
+
path: .
|
|
152
|
+
- uses: PyO3/maturin-action@v1
|
|
153
|
+
with:
|
|
154
|
+
command: sdist
|
|
155
|
+
args: --out dist
|
|
156
|
+
- uses: actions/upload-artifact@v4
|
|
157
|
+
with:
|
|
158
|
+
name: sdist
|
|
159
|
+
path: dist
|
|
160
|
+
|
|
161
|
+
smoke:
|
|
162
|
+
# Gate the publish on the ACTUAL release wheel importing and fetching a real
|
|
163
|
+
# page (catches ABI / packaging breakage a unit suite on a dev build won't).
|
|
164
|
+
needs: [version, linux]
|
|
165
|
+
runs-on: ubuntu-22.04
|
|
166
|
+
steps:
|
|
167
|
+
- uses: actions/setup-python@v5
|
|
168
|
+
with:
|
|
169
|
+
python-version: "3.12"
|
|
170
|
+
- uses: actions/download-artifact@v4
|
|
171
|
+
with:
|
|
172
|
+
name: wheels-linux-x86_64
|
|
173
|
+
path: dist
|
|
174
|
+
- name: Install the built wheel
|
|
175
|
+
run: |
|
|
176
|
+
python -m pip install --upgrade pip
|
|
177
|
+
python -m pip install --pre onyxweb --find-links dist --force-reinstall
|
|
178
|
+
- name: Fetch chrome-headless-shell
|
|
179
|
+
run: onyxweb --install
|
|
180
|
+
- name: Smoke test
|
|
181
|
+
run: |
|
|
182
|
+
python -c "import onyxweb; r = onyxweb.fetch('https://example.com'); assert r.status_code == 200, r.status_code; assert 'Example Domain' in r; print('smoke OK', r.status_code)"
|
|
183
|
+
|
|
184
|
+
publish:
|
|
185
|
+
name: Publish to ${{ inputs.repository }}
|
|
186
|
+
needs: [version, linux, macos, windows, sdist, smoke]
|
|
187
|
+
runs-on: ubuntu-latest
|
|
188
|
+
environment:
|
|
189
|
+
name: ${{ inputs.repository }}
|
|
190
|
+
url: ${{ inputs.repository == 'testpypi' && 'https://test.pypi.org/p/onyxweb' || 'https://pypi.org/p/onyxweb' }}
|
|
191
|
+
permissions:
|
|
192
|
+
id-token: write
|
|
193
|
+
contents: read
|
|
194
|
+
steps:
|
|
195
|
+
# Only wheel + sdist artifacts (not version-files) into dist.
|
|
196
|
+
- uses: actions/download-artifact@v4
|
|
197
|
+
with:
|
|
198
|
+
pattern: wheels-*
|
|
199
|
+
path: dist
|
|
200
|
+
merge-multiple: true
|
|
201
|
+
- uses: actions/download-artifact@v4
|
|
202
|
+
with:
|
|
203
|
+
name: sdist
|
|
204
|
+
path: dist
|
|
205
|
+
- uses: pypa/gh-action-pypi-publish@release/v1.14
|
|
206
|
+
with:
|
|
207
|
+
packages-dir: dist
|
|
208
|
+
# Tolerate files already uploaded (e.g. a retry after a partial run).
|
|
209
|
+
skip-existing: true
|
|
210
|
+
# Empty string → real PyPI; only set for TestPyPI.
|
|
211
|
+
repository-url: ${{ inputs.repository == 'testpypi' && 'https://test.pypi.org/legacy/' || '' }}
|
|
212
|
+
|
|
213
|
+
tag:
|
|
214
|
+
# Tag real releases (pypi only) with v<version> for traceability.
|
|
215
|
+
needs: [version, publish]
|
|
216
|
+
if: inputs.repository == 'pypi'
|
|
217
|
+
runs-on: ubuntu-latest
|
|
218
|
+
permissions:
|
|
219
|
+
contents: write
|
|
220
|
+
steps:
|
|
221
|
+
- uses: actions/checkout@v4
|
|
222
|
+
with:
|
|
223
|
+
fetch-depth: 0
|
|
224
|
+
- name: Tag the release commit
|
|
225
|
+
env:
|
|
226
|
+
V: v${{ needs.version.outputs.version }}
|
|
227
|
+
run: |
|
|
228
|
+
git config user.name "github-actions"
|
|
229
|
+
git config user.email "github-actions@github.com"
|
|
230
|
+
if git rev-parse "$V" >/dev/null 2>&1; then
|
|
231
|
+
echo "Tag $V already exists, skipping."
|
|
232
|
+
else
|
|
233
|
+
git tag -a "$V" -m "Release $V"
|
|
234
|
+
git push origin "$V"
|
|
235
|
+
fi
|
onyxweb-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Rust
|
|
2
|
+
/target/
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.pyc
|
|
7
|
+
*.pyo
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
*.so
|
|
11
|
+
*.pyd
|
|
12
|
+
|
|
13
|
+
# IDE
|
|
14
|
+
.idea/
|
|
15
|
+
|
|
16
|
+
# Env
|
|
17
|
+
.venv/
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# Maturin
|
|
21
|
+
*.whl
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Ruff
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
|
|
29
|
+
CLAUDE.md
|
|
30
|
+
|
|
31
|
+
# Bundled chromium binaries — populated at wheel build time (`uv run onyxweb-download-chrome`).
|
|
32
|
+
# Not committed to git (too large); maturin `include` picks them up from disk.
|
|
33
|
+
python/onyxweb/_binaries/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Screenshot engine benchmarks
|
|
2
|
+
|
|
3
|
+
BBOT-scale subdomain screenshot pumping. One workload, multiple engines, honest numbers.
|
|
4
|
+
|
|
5
|
+
## Test setup
|
|
6
|
+
|
|
7
|
+
- **Host**: Linux 6.19.10-203.nobara.fc43 (Nobara 43 / Fedora 43), 16 CPU cores, 24 GB free RAM
|
|
8
|
+
- **URLs**: 48–50 URLs from `servo_spike/urls_bench_big.txt` (mixed public sites; `gnu.org` and `google.com` excluded from `/tmp/urls_stable.txt` for the CDP/CEF head-to-head — the first due to network flakiness, the second due to headless anti-bot variance)
|
|
9
|
+
- **Viewport**: 1200×800
|
|
10
|
+
- **Timeout**: 30–45s per URL
|
|
11
|
+
- **Build**: all release-mode with `lto = "thin"`, `codegen-units = 1`
|
|
12
|
+
- **Measurement**: `/usr/bin/time` for wall + peak RSS
|
|
13
|
+
- **Metric**: URL/second sustained (ok_count / wall_time)
|
|
14
|
+
|
|
15
|
+
Important caveat on RSS: `/usr/bin/time` captures peak RSS of the wrapped process only — for chromiumoxide it UNDERCOUNTS (Chromium is a subprocess not counted); for cef_spike warm mode it's accurate (everything in-process).
|
|
16
|
+
|
|
17
|
+
## Headline table — fully rendered URL → PNG
|
|
18
|
+
|
|
19
|
+
| Engine | Best Config | **URL/s** | Peak per-proc RAM | Binary + runtime | Compat |
|
|
20
|
+
|---|---|---|---|---|---|
|
|
21
|
+
| Servo 0.1.0 (in-process) | cold P=8 | 1.13 | 1.6 GB | 121 MB (self-contained) | 45–46/50 |
|
|
22
|
+
| Chromium CLI (fork per URL) | cold P=16 | 4.51 | 310 MB | system chromium | 50/50 |
|
|
23
|
+
| Chromium via Python CDP | warm P=16 | 5.46 | ~500 MB | system chromium + Python | 50/50 |
|
|
24
|
+
| **CEF via tauri-apps/cef-rs (Option A)** | **cold P=16** | **6.21** | 331 MB | 4 MB bin + 1.3 GB libcef.so | 48/48 |
|
|
25
|
+
| **Chromium via chromiumoxide (CDP)** | warm P=32 | **9.30** | ~430 MB | 6 MB + system chromium | 50/50 |
|
|
26
|
+
| **Chromium via chromiumoxide + chrome-headless-shell** | **warm P=16** | **8.66** | ~400 MB | 6 MB + 190 MB shell | 47–48/48 |
|
|
27
|
+
|
|
28
|
+
**Winner for BBOT-scale**: chromiumoxide (Rust-native CDP client). With bundled chrome-headless-shell it's self-contained, 8.66 URL/s, ~350 MB wheel. The "bundle CEF" path (Option A) is ~30% slower AND ~4× larger to distribute.
|
|
29
|
+
|
|
30
|
+
## Deep data: Option A vs Option C head-to-head (48 URLs stable list)
|
|
31
|
+
|
|
32
|
+
### Option A — CEF via tauri-apps/cef-rs (in-process)
|
|
33
|
+
|
|
34
|
+
| Config | Wall (s) | OK/48 | URL/s | Peak MB |
|
|
35
|
+
|---|---|---|---|---|
|
|
36
|
+
| cef cold P=1 | 63.63 | 48 | 0.75 | 330 |
|
|
37
|
+
| cef cold P=4 | 17.08 | 48 | 2.81 | 328 |
|
|
38
|
+
| cef cold P=8 | 10.08 | 48 | 4.76 | 330 |
|
|
39
|
+
| **cef cold P=16** | **7.73** | 48 | **6.21** | 331 |
|
|
40
|
+
| cef warm P=1 | 47.91 | 48 | 1.00 | 1023 |
|
|
41
|
+
| cef warm P=4 | 16.36 | 48 | 2.93 | 1025 |
|
|
42
|
+
| cef warm P=8 | 15.30 | 48 | 3.14 | 1024 |
|
|
43
|
+
| cef warm P=16 | 10.76 | 48 | 4.46 | 999 |
|
|
44
|
+
|
|
45
|
+
**Counterintuitive finding**: warm mode (one CEF init, N browsers in flight) is SLOWER than cold (N separate CEF processes) at P=16. Reason: CEF's single main thread serializes paint/event dispatch for all browsers in one instance. Cold-parallel gives each browser its own main thread. For BBOT-scale, **always use cold-process parallelism with CEF, not warm-batch**.
|
|
46
|
+
|
|
47
|
+
### Option C — chromiumoxide (CDP via WebSocket)
|
|
48
|
+
|
|
49
|
+
| Config | Wall (median, 3 runs) | OK/48 | URL/s |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| sys chromium P=16 PNG | 6.22 | 48/48 | 7.72 |
|
|
52
|
+
| **headless-shell 148 P=16 PNG** | **5.43** | 47/48 | **8.66** |
|
|
53
|
+
| headless-shell 148 P=16 PNG+HTML | 5.60 | 48/48 | 8.57 |
|
|
54
|
+
| sys chromium P=8 PNG | 20.49 | 47/48 | 2.29 (straggler-dominated) |
|
|
55
|
+
| headless-shell 148 P=8 PNG | 7.81 | 47/48 | 6.02 |
|
|
56
|
+
| headless-shell 148 P=8 PNG+HTML | 7.45 | 48/48 | 6.44 |
|
|
57
|
+
|
|
58
|
+
**chrome-headless-shell 148** (Google's automation-optimized chromium variant) is ~12% faster than the full system chromium 146 at the same P. HTML extraction via `page.content()` (post-JS `document.documentElement.outerHTML`) is essentially free — same P, same wall time.
|
|
59
|
+
|
|
60
|
+
## Decision-rubric recap
|
|
61
|
+
|
|
62
|
+
| Concern | Option A (CEF) | Option C (chromiumoxide) |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| Peak throughput | 6.21 URL/s | **8.66 URL/s** (40% higher) |
|
|
65
|
+
| Distribution size | ~1.5 GB (libcef.so + resources + bin) | ~350 MB (wheel + bundled headless-shell) |
|
|
66
|
+
| Bundle chromium with wheel | ✓ (that's the point) | ✓ also possible (ship headless-shell alongside) |
|
|
67
|
+
| Headless server (no X) | ✗ (needs Xvfb) | **✓** (headless-first) |
|
|
68
|
+
| Build complexity | High (wrap macros, multi-process dispatch, sandbox, cache-path collision handling) | Low (one async function) |
|
|
69
|
+
| Rust code lines for basic use | ~500 | ~200 |
|
|
70
|
+
| Deeper integration possible | ✓ in-process API | ✗ via CDP only |
|
|
71
|
+
| Version pinning | ✓ (link libcef version) | ✓ (pin headless-shell download) |
|
|
72
|
+
| Time to working spike | ~1 day | ~2 hours |
|
|
73
|
+
| External deps at runtime | libcef + libX11 + lots | chrome binary + glibc |
|
|
74
|
+
|
|
75
|
+
Option A (CEF) has NO win that matters for BBOT-scale headless screenshot pumping. Its unique strength — in-process API — is unused by a fetch-load-screenshot-exit workflow. Its unique cost — 4× larger distribution, X11 requirement, multi-process dispatch complexity — is paid every day.
|
|
76
|
+
|
|
77
|
+
## Conclusion
|
|
78
|
+
|
|
79
|
+
**Build onyxweb on chromiumoxide, not CEF.** Ship chrome-headless-shell 148 alongside the wheel (~350 MB total). Target:
|
|
80
|
+
|
|
81
|
+
- ~9 URL/s per process at sweet-spot concurrency (P=16–32)
|
|
82
|
+
- HTML extraction free-of-charge via `page.content()`
|
|
83
|
+
- ~400 MB RSS per process; 4 parallel processes handle ~35 URL/s, network-bound beyond
|
|
84
|
+
- Wheel distribution: unpacks `chrome-headless-shell` into site-packages
|
|
85
|
+
- Python API: `onyxweb.Client()`, `client.screenshot(url)`, `client.batch(urls, concurrency=16)`
|
|
86
|
+
|
|
87
|
+
10,000 URLs → ~18 min single process, ~5 min with 4 parallel processes.
|
|
88
|
+
|
|
89
|
+
## Reproduction
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Option A — CEF
|
|
93
|
+
cd experiments/cef_spike
|
|
94
|
+
cargo build --release # first build downloads CEF 146 binaries (~300MB)
|
|
95
|
+
DISPLAY=:0 LD_LIBRARY_PATH=$(find target -name libcef.so | xargs dirname) ./bench.sh
|
|
96
|
+
|
|
97
|
+
# Option C — chromiumoxide + chrome-headless-shell
|
|
98
|
+
cd experiments/chromiumoxide_spike
|
|
99
|
+
cargo build --release # 90s
|
|
100
|
+
curl -sSL -o /tmp/hs.zip https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.56/linux64/chrome-headless-shell-linux64.zip
|
|
101
|
+
unzip -q /tmp/hs.zip -d /tmp
|
|
102
|
+
HS=/tmp/chrome-headless-shell-linux64/chrome-headless-shell
|
|
103
|
+
./target/release/chromiumoxide_spike --chrome $HS --out-dir /tmp/shots --concurrency 16 --mode both < urls.txt
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All raw benchmark artifacts under `experiments/{cef_spike,chromiumoxide_spike,servo_spike}/bench_*/`.
|