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
400 lines
11 KiB
TypeScript
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);
|
|
}
|
|
}
|