softphone: dock panel, incoming dialog, hold/mute/DTMF, transfers, one tab
The dock opens a panel with a 12-key dialpad (typing, paste normalization, Enter) and microphone/speaker selection. Call cards have mute, hold, keypad (DTMF) and transfer: "Transfer now" (blind) or "Talk first" (attended, with Complete/Cancel). An incoming call opens a dialog when idle, or a card during a call (call waiting holds the first call). A synthesized WebAudio ringtone and desktop notifications, auto-answer for Call-Info answer-after=0, and a poor-connection warning from getStats. One tab registers: a Web Lock plus a BroadcastChannel "Use here". Found by e2e: Chrome can report canInsertDTMF=false, so DTMF falls back to SIP INFO; after a REFER's 2xx NOTIFY the browser sends BYE itself (RFC 5589), otherwise SIP.js keeps the call up. apps/web/TESTING.md is the 12-step manual run for Chrome and Firefox (still open, roadmap note); e2e m5-t2 automates most of it in headless Edge. Task: M5-T2
This commit is contained in:
1 parent
ae3a723a3c
commit
c7fd4df77c
12 files changed
+1035
-94
No files matched your search
+21
-7
@@ -30,7 +30,25 @@ export async function launch({ width = 1280, height = 800, shotsDir, args = [] }
|
||||
.then((l) => l.find((t) => t.type === 'page'))
|
||||
.catch(() => undefined);
|
||||
}
|
||||
const ws = new WebSocket(info.webSocketDebuggerUrl);
|
||||
const opts = { width, height, shotsDir };
|
||||
const page = await attach(info.webSocketDebuggerUrl, opts);
|
||||
/** Another tab in the same browser (same profile: storage, Web Locks, BroadcastChannel). */
|
||||
page.newTab = async (url) => {
|
||||
const t = await fetch(`http://127.0.0.1:${port}/json/new?${url}`, { method: 'PUT' }).then((r) =>
|
||||
r.json(),
|
||||
);
|
||||
return attach(t.webSocketDebuggerUrl, opts);
|
||||
};
|
||||
const closeTab = page.close;
|
||||
page.close = async () => {
|
||||
await closeTab();
|
||||
proc.kill();
|
||||
};
|
||||
return page;
|
||||
}
|
||||
|
||||
async function attach(wsUrl, { width, height, shotsDir }) {
|
||||
const ws = new WebSocket(wsUrl);
|
||||
await new Promise((r) => ws.addEventListener('open', r));
|
||||
let id = 0;
|
||||
const pending = new Map();
|
||||
@@ -79,7 +97,7 @@ export async function launch({ width = 1280, height = 800, shotsDir, args = [] }
|
||||
}
|
||||
throw new Error(`timeout waiting for: ${what ?? expr}`);
|
||||
};
|
||||
const page = {
|
||||
return {
|
||||
evaluate,
|
||||
waitFor,
|
||||
consoleErrors,
|
||||
@@ -110,10 +128,6 @@ export async function launch({ width = 1280, height = 800, shotsDir, args = [] }
|
||||
const r = await send('Page.captureScreenshot', { format: 'png' });
|
||||
writeFileSync(join(shotsDir, `${name}.png`), Buffer.from(r.data, 'base64'));
|
||||
},
|
||||
close: async () => {
|
||||
ws.close();
|
||||
proc.kill();
|
||||
},
|
||||
close: async () => ws.close(),
|
||||
};
|
||||
return page;
|
||||
}
|
||||
Reference in new issue
Block a user