dogfooding light push v3

This commit is contained in:
Arseniy Klempner 2025-06-19 15:16:21 -07:00
parent a1b991b1ed
commit a7710da542
No known key found for this signature in database
GPG Key ID: 51653F18863BD24B
10 changed files with 731 additions and 50 deletions

View File

@ -50,6 +50,11 @@ jobs:
fi
working-directory: "examples/${{ matrix.example }}"
- name: Install Playwright browsers
run: npx playwright install --with-deps
working-directory: "examples/${{ matrix.example }}"
if: matrix.example == 'dogfooding'
- name: test
run: npm run test --if-present
working-directory: "examples/${{ matrix.example }}"

13
examples/dogfooding/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
# Playwright
playwright-report/
test-results/
playwright/.cache/
# Build output
build/
# Dependencies
node_modules/
# OS files
.DS_Store

View File

@ -10,18 +10,20 @@
"dependencies": {
"@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.32-4997440.0",
"@waku/sdk": "0.0.32-b0a2e39.0",
"libp2p": "^2.1.10",
"protobufjs": "^7.3.0",
"uint8arrays": "^5.1.0"
},
"devDependencies": {
"@libp2p/interface": "^2.1.3",
"@playwright/test": "^1.53.1",
"@types/node": "^20.12.11",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"html-webpack-plugin": "^5.6.3",
"playwright": "^1.53.1",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"webpack": "^5.74.0",
@ -783,6 +785,22 @@
"node": ">=12.4.0"
}
},
"node_modules/@playwright/test": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz",
"integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.53.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@ -1315,16 +1333,16 @@
]
},
"node_modules/@waku/discovery": {
"version": "0.0.9-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.9-4997440.0.tgz",
"integrity": "sha512-cNJking/6FgHdl0e9x3MC/j7P7jBRn7rF1qU3PPvJ5EiugPcLyrxFRVZocZj4EQzYar7wY909MqNnlA+tGwCaQ==",
"version": "0.0.9-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/discovery/-/discovery-0.0.9-b0a2e39.0.tgz",
"integrity": "sha512-4dmWAWX16fWecn7xI2WtbuQgoiuHiExF8ch7puv1pTrlLlbrtLm2miWbvgyraacBLUpn12UXUQOuZaM7lMGzGQ==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@waku/core": "0.0.36-4997440.0",
"@waku/enr": "0.0.30-4997440.0",
"@waku/interfaces": "0.0.31-4997440.0",
"@waku/proto": "0.0.11-4997440.0",
"@waku/utils": "0.0.24-4997440.0",
"@waku/core": "0.0.36-b0a2e39.0",
"@waku/enr": "0.0.30-b0a2e39.0",
"@waku/interfaces": "0.0.31-b0a2e39.0",
"@waku/proto": "0.0.11-b0a2e39.0",
"@waku/utils": "0.0.24-b0a2e39.0",
"debug": "^4.3.4",
"dns-over-http-resolver": "^3.0.8",
"hi-base32": "^0.5.1",
@ -1335,17 +1353,17 @@
}
},
"node_modules/@waku/discovery/node_modules/@waku/core": {
"version": "0.0.36-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-4997440.0.tgz",
"integrity": "sha512-1Z8bKLL8fFU7DeeAw+7iDazeNOayudnOSHIT4sP0pxnEe2kpLAaF5C5E0TeRj4Jk43f8xzTKmCMXXwN8/IbFgw==",
"version": "0.0.36-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-b0a2e39.0.tgz",
"integrity": "sha512-c3cXUE45Q5SdwJ9RZXKGhl0cQwu8zkK3GMAfhNoRqMl/qLhRo+Y1u5m45KbG8HasH4Pjpibw8DkMmEjSd0OdRA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@libp2p/ping": "2.0.1",
"@noble/hashes": "^1.3.2",
"@waku/enr": "0.0.30-4997440.0",
"@waku/interfaces": "0.0.31-4997440.0",
"@waku/proto": "0.0.11-4997440.0",
"@waku/utils": "0.0.24-4997440.0",
"@waku/enr": "0.0.30-b0a2e39.0",
"@waku/interfaces": "0.0.31-b0a2e39.0",
"@waku/proto": "0.0.11-b0a2e39.0",
"@waku/utils": "0.0.24-b0a2e39.0",
"debug": "^4.3.4",
"it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4",
@ -1419,9 +1437,9 @@
}
},
"node_modules/@waku/enr": {
"version": "0.0.30-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.30-4997440.0.tgz",
"integrity": "sha512-LHSJhCFCgEpHuoRWndUlE3JXXKUnHwokL+Av8zSvJPQTbpmSiH/RtFchk4w58muVGQNNzevGkXcNNEZ6/LhtZg==",
"version": "0.0.30-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/enr/-/enr-0.0.30-b0a2e39.0.tgz",
"integrity": "sha512-6/Uij9zErjUXDUGvmpdMDCCTWngJXkagbGxvNoij0VOfCe6M5wul/VV6qmlepZTbkoTKcv91k3stdrqv1jXaiw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@ethersproject/rlp": "^5.7.0",
@ -1429,7 +1447,7 @@
"@libp2p/peer-id": "^5.0.1",
"@multiformats/multiaddr": "^12.0.0",
"@noble/secp256k1": "^1.7.1",
"@waku/utils": "0.0.24-4997440.0",
"@waku/utils": "0.0.24-b0a2e39.0",
"debug": "^4.3.4",
"js-sha3": "^0.9.2"
},
@ -1446,18 +1464,18 @@
}
},
"node_modules/@waku/interfaces": {
"version": "0.0.31-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.31-4997440.0.tgz",
"integrity": "sha512-dV0o08C+NDCPTV1xzw6nfikC+1GnBszcY3/Vf2qD6RiZZxNygt9de0N1/c1xgEdGx1eVSrk/XZg8ImNsSl5qLg==",
"version": "0.0.31-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.31-b0a2e39.0.tgz",
"integrity": "sha512-VXTnq+NA5qLPbwS7nB8hRQjN6D4VY+SUc6eYx5SB2d1MpgBkAyHFD9tKM12yHiIm5N2XFouj5eYzngaMguHF1A==",
"license": "MIT OR Apache-2.0",
"engines": {
"node": ">=20"
}
},
"node_modules/@waku/proto": {
"version": "0.0.11-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.11-4997440.0.tgz",
"integrity": "sha512-DkFrWxzryt25mBmeRXwu/h3sR4yMGxRCujDc3fswUW8JssKdCu81QBcOaX6XBy6sda8D8lDdN0399MVY5g3Yyw==",
"version": "0.0.11-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/proto/-/proto-0.0.11-b0a2e39.0.tgz",
"integrity": "sha512-SQcL5rjSpTUy+1xSjHtq0GLgPd9NGTwCpZRRVu5K7GeN5tVZ5lyVtdXnO7MPVaWW/LYzbc7Fb2QtNxroi8sJ7w==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"protons-runtime": "^5.4.0"
@ -1467,9 +1485,9 @@
}
},
"node_modules/@waku/sdk": {
"version": "0.0.32-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.32-4997440.0.tgz",
"integrity": "sha512-3PMJYOUSj6PNL/JGqsRBvKxd4n8RltCKXJ13S97h4x3iGUR5CCYtN5vXa71ePUIL+wtXEhoGVpOZVsgKrZrXuw==",
"version": "0.0.32-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/sdk/-/sdk-0.0.32-b0a2e39.0.tgz",
"integrity": "sha512-UFoWy9PqtsbUz30qfnvAjrM+uVgCm5NWuzXCl6jyHdgyxplmUFkAfCHLoMTG9SG/ORiC5z07Jru+kOJoNtY5Eg==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@chainsafe/libp2p-noise": "16.0.0",
@ -1479,11 +1497,11 @@
"@libp2p/ping": "2.0.1",
"@libp2p/websockets": "^9.0.1",
"@noble/hashes": "^1.3.3",
"@waku/core": "0.0.36-4997440.0",
"@waku/discovery": "0.0.9-4997440.0",
"@waku/interfaces": "0.0.31-4997440.0",
"@waku/proto": "0.0.11-4997440.0",
"@waku/utils": "0.0.24-4997440.0",
"@waku/core": "0.0.36-b0a2e39.0",
"@waku/discovery": "0.0.9-b0a2e39.0",
"@waku/interfaces": "0.0.31-b0a2e39.0",
"@waku/proto": "0.0.11-b0a2e39.0",
"@waku/utils": "0.0.24-b0a2e39.0",
"libp2p": "2.1.8"
},
"engines": {
@ -1491,17 +1509,17 @@
}
},
"node_modules/@waku/sdk/node_modules/@waku/core": {
"version": "0.0.36-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-4997440.0.tgz",
"integrity": "sha512-1Z8bKLL8fFU7DeeAw+7iDazeNOayudnOSHIT4sP0pxnEe2kpLAaF5C5E0TeRj4Jk43f8xzTKmCMXXwN8/IbFgw==",
"version": "0.0.36-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/core/-/core-0.0.36-b0a2e39.0.tgz",
"integrity": "sha512-c3cXUE45Q5SdwJ9RZXKGhl0cQwu8zkK3GMAfhNoRqMl/qLhRo+Y1u5m45KbG8HasH4Pjpibw8DkMmEjSd0OdRA==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@libp2p/ping": "2.0.1",
"@noble/hashes": "^1.3.2",
"@waku/enr": "0.0.30-4997440.0",
"@waku/interfaces": "0.0.31-4997440.0",
"@waku/proto": "0.0.11-4997440.0",
"@waku/utils": "0.0.24-4997440.0",
"@waku/enr": "0.0.30-b0a2e39.0",
"@waku/interfaces": "0.0.31-b0a2e39.0",
"@waku/proto": "0.0.11-b0a2e39.0",
"@waku/utils": "0.0.24-b0a2e39.0",
"debug": "^4.3.4",
"it-all": "^3.0.4",
"it-length-prefixed": "^9.0.4",
@ -1573,13 +1591,13 @@
}
},
"node_modules/@waku/utils": {
"version": "0.0.24-4997440.0",
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.24-4997440.0.tgz",
"integrity": "sha512-abWy/tUpJLEEX9lbTIl/1TfDuODc5Og55T5n1vtzrb7xTPdaBxOlx0jbGcQdZO/ZfrunKJEY7Vdb/Ofh4XW/DQ==",
"version": "0.0.24-b0a2e39.0",
"resolved": "https://registry.npmjs.org/@waku/utils/-/utils-0.0.24-b0a2e39.0.tgz",
"integrity": "sha512-p8ZvIhROTrUFX+T2CKgepIypkWcHEMdJaJsIrxTIRb8xC412FiWNGGzViAJZ43rckvUatooEER/dvZ55ANFAyw==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@noble/hashes": "^1.3.2",
"@waku/interfaces": "0.0.31-4997440.0",
"@waku/interfaces": "0.0.31-b0a2e39.0",
"chai": "^4.3.10",
"debug": "^4.3.4",
"uint8arrays": "^5.0.1"
@ -3107,9 +3125,9 @@
}
},
"node_modules/dns-over-http-resolver": {
"version": "3.0.15",
"resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.15.tgz",
"integrity": "sha512-h2Ldu6b8LjW725Q5zjjv7T5s1K3dPjlU3DWvcEFqB3Ksb3QmqC4dHhPKlGlBS/1P47D4T5arZMiE4dD4OIfO6A==",
"version": "3.0.16",
"resolved": "https://registry.npmjs.org/dns-over-http-resolver/-/dns-over-http-resolver-3.0.16.tgz",
"integrity": "sha512-Qnq8HhNRuMnA61pf1lVPlStCAv1BVrraCx0umPESWgYKf995tUMF5oNhW59PKdnf7E8d5yqwHlEoFywXjsNMCw==",
"license": "Apache-2.0 OR MIT",
"dependencies": {
"quick-lru": "^7.0.0",
@ -7167,6 +7185,53 @@
"node": ">=8"
}
},
"node_modules/playwright": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz",
"integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.53.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz",
"integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",

