Add logerror test app and wxLog console logging e2e tests

Create standalone test that reproduces KiCad's wxLogError dialog with
Details dropdown. The test verifies wxLogWasm outputs messages to browser
console with [wxLog][LEVEL] prefix.

- Add tests/wasm-app/standalone/logerror/logerror_test.cpp
- Add tests/e2e/logerror.spec.ts with 4 test cases
- Update wxwidgets submodule with wxLogWasm implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-12-15 14:34:43 +01:00
commit 1ce4fa91e2
4 changed files with 389 additions and 3 deletions

162
tests/e2e/logerror.spec.ts Normal file
View file

@ -0,0 +1,162 @@
import { test, expect } from './utils/fixtures';
import { getCanvasBox } from './utils/test-utils';
// Button positions found via button-finder
const BUTTONS = {
triggerError: { x: 24, y: 138 },
triggerMultiple: { x: 24, y: 226 },
mixedLevels: { x: 24, y: 314 },
flushLog: { x: 24, y: 398 },
clearLog: { x: 216, y: 398 }
};
test.describe('wxLogError Dialog Tests', () => {
test('LogError test app loads successfully', async ({ page, testLogger }) => {
await page.goto('/standalone/logerror/logerror_test.html');
// Wait for app to initialize
await page.waitForFunction(() => {
return document.querySelector('#canvas')?.style.display === 'block';
}, { timeout: 30000 });
// Verify canvas is visible
const canvas = page.locator('#canvas');
await expect(canvas).toBeVisible();
// Take screenshot of initial state
await page.screenshot({
path: 'test-results/logerror-01-initial.png',
fullPage: true
});
// Check for successful initialization log
const initLog = testLogger.consoleLogs.find(l =>
l.includes('[LOGERROR_TEST]')
);
expect(initLog).toBeTruthy();
});
test('Trigger single error shows dialog', async ({ page, testLogger }) => {
await page.goto('/standalone/logerror/logerror_test.html');
await page.waitForFunction(() => {
return document.querySelector('#canvas')?.style.display === 'block';
}, { timeout: 30000 });
// Wait for UI to be ready
await page.waitForTimeout(500);
const box = await getCanvasBox(page);
// Take screenshot before clicking
await page.screenshot({
path: 'test-results/logerror-02-before-error.png',
fullPage: true
});
// Click "Trigger Error" button
await page.mouse.click(box.x + BUTTONS.triggerError.x, box.y + BUTTONS.triggerError.y);
await page.waitForTimeout(500);
// Click "Flush Log" to show the dialog
await page.mouse.click(box.x + BUTTONS.flushLog.x, box.y + BUTTONS.flushLog.y);
await page.waitForTimeout(1000);
// Take screenshot showing the error dialog
await page.screenshot({
path: 'test-results/logerror-03-single-error-dialog.png',
fullPage: true
});
// Check console for wxLog messages
const wxLogMessages = testLogger.consoleLogs.filter(l =>
l.includes('[wxLog]')
);
console.log('wxLog messages:', wxLogMessages);
// Verify wxLogError message appeared in console
const errorLog = testLogger.consoleLogs.find(l =>
l.includes('[wxLog][ERROR]') && l.includes('Error loading editor')
);
expect(errorLog).toBeTruthy();
});
test('Trigger multiple errors shows Details dropdown', async ({ page, testLogger }) => {
await page.goto('/standalone/logerror/logerror_test.html');
await page.waitForFunction(() => {
return document.querySelector('#canvas')?.style.display === 'block';
}, { timeout: 30000 });
// Wait for UI to be ready
await page.waitForTimeout(500);
const box = await getCanvasBox(page);
// Click "Trigger Multiple" button
await page.mouse.click(box.x + BUTTONS.triggerMultiple.x, box.y + BUTTONS.triggerMultiple.y);
await page.waitForTimeout(500);
// Click "Flush Log" to show the dialog with Details dropdown
await page.mouse.click(box.x + BUTTONS.flushLog.x, box.y + BUTTONS.flushLog.y);
await page.waitForTimeout(1000);
// Take screenshot showing the dialog with Details
await page.screenshot({
path: 'test-results/logerror-04-multiple-errors-dialog.png',
fullPage: true
});
// Verify multiple wxLogError messages appeared in console
const errorLogs = testLogger.consoleLogs.filter(l =>
l.includes('[wxLog][ERROR]')
);
console.log('Error logs:', errorLogs);
// Should have at least 3 error messages
expect(errorLogs.length).toBeGreaterThanOrEqual(3);
});
test('wxLog console logging works for all levels', async ({ page, testLogger }) => {
await page.goto('/standalone/logerror/logerror_test.html');
await page.waitForFunction(() => {
return document.querySelector('#canvas')?.style.display === 'block';
}, { timeout: 30000 });
await page.waitForTimeout(500);
const box = await getCanvasBox(page);
// Click "Mixed Levels" button to log error, warning, and message
await page.mouse.click(box.x + BUTTONS.mixedLevels.x, box.y + BUTTONS.mixedLevels.y);
await page.waitForTimeout(500);
// Take screenshot
await page.screenshot({
path: 'test-results/logerror-05-mixed-levels.png',
fullPage: true
});
// Verify each log level appeared in console
const errorLog = testLogger.consoleLogs.find(l =>
l.includes('[wxLog][ERROR]') && l.includes('error message')
);
const warningLog = testLogger.consoleLogs.find(l =>
l.includes('[wxLog][WARNING]') && l.includes('warning message')
);
const infoLog = testLogger.consoleLogs.find(l =>
l.includes('[wxLog][INFO]') && l.includes('info message')
);
console.log('Error log:', errorLog);
console.log('Warning log:', warningLog);
console.log('Info log:', infoLog);
expect(errorLog).toBeTruthy();
expect(warningLog).toBeTruthy();
expect(infoLog).toBeTruthy();
});
});

