Skip to content

WEB-971 [Playwright] Extract Layer-2 selector, route and behavior con…#3627

Open
devvaansh wants to merge 1 commit into
openMF:devfrom
devvaansh:WEB-971-playwright-layer-2-contracts
Open

WEB-971 [Playwright] Extract Layer-2 selector, route and behavior con…#3627
devvaansh wants to merge 1 commit into
openMF:devfrom
devvaansh:WEB-971-playwright-layer-2-contracts

Conversation

@devvaansh
Copy link
Copy Markdown
Member

@devvaansh devvaansh commented May 28, 2026

Description

Introduces a framework-agnostic Layer-2 contract between Playwright specs and page objects. Before this change, selector strings, route paths, and app behavioral flags were hardcoded directly inside page object methods — making them fragile to refactor and impossible to share across frameworks.

Three new contract files centralise everything:

  • playwright/config/selectors.ts - typed selector registry for all current page objects (login, client-view, close-client, create-client, dashboard). One place to update when the DOM changes.
  • playwright/config/routes.ts - hash-routing URL registry so no page object or spec ever hardcodes /#/login or /#/clients again.
  • playwright/config/behavior.ts - app-level behavioral flags (loginButtonStartsDisabled, usesHashRouting, authStorageKey) consumed by specs instead of embedded in assertions.

BasePage, LoginPage, ClientViewPage, and CloseClientPage were refactored to import from these contracts. Zero raw selector strings remain in any page object file.

This is a prerequisite for the React port (MXWAR-91): React page objects will implement the same selectors.ts interface with React-native locators, allowing the same spec files to run against both apps without modification.

Part of GSoC 2026 Epic WEB-967.

Related issues and discussion

https://mifosforge.jira.com/browse/WEB-971
Parent Epic: https://mifosforge.jira.com/browse/WEB-967

Screenshots, if any

No UI changes- this PR touches only the Playwright test infrastructure.

Summary by CodeRabbit

  • Chores
    • Centralized test configuration for Angular: common routing, UI locators, and behavior flags (login-button initial state, hash routing, auth-error snackbar, overlay dismissal, date display format).
    • Updated page implementations to consume shared configuration and clarified base-page docs for framework-agnostic use.
  • Tests
    • Added a reusable authentication helper to create and verify authenticated test states and credential persistence.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "pre_merge_checks"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f97267d9-f95f-4343-9da4-5ccc6c635810

📥 Commits

Reviewing files that changed from the base of the PR and between 63b7f10 and e796748.

📒 Files selected for processing (8)
  • playwright/auth-helpers.ts
  • playwright/config/behavior.ts
  • playwright/config/routes.ts
  • playwright/config/selectors.ts
  • playwright/pages/BasePage.ts
  • playwright/pages/client-view.page.ts
  • playwright/pages/close-client.page.ts
  • playwright/pages/login.page.ts
✅ Files skipped from review due to trivial changes (3)
  • playwright/config/behavior.ts
  • playwright/pages/BasePage.ts
  • playwright/config/routes.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • playwright/pages/client-view.page.ts
  • playwright/pages/close-client.page.ts
  • playwright/config/selectors.ts
  • playwright/pages/login.page.ts
  • playwright/auth-helpers.ts

Walkthrough

Adds Layer 2 Playwright contracts (behavior, routes, selectors), applies them to Login/ClientView/CloseClient page objects, and introduces authenticateRole to produce and verify Playwright storageState files.

Changes

Playwright Configuration Contracts and Page Object Refactoring

Layer / File(s) Summary
Configuration contracts (behavior, routes, selectors)
playwright/config/behavior.ts, playwright/config/routes.ts, playwright/config/selectors.ts
New BEHAVIOR flags, AppRoutes/ROUTES hash-based registry with parameterized builders, and typed selector maps for login, dashboard, create-client, client-view, and close-client.
BasePage contract documentation
playwright/pages/BasePage.ts
Clarified JSDoc that Layer 2 selector/route/behavior contracts are consumed by subclasses and that subclasses set url from ROUTES.
LoginPage configuration integration
playwright/pages/login.page.ts
LoginPage now uses ROUTES.login, LOGIN_SELECTORS for locator getters, replaces private locators with selectors-backed getters, and adds assertLoginButtonInitialState() using BEHAVIOR.loginButtonStartsDisabled.
ClientViewPage configuration integration
playwright/pages/client-view.page.ts
ClientViewPage now uses ROUTES.clientView(clientId), CLIENT_VIEW_SELECTORS for locators, refactors closedDateValue() selection, and conditionally skips dismissOverlay() when BEHAVIOR.overlayDismissNeeded is false.
CloseClientPage configuration integration
playwright/pages/close-client.page.ts
CloseClientPage now sets url via ROUTES.clientAction(clientId, 'Close') and uses CLOSE_CLIENT_SELECTORS for closure form fields and confirm/cancel button resolution.
Authentication helper
playwright/auth-helpers.ts
New authenticateRole(role, page, browser) writes verified storageState: resolves credentials, performs login via LoginPage, copies configured auth key from sessionStorage to localStorage, persists storageState file, and verifies it by launching a new context.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • openMF/web-app#3457: Implements a similar Playwright storageState generation flow, copying a credential from sessionStorage into localStorage for persisted auth state.
  • openMF/web-app#2912: Introduced initial Playwright page object scaffolding (LoginPage/BasePage) that this PR updates to consume shared config.
  • openMF/web-app#3504: Adds initial ClientView/CloseClient page objects and close-client E2E spec; this PR refactors those pages to use shared ROUTES/SELECTORS/BEHAVIOR.

