Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 61 additions & 83 deletions src/e2e/registration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,181 +6,159 @@ function uniqueEmail() {
}

test.describe('Registration Flow', () => {
test('TC1-1: happy path with validation checks', async ({ page }) => {
// Intercept registration API to avoid depending on backend state
await page.route('**/api/v1/auth/register', async (route) => {
route.fulfill({ status: 200, body: JSON.stringify({ success: true }) });
});

// Start at email page
// TC1-1: Invalid Email (missing @)
test('TC1-1: invalid email shows browser validation', async ({ page }) => {
await page.goto('/registration/email');

// Try invalid email
const emailInput = page.getByTestId('registration-email-input');
await emailInput.fill('invalid');
await page.getByTestId('registration-email-next-btn').click();
// Assert native browser validation message (Thai locale as in UI)
const validationMessage = await emailInput.evaluate((el) => (el as HTMLInputElement).validationMessage);
await expect(validationMessage).toContain("Please include an '@' in the email address. 'invalid' is missing an '@'.");
});

// Enter valid email
const email = uniqueEmail();
await page.getByTestId('registration-email-input').fill(email);
// TC1-2: Valid Email
test('TC1-2: valid email navigates to name step', async ({ page }) => {
await page.goto('/registration/email');
await page.getByTestId('registration-email-input').fill('example@gmail.com');
await page.getByTestId('registration-email-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/name$/);
});

// Name step - leave blank to trigger errors
// TC1-3: Empty First/Last Name
test('TC1-3: empty names show validation', async ({ page }) => {
await page.goto('/registration/name');
await page.getByTestId('registration-name-next-btn').click();
await expect(page.getByText("First name must be at least 2 characters")).toBeVisible();
await expect(page.getByText("Last name must be at least 2 characters")).toBeVisible();
await expect(page.getByText('First name must be at least 2 characters')).toBeVisible();
await expect(page.getByText('Last name must be at least 2 characters')).toBeVisible();
});

// Fill names
// TC1-4: Valid Name
test('TC1-4: valid names navigate to password step', async ({ page }) => {
await page.goto('/registration/name');
await page.getByTestId('registration-first-name-input').fill('John');
await page.getByTestId('registration-last-name-input').fill('Doe');
await page.getByTestId('registration-name-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/password$/);
});

// Password step - mismatch first
// TC1-5: Passwords not match
test('TC1-5: passwords mismatch shows error', async ({ page }) => {
await page.goto('/registration/password');
await page.getByTestId('registration-password-input').fill('StrongPass1!');
await page.getByTestId('registration-confirm-password-input').fill('Different1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByText('Passwords not match')).toBeVisible();
});

// Correct confirm password
// TC1-6: Strong Password (passes all) -> Terms
test('TC1-6: strong password navigates to terms', async ({ page }) => {
await page.goto('/registration/password');
await page.getByTestId('registration-password-input').fill('StrongPass1!');
await page.getByTestId('registration-confirm-password-input').fill('StrongPass1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/terms$/);
});

// Terms step - Next disabled until checked
// TC1-7: Terms not accepted
test('TC1-7: terms next disabled until accepted', async ({ page }) => {
await page.goto('/registration/terms');
const nextBtn = page.getByTestId('registration-terms-next-btn');
await expect(nextBtn).toBeDisabled();
});

// TC1-8: Terms accepted -> Success
test('TC1-8: accept terms navigates to success', async ({ page }) => {
await page.route('**/api/v1/auth/register', async (route) => {
route.fulfill({ status: 200, body: JSON.stringify({ success: true }) });
});
await page.goto('/registration/terms');
const nextBtn = page.getByTestId('registration-terms-next-btn');
await page.getByTestId('registration-terms-checkbox').check();
await expect(nextBtn).toBeEnabled();
await nextBtn.click();

// Success page
await expect(page).toHaveURL(/.*\/registration\/success$/);
await expect(page.getByTestId('registration-success-panel')).toBeVisible();
await expect(page.getByText(/Successfully Signed Up/i)).toBeVisible();
});

test('TC1-2: privacy policy link navigates correctly from password step', async ({ page }) => {
// TC1-9: Privacy Policy link
test('TC1-9: privacy policy link navigates', async ({ page }) => {
await page.goto('/registration/password');
await page.getByTestId('registration-password-privacy-link').click();
await expect(page).toHaveURL(/.*\/registration\/privacyPolicy$/);
await expect(page.getByText(/Privacy Policy/i)).toBeVisible();
});

// New test: verify back buttons navigate correctly through the flow
test('TC1-3: back buttons navigate to previous steps', async ({ page }) => {
// Start at first step
// TC1-10: Back button behavior
test('TC1-10: back buttons navigate and retain values', async ({ page }) => {
await page.goto('/registration/email');
// Enter valid email and proceed
const email = uniqueEmail();
await page.getByTestId('registration-email-input').fill(email);
await page.getByTestId('registration-email-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/name$/);

// Click Back on name step -> email step
await page.getByTestId('registration-name-back-btn').click();
await expect(page).toHaveURL(/.*\/registration\/email$/);

// Proceed again to name step (email should persist)
await page.getByTestId('registration-email-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/name$/);

// Fill names and go forward
await page.getByTestId('registration-first-name-input').fill('John');
await page.getByTestId('registration-last-name-input').fill('Doe');
await page.getByTestId('registration-name-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/password$/);

// Back from password -> name, verify values persisted
await page.getByTestId('registration-password-back-btn').click();
await expect(page).toHaveURL(/.*\/registration\/name$/);
await expect(page.getByTestId('registration-first-name-input')).toHaveValue('John');
await expect(page.getByTestId('registration-last-name-input')).toHaveValue('Doe');

// Forward again to password
await page.getByTestId('registration-name-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/password$/);

// Fill matching strong password and proceed
await page.getByTestId('registration-password-input').fill('StrongPass1!');
await page.getByTestId('registration-confirm-password-input').fill('StrongPass1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/terms$/);

// Back from terms -> password
await page.getByTestId('registration-terms-back-btn').click();
await expect(page).toHaveURL(/.*\/registration\/password$/);

// (Optional) Proceed again to terms to ensure navigation still works
await page.getByTestId('registration-password-input').fill('StrongPass1!');
await page.getByTestId('registration-confirm-password-input').fill('StrongPass1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/terms$/);
});

// New test: password requirement validations
test('TC1-4: password requirement validations', async ({ page }) => {
// TC1-11: Weak Password - length fails
test('TC1-11: weak password fails length', async ({ page }) => {
await page.goto('/registration/password');
await page.waitForLoadState('networkidle');
// Sanity: requirement hints rendered
await expect(page.getByTestId('registration-password-req-length')).toBeVisible();
await expect(page.getByTestId('registration-password-req-capital')).toBeVisible();
await expect(page.getByTestId('registration-password-req-number-symbol')).toBeVisible();

// Case 1: Fails length only (has capital + number)
await page.getByTestId('registration-password-input').fill('A1!');
await page.getByTestId('registration-confirm-password-input').fill('A1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page).toHaveURL(/.*\/registration\/password$/);
await expect(page.getByTestId('registration-password-req-length')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).not.toHaveClass(/text-error-main/);
});

// Case 2: Fails capital only
// TC1-12: Weak Password - capital fails
test('TC1-12: weak password fails capital', async ({ page }) => {
await page.goto('/registration/password');
await page.waitForLoadState('networkidle');
await expect(page.getByTestId('registration-password-req-capital')).toBeVisible();
await page.getByTestId('registration-password-input').fill('abcd1234!');
await page.getByTestId('registration-confirm-password-input').fill('abcd1234!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByTestId('registration-password-req-length')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).not.toHaveClass(/text-error-main/);
});

// Case 3: Fails number/symbol only
// TC1-13: Weak Password - number/symbol fails
test('TC1-13: weak password fails number or symbol', async ({ page }) => {
await page.goto('/registration/password');
await page.waitForLoadState('networkidle');
await expect(page.getByTestId('registration-password-req-number-symbol')).toBeVisible();
await page.getByTestId('registration-password-input').fill('Abcdefgh');
await page.getByTestId('registration-confirm-password-input').fill('Abcdefgh');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByTestId('registration-password-req-length')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).toHaveClass(/text-error-main/);

// Case 4: Fails length + capital
await page.getByTestId('registration-password-input').fill('abc1!');
await page.getByTestId('registration-confirm-password-input').fill('abc1!');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByTestId('registration-password-req-length')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).not.toHaveClass(/text-error-main/);

// Case 5: Fails capital + number/symbol
await page.getByTestId('registration-password-input').fill('abcdefgh');
await page.getByTestId('registration-confirm-password-input').fill('abcdefgh');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByTestId('registration-password-req-length')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).toHaveClass(/text-error-main/);

// Case 6: Fails length + number/symbol
await page.getByTestId('registration-password-input').fill('Abc');
await page.getByTestId('registration-confirm-password-input').fill('Abc');
await page.getByTestId('registration-password-next-btn').click();
await expect(page.getByTestId('registration-password-req-length')).toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-capital')).not.toHaveClass(/text-error-main/);
await expect(page.getByTestId('registration-password-req-number-symbol')).toHaveClass(/text-error-main/);
});

// Case 7: All pass -> proceed to terms
// TC1-14: Strong Password (passes all) -> Terms
test('TC1-14: strong password navigates to terms', async ({ page }) => {
await page.goto('/registration/password');
await page.getByTestId('registration-password-input').fill('StrongPass1!');
await page.getByTestId('registration-confirm-password-input').fill('StrongPass1!');
await page.getByTestId('registration-password-next-btn').click();
Expand Down