Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

400 lines
11 KiB
TypeScript

import type { Locator, Page } from '@playwright/test';
import { expect } from '../../utils/test';
const timerToSeconds = (time: string): number => {
const [seconds, minutes, hours = '00'] = time.split(':').reverse();
if (!seconds || !minutes) {
throw new Error('Voice call timer has an invalid time format');
}
return Number(hours) * 3600 + Number(minutes) * 60 + Number(seconds);
};
export class VoiceCallControls {
private readonly _controls: Locator;
constructor(controls: Locator) {
this._controls = controls;
}
get call(): Locator {
return this._controls.getByRole('button', { name: 'Call', exact: true });
}
get accept(): Locator {
return this._controls.getByRole('button', { name: 'Accept', exact: true });
}
get reject(): Locator {
return this._controls.getByRole('button', { name: 'Reject', exact: true });
}
get hangup(): Locator {
return this._controls.getByRole('button', { name: 'End call with', exact: false });
}
get cancel(): Locator {
return this._controls.getByRole('button', { name: 'Cancel', exact: true });
}
get dialpad(): Locator {
return this._controls.getByRole('button', { name: /Dialpad/i });
}
get mute(): Locator {
return this._controls.getByRole('button', { name: /Mute/i });
}
get hold(): Locator {
return this._controls.getByRole('button', { name: /Hold|Resume/i });
}
get transfer(): Locator {
return this._controls.getByRole('button', { name: 'Forward', exact: true });
}
get shareScreen(): Locator {
return this._controls.getByRole('button', { name: /Share screen|Stop sharing screen/i });
}
get toggleChat(): Locator {
return this._controls.getByRole('button', { name: /Hide chat|Show chat/i });
}
get directMessage(): Locator {
return this._controls.getByRole('button', { name: /Direct message/i });
}
get popout(): Locator {
return this._controls.getByRole('button', { name: /Open in new window|Return to main window/i });
}
}
export class TransferModal {
private readonly root: Locator;
private readonly page: Page;
constructor(page: Page, root: Locator) {
this.page = page;
this.root = root;
}
get content(): Locator {
return this.root;
}
get input(): Locator {
return this.root.getByRole('textbox');
}
get hangUpAndTransfer(): Locator {
return this.root.getByRole('button', { name: 'Hang up and transfer call', exact: true });
}
async transferCall(username: string): Promise<void> {
// await this.input.click();
await this.input.fill(username);
// Options have to be gotten from the page since the Options are rendered on a portal
await this.page.getByRole('option', { name: username }).getByRole('figure').click();
await expect(this.content).toContainText(username);
await this.hangUpAndTransfer.click();
}
}
export class Widget {
private readonly root: Locator;
private readonly callControls: VoiceCallControls;
private readonly headerControls: VoiceCallControls;
private readonly transferModal: TransferModal;
constructor(private readonly page: Page) {
this.transferModal = new TransferModal(page, page.getByRole('dialog', { name: 'Transfer call' }));
this.root = page.getByRole('dialog', { name: 'Voice call', exact: false });
this.callControls = new VoiceCallControls(this.root.getByRole('group'));
this.headerControls = new VoiceCallControls(this.root.getByRole('banner'));
}
get content(): Locator {
return this.root;
}
get controls(): VoiceCallControls {
return this.callControls;
}
get timer(): Locator {
return this.root.getByRole('time');
}
get btnShowCallHere(): Locator {
return this.root.getByRole('button', { name: 'Show call here' });
}
async showCallHere(): Promise<void> {
await this.btnShowCallHere.click();
await expect(this.btnShowCallHere).not.toBeVisible();
}
async getTimerContentInSeconds() {
const text = await this.timer.innerText();
return timerToSeconds(text);
}
async initiateCall(username?: string): Promise<void> {
if (username) {
await this.root.getByRole('textbox', { name: 'Enter username or number' }).fill(username);
await this.page.getByRole('listbox').getByRole('option', { name: username }).click();
}
await this.callControls.call.click();
await expect(this.callControls.cancel).toBeVisible();
}
async acceptCall(): Promise<void> {
await this.callControls.accept.click();
await expect(this.callControls.hangup).toBeVisible();
}
async hangup(): Promise<void> {
await this.callControls.hangup.click();
await expect(this.content).not.toBeVisible();
}
async reject(): Promise<void> {
await this.callControls.reject.click();
await expect(this.content).not.toBeVisible();
}
async goToDm(): Promise<void> {
await this.headerControls.directMessage.click();
await expect(this.content).not.toBeVisible();
}
async transferCall(username: string): Promise<void> {
await this.callControls.transfer.click();
await expect(this.transferModal.content).toBeVisible();
await this.transferModal.transferCall(username);
await expect(this.transferModal.content).not.toBeVisible();
await expect(this.content).not.toBeVisible();
}
async muteSelf(): Promise<void> {
await this.callControls.mute.click();
await expect(this.callControls.mute).toHaveAttribute('title', 'Unmute');
}
async unmuteSelf(): Promise<void> {
await this.callControls.mute.click();
await expect(this.callControls.mute).toHaveAttribute('title', 'Mute');
}
async holdSelf(): Promise<void> {
await this.callControls.hold.click();
await expect(this.callControls.hold).toHaveAttribute('title', 'Resume');
}
async resumeSelf(): Promise<void> {
await this.callControls.hold.click();
await expect(this.callControls.hold).toHaveAttribute('title', 'Hold');
}
async openDialpad(): Promise<void> {
await this.callControls.dialpad.click();
await expect(this.callControls.dialpad).toHaveAttribute('title', 'Close dialpad');
}
async closeDialpad(): Promise<void> {
await this.callControls.dialpad.click();
await expect(this.callControls.dialpad).toHaveAttribute('title', 'Open dialpad');
}
get allScreenShareVideos(): Locator {
return this.root.locator('video');
}
async shareScreen(): Promise<void> {
await this.callControls.shareScreen.click();
await expect(this.callControls.shareScreen).toHaveAttribute('title', 'Stop sharing screen');
}
async stopSharing(): Promise<void> {
await this.callControls.shareScreen.click();
await expect(this.callControls.shareScreen).toHaveAttribute('title', 'Share screen');
}
async openPopout(): Promise<void> {
await this.headerControls.popout.click();
}
peerCard(username: string): Locator {
return this.root.getByText(username);
}
}
export class RoomSection {
private readonly root: Locator;
public readonly callControls: VoiceCallControls;
public readonly otherControls: VoiceCallControls;
constructor(root: Locator) {
this.root = root;
// Right now there are 2 control groups in the room view, with no discrimination between them
this.callControls = new VoiceCallControls(this.root.getByRole('group').first());
this.otherControls = new VoiceCallControls(this.root.getByRole('group').last());
}
get content(): Locator {
return this.root;
}
get controls(): VoiceCallControls {
return this.callControls;
}
get timer(): Locator {
return this.root.getByRole('time');
}
get allScreenShareVideos(): Locator {
return this.root.locator('video');
}
get btnShowCallHere(): Locator {
return this.root.getByRole('button', { name: 'Show call here' });
}
async showCallHere(): Promise<void> {
await this.btnShowCallHere.click();
await expect(this.btnShowCallHere).not.toBeVisible();
}
async getTimerContentInSeconds() {
const text = await this.timer.innerText();
return timerToSeconds(text);
}
async hangup(): Promise<void> {
await this.callControls.hangup.click();
await expect(this.content).not.toBeVisible();
}
async hideChat(): Promise<void> {
await expect(this.otherControls.toggleChat).toHaveAccessibleName(/Hide chat/i);
await this.otherControls.toggleChat.click();
await expect(this.otherControls.toggleChat).toHaveAccessibleName(/Show chat/i);
}
async showChat(): Promise<void> {
await expect(this.otherControls.toggleChat).toHaveAccessibleName(/Show chat/i);
await this.otherControls.toggleChat.click();
await expect(this.otherControls.toggleChat).toHaveAccessibleName(/Hide chat/i);
}
async muteSelf(): Promise<void> {
await this.callControls.mute.click();
await expect(this.callControls.mute).toHaveAttribute('title', 'Unmute');
}
async unmuteSelf(): Promise<void> {
await this.callControls.mute.click();
await expect(this.callControls.mute).toHaveAttribute('title', 'Mute');
}
async holdSelf(): Promise<void> {
await this.callControls.hold.click();
await expect(this.callControls.hold).toHaveAttribute('title', 'Resume');
}
async resumeSelf(): Promise<void> {
await this.callControls.hold.click();
await expect(this.callControls.hold).toHaveAttribute('title', 'Hold');
}
async shareScreen(): Promise<void> {
await this.callControls.shareScreen.click();
await expect(this.callControls.shareScreen).toHaveAttribute('title', 'Stop sharing screen');
}
async stopSharing(): Promise<void> {
await this.callControls.shareScreen.click();
await expect(this.callControls.shareScreen).toHaveAttribute('title', 'Share screen');
}
peerCard(username: string): Locator {
return this.root.getByText(username);
}
async openPopout(): Promise<void> {
await this.otherControls.popout.click();
}
}
export class PopoutPage extends RoomSection {
private readonly page: Page;
constructor(page: Page) {
super(page.getByRole('main', { name: 'Voice call' }));
this.page = page;
}
override async hangup(): Promise<void> {
const pageClosed = new Promise((resolve) => this.page.on('close', () => resolve(true)));
await this.callControls.hangup.click();
await expect(pageClosed).resolves.toBe(true);
}
}
export class VoiceCalls {
public readonly widget: Widget;
public readonly roomSection: RoomSection;
public popoutPage: PopoutPage | undefined;
private readonly page: Page;
constructor(page: Page) {
this.page = page;
this.widget = new Widget(page);
this.roomSection = new RoomSection(page.getByRole('region', { name: 'Voice call' }));
}
get popout(): PopoutPage {
if (!this.popoutPage) {
throw new Error('Voice call - Popout is not open (call openPopout(Room|Widget) first)');
}
return this.popoutPage;
}
// Open the popout from the room section
async openPopoutRoom(): Promise<void> {
const [popout] = await Promise.all([this.page.waitForEvent('popup'), this.roomSection.openPopout()]);
if (!popout) {
throw new Error('Voice call - Failed to open popout');
}
await popout.waitForLoadState();
popout.on('close', () => {
this.popoutPage = undefined;
});
this.popoutPage = new PopoutPage(popout);
}
// Open the popout from the widget
async openPopoutWidget(): Promise<void> {
const [popout] = await Promise.all([this.page.waitForEvent('popup'), this.widget.openPopout()]);
if (!popout) {
throw new Error('Voice call - Failed to open popout');
}
await popout.waitForLoadState();
popout.on('close', () => {
this.popoutPage = undefined;
});
this.popoutPage = new PopoutPage(popout);
}
}