首次提交:初始化项目代码
This commit is contained in:
@@ -0,0 +1,819 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
test('test', async ({ page }) => {
|
||||
test.setTimeout(1200000); // 20 分钟:全流程含多项 AI 检查,避免语法检查阶段耗尽预算
|
||||
|
||||
// 错误收集数组
|
||||
const errors: { step: string; error: string; timestamp: string; pageUrl: string }[] = [];
|
||||
|
||||
// 登录信息(用于报告)
|
||||
// 注意:密码含字面量反斜杠,JS 字符串中必须写成 \\,写成 \; 会丢失 \
|
||||
const loginInfo = {
|
||||
email: 'prodream.admin@applify.ai',
|
||||
password: 'SW&`4t\\;/oh2gtSwvz&YeDHk~N5$/xIl'
|
||||
};
|
||||
|
||||
// 辅助函数:执行步骤并捕获错误
|
||||
const executeStep = async (stepName: string, stepFunction: () => Promise<void>) => {
|
||||
try {
|
||||
console.log(`\n[执行] ${stepName}`);
|
||||
await stepFunction();
|
||||
console.log(`[成功] ${stepName}`);
|
||||
} catch (error) {
|
||||
// 提取简洁的错误信息,去掉技术细节
|
||||
let errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// 清理错误信息:只保留第一行主要错误,去掉 Call log 等技术细节
|
||||
const firstLine = errorMessage.split('\n')[0];
|
||||
const cleanError = firstLine.replace(/\s+\(.*?\)/, '').trim();
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const pageUrl = page.url(); // 获取出错时的页面URL
|
||||
|
||||
errors.push({ step: stepName, error: cleanError, timestamp, pageUrl });
|
||||
console.error(`[失败] ${stepName}: ${cleanError}`);
|
||||
console.error(`[页面URL] ${pageUrl}`);
|
||||
// 浏览器已关闭时后续步骤无意义,直接中断
|
||||
if (/has been closed/i.test(cleanError)) {
|
||||
throw error;
|
||||
}
|
||||
// 其他错误继续执行下一步,不中断测试流程
|
||||
}
|
||||
};
|
||||
|
||||
// 步骤1: 访问首页并登录
|
||||
await executeStep('访问首页', async () => {
|
||||
await page.goto('https://prodream.cn/en');
|
||||
await expect(page.getByRole('button', { name: 'Log in' })).toBeVisible();
|
||||
});
|
||||
|
||||
await executeStep('点击登录按钮', async () => {
|
||||
await page.getByRole('button', { name: 'Log in' }).click();
|
||||
await expect(page.getByRole('textbox', { name: 'Email' })).toBeVisible();
|
||||
});
|
||||
|
||||
await executeStep('输入登录信息', async () => {
|
||||
await page.getByRole('textbox', { name: 'Email' }).click();
|
||||
await page.getByRole('textbox', { name: 'Email' }).fill(loginInfo.email);
|
||||
await page.getByRole('textbox', { name: 'Password' }).click();
|
||||
await page.getByRole('textbox', { name: 'Password' }).fill(loginInfo.password);
|
||||
await page.getByRole('button', { name: 'Log in' }).click();
|
||||
// 登录成功后可能直接进入 /students,不一定仍显示「学生工作台」链接
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const url = page.url();
|
||||
if (/\/(cn|en)\/students/.test(url)) return true;
|
||||
if (await page.getByRole('link').filter({ hasText: '学生工作台' }).isVisible().catch(() => false)) return true;
|
||||
if (await page.getByRole('link').filter({ hasText: 'Dreami' }).isVisible().catch(() => false)) return true;
|
||||
return false;
|
||||
}, { timeout: 20000 })
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
// 步骤2: 测试 DreamiExplore
|
||||
await executeStep('进入 DreamiExplore', async () => {
|
||||
await page.getByRole('link').filter({ hasText: 'Dreami' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Hello, Prodream Admin' })).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
await executeStep('测试聊天功能', async () => {
|
||||
const chatInput = page.getByRole('textbox');
|
||||
await chatInput.click();
|
||||
await chatInput.fill('hello');
|
||||
await page.getByRole('button', { name: '发送' }).click();
|
||||
|
||||
// 发送成功:输入框清空(避免误匹配输入框里的 hello)
|
||||
try {
|
||||
await expect(chatInput).toHaveValue('', { timeout: 8000 });
|
||||
} catch {
|
||||
await chatInput.press('Enter');
|
||||
await expect(chatInput).toHaveValue('', { timeout: 10000 });
|
||||
}
|
||||
|
||||
// AI 回复文案不固定:对话区出现任意非空回复即通过
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const replyBlocks = page.locator(
|
||||
'.markdown-body, .prose, [class*="assistant"], [class*="bot"], [class*="ai-message"], [class*="message"]'
|
||||
);
|
||||
const count = await replyBlocks.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const text = (await replyBlocks.nth(i).innerText()).trim();
|
||||
if (text.length > 0 && !/^hello$/i.test(text)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 兜底:出现复制按钮通常意味着已有回复内容
|
||||
return (await page.getByRole('button', { name: /Copy|复制/i }).count()) > 0;
|
||||
}, { timeout: 50000 })
|
||||
.toBeTruthy();
|
||||
});
|
||||
|
||||
// 步骤3: 创建新学生
|
||||
await executeStep('切换英文工作台', async () => {
|
||||
await page.goto('https://prodream.cn/en/students');
|
||||
await expect(page.getByText('Student Name')).toBeVisible();
|
||||
});
|
||||
|
||||
await executeStep('创建新学生', async () => {
|
||||
await page.getByRole('button', { name: 'New Student' }).click();
|
||||
await page.getByRole('textbox', { name: 'Name *', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'Name *', exact: true }).fill('黄子旭测试');
|
||||
// 该下拉组件选中后不一定把值回显在原占位元素上,关闭下拉后继续后续字段填写
|
||||
await page.keyboard.press('Escape');
|
||||
await page.getByRole('textbox', { name: 'Contract Category *' }).click();
|
||||
await page.getByRole('textbox', { name: 'Contract Category *' }).fill('11');
|
||||
await page.getByRole('textbox', { name: 'Contract Name *' }).click();
|
||||
await page.getByRole('textbox', { name: 'Contract Name *' }).fill('11');
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
// 等待学生创建成功,使用 first() 处理多个匹配
|
||||
await expect(page.getByText('黄子旭测试').first()).toBeVisible();
|
||||
});
|
||||
|
||||
// 步骤4: 添加材料 流程
|
||||
|
||||
await executeStep('添加材料', async () => {
|
||||
await page.getByText('黄子旭测试').first().click();
|
||||
await page.getByRole('button', { name: 'Material library' }).click();
|
||||
await page.getByRole('button', { name: 'Add Material' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Manual Add' }).click();
|
||||
await expect(page.getByText('Classification')).toBeVisible();
|
||||
await page.getByRole('textbox', { name: 'Title' }).click();
|
||||
await page.getByRole('textbox', { name: 'Title' }).fill('Community Art & Cultural Education Project(社区艺术与文化教育项目)');
|
||||
await page.getByRole('paragraph').filter({ hasText: /^$/ }).click();
|
||||
await page.locator('.tiptap').fill('在高二至高三期间,我每周投入约3小时参与社区艺术与文化教育项目,协助为当地儿童开设艺术工作坊。我主要负责示范水彩晕染、湿画法与层次叠加等技法,并教授楷书与行书的基础笔画练习,帮助孩子们理解中西方艺术表现的差异。');
|
||||
await page.getByRole('button', { name: 'icon Get Suggestions' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'More Suggestions' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await expect(page.getByRole('button', { name: 'Community Art' })).toBeVisible({ timeout: 15000 });
|
||||
// 等待页面稳定后再点击
|
||||
await page.waitForTimeout(1000);
|
||||
});
|
||||
|
||||
/* // 旧版思路探索检查点
|
||||
await executeStep('探索 Essay Idea', async () => {
|
||||
await page.getByRole('button', { name: 'Idea exploration' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Select an essay prompt or' })).toBeVisible();
|
||||
});
|
||||
|
||||
await executeStep('加载 Recommendation', async () => {
|
||||
// 点击 Recommendation 按钮
|
||||
await page.waitForTimeout(5000);
|
||||
await page.getByRole('button', { name: 'icon Recommendation' }).click();
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('等待 Recommendation 加载完成...');
|
||||
await expect(page.getByRole('heading', { name: 'Recommendation Material' })).toBeVisible({ timeout: 120000 });
|
||||
console.log('Recommendation Material 已出现');
|
||||
});
|
||||
|
||||
await executeStep('生成 Essay Idea', async () => {
|
||||
await page.getByRole('button', { name: 'icon Generate Essay Idea' }).click();
|
||||
await page.getByRole('button', { name: 'Generate' }).click();
|
||||
// AI 生成需要时间,增加超时
|
||||
await expect(page.getByRole('heading', { name: 'Essay Idea' })).toBeVisible({ timeout: 60000 });
|
||||
});
|
||||
|
||||
await executeStep('生成 Essay', async () => {
|
||||
await page.getByRole('button', { name: 'icon Generate Essay' }).click();
|
||||
await page.getByPlaceholder('Please enter the expected word count').click();
|
||||
await page.getByPlaceholder('Please enter the expected word count').fill('100');
|
||||
await page.getByRole('button', { name: 'Generate' }).click();
|
||||
|
||||
console.log('等待 Essay 生成完成...');
|
||||
const loadingMessage = page.getByText(/Generating the Draft. This usually takes about 30 seconds. Please wait./i);
|
||||
|
||||
// 第一步:先等待加载消息出现(确保生成已开始)
|
||||
console.log('等待加载消息出现...');
|
||||
await loadingMessage.waitFor({ state: 'visible', timeout: 10000 });
|
||||
console.log('加载消息已出现,文书正在生成中...');
|
||||
|
||||
// 第二步:等待加载消息消失(最多等待 3 分钟)
|
||||
await loadingMessage.waitFor({ state: 'hidden', timeout: 180000 });
|
||||
console.log('Essay 生成完成,加载消息已消失');
|
||||
|
||||
// 第三步:等待 10 秒钟让页面稳定
|
||||
console.log('等待 10 秒让页面稳定...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('页面已稳定,继续下一步');
|
||||
});
|
||||
|
||||
// 步骤5: 各种检查功能
|
||||
const dismissCheckTipIfAny = async () => {
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /提示|无需重新检测|无变更/i });
|
||||
if (await tip.isVisible({ timeout: 2500 }).catch(() => false)) {
|
||||
const ok = tip.getByRole('button', { name: /确认|Confirm|OK|关闭/i }).first();
|
||||
if (await ok.isVisible().catch(() => false)) await ok.click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const waitToolCheckDone = async (kind: string) => {
|
||||
console.log(`等待 ${kind} 加载完成...`);
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (await page.getByRole('button', { name: /Re-check|重新检测/i }).first().isVisible().catch(() => false)) {
|
||||
return 'recheck';
|
||||
}
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /无需重新检测|无变更|no need|unchanged/i });
|
||||
if (await tip.isVisible().catch(() => false)) {
|
||||
await tip.getByRole('button', { name: /确认|Confirm|OK/i }).click();
|
||||
return 'already-checked';
|
||||
}
|
||||
if (await page.getByRole('heading', { name: /suggestions/i }).isVisible().catch(() => false)) {
|
||||
return 'suggestions';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
{ timeout: 200000 }
|
||||
)
|
||||
.not.toEqual('');
|
||||
console.log(`${kind} 完成`);
|
||||
};
|
||||
|
||||
await executeStep('语法检查 (Grammar Check)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').first().click();
|
||||
const startGrammar = page.getByRole('button', { name: /Start Grammar Check|开始语法检测/i }).first();
|
||||
await expect(startGrammar).toBeVisible({ timeout: 20000 });
|
||||
await startGrammar.click();
|
||||
await dismissCheckTipIfAny();
|
||||
await waitToolCheckDone('Grammar Check');
|
||||
});
|
||||
|
||||
await executeStep('查重检查 (Plagiarism Check)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(1).click();
|
||||
const startBtn = page.getByRole('button', { name: /Start Plagiarism Check|开始查重/i }).first();
|
||||
await expect(startBtn).toBeVisible({ timeout: 20000 });
|
||||
await startBtn.click();
|
||||
await dismissCheckTipIfAny();
|
||||
await waitToolCheckDone('Plagiarism Check');
|
||||
});
|
||||
|
||||
await executeStep('AI检测 (AI Detection)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(2).click();
|
||||
const startBtn = page.getByRole('button', { name: /Start AI Detection|开始AI/i }).first();
|
||||
await expect(startBtn).toBeVisible({ timeout: 20000 });
|
||||
await startBtn.click();
|
||||
await dismissCheckTipIfAny();
|
||||
console.log('等待 Start AI Detection 加载完成...');
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (await page.getByRole('button', { name: 'GPTZero' }).isVisible().catch(() => false)) return 'gptzero';
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /无需重新检测|无变更|no need/i });
|
||||
if (await tip.isVisible().catch(() => false)) {
|
||||
await tip.getByRole('button', { name: /确认|Confirm|OK/i }).click();
|
||||
return 'already-checked';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
{ timeout: 120000 }
|
||||
)
|
||||
.not.toEqual('');
|
||||
if (await page.getByRole('button', { name: 'GPTZero' }).isVisible().catch(() => false)) {
|
||||
console.log('GPTZero 已出现');
|
||||
await expect(page.getByRole('button', { name: /ZeroGPT/i })).toBeVisible({ timeout: 80000 });
|
||||
console.log('ZeroGPT 已出现');
|
||||
}
|
||||
console.log('AI检测功能正常');
|
||||
});
|
||||
|
||||
await executeStep('人性化处理 (Humanize)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(3).click();
|
||||
// 等待界面切换和按钮加载
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByRole('button', { name: /Start Humanize|开始人性化/i }).click();
|
||||
console.log('等待 Start Humanize 加载完成...');
|
||||
await expect(page.getByRole('button', { name: 'Re-check' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Re-check 已出现');
|
||||
});
|
||||
|
||||
await executeStep('润色 (Polish)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(4).click();
|
||||
await page.getByRole('button', { name: /Start Polish|开始润色/i }).click();
|
||||
console.log('等待 Start Polish 加载完成...');
|
||||
await expect(page.getByRole('button', { name: 'Accept All' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Accept All 已出现');
|
||||
});
|
||||
|
||||
await executeStep('评分 (Get Rated)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(5).click();
|
||||
await page.getByRole('button', { name: 'Get Rated' }).click();
|
||||
console.log('等待 Get Rated 加载完成...');
|
||||
await expect(page.getByRole('heading', { name: 'Your essay rating is:' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Your essay rating is: 已出现');
|
||||
});
|
||||
|
||||
await executeStep('Improvement 检查', async () => {
|
||||
await page.getByRole('tab', { name: 'Improvement' }).click();
|
||||
await expect.poll(async () => {
|
||||
const congratulationsVisible = await page
|
||||
.getByRole('heading', { name: 'Congratulations! All have been revised' })
|
||||
.isVisible();
|
||||
const acceptAllVisible = await page
|
||||
.getByRole('button', { name: 'Accept all' })
|
||||
.isVisible();
|
||||
const suggestionsVisible = await page
|
||||
.getByRole('button', { name: 'Suggestions' })
|
||||
.isVisible();
|
||||
return congratulationsVisible || acceptAllVisible || suggestionsVisible;
|
||||
}, { timeout: 100000 }).toBeTruthy();
|
||||
console.log('Improvement 检查通过:任一结果已出现');
|
||||
});
|
||||
|
||||
// 步骤6: 进入 Essay Writing
|
||||
await executeStep('进入 Essay Writing', async () => {
|
||||
await page.getByRole('button', { name: '黄子旭测试-Essay Writing' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Idea exploration' })).toBeVisible({ timeout: 15000 });
|
||||
});
|
||||
|
||||
// 步骤7: 探索 Essay Idea
|
||||
await executeStep('探索 Essay Idea', async () => {
|
||||
await page.getByRole('button', { name: 'Idea exploration' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Select an essay prompt or' })).toBeVisible();
|
||||
});
|
||||
|
||||
// 步骤8: 加载 Recommendation
|
||||
await executeStep('加载 Recommendation', async () => {
|
||||
await page.getByRole('button', { name: 'icon Recommendation' }).click();
|
||||
console.log('等待 Recommendation 加载完成...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('Recommendation 加载完成');
|
||||
});
|
||||
|
||||
// 步骤9: 生成 Essay Idea
|
||||
await executeStep('生成 Essay Idea', async () => {
|
||||
await page.getByRole('button', { name: 'icon Generate Essay Idea' }).click();
|
||||
await page.getByRole('button', { name: 'Generate' }).click();
|
||||
console.log('等待 Essay Idea 生成...');
|
||||
await expect(page.getByRole('button', { name: 'Student Guide' })).toBeVisible({ timeout: 60000 });
|
||||
console.log('Essay Idea 生成完成');
|
||||
});
|
||||
|
||||
console.log('等待 10 秒让页面稳定...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('页面已稳定,继续下一步');
|
||||
|
||||
// 步骤10: 测试 Student Guide 功能
|
||||
await executeStep('测试 Student Guide 展开/收起', async () => {
|
||||
// 展开 Student Guide
|
||||
await page.getByRole('button', { name: 'Student Guide' }).click();
|
||||
console.log('等待 Student Guide 加载完成...');
|
||||
await expect(page.getByRole('button', { name: 'Translate' })).toBeVisible({ timeout: 110000 });
|
||||
// 测试复制功能
|
||||
await page.getByRole('button', { name: 'Copy' }).click();
|
||||
await expect(page.getByText('Copied to clipboard')).toBeVisible({ timeout: 110000 });
|
||||
await page.getByLabel('Student Guide').getByRole('button').filter({ hasText: /^$/ }).click();
|
||||
console.log('等待 10 秒让页面稳定...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('页面已稳定,继续下一步');
|
||||
});
|
||||
|
||||
// 步骤11: 测试翻译功能
|
||||
await executeStep('测试翻译功能', async () => {
|
||||
// 点击翻译按钮,英文 → 中文
|
||||
await page.getByRole('button', { name: 'Translate' }).click();
|
||||
await page.getByRole('button', { name: '简体中文' }).click();
|
||||
console.log('点击翻译按钮,等待翻译完成...');
|
||||
|
||||
// 等待翻译完成(等待按钮重新变为可用状态或等待固定时间)
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 验证翻译按钮仍然存在(表示功能可用)
|
||||
await expect(page.getByRole('button', { name: 'Translate' })).toBeVisible({ timeout: 5000 });
|
||||
console.log('翻译功能正常(已切换为中文)');
|
||||
});
|
||||
|
||||
// 步骤12: 测试复制功能
|
||||
await executeStep('测试复制到剪贴板', async () => {
|
||||
await page.getByRole('button', { name: 'Copy' }).click();
|
||||
await expect(page.getByText('Copied to clipboard')).toBeVisible({ timeout: 5000 });
|
||||
console.log('复制功能正常');
|
||||
console.log('等待 10 秒让页面稳定...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('页面已稳定,继续下一步');
|
||||
});
|
||||
|
||||
// 步骤13: 测试再次翻译
|
||||
await executeStep('测试翻译返回英文', async () => {
|
||||
await page.getByRole('button', { name: 'Translate' }).click();
|
||||
await page.getByRole('button', { name: '英文' }).click();
|
||||
await page.waitForTimeout(2000);
|
||||
console.log('翻译返回英文完成');
|
||||
});
|
||||
|
||||
// 步骤14: 再次测试复制
|
||||
await executeStep('再次测试复制功能', async () => {
|
||||
await page.getByRole('button', { name: 'Copy' }).click();
|
||||
await expect(page.getByText('Copied to clipboard')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// 步骤15: 测试重新生成 Essay Idea
|
||||
await executeStep('测试重新生成 Essay Idea', async () => {
|
||||
await page.getByRole('button', { name: 'Regenerate' }).click();
|
||||
console.log('等待重新生成 Essay Idea...');
|
||||
await expect(page.getByRole('button', { name: 'Student Guide' })).toBeVisible({ timeout: 60000 });
|
||||
console.log('重新生成完成');
|
||||
});
|
||||
|
||||
// 步骤16: 生成 Outline
|
||||
await executeStep('生成 Outline', async () => {
|
||||
await page.getByRole('button', { name: 'icon Generate Outline First' }).click();
|
||||
await page.getByRole('button', { name: 'Generate' }).click();
|
||||
console.log('等待 Outline 生成...');
|
||||
await expect(page.getByRole('heading', { name: 'Untitled Document' })).toBeVisible({ timeout: 150000 });
|
||||
console.log('Outline 生成完成');
|
||||
await page.waitForTimeout(5000);
|
||||
});
|
||||
|
||||
// 步骤17: 生成 Draft
|
||||
await executeStep('生成 Draft', async () => {
|
||||
await page.getByRole('button', { name: 'icon Generate Draft' }).click();
|
||||
await page.getByPlaceholder('Please enter the expected word count').click();
|
||||
await page.getByPlaceholder('Please enter the expected word count').fill('100');
|
||||
await page.getByRole('button', { name: 'Generate' }).click();
|
||||
console.log('等待 Draft 生成完成...');
|
||||
const loadingMessage = page.getByText(/Generating the Draft. This usually takes about 30 seconds. Please wait./i);
|
||||
// 第一步:先等待加载消息出现(确保生成已开始)
|
||||
console.log('等待加载消息出现...');
|
||||
await loadingMessage.waitFor({ state: 'visible', timeout: 10000 });
|
||||
console.log('加载消息已出现,文书正在生成中...');
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
// 步骤18: 进入当前学生 Essay Writing 首页
|
||||
await executeStep('进入 Essay Writing', async () => {
|
||||
const createOutlineHome = page.getByRole('button', { name: /Create Outline Organize an|Create Outline/i }).first();
|
||||
if (!(await createOutlineHome.isVisible().catch(() => false))) {
|
||||
const homeBtn = page.getByRole('button', { name: /黄子旭测试-Essay Writing|.+-Essay Writing$|.-文书写作$/i }).first();
|
||||
if (await homeBtn.isVisible().catch(() => false)) {
|
||||
await homeBtn.click();
|
||||
} else {
|
||||
await page.getByRole('button', { name: /Essay Writing|文书写作/i }).first().click();
|
||||
}
|
||||
}
|
||||
await expect(page.getByRole('button', { name: /Create Outline Organize an|Create Outline/i }).first()).toBeVisible({
|
||||
timeout: 15000,
|
||||
});
|
||||
});
|
||||
|
||||
// 步骤19: 直接生成 Outline
|
||||
await executeStep('直接生成 Outline', async () => {
|
||||
await page.getByRole('button', { name: /Create Outline Organize an|Create Outline/i }).first().click();
|
||||
await expect
|
||||
.poll(async () => /outline-create|\/outline\//.test(page.url()), { timeout: 20000 })
|
||||
.toBeTruthy();
|
||||
await expect(page.getByRole('heading', { name: /Select an essay prompt|Select Material/i }).first()).toBeVisible({
|
||||
timeout: 20000,
|
||||
});
|
||||
|
||||
// 选择一个 material(未选时 Generate Outline 为 disabled)
|
||||
const communityArt = page.getByRole('button', { name: /Community Art/i }).first();
|
||||
//const mathDoc = page.getByRole('button', { name: /math\.docx/i }).first();
|
||||
const materialCard = page
|
||||
.getByRole('button')
|
||||
.filter({ has: page.getByRole('heading', { level: 2 }) })
|
||||
.filter({ hasNotText: /Add material|Download|Delete|Recommendation|Customization|Select Material|Essay Idea/i })
|
||||
.first();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await communityArt.isVisible().catch(() => false)) ||
|
||||
// (await mathDoc.isVisible().catch(() => false)) ||
|
||||
(await materialCard.isVisible().catch(() => false)),
|
||||
{ timeout: 30000 }
|
||||
)
|
||||
.toBeTruthy();
|
||||
|
||||
if (await communityArt.isVisible().catch(() => false)) {
|
||||
await communityArt.click();
|
||||
console.log('已选择材料: Community Art');
|
||||
//} else if (await mathDoc.isVisible().catch(() => false)) {
|
||||
// await mathDoc.click();
|
||||
// console.log('已选择材料: math.docx');
|
||||
} else {
|
||||
await expect(materialCard).toBeVisible({ timeout: 10000 });
|
||||
await materialCard.click();
|
||||
}
|
||||
|
||||
const selectedHint = page.getByText(/[1-9]\d*\/\d+\s*selected|[1-9].*已选/i);
|
||||
await expect(selectedHint.first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const generateOutline = page.getByRole('button', { name: /Generate Outline|生成大纲/i }).last();
|
||||
await expect(generateOutline).toBeEnabled({ timeout: 15000 });
|
||||
await generateOutline.click();
|
||||
console.log('已点击 Generate Outline');
|
||||
|
||||
// 可能弹出额外建议对话框,需再点一次 Generate
|
||||
const adviceDialog = page.getByRole('dialog');
|
||||
if (await adviceDialog.isVisible({ timeout: 8000 }).catch(() => false)) {
|
||||
const dlgGenerate = adviceDialog.getByRole('button', { name: /^Generate$|^生成$/i });
|
||||
if (await dlgGenerate.isVisible().catch(() => false)) {
|
||||
await dlgGenerate.click();
|
||||
}
|
||||
}
|
||||
|
||||
console.log('等待 Outline 生成...');
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (/\/outline\/[^/?]+/.test(page.url()) && !/outline-create/.test(page.url())) return true;
|
||||
return page.getByRole('heading', { name: /Untitled Document|未命名/i }).first().isVisible().catch(() => false);
|
||||
},
|
||||
{ timeout: 180000 }
|
||||
)
|
||||
.toBeTruthy();
|
||||
console.log('Outline 生成完成,额外等待 30 秒...');
|
||||
await page.waitForTimeout(30000);
|
||||
//点击generate first draft
|
||||
});
|
||||
|
||||
// 步骤20: Outline 生成后返回 Essay Writing 首页
|
||||
await executeStep('进入 Essay Writing', async () => {
|
||||
// Outline 页面包屑:{学生名}-Essay Writing(不硬编码具体学生)
|
||||
const homeBtn = page.getByRole('button', { name: /.+-Essay Writing$|黄子旭测试-Essay Writing|.-文书写作$/i }).first();
|
||||
await expect(homeBtn).toBeVisible({ timeout: 15000 });
|
||||
await homeBtn.click();
|
||||
await expect(page.getByRole('button', { name: /Create Outline Organize an/i }).first()).toBeVisible({ timeout: 15000 });
|
||||
});
|
||||
|
||||
// 步骤21: 直接生成 Essay / Draft
|
||||
await executeStep('直接生成 Essay', async () => {
|
||||
await page.getByRole('button', { name: /Generate Draft Turn available|Generate first draft|Generate First Draft/i }).first().click();
|
||||
console.log('直接生成 Essay');
|
||||
|
||||
// 进入 essay-create;若仍停在首页,再点 Generate First Draft
|
||||
const onCreatePage = async () => /essay-create/.test(page.url());
|
||||
if (!(await onCreatePage())) {
|
||||
const firstDraft = page.getByRole('button', { name: /Generate First Draft|Generate first draft/i }).last();
|
||||
if (await firstDraft.isVisible().catch(() => false)) {
|
||||
await firstDraft.click();
|
||||
}
|
||||
}
|
||||
await expect.poll(async () => onCreatePage(), { timeout: 20000 }).toBeTruthy();
|
||||
console.log('已进入 Draft 材料页, url=', page.url());
|
||||
|
||||
const communityArt = page.getByRole('button', { name: /General Community Art|Community Art/i }).first();
|
||||
const materialCard = page
|
||||
.getByRole('button')
|
||||
.filter({ has: page.getByRole('heading', { level: 2 }) })
|
||||
.filter({ hasNotText: /Add material|Download|Delete|Recommendation|Customization|Select Material|Essay Idea/i })
|
||||
.first();
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await communityArt.isVisible().catch(() => false)) || (await materialCard.isVisible().catch(() => false)),
|
||||
{ timeout: 20000 }
|
||||
)
|
||||
.toBeTruthy();
|
||||
if (await communityArt.isVisible().catch(() => false)) {
|
||||
await communityArt.click();
|
||||
} else {
|
||||
await materialCard.click();
|
||||
}
|
||||
await expect(page.getByText(/[1-9]\d*\/\d+\s*selected|[1-9].*已选/i).first()).toBeVisible({ timeout: 10000 });
|
||||
console.log('素材已选择');
|
||||
|
||||
const generateDraftBtn = page
|
||||
.getByRole('button', { name: /Generate First Draft|Generate first draft|icon Generate Draft/i })
|
||||
.filter({ hasNotText: /Turn available/i })
|
||||
.last();
|
||||
await expect(generateDraftBtn).toBeEnabled({ timeout: 15000 });
|
||||
await generateDraftBtn.click();
|
||||
console.log('已点击 Generate First Draft');
|
||||
|
||||
const wordCount = page.getByPlaceholder(/expected word count|word count|expected|字数/i).first();
|
||||
if (await wordCount.isVisible({ timeout: 8000 }).catch(() => false)) {
|
||||
await wordCount.fill('100');
|
||||
}
|
||||
const confirmGenerate = page.getByRole('dialog').getByRole('button', { name: /^Generate$|^生成$/i });
|
||||
if (await confirmGenerate.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await confirmGenerate.click();
|
||||
} else if (await page.getByRole('button', { name: /^Generate$/ }).isVisible().catch(() => false)) {
|
||||
await page.getByRole('button', { name: /^Generate$/ }).click();
|
||||
}
|
||||
console.log('已确认 Generate');
|
||||
|
||||
//等待 Essay 生成完成
|
||||
console.log('等待 Essay 生成完成...');
|
||||
const loadingMessage = page.getByText(/Generating the Draft. This usually takes about 30 seconds. Please wait./i);
|
||||
console.log('等待加载消息出现...');
|
||||
try {
|
||||
await loadingMessage.first().waitFor({ state: 'visible', timeout: 15000 });
|
||||
console.log('加载消息已出现,文书正在生成中...');
|
||||
await loadingMessage.first().waitFor({ state: 'hidden', timeout: 180000 });
|
||||
console.log('Essay 生成完成,加载消息已消失');
|
||||
} catch {
|
||||
// 加载文案可能变化:以编辑器内容出现作为兜底
|
||||
await expect(page.locator('.tiptap, [contenteditable="true"]').first()).toBeVisible({ timeout: 180000 });
|
||||
console.log('未捕获到加载文案,但编辑器已可见');
|
||||
}
|
||||
console.log('等待 10 秒让页面稳定...');
|
||||
await page.waitForTimeout(10000);
|
||||
console.log('页面已稳定');
|
||||
});
|
||||
|
||||
// 步骤5: 各种检查功能
|
||||
const dismissCheckTipIfAny = async () => {
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /提示|无需重新检测|无变更/i });
|
||||
if (await tip.isVisible({ timeout: 2500 }).catch(() => false)) {
|
||||
const ok = tip.getByRole('button', { name: /确认|Confirm|OK|关闭/i }).first();
|
||||
if (await ok.isVisible().catch(() => false)) await ok.click();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const waitToolCheckDone = async (kind: string) => {
|
||||
console.log(`等待 ${kind} 加载完成...`);
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (await page.getByRole('button', { name: /Re-check|重新检测/i }).first().isVisible().catch(() => false)) {
|
||||
return 'recheck';
|
||||
}
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /无需重新检测|无变更|no need|unchanged/i });
|
||||
if (await tip.isVisible().catch(() => false)) {
|
||||
await tip.getByRole('button', { name: /确认|Confirm|OK/i }).click();
|
||||
return 'already-checked';
|
||||
}
|
||||
if (await page.getByRole('heading', { name: /suggestions/i }).isVisible().catch(() => false)) {
|
||||
return 'suggestions';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
{ timeout: 200000 }
|
||||
)
|
||||
.not.toEqual('');
|
||||
console.log(`${kind} 完成`);
|
||||
};
|
||||
|
||||
await executeStep('语法检查 (Grammar Check)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').first().click();
|
||||
const startGrammar = page.getByRole('button', { name: /Start Grammar Check|开始语法检测/i }).first();
|
||||
await expect(startGrammar).toBeVisible({ timeout: 20000 });
|
||||
await startGrammar.click();
|
||||
await dismissCheckTipIfAny();
|
||||
await waitToolCheckDone('Grammar Check');
|
||||
});
|
||||
|
||||
await executeStep('查重检查 (Plagiarism Check)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(1).click();
|
||||
const startBtn = page.getByRole('button', { name: /Start Plagiarism Check|开始查重/i }).first();
|
||||
await expect(startBtn).toBeVisible({ timeout: 20000 });
|
||||
await startBtn.click();
|
||||
await dismissCheckTipIfAny();
|
||||
await waitToolCheckDone('Plagiarism Check');
|
||||
});
|
||||
|
||||
await executeStep('AI检测 (AI Detection)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(2).click();
|
||||
const startBtn = page.getByRole('button', { name: /Start AI Detection|开始AI/i }).first();
|
||||
await expect(startBtn).toBeVisible({ timeout: 20000 });
|
||||
await startBtn.click();
|
||||
await dismissCheckTipIfAny();
|
||||
console.log('等待 Start AI Detection 加载完成...');
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
if (await page.getByRole('button', { name: 'GPTZero' }).isVisible().catch(() => false)) return 'gptzero';
|
||||
const tip = page.getByRole('dialog').filter({ hasText: /无需重新检测|无变更|no need/i });
|
||||
if (await tip.isVisible().catch(() => false)) {
|
||||
await tip.getByRole('button', { name: /确认|Confirm|OK/i }).click();
|
||||
return 'already-checked';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
{ timeout: 120000 }
|
||||
)
|
||||
.not.toEqual('');
|
||||
if (await page.getByRole('button', { name: 'GPTZero' }).isVisible().catch(() => false)) {
|
||||
console.log('GPTZero 已出现');
|
||||
await expect(page.getByRole('button', { name: /ZeroGPT/i })).toBeVisible({ timeout: 80000 });
|
||||
console.log('ZeroGPT 已出现');
|
||||
}
|
||||
console.log('AI检测功能正常');
|
||||
});
|
||||
|
||||
await executeStep('人性化处理 (Humanize)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(3).click();
|
||||
// 等待界面切换和按钮加载
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByRole('button', { name: /Start Humanize|开始人性化/i }).click();
|
||||
console.log('等待 Start Humanize 加载完成...');
|
||||
await expect(page.getByRole('button', { name: 'Re-check' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Re-check 已出现');
|
||||
});
|
||||
|
||||
await executeStep('润色 (Polish)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(4).click();
|
||||
await page.getByRole('button', { name: /Start Polish|开始润色/i }).click();
|
||||
console.log('等待 Start Polish 加载完成...');
|
||||
await expect(page.getByRole('button', { name: 'Accept All' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Accept All 已出现');
|
||||
});
|
||||
|
||||
await executeStep('评分 (Get Rated)', async () => {
|
||||
const toolRail = page.getByRole('complementary').filter({ has: page.getByRole('listitem') }).first();
|
||||
await toolRail.getByRole('listitem').nth(5).click();
|
||||
await page.getByRole('button', { name: 'Get Rated' }).click();
|
||||
console.log('等待 Get Rated 加载完成...');
|
||||
await expect(page.getByRole('heading', { name: 'Your essay rating is:' })).toBeVisible({ timeout: 200000 });
|
||||
console.log('Your essay rating is: 已出现');
|
||||
});
|
||||
|
||||
await executeStep('Improvement 检查', async () => {
|
||||
await page.getByRole('tab', { name: 'Improvement' }).click();
|
||||
await expect.poll(async () => {
|
||||
const congratulationsVisible = await page
|
||||
.getByRole('heading', { name: 'Congratulations! All have been revised' })
|
||||
.isVisible();
|
||||
const acceptAllVisible = await page
|
||||
.getByRole('button', { name: 'Accept all' })
|
||||
.isVisible();
|
||||
const suggestionsVisible = await page
|
||||
.getByRole('button', { name: 'Suggestions' })
|
||||
.isVisible();
|
||||
return congratulationsVisible || acceptAllVisible || suggestionsVisible;
|
||||
}, { timeout: 100000 }).toBeTruthy();
|
||||
console.log('Improvement 检查通过:任一结果已出现');
|
||||
});
|
||||
// 最后输出错误汇总
|
||||
console.log('\n\n========== 测试执行完成 ==========');
|
||||
|
||||
// 生成错误报告文件
|
||||
const generateErrorReport = () => {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const reportPath = path.join(process.cwd(), `test-error-report-${timestamp}.txt`);
|
||||
|
||||
let report = '========== 测试错误报告 ==========\n\n';
|
||||
report += `报告生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
|
||||
|
||||
// 第一部分:登录账号信息(只显示一次)
|
||||
report += '【登录账号信息】\n';
|
||||
report += `账号: ${loginInfo.email}\n`;
|
||||
report += `密码: ${loginInfo.password}\n`;
|
||||
report += `测试环境: https://prodream.cn/en\n\n`;
|
||||
report += `${'='.repeat(60)}\n\n`;
|
||||
|
||||
if (errors.length === 0) {
|
||||
report += '✅ 所有功能测试通过,未发现问题!\n';
|
||||
} else {
|
||||
report += `发现 ${errors.length} 个问题,详情如下:\n\n`;
|
||||
|
||||
// 每个错误按照格式:问题功能 + 页面链接
|
||||
errors.forEach((err, index) => {
|
||||
report += `【问题 ${index + 1}】\n`;
|
||||
report += `问题功能: ${err.step}\n`;
|
||||
report += `页面链接: ${err.pageUrl}\n`;
|
||||
report += `错误详情: ${err.error}\n`;
|
||||
report += `发生时间: ${new Date(err.timestamp).toLocaleString('zh-CN')}\n`;
|
||||
report += '\n';
|
||||
});
|
||||
|
||||
report += `${'='.repeat(60)}\n`;
|
||||
report += `\n说明: 测试过程中遇到错误会自动跳过并继续执行后续步骤。\n`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(reportPath, report, 'utf-8');
|
||||
return reportPath;
|
||||
};
|
||||
|
||||
if (errors.length === 0) {
|
||||
console.log('✅ 所有步骤执行成功!');
|
||||
} else {
|
||||
console.log(`\n⚠️ 发现 ${errors.length} 个问题:\n`);
|
||||
errors.forEach((err, index) => {
|
||||
console.log(`【问题 ${index + 1}】`);
|
||||
console.log(` 问题功能: ${err.step}`);
|
||||
console.log(` 页面链接: ${err.pageUrl}`);
|
||||
console.log(` 错误详情: ${err.error}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// 生成并保存错误报告文件
|
||||
const reportPath = generateErrorReport();
|
||||
console.log(`📄 详细错误报告已保存到: ${reportPath}`);
|
||||
console.log(`\n账号: ${loginInfo.email}`);
|
||||
console.log(`密码: ${loginInfo.password}\n`);
|
||||
|
||||
// 不抛出错误,让测试标记为通过,但在日志中记录所有失败步骤
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user