View File

@ -4,23 +4,27 @@
"private": true,
"scripts": {
"build": "NODE_ENV=production webpack --config webpack.config.js --mode production",
"start": "webpack-dev-server"
"start": "webpack-dev-server",
"test": "playwright test",
"test:ui": "playwright test --ui"
},
"dependencies": {
"@libp2p/crypto": "^5.0.5",
"@multiformats/multiaddr": "^12.3.1",
"@waku/sdk": "0.0.32-4997440.0",
"@waku/sdk": "0.0.32-b0a2e39.0",
"libp2p": "^2.1.10",
"protobufjs": "^7.3.0",
"uint8arrays": "^5.1.0"
},
"devDependencies": {
"@libp2p/interface": "^2.1.3",
"@playwright/test": "^1.53.1",
"@types/node": "^20.12.11",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8",
"eslint-config-next": "13.5.6",
"html-webpack-plugin": "^5.6.3",
"playwright": "^1.53.1",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"webpack": "^5.74.0",

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,27 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:8080',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: {
command: 'npm start',
port: 8080,
reuseExistingServer: !process.env.CI,
},
});

View File

@ -0,0 +1,17 @@
// Light Push V3 Error Code Mapping
export const LIGHT_PUSH_V3_ERRORS: Record<string, string> = {
'not_published_to_any_peer': 'Message was not relayed to any peers. This can happen if the remote peer has no relay connections.',
'rate_limited': 'Message rejected due to rate limiting. Please slow down message sending.',
'bad_request': 'Invalid message format or parameters.',
'internal_server_error': 'Remote peer encountered an internal error.',
'no_peers_available': 'No suitable peers found for relaying the message.',
'duplicate_message': 'Message already exists in the network.',
'message_too_large': 'Message exceeds the maximum allowed size.',
'invalid_topic': 'The pubsub topic or content topic is invalid.',
'unauthorized': 'Not authorized to publish to this topic.',
'service_unavailable': 'Light Push service temporarily unavailable.'
};
export function getLightPushErrorMessage(error: string): string {
return LIGHT_PUSH_V3_ERRORS[error] || `Unknown Light Push error: ${error}`;
}

