Files
ersatztv/web/e2e/ffmpeg-pacing.spec.ts
T
timothyandtimothy ed8b602445
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 11s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 25s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m9s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m41s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m23s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 6m23s
feat(735): bound the numeric FFmpeg profile fields with a 422, and expose readrate pacing (#847)
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-26 22:05:38 +00:00

53 lines
2.4 KiB
TypeScript

import { expect, test, type Page } from '@playwright/test';
import { PASSWORD, USERNAME } from './credentials';
async function claimAndSignIn(page: Page) {
await page.goto('/app');
const setupUser = page.getByPlaceholder('Choose a username');
if (await setupUser.isVisible().catch(() => false)) {
await setupUser.fill(USERNAME);
await page.getByPlaceholder('Choose a password').fill(PASSWORD);
await page.getByPlaceholder('Re-enter the password').fill(PASSWORD);
} else {
await page.getByPlaceholder('Username', { exact: true }).fill(USERNAME);
await page.getByPlaceholder('Password', { exact: true }).fill(PASSWORD);
}
await page.getByRole('button', { name: /Create|Sign in/ }).first().click();
await expect(page.getByRole('button', { name: new RegExp(USERNAME) })).toBeVisible({ timeout: 15000 });
}
test('the readrate pacing fields render, validate and save', async ({ page }) => {
await claimAndSignIn(page);
await page.goto('/app/ffmpeg-profiles/1');
const readRate = page.getByLabel('Read rate', { exact: true });
const catchup = page.getByLabel('Read rate catchup', { exact: true });
await expect(readRate).toBeVisible({ timeout: 15000 });
await expect(catchup).toBeVisible();
// unset renders as an empty box showing the built-in default as a placeholder
await expect(readRate).toHaveValue('');
await expect(readRate).toHaveAttribute('placeholder', '1.05');
await expect(catchup).toHaveAttribute('placeholder', '6.0');
// the cross-field bound is enforced before the request leaves the browser: the screen surfaces
// it as a badge and disables Save
const save = page.getByRole('button', { name: 'Save profile' });
await catchup.fill('1');
await expect(page.getByText('Read rate catchup must be greater than the read rate (1.05)')).toBeVisible();
await expect(save).toBeDisabled();
// a valid pair saves and survives a reload through the read path
await readRate.fill('1.2');
await catchup.fill('4');
await expect(save).toBeEnabled();
await save.click();
// saving returns to the list, so re-enter the editor: that reload is the READ path
await expect(page.getByRole('button', { name: 'Save profile' })).toHaveCount(0, { timeout: 15000 });
await page.goto('/app/ffmpeg-profiles/1');
await expect(page.getByLabel('Read rate', { exact: true })).toHaveValue('1.2', { timeout: 15000 });
await expect(page.getByLabel('Read rate catchup', { exact: true })).toHaveValue('4');
});