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:
parent
0ac29130ca
commit
3f67fed173
10 changed files with 1506 additions and 2 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -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
58
scripts/build-wasm-test.sh
Executable 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
43
tests/README.md
Normal 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
|
||||||
70
tests/e2e/wxwidgets.spec.ts
Normal file
70
tests/e2e/wxwidgets.spec.ts
Normal 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
1124
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
16
tests/package.json
Normal file
16
tests/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tests/playwright.config.ts
Normal file
29
tests/playwright.config.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
42
tests/wasm-app/minimal_test.cpp
Normal file
42
tests/wasm-app/minimal_test.cpp
Normal 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
|
||||||
|
}
|
||||||
114
tests/wasm-app/minimal_test.html
Normal file
114
tests/wasm-app/minimal_test.html
Normal 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
|
||||||
Loading…
Reference in a new issue