Skip to content

Commit e3ff1e8

Browse files
Merge branch 'dev' into feat/image-heatmap
2 parents 1c9bf17 + 0b63429 commit e3ff1e8

32 files changed

Lines changed: 2860 additions & 23 deletions

.agents/context/testing.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
# Testing Instructions
22

3-
Cypress 9 for E2E and visual regression. No Python unit tests — validation is via demo scripts and Cypress.
3+
Two layers: **pytest** for Python unit tests (pure functions, server utils, window/env lifecycle) and **Cypress 9** for E2E and visual regression. Demo scripts remain useful for manual/visual validation.
44

5-
## Run Tests
5+
## Run Python Tests (pytest)
6+
7+
```bash
8+
pip install -r test-requirements.txt # includes pytest, pytest-cov
9+
pytest # runs the tracked suite under py/tests/
10+
pytest -m "not server" # skip tests that need a live server (CI default)
11+
```
12+
13+
Config lives in `pyproject.toml` (`[tool.pytest.ini_options]`): discovery is scoped to
14+
`py/tests/` and `pythonpath = ["py"]` makes `import visdom` work without an editable install.
15+
Experimental `test_*.py` scripts in the repo root (and `test/`) are intentionally out of scope.
16+
17+
## Run E2E / Visual Tests (Cypress)
618