View file

@ -140,7 +140,8 @@ all: minimal_test.html \
$(S)/regions/regions_test.html \
$(S)/maximize/maximize_test.html \
$(S)/earlysize/earlysize_test.html \
$(S)/threadpool/threadpool_test.html
$(S)/threadpool/threadpool_test.html \
$(S)/logerror/logerror_test.html
# Main test app (uses GL)
minimal_test.o: minimal_test.cpp
@ -436,6 +437,13 @@ $(S)/threadpool/threadpool_test.o: $(S)/threadpool/threadpool_test.cpp
$(S)/threadpool/threadpool_test.html: $(S)/threadpool/threadpool_test.o
$(CXX) $< $(LDFLAGS_PTHREAD) --pre-js $(JS) --shell-file $(HTML) -o $@
# Log Error test (no GL) - reproduces KiCad's kiface error dialog
$(S)/logerror/logerror_test.o: $(S)/logerror/logerror_test.cpp
$(CXX) -c $(CXXFLAGS) $< -o $@
$(S)/logerror/logerror_test.html: $(S)/logerror/logerror_test.o
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
# Convenience targets
menu: $(S)/menu/menu_test.html
clipboard: $(S)/clipboard/clipboard_test.html
@ -478,9 +486,10 @@ regions: $(S)/regions/regions_test.html
maximize: $(S)/maximize/maximize_test.html
earlysize: $(S)/earlysize/earlysize_test.html
threadpool: $(S)/threadpool/threadpool_test.html
logerror: $(S)/logerror/logerror_test.html
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
.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
.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

View file