Suggested reviewers

  • alberto-art3ch
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: extracting Layer-2 selector, route, and behavior contracts for Playwright testing, which is the primary objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@playwright/config/behavior.ts`:
- Around line 47-51: Tests define the auth storage key in
BEHAVIOR.authStorageKey but auth.setup.ts still hardcodes 'mifosXCredentials',
causing duplicate definitions; fix by importing BEHAVIOR (or the exported
constant that contains authStorageKey) into the auth.setup module and replace
the literal 'mifosXCredentials' with BEHAVIOR.authStorageKey so all consumers
use the single source of truth.

In `@playwright/config/routes.ts`:
- Line 43: The route builder clientAction currently interpolates the raw action
into the path which can break for reserved characters; update clientAction (the
arrow function named clientAction) to percent-encode the action segment using
encodeURIComponent(action) before interpolation (optionally also encode id with
encodeURIComponent(id)) so the generated URL path is safe for all characters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2ec06212-b35a-4dfc-8842-c6394edd4a93

📥 Commits

Reviewing files that changed from the base of the PR and between 69a1429 and 19f551d.

📒 Files selected for processing (7)
  • playwright/config/behavior.ts
  • playwright/config/routes.ts
  • playwright/config/selectors.ts
  • playwright/pages/BasePage.ts
  • playwright/pages/client-view.page.ts
  • playwright/pages/close-client.page.ts
  • playwright/pages/login.page.ts

Comment on lines +47 to +51
* Angular persists Fineract credentials in sessionStorage under this
* key. The auth-setup project copies localStorage -> sessionStorage
* on every test to survive page reloads.
*/
authStorageKey: 'mifosXCredentials',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Eliminate duplicate auth-storage key definitions to avoid contract drift.

Line 51 introduces BEHAVIOR.authStorageKey, but playwright/auth.setup.ts still hardcodes 'mifosXCredentials' (Lines 32-41 in the provided snippet). If one side changes, auth bootstrap can silently break across all tests.

♻️ Proposed follow-up (outside this file) to consume the contract
+import { BEHAVIOR } from './config/behavior';

-const creds = sessionStorage.getItem('mifosXCredentials');
+const creds = sessionStorage.getItem(BEHAVIOR.authStorageKey);

-localStorage.setItem('mifosXCredentials', creds);
+localStorage.setItem(BEHAVIOR.authStorageKey, creds);

-throw new Error('CRITICAL: mifosXCredentials not found in sessionStorage. ' + 'Did the auth storage key change?');
+throw new Error(`CRITICAL: ${BEHAVIOR.authStorageKey} not found in sessionStorage. Did the auth storage key change?`);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@playwright/config/behavior.ts` around lines 47 - 51, Tests define the auth
storage key in BEHAVIOR.authStorageKey but auth.setup.ts still hardcodes
'mifosXCredentials', causing duplicate definitions; fix by importing BEHAVIOR
(or the exported constant that contains authStorageKey) into the auth.setup
module and replace the literal 'mifosXCredentials' with BEHAVIOR.authStorageKey
so all consumers use the single source of truth.

Comment thread playwright/config/routes.ts Outdated
@devvaansh devvaansh force-pushed the WEB-971-playwright-layer-2-contracts branch from 19f551d to 63b7f10 Compare May 28, 2026 16:40
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
playwright/auth-helpers.ts (1)

73-74: ⚡ Quick win

Use the route contract in verification navigation.

Line 73 hardcodes '/#/'; that bypasses ROUTES and weakens the config-only portability goal for Angular/React swaps.

Proposed diff
 import { BEHAVIOR } from './config/behavior';
