A browser-based Python testing framework for educational projects and coding challenges.
TestPylot is a simple, browser-friendly framework for creating Python coding exercises. It provides:
- Browser-based test runner - No installation needed, runs entirely in the browser using PyScript/Pyodide
- Custom test framework - Similar to Mocha.js, designed specifically for Python learners
- Beautiful test UI - Color-coded results, collapsible test suites, detailed error messages
- Hot reload - Changes to your code are reflected immediately when you refresh the page
- A modern web browser (Chrome, Firefox, Safari, or Edge)
- A text editor or IDE (VS Code, PyCharm, etc.)
- Optional: Python 3.7+ for local testing
-
Open the test runner in your browser:
Simply open
SpecRunner.htmlin your web browser.If opening directly doesn't work, start a local server:
python3 -m http.server 8000
Then navigate to:
http://localhost:8000/SpecRunner.html -
Watch the tests run!
The page will show all tests with green checkmarks (passing) or red X marks (failing).
-
Implement the functions:
Edit
src/main.pyand implement the required functions. Each function includes:- A function signature
- A detailed docstring explaining what it should do
- Examples of expected behavior
-
See your progress:
Save your changes and refresh the browser. Watch the tests turn green as you implement each function correctly!
framework/
├── SpecRunner.html # Browser-based test runner (open this!)
├── README.md # This file
├── src/
│ ├── __init__.py # Package initialization
│ └── main.py # Your implementations go here
├── tests/
│ ├── test_framework.py # Custom test framework (don't modify)
│ └── test_main.py # Tests for main.py functions
└── lib/
└── styles.css # Test runner styling
TestPylot uses a decorator-based test framework similar to Mocha.js. Here's how to structure tests:
from tests.test_framework import create_runner, expect
import src.main as main
runner = create_runner()
@runner.describe("My Test Suite")
def test_suite():
@runner.subsuite("my_function")
def test_my_function():
@runner.it("should do something specific")
def test_something():
result = main.my_function(1, 2)
assert result == 3@runner.describe(title)- Create a test suite (appears as main heading)@runner.subsuite(name)- Group tests by function (appears as collapsible section)@runner.it(description)- Individual test case (green checkmark if passes, red X if fails)
Use Python's assert statement:
@runner.it("should return sum")
def test_sum():
assert main.sum(2, 3) == 5
assert main.sum() == 0Or use the chainable expect() API:
from tests.test_framework import expect
@runner.it("should multiply")
def test_multiply():
expect(main.multiply(2, 3)).to_equal(6)
expect(main.multiply(-2, 3)).to_equal(-6)expect(value).to_equal(expected)- Equality checkexpect(value).to_be(expected)- Identity check (is)expect(value).to_be_truthy()- Check if truthyexpect(value).to_be_falsy()- Check if falsyexpect(value).to_be_none()- Check if Noneexpect(value).to_not_be_none()- Check if not Noneexpect(value).to_contain(item)- Check membership (in)expect(value).to_have_length(n)- Check lengthexpect(value).to_be_instance_of(cls)- Check typeexpect(func).to_raise(exception_type)- Check exception
Edit src/main.py to implement your functions:
def my_function(a, b):
"""
Brief description of what this function does.
Args:
a: First argument
b: Second argument
Returns:
The result of some operation
Examples:
>>> my_function(2, 3)
5
"""
return a + bThe framework includes example functions:
sum(*args) - IMPLEMENTED (tests pass)
def sum(*args):
"""Sum all arguments."""
total = 0
for num in args:
total += num
return totalmultiply(*args) - STUB (tests fail)
def multiply(*args):
"""Multiply all arguments together."""
passYour task: Implement the multiply() function to make its tests pass!
from tests.test_framework import create_runner
# Create a new runner for this test module
runner = create_runner()@runner.describe("Feature Name")
def test_feature():
# All tests in this function belong to "Feature Name" suite
@runner.subsuite("function_name")
def test_function():
# All tests here are grouped under "function_name"
@runner.it("does something")
def test_behavior():
# This is an individual test case
assert True# After tests run, get results as a dictionary
results = runner.get_results()
print(f"Passed: {results['passed']}")
print(f"Failed: {results['failed']}")
print(f"Total: {results['total']}")When a test fails, you'll see:
- Test description - What it was testing
- Error message - What went wrong
- Stack trace - Where the error occurred
Use this information to debug your implementation!
- Green checkmark (✓) - Test passed
- Red X (✗) - Test failed
- Collapsible sections - Click to expand/collapse test suites
- Progress bar - Visual representation of pass rate
- Pass count - Shows X/Y passing in each suite
When you modify src/main.py or tests/test_main.py:
- Save the file
- Refresh the browser (F5 or Cmd+R)
- The page will auto-clear its cache and load the latest code
You can also import and test your code locally:
# In Python shell
from src.main import sum, multiply
print(sum(1, 2, 3)) # Should print: 6
print(multiply(2, 3, 4)) # Should print: 24- Add function signatures to
src/main.py - Add tests to
tests/test_main.py - Implement functions to pass tests
- Refresh browser to see results
If you want to split tests across files:
- Create
src/module1.pyandsrc/module2.py - Create
tests/test_module1.pyandtests/test_module2.py - Update
SpecRunner.htmlto fetch the new files:[[fetch]] files = ["src/__init__.py", "src/main.py", "src/module1.py", "src/module2.py", "tests/__init__.py", "tests/test_framework.py", "tests/test_main.py", "tests/test_module1.py", "tests/test_module2.py"]
- Import and run the new test modules in the
<script type="py">block
AttributeError: module 'main' has no attribute 'my_function'
→ You haven't defined that function yet, or there's a syntax error preventing the file from loading.
AssertionError: Expected 5, but got 6
→ Your function is returning the wrong result. Check your logic!
ImportError: cannot import name 'my_function' from 'src.main'
→ Make sure your function is defined at the module level (not inside another function).
- PyScript loads and runs Python code in the browser using Pyodide (Python compiled to WebAssembly)
- The custom test framework decorators register tests without running them
runner.run()executes all registered tests- Results are rendered as HTML with color-coded status
- JavaScript enables collapse/expand functionality
The test runner automatically clears Python's cache on page load, ensuring your latest changes are always tested. This is transparent to you!
- The custom test framework is in
tests/test_framework.py - Review it to understand how tests are registered and executed
- You can extend it with additional assertion methods if needed
- PyScript Docs
- Learn how Python runs in the browser
TestPylot emphasizes:
- Simplicity - Minimal dependencies, browser-native
- Clarity - Tests are readable and descriptive
- Accessibility - No complex setup required
- Learning - Designed for educational purposes
Ready to start?
You could:
- Explore: open
src/main.pyand implement themultiply()function to make its tests pass - Replace the
src/main.pywith your own code and put your tests intests/test_main.py
Happy coding! 🐍✨