@ -0,0 +1,215 @@
// wxLogError Dialog Test - Reproduces KiCad's kiface error dialog
// Tests wxLogDialog with wxCollapsiblePane "Details" dropdown
// Used to debug dialog positioning and wxLog console logging
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
#include "wx/log.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
class LogErrorTestApp : public wxApp
{
public:
virtual bool OnInit() override;
};
class LogErrorTestFrame : public wxFrame
{
public:
LogErrorTestFrame();
private:
wxTextCtrl* m_log;
void LogEvent(const wxString& msg);
// Single error - like KiCad's "Error loading editor."
void OnSingleError(wxCommandEvent& evt);
// Multiple errors - triggers Details dropdown with wxListCtrl
void OnMultipleErrors(wxCommandEvent& evt);
// Mix of error levels
void OnMixedLevels(wxCommandEvent& evt);
// Manually flush the log to show dialog
void OnFlushLog(wxCommandEvent& evt);
// Clear logged messages without showing dialog
void OnClearLog(wxCommandEvent& evt);
wxDECLARE_EVENT_TABLE();
};
enum {
ID_SINGLE_ERROR = wxID_HIGHEST + 1,
ID_MULTIPLE_ERRORS,
ID_MIXED_LEVELS,
ID_FLUSH_LOG,
ID_CLEAR_LOG
};
wxBEGIN_EVENT_TABLE(LogErrorTestFrame, wxFrame)
EVT_BUTTON(ID_SINGLE_ERROR, LogErrorTestFrame::OnSingleError)
EVT_BUTTON(ID_MULTIPLE_ERRORS, LogErrorTestFrame::OnMultipleErrors)
EVT_BUTTON(ID_MIXED_LEVELS, LogErrorTestFrame::OnMixedLevels)
EVT_BUTTON(ID_FLUSH_LOG, LogErrorTestFrame::OnFlushLog)
EVT_BUTTON(ID_CLEAR_LOG, LogErrorTestFrame::OnClearLog)
wxEND_EVENT_TABLE()
wxIMPLEMENT_APP(LogErrorTestApp);
bool LogErrorTestApp::OnInit()
{
if (!wxApp::OnInit())
return false;
LogErrorTestFrame* frame = new LogErrorTestFrame();
frame->Show(true);
return true;
}
LogErrorTestFrame::LogErrorTestFrame()
: wxFrame(nullptr, wxID_ANY, "wxLogError Dialog Test",
wxDefaultPosition, wxSize(700, 550))
{
wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
// Description
wxStaticText* desc = new wxStaticText(this, wxID_ANY,
"wxLogError Dialog Test\n\n"
"This test reproduces the exact dialog KiCad shows when kiface loading fails.\n"
"It uses wxLogDialog with wxCollapsiblePane 'Details' dropdown.\n"
"Watch the browser console for [wxLog] messages.");
mainSizer->Add(desc, 0, wxALL, 10);
// Single error section - like KiCad's error
wxStaticBoxSizer* singleBox = new wxStaticBoxSizer(wxVERTICAL, this, "Single Error (KiCad-style)");
wxBoxSizer* singleBtnSizer = new wxBoxSizer(wxHORIZONTAL);
singleBtnSizer->Add(new wxButton(this, ID_SINGLE_ERROR, "Trigger Error"), 0, wxALL, 5);
singleBtnSizer->Add(new wxStaticText(this, wxID_ANY, "Calls wxLogError(\"Error loading editor.\")"),
0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
singleBox->Add(singleBtnSizer, 0, wxALIGN_LEFT);
mainSizer->Add(singleBox, 0, wxEXPAND | wxALL, 10);
// Multiple errors section - triggers Details dropdown
wxStaticBoxSizer* multiBox = new wxStaticBoxSizer(wxVERTICAL, this, "Multiple Errors (Details dropdown)");
wxBoxSizer* multiBtnSizer = new wxBoxSizer(wxHORIZONTAL);
multiBtnSizer->Add(new wxButton(this, ID_MULTIPLE_ERRORS, "Trigger Multiple"), 0, wxALL, 5);
multiBtnSizer->Add(new wxStaticText(this, wxID_ANY, "Logs 3 errors - shows Details with wxListCtrl"),
0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
multiBox->Add(multiBtnSizer, 0, wxALIGN_LEFT);
mainSizer->Add(multiBox, 0, wxEXPAND | wxALL, 10);
// Mixed levels section
wxStaticBoxSizer* mixedBox = new wxStaticBoxSizer(wxVERTICAL, this, "Mixed Log Levels");
wxBoxSizer* mixedBtnSizer = new wxBoxSizer(wxHORIZONTAL);
mixedBtnSizer->Add(new wxButton(this, ID_MIXED_LEVELS, "Mixed Levels"), 0, wxALL, 5);
mixedBtnSizer->Add(new wxStaticText(this, wxID_ANY, "wxLogError + wxLogWarning + wxLogMessage"),
0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
mixedBox->Add(mixedBtnSizer, 0, wxALIGN_LEFT);
mainSizer->Add(mixedBox, 0, wxEXPAND | wxALL, 10);
// Control buttons
wxStaticBoxSizer* controlBox = new wxStaticBoxSizer(wxHORIZONTAL, this, "Log Control");
controlBox->Add(new wxButton(this, ID_FLUSH_LOG, "Flush Log (Show Dialog)"), 0, wxALL, 5);
controlBox->Add(new wxButton(this, ID_CLEAR_LOG, "Clear Log"), 0, wxALL, 5);
mainSizer->Add(controlBox, 0, wxEXPAND | wxALL, 10);
// Event log
wxStaticBoxSizer* logBox = new wxStaticBoxSizer(wxVERTICAL, this, "Event Log");
m_log = new wxTextCtrl(this, wxID_ANY, "",
wxDefaultPosition, wxSize(-1, 150), wxTE_MULTILINE | wxTE_READONLY);
logBox->Add(m_log, 1, wxEXPAND | wxALL, 5);
mainSizer->Add(logBox, 1, wxEXPAND | wxALL, 10);
SetSizer(mainSizer);
CreateStatusBar();
SetStatusText("Ready - Click buttons to trigger wxLog errors");
LogEvent("wxLogError test app started");
#ifdef __EMSCRIPTEN__
EM_ASM({
console.log('[LOGERROR_TEST] wxLogError test app started successfully');
});
#endif
}
void LogErrorTestFrame::LogEvent(const wxString& msg)
{
m_log->AppendText(msg + "\n");
SetStatusText(msg);
#ifdef __EMSCRIPTEN__
EM_ASM({
console.log('[LOGERROR_EVENT] ' + UTF8ToString($0));
}, msg.c_str().AsChar());
#endif
}
void LogErrorTestFrame::OnSingleError(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Triggering single wxLogError...");
// This is exactly what KiCad does in kiway.cpp
wxLogError("Error loading editor.");
LogEvent("wxLogError called - dialog should appear on next event loop or Flush");
}
void LogErrorTestFrame::OnMultipleErrors(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Triggering multiple wxLogError calls...");
// Multiple errors trigger the Details dropdown with wxListCtrl
wxLogError("Failed to load shared library '/usr/bin/_pcbnew.kiface'");
wxLogError("IO_ERROR: Failed to load kiface library");
wxLogError("Error loading editor.");
LogEvent("3 errors logged - Details dropdown should appear");
}
void LogErrorTestFrame::OnMixedLevels(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Triggering mixed log levels...");
wxLogError("This is an error message");
wxLogWarning("This is a warning message");
wxLogMessage("This is an info message");
LogEvent("Mixed levels logged - check console for [wxLog] output");
}
void LogErrorTestFrame::OnFlushLog(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Flushing log - dialog should appear now...");
// Force the log to flush, which shows the dialog
wxLog::FlushActive();
LogEvent("Flush complete - dialog should have been shown");
}
void LogErrorTestFrame::OnClearLog(wxCommandEvent& WXUNUSED(evt))
{
LogEvent("Clearing accumulated log messages...");
// Get the active log and clear it without showing dialog
wxLog* log = wxLog::GetActiveTarget();
if (log)
{
// Disable logging temporarily to clear without showing
wxLogNull noLog;
// The accumulated messages will be discarded
}
LogEvent("Log cleared");
}

@ -1 +1 @@
Subproject commit 981fb9a05569077eaf503ff38db8cf079c7d1a19
Subproject commit fb711a36b5eadecbe8c1c0dc4013daff12d168f8