+import { ROUTES } from './config/routes';
@@
-  await verifyPage.goto('/#/');
-  await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
+  await verifyPage.goto(ROUTES.home);
+  await expect(verifyPage).not.toHaveURL(new RegExp(`${ROUTES.login}`), { timeout: 30000 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@playwright/auth-helpers.ts` around lines 73 - 74, The navigation call
verifyPage.goto('/#/') is hardcoded; replace it with the canonical route from
your ROUTES constant (e.g., verifyPage.goto(ROUTES.home) or the ROUTES key that
represents the app root) and import ROUTES if not already imported, and update
the URL-check to avoid hardcoded login pattern by referencing ROUTES.login (or
the appropriate login route) in the expectation (e.g., not.toHaveURL(new
RegExp(ROUTES.login))) so both navigation and assertions use the shared ROUTES
configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@playwright/auth-helpers.ts`:
- Around line 71-75: The verifyContext created via browser.newContext (and
verifyPage) may leak if the assertion await
expect(verifyPage).not.toHaveURL(/.*login.*/...) throws; wrap the navigation +
assertion in a try/finally so verifyContext.close() always runs in the finally
block (and close verifyPage if created separately). Specifically, modify the
block around verifyContext/verifyPage and the expect call to ensure
verifyContext.close() is invoked in a finally clause so the context is always
cleaned up even on assertion failures.

---

Nitpick comments:
In `@playwright/auth-helpers.ts`:
- Around line 73-74: The navigation call verifyPage.goto('/#/') is hardcoded;
replace it with the canonical route from your ROUTES constant (e.g.,
verifyPage.goto(ROUTES.home) or the ROUTES key that represents the app root) and
import ROUTES if not already imported, and update the URL-check to avoid
hardcoded login pattern by referencing ROUTES.login (or the appropriate login
route) in the expectation (e.g., not.toHaveURL(new RegExp(ROUTES.login))) so
both navigation and assertions use the shared ROUTES configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 365f866b-a61a-4ef6-b389-19864863b42f

📥 Commits

Reviewing files that changed from the base of the PR and between 19f551d and 63b7f10.

📒 Files selected for processing (8)
  • playwright/auth-helpers.ts
  • playwright/config/behavior.ts
  • playwright/config/routes.ts
  • playwright/config/selectors.ts
  • playwright/pages/BasePage.ts
  • playwright/pages/client-view.page.ts
  • playwright/pages/close-client.page.ts
  • playwright/pages/login.page.ts
✅ Files skipped from review due to trivial changes (1)
  • playwright/pages/BasePage.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • playwright/pages/client-view.page.ts
  • playwright/pages/close-client.page.ts
  • playwright/pages/login.page.ts
  • playwright/config/behavior.ts
  • playwright/config/selectors.ts

Comment on lines +71 to +75
const verifyContext = await browser.newContext({ storageState: role.storageStateFile });
const verifyPage = await verifyContext.newPage();
await verifyPage.goto('/#/');
await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
await verifyContext.close();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure verifyContext is always closed.

If the assertion on Line 74 fails, Line 75 is skipped and the context leaks.

Proposed diff
   const verifyContext = await browser.newContext({ storageState: role.storageStateFile });
-  const verifyPage = await verifyContext.newPage();
-  await verifyPage.goto('/#/');
-  await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
-  await verifyContext.close();
+  try {
+    const verifyPage = await verifyContext.newPage();
+    await verifyPage.goto('/#/');
+    await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
+  } finally {
+    await verifyContext.close();
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const verifyContext = await browser.newContext({ storageState: role.storageStateFile });
const verifyPage = await verifyContext.newPage();
await verifyPage.goto('/#/');
await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
await verifyContext.close();
const verifyContext = await browser.newContext({ storageState: role.storageStateFile });
try {
const verifyPage = await verifyContext.newPage();
await verifyPage.goto('/#/');
await expect(verifyPage).not.toHaveURL(/.*login.*/, { timeout: 30000 });
} finally {
await verifyContext.close();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@playwright/auth-helpers.ts` around lines 71 - 75, The verifyContext created
via browser.newContext (and verifyPage) may leak if the assertion await
expect(verifyPage).not.toHaveURL(/.*login.*/...) throws; wrap the navigation +
assertion in a try/finally so verifyContext.close() always runs in the finally
block (and close verifyPage if created separately). Specifically, modify the
block around verifyContext/verifyPage and the expect call to ensure
verifyContext.close() is invoked in a finally clause so the context is always
cleaned up even on assertion failures.

…tracts

- Add playwright/config/selectors.ts: typed selector registry for login,
  client-view, close-client, create-client and dashboard page objects.
  Single source of truth — no selector string is hardcoded in page logic.

- Add playwright/config/routes.ts: hash-routing URL registry (/#/login,
  /#/clients, etc.) so no page object or spec ever hardcodes a route.

- Add playwright/config/behavior.ts: app-level behavioral flags consumed
  by specs (loginButtonStartsDisabled, usesHashRouting, authStorageKey)
  so assertions stay framework-agnostic.

- Refactor BasePage, LoginPage, ClientViewPage, CloseClientPage to import
  from the above contracts. Zero raw selector strings remain in any page
  object file (verified: grep returns empty).

This is the prerequisite for the React port (MXWAR-91): React page objects
will implement the same selectors interface with framework-native locators,
allowing specs to run unchanged against both apps.

Part of GSoC 2026 Epic WEB-967.
@devvaansh devvaansh force-pushed the WEB-971-playwright-layer-2-contracts branch from 63b7f10 to e796748 Compare May 28, 2026 16:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

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.

1 participant