View File

@ -0,0 +1,84 @@
import { test, expect } from '@playwright/test';
test.describe('Waku Dogfooding App', () => {
test('should load the app and initialize Waku node', async ({ page }) => {
// Navigate to the app
await page.goto('/');
// Wait for the app to load
await expect(page).toHaveTitle(/Waku/);
// Wait for Waku node initialization
await page.waitForFunction(() => {
return (window as any).waku !== undefined;
}, { timeout: 30000 });
// Verify Waku node is available and has expected properties
const wakuNodeInfo = await page.evaluate(() => {
const waku = (window as any).waku;
if (!waku) return null;
return {
isStarted: typeof waku.isStarted === 'function' ? waku.isStarted() : false,
peerId: waku.peerId?.toString() || null,
hasLightPush: !!waku.lightPush,
hasFilter: !!waku.filter,
hasStore: !!waku.store,
};
});
// Assert Waku node is properly initialized
expect(wakuNodeInfo).not.toBeNull();
expect(wakuNodeInfo?.isStarted).toBe(true);
expect(wakuNodeInfo?.peerId).toBeTruthy();
expect(wakuNodeInfo?.hasLightPush).toBe(true);
expect(wakuNodeInfo?.hasFilter).toBe(true);
expect(wakuNodeInfo?.hasStore).toBe(true);
// Verify UI elements are present
await expect(page.locator('#peerIdDisplay')).toBeVisible();
await expect(page.locator('#peerIdDisplay')).not.toHaveText('Connecting...');
// Peer IDs can start with either 16Uiu2 or 12D3KooW depending on the key type
const peerIdText = await page.locator('#peerIdDisplay').textContent();
expect(peerIdText).toMatch(/^(16Uiu2|12D3KooW)/);
// Verify send message button is present
await expect(page.locator('#sendMessageButton')).toBeVisible();
});
test('should display peer ID in the UI', async ({ page }) => {
await page.goto('/');
// Wait for peer ID to be displayed
await page.waitForSelector('#peerIdDisplay', { state: 'visible' });
// Wait for the actual peer ID to load (not "Connecting...")
await page.waitForFunction(() => {
const el = document.querySelector('#peerIdDisplay');
return el && el.textContent !== 'Connecting...';
}, { timeout: 30000 });
const peerIdText = await page.locator('#peerIdDisplay').textContent();
expect(peerIdText).toBeTruthy();
expect(peerIdText).toMatch(/^(16Uiu2|12D3KooW)/); // Peer IDs can start with either prefix
});
test('should have functional message sending UI', async ({ page }) => {
await page.goto('/');
// Wait for Waku node to be ready
await page.waitForFunction(() => {
return (window as any).waku !== undefined;
}, { timeout: 30000 });
// Check counters are initialized
await expect(page.locator('#sentByMeCount')).toHaveText('0');
await expect(page.locator('#receivedMineCount')).toHaveText('0');
await expect(page.locator('#receivedOthersCount')).toHaveText('0');
await expect(page.locator('#failedToSendCount')).toHaveText('0');
// Verify send button is enabled
const sendButton = page.locator('#sendMessageButton');
await expect(sendButton).toBeEnabled();
});
});

