import { expect, test, type Page } from '@playwright/test'; async function login(page: Page, username: string) { await page.addInitScript(() => localStorage.setItem('pipu.locale', 'ru')); await page.goto('/login'); await page.getByLabel('Логин', { exact: true }).fill(username); await page.getByRole('textbox', { name: 'Пароль', exact: true }).fill(`${username}-password`); await page.getByRole('button', { name: 'Войти', exact: true }).click(); await expect(page.getByRole('button', { name: 'Ещё', exact: true })).toBeVisible(); } async function fits(page: Page) { await expect.poll(() => page.evaluate(() => { const main = document.querySelector('main')!; return Math.max(document.documentElement.scrollWidth - innerWidth, main.scrollWidth - main.clientWidth); })).toBeLessThanOrEqual(1); } for (const [width, username] of [[320, 'mobile-admin'], [390, 'moderator'], [430, 'alice']] as const) { test(`mobile ${width}: ${username} navigation, forms and search`, async ({ page }, info) => { test.setTimeout(60_000); await page.setViewportSize({ width, height: 844 }); await login(page, username); await fits(page); const bottom = page.getByRole('navigation', { name: 'Навигация' }).filter({ has: page.getByRole('button', { name: 'Ещё', exact: true }) }); await expect(bottom.getByRole('link')).toHaveCount(3); await page.getByRole('button', { name: 'Ещё', exact: true }).click(); const dialog = page.getByRole('dialog', { name: 'Навигация' }); await expect(dialog).toBeVisible(); await expect(dialog.getByRole('link', { name: 'Дизлайки', exact: true })).toBeVisible(); if (username !== 'alice') { await expect(dialog.getByRole('link', { name: 'Пользователи', exact: true })).toBeVisible(); await expect(dialog.getByRole('link', { name: 'Очередь', exact: true })).toBeVisible(); await expect(dialog.getByRole('link', { name: 'Хранилища', exact: true })).toHaveCount(username === 'mobile-admin' ? 1 : 0); await dialog.getByRole('link', { name: 'Пользователи', exact: true }).click(); await expect(dialog).not.toBeVisible(); await expect(page.getByRole('heading', { name: 'Пользователи', exact: true })).toBeVisible(); await fits(page); await page.screenshot({ path: info.outputPath('users.png') }); await page.getByRole('button', { name: /Действия.*alice/ }).click(); const menu = page.getByRole('menu'); await expect(menu).toBeVisible(); const rect = await menu.boundingBox(); expect(rect!.x).toBeGreaterThanOrEqual(0); expect(rect!.x + rect!.width).toBeLessThanOrEqual(width); await page.keyboard.press('Escape'); await page.getByRole('button', { name: 'Создать пользователя', exact: true }).click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.screenshot({ path: info.outputPath('create-user.png') }); await page.getByRole('button', { name: 'Закрыть', exact: true }).click(); } else { await expect(dialog.getByRole('link', { name: 'Пользователи', exact: true })).toHaveCount(0); await page.getByRole('button', { name: 'Закрыть', exact: true }).click(); } await page.getByRole('button', { name: 'Меню профиля' }).click(); await page.getByRole('menuitem', { name: 'Настройки', exact: true }).click(); await expect(page.getByRole('heading', { name: 'Настройки', exact: true })).toBeVisible(); await fits(page); await page.screenshot({ path: info.outputPath('settings.png') }); for (const name of ['Слева', 'По центру', 'Справа']) { await page.getByRole('button', { name, exact: true }).click(); await page.getByRole('combobox', { name: 'Поиск', exact: true }).fill('кино'); await expect(page.getByRole('option', { name: /Кино/ }).first()).toBeVisible(); await fits(page); await page.getByRole('combobox', { name: 'Поиск', exact: true }).press('Escape'); } if (username !== 'alice') { await page.getByRole('button', { name: 'Ещё', exact: true }).click(); await page.getByRole('dialog').getByRole('link', { name: 'Очередь', exact: true }).click(); await expect(page.getByRole('heading', { name: 'Очередь на добавление' })).toBeVisible(); await expect(page.locator('audio')).toBeVisible(); await fits(page); await page.getByRole('checkbox', { name: /^Выбрать Длинное/ }).check(); await expect(page.getByRole('region', { name: 'Действия с выбранными' })).toBeVisible(); await fits(page); await page.screenshot({ path: info.outputPath('upload-queue.png') }); await page.getByRole('region', { name: 'Действия с выбранными' }).getByRole('button', { name: 'Отклонить' }).click(); await expect(page.getByRole('dialog')).toBeVisible(); await page.getByRole('button', { name: 'Закрыть', exact: true }).click(); } if (username === 'mobile-admin') { await page.getByRole('button', { name: 'Ещё', exact: true }).click(); await page.getByRole('dialog').getByRole('link', { name: 'Хранилища', exact: true }).click(); await expect(page.getByRole('article', { name: 'Музыкальное хранилище' })).toBeVisible(); await fits(page); await page.getByRole('button', { name: 'Шаблоны путей', exact: true }).click(); await page.getByRole('button', { name: 'Добавить шаблон', exact: true }).click(); await fits(page); await page.screenshot({ path: info.outputPath('patterns.png') }); } }); } for (const width of [390, 1280]) { test(`player artist opens without a browser URL preview at ${width}px`, async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await login(page, 'alice'); await page.setViewportSize({ width, height: 844 }); const box = page.getByRole('combobox', { name: 'Поиск', exact: true }); await box.fill('кино'); await box.press('Enter'); await page.getByRole('link', { name: /Группа крови/ }).first().click(); await page.getByRole('listitem').first().click(); const artist = page.getByRole('contentinfo', { name: 'Плеер' }).getByRole('link', { name: 'Кино', exact: true }); await artist.hover(); await expect(artist).not.toHaveAttribute('href'); expect(await artist.evaluate((el) => Boolean(el.closest('[title], a[href]')))).toBe(false); if (width === 390) { await artist.focus(); await page.keyboard.press('Enter'); } else { await artist.click(); } await expect(page).toHaveURL(/\/artist\//); await expect(page.getByRole('heading', { name: 'Кино', exact: true })).toBeVisible(); }); } test('mobile player keeps the island above navigation and exposes the queue', async ({ page }, info) => { await page.setViewportSize({ width: 320, height: 740 }); await login(page, 'alice'); const box = page.getByRole('combobox', { name: 'Поиск', exact: true }); await box.fill('кино'); await box.press('Enter'); await page.getByRole('link', { name: /Группа крови/ }).first().click(); await page.getByRole('listitem').first().click(); const player = page.getByRole('contentinfo', { name: 'Плеер' }); await expect(player.getByRole('button', { name: 'Очередь', exact: true })).toBeVisible(); expect((await player.getByText('Группа крови', { exact: true }).boundingBox())!.width).toBeGreaterThan(220); const artist = player.getByRole('link', { name: 'Кино', exact: true }); await expect(artist).toBeVisible(); const artistArea = await artist.locator('..').boundingBox(); expect(artistArea!.width).toBeGreaterThan(220); const trackInfo = await player.getByText('Группа крови', { exact: true }).boundingBox(); const play = await player.getByRole('button', { name: /^(Пауза|Слушать)$/ }).boundingBox(); expect(trackInfo!.y + trackInfo!.height).toBeLessThanOrEqual(play!.y); await page.screenshot({ path: info.outputPath('player-readable.png') }); await fits(page); await page.getByRole('button', { name: 'Меню профиля' }).click(); await page.getByRole('menuitem', { name: 'Настройки', exact: true }).click(); await page.getByRole('button', { name: 'Остров', exact: true }).click(); const nav = page.getByRole('navigation').filter({ has: page.getByRole('button', { name: 'Ещё', exact: true }) }); await expect.poll(async () => { const p = await player.boundingBox(); const n = await nav.boundingBox(); return p!.y + p!.height <= n!.y + 1; }).toBe(true); await player.getByRole('button', { name: 'Очередь', exact: true }).click(); await expect(page.getByRole('complementary', { name: 'Очередь', exact: true })).toBeVisible(); await page.screenshot({ path: info.outputPath('player-queue.png') }); });