Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a9233ea
Migrate to Flask and Jinja templates.
jscheidtmann Jul 13, 2025
b229e76
Remove all logging not related to server.
jscheidtmann Jul 21, 2025
78c02b5
Add a few missing routes
jscheidtmann Jul 21, 2025
4095f94
Add obs_sessions.html template
jscheidtmann Jul 21, 2025
1d6ca76
Convert DMS <-> Decimal on clicking DMS checkbox
jscheidtmann Jul 22, 2025
717b000
Add jinja templates for locations, logs and tools. Fix gettext handli…
jscheidtmann Aug 23, 2025
6fd08af
Fix lint error: unused var babel
jscheidtmann Aug 23, 2025
c67ca53
First stab at translations
jscheidtmann Aug 23, 2025
adced99
extract i18n messages for webserver. German translation
jscheidtmann Aug 28, 2025
b575ccb
Update messages.po
laurentbourasseau Aug 28, 2025
6f61a12
few updates of German translation, that were off
jscheidtmann Aug 29, 2025
8a55c59
Update french translations
jscheidtmann Aug 29, 2025
6c404e9
compiled french translation
jscheidtmann Aug 29, 2025
34a273d
Bootstrapped spanish translation using LLM
jscheidtmann Aug 29, 2025
c2df6bf
Update messages.mo files.
jscheidtmann Aug 29, 2025
ec3d728
Fix lint message
jscheidtmann Aug 29, 2025
767a446
Fixed nox tests, run all nox sessions.
jscheidtmann Aug 29, 2025
bc58664
Update messages.po
laurentbourasseau Aug 29, 2025
db453d4
Add a first webserver test.
jscheidtmann Aug 29, 2025
f6c01d9
...
jscheidtmann Aug 29, 2025
51a0483
Merge branch 'jinja' of github.com:jscheidtmann/PiFinder into jinja
jscheidtmann Aug 31, 2025
27729e3
Selenium Tests added.
jscheidtmann Sep 1, 2025
e6744c0
Added more selenium tests.
jscheidtmann Sep 1, 2025
0f33110
Add details of selected target
jscheidtmann Sep 1, 2025
9a9a6bc
Add remote tests for up, down, left, right, and digits 0-9.
jscheidtmann Sep 2, 2025
fa3bf12
running nox formatting.
jscheidtmann Sep 2, 2025
87a7ef3
silence lint in test_web_remote
jscheidtmann Sep 2, 2025
a612dcb
Tested Marking Menus (sorting in objects list), LNG_LEFT, LNG_RIGHT.
jscheidtmann Sep 3, 2025
3921356
Added description to test_web_remote
jscheidtmann Sep 3, 2025
6a2c72c
Credit to Claude
jscheidtmann Sep 3, 2025
647f6b1
Test network page in webserver
jscheidtmann Sep 3, 2025
a02d440
Test webserver locations.
jscheidtmann Sep 4, 2025
f79dea9
Test webserver locations.
jscheidtmann Sep 4, 2025
9633c8f
Refactored helper functions into separate module.
jscheidtmann Sep 4, 2025
d8dae48
Ignore errors when shutting down Selenium WebDriver
jscheidtmann Sep 5, 2025
f85c2c5
Ran nox.
jscheidtmann Sep 5, 2025
6f243a5
Add webserver testing strategy to CLAUDE.md
jscheidtmann Sep 5, 2025
03d34ab
Testing webserver equipment page
jscheidtmann Sep 8, 2025
407835d
Add tests for observation list and details
jscheidtmann Sep 10, 2025
0364bab
In sys_utils_fake use backup and restore user data for testing
jscheidtmann Sep 10, 2025
64a47e0
Test for tools passing
jscheidtmann Sep 10, 2025
df356b0
Use headless testing
jscheidtmann Sep 10, 2025
3796291
Testing /tools page in webserver.
jscheidtmann Sep 10, 2025
109217a
Web tests running through. Ruff formatting
jscheidtmann Sep 11, 2025
e4f7bbd
Added smoke tests for important menu items.
jscheidtmann Sep 11, 2025
f85fd64
Refactor web login methods. Add PIFINDER_HOMEPAGE env variable
jscheidtmann Sep 11, 2025
9a08351
Add developer infos for running the web tests.
jscheidtmann Sep 11, 2025
c868bc6
Add warning to not run against a PiFinder in observational use.
jscheidtmann Sep 11, 2025
30d650f
PoC for mount control using Indi
jscheidtmann Sep 13, 2025
77d012b
Remove bottle dependency
jscheidtmann Sep 16, 2025
e797e39
Remove indi poc
jscheidtmann Sep 16, 2025
6d3dd24
Move fixtures to conftest.py
jscheidtmann Sep 16, 2025
b954043
Remove hardcoded urls to use get_homepage_url() instead
jscheidtmann Sep 16, 2025
3ecc8e9
Fix import of web_test_utils
jscheidtmann Sep 16, 2025
15c0df9
Add note that tests may occasionally fail.
jscheidtmann Sep 16, 2025
7766e4c
Ran nox
jscheidtmann Sep 16, 2025
73d08e7
Refactor: use consistent naming of keyboard keys.
jscheidtmann Sep 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Development Commands