View File

@ -0,0 +1,151 @@
import { test, expect } from '@playwright/test';
test.describe('Light Push Messages', () => {
test('should send 5 messages over 30 seconds with at least one success', async ({ page }) => {
test.setTimeout(60000); // Set timeout to 60 seconds for this test
// Navigate to the app
await page.goto('/');
// Wait for Waku node initialization
await page.waitForFunction(() => {
return (window as any).waku !== undefined;
}, { timeout: 30000 });
// Wait for peer ID to be displayed (indicates node is ready)
await page.waitForSelector('#peerIdDisplay', { state: 'visible' });
await page.waitForFunction(() => {
const el = document.querySelector('#peerIdDisplay');
return el && el.textContent !== 'Connecting...';
}, { timeout: 30000 });
// Remove webpack dev server overlay if it exists
await page.evaluate(() => {
const overlay = document.querySelector('#webpack-dev-server-client-overlay');
if (overlay) {
overlay.remove();
}
});
// Get initial counter values
const getCounters = async () => {
return await page.evaluate(() => {
return {
sent: parseInt(document.querySelector('#sentByMeCount')?.textContent || '0'),
receivedMine: parseInt(document.querySelector('#receivedMineCount')?.textContent || '0'),
receivedOthers: parseInt(document.querySelector('#receivedOthersCount')?.textContent || '0'),
failed: parseInt(document.querySelector('#failedToSendCount')?.textContent || '0')
};
});
};
const initialCounters = await getCounters();
console.log('Initial counters:', initialCounters);
// Send 5 messages over 30 seconds (one every 6 seconds)
const sendButton = page.locator('#sendMessageButton');
const messagesPerBatch = 5; // Based on NUM_MESSAGES_PER_BATCH in the app
let totalMessagesSent = 0;
for (let i = 0; i < 5; i++) {
console.log(`Sending batch ${i + 1} of 5...`);
// Click send button (use force if needed to bypass any overlays)
await sendButton.click({ force: true });
totalMessagesSent += messagesPerBatch;
// Wait 6 seconds before next batch (except for the last one)
if (i < 4) {
await page.waitForTimeout(6000);
}
}
// Wait a bit for the last messages to be processed
await page.waitForTimeout(3000);
// Get final counter values
const finalCounters = await getCounters();
console.log('Final counters:', finalCounters);
// Calculate the changes
const sentMessages = finalCounters.sent - initialCounters.sent;
const receivedMine = finalCounters.receivedMine - initialCounters.receivedMine;
const failedMessages = finalCounters.failed - initialCounters.failed;
const totalProcessed = sentMessages + failedMessages;
console.log(`Messages sent successfully (according to lightPush): ${sentMessages}`);
console.log(`Messages received back (mine): ${receivedMine}`);
console.log(`Messages failed: ${failedMessages}`);
console.log(`Total messages processed: ${totalProcessed}`);
console.log(`Total messages expected: ${totalMessagesSent}`);
// Verify at least one message was successfully delivered
// A message is considered successful if either:
// 1. Light push reports success (sentMessages > 0), OR
// 2. We received our own messages back via Filter (receivedMine > 0)
const successfulDeliveries = sentMessages + receivedMine;
expect(successfulDeliveries).toBeGreaterThan(0);
// Verify that at least 20 out of 25 messages were processed (either sent or failed)
// Allowing for some messages to be lost due to timing or network issues
expect(totalProcessed).toBeGreaterThanOrEqual(20);
// Additional verification: check message log
const allMessageElements = await page.locator('.message-item').count();
console.log(`Total messages in UI: ${allMessageElements}`);
expect(allMessageElements).toBeGreaterThan(0);
// Log success rate based on actual delivery
const effectiveSuccessRate = (receivedMine / totalMessagesSent) * 100;
console.log(`Effective delivery rate: ${effectiveSuccessRate.toFixed(2)}%`);
});
test('should handle message failures gracefully', async ({ page }) => {
// Navigate to the app
await page.goto('/');
// Wait for Waku node initialization
await page.waitForFunction(() => {
return (window as any).waku !== undefined;
}, { timeout: 30000 });
// Remove webpack dev server overlay if it exists
await page.evaluate(() => {
const overlay = document.querySelector('#webpack-dev-server-client-overlay');
if (overlay) {
overlay.remove();
}
});
// Monitor console for error messages
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error' || msg.type() === 'warning') {
consoleErrors.push(msg.text());
}
});
// Send a batch of messages
const sendButton = page.locator('#sendMessageButton');
await sendButton.click({ force: true });
// Wait for processing
await page.waitForTimeout(3000);
// Check if failed counter is visible and functional
const failedCount = await page.locator('#failedToSendCount').textContent();
expect(failedCount).toBeDefined();
// If there were failures, verify they were logged properly
const failedMessages = parseInt(failedCount || '0');
if (failedMessages > 0) {
// Check for failed messages in the UI
const failedMessageElements = await page.locator('.message-item.failed').count();
expect(failedMessageElements).toBe(failedMessages);
// Verify error details are displayed
const firstFailedMessage = page.locator('.message-item.failed').first();
await expect(firstFailedMessage).toContainText('Failed');
}
});
});