719
```bash
820
visdom -port 8098 -env_path /tmp # Always start fresh server first
@@ -24,9 +36,19 @@ Always use port `8098` and `-env_path /tmp` for isolation.
2436

2537
`basic.js` (connection), `pane.js` (CRUD), `text.js`, `image.js`, `properties.js`, `modal.js`, `misc.js`, `screenshots.init.js` (baseline), `screenshots.js` (comparison).
2638

39+
## Writing Python Tests
40+
41+
- Place in `py/tests/`, name files `test_*.py`, classes `Test*` (unittest `TestCase` or plain
42+
pytest functions both work — pytest auto-discovers unittest).
43+
- Keep them hermetic (no running server). A test that genuinely needs a live server must be
44+
marked `@pytest.mark.server` so CI can deselect it.
45+
- Start with simple hermetic tests (e.g., `test_smoke.py`) and add focused unit tests for
46+
server/window/env lifecycle code as coverage grows.
47+
2748
## CI
2849

29-
- Python 3.8, 3.9, 3.10 matrix, both WebSocket and polling modes
50+
- `python-tests.yml` runs `pytest -m "not server"` on a Python version matrix for every pull
51+
request (and on pushes to master/dev)
3052
- Visual regression compares PR screenshots against base branch
3153
- `update-js-build-files.yml` auto-compiles JS on master
3254
- `pypi.yml` publishes to PyPI when VERSION changes

.github/workflows/process-changes.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,27 @@ jobs:
202202
with:
203203
name: cypress-screenshots-functional-polling
204204
path: cypress/screenshots
205+
206+
playwright-test:
207+
name: "Playwright Functional Test"
208+
runs-on: ubuntu-latest
209+
needs: install-and-build
210+
steps:
211+
- name: "Checkout Repository"
212+
uses: actions/checkout@v7.0.0
213+
- uses: ./.github/actions/prepare
214+
215+
- name: Install Playwright Chromium
216+
run: npx playwright install --with-deps chromium
217+
218+
219+
220+
- name: Run Playwright tests
221+
run: npm run test:pw
222+
223+
- name: Upload Playwright Report
224+
uses: actions/upload-artifact@v7
225+
if: failure()
226+
with:
227+
name: playwright-report
228+
path: playwright/playwright-report/

.github/workflows/python-tests.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Python Tests
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- master
8+
- dev
9+
10+
jobs:
11+
pytest:
12+
name: "pytest (Python ${{ matrix.python-version }})"
13+
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
python-version: ["3.12", "3.13"]
18+
steps:
19+
- name: Checkout Repository
20+
uses: actions/checkout@v7.0.0
21+
- name: Set up Python
22+
uses: actions/setup-python@v6
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
- name: Install package and test dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
python -m pip install -e .
29+
python -m pip install -r test-requirements.txt
30+
- name: Run pytest
31+
run: pytest -m "not server"

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,10 @@ website/.docusaurus
3030
venv
3131
test-bin
3232
bin
33+
34+
playwright/playwright-report/
35+
playwright/test-results/
36+
playwright/tmp/
37+
playwright-report/
38+
test-results/
39+
tmp/

AGENTS.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ Version `0.2.4` · Python >= 3.12 · Apache 2.0
3838

3939
## Testing
4040

41-
- Start server: `visdom -port 8098 -env_path /tmp`
41+
- Python unit tests: `pytest -m "not server"` (config in `pyproject.toml`, suite in `py/tests/`)
42+
- Start server (for Cypress): `visdom -port 8098 -env_path /tmp`
4243
- Baseline: `npm run test:init`
43-
- Run tests: `npm run test`
44+
- Run E2E tests: `npm run test`
4445
- Pre-commit: `pre-commit run --all-files`
4546

4647
## PR Checklist
4748

48-
- Branch from `master`, add Cypress tests for new code
49+
- Branch from `master`; add `pytest` tests in `py/tests/` for Python code and Cypress tests for frontend behavior
4950
- Update `README` for API changes, `__init__.pyi` for interface changes
5051
- Run linters, do not commit `py/visdom/static/`
5152

CONTRIBUTING.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,19 @@ To run the predefined tests
193193
5. run `npm run test:gui` (a new window should appear)
194194
6. click through the test spec-files and observe the tests done automatically in a newly opened browser instance
195195

196-
**as CLI tests**:
196+
**as CLI tests (Cypress)**:
197197
1. start a fresh visdom server instance on port `8098` , i.e. by just calling `visdom -port 8098` (Just to make sure another instance is not interfering with our test.)
198198
2. run `npm run test:init`. This generates screenshots of all plots for the visual regression testing.
199199
3. Adapt the code now to your needs.
200200
4. run `npm run build` *or* `npm run dev` (enables automatic building)
201201
5. run `npm run test`
202202

203+
**using Playwright (E2E tests)**:
204+
1. start a fresh visdom server instance on port `8098` using your environment's Python, i.e. `python -m visdom.server -port 8098 -env_path playwright/tmp` (make sure no other instances interfere).
205+
2. run `npx playwright install chromium` on first setup to install the browser.
206+
3. run `npm run build` *or* `npm run dev` to compile the frontend code.
207+
4. run the Playwright test suite: `npm run test:pw`
208+
203209
## Issues
204210
We use GitHub issues to track public bugs. Please ensure your description is
205211
clear and has sufficient instructions to be able to reproduce the issue.

README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ The following API is currently supported:
286286
- [`vis.scatter`](#visscatter) : 2D or 3D scatter plots
287287
- [`vis.sunburst`](#vissunburst) : sunburst (hierarchy) charts
288288
- [`vis.line`](#visline) : line plots
289+
- [`vis.learning_curve`](#vislearning_curve) : named training metric curves
289290
- [`vis.stem`](#visstem) : stem plots
290291
- [`vis.heatmap`](#visheatmap) : heatmap plots
291292
- [`vis.confusion_matrix`](#visconfusion_matrix) : confusion matrix plots
@@ -297,6 +298,8 @@ The following API is currently supported:
297298
- [`vis.pie`](#vispie) : pie charts
298299
- [`vis.surf`](#vissurf) : surface plots
299300
- [`vis.contour`](#viscontour) : contour plots
301+
- [`vis.roc_curve`](#visroc_curve) : ROC curves
302+
- [`vis.pr_curve`](#vispr_curve) : precision-recall curves
300303
- [`vis.quiver`](#visquiver) : quiver plots
301304
- [`vis.mesh`](#vismesh) : mesh plots
302305
- [`vis.sankey`](#vissankey) : sankey (flow) diagrams
@@ -579,6 +582,57 @@ The following `opts` are supported:
579582
- `opts.traceopts` : `dict` mapping trace names or indices to `dict`s of additional options that plot.ly accepts for a trace.
580583
- `opts.webgl` : use WebGL for plotting (`boolean`; default = `false`). It is faster if a plot contains too many points. Use sparingly as browsers won't allow more than a couple of WebGL contexts on a single page.
581584

585+
#### vis.roc_curve
586+
This function draws a ROC curve for binary classification.
587+
588+
It accepts either:
589+
- raw binary labels and scores via `y_true` and `y_score`, or
590+
- precomputed curve points via `fpr` and `tpr`.
591+
592+
The following `opts` are supported:
593+
- `opts.title` : plot title (`string`; default includes ROC-AUC)
594+
- `opts.legend` : two legend labels for curve and baseline (`list`)
595+
- `opts.xlabel` : x-axis label (`string`; default = `False Positive Rate`)
596+
- `opts.ylabel` : y-axis label (`string`; default = `True Positive Rate`)
597+
- `opts.layoutopts` : additional backend layout options (`dict`)
598+
599+
#### vis.pr_curve
600+
This function draws a precision-recall curve for binary classification.
601+
602+
It accepts either:
603+
- raw binary labels and scores via `y_true` and `y_score`, or
604+
- precomputed curve points via `precision` and `recall`.
605+
606+
The following `opts` are supported:
607+
- `opts.title` : plot title (`string`; default includes PR-AUC)
608+
- `opts.legend` : two legend labels for curve and baseline (`list`)
609+
- `opts.xlabel` : x-axis label (`string`; default = `Recall`)
610+
- `opts.ylabel` : y-axis label (`string`; default = `Precision`)
611+
- `opts.layoutopts` : additional backend layout options (`dict`)
612+
613+
614+
#### vis.learning_curve
615+
This function draws named machine-learning metrics as line plots. It accepts a mapping from metric names to scalar values or equal-length 1D series and forwards to [`vis.line`](#visline).
616+
617+
For example:
618+
619+
```python
620+
win = vis.learning_curve(
621+
{"train_loss": [1.0, 0.8, 0.6], "val_loss": [1.1, 0.9, 0.7]},
622+
step=[1, 2, 3],
623+
env="training",
624+
opts={"title": "Loss", "ylabel": "loss"},
625+
)
626+
627+
vis.learning_curve(
628+
{"train_loss": 0.55, "val_loss": 0.68},
629+
step=4,
630+
win=win,
631+
env="training",
632+
update="append",
633+
)
634+
```
635+
582636

583637
#### vis.stem
584638
This function draws a stem plot. It takes as input an `N` or `NxM` tensor
@@ -935,7 +989,7 @@ visdom is Apache 2.0 licensed, as found in the LICENSE file.
935989
Support for Lua Torch was deprecated following `v0.1.8.4`. If you'd like to use torch support, you'll need to download that release. You can follow the usage instructions there, but it is no longer officially supported.
936990

937991
## Contributing
938-
See guidelines for contributing [here.](./CONTRIBUTING.md)
992+
See guidelines for contributing and running E2E/visual tests (Cypress and Playwright) [here.](./CONTRIBUTING.md)
939993

940994
## Acknowledgments
941995
Visdom was inspired by tools like [display](https://github.com/szym/display) and relies on [Plotly](https://plot.ly/) as a plotting front-end.

REFACTORING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ Phases 1 & 2 can start immediately in parallel. Phase 5 can proceed independentl
212212
## Verification Plan
213213

214214
For each phase:
215-
1. Run existing tests: `cd py && python -m pytest ../test/`
215+
1. Run existing tests: `python -m pytest` (config in `pyproject.toml`; suite lives in `py/tests/`)
216216
2. Run Cypress E2E: `npm run test` (server on port 8098)
217217
3. Manual smoke test: `python -m visdom.server -port 8098` then run `example/demo.py`
218218
4. Verify no regressions in visual regression screenshots

cypress/integration/pane.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ const basic_examples = [
99
['Bar Plot', 'plot_bar_basic'],
1010
['Scatter Plot', 'plot_scatter_basic'],
1111
['Surface Plot', 'plot_surface_basic'],
12+
['ROC Curve', 'plot_roc_curve'],
13+
['PR Curve', 'plot_pr_curve'],
1214
['Box Plot', 'plot_special_boxplot'],
1315
['Quiver Plot', 'plot_special_quiver'],
1416
['Violin Plot', 'plot_violin_basic'],
1517
// ["Mesh Plot", "plot_special_mesh"], // disabled due to webgl
1618
['Graph Plot', 'plot_special_graph'],
1719
['Sankey Plot', 'plot_special_sankey'],
20+
['Learning Curve', 'plot_line_learning_curve'],
1821
['Matplotlib Plot', 'misc_plot_matplot'],
1922
['Latex Plot', 'misc_plot_latex'],
2023
['Video Pane', 'misc_video_tensor'],

example/components/plot_line.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,33 @@ def plot_line_many_updates(viz, env, args):
114114
)
115115

116116

117+
def plot_line_learning_curve(viz, env, args):
118+
epochs = np.arange(1, 11)
119+
train_loss = np.exp(-epochs / 4.0) + 0.08 * np.random.rand(len(epochs))
120+
val_loss = np.exp(-epochs / 3.7) + 0.12 * np.random.rand(len(epochs)) + 0.08
121+
win = viz.learning_curve(
122+
{
123+
"train_loss": train_loss,
124+
"val_loss": val_loss,
125+
},
126+
step=epochs,
127+
opts={"title": "Learning curve", "ylabel": "loss"},
128+
env=env,
129+
)
130+
131+
for epoch in range(11, 16):
132+
viz.learning_curve(
133+
{
134+
"train_loss": np.exp(-epoch / 4.0),
135+
"val_loss": np.exp(-epoch / 3.7) + 0.08,
136+
},
137+
step=epoch,
138+
win=win,
139+
update="append",
140+
env=env,
141+
)
142+
143+
117144
def plot_line_opts(viz, env, args):
118145
return viz.line(
119146
X=np.column_stack(

0 commit comments

Comments
 (0)