* -------------------------------------------------------------------------
* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
import { expect, type FrameLocator, type Page, WebSocket } from '@playwright/test';
import { FrameworkPage, FileExploreDialogPage } from '@/page-object';
import { FilePath, WEBSOCKET_URL } from './constants';
let iterationNum = 0;
export async function importData(page: Page, filePath: string = FilePath.TEXT): Promise<void> {
if (iterationNum > 3) {
return;
}
iterationNum++;
try {
const frameworkPage = new FrameworkPage(page);
const fileExploreDialogPage = new FileExploreDialogPage(page);
const { importDataBtn, projectList } = frameworkPage;
const { mainDialog, input, confirmBtn, fileTree } = fileExploreDialogPage;
await importDataBtn.click();
await expect(mainDialog).toBeVisible();
const emptyBlock = fileTree.locator('.el-tree__empty-block');
await emptyBlock.waitFor({ state: 'hidden', timeout: 2000 });
await input.waitFor({ state: 'visible', timeout: 2000 });
await input.click();
await input.fill(filePath);
await input.press('Enter');
await page.waitForTimeout(1000);
await confirmBtn.click();
await mainDialog.waitFor({ state: 'hidden', timeout: 3000 });
const currentProject = projectList.getByText(filePath, { exact: true }).first();
await currentProject.waitFor({ state: 'visible' });
await expect(currentProject).toBeVisible();
} catch {
await page.reload();
await importData(page, filePath);
}
iterationNum = 0;
}
export async function clearAllData(page: Page, ws?: Promise<WebSocket>): Promise<void> {
const frameworkPage = new FrameworkPage(page);
const { settingsBtn, deleteAllBtn, deleteAllDialog, deleteAllConfirmBtn, projectList } = frameworkPage;
const isSettingsBtnDisabled = await settingsBtn.evaluate((el) => el.classList.contains('disabled'));
if (isSettingsBtnDisabled) {
return;
}
await settingsBtn.click();
await deleteAllBtn.click();
await expect(deleteAllDialog).toBeVisible();
await deleteAllConfirmBtn.click();
if (ws) {
await waitForResponse(await ws, (res) => res?.command === 'remote/reset');
}
const checkbtn = page.locator('.dragContainer').first().getByText('All');
await checkbtn.waitFor({ state: 'hidden' });
await page.waitForTimeout(500);
const elementText = await projectList.textContent();
await expect(elementText?.trim()).toBe('');
await page.waitForTimeout(1000);
}
export async function waitForWebSocketEvent<T>(page: Page, matchCondition: (payload: any) => boolean): Promise<T> {
return new Promise((resolve, reject) => {
page.on('websocket', (ws) => {
const url = ws.url();
if (url !== WEBSOCKET_URL) {
return;
}
ws.on('framereceived', (event) => {
if (typeof event.payload !== 'string') {
return;
}
try {
const res = JSON.parse(event.payload);
if (matchCondition(res)) {
resolve(res);
}
} catch (error) {
reject(new Error(`WebSocket 消息解析失败: ${error}`));
}
});
});
});
}
export function setupWebSocketListener(page: Page): Promise<WebSocket> {
return new Promise((resolve) => {
page.on('websocket', (ws) => {
const url = ws.url();
if (url === WEBSOCKET_URL) {
resolve(ws);
}
});
});
}
export function waitForResponse(ws: WebSocket, matchCondition: (payload: any) => boolean): Promise<any> {
return new Promise((resolve, reject) => {
const frameHandler = (event: any): void => {
if (typeof event.payload !== 'string') {
return;
}
try {
const res = JSON.parse(event.payload);
if (matchCondition(res)) {
ws.off('framereceived', frameHandler);
resolve(res);
}
} catch (error) {
reject(new Error(`WebSocket 消息解析失败: ${error}`));
}
};
ws.on('framereceived', frameHandler);
});
}
export async function setCompare(
page: Page,
frame: FrameLocator,
{
baseline,
comparison,
}: {
baseline: string;
comparison: string;
} = { baseline: FilePath.DB_RANK_0, comparison: FilePath.DB_RANK_1 },
): Promise<void> {
const frameworkPage = new FrameworkPage(page);
const rank1 = frameworkPage.getRankLocator(baseline);
await rank1.click({ button: 'right' });
const setBaselineBtn = frameworkPage.page.getByText('Set as Baseline Data');
await setBaselineBtn.click();
const rank2 = frameworkPage.getRankLocator(comparison);
await rank2.click({ button: 'right' });
const setComparisonBtn = frameworkPage.page.getByText('Set as Comparison Data');
await setComparisonBtn.click();
await page.mouse.move(0,0);
await frame.getByText('loading').first().waitFor({ state: 'hidden' });
await frameworkPage.mouseOut();
}
export async function waitForAllParsed(page: Page, tabPage: any): Promise<void> {
const frameworkPage = new FrameworkPage(page);
await frameworkPage.clickTab('timeline');
const timelineFrame = page.frameLocator('#Timeline');
const parsingProgress = timelineFrame.locator('.unit-info .ant-progress').first();
await parsingProgress.waitFor({ state: 'hidden' });
await tabPage.goto();
}
* 在页面上框选一段范围
* @param page Playwright Page 实例
* @param start 起点坐标 { x, y }
* @param end 终点坐标 { x, y }
* @param steps 鼠标移动的步数(越大越平滑,默认 20)
*/
export async function dragSelect(
page: Page,
start: { x: number; y: number },
end: { x: number; y: number },
steps = 10,
) {
await page.mouse.move(start.x, start.y);
await page.mouse.down();
await page.mouse.move(end.x, end.y, { steps });
await page.mouse.up();
}