View File

@ -0,0 +1,234 @@
import { test, expect, Browser, BrowserContext, Page } from '@playwright/test';
test.describe('Multi-Node Light Push Messages', () => {
test('should send messages between two nodes and track delivery', async ({ browser }) => {
test.setTimeout(90000); // Set timeout to 90 seconds for this test
// Create two separate browser contexts (like incognito windows)
const context1 = await browser.newContext();
const context2 = await browser.newContext();
// Create pages in each context
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Helper function to initialize a node
const initializeNode = async (page: Page, nodeName: string) => {
await page.goto('/');
// Wait for Waku node initialization
await page.waitForFunction(() => {
return (window as any).waku !== undefined;
}, { timeout: 30000 });
// Wait for peer ID to be displayed
await page.waitForSelector('#peerIdDisplay', { state: 'visible' });
await page.waitForFunction(() => {
const el = document.querySelector('#peerIdDisplay');
return el && el.textContent !== 'Connecting...';
}, { timeout: 30000 });
// Remove webpack dev server overlay if it exists
await page.evaluate(() => {
const overlay = document.querySelector('#webpack-dev-server-client-overlay');
if (overlay) {
overlay.remove();
}
});
// Get peer ID
const peerId = await page.locator('#peerIdDisplay').textContent();
console.log(`${nodeName} initialized with Peer ID: ${peerId}`);
return peerId;
};
// Initialize both nodes
console.log('Initializing Node 1...');
const peerId1 = await initializeNode(page1, 'Node 1');
console.log('Initializing Node 2...');
const peerId2 = await initializeNode(page2, 'Node 2');
// Helper function to get counters
const getCounters = async (page: Page) => {
return await page.evaluate(() => {
return {
sent: parseInt(document.querySelector('#sentByMeCount')?.textContent || '0'),
receivedMine: parseInt(document.querySelector('#receivedMineCount')?.textContent || '0'),
receivedOthers: parseInt(document.querySelector('#receivedOthersCount')?.textContent || '0'),
failed: parseInt(document.querySelector('#failedToSendCount')?.textContent || '0')
};
});
};
// Get initial counters for both nodes
const initialCounters1 = await getCounters(page1);
const initialCounters2 = await getCounters(page2);
console.log('Initial counters Node 1:', initialCounters1);
console.log('Initial counters Node 2:', initialCounters2);
// Send 5 batches of messages from Node 1
console.log('\n--- Starting message sending from Node 1 ---');
const sendButton1 = page1.locator('#sendMessageButton');
const messagesPerBatch = 5;
const totalBatches = 5;
for (let i = 0; i < totalBatches; i++) {
console.log(`Node 1: Sending batch ${i + 1} of ${totalBatches}...`);
await sendButton1.click({ force: true });
// Wait between batches
if (i < totalBatches - 1) {
await page1.waitForTimeout(3000);
}
}
// Wait for messages to propagate
console.log('Waiting for message propagation...');
await page1.waitForTimeout(5000);
// Get final counters for both nodes
const finalCounters1 = await getCounters(page1);
const finalCounters2 = await getCounters(page2);
console.log('\n--- Final Results ---');
console.log('Final counters Node 1:', finalCounters1);
console.log('Final counters Node 2:', finalCounters2);
// Calculate results for Node 1 (sender)
const node1Results = {
sent: finalCounters1.sent - initialCounters1.sent,
receivedMine: finalCounters1.receivedMine - initialCounters1.receivedMine,
receivedOthers: finalCounters1.receivedOthers - initialCounters1.receivedOthers,
failed: finalCounters1.failed - initialCounters1.failed
};
// Calculate results for Node 2 (receiver)
const node2Results = {
sent: finalCounters2.sent - initialCounters2.sent,
receivedMine: finalCounters2.receivedMine - initialCounters2.receivedMine,
receivedOthers: finalCounters2.receivedOthers - initialCounters2.receivedOthers,
failed: finalCounters2.failed - initialCounters2.failed
};
// Generate report
console.log('\n========== DELIVERY REPORT ==========');
console.log(`Total messages sent: ${messagesPerBatch * totalBatches}`);
console.log('\nNode 1 (Sender):');
console.log(` - Peer ID: ${peerId1}`);
console.log(` - Messages sent successfully: ${node1Results.sent}`);
console.log(` - Messages failed: ${node1Results.failed}`);
console.log(` - Own messages received back: ${node1Results.receivedMine}`);
console.log(` - Messages from others: ${node1Results.receivedOthers}`);
console.log('\nNode 2 (Receiver):');
console.log(` - Peer ID: ${peerId2}`);
console.log(` - Messages received from Node 1: ${node2Results.receivedOthers}`);
console.log(` - Own messages received: ${node2Results.receivedMine}`);
const totalExpected = messagesPerBatch * totalBatches;
const node1DeliveryRate = (node1Results.receivedMine / totalExpected) * 100;
const node2DeliveryRate = (node2Results.receivedOthers / totalExpected) * 100;
console.log('\nDelivery Rates:');
console.log(` - Node 1 self-delivery rate: ${node1DeliveryRate.toFixed(2)}%`);
console.log(` - Node 2 reception rate: ${node2DeliveryRate.toFixed(2)}%`);
console.log('=====================================\n');
// Verify at least one message was delivered
expect(node1Results.receivedMine + node2Results.receivedOthers).toBeGreaterThan(0);
// Verify Node 2 received at least some messages from Node 1
expect(node2Results.receivedOthers).toBeGreaterThan(0);
// Check message elements in UI
const messageCount1 = await page1.locator('.message-item').count();
const messageCount2 = await page2.locator('.message-item').count();
console.log(`Messages in Node 1 UI: ${messageCount1}`);
console.log(`Messages in Node 2 UI: ${messageCount2}`);
// Cleanup
await context1.close();
await context2.close();
});
test('should handle bidirectional messaging between two nodes', async ({ browser }) => {
test.setTimeout(90000);
const context1 = await browser.newContext();
const context2 = await browser.newContext();
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// Initialize both nodes
const initializeNode = async (page: Page, nodeName: string) => {
await page.goto('/');
await page.waitForFunction(() => (window as any).waku !== undefined, { timeout: 30000 });
await page.waitForSelector('#peerIdDisplay', { state: 'visible' });
await page.waitForFunction(() => {
const el = document.querySelector('#peerIdDisplay');
return el && el.textContent !== 'Connecting...';
}, { timeout: 30000 });
await page.evaluate(() => {
const overlay = document.querySelector('#webpack-dev-server-client-overlay');
if (overlay) overlay.remove();
});
const peerId = await page.locator('#peerIdDisplay').textContent();
console.log(`${nodeName} initialized with Peer ID: ${peerId}`);
return peerId;
};
await initializeNode(page1, 'Node 1');
await initializeNode(page2, 'Node 2');
// Helper to get message counts
const getMessageCounts = async (page: Page) => {
return await page.evaluate(() => {
return {
receivedOthers: parseInt(document.querySelector('#receivedOthersCount')?.textContent || '0')
};
});
};
const initial1 = await getMessageCounts(page1);
const initial2 = await getMessageCounts(page2);
// Send messages from both nodes alternately
console.log('\n--- Bidirectional messaging test ---');
const sendButton1 = page1.locator('#sendMessageButton');
const sendButton2 = page2.locator('#sendMessageButton');
for (let i = 0; i < 3; i++) {
console.log(`Round ${i + 1}: Node 1 sending...`);
await sendButton1.click({ force: true });
await page1.waitForTimeout(2000);
console.log(`Round ${i + 1}: Node 2 sending...`);
await sendButton2.click({ force: true });
await page2.waitForTimeout(2000);
}
// Wait for final propagation
await page1.waitForTimeout(3000);
const final1 = await getMessageCounts(page1);
const final2 = await getMessageCounts(page2);
const node1ReceivedFromNode2 = final1.receivedOthers - initial1.receivedOthers;
const node2ReceivedFromNode1 = final2.receivedOthers - initial2.receivedOthers;
console.log('\n--- Bidirectional Results ---');
console.log(`Node 1 received ${node1ReceivedFromNode2} messages from Node 2`);
console.log(`Node 2 received ${node2ReceivedFromNode1} messages from Node 1`);
// Verify bidirectional communication
expect(node1ReceivedFromNode2).toBeGreaterThan(0);
expect(node2ReceivedFromNode1).toBeGreaterThan(0);
await context1.close();
await context2.close();
});
});