Play levels 7-13 in the app and pin E2E to the local thirteen-stage backend commit. Co-authored-by: Cursor <cursoragent@cursor.com>
243 lines
15 KiB
TypeScript
243 lines
15 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
import { fetchMe, loginViaUi, logoutViaProfile, registerViaUi, uniqueUsername } from './helpers/auth'
|
|
|
|
test.describe.configure({ mode: 'serial' })
|
|
|
|
async function openLevel(page: Page, name: string, start: string) {
|
|
await page.goto('/map')
|
|
await page.getByRole('button', { name }).click()
|
|
await page.getByRole('button', { name: start }).click()
|
|
await page.getByText('در حال بارگذاری').waitFor({ state: 'detached', timeout: 30_000 }).catch(() => undefined)
|
|
}
|
|
|
|
async function clickAnswer(page: Page, label: string) {
|
|
if (label === 'درست' || label === 'نادرست') {
|
|
await page.getByRole('button', { name: label, exact: true }).click()
|
|
return
|
|
}
|
|
const button = page.getByRole('button', { name: label, exact: true })
|
|
if (await button.count()) {
|
|
await button.first().click()
|
|
return
|
|
}
|
|
await page.getByText(label, { exact: true }).click()
|
|
}
|
|
|
|
async function finishLesson(page: Page, labels: string[]) {
|
|
let cursor = 0
|
|
for (let step = 0; step < 30; step += 1) {
|
|
if (await page.getByText('امتیاز').first().isVisible().catch(() => false)) return
|
|
if (await page.getByText('خوراکی ضروری').isVisible().catch(() => false)) {
|
|
for (let move = 0; move < 4; move += 1) {
|
|
await page.locator('.sort-item').filter({ hasText: 'خوراکی ضروری' }).getByRole('button', { name: '↑' }).click()
|
|
}
|
|
}
|
|
if (cursor < labels.length) {
|
|
const label = labels[cursor]!
|
|
const visible = await page.getByText(label, { exact: true }).isVisible().catch(() => false)
|
|
if (visible) {
|
|
await clickAnswer(page, label)
|
|
cursor += 1
|
|
}
|
|
}
|
|
const finish = page.getByRole('button', { name: 'اتمام درس' })
|
|
if (await finish.isVisible().catch(() => false) && await finish.isEnabled().catch(() => false)) {
|
|
await finish.click()
|
|
await page.getByText('امتیاز').first().waitFor()
|
|
return
|
|
}
|
|
const next = page.getByRole('button', { name: 'بعدی' })
|
|
if (await next.isEnabled().catch(() => false)) await next.click()
|
|
}
|
|
throw new Error('lesson did not finish')
|
|
}
|
|
|
|
async function starsOf(page: Page, levelId: number) {
|
|
const me = await fetchMe(page)
|
|
return me.progress.mapLevels.find((level) => level.id === levelId)?.stars ?? 0
|
|
}
|
|
|
|
test('full thirteen-level journey from the interface', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name !== 'desktop', 'full journey runs once on desktop')
|
|
test.setTimeout(600_000)
|
|
const errors: string[] = []
|
|
page.on('pageerror', (error) => errors.push(error.message))
|
|
page.on('console', (message) => {
|
|
if (message.type() === 'error') errors.push(message.text())
|
|
})
|
|
|
|
const user = uniqueUsername('path')
|
|
await registerViaUi(page, user, 'pass1234', 'مسیر')
|
|
|
|
const lessons: Array<{ name: string; labels: string[] }> = [
|
|
{ name: 'مرحله 1: پول چیه؟', labels: ['مبادله آسانتر کالاها', 'درست', 'چون همه همیشه همان چیز را میخواهند', 'درست', 'پول وسیلهٔ مبادله و نگهداری ارزش است'] },
|
|
{ name: 'مرحله 2: پسانداز', labels: ['بخشی از پول را برای بعد کنار گذاشتن', 'درست', 'برای هدفش برنامه نداشت و همه را خرج کرد', 'درست', 'هدف آینده چیزی است که برایش برنامه میریزیم'] },
|
|
{ name: 'مرحله 3: نیاز و خواسته', labels: ['چیزی که برای زندگی سالم لازم است', 'درست', 'اول نیازها را برطرف کنیم', 'درست'] },
|
|
{ name: 'مرحله 4: طلا چیه؟', labels: ['چون واحد استاندارد وزن طلا است', 'درست', 'زیورآلات و صنعت', 'نادرست', 'طلا دارایی با ارزش است که قیمتش میتواند تغییر کند'] },
|
|
{ name: 'مرحله 5: بازار طلا', labels: ['قیمت بالا و پایین میشود', 'نادرست', 'چون ممکن است وقت کافی برای فکر کردن نداشته باشیم', 'درست', 'فکر کردن و پراکنده نکردن تمام پول در یک دارایی'] },
|
|
{ name: 'مرحله 6: تورم', labels: ['افزایش عمومی قیمتها در طول زمان', 'درست', 'قدرت خرید پول کم شده', 'نادرست', 'ممکن است ارزش واقعیاش کم شود'] },
|
|
]
|
|
for (let index = 0; index < lessons.length; index += 1) {
|
|
const lesson = lessons[index]!
|
|
await openLevel(page, lesson.name, 'شروع درس')
|
|
await finishLesson(page, lesson.labels)
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, index + 1)).toBeGreaterThan(0)
|
|
if (index < 5) {
|
|
await expect(page.getByRole('button', { name: lessons[index + 1]!.name })).toBeEnabled()
|
|
}
|
|
}
|
|
await page.reload()
|
|
expect(await starsOf(page, 6)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 7: بودجهبندی', 'شروع بازی')
|
|
await page.getByRole('button', { name: 'ساخت بودجه' }).click()
|
|
for (let index = 0; index < 4; index += 1) await page.getByRole('button', { name: 'افزایش رفتوآمد' }).click()
|
|
for (let index = 0; index < 5; index += 1) await page.getByRole('button', { name: 'افزایش آب و غذا' }).click()
|
|
for (let index = 0; index < 4; index += 1) await page.getByRole('button', { name: 'افزایش پسانداز برای هدف' }).click()
|
|
await page.getByRole('button', { name: 'بررسی برنامه' }).click()
|
|
await page.getByRole('button', { name: 'تأیید برنامه' }).click()
|
|
await page.getByRole('button', { name: 'بازتنظیم بودجه' }).click()
|
|
for (let index = 0; index < 2; index += 1) await page.getByRole('button', { name: 'افزایش رفتوآمد' }).click()
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 7)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 8: ریسک و بازده', 'شروع چالش')
|
|
await page.getByRole('button', { name: 'دیدن دو پیشنهاد' }).click()
|
|
await page.getByRole('button', { name: 'رفتن به فعالیتها' }).click()
|
|
const risk = [
|
|
'پیشنهاد ب',
|
|
'خیر، ۳۶ فقط یکی از نتیجههای ممکن است',
|
|
'این پول ضروری را وارد پیشنهادهای نامطمئن نکن',
|
|
'پیشنهاد ب امکان نتیجه بالاتر و زیان بیشتری دارد؛ سود بیشتر قطعی نیست',
|
|
'وعده را تضمین معتبر فرض نکن، بررسی کن و از بزرگسال قابل اعتماد کمک بگیر',
|
|
]
|
|
for (let index = 0; index < risk.length; index += 1) {
|
|
await page.getByText(risk[index]!, { exact: true }).click()
|
|
await page.getByRole('button', { name: index === risk.length - 1 ? 'مرور پاسخها' : 'بعدی' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 8)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 9: سبد سرمایهگذاری', 'شروع بازی')
|
|
await page.getByRole('button', { name: 'ساخت سبد' }).click()
|
|
for (let index = 0; index < 4; index += 1) await page.getByRole('button', { name: 'افزایش آفتاب' }).click()
|
|
for (let index = 0; index < 3; index += 1) await page.getByRole('button', { name: 'افزایش باران' }).click()
|
|
for (let index = 0; index < 3; index += 1) await page.getByRole('button', { name: 'افزایش باد' }).click()
|
|
await page.getByRole('button', { name: 'دیدن سه سناریو' }).click()
|
|
await page.getByRole('button', { name: 'رفتن به سؤالها' }).click()
|
|
const portfolio = [
|
|
'نتیجه بیشتر به همان گزینه وابسته میشود',
|
|
'خیر؛ افت همگانی میتواند چند گزینه را همزمان کم کند',
|
|
'خیر؛ رفتار و وابستگی آنها مهم است',
|
|
'خیر؛ یک سناریو آینده را تضمین نمیکند',
|
|
'توجه به هدف، زمان نیاز به پول و ریسک؛ در مسائل واقعی کمک از بزرگسال قابل اعتماد',
|
|
]
|
|
for (let index = 0; index < portfolio.length; index += 1) {
|
|
await page.getByText(portfolio[index]!, { exact: true }).click()
|
|
await page.getByRole('button', { name: index === portfolio.length - 1 ? 'مرور پاسخها' : 'بعدی' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 9)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 10: سود مرکب', 'شروع درس')
|
|
await page.getByRole('button', { name: 'بعدی' }).click()
|
|
await page.getByRole('button', { name: 'دوره بعد' }).click()
|
|
await page.getByRole('button', { name: 'دوره بعد' }).click()
|
|
await page.getByRole('button', { name: 'دوره بعد' }).click()
|
|
await page.getByRole('button', { name: 'بعدی' }).click()
|
|
const compound = [
|
|
'رشد دوره بعد بر موجودی جدید، شامل رشد قبلی، محاسبه میشود',
|
|
'۱۲۱ سکه',
|
|
'چون ۱۰٪ از ۱۱۰ محاسبه شده است',
|
|
'مرکب؛ ۱۳۳٫۱ در برابر ۱۳۰',
|
|
]
|
|
for (const label of compound) {
|
|
await page.getByRole('button', { name: label, exact: true }).click()
|
|
await page.getByRole('button', { name: 'بعدی' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'نادرست', exact: true }).click()
|
|
await page.getByRole('button', { name: 'بعدی' }).click()
|
|
await page.getByRole('button', { name: 'اتمام درس' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 10)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 11: خرید هوشمند', 'شروع بازی')
|
|
await page.getByRole('button', { name: 'شروع خریدها' }).click()
|
|
const shop = [
|
|
['دفتر ۶۰برگ، ۴۰ سکه، تحویل ۵ سکه', 'نیاز را برآورده میکند و هزینه نهاییاش بین گزینههای مناسب کمتر است'],
|
|
['مداد با قیمت نهایی ۲۸، بدون تخفیف', 'برای کالای یکسان، مبلغ نهایی پرداختی مهم است؛ درصد تخفیف بهتنهایی کافی نیست'],
|
|
['فعلاً نخر و اندازه و هزینه نهایی را بپرس، با کمک بزرگسال قابل اعتماد', 'اطلاعات لازم برای تصمیم و مقایسه هنوز کامل نیست'],
|
|
]
|
|
for (let index = 0; index < shop.length; index += 1) {
|
|
await page.getByText(shop[index]![0]!, { exact: true }).click()
|
|
await page.getByText(shop[index]![1]!, { exact: true }).click()
|
|
await page.getByRole('button', { name: index === shop.length - 1 ? 'مرور انتخابها' : 'مأموریت بعد' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 11)).toBeGreaterThan(0)
|
|
|
|
await openLevel(page, 'مرحله 12: هدف مالی', 'شروع چالش')
|
|
await page.getByRole('button', { name: 'ساخت برنامه' }).click()
|
|
await page.getByRole('button', { name: '15 سکه در هفته' }).click()
|
|
await page.getByRole('button', { name: 'تأیید برنامه اولیه' }).click()
|
|
await page.getByRole('button', { name: 'هفته دوم: 5 سکه' }).click()
|
|
await page.getByRole('button', { name: 'ادامه: 15 سکه' }).click()
|
|
await page.getByRole('button', { name: 'رفتن به سؤالها' }).click()
|
|
const goal = [
|
|
'خرید بسته مشخص با قیمت ۱۲۰ و برنامه پسانداز',
|
|
'۹۰ سکه',
|
|
'۶ هفته',
|
|
'نیاز را تأمین کن و مبلغ پسانداز یا زمان هدف را اصلاح کن',
|
|
'برنامه و زمان را دوباره بررسی و بهروز کن',
|
|
]
|
|
for (let index = 0; index < goal.length; index += 1) {
|
|
await page.getByText(goal[index]!, { exact: true }).click()
|
|
await page.getByRole('button', { name: index === goal.length - 1 ? 'مرور پاسخها' : 'بعدی' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 12)).toBeGreaterThan(0)
|
|
await page.reload()
|
|
expect(await starsOf(page, 12)).toBeGreaterThan(0)
|
|
await expect(page.getByRole('button', { name: 'مرحله 14', exact: false })).toHaveCount(0)
|
|
|
|
await openLevel(page, 'مرحله 13: آزمون قلعه', 'شروع آزمون')
|
|
await page.getByRole('button', { name: 'شروع سؤالها' }).click()
|
|
const castle = [
|
|
'مبادله را آسان میکند و مقایسه قیمت را ممکن میسازد',
|
|
'مبلغی متناسب با شرایط را منظم کنار بگذاریم',
|
|
'اول آب آشامیدنی را تأمین کنیم',
|
|
'طلا یک دارایی است و قیمتش میتواند بالا یا پایین برود',
|
|
'خیر',
|
|
'با همان پول، مقدار کمتری از آن کالاها میتوان خرید',
|
|
'رفتوآمد ۳۰، غذا ۲۵، پسانداز ۲۰، تفریح ۱۵، ذخیره ۱۰',
|
|
'پول لازم را وارد پیشنهاد نامطمئن با احتمال زیان نکنیم',
|
|
'خیر؛ چند گزینه ممکن است همزمان افت کنند',
|
|
'۱۲۱',
|
|
'ب، چون ۴۵ در برابر ۵۰ است',
|
|
'۶ هفته',
|
|
]
|
|
for (let index = 0; index < castle.length; index += 1) {
|
|
await page.getByText(castle[index]!, { exact: true }).click()
|
|
await page.getByRole('button', { name: index === castle.length - 1 ? 'مرور پاسخها' : 'بعدی' }).click()
|
|
}
|
|
await page.getByRole('button', { name: 'ثبت نتیجه' }).click()
|
|
await expect(page.getByText('آفرین! مسیر آموزشی قلعه را کامل کردی')).toBeVisible()
|
|
await expect(page.getByRole('button', { name: 'مرحله بعد' })).toHaveCount(0)
|
|
await page.getByRole('button', { name: 'بازگشت به نقشه' }).click()
|
|
expect(await starsOf(page, 13)).toBeGreaterThan(0)
|
|
|
|
await logoutViaProfile(page)
|
|
await loginViaUi(page, user, 'pass1234')
|
|
await page.goto('/map')
|
|
for (let levelId = 1; levelId <= 13; levelId += 1) {
|
|
expect(await starsOf(page, levelId)).toBeGreaterThan(0)
|
|
}
|
|
expect(errors.filter((error) => !/favicon|Download the React DevTools/i.test(error))).toEqual([])
|
|
})
|