test(e2e): fix wx suite — header deps, port pinning, drop legacy-GL tests, wx+pthread worker fix
Suite went 64 failed → 291 passed / 0 failed: - Makefile.wasm: compile with -MMD -MP and include .d files — stale objects relinked against a newer wx lib crashed apps at startup with "function signature mismatch" after any header/vtable change - playwright(.coroutine).config.ts: pin the server port for the whole run (resolvePort from playwright-kicad.config.ts); the 60s freshness window made workers restarted after a failure rotate to a dead port (ERR_CONNECTION_REFUSED cascade across ~50 tests) - drop legacy-GL testing: KiCad renders via WebGL GAL (gal-webgl.spec.ts); remove minimal_test's OpenGL tab/GLTestCanvas, opengl.spec.ts, GL z-order describes, -sLEGACY_GL_EMULATION + gl_immediate_shim.js from the test build, and the orphaned GL baselines - coroutine-pthread repro apps join the `all` target (make clean used to delete them while all never rebuilt them); clean no longer eats checked-in JS like worker_dom_stub.js - bump wxwidgets: guard module-eval document access so wx+pthread apps (threadpool tests) survive Web Worker eval Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|
@ -42,7 +42,6 @@ tests/
|
|||
│ ├── dialog.spec.ts # wxDialog/wxMessageBox tests
|
||||
│ ├── tree.spec.ts # wxTreeCtrl tests
|
||||
│ ├── grid.spec.ts # wxGrid/wxSpinCtrl/wxSearchCtrl tests
|
||||
│ ├── opengl.spec.ts # OpenGL tab tests
|
||||
│ ├── wxwidgets.spec.ts # Comprehensive UI interaction tests
|
||||
│ └── ...
|
||||
├── logs/ # Test logs (auto-generated)
|
||||
|
|
@ -102,13 +101,12 @@ Then open http://localhost:8000/minimal_test.html
|
|||
|
||||
| Spec File | Tests | Description |
|
||||
|-----------|-------|-------------|
|
||||
| `wxwidgets.spec.ts` | Comprehensive | Full UI interaction, stability, OpenGL |
|
||||
| `wxwidgets.spec.ts` | Comprehensive | Full UI interaction, stability |
|
||||
| `menu.spec.ts` | wxMenuBar | Menu bar visibility and interactions |
|
||||
| `timer.spec.ts` | wxTimer | Timer start/stop/reset functionality |
|
||||
| `dialog.spec.ts` | wxDialog | Message boxes and custom dialogs |
|
||||
| `tree.spec.ts` | wxTreeCtrl | Tree control with expand/collapse |
|
||||
| `grid.spec.ts` | wxGrid | Grid, SpinCtrl, SearchCtrl |
|
||||
| `opengl.spec.ts` | OpenGL | GL tests (immediate mode, vertex arrays) |
|
||||
| `aui.spec.ts` | wxAuiManager | Dockable panels |
|
||||
| `clipboard.spec.ts` | wxClipboard | Copy/paste operations |
|
||||
| `dataview.spec.ts` | wxDataViewCtrl | List and tree data views (Zone Manager-like) |
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ This section maps KiCad's wxWidgets usage to our test coverage.
|
|||
| Tab panels | wxNotebook | WORKS | 7 tabs switching correctly |
|
||||
| Property editor panels | wxSplitterWindow | WORKS | layout-01-loaded.png shows split panes |
|
||||
| Scrollable content | wxScrolledWindow | WORKS | 20 items scrollable in each pane |
|
||||
| OpenGL canvas | wxGLCanvas | WORKS | gl-01-opengl-tab.png shows rendering |
|
||||
| OpenGL context | wxGLContext | WORKS | Context created successfully |
|
||||
| OpenGL canvas | wxGLCanvas | WORKS | covered by gal-webgl tests (WebGL GAL) |
|
||||
| OpenGL context | wxGLContext | WORKS | covered by gal-webgl tests (WebGL GAL) |
|
||||
| Drawing/painting | wxDC | WORKS | 08-drawing-done.png shows strokes |
|
||||
| Mouse events | wxMouseEvent | WORKS | Drawing strokes captured |
|
||||
|
||||
|
|
@ -88,20 +88,11 @@ This section maps KiCad's wxWidgets usage to our test coverage.
|
|||
| Multi-line entry | wxTextCtrl (multiline) | WORKS | Line 1/2/3 typed successfully |
|
||||
| Password fields | wxTextCtrl (password) | WORKS | Password field visible |
|
||||
|
||||
### OpenGL Rendering - WORKING
|
||||
### OpenGL Rendering
|
||||
|
||||
| KiCad Use | GL Function | Test Status | Evidence |
|
||||
|-----------|-------------|-------------|----------|
|
||||
| Immediate mode | glBegin/glEnd | WORKS | Rainbow triangle renders |
|
||||
| Vertex positioning | glVertex2f/3f | WORKS | Shapes positioned correctly |
|
||||
| Colors | glColor3f/4f | WORKS | Rainbow colors visible |
|
||||
| Triangles | GL_TRIANGLES | WORKS | Triangle visible |
|
||||
| Quads | GL_QUADS | WORKS | Yellow rectangle visible |
|
||||
| Lines | GL_LINES | WORKS | Cyan lines visible |
|
||||
| Matrix operations | glMatrixMode, glTranslatef | WORKS | Test passes |
|
||||
| Vertex arrays | glVertexPointer, glDrawElements | WORKS | Test passes |
|
||||
| State management | glEnable, glDisable | WORKS | Test passes |
|
||||
| Blending | glBlendFunc, GL_BLEND | WORKS | Test passes |
|
||||
KiCad renders through the WebGL GAL path; see `gal-webgl.spec.ts` (26 scenarios).
|
||||
The legacy-GL-emulation tests (immediate mode via -sLEGACY_GL_EMULATION and
|
||||
gl_immediate_shim.js) were removed 2026-06-10 along with minimal_test's OpenGL tab.
|
||||
|
||||
### File Operations - PARTIAL
|
||||
|
||||
|
|
@ -251,11 +242,6 @@ Organized in `apps/standalone/` folders:
|
|||
- Add/Remove buttons functional
|
||||
- wxChoice: Dropdown opens with items (Red, Green, Blue, Yellow, Purple)
|
||||
|
||||
### OpenGL Tab
|
||||
- wxGLCanvas renders blue background
|
||||
- Immediate mode: Rainbow triangle
|
||||
- GL_QUADS: Yellow rectangle
|
||||
- GL_LINES: Cyan lines
|
||||
- Vertex arrays: Working
|
||||
- Matrix operations: Working
|
||||
- State management: Working
|
||||
|
|
@ -283,43 +269,42 @@ Organized in `apps/standalone/` folders:
|
|||
4. Basic controls (buttons, checkboxes, radio buttons, sliders)
|
||||
5. Text input (single/multi-line)
|
||||
6. List controls (wxListBox, wxChoice, wxComboBox)
|
||||
7. OpenGL rendering (immediate mode, vertex arrays, matrix ops)
|
||||
8. Drawing/painting (wxDC, mouse events)
|
||||
9. **wxMessageBox/wxDialog** - Modal dialogs with Asyncify
|
||||
10. **wxGrid** - Property grids with cells, labels, and events
|
||||
11. **wxTreeCtrl** - Hierarchy browsers with expand/collapse, selection, add/delete
|
||||
12. **wxClipboard** - Copy/paste via browser Clipboard API with Asyncify
|
||||
13. **wxDataViewCtrl** - Zone Manager, Net Inspector, Library browsers
|
||||
14. **wxHtmlWindow** - About dialogs, error message formatting
|
||||
15. **wxStyledTextCtrl** - DRC rules editor, Python console, script editors
|
||||
16. **wxPrinting** - Print preview, print dialog, browser print via window.print()
|
||||
17. **wxDragDrop** - External file drops via HTML5 drag and drop API
|
||||
18. **wxPropertyGrid** - Property panels with categories, various property types
|
||||
19. **wxPropertyGridManager** - Multi-page property organization
|
||||
20. **wxColourPickerCtrl** - Color pickers for layer colors
|
||||
21. **wxFontPickerCtrl** - Font pickers for text preferences
|
||||
22. **wxCollapsiblePane** - Expandable/collapsible property sections
|
||||
23. **wxListCtrl (virtual)** - Virtual mode for 10000+ item lists
|
||||
24. **wxInfoBar** - Notification bar for DRC messages, warnings
|
||||
25. **wxDataViewCtrl (virtual)** - Virtual mode for 10,000+ items (Zone Manager, Net Inspector)
|
||||
26. **wxAuiNotebook** - Closeable, reorderable tabbed panels
|
||||
27. **wxWizard** - Step-by-step Footprint Wizard dialog
|
||||
28. **wxGrid cell editing** - Full editing with text, number, choice, checkbox editors
|
||||
29. **wxCalendarCtrl** - Date picker with month/year navigation
|
||||
30. **wxGrid custom renderers** - Color swatches, icon+text, striped rows for layer manager
|
||||
31. **wxPrintPreview frame** - Print preview with zoom, page navigation
|
||||
32. **wxBitmapButton** - Toolbar buttons with custom bitmap icons
|
||||
33. **wxTreebook** - Hierarchical settings pages (General, Display>Colors, etc.)
|
||||
34. **wxBitmapComboBox** - Layer chooser dropdown with color swatch icons
|
||||
35. **wxCheckListBox** - Layer visibility list with checkboxes
|
||||
36. **wxValidator** - Input validation (text, integer, float, custom validators)
|
||||
37. **wxOwnerDrawnComboBox** - Custom dropdown rendering (layer selectors, font choosers)
|
||||
38. **wxPopupWindow** - Transient popups (toolbar palettes, color pickers)
|
||||
39. **wxXmlDocument** - XML parsing for config and project files
|
||||
40. **wxFontEnumerator** - Font enumeration via Local Font Access API (Chrome 103+)
|
||||
41. **wxMask (bitmap masking)** - Color-keyed transparency for toolbar icons
|
||||
42. **Text decorations** - Underline/strikethrough in wxDC text rendering
|
||||
43. **wxRegion clipping** - Non-rectangular clipping for complex shapes
|
||||
7. Drawing/painting (wxDC, mouse events)
|
||||
8. **wxMessageBox/wxDialog** - Modal dialogs with Asyncify
|
||||
9. **wxGrid** - Property grids with cells, labels, and events
|
||||
10. **wxTreeCtrl** - Hierarchy browsers with expand/collapse, selection, add/delete
|
||||
11. **wxClipboard** - Copy/paste via browser Clipboard API with Asyncify
|
||||
12. **wxDataViewCtrl** - Zone Manager, Net Inspector, Library browsers
|
||||
13. **wxHtmlWindow** - About dialogs, error message formatting
|
||||
14. **wxStyledTextCtrl** - DRC rules editor, Python console, script editors
|
||||
15. **wxPrinting** - Print preview, print dialog, browser print via window.print()
|
||||
16. **wxDragDrop** - External file drops via HTML5 drag and drop API
|
||||
17. **wxPropertyGrid** - Property panels with categories, various property types
|
||||
18. **wxPropertyGridManager** - Multi-page property organization
|
||||
19. **wxColourPickerCtrl** - Color pickers for layer colors
|
||||
20. **wxFontPickerCtrl** - Font pickers for text preferences
|
||||
21. **wxCollapsiblePane** - Expandable/collapsible property sections
|
||||
22. **wxListCtrl (virtual)** - Virtual mode for 10000+ item lists
|
||||
23. **wxInfoBar** - Notification bar for DRC messages, warnings
|
||||
24. **wxDataViewCtrl (virtual)** - Virtual mode for 10,000+ items (Zone Manager, Net Inspector)
|
||||
25. **wxAuiNotebook** - Closeable, reorderable tabbed panels
|
||||
26. **wxWizard** - Step-by-step Footprint Wizard dialog
|
||||
27. **wxGrid cell editing** - Full editing with text, number, choice, checkbox editors
|
||||
28. **wxCalendarCtrl** - Date picker with month/year navigation
|
||||
29. **wxGrid custom renderers** - Color swatches, icon+text, striped rows for layer manager
|
||||
30. **wxPrintPreview frame** - Print preview with zoom, page navigation
|
||||
31. **wxBitmapButton** - Toolbar buttons with custom bitmap icons
|
||||
32. **wxTreebook** - Hierarchical settings pages (General, Display>Colors, etc.)
|
||||
33. **wxBitmapComboBox** - Layer chooser dropdown with color swatch icons
|
||||
34. **wxCheckListBox** - Layer visibility list with checkboxes
|
||||
35. **wxValidator** - Input validation (text, integer, float, custom validators)
|
||||
36. **wxOwnerDrawnComboBox** - Custom dropdown rendering (layer selectors, font choosers)
|
||||
37. **wxPopupWindow** - Transient popups (toolbar palettes, color pickers)
|
||||
38. **wxXmlDocument** - XML parsing for config and project files
|
||||
39. **wxFontEnumerator** - Font enumeration via Local Font Access API (Chrome 103+)
|
||||
40. **wxMask (bitmap masking)** - Color-keyed transparency for toolbar icons
|
||||
41. **Text decorations** - Underline/strikethrough in wxDC text rendering
|
||||
42. **wxRegion clipping** - Non-rectangular clipping for complex shapes
|
||||
|
||||
### All KiCad-Critical Features Tested
|
||||
|
||||
|
|
@ -388,9 +373,8 @@ The `wasmedge_test` app verifies WASM-specific behaviors:
|
|||
## Build Notes
|
||||
|
||||
### GL vs Non-GL Apps
|
||||
- Main app (minimal_test) uses `LDFLAGS_GL` with OpenGL support
|
||||
- Standalone test apps use `LDFLAGS_NOGL` without OpenGL
|
||||
- This prevents "_glBegin is not defined" crashes in non-GL apps
|
||||
- All wx test apps (including minimal_test) link with `LDFLAGS_NOGL`; legacy GL
|
||||
emulation was removed 2026-06-10. WebGL rendering is tested by gal-webgl.
|
||||
|
||||
### Build Command
|
||||
Always use the build script:
|
||||
|
|
@ -483,7 +467,6 @@ npx playwright test --ui # Interactive mode with screenshot preview
|
|||
| 08-drawing-done.png | Drawing strokes on canvas |
|
||||
| 09-lists-tab.png | ListBox and Choice controls |
|
||||
| 10a-choice-dropdown-open.png | Dropdown menu open |
|
||||
| gl-01-opengl-tab.png | OpenGL rendering |
|
||||
| aui-01-loaded.png | AUI dockable panels |
|
||||
| menu-03-file-clicked.png | File menu dropdown |
|
||||
| toolbar-01-loaded.png | Toolbar with icons |
|
||||
|
|
|
|||
|
|
@ -12,9 +12,6 @@ KICAD_ROOT = ../../kicad
|
|||
CXX = em++
|
||||
WX_CXXFLAGS := $(shell $(WXCONFIG) --cxxflags)
|
||||
|
||||
# Libraries for GL apps (minimal_test uses OpenGL)
|
||||
WX_LDFLAGS_GL := $(shell $(WXCONFIG) --libs base,core,gl,aui)
|
||||
|
||||
# Libraries for non-GL apps (standalone tests don't need GL)
|
||||
WX_LDFLAGS_NOGL := $(shell $(WXCONFIG) --libs base,core,aui)
|
||||
|
||||
|
|
@ -47,6 +44,11 @@ else
|
|||
DEBUG_LDFLAGS =
|
||||
endif
|
||||
|
||||
# Track header dependencies: without this, objects compiled against old wx headers
|
||||
# get silently relinked into a newer library, and any vtable layout change then
|
||||
# crashes apps at startup with "RuntimeError: function signature mismatch".
|
||||
CXXFLAGS += -MMD -MP
|
||||
|
||||
# Base Emscripten flags (for all apps)
|
||||
# ASYNCIFY enables blocking modal dialogs (ShowModal waits for user)
|
||||
# ASYNCIFY_IMPORTS tells Emscripten which imported JS functions can unwind the stack
|
||||
|
|
@ -59,13 +61,6 @@ BASE_LDFLAGS = -sALLOW_MEMORY_GROWTH -sERROR_ON_UNDEFINED_SYMBOLS=0 \
|
|||
-sASYNCIFY_STACK_SIZE=8192 \
|
||||
-sASYNCIFY_IMPORTS=['startModal','js_writeTextToClipboard','js_readTextFromClipboard','js_clipboardHasText','js_clearClipboard','js_enumerateFonts']
|
||||
|
||||
# GL-specific flags
|
||||
EM_GL_FLAGS = -sLEGACY_GL_EMULATION -sMAX_WEBGL_VERSION=2
|
||||
GL_SHIM = ../../wasm/shims/gl_immediate_shim.js
|
||||
|
||||
# LDFLAGS for GL apps (minimal_test)
|
||||
LDFLAGS_GL = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(EM_GL_FLAGS) --js-library=$(GL_SHIM) $(WX_LDFLAGS_GL)
|
||||
|
||||
# LDFLAGS for non-GL apps (standalone tests)
|
||||
LDFLAGS_NOGL = $(DEBUG_LDFLAGS) $(BASE_LDFLAGS) $(WX_LDFLAGS_NOGL)
|
||||
|
||||
|
|
@ -113,6 +108,9 @@ WX_CORE_LIB = $(WX_LIB_DIR)/libwx_wasmunivu_core-3.2-emscripten.a
|
|||
# Standalone directories
|
||||
S = standalone
|
||||
|
||||
# Pull in the dependency files generated by -MMD
|
||||
-include $(wildcard *.d) $(wildcard $(S)/*/*.d)
|
||||
|
||||
all: minimal_test.html \
|
||||
$(S)/menu/menu_test.html \
|
||||
$(S)/clipboard/clipboard_test.html \
|
||||
|
|
@ -158,14 +156,22 @@ all: minimal_test.html \
|
|||
$(S)/logerror/logerror_test.html \
|
||||
$(S)/retinascale/retinascale_test.html \
|
||||
$(S)/coroutine/coroutine_test.html \
|
||||
$(S)/coroutine-nested/nested_test.html
|
||||
$(S)/coroutine-nested/nested_test.html \
|
||||
$(S)/coroutine-pthread/coroutine_test_wxpt.html \
|
||||
$(S)/coroutine-pthread/embind_repro.html \
|
||||
$(S)/coroutine-pthread/gl_repro.html \
|
||||
$(S)/coroutine-pthread/gl_repro_pt.html \
|
||||
$(S)/coroutine-pthread/main_repro.html \
|
||||
$(S)/coroutine-pthread/mainloop_repro.html \
|
||||
$(S)/coroutine-pthread/nested_repro_ex.html \
|
||||
$(S)/coroutine-pthread/vcall_repro.html
|
||||
|
||||
# Main test app (uses GL)
|
||||
# Main test app
|
||||
minimal_test.o: minimal_test.cpp
|
||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
||||
|
||||
minimal_test.html: minimal_test.o $(WX_CORE_LIB)
|
||||
$(CXX) $< $(LDFLAGS_GL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
|
||||
# Menu test (no GL)
|
||||
$(S)/menu/menu_test.o: $(S)/menu/menu_test.cpp
|
||||
|
|
@ -536,9 +542,13 @@ retinascale: $(S)/retinascale/retinascale_test.html
|
|||
coroutine: $(S)/coroutine/coroutine_test.html
|
||||
coroutine-nested: $(S)/coroutine-nested/nested_test.html
|
||||
|
||||
# Only delete generated app files (*_test*, *_repro*) — a bare $(S)/*/*.js would
|
||||
# also delete checked-in sources like coroutine-pthread/worker_dom_stub.js.
|
||||
clean:
|
||||
rm -f minimal_test.o minimal_test.html minimal_test.js minimal_test.wasm
|
||||
rm -f $(S)/*/*.o $(S)/*/*.html $(S)/*/*.js $(S)/*/*.wasm
|
||||
rm -f minimal_test.o minimal_test.d minimal_test.html minimal_test.js minimal_test.wasm minimal_test.wasm.map
|
||||
rm -f $(S)/*/*.o $(S)/*/*.d $(S)/*/*.wasm.map
|
||||
rm -f $(S)/*/*_test*.html $(S)/*/*_test*.js $(S)/*/*_test*.wasm
|
||||
rm -f $(S)/*/*_repro*.html $(S)/*/*_repro*.js $(S)/*/*_repro*.wasm
|
||||
|
||||
.PHONY: all clean menu clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize earlysize threadpool logerror retinascale coroutine coroutine-nested
|
||||
|
||||
|
|
|
|||
|
|
@ -16,28 +16,10 @@
|
|||
#include "wx/gauge.h"
|
||||
#include "wx/dcbuffer.h"
|
||||
#include "wx/datetime.h"
|
||||
#include "wx/glcanvas.h"
|
||||
#include "wx/grid.h"
|
||||
#include "wx/spinctrl.h"
|
||||
#include "wx/srchctrl.h"
|
||||
|
||||
// OpenGL headers - using legacy GL with Emscripten's emulation
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glu.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#else
|
||||
#include <OpenGL/gl.h>
|
||||
#include <OpenGL/glu.h>
|
||||
#endif
|
||||
|
||||
// Console logging macro for WASM debugging
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#define CONSOLE_LOG(msg) EM_ASM({ console.log('[GL-CPP] ' + UTF8ToString($0)); }, msg)
|
||||
#else
|
||||
#define CONSOLE_LOG(msg) printf("[GL-CPP] %s\n", msg)
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
// Control IDs
|
||||
|
|
@ -59,12 +41,6 @@ enum {
|
|||
ID_BTN_CLEAR,
|
||||
ID_EVENT_LOG,
|
||||
ID_DRAWING_PANEL,
|
||||
ID_GL_CANVAS,
|
||||
ID_BTN_GL_TEST_IMMEDIATE,
|
||||
ID_BTN_GL_TEST_MATRIX,
|
||||
ID_BTN_GL_TEST_VERTEX_ARRAY,
|
||||
ID_BTN_GL_RUN_ALL,
|
||||
ID_GL_TEST_SELECT,
|
||||
// Grid tab IDs
|
||||
ID_GRID,
|
||||
ID_SPIN_CTRL,
|
||||
|
|
@ -164,477 +140,6 @@ void DrawingPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
|
|||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// GLTestCanvas - OpenGL canvas for testing legacy GL functions (KiCad uses these)
|
||||
//-----------------------------------------------------------------------------
|
||||
class GLTestCanvas : public wxGLCanvas
|
||||
{
|
||||
public:
|
||||
GLTestCanvas(wxWindow* parent);
|
||||
virtual ~GLTestCanvas();
|
||||
|
||||
// Test functions matching KiCad's GL usage
|
||||
void TestImmediateMode(); // glBegin/glEnd, glVertex, glColor
|
||||
void TestMatrixOperations(); // glMatrixMode, glPushMatrix, glTranslate, etc.
|
||||
void TestVertexArrays(); // glEnableClientState, glVertexPointer, etc.
|
||||
void TestStateManagement(); // glEnable/glDisable, glBlendFunc
|
||||
void TestTexCoords(); // glTexCoord2f
|
||||
void TestNormals(); // glNormal3f
|
||||
void RunAllTests();
|
||||
void SetCurrentTest(int test);
|
||||
|
||||
bool IsGLInitialized() const { return m_glInitialized; }
|
||||
|
||||
private:
|
||||
wxGLContext* m_context;
|
||||
bool m_glInitialized;
|
||||
int m_currentTest; // Which test pattern to display
|
||||
|
||||
void OnPaint(wxPaintEvent& evt);
|
||||
void OnSize(wxSizeEvent& evt);
|
||||
void InitGL();
|
||||
void SetupViewport();
|
||||
void Render();
|
||||
|
||||
wxDECLARE_EVENT_TABLE();
|
||||
};
|
||||
|
||||
wxBEGIN_EVENT_TABLE(GLTestCanvas, wxGLCanvas)
|
||||
EVT_PAINT(GLTestCanvas::OnPaint)
|
||||
EVT_SIZE(GLTestCanvas::OnSize)
|
||||
wxEND_EVENT_TABLE()
|
||||
|
||||
// Helper to get GL attributes
|
||||
static wxGLAttributes GetGLAttributes()
|
||||
{
|
||||
wxGLAttributes attrs;
|
||||
attrs.PlatformDefaults().Defaults().EndList();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
GLTestCanvas::GLTestCanvas(wxWindow* parent)
|
||||
: wxGLCanvas(parent, GetGLAttributes(), ID_GL_CANVAS,
|
||||
wxDefaultPosition, wxSize(400, 300))
|
||||
, m_context(nullptr)
|
||||
, m_glInitialized(false)
|
||||
, m_currentTest(0)
|
||||
{
|
||||
CONSOLE_LOG("GLTestCanvas constructor called");
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
// Check if wxGLCanvas was created successfully
|
||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = GetWebGLContext();
|
||||
if (ctx > 0) {
|
||||
EM_ASM({ console.log('[GL-CPP] WebGL context ID: ' + $0); }, ctx);
|
||||
} else {
|
||||
CONSOLE_LOG("ERROR: No WebGL context from wxGLCanvas!");
|
||||
}
|
||||
}
|
||||
|
||||
GLTestCanvas::~GLTestCanvas()
|
||||
{
|
||||
delete m_context;
|
||||
}
|
||||
|
||||
void GLTestCanvas::InitGL()
|
||||
{
|
||||
CONSOLE_LOG("InitGL called");
|
||||
|
||||
if (m_glInitialized) {
|
||||
CONSOLE_LOG("Already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
CONSOLE_LOG("Creating wxGLContext...");
|
||||
if (!m_context) {
|
||||
m_context = new wxGLContext(this);
|
||||
if (m_context && m_context->IsOK()) {
|
||||
CONSOLE_LOG("wxGLContext created successfully");
|
||||
} else {
|
||||
CONSOLE_LOG("ERROR: wxGLContext creation failed!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CONSOLE_LOG("Calling SetCurrent...");
|
||||
if (!SetCurrent(*m_context)) {
|
||||
CONSOLE_LOG("ERROR: SetCurrent failed!");
|
||||
return;
|
||||
}
|
||||
CONSOLE_LOG("SetCurrent succeeded");
|
||||
|
||||
// Basic GL setup
|
||||
CONSOLE_LOG("Setting up GL state...");
|
||||
glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
m_glInitialized = true;
|
||||
|
||||
CONSOLE_LOG("OpenGL initialized successfully");
|
||||
wxPrintf("[GL] Vendor: %s\n", glGetString(GL_VENDOR));
|
||||
wxPrintf("[GL] Renderer: %s\n", glGetString(GL_RENDERER));
|
||||
wxPrintf("[GL] Version: %s\n", glGetString(GL_VERSION));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void GLTestCanvas::SetupViewport()
|
||||
{
|
||||
wxSize size = GetClientSize();
|
||||
glViewport(0, 0, size.x, size.y);
|
||||
|
||||
// Setup orthographic projection (legacy style)
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(-2.0, 2.0, -2.0, 2.0, -10.0, 10.0);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
}
|
||||
|
||||
void GLTestCanvas::OnSize(wxSizeEvent& evt)
|
||||
{
|
||||
if (m_glInitialized && m_context) {
|
||||
SetCurrent(*m_context);
|
||||
SetupViewport();
|
||||
}
|
||||
evt.Skip();
|
||||
}
|
||||
|
||||
void GLTestCanvas::OnPaint(wxPaintEvent& WXUNUSED(evt))
|
||||
{
|
||||
CONSOLE_LOG("OnPaint called");
|
||||
wxPaintDC dc(this); // Required even for GL
|
||||
|
||||
// Initialize GL if not done yet (this creates m_context)
|
||||
if (!m_glInitialized) {
|
||||
CONSOLE_LOG("GL not initialized, calling InitGL...");
|
||||
InitGL();
|
||||
if (!m_glInitialized) {
|
||||
CONSOLE_LOG("InitGL failed, returning");
|
||||
return;
|
||||
}
|
||||
SetupViewport();
|
||||
}
|
||||
|
||||
if (!m_context) {
|
||||
CONSOLE_LOG("No context after InitGL, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
CONSOLE_LOG("Setting context current in OnPaint...");
|
||||
SetCurrent(*m_context);
|
||||
|
||||
CONSOLE_LOG("Calling Render...");
|
||||
Render();
|
||||
CONSOLE_LOG("Calling SwapBuffers...");
|
||||
SwapBuffers();
|
||||
CONSOLE_LOG("OnPaint complete");
|
||||
}
|
||||
|
||||
void GLTestCanvas::Render()
|
||||
{
|
||||
// Set a bright visible color to prove GL is working
|
||||
glClearColor(0.2f, 0.4f, 0.8f, 1.0f); // Bright blue
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
CONSOLE_LOG("glClear done with blue color");
|
||||
|
||||
// Debug GLImmediate state before drawing
|
||||
EM_ASM({
|
||||
if (typeof GLImmediate !== 'undefined') {
|
||||
console.log('[GL-DEBUG] GLImmediate state:');
|
||||
console.log(' initted:', GLImmediate.initted);
|
||||
console.log(' enabledClientAttributes:', GLImmediate.enabledClientAttributes);
|
||||
console.log(' totalEnabledClientAttributes:', GLImmediate.totalEnabledClientAttributes);
|
||||
if (GLImmediate.TexEnvJIT) {
|
||||
console.log(' TexEnvJIT.enabled:', GLImmediate.TexEnvJIT.enabled);
|
||||
}
|
||||
} else {
|
||||
console.log('[GL-DEBUG] GLImmediate not defined!');
|
||||
}
|
||||
});
|
||||
|
||||
// Draw based on current test
|
||||
switch (m_currentTest) {
|
||||
case 0: TestImmediateMode(); break;
|
||||
case 1: TestMatrixOperations(); break;
|
||||
case 2: TestVertexArrays(); break;
|
||||
case 3: TestStateManagement(); break;
|
||||
default: TestImmediateMode(); break;
|
||||
}
|
||||
|
||||
glFlush();
|
||||
}
|
||||
|
||||
// Test 1: Immediate Mode Drawing (glBegin/glEnd) - KiCad uses this heavily
|
||||
// Using STANDARD OpenGL patterns (color once, multiple vertices)
|
||||
// This tests the GL shim that handles Emscripten's color-per-vertex requirement
|
||||
void GLTestCanvas::TestImmediateMode()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing immediate mode (glBegin/glEnd)...\n");
|
||||
wxPrintf("[GL TEST] Using STANDARD OpenGL patterns (color set once per primitive)\n");
|
||||
fflush(stdout);
|
||||
|
||||
// Test GL_TRIANGLES with glVertex3f and glColor3f
|
||||
// RGB triangle - each vertex has a different color (smooth shading)
|
||||
glBegin(GL_TRIANGLES);
|
||||
glColor3f(1.0f, 0.0f, 0.0f); // Red
|
||||
glVertex3f(-1.0f, -0.5f, 0.0f);
|
||||
glColor3f(0.0f, 1.0f, 0.0f); // Green
|
||||
glVertex3f(0.0f, 1.0f, 0.0f);
|
||||
glColor3f(0.0f, 0.0f, 1.0f); // Blue
|
||||
glVertex3f(1.0f, -0.5f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
// Test GL_QUADS with glVertex2f and glColor4f
|
||||
// STANDARD OPENGL: Set color ONCE, then multiple vertices
|
||||
// The GL shim should inject color before each vertex automatically
|
||||
glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // Yellow, semi-transparent - SET ONCE
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(-1.8f, -1.8f); // All 4 vertices use the same yellow color
|
||||
glVertex2f(-1.2f, -1.8f);
|
||||
glVertex2f(-1.2f, -1.2f);
|
||||
glVertex2f(-1.8f, -1.2f);
|
||||
glEnd();
|
||||
|
||||
// Test GL_LINES with standard pattern
|
||||
// STANDARD OPENGL: Set color ONCE, then multiple vertices
|
||||
glColor3f(1.0f, 1.0f, 1.0f); // White - SET ONCE
|
||||
glBegin(GL_LINES);
|
||||
glVertex3f(-1.5f, 1.5f, 0.0f);
|
||||
glVertex3f(1.5f, 1.5f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
// Test GL_LINE_STRIP with standard pattern
|
||||
glColor3f(0.0f, 1.0f, 1.0f); // Cyan - SET ONCE
|
||||
glBegin(GL_LINE_STRIP);
|
||||
glVertex3f(1.2f, -1.8f, 0.0f);
|
||||
glVertex3f(1.4f, -1.4f, 0.0f);
|
||||
glVertex3f(1.6f, -1.6f, 0.0f);
|
||||
glVertex3f(1.8f, -1.2f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
// Test GL_LINE_LOOP with standard pattern
|
||||
glColor3f(1.0f, 0.0f, 1.0f); // Magenta - SET ONCE
|
||||
glBegin(GL_LINE_LOOP);
|
||||
glVertex3f(1.2f, 1.2f, 0.0f);
|
||||
glVertex3f(1.8f, 1.2f, 0.0f);
|
||||
glVertex3f(1.8f, 1.8f, 0.0f);
|
||||
glVertex3f(1.2f, 1.8f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
wxPrintf("[GL TEST] Immediate mode test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Test 2: Matrix Operations - KiCad uses glPushMatrix/glPopMatrix extensively
|
||||
// Using STANDARD OpenGL patterns (color once, multiple vertices)
|
||||
void GLTestCanvas::TestMatrixOperations()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing matrix operations...\n");
|
||||
fflush(stdout);
|
||||
|
||||
// Draw centered triangle with standard GL pattern
|
||||
glPushMatrix();
|
||||
glTranslatef(0.0f, 0.0f, 0.0f);
|
||||
glColor3f(0.5f, 0.5f, 0.5f); // Gray - SET ONCE
|
||||
glBegin(GL_TRIANGLES);
|
||||
glVertex3f(-0.3f, -0.3f, 0.0f);
|
||||
glVertex3f(0.3f, -0.3f, 0.0f);
|
||||
glVertex3f(0.0f, 0.3f, 0.0f);
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
|
||||
// Draw 4 rotated/translated copies with standard GL pattern
|
||||
for (int i = 0; i < 4; i++) {
|
||||
glPushMatrix();
|
||||
float angle = i * 90.0f;
|
||||
float tx = 1.2f * ((i % 2) * 2 - 1); // -1.2 or 1.2
|
||||
float ty = 1.2f * ((i / 2) * 2 - 1); // -1.2 or 1.2
|
||||
|
||||
glTranslatef(tx, ty, 0.0f);
|
||||
glRotatef(angle, 0.0f, 0.0f, 1.0f);
|
||||
glScalef(0.5f, 0.5f, 1.0f);
|
||||
|
||||
// Draw colored square with standard GL pattern
|
||||
float r = (i == 0 || i == 3) ? 1.0f : 0.3f;
|
||||
float g = (i == 1 || i == 3) ? 1.0f : 0.3f;
|
||||
float b = (i == 2 || i == 3) ? 1.0f : 0.3f;
|
||||
|
||||
glColor3f(r, g, b); // SET ONCE before glBegin
|
||||
glBegin(GL_QUADS);
|
||||
glVertex3f(-0.5f, -0.5f, 0.0f);
|
||||
glVertex3f(0.5f, -0.5f, 0.0f);
|
||||
glVertex3f(0.5f, 0.5f, 0.0f);
|
||||
glVertex3f(-0.5f, 0.5f, 0.0f);
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
wxPrintf("[GL TEST] Matrix operations test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Test 3: Legacy Vertex Arrays - KiCad uses glVertexPointer, glColorPointer
|
||||
void GLTestCanvas::TestVertexArrays()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing legacy vertex arrays...\n");
|
||||
fflush(stdout);
|
||||
|
||||
// Vertex data for a hexagon
|
||||
static GLfloat vertices[] = {
|
||||
0.0f, 0.0f, 0.0f, // Center
|
||||
1.0f, 0.0f, 0.0f, // Right
|
||||
0.5f, 0.866f, 0.0f, // Upper right
|
||||
-0.5f, 0.866f, 0.0f, // Upper left
|
||||
-1.0f, 0.0f, 0.0f, // Left
|
||||
-0.5f, -0.866f, 0.0f,// Lower left
|
||||
0.5f, -0.866f, 0.0f // Lower right
|
||||
};
|
||||
|
||||
static GLfloat colors[] = {
|
||||
1.0f, 1.0f, 1.0f, // White center
|
||||
1.0f, 0.0f, 0.0f, // Red
|
||||
1.0f, 0.5f, 0.0f, // Orange
|
||||
1.0f, 1.0f, 0.0f, // Yellow
|
||||
0.0f, 1.0f, 0.0f, // Green
|
||||
0.0f, 0.0f, 1.0f, // Blue
|
||||
0.5f, 0.0f, 1.0f // Purple
|
||||
};
|
||||
|
||||
// KiCad uses GL_UNSIGNED_SHORT or GL_UNSIGNED_INT for indices, never GL_UNSIGNED_BYTE
|
||||
// Emscripten's legacy GL emulation only supports GL_UNSIGNED_SHORT for client-side arrays
|
||||
static GLushort indices[] = {
|
||||
0, 1, 2,
|
||||
0, 2, 3,
|
||||
0, 3, 4,
|
||||
0, 4, 5,
|
||||
0, 5, 6,
|
||||
0, 6, 1
|
||||
};
|
||||
|
||||
// Enable client state (legacy)
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
|
||||
// Set up pointers
|
||||
glVertexPointer(3, GL_FLOAT, 0, vertices);
|
||||
glColorPointer(3, GL_FLOAT, 0, colors);
|
||||
|
||||
// Draw using vertex arrays (GL_UNSIGNED_SHORT matches KiCad's usage)
|
||||
glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_SHORT, indices);
|
||||
|
||||
// Disable client state
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
wxPrintf("[GL TEST] Legacy vertex arrays test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Test 4: State Management - glEnable/glDisable, blending
|
||||
// Using STANDARD OpenGL patterns with separate glBegin/glEnd for each color
|
||||
void GLTestCanvas::TestStateManagement()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing state management...\n");
|
||||
fflush(stdout);
|
||||
|
||||
// Enable blending
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Draw overlapping semi-transparent squares using standard GL pattern
|
||||
// Each quad has its own color set before glBegin
|
||||
|
||||
// Red square
|
||||
glColor4f(1.0f, 0.0f, 0.0f, 0.5f); // SET ONCE
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(-1.0f, -1.0f);
|
||||
glVertex2f(0.5f, -1.0f);
|
||||
glVertex2f(0.5f, 0.5f);
|
||||
glVertex2f(-1.0f, 0.5f);
|
||||
glEnd();
|
||||
|
||||
// Green square
|
||||
glColor4f(0.0f, 1.0f, 0.0f, 0.5f); // SET ONCE
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(-0.5f, -0.5f);
|
||||
glVertex2f(1.0f, -0.5f);
|
||||
glVertex2f(1.0f, 1.0f);
|
||||
glVertex2f(-0.5f, 1.0f);
|
||||
glEnd();
|
||||
|
||||
// Blue square
|
||||
glColor4f(0.0f, 0.0f, 1.0f, 0.5f); // SET ONCE
|
||||
glBegin(GL_QUADS);
|
||||
glVertex2f(0.0f, 0.0f);
|
||||
glVertex2f(1.5f, 0.0f);
|
||||
glVertex2f(1.5f, 1.5f);
|
||||
glVertex2f(0.0f, 1.5f);
|
||||
glEnd();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
wxPrintf("[GL TEST] State management test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Test 5: Texture coordinates (no actual texture, just testing the calls)
|
||||
// Note: glTexCoord is per-vertex anyway, and color is set once before glBegin
|
||||
void GLTestCanvas::TestTexCoords()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing texture coordinates...\n");
|
||||
fflush(stdout);
|
||||
|
||||
glColor3f(0.8f, 0.8f, 0.8f); // SET ONCE
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0.0f, 0.0f); glVertex2f(-1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, -1.0f);
|
||||
glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f);
|
||||
glTexCoord2f(0.0f, 1.0f); glVertex2f(-1.0f, 1.0f);
|
||||
glEnd();
|
||||
|
||||
wxPrintf("[GL TEST] Texture coordinates test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// Test 6: Normal vectors (for lighting, which we're not testing but calls should work)
|
||||
// Note: glNormal is per-vertex, and color is set once before glBegin
|
||||
void GLTestCanvas::TestNormals()
|
||||
{
|
||||
wxPrintf("[GL TEST] Testing normal vectors...\n");
|
||||
fflush(stdout);
|
||||
|
||||
glColor3f(0.7f, 0.7f, 0.9f); // SET ONCE
|
||||
glBegin(GL_TRIANGLES);
|
||||
glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
glVertex3f(-1.0f, -1.0f, 0.0f);
|
||||
glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
glVertex3f(1.0f, -1.0f, 0.0f);
|
||||
glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
glVertex3f(0.0f, 1.0f, 0.0f);
|
||||
glEnd();
|
||||
|
||||
wxPrintf("[GL TEST] Normal vectors test complete\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void GLTestCanvas::RunAllTests()
|
||||
{
|
||||
wxPrintf("[GL TEST] Running all legacy GL tests...\n");
|
||||
fflush(stdout);
|
||||
|
||||
m_currentTest = 0;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void GLTestCanvas::SetCurrentTest(int test)
|
||||
{
|
||||
m_currentTest = test;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// TestFrame - Main application frame
|
||||
//-----------------------------------------------------------------------------
|
||||
class TestFrame : public wxFrame
|
||||
|
|
@ -651,8 +156,6 @@ private:
|
|||
wxTextCtrl* m_textMulti;
|
||||
DrawingPanel* m_drawingPanel;
|
||||
wxListBox* m_listBox;
|
||||
GLTestCanvas* m_glCanvas;
|
||||
wxChoice* m_glTestChoice;
|
||||
// Grid tab controls
|
||||
wxGrid* m_grid;
|
||||
wxSpinCtrl* m_spinCtrl;
|
||||
|
|
@ -667,7 +170,6 @@ private:
|
|||
wxPanel* CreateTextPage(wxNotebook* parent);
|
||||
wxPanel* CreateDrawingPage(wxNotebook* parent);
|
||||
wxPanel* CreateListsPage(wxNotebook* parent);
|
||||
wxPanel* CreateOpenGLPage(wxNotebook* parent);
|
||||
wxPanel* CreateGridPage(wxNotebook* parent);
|
||||
wxPanel* CreateDialogsPage(wxNotebook* parent);
|
||||
|
||||
|
|
@ -688,8 +190,6 @@ private:
|
|||
void OnRemoveItem(wxCommandEvent& evt);
|
||||
void OnClearDrawing(wxCommandEvent& evt);
|
||||
void OnNotebookPageChanged(wxBookCtrlEvent& evt);
|
||||
void OnGLTestSelect(wxCommandEvent& evt);
|
||||
void OnGLRunAll(wxCommandEvent& evt);
|
||||
// Grid tab event handlers
|
||||
void OnGridCellChange(wxGridEvent& evt);
|
||||
void OnGridCellSelect(wxGridEvent& evt);
|
||||
|
|
@ -725,8 +225,6 @@ wxBEGIN_EVENT_TABLE(TestFrame, wxFrame)
|
|||
EVT_BUTTON(ID_BTN_REMOVE_ITEM, TestFrame::OnRemoveItem)
|
||||
EVT_BUTTON(ID_BTN_CLEAR, TestFrame::OnClearDrawing)
|
||||
EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, TestFrame::OnNotebookPageChanged)
|
||||
EVT_CHOICE(ID_GL_TEST_SELECT, TestFrame::OnGLTestSelect)
|
||||
EVT_BUTTON(ID_BTN_GL_RUN_ALL, TestFrame::OnGLRunAll)
|
||||
// Grid tab events
|
||||
EVT_GRID_CELL_CHANGED(TestFrame::OnGridCellChange)
|
||||
EVT_GRID_SELECT_CELL(TestFrame::OnGridCellSelect)
|
||||
|
|
@ -773,7 +271,6 @@ TestFrame::TestFrame(const wxString& title)
|
|||
m_notebook->AddPage(CreateTextPage(m_notebook), "Text Input");
|
||||
m_notebook->AddPage(CreateDrawingPage(m_notebook), "Drawing");
|
||||
m_notebook->AddPage(CreateListsPage(m_notebook), "Lists");
|
||||
m_notebook->AddPage(CreateOpenGLPage(m_notebook), "OpenGL");
|
||||
m_notebook->AddPage(CreateGridPage(m_notebook), "Grid");
|
||||
m_notebook->AddPage(CreateDialogsPage(m_notebook), "Dialogs");
|
||||
|
||||
|
|
@ -980,60 +477,6 @@ wxPanel* TestFrame::CreateListsPage(wxNotebook* parent)
|
|||
return panel;
|
||||
}
|
||||
|
||||
wxPanel* TestFrame::CreateOpenGLPage(wxNotebook* parent)
|
||||
{
|
||||
wxPanel* panel = new wxPanel(parent);
|
||||
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
// Description
|
||||
wxStaticText* desc = new wxStaticText(panel, wxID_ANY,
|
||||
"OpenGL Legacy Function Tests\n"
|
||||
"Tests the GL functions KiCad uses (immediate mode, matrix ops, vertex arrays).\n"
|
||||
"Using Emscripten's -sLEGACY_GL_EMULATION for WebGL compatibility.");
|
||||
mainSizer->Add(desc, 0, wxALL, 10);
|
||||
|
||||
// Test selection row
|
||||
wxBoxSizer* controlSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
controlSizer->Add(new wxStaticText(panel, wxID_ANY, "Test:"), 0,
|
||||
wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
wxString testChoices[] = {
|
||||
"Immediate Mode (glBegin/glEnd)",
|
||||
"Matrix Operations (glPush/Pop)",
|
||||
"Vertex Arrays (glVertexPointer)",
|
||||
"State Management (glEnable/Blend)"
|
||||
};
|
||||
m_glTestChoice = new wxChoice(panel, ID_GL_TEST_SELECT, wxDefaultPosition,
|
||||
wxSize(250, -1), 4, testChoices);
|
||||
m_glTestChoice->SetSelection(0);
|
||||
controlSizer->Add(m_glTestChoice, 0, wxALL, 5);
|
||||
|
||||
wxButton* btnRunAll = new wxButton(panel, ID_BTN_GL_RUN_ALL, "Run All Tests");
|
||||
controlSizer->Add(btnRunAll, 0, wxALL, 5);
|
||||
|
||||
mainSizer->Add(controlSizer, 0, wxEXPAND);
|
||||
|
||||
// GL Canvas
|
||||
m_glCanvas = new GLTestCanvas(panel);
|
||||
mainSizer->Add(m_glCanvas, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
// Legend/info
|
||||
wxStaticBox* legendBox = new wxStaticBox(panel, wxID_ANY, "Test Details");
|
||||
wxStaticBoxSizer* legendSizer = new wxStaticBoxSizer(legendBox, wxVERTICAL);
|
||||
|
||||
wxStaticText* legend = new wxStaticText(panel, wxID_ANY,
|
||||
"Immediate Mode: glBegin, glEnd, glVertex2f/3f, glColor3f/4f, GL_TRIANGLES/QUADS/LINES\n"
|
||||
"Matrix Ops: glMatrixMode, glPushMatrix, glPopMatrix, glTranslatef, glRotatef, glScalef\n"
|
||||
"Vertex Arrays: glEnableClientState, glVertexPointer, glColorPointer, glDrawElements\n"
|
||||
"State Mgmt: glEnable, glDisable, glBlendFunc, GL_BLEND, GL_DEPTH_TEST");
|
||||
legendSizer->Add(legend, 0, wxALL, 5);
|
||||
mainSizer->Add(legendSizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
|
||||
panel->SetSizer(mainSizer);
|
||||
return panel;
|
||||
}
|
||||
|
||||
wxPanel* TestFrame::CreateGridPage(wxNotebook* parent)
|
||||
{
|
||||
wxPanel* panel = new wxPanel(parent);
|
||||
|
|
@ -1202,29 +645,6 @@ void TestFrame::OnNotebookPageChanged(wxBookCtrlEvent& evt)
|
|||
evt.Skip();
|
||||
}
|
||||
|
||||
void TestFrame::OnGLTestSelect(wxCommandEvent& evt)
|
||||
{
|
||||
int sel = evt.GetSelection();
|
||||
wxString testNames[] = {
|
||||
"Immediate Mode",
|
||||
"Matrix Operations",
|
||||
"Vertex Arrays",
|
||||
"State Management"
|
||||
};
|
||||
|
||||
if (sel >= 0 && sel < 4) {
|
||||
LogEvent(wxString::Format("GL Test selected: %s", testNames[sel]));
|
||||
m_glCanvas->SetCurrentTest(sel);
|
||||
}
|
||||
}
|
||||
|
||||
void TestFrame::OnGLRunAll(wxCommandEvent& WXUNUSED(evt))
|
||||
{
|
||||
LogEvent("Running all GL tests...");
|
||||
m_glCanvas->RunAllTests();
|
||||
LogEvent("All GL tests completed - check console for detailed results");
|
||||
}
|
||||
|
||||
// Grid tab event handlers
|
||||
void TestFrame::OnGridCellChange(wxGridEvent& evt)
|
||||
{
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 79 KiB |
BIN
tests/baseline-screenshots/coroutine-01-loaded.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
tests/baseline-screenshots/coroutine-02-summary.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
tests/baseline-screenshots/coroutine-nested-01-loaded.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
tests/baseline-screenshots/coroutine-nested-02-summary.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
tests/baseline-screenshots/earlysize-01-loaded.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
tests/baseline-screenshots/earlysize-02-result.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
tests/baseline-screenshots/filedlg-folder-nav.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 43 KiB |
BIN
tests/baseline-screenshots/logerror-01-initial.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
tests/baseline-screenshots/logerror-02-before-error.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
tests/baseline-screenshots/logerror-03-single-error-dialog.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 75 KiB |
BIN
tests/baseline-screenshots/logerror-05-mixed-levels.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tests/baseline-screenshots/maximize-01-loaded.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
tests/baseline-screenshots/maximize-02-fullscreen.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
tests/baseline-screenshots/maximize-03-canvas.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 69 KiB |
BIN
tests/baseline-screenshots/retinascale-01-loaded.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
|
@ -1,94 +0,0 @@
|
|||
import { test, expect, MAIN_CANVAS, waitForApp, getCanvasBox } from './utils/fixtures';
|
||||
import { clickTab, clickByLabel, selectComboItem } from './utils/element-tracker';
|
||||
|
||||
async function switchToOpenGLTab(page: any) {
|
||||
// Click OpenGL tab using element registry
|
||||
const clicked = await clickTab(page, 'OpenGL');
|
||||
if (!clicked) {
|
||||
// Fallback: try clicking by label
|
||||
await clickByLabel(page, 'OpenGL');
|
||||
}
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
// Open the test dropdown and select by name
|
||||
async function selectGLTest(page: any, testName: string) {
|
||||
// Use combo helper to select from wxChoice dropdown
|
||||
await selectComboItem(page, testName);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
test.describe('OpenGL Tests', () => {
|
||||
test('Vertex Arrays test - debug freeze', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
// Switch to OpenGL tab using element registry
|
||||
await switchToOpenGLTab(page);
|
||||
await page.screenshot({ path: 'test-results/gl-01-opengl-tab.png', fullPage: true });
|
||||
|
||||
// Select "Vertex Arrays" test by name
|
||||
await selectGLTest(page, 'Vertex Arrays');
|
||||
|
||||
await page.screenshot({ path: 'test-results/gl-02-vertex-arrays-selected.png', fullPage: true });
|
||||
|
||||
// Wait a bit to see if it freezes
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gl-03-after-wait.png', fullPage: true });
|
||||
|
||||
// Check if app is still responsive
|
||||
const isResponsive = await page.evaluate(() => {
|
||||
return document.querySelector('#canvas') !== null;
|
||||
});
|
||||
expect(isResponsive).toBe(true);
|
||||
});
|
||||
|
||||
test('All GL tests individually', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
await switchToOpenGLTab(page);
|
||||
|
||||
const testNames = [
|
||||
'Immediate Mode',
|
||||
'Matrix Operations',
|
||||
'Vertex Arrays',
|
||||
'State Management'
|
||||
];
|
||||
|
||||
for (let i = 0; i < testNames.length; i++) {
|
||||
try {
|
||||
await selectGLTest(page, testNames[i]);
|
||||
await page.screenshot({
|
||||
path: `test-results/gl-test-${i}-${testNames[i].replace(/\s+/g, '-').toLowerCase()}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
} catch (e) {
|
||||
await page.screenshot({
|
||||
path: `test-results/gl-test-${i}-${testNames[i].replace(/\s+/g, '-').toLowerCase()}-error.png`,
|
||||
fullPage: true
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
test('Run All Tests button', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
await switchToOpenGLTab(page);
|
||||
|
||||
// Click "Run All Tests" button using element registry
|
||||
const clicked = await clickByLabel(page, 'Run All Tests');
|
||||
expect(clicked, 'Run All Tests button should be found and clicked').toBe(true);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.screenshot({ path: 'test-results/gl-run-all-tests.png', fullPage: true });
|
||||
|
||||
// App should remain stable
|
||||
await expect(page.locator(MAIN_CANVAS)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -212,24 +212,6 @@ test.describe('wxWidgets WASM - Diagnostics', () => {
|
|||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// === TAB 5: OpenGL ===
|
||||
// Click OpenGL tab using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1000); // Give GL time to initialize
|
||||
|
||||
await page.screenshot({ path: 'test-results/14-opengl-tab.png', fullPage: true });
|
||||
|
||||
// Click on different GL tests in the dropdown using element tracking
|
||||
const glDropdownOpened = await clickComboButton(page);
|
||||
expect(glDropdownOpened, 'Should be able to open GL test dropdown').toBe(true);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Click "Run All Tests" button using element registry
|
||||
await clickByLabel(page, 'Run All Tests');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/15-opengl-tests.png', fullPage: true });
|
||||
|
||||
// === Menu interaction ===
|
||||
// Click File menu using element registry
|
||||
await clickMenuBarItem(page, 'File');
|
||||
|
|
@ -491,265 +473,3 @@ test.describe('wxWidgets WASM - Stability', () => {
|
|||
await expect(page.locator(MAIN_CANVAS)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('wxWidgets WASM - OpenGL', () => {
|
||||
test('OpenGL tab switches successfully', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
// Click OpenGL tab using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1500); // Give GL time to initialize
|
||||
|
||||
// Check that we switched to the OpenGL tab
|
||||
const tabChanged = testLogger.consoleLogs.some(log => log.includes('Tab changed to: OpenGL'));
|
||||
expect(tabChanged).toBe(true);
|
||||
|
||||
// Save screenshot for visual verification
|
||||
await page.screenshot({ path: 'test-results/opengl-tab-initial.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('OpenGL tab interaction without crashes', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
// Switch to OpenGL tab using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Click "Run All Tests" button using element registry
|
||||
await clickByLabel(page, 'Run All Tests');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Save screenshot after running tests
|
||||
await page.screenshot({ path: 'test-results/opengl-after-tests.png', fullPage: true });
|
||||
|
||||
// App should remain stable - no crashes or critical errors
|
||||
await expect(page.locator(MAIN_CANVAS)).toBeVisible();
|
||||
|
||||
// Note: wxPrintf logs go to stdout which may not appear in browser console
|
||||
// The main verification is that the app doesn't crash
|
||||
const criticalErrors = testLogger.errors.filter(e => !isKnownWarning(e));
|
||||
expect(criticalErrors.length).toBe(0);
|
||||
});
|
||||
|
||||
test('OpenGL tab renders without errors', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
// Switch to OpenGL tab using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Click "Run All Tests" button using element registry
|
||||
await clickByLabel(page, 'Run All Tests');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Take screenshot before checking GL canvas
|
||||
await page.screenshot({ path: 'test-results/opengl-before-debug.png', fullPage: true });
|
||||
|
||||
// Debug: Check GL canvas element position and visibility
|
||||
const glCanvasInfo = await page.evaluate(() => {
|
||||
const glCanvas = document.querySelector('[id^="glcanvas-"]') as HTMLCanvasElement;
|
||||
if (!glCanvas) return { exists: false };
|
||||
const style = window.getComputedStyle(glCanvas);
|
||||
return {
|
||||
exists: true,
|
||||
id: glCanvas.id,
|
||||
display: style.display,
|
||||
visibility: style.visibility,
|
||||
pointerEvents: style.pointerEvents,
|
||||
position: style.position,
|
||||
left: style.left,
|
||||
top: style.top,
|
||||
width: style.width,
|
||||
height: style.height,
|
||||
canvasWidth: glCanvas.width,
|
||||
canvasHeight: glCanvas.height,
|
||||
zIndex: style.zIndex,
|
||||
boundingRect: glCanvas.getBoundingClientRect()
|
||||
};
|
||||
});
|
||||
|
||||
// Take screenshot of GL canvas
|
||||
const screenshot = await page.screenshot();
|
||||
expect(screenshot.length).toBeGreaterThan(0);
|
||||
|
||||
// App should still be responsive after GL rendering
|
||||
await expect(page.locator(MAIN_CANVAS)).toBeVisible();
|
||||
|
||||
// No critical JavaScript errors
|
||||
const criticalErrors = testLogger.errors.filter(e => !isKnownWarning(e));
|
||||
expect(criticalErrors.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('wxWidgets WASM - Canvas Z-Ordering and Visibility', () => {
|
||||
test('GL canvas should hide when switching away from OpenGL tab', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
// Step 1: Go to OpenGL tab first using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify we're on OpenGL tab
|
||||
const onOpenGLTab = testLogger.consoleLogs.some(log => log.includes('Tab changed to: OpenGL'));
|
||||
expect(onOpenGLTab).toBe(true);
|
||||
|
||||
await page.screenshot({ path: 'test-results/glcanvas-01-on-opengl-tab.png', fullPage: true });
|
||||
|
||||
// Check GL canvas is visible
|
||||
const glCanvasBefore = await page.evaluate(() => {
|
||||
const glCanvas = document.querySelector('[id^="glcanvas-"]') as HTMLCanvasElement;
|
||||
if (!glCanvas) return { exists: false, display: 'none' };
|
||||
return {
|
||||
exists: true,
|
||||
id: glCanvas.id,
|
||||
display: window.getComputedStyle(glCanvas).display,
|
||||
zIndex: window.getComputedStyle(glCanvas).zIndex
|
||||
};
|
||||
});
|
||||
|
||||
// Step 2: Switch to Controls tab using element registry
|
||||
await clickTab(page, 'Controls');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/glcanvas-02-after-switch-to-controls.png', fullPage: true });
|
||||
|
||||
// Check GL canvas visibility after switching tabs
|
||||
const glCanvasAfter = await page.evaluate(() => {
|
||||
const glCanvas = document.querySelector('[id^="glcanvas-"]') as HTMLCanvasElement;
|
||||
if (!glCanvas) return { exists: false, display: 'none' };
|
||||
return {
|
||||
exists: true,
|
||||
id: glCanvas.id,
|
||||
display: window.getComputedStyle(glCanvas).display,
|
||||
visibility: window.getComputedStyle(glCanvas).visibility,
|
||||
zIndex: window.getComputedStyle(glCanvas).zIndex
|
||||
};
|
||||
});
|
||||
|
||||
// Step 3: Switch to Drawing tab using element registry
|
||||
await clickTab(page, 'Drawing');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/glcanvas-03-on-drawing-tab.png', fullPage: true });
|
||||
|
||||
// Step 4: Switch to Lists tab using element registry
|
||||
await clickTab(page, 'Lists');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/glcanvas-04-on-lists-tab.png', fullPage: true });
|
||||
|
||||
// GL canvas should be hidden (display: none) when not on OpenGL tab
|
||||
if (glCanvasAfter.exists) {
|
||||
expect(glCanvasAfter.display).toBe('none');
|
||||
}
|
||||
});
|
||||
|
||||
test('dropdown should appear above GL canvas on OpenGL tab', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
const canvas = page.locator(MAIN_CANVAS);
|
||||
const box = await canvas.boundingBox();
|
||||
if (!box) throw new Error('Canvas not found');
|
||||
|
||||
// Step 1: Go to OpenGL tab using element registry
|
||||
await clickTab(page, 'OpenGL');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.screenshot({ path: 'test-results/zorder-01-opengl-tab.png', fullPage: true });
|
||||
|
||||
// Step 2: Click on the test selection dropdown (wxChoice) on the OpenGL tab using element tracking
|
||||
// Open dropdown using element tracking
|
||||
const dropdownClicked = await clickComboButton(page);
|
||||
expect(dropdownClicked, 'Should be able to click dropdown on OpenGL tab').toBe(true);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({ path: 'test-results/zorder-02-dropdown-clicked.png', fullPage: true });
|
||||
|
||||
// Check z-index values
|
||||
const zIndexInfo = await page.evaluate(() => {
|
||||
const glCanvas = document.querySelector('[id^="glcanvas-"]') as HTMLCanvasElement;
|
||||
const popupWindows = document.querySelectorAll('[id^="window-"]');
|
||||
|
||||
const result: any = {
|
||||
glCanvas: null,
|
||||
popupWindows: []
|
||||
};
|
||||
|
||||
if (glCanvas) {
|
||||
const style = window.getComputedStyle(glCanvas);
|
||||
result.glCanvas = {
|
||||
id: glCanvas.id,
|
||||
zIndex: style.zIndex,
|
||||
display: style.display
|
||||
};
|
||||
}
|
||||
|
||||
popupWindows.forEach((w) => {
|
||||
const el = w as HTMLElement;
|
||||
const style = window.getComputedStyle(el);
|
||||
if (style.display !== 'none') {
|
||||
result.popupWindows.push({
|
||||
id: el.id,
|
||||
zIndex: style.zIndex,
|
||||
display: style.display,
|
||||
width: style.width,
|
||||
height: style.height
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// Step 3: Close the dropdown using Escape
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.screenshot({ path: 'test-results/zorder-03-dropdown-closed.png', fullPage: true });
|
||||
|
||||
// After the fix, popup z-index should be higher than GL canvas z-index
|
||||
// This will be verified by visual inspection of screenshots
|
||||
});
|
||||
|
||||
test('switching tabs multiple times maintains correct visibility', async ({ page, testLogger }) => {
|
||||
await page.goto('/minimal_test.html');
|
||||
await waitForApp(page);
|
||||
|
||||
const canvas = page.locator(MAIN_CANVAS);
|
||||
const box = await canvas.boundingBox();
|
||||
if (!box) throw new Error('Canvas not found');
|
||||
|
||||
// Tab switch sequence: Controls -> OpenGL -> Lists -> OpenGL -> Drawing -> Controls
|
||||
const tabNames = ['Controls', 'OpenGL', 'Lists', 'OpenGL', 'Drawing', 'Controls'];
|
||||
|
||||
for (let i = 0; i < tabNames.length; i++) {
|
||||
const tabName = tabNames[i];
|
||||
await clickTab(page, tabName);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.screenshot({
|
||||
path: `test-results/tabswitch-${String(i + 1).padStart(2, '0')}-${tabName.toLowerCase()}.png`,
|
||||
fullPage: true
|
||||
});
|
||||
|
||||
// Check GL canvas visibility
|
||||
const glVisible = await page.evaluate(() => {
|
||||
const glCanvas = document.querySelector('[id^="glcanvas-"]') as HTMLCanvasElement;
|
||||
if (!glCanvas) return 'not-created';
|
||||
return window.getComputedStyle(glCanvas).display;
|
||||
});
|
||||
|
||||
// GL canvas should only be visible on OpenGL tab
|
||||
if (tabName === 'OpenGL') {
|
||||
expect(glVisible).toBe('block');
|
||||
} else {
|
||||
expect(glVisible).toBe('none');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,20 +23,25 @@ function findFreePort(): number {
|
|||
}
|
||||
}
|
||||
|
||||
function getOrFindPort(): number {
|
||||
try {
|
||||
const stat = fs.statSync(PORT_FILE);
|
||||
if (Date.now() - stat.mtimeMs < 60000) {
|
||||
const port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim());
|
||||
if (port > 0 && port < 65536) return port;
|
||||
}
|
||||
} catch { /* fall through */ }
|
||||
// Same port-pinning scheme as playwright-kicad.config.ts: the main runner (argv
|
||||
// contains `test`) always picks a fresh port and writes the file before workers
|
||||
// spawn; workers — including ones recreated mid-run after a failure — always
|
||||
// reuse it. No freshness window, so a recreated worker can never rotate to a
|
||||
// dead port (ERR_CONNECTION_REFUSED cascade).
|
||||
function resolvePort(): number {
|
||||
const isMainRunner = process.argv.slice(2).includes('test');
|
||||
if (!isMainRunner) {
|
||||
try {
|
||||
const existing = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim(), 10);
|
||||
if (existing > 0 && existing < 65536) return existing;
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
const port = findFreePort();
|
||||
fs.writeFileSync(PORT_FILE, port.toString());
|
||||
return port;
|
||||
}
|
||||
|
||||
const port = getOrFindPort();
|
||||
const port = resolvePort();
|
||||
|
||||
export default defineConfig({
|
||||
globalSetup: './global-setup.ts',
|
||||
|
|
|
|||
|
|
@ -5,24 +5,41 @@ import * as path from 'path';
|
|||
|
||||
const PORT_FILE = path.join(__dirname, '.test-port');
|
||||
|
||||
// Get existing port from file or find a new one
|
||||
// This ensures all workers use the same port
|
||||
function getOrFindPort(): number {
|
||||
// Check if port file exists and is recent (created in last 60 seconds)
|
||||
try {
|
||||
const stat = fs.statSync(PORT_FILE);
|
||||
const age = Date.now() - stat.mtimeMs;
|
||||
if (age < 60000) {
|
||||
const port = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim());
|
||||
if (port > 0 && port < 65536) {
|
||||
return port;
|
||||
// Resolve the static-server port for this run.
|
||||
//
|
||||
// This config file is re-imported by EVERY Playwright process: the main runner
|
||||
// (which launches the webServer) and each worker process (which calls
|
||||
// page.goto(baseURL)). They must all agree on one port. Playwright also
|
||||
// *recreates* a worker mid-run after a test times out or crashes — and that new
|
||||
// worker re-imports this config.
|
||||
//
|
||||
// The previous heuristic ("reuse .test-port if it's <60s old, else pick a new
|
||||
// free port") broke exactly there: once a run passed the 60s mark, a recreated
|
||||
// worker treated the file as stale, picked a DIFFERENT free port, and every
|
||||
// subsequent page.goto hit a dead port (net::ERR_CONNECTION_REFUSED) because the
|
||||
// webServer was still listening on the original port. A single failing test
|
||||
// thus cascaded into ~all later tests failing.
|
||||
//
|
||||
// Fix (same as playwright-kicad.config.ts): drop the time window entirely. The
|
||||
// main runner always picks a fresh port and writes it; workers always reuse
|
||||
// whatever the main runner wrote. The main runner is the only process whose
|
||||
// argv carries the `test` command (workers are forked with an empty argv), and
|
||||
// it imports this config — and so writes the file — before any worker is
|
||||
// spawned.
|
||||
function resolvePort(): number {
|
||||
const isMainRunner = process.argv.slice(2).includes('test');
|
||||
if (!isMainRunner) {
|
||||
try {
|
||||
const existing = parseInt(fs.readFileSync(PORT_FILE, 'utf-8').trim(), 10);
|
||||
if (existing > 0 && existing < 65536) {
|
||||
return existing;
|
||||
}
|
||||
} catch {
|
||||
// No readable port file — fall through. Shouldn't happen in a worker,
|
||||
// since the main runner writes the file before spawning workers.
|
||||
}
|
||||
} catch {
|
||||
// File doesn't exist or can't be read
|
||||
}
|
||||
|
||||
// Find a new free port
|
||||
const port = findFreePort();
|
||||
fs.writeFileSync(PORT_FILE, port.toString());
|
||||
return port;
|
||||
|
|
@ -43,7 +60,7 @@ function findFreePort(): number {
|
|||
}
|
||||
}
|
||||
|
||||
const port = getOrFindPort();
|
||||
const port = resolvePort();
|
||||
|
||||
export default defineConfig({
|
||||
globalSetup: './global-setup.ts',
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit ea599f7f82bd0de597db2b4665802ae3fba02083
|
||||
Subproject commit 765f469f9ea358c370f4db43156b689aa65ce7b1
|
||||