**Running Python**
Developers may have created virtual environments in directories like ".venv" or "venv". Make sure these virtual
environments are activated before any of the python based tools below.

**Development workflow uses Nox for task automation:**
```bash
nox -s lint # Code linting with Ruff (auto-fixes issues)
Expand All @@ -12,6 +16,7 @@ nox -s type_hints # Type checking with MyPy
nox -s smoke_tests # Quick functionality validation
nox -s unit_tests # Full unit test suite
nox -s babel # I18n message extraction and compilation
nox -s web_tests # Testing the webserver, see below
```

**Direct testing with pytest:**
Expand All @@ -28,6 +33,8 @@ pip install -r requirements.txt
pip install -r requirements_dev.txt
```

Watch out for .venv directories containing virtual environments, that you need to activate first.

**Running the application:**
```bash
cd python/
Expand All @@ -44,13 +51,12 @@ python -m PiFinder.main [options]
- **GPS Process** - Location/time via GPSD or UBlox direct interface
- **IMU Process** - Motion tracking with BNO055 sensor
- **Integrator Process** - Combines solver + IMU data for real-time positioning
- **Web Server Process** - Web interface and SkySafari telescope control integration
- **Web Server Process** - Web interface and SkySafari integration as a telescope
- **Position Server Process** - External protocol support

**State Management:**
- `SharedStateObj` - Process-shared state using multiprocessing managers
- `UIState` - UI-specific state management
- Real-time synchronization of telescope position, GPS coordinates, and solved sky coordinates

**Database Layer:**
- SQLite backend (`astro_data/pifinder_objects.db`)
Expand All @@ -60,7 +66,7 @@ python -m PiFinder.main [options]

**Hardware Abstraction:**
- Camera interface supporting IMX296 (global shutter), IMX290/462, HQ cameras
- Display system for SSD1351 OLED and ST7789 LCD with red-light preservation
- Display system for SSD1351 OLED and ST7789 LCD with night vision preservation using red channel only
- Hardware keypad with PWM brightness control
- GPS integration via GPSD or direct UBlox protocol
- IMU sensor integration for motion detection and telescope orientation
Expand Down Expand Up @@ -99,6 +105,33 @@ Tests use pytest with custom markers for different test types. The smoke tests p
- Menu structure and navigation logic
- Multi-process logging and communication
- Hardware interface abstractions
- Website testing

### Website testing setup

**Testing Framework:** Uses Selenium WebDriver with Pytest for automated browser testing of the web interface

**Infrastructure Requirements:**
- Selenium Grid server at localhost:4444 (configurable via SELENIUM_GRID_URL environment variable).
This server is started outside of the test code, for maximum flexibility
- Chrome browser in headless mode for test execution
- Tests automatically skip if Selenium Grid is unavailable

