Add Playwright test infrastructure for wxWidgets WASM port

- Add tests/ directory with Playwright e2e tests
- Add minimal wxWidgets WASM test application
- Add build script for test app (scripts/build-wasm-test.sh)
- Update wxWidgets submodule with WASM build fixes
- Update .gitignore for test artifacts

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Viktor Vaczi 2025-11-28 13:10:05 +01:00
commit 3f67fed173
10 changed files with 1506 additions and 2 deletions

10
.gitignore vendored
View file

@ -29,4 +29,12 @@ cmake-build-*/
.emscripten_cache/ .emscripten_cache/
# Clean clone for reproducible builds (generated by build-wxwidgets-wasm-clean.sh) # Clean clone for reproducible builds (generated by build-wxwidgets-wasm-clean.sh)
wxwidgets-clean/ wxwidgets-clean/
# Test artifacts
/tests/node_modules/
/tests/playwright-report/
/tests/test-results/
/tests/wasm-app/*.js
/tests/wasm-app/*.wasm
/temp/

58
scripts/build-wasm-test.sh Executable file
View file

@ -0,0 +1,58 @@
#!/bin/bash
# Build the minimal wxWidgets WASM test application
# This script creates library symlinks and builds the test app
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="$PROJECT_ROOT/build-wasm/wxwidgets-universal"
TESTS_DIR="$PROJECT_ROOT/tests"
WASM_APP_DIR="$TESTS_DIR/wasm-app"
echo "=== Building wxWidgets WASM Test Application ==="
echo "Project root: $PROJECT_ROOT"
echo "wxWidgets build: $BUILD_DIR"
echo "Test app dir: $WASM_APP_DIR"
# Verify wxWidgets is built
if [ ! -f "$BUILD_DIR/wx-config" ]; then
echo "ERROR: wxWidgets not built. Run build-wxuniversal-wasm.sh first"
exit 1
fi
# Create library symlinks
# wx-config outputs paths like libwx_baseu-3.2.a but files are named libwx_baseu-3.2-emscripten.a
echo ""
echo "=== Creating library symlinks ==="
cd "$BUILD_DIR/lib"
for f in libwx_*-emscripten.a; do
if [ -f "$f" ]; then
link="${f%-emscripten.a}.a"
if [ ! -e "$link" ]; then
ln -s "$f" "$link"
echo "Created: $link -> $f"
fi
fi
done
# Build the test app
echo ""
echo "=== Building test application ==="
cd "$WASM_APP_DIR"
# Clean any previous build
make -f Makefile.wasm clean 2>/dev/null || true
# Build
make -f Makefile.wasm
echo ""
echo "=== Build complete ==="
ls -lh "$WASM_APP_DIR"/minimal_test.* 2>/dev/null
echo ""
echo "To run the tests:"
echo " cd $TESTS_DIR"
echo " npm install"
echo " npm test"

43
tests/README.md Normal file
View file

@ -0,0 +1,43 @@
# KiCad WASM Tests
Playwright tests for verifying the wxWidgets WASM port.
## Prerequisites
- Node.js 18+
- Emscripten SDK (for building)
## Building the Test App
```bash
../scripts/build-wasm-test.sh
```
This builds `wasm-app/minimal_test.{html,js,wasm}`.
## Running Tests
```bash
npm install
npm test
```
## Viewing the App Directly
Start a local server in the wasm-app directory:
```bash
cd wasm-app
npx serve .
```
Then open http://localhost:3000/minimal_test.html in your browser.
Alternative using Python:
```bash
cd wasm-app
python3 -m http.server 8000
```
Then open http://localhost:8000/minimal_test.html

View file

@ -0,0 +1,70 @@
import { test, expect } from '@playwright/test';
test.describe('wxWidgets WASM', () => {
test('app loads without JavaScript errors', async ({ page }) => {
const errors: string[] = [];
page.on('pageerror', err => errors.push(err.message));
page.on('console', msg => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
await page.goto('/minimal_test.html');
// Wait for WASM to initialize (canvas becomes visible)
await page.waitForSelector('canvas', { state: 'visible', timeout: 30000 });
// Give it a moment to settle
await page.waitForTimeout(1000);
// Filter out known non-critical errors
const criticalErrors = errors.filter(e =>
!e.includes('SharedArrayBuffer') && // COOP/COEP warning
!e.includes('cross-origin')
);
expect(criticalErrors).toHaveLength(0);
});
test('canvas element is rendered with dimensions', async ({ page }) => {
await page.goto('/minimal_test.html');
const canvas = page.locator('canvas');
await expect(canvas).toBeVisible({ timeout: 30000 });
// Canvas should have non-zero dimensions
const box = await canvas.boundingBox();
expect(box).not.toBeNull();
expect(box?.width).toBeGreaterThan(0);
expect(box?.height).toBeGreaterThan(0);
});
test('loading progress completes', async ({ page }) => {
await page.goto('/minimal_test.html');
// Progress text should eventually show completion or disappear
await page.waitForFunction(() => {
const status = document.getElementById('progress-text');
if (!status) return true;
const text = status.textContent || '';
return text.toLowerCase().includes('complete') ||
text === '' ||
status.style.display === 'none';
}, { timeout: 30000 });
});
test('WASM module initializes successfully', async ({ page }) => {
await page.goto('/minimal_test.html');
// Wait for canvas to be visible (indicates WASM loaded)
await page.waitForSelector('canvas', { state: 'visible', timeout: 30000 });
// Check that the Module object exists and is initialized
const moduleExists = await page.evaluate(() => {
return typeof (window as any).Module !== 'undefined';
});
expect(moduleExists).toBe(true);
});
});

1124
tests/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

16
tests/package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "kicad-wasm-tests",
"version": "1.0.0",
"description": "Playwright tests for wxWidgets WASM port",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"build-wasm": "cd wasm-app && make -f Makefile.wasm",
"serve": "npx serve wasm-app -p 8080"
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"serve": "^14.2.0"
}
}

View file

@ -0,0 +1,29 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
timeout: 60000, // WASM can be slow to load
use: {
baseURL: 'http://localhost:8080',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npx serve wasm-app -p 8080',
port: 8080,
reuseExistingServer: !process.env.CI,
},
});

View file

@ -0,0 +1,42 @@
// Minimal wxWidgets WASM Test Application
// Purpose: Verify wxWidgets WASM port is working correctly
#include "wx/wxprec.h"
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
class TestApp : public wxApp
{
public:
virtual bool OnInit() wxOVERRIDE;
};
class TestFrame : public wxFrame
{
public:
TestFrame(const wxString& title);
};
wxIMPLEMENT_APP(TestApp);
bool TestApp::OnInit()
{
if (!wxApp::OnInit())
return false;
TestFrame *frame = new TestFrame("wxWidgets WASM Test");
frame->Show(true);
return true;
}
TestFrame::TestFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(640, 480))
{
#if wxUSE_STATUSBAR
CreateStatusBar();
SetStatusText("wxWidgets WASM is working!");
#endif
}

View file

@ -0,0 +1,114 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title></title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; }
.window {
position: absolute;
pointer-events: none;
z-index: 10;
background-color: black;
overflow: hidden;
width: 0;
height: 0;
}
.window-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
</style>
</head>
<body style="margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden">
<div id="main-window"></div>
<div id="progress-container" class="centered">
<div id="progress-text">Loading...</div>
<div id="progress-bar">
<div id="progress-bar-position">
</div>
</div>
</div>
<div id="window-container"></div>
<script>
var mainWindow = document.getElementById('main-window');
var statusElement = document.getElementById('progress-text');
var progressElement = document.getElementById('progress-bar-position');
var createCanvas = function () {
var canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvas.style.display = 'none';
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
canvas.oncontextmenu = function () { event.preventDefault(); }
canvas.addEventListener("webglcontextlost", function (e) {
showError('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
mainWindow.appendChild(canvas);
Module.canvas = canvas;
};
var onRuntimeInitialized = function () {
var canvas = Module.canvas;
canvas.style.display = 'block';
};
var Module = {
preRun: [createCanvas],
postRun: [],
print: function(text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.log(text);
},
printErr: function(text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(' ');
console.error(text);
},
setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
if (m) {
text = m[1];
progressElement.style.width = m[2] / m[4] + '%';
progressElement.hidden = false;
} else {
progressElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
},
onRuntimeInitialized: onRuntimeInitialized
};
Module.setStatus('Downloading...');
window.onerror = function() {
showError('Exception thrown, see JavaScript console');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
<script async type="text/javascript" src="minimal_test.js"></script>
</body>
</html>

@ -1 +1 @@
Subproject commit 4ab6d13cf0ae7469809236b24b81d658738a4ffa Subproject commit 5ca6237800627c7d5e49a60ac2317ac71fa1e5e3