Some checks failed
Deploy Web Apps / deploy (push) Has been cancelled
Queue Release Build / prepare (push) Successful in 21s
Queue Release Build / build-linux (push) Successful in 27m44s
Queue Release Build / build-windows (push) Successful in 32m16s
Queue Release Build / finalize (push) Successful in 1m54s
144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import {
|
|
expect,
|
|
type Locator,
|
|
type Page
|
|
} from '@playwright/test';
|
|
|
|
export type ChatDropFilePayload = {
|
|
name: string;
|
|
mimeType: string;
|
|
base64: string;
|
|
};
|
|
|
|
export class ChatMessagesPage {
|
|
readonly composer: Locator;
|
|
readonly composerInput: Locator;
|
|
readonly sendButton: Locator;
|
|
readonly typingIndicator: Locator;
|
|
readonly gifButton: Locator;
|
|
readonly gifPicker: Locator;
|
|
readonly messageItems: Locator;
|
|
|
|
constructor(private page: Page) {
|
|
this.composer = page.locator('app-chat-message-composer');
|
|
this.composerInput = page.getByPlaceholder('Type a message...');
|
|
this.sendButton = page.getByRole('button', { name: 'Send message' });
|
|
this.typingIndicator = page.locator('app-typing-indicator');
|
|
this.gifButton = page.getByRole('button', { name: 'Search KLIPY GIFs' });
|
|
this.gifPicker = page.getByRole('dialog', { name: 'KLIPY GIF picker' });
|
|
this.messageItems = page.locator('[data-message-id]');
|
|
}
|
|
|
|
async waitForReady(): Promise<void> {
|
|
await expect(this.composerInput).toBeVisible({ timeout: 30_000 });
|
|
}
|
|
|
|
async sendMessage(content: string): Promise<void> {
|
|
await this.waitForReady();
|
|
await this.composerInput.fill(content);
|
|
await this.sendButton.click();
|
|
}
|
|
|
|
async typeDraft(content: string): Promise<void> {
|
|
await this.waitForReady();
|
|
await this.composerInput.fill(content);
|
|
}
|
|
|
|
async clearDraft(): Promise<void> {
|
|
await this.waitForReady();
|
|
await this.composerInput.fill('');
|
|
}
|
|
|
|
async attachFiles(files: ChatDropFilePayload[]): Promise<void> {
|
|
await this.waitForReady();
|
|
|
|
await this.composerInput.evaluate((element, payloads: ChatDropFilePayload[]) => {
|
|
const dataTransfer = new DataTransfer();
|
|
|
|
for (const payload of payloads) {
|
|
const binary = atob(payload.base64);
|
|
const bytes = new Uint8Array(binary.length);
|
|
|
|
for (let index = 0; index < binary.length; index++) {
|
|
bytes[index] = binary.charCodeAt(index);
|
|
}
|
|
|
|
dataTransfer.items.add(new File([bytes], payload.name, { type: payload.mimeType }));
|
|
}
|
|
|
|
element.dispatchEvent(new DragEvent('drop', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
dataTransfer
|
|
}));
|
|
}, files);
|
|
}
|
|
|
|
async openGifPicker(): Promise<void> {
|
|
await this.waitForReady();
|
|
await this.gifButton.click();
|
|
await expect(this.gifPicker).toBeVisible({ timeout: 10_000 });
|
|
}
|
|
|
|
async selectFirstGif(): Promise<void> {
|
|
const gifCard = this.gifPicker.getByRole('button', { name: /click to select/i }).first();
|
|
|
|
await expect(gifCard).toBeVisible({ timeout: 10_000 });
|
|
await gifCard.click();
|
|
}
|
|
|
|
getMessageItemByText(text: string): Locator {
|
|
return this.messageItems.filter({
|
|
has: this.page.getByText(text, { exact: false })
|
|
}).last();
|
|
}
|
|
|
|
getMessageImageByAlt(altText: string): Locator {
|
|
return this.page.locator(`[data-message-id] img[alt="${altText}"]`).last();
|
|
}
|
|
|
|
async expectMessageImageLoaded(altText: string): Promise<void> {
|
|
const image = this.getMessageImageByAlt(altText);
|
|
|
|
await expect(image).toBeVisible({ timeout: 20_000 });
|
|
await expect.poll(async () =>
|
|
image.evaluate((element) => {
|
|
const img = element as HTMLImageElement;
|
|
|
|
return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
|
|
}), {
|
|
timeout: 20_000,
|
|
message: `Image ${altText} should fully load in chat`
|
|
}).toBe(true);
|
|
}
|
|
|
|
getEmbedCardByTitle(title: string): Locator {
|
|
return this.page.locator('app-chat-link-embed').filter({
|
|
has: this.page.getByText(title, { exact: true })
|
|
}).last();
|
|
}
|
|
|
|
async editOwnMessage(originalText: string, updatedText: string): Promise<void> {
|
|
const messageItem = this.getMessageItemByText(originalText);
|
|
const editButton = messageItem.locator('button:has(ng-icon[name="lucideEdit"])').first();
|
|
const editTextarea = this.page.locator('textarea.edit-textarea').first();
|
|
const saveButton = this.page.locator('button:has(ng-icon[name="lucideCheck"])').first();
|
|
|
|
await expect(messageItem).toBeVisible({ timeout: 15_000 });
|
|
await messageItem.hover();
|
|
await editButton.click();
|
|
await expect(editTextarea).toBeVisible({ timeout: 10_000 });
|
|
await editTextarea.fill(updatedText);
|
|
await saveButton.click();
|
|
}
|
|
|
|
async deleteOwnMessage(text: string): Promise<void> {
|
|
const messageItem = this.getMessageItemByText(text);
|
|
const deleteButton = messageItem.locator('button:has(ng-icon[name="lucideTrash2"])').first();
|
|
|
|
await expect(messageItem).toBeVisible({ timeout: 15_000 });
|
|
await messageItem.hover();
|
|
await deleteButton.click();
|
|
}
|
|
}
|