"""Muji (XEP-0272) group call between three browsers in a group chat.""" import asyncio, time from playwright.async_api import async_playwright import sys from browser import login, BASE, OUT VIDEO = 'true' if '--video' in sys.argv else 'false' async def open_room(page, room): await page.evaluate(f"Chat_ajaxGetRoom('{room}')") await page.wait_for_selector("#chat_textarea", timeout=15000) await page.wait_for_timeout(1500) async def join_from_lobby(page): await page.wait_for_selector("#lobby_start:not(.disabled)", timeout=15000) await page.click("#lobby_start") async def sessions(page): return await page.evaluate( "Object.fromEntries(Object.entries(MovimJingles.sessions || {}).map(([j, s]) => [j, s.pc && s.pc.connectionState]))" ) async def main(): room = f"call{int(time.time())}@conference.localhost" async with async_playwright() as p: browser = await p.chromium.launch(args=["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream"]) users = [("alice", "alicepass"), ("bob", "bobpass"), ("carol", "carolpass")] pages, errors = {}, [] for name, password in users: ctx, page, err = await login(browser, f"{name}@localhost", password, f"muji{name}") await ctx.grant_permissions(["microphone", "camera"], origin=BASE) pages[name] = page await page.evaluate(""" window.addEventListener('unhandledrejection', e => console.error('REJECTION', e.reason && e.reason.stack)); window.addEventListener('error', e => console.error('ERROR', e.error && e.error.stack)); """) errors.append(err) alice, bob, carol = pages["alice"], pages["bob"], pages["carol"] # Alice creates a group chat and invites the others, who join it await alice.evaluate(f"""RoomsUtils_ajaxAddCreate({{ jid: {{value: '{room}'}}, name: {{value: 'Call room'}}, nick: {{value: 'alice'}}, type: {{value: 'groupchat'}}, autojoin: {{value: true}}, pinned: {{value: false}}, notify: {{value: 'always'}} }})""") await alice.wait_for_timeout(3000) for name in ("bob", "carol"): await alice.evaluate(f"RoomsUtils_ajaxInvite({{to: {{value: '{room}'}}, invite: {{value: '{name}@localhost'}}}})") await alice.wait_for_timeout(1500) for name in ("bob", "carol"): await pages[name].evaluate(f"""RoomsUtils_ajaxAddConfirm({{ jid: {{value: '{room}'}}, name: {{value: 'Call room'}}, nick: {{value: '{name}'}}, autojoin: {{value: true}}, pinned: {{value: false}}, notify: {{value: 'on-mention'}} }})""") await alice.wait_for_timeout(3000) for page in pages.values(): await open_room(page, room) await alice.screenshot(path=f"{OUT}/muji_0_header.png") # Alice starts the call await alice.evaluate(f"Visio_ajaxGetMujiLobby('{room}', true, {VIDEO})") await join_from_lobby(alice) await alice.wait_for_timeout(3000) await alice.screenshot(path=f"{OUT}/muji_1_alice_waiting.png") await bob.screenshot(path=f"{OUT}/muji_1_bob_header.png") print("bob sees a call to join:", await bob.locator("button.oppose.color.blue").count() > 0) # Bob then Carol join for name, page in (("bob", bob), ("carol", carol)): button = page.locator("button.oppose.color.blue").first try: await button.click(timeout=10000) except Exception: await page.screenshot(path=f"{OUT}/muji_fail_{name}.png") print(name, "header:", await page.content()) raise await join_from_lobby(page) await page.wait_for_timeout(6000) print(f"after {name} joined:", {n: await sessions(pg) for n, pg in pages.items()}) for name, page in pages.items(): await page.screenshot(path=f"{OUT}/muji_2_{name}.png") print(name, "sessions:", await sessions(page)) print("JS errors before leaving:", sum(len(e) for e in errors)) # Carol leaves, the others keep talking await carol.evaluate(f"Visio_ajaxLeaveMuji('{room}')") await alice.wait_for_timeout(3000) print("after carol left, alice sessions:", await sessions(alice)) print("carol sessions:", await sessions(carol)) await alice.screenshot(path=f"{OUT}/muji_3_alice.png") for err in errors: for e in err: print(e) await browser.close() asyncio.run(main())