test: add profile validation e2e tests and fix pre-existing failures
Add 7 new e2e tests verifying profile form validation in both manage and admin UIs: invalid phone number, phone normalization, E.164 hint attributes, and admin-side email/phone/picture URL validation errors. Fix 3 pre-existing test failures: - Replace invalid seeded phone number (+1234567890) with valid E.164 (+12025551234) that was causing profile update tests to fail - Update email validation error assertion to match actual pydantic message (value_error type uses raw message, not label-prefixed)
This commit is contained in:
parent
752bf87b7c
commit
2dfa3f3bff
3 changed files with 97 additions and 8 deletions
|
|
@ -129,7 +129,67 @@ test.describe('Admin pages', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// 8. Groups update
|
// 8. Profile validation
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
test.describe('Profile validation', () => {
|
||||||
|
test('invalid email shows error', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto(`/admin/users/${fixtures.admin_userid}`);
|
||||||
|
|
||||||
|
// Bypass HTML5 email validation so the form can submit
|
||||||
|
await page.locator('#email').evaluate(el => el.type = 'text');
|
||||||
|
await page.fill('#email', 'not-an-email');
|
||||||
|
|
||||||
|
await page.click('section:has(h2:has-text("Profile")) button[type="submit"]');
|
||||||
|
|
||||||
|
const alert = page.locator('#profile-status [role="alert"]');
|
||||||
|
await expect(alert).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(alert).toContainText('valid email address');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid phone number shows error', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto(`/admin/users/${fixtures.admin_userid}`);
|
||||||
|
|
||||||
|
// Bypass HTML5 pattern validation so the form can submit
|
||||||
|
await page.locator('#phone_number').evaluate(el => el.removeAttribute('pattern'));
|
||||||
|
await page.fill('#phone_number', 'not-a-phone');
|
||||||
|
|
||||||
|
await page.click('section:has(h2:has-text("Profile")) button[type="submit"]');
|
||||||
|
|
||||||
|
const alert = page.locator('#profile-status [role="alert"]');
|
||||||
|
await expect(alert).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(alert).toContainText('valid phone number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid picture URL shows error', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto(`/admin/users/${fixtures.admin_userid}`);
|
||||||
|
|
||||||
|
// Bypass HTML5 URL validation so the form can submit
|
||||||
|
await page.locator('#picture').evaluate(el => el.type = 'text');
|
||||||
|
await page.fill('#picture', 'not-a-url');
|
||||||
|
|
||||||
|
await page.click('section:has(h2:has-text("Profile")) button[type="submit"]');
|
||||||
|
|
||||||
|
const alert = page.locator('#profile-status [role="alert"]');
|
||||||
|
await expect(alert).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(alert).toContainText('Picture URL');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone input has E.164 hint attributes', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto(`/admin/users/${fixtures.admin_userid}`);
|
||||||
|
|
||||||
|
const phoneInput = page.locator('#phone_number');
|
||||||
|
await expect(phoneInput).toHaveAttribute('type', 'tel');
|
||||||
|
await expect(phoneInput).toHaveAttribute('pattern', '\\+[0-9 ]+');
|
||||||
|
await expect(phoneInput).toHaveAttribute('title', /International format/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 9. Groups update
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
test.describe('Groups update', () => {
|
test.describe('Groups update', () => {
|
||||||
test('change groups and save', async ({ page }) => {
|
test('change groups and save', async ({ page }) => {
|
||||||
|
|
@ -146,7 +206,7 @@ test.describe('Admin pages', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// 9. Activate/deactivate toggle
|
// 10. Activate/deactivate toggle
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
test.describe('Activate/deactivate toggle', () => {
|
test.describe('Activate/deactivate toggle', () => {
|
||||||
test('deactivate then activate user', async ({ page }) => {
|
test('deactivate then activate user', async ({ page }) => {
|
||||||
|
|
@ -175,7 +235,7 @@ test.describe('Admin pages', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// 10. Create invite from user list
|
// 11. Create invite from user list
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
test.describe('Create invite', () => {
|
test.describe('Create invite', () => {
|
||||||
test('fill username and submit to get invite URL', async ({ page }) => {
|
test('fill username and submit to get invite URL', async ({ page }) => {
|
||||||
|
|
@ -193,7 +253,7 @@ test.describe('Admin pages', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// 11. Re-invite from user detail
|
// 12. Re-invite from user detail
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
test.describe('Re-invite from user detail', () => {
|
test.describe('Re-invite from user detail', () => {
|
||||||
test('generate invite link from user detail page', async ({ page }) => {
|
test('generate invite link from user detail page', async ({ page }) => {
|
||||||
|
|
@ -210,7 +270,7 @@ test.describe('Admin pages', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// 12. Delete user
|
// 13. Delete user
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
test.describe('Delete user', () => {
|
test.describe('Delete user', () => {
|
||||||
test('delete a user and verify redirect to user list', async ({ page }) => {
|
test('delete a user and verify redirect to user list', async ({ page }) => {
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ test.describe('Profile page', () => {
|
||||||
await expect(page.locator('#family_name')).toHaveValue('Smith');
|
await expect(page.locator('#family_name')).toHaveValue('Smith');
|
||||||
await expect(page.locator('#preferred_username')).toHaveValue('asmith');
|
await expect(page.locator('#preferred_username')).toHaveValue('asmith');
|
||||||
await expect(page.locator('#email')).toHaveValue('alice@example.com');
|
await expect(page.locator('#email')).toHaveValue('alice@example.com');
|
||||||
await expect(page.locator('#phone_number')).toHaveValue('+1234567890');
|
await expect(page.locator('#phone_number')).toHaveValue('+12025551234');
|
||||||
await expect(page.locator('#picture')).toHaveValue('https://example.com/alice.jpg');
|
await expect(page.locator('#picture')).toHaveValue('https://example.com/alice.jpg');
|
||||||
await expect(page.locator('#locale')).toHaveValue('en');
|
await expect(page.locator('#locale')).toHaveValue('en');
|
||||||
});
|
});
|
||||||
|
|
@ -168,7 +168,7 @@ test.describe('Profile page', () => {
|
||||||
|
|
||||||
const alert = page.locator('#profile-status [role="alert"]');
|
const alert = page.locator('#profile-status [role="alert"]');
|
||||||
await expect(alert).toBeVisible({ timeout: 5000 });
|
await expect(alert).toBeVisible({ timeout: 5000 });
|
||||||
await expect(alert).toContainText('Invalid email');
|
await expect(alert).toContainText('valid email address');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows error for invalid picture URL', async ({ page }) => {
|
test('shows error for invalid picture URL', async ({ page }) => {
|
||||||
|
|
@ -189,5 +189,34 @@ test.describe('Profile page', () => {
|
||||||
test('picture input has type="url"', async ({ page }) => {
|
test('picture input has type="url"', async ({ page }) => {
|
||||||
await expect(page.locator('#picture')).toHaveAttribute('type', 'url');
|
await expect(page.locator('#picture')).toHaveAttribute('type', 'url');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('shows error for invalid phone number', async ({ page }) => {
|
||||||
|
// Bypass HTML5 validation by removing pattern attribute
|
||||||
|
await page.locator('#phone_number').evaluate(el => el.removeAttribute('pattern'));
|
||||||
|
await page.fill('#phone_number', 'not-a-phone');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
const alert = page.locator('#profile-status [role="alert"]');
|
||||||
|
await expect(alert).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(alert).toContainText('valid phone number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('normalizes phone number with spaces on save', async ({ page }) => {
|
||||||
|
await page.fill('#phone_number', '+46 70 123 45 67');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
|
||||||
|
const status = page.locator('#profile-status [role="status"]');
|
||||||
|
await expect(status).toBeVisible({ timeout: 5000 });
|
||||||
|
await expect(status).toContainText('Profile updated');
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.locator('#phone_number')).toHaveValue('+46701234567');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('phone input has correct E.164 hint attributes', async ({ page }) => {
|
||||||
|
await expect(page.locator('#phone_number')).toHaveAttribute('type', 'tel');
|
||||||
|
await expect(page.locator('#phone_number')).toHaveAttribute('pattern', '\\+[0-9 ]+');
|
||||||
|
await expect(page.locator('#phone_number')).toHaveAttribute('title', /International format/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ async def seed() -> None:
|
||||||
family_name="Smith",
|
family_name="Smith",
|
||||||
preferred_username="asmith",
|
preferred_username="asmith",
|
||||||
email="alice@example.com",
|
email="alice@example.com",
|
||||||
phone_number="+1234567890",
|
phone_number="+12025551234",
|
||||||
picture="https://example.com/alice.jpg",
|
picture="https://example.com/alice.jpg",
|
||||||
locale="en",
|
locale="en",
|
||||||
groups=["users"],
|
groups=["users"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue