mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16:15 -08:00
* fix(import): consistent add-source forms with masked passwords and URL errors - Mask the Xtream password in add and edit (and the Stalker one in edit) behind a shared PasswordVisibilityToggleDirective: one translated "Show password" label, state in aria-pressed, type="button". - Give the Xtream server URL its own mat-error and a neutral hint instead of the EPG file error; give the M3U URL a mat-error. - Use "Playlist title" in every add form, "MAC address" casing, a single ellipsis in "Validating portal…" and one "Add playlist" submit label; translate the method radiogroup's aria-label. - Show Stalker refusals inline under the portal URL (role="status", like the Xtream connection test), translated in the template and cleared by edits; translate the snackbars for outcomes that close the dialog. - Translate new strings into all locales; reuse the identical Stalker URL error translations; fix MAC casing and ellipses; drop unused keys. - Unit specs per form, edit-dialog spec, new add-source-forms web E2E; update E2E locators; UI guidelines Forms section; Stalker contract. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(import): mask the password again when an add form is cleared Clear erased the password but left the visibility toggle on, so the next password typed in the Xtream or Stalker form showed in plain text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
4.2 KiB
TypeScript
118 lines
4.2 KiB
TypeScript
import type { Locator, Page } from '@playwright/test';
|
|
import { setInputValue } from './e2e-helpers';
|
|
import { expect, test } from './fixtures';
|
|
import {
|
|
XTREAM_MOCK_SERVER,
|
|
interceptPwaProviderRequests,
|
|
resetPwaMockServers,
|
|
} from './sources-pwa.helpers';
|
|
|
|
/**
|
|
* The three add-source forms share one vocabulary: the same name label, a
|
|
* masked password with a show/hide toggle, a URL error of their own and the
|
|
* single "Add playlist" submit. Credentials are the Xtream mock's fixtures.
|
|
*/
|
|
test.beforeEach(async ({ page, request }) => {
|
|
await resetPwaMockServers(request);
|
|
await interceptPwaProviderRequests(page);
|
|
await page.goto('/');
|
|
});
|
|
|
|
async function openMethod(page: Page, method: RegExp): Promise<Locator> {
|
|
await page.getByRole('button', { name: 'Add playlist' }).click();
|
|
const dialog = page.locator('mat-dialog-container');
|
|
await expect(dialog).toBeVisible();
|
|
await dialog.getByRole('radio', { name: method }).click();
|
|
return dialog;
|
|
}
|
|
|
|
async function expectPasswordToggle(dialog: Locator): Promise<void> {
|
|
const password = dialog.locator('#password');
|
|
const toggle = dialog.getByRole('button', { name: 'Show password' });
|
|
|
|
await expect(password).toHaveAttribute('type', 'password');
|
|
await expect(toggle).toHaveAttribute('aria-pressed', 'false');
|
|
await toggle.click();
|
|
await expect(password).toHaveAttribute('type', 'text');
|
|
await expect(toggle).toHaveAttribute('aria-pressed', 'true');
|
|
await toggle.click();
|
|
await expect(password).toHaveAttribute('type', 'password');
|
|
await expect(toggle).toHaveAttribute('aria-pressed', 'false');
|
|
}
|
|
|
|
test('@sources @pwa adds an Xtream source through the dialog', async ({
|
|
page,
|
|
}) => {
|
|
const dialog = await openMethod(page, /Xtream credentials/i);
|
|
const serverUrl = dialog.locator('#serverUrl');
|
|
|
|
await expect(
|
|
dialog.getByText('Server address, e.g. http://host:port')
|
|
).toBeVisible();
|
|
await setInputValue(serverUrl, 'panel.example');
|
|
await serverUrl.blur();
|
|
await expect(dialog.locator('mat-error')).toHaveText(
|
|
'Enter a valid http:// or https:// URL.'
|
|
);
|
|
// The field used to borrow the EPG source error.
|
|
await expect(dialog.getByText(/absolute file path/i)).toHaveCount(0);
|
|
|
|
await setInputValue(
|
|
dialog.getByRole('textbox', { name: 'Playlist title' }),
|
|
'Dialog Xtream Source'
|
|
);
|
|
await setInputValue(serverUrl, XTREAM_MOCK_SERVER);
|
|
await expect(dialog.locator('mat-error')).toHaveCount(0);
|
|
await setInputValue(dialog.locator('#username'), 'user1');
|
|
await setInputValue(dialog.locator('#password'), 'pass1');
|
|
await expectPasswordToggle(dialog);
|
|
await expect(dialog.locator('#password')).toHaveValue('pass1');
|
|
|
|
const add = dialog.getByRole('button', {
|
|
name: 'Add playlist',
|
|
exact: true,
|
|
});
|
|
await expect(add).toBeEnabled({ timeout: 10_000 });
|
|
await add.click();
|
|
await expect(dialog).toBeHidden();
|
|
await page.waitForURL(/xtreams.*vod/);
|
|
});
|
|
|
|
test('@sources @pwa explains an invalid M3U playlist URL', async ({ page }) => {
|
|
const dialog = await openMethod(page, /M3U URL/i);
|
|
const url = dialog.getByRole('textbox', {
|
|
name: 'Playlist URL (m3u, m3u8)',
|
|
});
|
|
|
|
await expect(
|
|
dialog.getByRole('textbox', { name: 'Playlist title' })
|
|
).toBeVisible();
|
|
await setInputValue(url, 'playlist.m3u');
|
|
await url.blur();
|
|
|
|
await expect(dialog.locator('mat-error')).toHaveText(
|
|
'Enter a valid http:// or https:// URL.'
|
|
);
|
|
await expect(
|
|
dialog.getByRole('button', { name: 'Add playlist', exact: true })
|
|
).toBeDisabled();
|
|
});
|
|
|
|
test('@sources @pwa gives the Stalker form the shared labels and a masked password', async ({
|
|
page,
|
|
}) => {
|
|
const dialog = await openMethod(page, /Stalker portal/i);
|
|
|
|
await expect(
|
|
dialog.getByRole('textbox', { name: 'Playlist title' })
|
|
).toBeVisible();
|
|
await expect(
|
|
dialog.getByRole('textbox', { name: 'MAC address', exact: true })
|
|
).toBeVisible();
|
|
await setInputValue(dialog.locator('#password'), 'secret');
|
|
await expectPasswordToggle(dialog);
|
|
await expect(
|
|
dialog.getByRole('button', { name: 'Add playlist', exact: true })
|
|
).toBeVisible();
|
|
});
|