import { expect, test } from '@playwright/test' async function login(page: import('@playwright/test').Page, email: string, home: RegExp) { await page.goto('http://127.0.0.1:5173/login') await page.getByLabel(/ایمیل|Email/).fill(email) await page.getByLabel(/رمز عبور|Password/).fill('password') await page.getByRole('button', { name: /ورود|Sign in/ }).click() await expect(page).toHaveURL(home) } test('Builder switches between Quick Edit and desktop canvas at Phase 7 viewports', async ({ page }) => { await login(page, 'designer@microlearn.test', /\/app\/dashboard$/) for (const width of [375, 430, 768, 1024, 1280, 1440, 1920]) { await page.setViewportSize({ width, height: width < 768 ? 812 : 900 }) await page.goto('http://127.0.0.1:5173/app/builder-preview') if (width < 768) { await expect(page.getByRole('region', { name: 'ویرایش سریع موبایل' })).toBeVisible() await expect(page.getByRole('button', { name: 'افزودن بلاک', exact: true })).toBeVisible() } else { await expect(page.locator('.builder-canvas')).toBeVisible() } expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), `${width}px overflow`).toBe(true) } await page.setViewportSize({ width: 375, height: 812 }) await page.getByRole('button', { name: /ویرایش بلاک بهترین روش‌های ایمنی/ }).click() await expect(page.getByRole('dialog', { name: 'ویرایش بلاک در موبایل' })).toBeVisible() await page.keyboard.press('Escape') await expect(page.getByRole('dialog', { name: 'ویرایش بلاک در موبایل' })).toBeHidden() }) test('Learner Home is action-led, responsive, offline-aware and accessible', async ({ page, context }) => { const runtimeErrors: string[] = [] page.on('pageerror', error => runtimeErrors.push(error.message)) page.on('console', message => { if (message.type() === 'error') runtimeErrors.push(message.text()) }) await page.setViewportSize({ width: 430, height: 932 }) await login(page, 'maryam@microlearn.test', /\/learn\/home$/) await expect(page.getByRole('navigation', { name: 'ناوبری یادگیرنده' }).getByRole('link')).toHaveCount(5) await expect(page.getByRole('heading', { name: 'ادامه یادگیری', level: 2 })).toBeVisible() await expect(page.getByRole('heading', { name: 'جلسه امروز', level: 2 })).toBeVisible() await expect(page.getByRole('heading', { name: 'موعدهای نزدیک', level: 2 })).toBeVisible() await expect(page.getByRole('heading', { name: 'اعلان‌های مهم', level: 2 })).toBeVisible() await expect(page.locator('.learner-primary-action')).toHaveCount(1) for (const width of [320, 375, 430, 768]) { await page.setViewportSize({ width, height: width < 768 ? 812 : 900 }) expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), `${width}px learner overflow`).toBe(true) await expect(page.getByRole('navigation', { name: 'ناوبری یادگیرنده' })).toBeVisible() expect(await page.getByRole('navigation', { name: 'ناوبری یادگیرنده' }).evaluate(element => getComputedStyle(element).position)).toBe('fixed') } await page.setViewportSize({ width: 430, height: 932 }) await context.setOffline(true) await page.evaluate(() => window.dispatchEvent(new Event('offline'))) await expect(page.getByText('آفلاین هستید')).toBeVisible() await expect(page.locator('.learner-next-action')).toBeVisible() await context.setOffline(false) await page.evaluate(() => window.dispatchEvent(new Event('online'))) const account = page.getByRole('button', { name: 'بازکردن حساب کاربری' }) await account.click() await expect(page.getByRole('dialog', { name: 'حساب کاربری' })).toBeFocused() await page.keyboard.press('Escape') await expect(account).toBeFocused() await account.click() await page.getByRole('button', { name: 'English' }).click() await page.getByRole('button', { name: 'Change theme' }).click() await expect(page.locator('html')).toHaveAttribute('dir', 'ltr') await expect(page.locator('html')).toHaveAttribute('lang', 'en') await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark') await expect(page.getByRole('navigation', { name: 'Learner navigation' })).toBeVisible() expect(runtimeErrors).toEqual([]) }) test('My Learning works as a searchable responsive portfolio', async ({ page, context }) => { const runtimeErrors: string[] = [] page.on('pageerror', error => runtimeErrors.push(error.message)) page.on('console', message => { if (message.type() === 'error') runtimeErrors.push(message.text()) }) await page.setViewportSize({ width: 430, height: 932 }) await login(page, 'maryam@microlearn.test', /\/learn\/home$/) await page.goto('http://127.0.0.1:5173/learn/my-learning') await expect(page.getByRole('heading', { name: 'یادگیری‌های من', level: 1 })).toBeVisible() await expect(page.getByRole('searchbox', { name: 'جستجو در یادگیری‌های من' })).toBeVisible() await expect(page.getByRole('tab', { name: 'در حال انجام' })).toHaveAttribute('aria-selected', 'true') await expect(page.getByRole('navigation', { name: 'ناوبری یادگیرنده' }).getByRole('link', { name: 'یادگیری من' })).toHaveAttribute('aria-current', 'page') const filterButton = page.getByRole('button', { name: /^(?:فیلترها.*|بازکردن فیلترها)$/ }) await filterButton.click() const filtersDialog = page.getByRole('dialog', { name: 'فیلتر و مرتب‌سازی' }) await expect(filtersDialog).toBeVisible() await filtersDialog.getByRole('combobox').last().selectOption('progress') await page.keyboard.press('Escape') await expect(filterButton).toHaveAttribute('aria-expanded', 'false') const overflowButton = page.getByRole('button', { name: /عملیات / }).first() if (await overflowButton.count()) { await overflowButton.click() await expect(page.getByRole('menu')).toBeVisible() await expect(page.getByRole('menuitem', { name: 'مشاهده جزئیات' })).toBeVisible() await page.keyboard.press('Escape') await expect(page.getByRole('menu')).toBeHidden() } for (const width of [320, 375, 430, 768]) { await page.setViewportSize({ width, height: width < 768 ? 812 : 900 }) expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), `${width}px portfolio overflow`).toBe(true) const targets = page.locator('.my-learning-segments button, .my-learning-filter-trigger') const heights = await targets.evaluateAll(elements => elements.map(element => element.getBoundingClientRect().height)) expect(heights.every(height => height >= 44), `${width}px portfolio touch targets`).toBe(true) } await context.setOffline(true) await page.evaluate(() => window.dispatchEvent(new Event('offline'))) await expect(page.getByText('آفلاین هستید')).toBeVisible() await expect(page.getByRole('heading', { name: 'یادگیری‌های من', level: 1 })).toBeVisible() await context.setOffline(false) await page.evaluate(() => window.dispatchEvent(new Event('online'))) expect(runtimeErrors).toEqual([]) }) test('Daily Learning creates a focused, persistent and offline-aware session', async ({ page, context }) => { const runtimeErrors: string[] = [] page.on('pageerror', error => runtimeErrors.push(error.message)) page.on('console', message => { if (message.type() === 'error') runtimeErrors.push(message.text()) }) await page.setViewportSize({ width: 430, height: 932 }) await login(page, 'maryam@microlearn.test', /\/learn\/home$/) await page.evaluate(() => { localStorage.removeItem('microlearn.daily.history'); localStorage.removeItem('microlearn.daily.duration'); sessionStorage.removeItem('microlearn.daily.active') }) await page.goto('http://127.0.0.1:5173/learn/daily') await expect(page.getByRole('heading', { name: 'جلسه امروز', level: 1 })).toBeVisible() await expect(page.getByText('امروز چقدر وقت داری؟')).toBeVisible() await expect(page.getByRole('button', { name: '۱۰ دقیقه' })).toHaveAttribute('aria-pressed', 'true') await expect(page.getByRole('heading', { name: 'جلسه امروز شما', level: 2 })).toBeVisible() await expect(page.getByRole('button', { name: 'شروع جلسه' })).toHaveCount(1) await page.getByRole('button', { name: '۱۵ دقیقه' }).click() expect(await page.evaluate(() => localStorage.getItem('microlearn.daily.duration'))).toBe('15') const why = page.getByText('چرا این محتوا پیشنهاد شده؟') await why.click() await expect(why.locator('xpath=..')).toHaveAttribute('open', '') for (const width of [320, 375, 430, 768]) { await page.setViewportSize({ width, height: width < 768 ? 812 : 900 }) expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), `${width}px daily overflow`).toBe(true) const targets = page.locator('.daily-time-selector button, .daily-primary-action') const heights = await targets.evaluateAll(elements => elements.map(element => element.getBoundingClientRect().height)) expect(heights.every(height => height >= 44), `${width}px daily touch targets`).toBe(true) } await context.setOffline(true) await page.evaluate(() => window.dispatchEvent(new Event('offline'))) await expect(page.getByText('آفلاین هستید')).toBeVisible() await expect(page.getByRole('heading', { name: 'جلسه امروز شما', level: 2 })).toBeVisible() await context.setOffline(false) await page.evaluate(() => window.dispatchEvent(new Event('online'))) const activityCount = await page.locator('.daily-activity-list > li').count() await page.getByRole('button', { name: 'شروع جلسه' }).click() await expect(page).toHaveURL(/\/learn\/player\/.*daily=1/) for (let index = 0; index < activityCount; index += 1) { const before = page.url() await page.getByRole('button', { name: 'تکمیل فعالیت' }).click() if (index < activityCount - 1) await expect.poll(() => page.url()).not.toBe(before) } await expect(page).toHaveURL(/\/learn\/daily\?completed=1/) await expect(page.locator('.daily-completion')).toBeFocused() expect(runtimeErrors).toEqual([]) }) test('Course Player keeps progress and touch actions usable at all target viewports', async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }) await login(page, 'maryam@microlearn.test', /\/learn\/home$/) const continueLink = page.locator('.learner-next-action').first() await expect(continueLink).toBeVisible() await continueLink.click() await expect(page).toHaveURL(/\/learn\/player\//) await expect(page.getByRole('progressbar', { name: 'پیشرفت دوره' })).toBeVisible() await expect(page.getByText(/درس .* از/)).toBeVisible() for (const width of [375, 430, 768, 1024, 1280, 1440, 1920]) { await page.setViewportSize({ width, height: width < 768 ? 812 : 900 }) expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), `${width}px player overflow`).toBe(true) const actions = page.locator('.player-footer .button') await expect(actions).toHaveCount(3) const heights = await actions.evaluateAll(elements => elements.map(element => element.getBoundingClientRect().height)) expect(heights.every(height => height >= 44), `${width}px player touch targets`).toBe(true) } })