**Test Coverage Areas:**
- **Web Interface** (`test_web_interface.py`): Basic page loading, image display, status table elements (Mode, coordinates, software version)
- **Location Management** (`test_web_locations.py`): Location CRUD operations, DMS coordinate entry, default switching, GPS integration via remote interface
- **Network Configuration** (`test_web_network.py`): WiFi settings form validation, network management, restart flows, modal dialogs
- **Remote Control** (`test_web_remote.py`): Authentication, virtual keypad, menu navigation, marking menus, API endpoint validation
- **Equipment Management** (`test_web_equipment.py`): Telescope and eyepiece CRUD operations, active equipment selection, form validation
- **Observation Tracking** (`test_web_observations.py`): Session list display, observation counters, detail pages, TSV export functionality

**Authentication:** All protected pages use default password "solveit"

**Responsive Testing:** Tests run on both desktop (1920x1080) and mobile (375x667) viewports

**API Integration:** Extensive use of `/api/current-selection` endpoint to validate UI state changes and ensure web interface accurately reflects PiFinder's internal state

**Helper Utilities:** Shared utilities in `web_test_utils.py` for login flows, key simulation, and state validation with recursive dictionary comparison

## Code Quality

Expand Down
62 changes: 61 additions & 1 deletion docs/source/dev_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,12 @@ The defined sessions are:
That means extracts strings to translate and updates the `.po`-files in `python/locale/**`
Then these are compiled into `.mo`-files. Unfortuntely, this changes the `.mo`-files in any case,
even if the there have been no changes to strings or their translation. As this will show up
as changes to checked-in, this is not run by default.
as changes to checked-in, this is not run by default.

- web_tests -> Runs PyTest and executes all tests marked as WEB. Web tests use Selenium
to automate browser testing of the PiFinder web interface. These tests require a
running Selenium Grid server and a running PiFinder web server. You can test against a real PiFinder
or a locally running instance. See the sections below for setup instructions.


CI/CD
Expand All @@ -330,6 +335,61 @@ your fork to run the existing automation to validate your code as you develop.

If you need help, reach out via email or discord. We are happy to help :-)

Website Tests
.............

The PiFinder web interface can be tested using automated browser tests powered by Selenium.
These tests verify functionality across different viewports (desktop and mobile) and ensure
the web interface works correctly.

The tests exercise the remote control features of PiFinder, changing **the state of the PiFinder** and
therefore should **not be run** against a PiFinder you are actively using for observing.

Running Website Tests
______________________________

To run the website tests needs a running Selenium Grid server and a running PiFinder web server.
You can test against a real PiFinder or a locally running instance.

Running against a locally running instance at localhost:8080:

.. code-block:: bash

cd ~/PiFinder/python
. .venv/bin/activate # Optionally active your virtual environment
export SELENIUM_GRID_URL=<your selenium grid url which ends in /wd/hub> # Optional, default is http://localhost:4444/wd/hub
nox -s web_tests

If you want to test against a real PiFinder, set the ``PIFINDER_HOMEPAGE`` environment variable to the URL of your PiFinder instance:

.. code-block:: bash

cd ~/PiFinder/python
. .venv/bin/activate # Optionally active your virtual environment
export SELENIUM_GRID_URL=<your selenium grid url which ends in /wd/hub> # Optional, default is http://localhost:4444/wd/hub
export PIFINDER_HOMEPAGE=http://pifinder.local # Change to the URL of your PiFinder, which needs to be in the same WiFi
nox -s web_tests

If you run the tests with-out a working Selenium Grid instance, the tests will all be skipped.
You can also run individual tests with PyTest directly, use ``SELENIUM_GRID_URL=... PIFINDER_HOMEPAGE=... pytest tests/webstite/test_file.py``.

Note that due to the tests depending on the response times of the PiFinder web server and the Selenium Grid server, there may be occasional timeouts or failures.
If you encounter such issues, simply re-run the tests. We need to strike a balance between test speed and reliability, and this may require some tuning in the future.
Note that the tests run approximately 10 minutes.

