Files
meshcore-analyzer/test-channels-share-color-e2e.js
T
d837166158 test(coverage): add Playwright E2E for channels page (#1297 B3) (#1300)
## #1297 B3 — Playwright E2E coverage for `public/channels.js`

Pure-coverage PR. Adds five Playwright suites targeting the largest
under-tested branches of `public/channels.js` (1950 LOC, was **19.9%
statements** per the live coverage refinement in #1297 — the single
biggest delta opportunity in the umbrella). No production code changes.

### Coverage exemption

Per repo `AGENTS.md` TDD rule: this is the **net-new test coverage**
case — there is no production change to gate, so a failing-then-passing
red commit isn't applicable. All five suites exercise existing channels
init() code paths that ship today.

### New test files

| File | Scenarios exercised |
| --- | --- |
| `test-channels-list-render-e2e.js` | Sectioned sidebar (My Channels /
Network / Encrypted) headers, encrypted collapse toggle + localStorage
persistence, row badges + previews, color dot + color clear control,
sidebar resize handle width persist |
| `test-channels-selection-flow-e2e.js` | `selectChannel()` header
update + URL replaceState, message row rendering (avatars, sender
colors, packet links), node detail panel open via mouse + keyboard +
close-with-focus-restore, deep-link route restoration, scroll button
initial state |
| `test-channels-add-modal-e2e.js` | Generate PSK Channel (key + QR +
status banner + localStorage persist), Add PSK invalid hex error path,
Add PSK valid hex success + close + My Channels row, Monitor Hashtag
with and without leading `#`, empty-hashtag no-op, Scan QR unavailable
fallback, Escape close, Remove ✕ flow |
| `test-channels-share-color-e2e.js` | Share modal normal mode
(dedicated `#chShareModal` with QR + Hex Key + Copy success label),
Share modal error mode (`openShareModalError` when no stored key — field
groups hidden), Escape close, `ChannelColorPicker.show` invocation on
color-dot click, keyboard Enter on a `[data-share-channel]` span |
| `test-channels-ws-batch-e2e.js` | `processWSBatch` via
`_channelsProcessWSBatchForTest`: explicit-sender append, `"Sender:
text"` parsing branch, packetHash dedup + observer accumulation,
new-channel append (channel previously unseen), scroll-button branch
when user not at bottom, region-filter exclusion code path |

All five tests wired into `.github/workflows/deploy.yml` after the
existing `test-channel-fluid-e2e.js` step.

### Preflight

`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
→
exit 0, all gates pass (PII, CSS vars, branch scope, etc.).

Refs #1297

---------

Co-authored-by: openclaw-bot <openclaw-bot@users.noreply.github.com>
Co-authored-by: openclaw-bot <bot@openclaw.local>
Co-authored-by: mc-bot <bot@meshcore.local>
2026-05-29 11:46:51 +00:00

142 lines
6.6 KiB
JavaScript

/**
* #1297 B3 — channels.js share modal + color picker coverage.
*
* Exercises:
* - Share modal normal mode (QR + Hex key + Copy button)
* - Share modal error mode (no key found → openShareModalError)
* - Escape closes share modal + focus restore
* - Channel color dot click triggers ChannelColorPicker.show (stubbed)
*
* Usage: BASE_URL=http://localhost:13581 node test-channels-share-color-e2e.js
*/
'use strict';
const { chromium } = require('playwright');
const BASE = process.env.BASE_URL || 'http://localhost:13581';
let passed = 0, failed = 0;
async function step(name, fn) {
try { await fn(); passed++; console.log(' ✓ ' + name); }
catch (e) { failed++; console.error(' ✗ ' + name + ': ' + e.message); }
}
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
(async () => {
const browser = await chromium.launch({
headless: true,
executablePath: process.env.CHROMIUM_PATH || undefined,
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
});
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
const page = await ctx.newPage();
page.on('dialog', (d) => d.accept());
page.setDefaultTimeout(8000);
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
console.log(`\n=== #1297 B3 channels share-color E2E against ${BASE} ===`);
await page.goto(BASE + '/', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => { try { localStorage.clear(); } catch (e) {} });
await page.goto(BASE + '/#/channels', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#chAddChannelBtn', { timeout: 8000 });
// Add a PSK channel so we have a Share button to click.
await page.click('#chAddChannelBtn');
await page.waitForSelector('#chAddChannelModal:not(.hidden)');
await page.fill('#chPskKey', '00112233445566778899aabbccddeeff');
await page.fill('#chPskName', 'CovShare');
await page.click('#chPskAddBtn');
await page.waitForFunction(() => document.getElementById('chAddChannelModal')?.classList.contains('hidden'), { timeout: 5000 });
await page.waitForSelector('.ch-section-mychannels [data-share-channel]',
{ timeout: 5000 });
await step('clicking Share opens dedicated #chShareModal with QR + Hex key', async () => {
await page.click('.ch-section-mychannels [data-share-channel]');
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
const title = await page.textContent('#chShareModalTitle');
assert(/Share/i.test(title), 'title should start with Share: ' + title);
const keyField = await page.$eval('#chShareKey', (el) => el.value);
assert(/^[0-9a-f]{32}$/i.test(keyField),
'hex key field should be populated with 32-char hex, got: ' + keyField);
const qrEl = await page.$('#chShareQr img, #chShareQr canvas, #chShareQr table, #chShareQr svg');
assert(qrEl, 'QR element should be rendered in #chShareQr');
});
await step('Share modal Copy button labels success', async () => {
const copyBtn = await page.$('#chShareModal [data-share-copy]');
assert(copyBtn, 'copy button missing');
await copyBtn.click();
await page.waitForFunction(() => {
const b = document.querySelector('#chShareModal [data-share-copy]');
return b && /Copied/i.test(b.textContent);
}, { timeout: 3000 });
});
await step('Escape closes share modal', async () => {
await page.keyboard.press('Escape');
await page.waitForFunction(() => document.getElementById('chShareModal')?.classList.contains('hidden'), { timeout: 3000 });
});
await step('Share with no stored key → error modal (openShareModalError)', async () => {
// Wipe the stored key for our channel, then click Share again.
await page.evaluate(() => {
try {
var KEY = 'corescope_channel_keys';
var keys = JSON.parse(localStorage.getItem(KEY) || '{}');
// Remove every key so getStoredKeys()[name] is undefined.
localStorage.setItem(KEY, '{}');
return keys;
} catch (e) { return null; }
});
await page.click('.ch-section-mychannels [data-share-channel]');
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
const errTxt = await page.textContent('#chShareQr');
assert(/No stored key|cannot share/i.test(errTxt),
'error mode should show "No stored key" message, got: ' + errTxt);
// Field-groups should be hidden in error mode.
const fieldsHidden = await page.$$eval(
'#chShareModal .ch-share-field-group',
(els) => els.every((e) => e.hidden));
assert(fieldsHidden, 'field groups should be hidden in error mode');
// Close via the X button.
await page.click('#chShareModalClose');
await page.waitForFunction(() => document.getElementById('chShareModal')?.classList.contains('hidden'), { timeout: 3000 });
});
await step('color dot click invokes ChannelColorPicker.show', async () => {
// Stub the color picker so we don't depend on its DOM.
await page.evaluate(() => {
window.__pickerCalls = [];
window.ChannelColorPicker = {
show: function (ch, x, y) { window.__pickerCalls.push({ ch: ch, x: x, y: y }); },
};
});
const dot = await page.$('.ch-section-network .ch-color-dot');
assert(dot, 'no .ch-color-dot found in network section');
await dot.click();
const calls = await page.evaluate(() => window.__pickerCalls);
assert(calls.length >= 1, 'ChannelColorPicker.show should fire on dot click');
});
await step('share via keyboard Enter on the Share span (#chList keydown handler)', async () => {
// Re-add a key so the share button exists with stored key again.
await page.click('#chAddChannelBtn');
await page.waitForSelector('#chAddChannelModal:not(.hidden)');
await page.fill('#chPskKey', '11223344556677889900aabbccddeeff');
await page.fill('#chPskName', 'CovKbd');
await page.click('#chPskAddBtn');
await page.waitForFunction(() => document.getElementById('chAddChannelModal')?.classList.contains('hidden'), { timeout: 5000 });
await page.waitForSelector('.ch-section-mychannels [data-share-channel]',
{ timeout: 5000 });
const shareSpan = await page.$('.ch-section-mychannels [data-share-channel]');
await shareSpan.focus();
await page.keyboard.press('Enter');
await page.waitForSelector('#chShareModal:not(.hidden)', { timeout: 5000 });
await page.keyboard.press('Escape');
});
await browser.close();
console.log(`\n=== B3 share-color: ${passed} passed, ${failed} failed ===\n`);
process.exit(failed === 0 ? 0 : 1);
})().catch((e) => { console.error(e); process.exit(1); });