Skip to content

Commit 6583030

Browse files
linesightclaude
andcommitted
Add a headless CI smoke test for the built wheel
Add tools/smoke_test.py, a standalone script that launches a browser, loads a page, and exits non-zero if the browser fails to initialize or the page never loads. It applies the per-platform CEF switches the unit tests use so it runs headless under CI (Xvfb on Linux; single-process and the Mach-port workarounds on macOS for unsigned CI processes). Wire it into the Linux, macOS and Windows CI workflows as a separate step after the unit tests, so a broken wheel is caught before it is published as an artifact. tools/run_examples.py is left untouched for interactive use. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 519d747 commit 6583030

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

.github/workflows/ci-linux.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ jobs:
137137
pip install build/dist/*.whl
138138
xvfb-run python unittests/_test_runner.py
139139
140+
- name: Smoke-test the wheel
141+
timeout-minutes: 5
142+
run: xvfb-run python tools/smoke_test.py
143+
140144
- name: Upload wheel artifact
141145
uses: actions/upload-artifact@v7
142146
with:

.github/workflows/ci-macos.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ jobs:
153153
pip install build/dist/*.whl
154154
python unittests/_test_runner.py
155155
156+
- name: Smoke-test the wheel
157+
timeout-minutes: 5
158+
# CFProcessPath is inherited from the app-bundle step above.
159+
run: python tools/smoke_test.py
160+
156161
- name: Upload wheel artifact
157162
uses: actions/upload-artifact@v7
158163
with:

.github/workflows/ci-windows.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ jobs:
119119
pip install (Get-ChildItem build/dist/*.whl | Select-Object -First 1).FullName
120120
python unittests/_test_runner.py
121121
122+
- name: Smoke-test the wheel
123+
timeout-minutes: 5
124+
run: python tools/smoke_test.py
125+
122126
- name: Upload wheel artifact
123127
uses: actions/upload-artifact@v7
124128
with:

tools/smoke_test.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2026 CEF Python, see the Authors file.
3+
# All rights reserved. Licensed under BSD 3-clause license.
4+
# Project website: https://github.com/cztomczak/cefpython
5+
6+
"""Headless CI smoke test for the built cefpython3 wheel.
7+
8+
Launches a browser on the current platform, confirms it initializes and loads
9+
a page, then exits. This is a separate script from tools/run_examples.py (which
10+
is for interactive use); it applies the same platform-specific CEF switches the
11+
unit tests use so it runs under CI (Xvfb on Linux, single-process on macOS for
12+
unsigned CI processes, etc.).
13+
14+
Exit code:
15+
0 browser was created and a page finished loading
16+
1 failure (browser not created, page never loaded, or an exception)
17+
18+
Usage (CI):
19+
Linux: xvfb-run python tools/smoke_test.py
20+
macOS/Windows: python tools/smoke_test.py
21+
"""
22+
import platform
23+
import sys
24+
25+
from cefpython3 import cefpython as cef
26+
27+
LINUX = sys.platform.startswith("linux")
28+
MAC = sys.platform == "darwin"
29+
30+
# Self-contained page, no network dependency.
31+
HTML = ("data:text/html,<!DOCTYPE html><html><body>"
32+
"<h1>cefpython smoke test</h1></body></html>")
33+
34+
# Force-quit backstop (ms) in case OnLoadEnd never fires, so the process never
35+
# hangs. CI also caps the step via timeout-minutes.
36+
QUIT_TIMEOUT_MS = 15000
37+
38+
39+
def _ci_switches():
40+
"""Per-platform switches needed to run headless / unsigned under CI.
41+
Mirrors the switches applied by unittests/main_test.py."""
42+
if LINUX:
43+
return {
44+
"disable-setuid-sandbox": "",
45+
"disable-dev-shm-usage": "",
46+
"disable-gpu": "",
47+
"disable-gpu-compositing": "",
48+
"in-process-gpu": "",
49+
"no-zygote": "",
50+
"ozone-platform": "x11",
51+
"enable-features": "NetworkServiceInProcess2",
52+
"password-store": "basic",
53+
}
54+
if MAC:
55+
return {
56+
"no-sandbox": "",
57+
"disable-gpu": "",
58+
"disable-gpu-compositing": "",
59+
"in-process-gpu": "",
60+
"use-mock-keychain": "",
61+
# An unsigned CI process can't spawn the renderer subprocess (Mach
62+
# port rendezvous fails), so run it in the browser process instead.
63+
"single-process": "",
64+
"js-flags": "--jitless",
65+
"enable-features": "NetworkServiceInProcess2",
66+
}
67+
return {} # Windows needs no special switches.
68+
69+
70+
class _LoadHandler(object):
71+
def __init__(self):
72+
self.loaded = False
73+
74+
def OnLoadEnd(self, browser, frame, http_code, **_):
75+
if frame.IsMain():
76+
self.loaded = True
77+
cef.QuitMessageLoop()
78+
79+
80+
def main():
81+
sys.excepthook = cef.ExceptHook
82+
print("[smoke_test] {plat}, Python {ver}".format(
83+
plat=sys.platform, ver=platform.python_version()))
84+
85+
cef.Initialize(settings={"log_severity": cef.LOGSEVERITY_WARNING},
86+
switches=_ci_switches())
87+
ver = cef.GetVersion()
88+
print("[smoke_test] CEF Python {v}, CEF {c}".format(
89+
v=ver["version"], c=ver["cef_version"]))
90+
91+
browser = cef.CreateBrowserSync(url=HTML, window_title="smoke test")
92+
if not browser:
93+
print("[smoke_test] ERROR: CreateBrowserSync() returned None")
94+
cef.Shutdown()
95+
sys.exit(1)
96+
97+
handler = _LoadHandler()
98+
# Set the handler before entering the message loop so OnLoadEnd (which
99+
# fires while the loop runs) can't be missed.
100+
browser.SetClientHandler(handler)
101+
cef.PostDelayedTask(cef.TID_UI, QUIT_TIMEOUT_MS, cef.QuitMessageLoop)
102+
103+
cef.MessageLoop()
104+
105+
browser.CloseBrowser(True)
106+
del browser
107+
cef.Shutdown()
108+
109+
if not handler.loaded:
110+
print("[smoke_test] ERROR: page did not finish loading within timeout")
111+
sys.exit(1)
112+
print("[smoke_test] OK: browser launched and page loaded")
113+
114+
115+
if __name__ == "__main__":
116+
main()

0 commit comments

Comments
 (0)