import { expect, type Locator, type Page } from "@playwright/test"; /** The Messages page: compose box + rendered message list. */ export class MessagesPage { constructor(private readonly page: Page) {} input(): Locator { return this.page.locator('input[name="messageInput"]'); } sendButton(): Locator { return this.page.locator('form[name="messageInput"] button[type="submit"]'); } /** A rendered message bubble (an
  • ) containing the given text. */ message(text: string): Locator { return this.page.getByRole("listitem").filter({ hasText: text }); } /** Wait until the device is configured enough that the composer is usable. */ async waitReady(): Promise { await expect(this.input()).toBeVisible({ timeout: 60_000 }); await expect(this.input()).toBeEnabled(); // The SDK chat client lags the composer (device handshake + sqlocal init), so // an immediate send is silently dropped. Gate on the "Connected" status. await expect( this.page.getByText("Connected", { exact: true }).first(), ).toBeVisible({ timeout: 30_000, }); } /** * Open a direct-message thread by clicking the peer node in the sidebar * (client-side nav, so the live connection is preserved — a full reload would * drop it). The node's default short name is the last 4 hex digits of its * number, e.g. 0xccddee02 -> "ee02". */ async openDirectMessageByNodeNum(nodeNum: number): Promise { const shortName = (nodeNum >>> 0).toString(16).slice(-4); await this.page .getByRole("button", { name: new RegExp(shortName, "i") }) .first() .click(); await expect(this.input()).toBeVisible(); } async send(text: string): Promise { await this.input().fill(text); await this.sendButton().click(); } async expectMessage(text: string, timeout = 45_000): Promise { await expect(this.message(text).first()).toBeVisible({ timeout }); } }