Skip to content
This repository was archived by the owner on Jun 22, 2025. It is now read-only.

Commit 14fa758

Browse files
Add some integration tests
At the moment we have no tests, which is bad. We provide a number of examples that are intended to help users see working examples in order to grab what they need and get working. It would be nice to simultaneously validate that our examples are functioning as well as provide the first bits of assurance that Eel itself is working, so the first integration tests will run the example code and make assertions as needed against those. This feels like the most bang-for-buck per test at the moment. We also set a default timeout of 5 seconds on all integration tests for the time being, as (locally, at least) they shouldn't take too long. On GitHub CI, we bump this to 30 seconds because things seem to run quite a bit slower there. But we still don't want them to stall and get stuck forever if we do something wrong. 30 seconds should be more than generous - but we could bump it if needed.
1 parent bdd54cb commit 14fa758

File tree

10 files changed

+183
-6
lines changed

10 files changed

+183
-6
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ jobs:
2424
- name: Setup test execution environment.
2525
run: pip3 install -r requirements-meta.txt
2626
- name: Run tox tests
27-
run: tox
27+
run: tox -- --durations=0 --timeout=30

examples/01 - hello_world/hello.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ def say_hello_py(x):
1010
say_hello_py('Python World!')
1111
eel.say_hello_js('Python World!') # Call a Javascript function
1212

13-
eel.start('hello.html', size=(300, 200)) # Start
13+
eel.start('hello.html', size=(300, 200)) # Start

examples/02 - callbacks/web/callbacks.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!DOCTYPE html>
1+
<!DOCTYPE html>
22
<html>
33
<head>
44
<title>Callbacks Demo</title>
@@ -27,4 +27,4 @@
2727
<body>
2828
Callbacks demo
2929
</body>
30-
</html>
30+
</html>

examples/06 - jinja_templates/hello.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import random
2+
13
import eel
24

35
eel.init('web') # Give folder containing web files
46

7+
@eel.expose
8+
def py_random():
9+
return random.random()
10+
511
@eel.expose # Expose this function to Javascript
612
def say_hello_py(x):
713
print('Hello from %s' % x)

requirements-test.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1+
.[jinja2]
2+
3+
psutil==5.7.0
14
pytest==5.4.3
5+
pytest-timeout==1.4.1
6+
selenium==3.141.0

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ bottle-websocket==0.2.9
33
gevent==1.3.6
44
gevent-websocket==0.10.1
55
greenlet==0.4.15
6-
pkg-resources==0.0.0
76
whichcraft==0.4.1

tests/conftest.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
from unittest import mock
3+
4+
import pytest
5+
from selenium import webdriver
6+
from selenium.webdriver import DesiredCapabilities
7+
8+
9+
@pytest.fixture
10+
def driver():
11+
TEST_BROWSER = os.environ.get("TEST_BROWSER", "chrome").lower()
12+
13+
if TEST_BROWSER == "chrome":
14+
options = webdriver.ChromeOptions()
15+
options.headless = True
16+
capabilities = DesiredCapabilities.CHROME
17+
capabilities['goog:loggingPrefs'] = {"browser": "ALL"}
18+
19+
driver = webdriver.Chrome(options=options, desired_capabilities=capabilities, service_log_path=os.path.devnull)
20+
21+
# Firefox doesn't currently supported pulling JavaScript console logs, which we currently scan to affirm that
22+
# JS/Python can communicate in some places. So for now, we can't really use firefox/geckodriver during testing.
23+
# This may be added in the future: https://github.com/mozilla/geckodriver/issues/284
24+
25+
# elif TEST_BROWSER == "firefox":
26+
# options = webdriver.FirefoxOptions()
27+
# options.headless = True
28+
# capabilities = DesiredCapabilities.FIREFOX
29+
# capabilities['loggingPrefs'] = {"browser": "ALL"}
30+
#
31+
# driver = webdriver.Firefox(options=options, capabilities=capabilities, service_log_path=os.path.devnull)
32+
33+
else:
34+
raise ValueError(f"Unsupported browser for testing: {TEST_BROWSER}")
35+
36+
with mock.patch("eel.browsers.open"):
37+
yield driver

