Skip to content

Migrate remaining Selenide UI tests to Playwright#70

Merged
devondragon merged 4 commits intomainfrom
feature/67-migrate-selenide-to-playwright
Mar 23, 2026
Merged

Migrate remaining Selenide UI tests to Playwright#70
devondragon merged 4 commits intomainfrom
feature/67-migrate-selenide-to-playwright

Conversation

@devondragon
Copy link
Owner

Summary

  • Remove all Selenide test infrastructure (18 Java files, ~1,700 lines) — all scenarios already covered by existing Playwright specs
  • Add 2 new Playwright validation tests for profile update (empty fields, long names)
  • Replace networkidle with domcontentloaded across all 14 Playwright spec files, fixing ~68 tests that were timing out due to WebAuthn endpoints returning 500
  • Upgrade Spring User Framework from 4.3.0 to 4.3.1, fixing the WebAuthn 500 errors caused by missing user_credentials table (see WebAuthnCredential table not created: VARBINARY(65535) exceeds MariaDB row size limit SpringUserFramework#286)
  • Fix pre-existing Instant/Date type mismatch in TestDataController
  • Fix race condition in duplicate-registration test (async fetch, not page navigation)

Changes

Area What changed
Selenide removal Deleted 18 Java files: 3 test classes, 10 page objects, 3 utilities, 2 JDBC helpers
build.gradle Removed selenide, webdrivermanager deps; removed uiTest task and excludeTags 'ui'; upgraded framework to 4.3.1
Playwright tests Added validation tests; replaced all networkidle with domcontentloaded; fixed async error assertion
Bug fixes TestDataController: Date.from(Instant.now()); EmailVerificationEdgeCaseTest: use userRepository instead of deleted DatabaseStateValidator; ApiTestData: remove unused DSUserDetails field
Docs Updated CLAUDE.md and README.md to reflect Playwright as the E2E framework

Test plan

  • ./gradlew clean test — BUILD SUCCESSFUL (all Java tests pass)
  • npx playwright test --project=chromium — 99 passed, 0 failed
  • grep -r "selenide\|Selenide\|uiTest" src/ — no matches
  • Verified user_credentials table auto-created on startup with framework 4.3.1

Closes #67

Remove all Selenide test infrastructure (18 Java files, 1,664 lines) and
consolidate on Playwright as the single E2E framework. All Selenide test
scenarios were already covered by existing Playwright specs; two new
validation tests (empty fields, long names) were added to fill a gap.

Key changes:
- Delete Selenide test classes, page objects, utilities, and JDBC helpers
- Remove selenide and webdrivermanager dependencies from build.gradle
- Remove uiTest Gradle task and excludeTags 'ui' filter
- Replace all networkidle waits with domcontentloaded across 14 Playwright
  spec files (fixes timeout failures caused by WebAuthn endpoints returning
  500 on pages that load webauthn JS)
- Add profile update validation tests to update-profile.spec.ts
- Fix pre-existing Instant/Date type mismatch in TestDataController
- Update EmailVerificationEdgeCaseTest to use userRepository instead of
  deleted DatabaseStateValidator
- Update CLAUDE.md and README.md to reflect Playwright as E2E framework

Closes #67
Fixes WebAuthn 500 errors caused by missing user_credentials table
(see devondragon/SpringUserFramework#286). Also removes unused
DSUserDetails constant from ApiTestData that caused NPE with the
new constructor signature.
The registration form uses async fetch (no page navigation), so
waitForLoadState returns immediately. Wait for the error element
to become visible instead of checking synchronously.
Copilot AI review requested due to automatic review settings March 22, 2026 23:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the migration away from legacy Selenide-based UI tests by removing the remaining Selenide test infrastructure and consolidating E2E coverage under Playwright, while also addressing WebAuthn-related flakiness/timeouts and a test-data type mismatch.

Changes:

  • Removed remaining Selenide UI test classes, page objects, and JDBC-based helpers; updated Gradle to drop Selenide/WebDriverManager and the uiTest task.
  • Updated Playwright specs/page objects to use domcontentloaded (instead of networkidle) and added two profile-update validation scenarios.
  • Upgraded ds-spring-user-framework to 4.3.1 and fixed Instant/Date mismatch in TestDataController.

Reviewed changes

Copilot reviewed 41 out of 42 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/test/resources/application-test.properties Removed unused test.browser setting tied to deleted Selenide tests.
src/test/java/com/digitalsanctuary/spring/user/ui/page/SuccessResetPasswordPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/user/ui/page/ForgotPasswordPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/user/security/EmailVerificationEdgeCaseTest.java Replaced DB validator usage with repository lookups after validator deletion.
src/test/java/com/digitalsanctuary/spring/user/jdbc/Jdbc.java Deleted Selenide-era JDBC helper used for UI test setup/cleanup.
src/test/java/com/digitalsanctuary/spring/user/jdbc/ConnectionManager.java Deleted Selenide-era JDBC connection manager.
src/test/java/com/digitalsanctuary/spring/user/api/data/ApiTestData.java Removed unused DSUserDetails test constant/import.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/util/EmailVerificationSimulator.java Deleted Selenide-era utility for simulating email verification.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/util/DatabaseStateValidator.java Deleted Selenide-era DB state assertion helper.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/VerificationPendingPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/UpdateUserPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/UpdatePasswordPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/SuccessResetPasswordPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/SuccessRegisterPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/RegisterPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/LoginSuccessPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/LoginPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/ForgotPasswordPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/page/DeleteAccountPage.java Deleted legacy Selenide page object.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/data/UiTestData.java Deleted Selenide UI test data helper.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/SpringUserFrameworkUiTest.java Deleted remaining Selenide UI test class.
src/test/java/com/digitalsanctuary/spring/demo/user/ui/CompleteUserJourneyE2ETest.java Deleted comprehensive Selenide E2E suite (now covered by Playwright).
src/test/java/com/digitalsanctuary/spring/demo/user/ui/BaseUiTest.java Deleted Selenide base test harness (drivers/config).
src/main/java/com/digitalsanctuary/spring/demo/test/api/TestDataController.java Fixed InstantDate mismatch when setting registration date.
playwright/tests/profile/update-profile.spec.ts Switched waits to domcontentloaded and added profile validation scenarios.
playwright/tests/profile/delete-account.spec.ts Switched waits to domcontentloaded.
playwright/tests/profile/change-password.spec.ts Switched waits to domcontentloaded.
playwright/tests/profile/auth-methods.spec.ts Switched waits to domcontentloaded.
playwright/tests/events/event-registration.spec.ts Switched waits to domcontentloaded.
playwright/tests/events/browse-events.spec.ts Switched waits to domcontentloaded.
playwright/tests/e2e/complete-user-journey.spec.ts Switched waits to domcontentloaded (incl. page.reload({ waitUntil })).
playwright/tests/auth/registration.spec.ts Fixed async error assertion (wait for error element vs navigation) and updated load-state wait.
playwright/tests/auth/passwordless-registration.spec.ts Switched waits to domcontentloaded.
playwright/tests/auth/password-reset.spec.ts Switched waits to domcontentloaded.
playwright/tests/auth/login.spec.ts Switched waits to domcontentloaded.
playwright/tests/auth/email-verification.spec.ts Switched waits to domcontentloaded.
playwright/tests/access-control/protected-pages.spec.ts Switched waits to domcontentloaded.
playwright/src/pages/EventDetailsPage.ts Updated post-reload wait from networkidle to domcontentloaded.
playwright/src/pages/BasePage.ts Updated common “wait” helpers to use domcontentloaded.
build.gradle Upgraded framework to 4.3.1, removed Selenide deps, removed uiTest task and tag exclusions.
README.md Updated docs to reflect Playwright as the E2E/UI framework and removed uiTest guidance.
CLAUDE.md Updated contributor guidance to reflect Playwright E2E usage and removed uiTest guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await updateUserPage.submit();

// HTML5 required validation should prevent submission
// or server should return an error - verify no unhandled crash
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new empty-fields “validation” test doesn’t actually assert any validation behavior. After clearing the inputs and clicking submit, it only checks the URL, so it would still pass even if the form submits and the server accepts empty values (or if an error is rendered incorrectly). Consider asserting a concrete outcome (e.g., inputs remain :invalid / have required attributes, or #firstNameError/#lastNameError/#globalMessage becomes visible with an error state).

Suggested change
// or server should return an error - verify no unhandled crash
// or server should return an error - verify no unhandled crash
// Assert that the inputs are considered invalid by the browser
const firstNameIsValid = await updateUserPage.firstNameInput.evaluate(
(el: HTMLInputElement) => el.checkValidity()
);
const lastNameIsValid = await updateUserPage.lastNameInput.evaluate(
(el: HTMLInputElement) => el.checkValidity()
);
expect(firstNameIsValid).toBe(false);
expect(lastNameIsValid).toBe(false);

Copilot uses AI. Check for mistakes.
Comment on lines +210 to +214
await updateUserPage.updateProfile(longName, longName);

// Wait for server response
await page.waitForLoadState('domcontentloaded');

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateProfile(longName, longName) doesn’t wait for the async result, and the subsequent waitForLoadState('domcontentloaded') is likely a no-op (DOMContentLoaded already fired on initial navigation). This can make the test flaky and it won’t reliably detect server-side validation failures. Prefer waiting on a deterministic signal (e.g., updateProfileAndWait(...), page.waitForResponse(...), or asserting that a success/error message or field-level error becomes visible).

Suggested change
await updateUserPage.updateProfile(longName, longName);
// Wait for server response
await page.waitForLoadState('domcontentloaded');
await updateUserPage.updateProfileAndWait(longName, longName);

Copilot uses AI. Check for mistakes.
Comment on lines 138 to 141
// Try to change to a weak password
await updatePasswordPage.changePassword(user.password, 'weak');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After submitting the weak password change, page.waitForLoadState('domcontentloaded') doesn’t wait for the form POST/fetch to complete (and may resolve immediately if the page already reached that state). This can cause the test to proceed before the UI updates. Prefer waiting for a specific response (waitForResponse on the update endpoint) or for #globalMessage / field error locators to become visible.

Copilot uses AI. Check for mistakes.
Comment on lines 170 to 172
await page.waitForLoadState('domcontentloaded');

// Should show error or validation message (client-side validation)
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test submits mismatched passwords but then only waits for domcontentloaded, which won’t reliably wait for client-side validation or an async POST result (and may resolve immediately). To make the test meaningful and non-flaky, assert a concrete validation outcome (e.g., #confirmPasswordError visible, the confirm input is :invalid, or a global error/message appears).

Suggested change
await page.waitForLoadState('domcontentloaded');
// Should show error or validation message (client-side validation)
// Wait for client-side validation / error message
await updatePasswordPage.waitForMessage(5000);
const hasError = await updatePasswordPage.isErrorMessage();
expect(hasError).toBe(true);

Copilot uses AI. Check for mistakes.
- Empty field submission: assert HTML5 checkValidity() instead of just URL
- Long name submission: use updateProfileAndWait() and assert message visible
- Weak password: wait for globalMessage with sufficient timeout before asserting error
- Mismatched passwords: assert confirmPasswordError element is visible
@devondragon
Copy link
Owner Author

Copilot feedback addressed

All 4 Copilot review comments have been fixed in the latest commit (6f2709a):

Comment Fix
Empty field test only checked URL Now asserts el.checkValidity() === false on both inputs via HTML5 constraint API
Long name test used no-op waitForLoadState Now uses updateProfileAndWait() and asserts #globalMessage is visible
Weak password test used no-op waitForLoadState Now calls waitForMessage(10000) (10s to account for bcrypt hashing time) then asserts isErrorMessage()
Mismatched passwords had no assertion Now asserts #confirmPasswordError is visible (client-side validation fires before fetch)

Full Playwright suite still passes 99/99 after these changes.

@devondragon devondragon merged commit 18610cb into main Mar 23, 2026
6 checks passed
@devondragon devondragon deleted the feature/67-migrate-selenide-to-playwright branch March 23, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate remaining Selenide UI tests to Playwright

2 participants