Setting up Selenium Grid
___________________________

The website tests require a Selenium Grid server to run browser automation. The easiest way is to download the Selenum Grid server jar
from the selenium website, see https://www.selenium.dev/downloads/ and run it with Java:

.. code-block:: bash

java -jar selenium-server-<version>.jar standalone

The Selenium Grid server needs to run on the same machine where you have the browser installed, which you want to use for testing.
At the moment the tests will use Chrome.


Running/Debugging from the command line
---------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion python/PiFinder/gps_ubx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def _parse_nav_posecef(self, data: bytes) -> dict:
ecefZ = int.from_bytes(data[12:16], "little", signed=True) / 100.0
result = {}
if ecefX == 0 or ecefY == 0 or ecefZ == 0:
logging.debug(
logger.debug(
f"nav_posecef zeroes: ecefX: {ecefX}, ecefY: {ecefY}, ecefZ: {ecefZ}"
)
else:
Expand Down
6 changes: 3 additions & 3 deletions python/PiFinder/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from PiFinder import config
from PiFinder import pos_server
from PiFinder import utils
from PiFinder import server
from PiFinder import server2
from PiFinder import keyboard_interface

from PiFinder.multiproclogging import MultiprocLogging
Expand Down Expand Up @@ -364,7 +364,7 @@ def main(

server_process = Process(
name="Webserver",
target=server.run_server,
target=server2.run_server,
args=(
keyboard_queue,
ui_queue,
Expand Down Expand Up @@ -517,7 +517,7 @@ def main(
) # Only if new error is smaller
)
):
logger.info(
logger.debug(
f"Updating GPS location: new content: {gps_content}, old content: {location}"
)
location.lat = gps_content["lat"]
Expand Down
40 changes: 30 additions & 10 deletions python/PiFinder/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,18 @@ def __init__(
"B": self.ki.UP,
"C": self.ki.DOWN,
"D": self.ki.RIGHT,
"ALT_PLUS": self.ki.ALT_PLUS,
"ALT_MINUS": self.ki.ALT_MINUS,
"ALT_LEFT": self.ki.ALT_LEFT,
"ALT_UP": self.ki.ALT_UP,
"ALT_DOWN": self.ki.ALT_DOWN,
"ALT_RIGHT": self.ki.ALT_RIGHT,
"ALT_UP": self.ki.ALT_PLUS,
"ALT_DN": self.ki.ALT_MINUS,
"ALT_A": self.ki.ALT_LEFT,
"ALT_B": self.ki.ALT_UP,
"ALT_C": self.ki.ALT_DOWN,
"ALT_D": self.ki.ALT_RIGHT,
"ALT_0": self.ki.ALT_0,
"LNG_LEFT": self.ki.LNG_LEFT,
"LNG_UP": self.ki.LNG_UP,
"LNG_DOWN": self.ki.LNG_DOWN,
"LNG_RIGHT": self.ki.LNG_RIGHT,
"ALT_SQUARE": self.ki.ALT_SQUARE,
"LNG_A": self.ki.LNG_LEFT,
"LNG_B": self.ki.LNG_UP,
"LNG_C": self.ki.LNG_DOWN,
"LNG_D": self.ki.LNG_RIGHT,
"LNG_SQUARE": self.ki.LNG_SQUARE,
}

Expand Down Expand Up @@ -916,6 +917,25 @@ def key_callback():
self.key_callback(int(button))
return {"message": "success"}

@app.route("/api/current-selection")
@auth_required
def current_selection():
"""
Returns information about the currently active UI item for testing purposes
"""
try:
ui_state_data = self.shared_state.current_ui_state()
if ui_state_data is None:
return {"error": "UI state not available"}

response.content_type = "application/json"
return ui_state_data

except Exception as e:
logger.error(f"Error getting current UI state: {e}")
response.content_type = "application/json"
return {"error": str(e)}

@app.route("/image")
def serve_pil_image():
empty_img = Image.new(
Expand Down
Loading