Files
essaycheck/essaypro.spec.ts

820 lines
37 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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`);
// 不抛出错误,让测试标记为通过,但在日志中记录所有失败步骤
}
});