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>
This commit is contained in:
Viktor Vaczi 2026-06-10 13:40:00 +02:00
commit 7629ab978d
53 changed files with 119 additions and 1060 deletions

View file

@ -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) |

View file

@ -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 |

View file

@ -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

View file

@ -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)
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View file

@ -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();
});
});

View file

@ -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');
}
}
});
});

View file

@ -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',

View file

@ -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