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:
parent
ad5c4f6772
commit
1ce4fa91e2
4 changed files with 389 additions and 3 deletions
162
tests/e2e/logerror.spec.ts
Normal file
162
tests/e2e/logerror.spec.ts
Normal 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();
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
215
tests/wasm-app/standalone/logerror/logerror_test.cpp
Normal file
215
tests/wasm-app/standalone/logerror/logerror_test.cpp
Normal 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
|
||||
Loading…
Reference in a new issue