tests/integration/test_examples.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
from tempfile import TemporaryDirectory, NamedTemporaryFile
3+
4+
from selenium import webdriver
5+
from selenium.webdriver.common.by import By
6+
from selenium.webdriver.support import expected_conditions
7+
from selenium.webdriver.support.wait import WebDriverWait
8+
9+
from tests.utils import get_eel_server, get_console_logs
10+
11+
12+
def test_01_hello_world(driver):
13+
with get_eel_server('examples/01 - hello_world/hello.py', 'hello.html') as eel_url:
14+
driver.get(eel_url)
15+
assert driver.title == "Hello, World!"
16+
17+
console_logs = get_console_logs(driver, minimum_logs=2)
18+
assert "Hello from Javascript World!" in console_logs[0]['message']
19+
assert "Hello from Python World!" in console_logs[1]['message']
20+
21+
22+
def test_02_callbacks(driver):
23+
with get_eel_server('examples/02 - callbacks/callbacks.py', 'callbacks.html') as eel_url:
24+
driver.get(eel_url)
25+
assert driver.title == "Callbacks Demo"
26+
27+
console_logs = get_console_logs(driver, minimum_logs=1)
28+
assert "Got this from Python:" in console_logs[0]['message']
29+
assert "callbacks.html" in console_logs[0]['message']
30+
31+
32+
def test_03_callbacks(driver):
33+
with get_eel_server('examples/03 - sync_callbacks/sync_callbacks.py', 'sync_callbacks.html') as eel_url:
34+
driver.get(eel_url)
35+
assert driver.title == "Synchronous callbacks"
36+
37+
console_logs = get_console_logs(driver, minimum_logs=1)
38+
assert "Got this from Python:" in console_logs[0]['message']
39+
assert "callbacks.html" in console_logs[0]['message']
40+
41+
42+
def test_04_file_access(driver: webdriver.Remote):
43+
with get_eel_server('examples/04 - file_access/file_access.py', 'file_access.html') as eel_url:
44+
driver.get(eel_url)
45+
assert driver.title == "Eel Demo"
46+
47+
with TemporaryDirectory() as temp_dir, NamedTemporaryFile(dir=temp_dir) as temp_file:
48+
driver.find_element_by_id('input-box').clear()
49+
driver.find_element_by_id('input-box').send_keys(temp_dir)
50+
driver.find_element_by_css_selector('button').click()
51+
52+
assert driver.find_element_by_id('file-name').text == os.path.basename(temp_file.name)
53+
54+
55+
def test_06_jinja_templates(driver: webdriver.Remote):
56+
with get_eel_server('examples/06 - jinja_templates/hello.py', 'templates/hello.html') as eel_url:
57+
driver.get(eel_url)
58+
assert driver.title == "Hello, World!"
59+
60+
driver.find_element_by_css_selector('a').click()
61+
WebDriverWait(driver, 2.0).until(expected_conditions.presence_of_element_located((By.XPATH, '//h1[text()="This is page 2"]')))

tests/utils.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import contextlib
2+
import os
3+
import subprocess
4+
import tempfile
5+
import time
6+
7+
import psutil
8+
9+
10+
def get_process_listening_port(proc):
11+
psutil_proc = psutil.Process(proc.pid)
12+
while not any(conn.status == 'LISTEN' for conn in psutil_proc.connections()):
13+
time.sleep(0.01)
14+
15+
conn = next(filter(lambda conn: conn.status == 'LISTEN', psutil_proc.connections()))
16+
return conn.laddr.port
17+
18+
19+
@contextlib.contextmanager
20+
def get_eel_server(example_py, start_html):
21+
"""Run an Eel example with the mode/port overridden so that no browser is launched and a random port is assigned"""
22+
test = None
23+
24+
try:
25+
with tempfile.NamedTemporaryFile(mode='w', dir=os.path.dirname(example_py), delete=False) as test:
26+
# We want to run the examples unmodified to keep the test as realistic as possible, but all of the examples
27+
# want to launch browsers, which won't be supported in CI. The below script will configure eel to open on
28+
# a random port and not open a browser, before importing the Python example file - which will then
29+
# do the rest of the set up and start the eel server. This is definitely hacky, and means we can't
30+
# test mode/port settings for examples ... but this is OK for now.
31+
test.write(f"""
32+
import eel
33+
34+
eel._start_args['mode'] = None
35+
eel._start_args['port'] = 0
36+
37+
import {os.path.splitext(os.path.basename(example_py))[0]}
38+
""")
39+
40+
proc = subprocess.Popen(['python', test.name], cwd=os.path.dirname(example_py))
41+
eel_port = get_process_listening_port(proc)
42+
43+
yield f"http://localhost:{eel_port}/{start_html}"
44+
45+
proc.terminate()
46+
47+
finally:
48+
if test:
49+
try:
50+
os.unlink(test.name)
51+
except FileNotFoundError:
52+
pass
53+
54+
55+
def get_console_logs(driver, minimum_logs=0):
56+
console_logs = driver.get_log('browser')
57+
58+
while len(console_logs) < minimum_logs:
59+
console_logs += driver.get_log('browser')
60+
time.sleep(0.1)
61+
62+
return console_logs

tox.ini

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[tox]
22
envlist = py36,py37,py38
33

4+
[pytest]
5+
timeout = 5
6+
47
[gh-actions]
58
python =
69
3.6: py36
@@ -9,4 +12,8 @@ python =
912

1013
[testenv]
1114
deps = -r requirements-test.txt
12-
commands = pytest {posargs}
15+
commands =
16+
# this ugly hack is here because:
17+
# https://github.com/tox-dev/tox/issues/149
18+
pip install -q -r {toxinidir}/requirements-test.txt
19+
{envpython} -m pytest {posargs}

0 commit comments

Comments
 (0)