import { expect, Locator, Page } from '@playwright/test'; import { readFileSync } from 'fs'; type ResponseSelector = (json: any) => boolean; export default abstract class BasePage { readonly rootPage: Page; abstract get(args?: any): Locator; constructor(rootPage: Page) { this.rootPage = rootPage; } async verifyToast({ message }: { message: string }) { await this.rootPage.locator('.ant-message .ant-message-notice-content', { hasText: message }).last().isVisible(); // ensure that the toast is removed from the DOM // await this.rootPage.waitForSelector('.ant-message .ant-message-notice-content', { state: 'hidden' }); } async verifyErrorMessage({ message }: { message: RegExp }) { await expect(this.get().locator('.ant-form-item-explain-error', { hasText: message })).toBeVisible(); } async waitForResponse({ // Playwright action that triggers the request i.e locatorSomething.click() uiAction, httpMethodsToMatch = [], requestUrlPathToMatch, // A function that takes the response body and returns true if the response is the one we are looking for responseJsonMatcher, timeout, responseStatusCodeToMatch = 200, }: { uiAction: () => Promise; requestUrlPathToMatch: string | RegExp; httpMethodsToMatch?: string[]; responseJsonMatcher?: ResponseSelector; timeout?: number; responseStatusCodeToMatch?: number; }) { const [res] = await Promise.all([ this.rootPage.waitForResponse( res => (requestUrlPathToMatch instanceof RegExp ? requestUrlPathToMatch.test(res.url()) : res.url().includes(requestUrlPathToMatch)) && res.status() === responseStatusCodeToMatch && httpMethodsToMatch.includes(res.request().method()), timeout ? { timeout } : undefined ), uiAction(), ]); // handle JSON matcher if provided let isResJsonMatched = true; if (responseJsonMatcher) { try { isResJsonMatched = responseJsonMatcher(await res.json()); } catch { isResJsonMatched = false; } } return isResJsonMatched; } async attachFile({ filePickUIAction, filePath }: { filePickUIAction: Promise; filePath: string[] }) { const [fileChooser] = await Promise.all([ // It is important to call waitForEvent before click to set up waiting. this.rootPage.waitForEvent('filechooser'), // Opens the file chooser. filePickUIAction, ]); await fileChooser.setFiles(filePath); } async downloadAndGetFile({ downloadUIAction }: { downloadUIAction: Promise }) { const [download] = await Promise.all([ // It is important to call waitForEvent before click to set up waiting. this.rootPage.waitForEvent('download'), // Triggers the download. downloadUIAction, ]); // wait for download to complete if (await download.failure()) { throw new Error('Download failed'); } const file = await download.createReadStream(); const data = await new Promise((resolve, reject) => { let data = ''; file?.on('data', chunk => (data += chunk)); file?.on('end', () => resolve(data)); file?.on('error', reject); }); return data as any; } async getClipboardText() { return await this.rootPage.evaluate(() => navigator.clipboard.readText()); } async copyToClipboard({ text }: { text: string }) { await this.rootPage.evaluate(text => navigator.clipboard.writeText(text), text); } async os() { return await this.rootPage.evaluate(() => navigator.platform); } async isMacOs() { return (await this.os()).includes('Mac'); } async dropFile({ imageFilePath, domSelector }: { imageFilePath?: string; domSelector: string }) { const buffer = readFileSync(imageFilePath).toString('base64'); const dataTransfer = await this.rootPage.evaluateHandle( async ({ bufferData, localFileName, localFileType }) => { const dt = new DataTransfer(); const blobData = await fetch(bufferData).then(res => res.blob()); const file = new File([blobData], localFileName, { type: localFileType }); dt.items.add(file); return dt; }, { bufferData: `data:application/octet-stream;base64,${buffer}`, localFileName: 'test.png', localFileType: 'image/png', } ); await this.rootPage.dispatchEvent(domSelector, 'drop', { dataTransfer }); } // async copyImageToClipboard({ imageFilePath, domSelector }: { imageFilePath?: string; domSelector: string }) { // const pasteEvent = await this.rootPage.evaluate(async () => { // const base64 = `data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA // AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO // 9TXL0Y4OHwAAAABJRU5ErkJggg==`; // const response = await fetch(base64); // const blob = await response.blob(); // const clipboardData = new DataTransfer(); // clipboardData.items.add(new File([blob], 'foo.png', { type: blob.type })); // let pasteEvent = new Event('paste', { bubbles: true, cancelable: true }); // pasteEvent = Object.assign(pasteEvent, { // clipboardData, // }); // return pasteEvent; // }); // await this.rootPage.dispatchEvent(domSelector, 'paste', { pasteEvent }); // } }