diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..71b9e19 Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env new file mode 100644 index 0000000..80af16a --- /dev/null +++ b/.env @@ -0,0 +1,31 @@ +FUND_FIT_LANDING_PAGE_URL=https://streamlyne.com/fundfit/ +FUND_FIT_TEST_ENV_URL=https://test-fundfit.streamlyne.org/ +FUND_FIT_LOGIN_PAGE_URL=https://test-fundfit.streamlyne.org/login +FUND_FIT_REGISTER_PAGE_URL=https://test-fundfit.streamlyne.org/register +FUND_FIT_USER_EMAIL=mahbubasr091@gmail.com +FUND_FIT_USER_NEW_EMAIL=mahbubasr091@gmail.com +FUND_FIT_USER_PASSWORD=streamLyne8068!@ +FUND_FIT_USER_PREVIOUS_PASSWORD=streamLyne2025@ +FUND_FIT_FORGET_PASSWORD_PAGE_URL=https://test-fundfit.streamlyne.org/forgot-password +FUND_FIT_DASHBOARD_PAGE_URL=https://test-fundfit.streamlyne.org/dashboard +FUND_FIT_START_MATCHING_PAGE_URL=https://test-fundfit.streamlyne.org/matcher +FUND_FIT_MATCHES_PAGE_URL=https://test-fundfit.streamlyne.org/match +FUND_FIT_NOTIFICATIONS_PAGE_URL=https://test-fundfit.streamlyne.org/notifications + +FUND_FIT_PROFILE_PAGE_URL=https://test-fundfit.streamlyne.org/profile +FUND_FIT_PROFILE_EDIT_PAGE_URL=https://test-fundfit.streamlyne.org/profile/settings +FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL=https://test-fundfit.streamlyne.org/profile/settings/password +FUND_FIT_TEST_PASSWORD=test2025@FundFit +FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL=https://test-fundfit.streamlyne.org/profile/settings/email + +FUND_FIT_PROFILE_BACKGROUND_PAGE_URL=https://test-fundfit.streamlyne.org/profile/background +FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL=https://test-fundfit.streamlyne.org/profile/background/create +FUND_FIT_PROFILE_BACKGROUND_UPDATE_PAGE_URL=https://test-fundfit.streamlyne.org/profile/background/update + +FUND_FIT_PROFILE_WORK_PAGE_URL=https://test-fundfit.streamlyne.org/profile/work +FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL=https://test-fundfit.streamlyne.org/profile/work/create +FUND_FIT_PROFILE_WORK_UPDATE_PAGE_URL=https://test-fundfit.streamlyne.org/profile/work/update + +FUND_FIT_PROFILE_PROJECT_PAGE_URL=https://test-fundfit.streamlyne.org/profile/funding +FUND_FIT_PROFILE_ATTACHMENT_PAGE_URL=https://test-fundfit.streamlyne.org/profile/attachment +FUND_FIT_CREATE_PROJECT_PAGE_URL=https://test-fundfit.streamlyne.org/profile/funding/create diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 18c11a1..a8e5c4b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,33 +1,23 @@ -name: Playwright Test Suite -on: - [push] - +name: Playwright Tests +on: [push, pull_request] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - name: Install dependencies - run: npm ci - - - name: Install Playwright Browsers - run: npx playwright install --with-deps - - - name: Run Playwright tests - run: | - npx playwright test - npm install -D allure-commandline - - name: Generate Allure report - run: npx allure generate allure-results --clean -o allure-report - - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: allure-reports - path: allure-report/ - retention-days: 30 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 67e9360..76de6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,7 @@ web_modules/ .yarn-integrity # dotenv environment variable files -.env +# .env .env.development.local .env.test.local .env.production.local @@ -135,3 +135,4 @@ dist /blob-report/ /playwright/.cache/ /reports/* +/allure-results/* diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 17e60a4..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Md. Mahbubur Rahman (Tom) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index 0a4940e..b7d06af 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,119 @@ -# playwright_TS_Project -creating playwright hybrid framework using Typescript. +# Streamlyne FundFit Test + +

+ + Build Status + + + Node Version + + + TypeScript + +

+ +An interactive test automation project using Playwright and TypeScript. + +## Overview + +This project is designed to automate testing for the Streamlyne FundFit application. It leverages the power of Playwright, a modern end-to-end testing framework, combined with TypeScript for type safety and maintainability. + +## Features + +- Cross-browser testing (Chromium, Firefox, WebKit) +- Headless and headed execution modes +- Parallel test execution +- Detailed test reports +- Easy-to-maintain test structure + +## Prerequisites + +Before you begin, ensure you have the following installed: + +- [Node.js](https://nodejs.org/) (v16 or later) +- [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) + +## Installation + +1. Clone the repository: + + ```bash + git clone https://github.com/your-username/streamlyne-fundfit-test.git + cd streamlyne-fundfit-test + ``` + +2. Install dependencies: + `bash + npm install + ` + +## Usage + +### Running Tests + +To execute all tests in headless mode: + +```bash +npx playwright test +``` + +To run tests in a specific browser: + +```bash +npx playwright test --project=chromium +npx playwright test --project=firefox +npx playwright test --project=webkit +``` + +To run tests in headed mode: + +```bash +npx playwright test --headed +``` + +### Generating Reports + +After running tests, generate an HTML report: + +```bash +npm run allure:serve +``` + +### Debugging Tests + +Use the Playwright Inspector for debugging: + +```bash +npx playwright test --debug +``` + +## Project Structure + +``` +streamlyne-fundfit-test/ +├── pageObjectModel/ # Page Resources with Page Selectors +├── tests/ # Test files +├── testData/ # Storing test data on page resource basis +├── utilities/ # Resource needed for test management with custom action class +├── reports/ # Storing log and reporting resources +├── playwright.config.ts # Playwright configuration +├── .env # Storing Credentials and Sensitive information +├── package.json # Project metadata and dependencies +└── README.md # Project documentation +``` + +## Tools and Technologies + +- **Playwright**: End-to-end testing framework +- **TypeScript**: Strongly typed programming language +- **Jest**: Test runner (integrated with Playwright) +- **ESLint**: Linting tool for code quality +- **Prettier**: Code formatter +- **Allure Reporting**: Test Reporting + +## Contributing + +1. Fork the repository. +2. Create a new branch (`git checkout -b feature/your-feature`). +3. Commit your changes (`git commit -m 'Add your feature'`). + For questions or support, please contact [jvalle@streamlyne.com](mailto:jvalle@streamlyne.com). diff --git a/package-lock.json b/package-lock.json index 9dac95e..4e4fdbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,24 @@ { - "name": "playwright_ts_project", + "name": "playwright-hybrid-framework-stramlyne", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "playwright_ts_project", + "name": "playwright-hybrid-framework-stramlyne", "version": "1.0.0", "license": "ISC", "dependencies": { + "@faker-js/faker": "^9.7.0", "allure-js-commons": "^3.2.0", - "winston": "^3.17.0" + "dotenv": "^16.5.0", + "winston": "^3.17.0", + "zod": "^3.24.3" }, "devDependencies": { "@playwright/test": "^1.50.1", "@types/node": "^22.13.9", + "allure-commandline": "^2.34.0", "allure-playwright": "^3.0.9", "rimraf": "^6.0.1" } @@ -39,6 +43,22 @@ "kuler": "^2.0.0" } }, + "node_modules/@faker-js/faker": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.7.0.tgz", + "integrity": "sha512-aozo5vqjCmDoXLNUJarFZx2IN/GgGaogY4TMJ6so/WLZOWpSV7fvj2dmrV6sEAnUm1O7aCrhTibjpzeDFgNqbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -86,6 +106,16 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/allure-commandline": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/allure-commandline/-/allure-commandline-2.34.0.tgz", + "integrity": "sha512-+SgVJ9+o7OH43KVCln0KT7VIsXCfMVEvFuYArIFNtS0fWu61UFdeiCYvrlQPC9trEoFhOe2e5i8Xhghwo46iRQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "allure": "bin/allure" + } + }, "node_modules/allure-js-commons": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-3.2.0.tgz", @@ -255,6 +285,18 @@ "node": "*" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -926,6 +968,15 @@ "engines": { "node": ">=8" } + }, + "node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index ab8f84b..0a2c1fe 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,37 @@ { - "name": "playwright_ts_project", + "name": "playwright-hybrid-framework-stramlyne", "version": "1.0.0", "description": "creating playwright hybrid framework using Typescript.", "main": "index.js", "scripts": { "test:codegen": "npx playwright codegen", - "pretest": "npx playwright install && npx rimraf playwright-report test-results", - "test": "npx playwright test || npm run posttest", + "pretest": "npx playwright install && npx rimraf playwright-report test-results reports", + "test": "npx playwright test; npm run allure:serve", "testpw": "npx playwright test", "test:ui": "npx playwright test --ui", "test:chrome": "npx playwright test --project=chromium", "test:debug": "npx playwright test --debug", "test:headed": "npx playwright test --headed", - "posttest": "npx playwright show-report", - "dot:report": "npx playwright test --reporter=dot", - "line:report": "npx playwright test --reporter=line" + "posttest": "npx playwright show-report reports/html", + "allure:serve": "allure serve ./reports/allure-results", + "mcp": "npx @playwright/mcp@latest", + "playwright:install": "npx playwright install --with-deps" }, "keywords": [], - "author": "", + "author": "StreamLyne", "license": "ISC", "devDependencies": { "@playwright/test": "^1.50.1", "@types/node": "^22.13.9", + "allure-commandline": "^2.34.0", "allure-playwright": "^3.0.9", "rimraf": "^6.0.1" }, "dependencies": { + "@faker-js/faker": "^9.7.0", "allure-js-commons": "^3.2.0", - "winston": "^3.17.0" + "dotenv": "^16.5.0", + "winston": "^3.17.0", + "zod": "^3.24.3" } } diff --git a/pageObjectModel/dashboardPage.ts b/pageObjectModel/dashboardPage.ts new file mode 100644 index 0000000..106e96d --- /dev/null +++ b/pageObjectModel/dashboardPage.ts @@ -0,0 +1,162 @@ +import { Page } from "@playwright/test"; + +export class dashboardPage { + readonly dashboardButton: string; + readonly dashboardNavbarText: string; + readonly dashboardNavbarStartMatchingButton: string; + readonly dashboardNavbarMatchesButton: string; + readonly dashboardNavbarNotificationsButton: string; + readonly dashboardNavbarAccountButton: string; + readonly dashboardNavbarAccountTexts: string; + readonly dashboardNavbarAccountProfileText: string; + readonly dashboardNavbarAccountAdminText: string; + readonly dashboardNavbarAccountLogoutText: string; + readonly dashboardNavbarAccountSubmitFeedbackText: string; + + readonly dashboardAllOpportunitiesCard: string; + + readonly dashboardAllCardHeaderText: string; + + readonly dashboardAllOpportunitiesCardText: string; + readonly dashboardNewResearchersOpportunitiesCardText: string; + readonly dashboardExpireResearchersOpportunitiesCardText: string; + readonly dashboardFoundingAvailableCardText: string; + readonly dashboardAllResearchersCardText: string; + readonly dashboardFoundingScenarioText: string; + readonly dashboardRecentMatchNotificationsText: string; + + readonly dashboardFoundingScenarioButton: string; + readonly dashboardRecentMatchNotificationsButton: string; + + readonly dashboardAllOpportunitiesIcon: string; + readonly dashboardResearchersOpportunitiesIcon: string; + readonly dashboardFoundingAvailableIcon: string; + readonly dashboardAllResearchersIcon: string; + readonly dashboardFoundingScenarioIcon: string; + readonly dashboardRecentMatchNotificationsIcon: string; + + readonly dashboardAllOpportunitiesValue: string; + readonly dashboardNewResearchersOpportunitiesValue: string; + readonly dashboardExpireResearchersOpportunitiesValue: string; + readonly dashboardFoundingAvailablePotentialValue: string; + readonly dashboardFoundingAvailableTotalValue: string; + readonly dashboardAllResearchersValue: string; + + readonly dashboardFoundingScenarioRechart: string; + + readonly dashboardNotificationTitle: string; + readonly dashboardNotificationText: string; + readonly dashboardNotificationViewButton: string; + readonly dashboardNotificationButtonIcon: string; + + readonly dashboardNotificationLists: string; + readonly dashboardNoNotificationText: string; + + // Dashboard NotificationModal + readonly notificationModalHeader: string; + readonly notificationModalCloseIcon: string; + readonly notificationModalNameLabel: string; + readonly notificationModalName: string; + readonly notificationModalNotificationTypeSelectLabel: string; + readonly notificationModalNotificationType: string; + readonly notificationModalUsersEmailSelectLabel: string; + readonly notificationModalUsersEmail: string; + readonly notificationModalEmailCheckboxLabel: string; + readonly notificationModalEmailCheckboxText: string; + readonly notificationModalEmailCheckbox: string; + readonly notificationModalMatchTypeSelectLabel: string; + readonly notificationModalMatchTypeSelect: string; + readonly notificationModalOpportunitiesSelectLabel: string; + readonly notificationModalOpportunitiesSelect: string; + readonly notificationModalSelectCollaborationLabel: string; + readonly notificationModalSelectCollaborationIcon: string; + readonly notificationModalSelectCollaborationText: string; + readonly notificationModalFindCollaborationHeaderIcon: string; + readonly notificationModalFindCollaborationHeader: string; + readonly notificationModalFindCollaborationLabel: string; + readonly notificationModalFindCollaborationSelect: string; + readonly notificationModalFindCollaborationText: string; + readonly notificationModalCloseButton: string; + + readonly dashboardCardLoader: string; + + constructor(page: Page) { + this.dashboardButton = `css=a[data-intro='navbar-dashboard']`; + this.dashboardNavbarText = `css=header[class='sticky border-b-[1px] bg-white shadow-sm top-0 z-40 w-full bg-blue-50'] a[data-intro]`; + this.dashboardNavbarStartMatchingButton = `css=div[class='hidden md:flex items-center gap-4'] a:nth-child(1)`; + this.dashboardNavbarMatchesButton = `css=a[data-intro='navbar-matchlist']`; + this.dashboardNavbarNotificationsButton = `css=a[data-intro='navbar-notification']`; + this.dashboardNavbarAccountButton = `css=nav[class='hidden md:flex justify-end items-center gap-4'] div[type='button']`; + this.dashboardNavbarAccountTexts = `css=a[role="menuitem"]`; + this.dashboardNavbarAccountProfileText = `css=a[href='/profile']`; + this.dashboardNavbarAccountAdminText = `css=a[href='/organization-admin']`; + this.dashboardNavbarAccountLogoutText = `css=a[href='/logout']`; + this.dashboardNavbarAccountSubmitFeedbackText = `css=div[class='relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 w-full flex items-center gap-2 cursor-pointer']`; + + this.dashboardAllOpportunitiesCard = `css=div[data-intro='opportunities-count'] div[class='tracking-tight text-sm font-medium']`; + + this.dashboardAllCardHeaderText = `css=div[class='grid gap-4 md:grid-cols-2 lg:grid-cols-4 w-full'] div div[class='p-6 flex flex-row items-center justify-between space-y-0 pb-2']`; + + this.dashboardAllOpportunitiesCardText = `css=div[data-intro='opportunities-count'] p[class='text-xs text-muted-foreground']`; + this.dashboardNewResearchersOpportunitiesCardText = `css=body > div:nth-child(1) > div:nth-child(1) > main:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > p:nth-child(2)`; + this.dashboardExpireResearchersOpportunitiesCardText = `css=body > div:nth-child(1) > div:nth-child(1) > main:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(2) > p:nth-child(2)`; + this.dashboardFoundingAvailableCardText = `css=div[data-intro='funding-available'] p[class='text-xs text-muted-foreground']`; + this.dashboardAllResearchersCardText = `css=div[data-intro='researchers-count'] p[class='text-xs text-muted-foreground']`; + this.dashboardFoundingScenarioText = `css=div[data-intro='scenario-status'] p[class='text-xs text-muted-foreground']`; + this.dashboardRecentMatchNotificationsText = `css=div[data-intro='recent-notifications'] div[class='flex items-center'] p[class='text-xs text-muted-foreground']`; + + this.dashboardFoundingScenarioButton = `css=div[data-intro='scenario-status'] span[class='text-sm font-bold text-blue-600 cursor-pointer hover:underline']`; + this.dashboardRecentMatchNotificationsButton = `css=div[data-intro='recent-notifications'] span[class='text-sm font-bold text-blue-600 cursor-pointer hover:underline']`; + + this.dashboardAllOpportunitiesIcon = `css=svg[class='lucide lucide-dollar-sign']`; + this.dashboardResearchersOpportunitiesIcon = `css=svg[class='lucide lucide-hand-coins']`; + this.dashboardFoundingAvailableIcon = `css=svg[class='lucide lucide-circle-dollar-sign']`; + this.dashboardAllResearchersIcon = `css=svg[class='lucide lucide-circle-help h-4 w-4 text-blue-600']`; + this.dashboardFoundingScenarioIcon = `css=svg[class='lucide lucide-folder-kanban']`; + this.dashboardRecentMatchNotificationsIcon = `css=svg[class='lucide lucide-bell-ring']`; + + this.dashboardAllOpportunitiesValue = `css=div[data-intro='opportunities-count'] div[class='text-2xl font-bold h-[1.75rem] flex items-center']`; + this.dashboardNewResearchersOpportunitiesValue = `css=div[class='text-2xl font-bold text-blue-600 h-[1.75rem] flex items-center'] span`; + this.dashboardExpireResearchersOpportunitiesValue = `css=div[class='text-2xl font-bold text-amber-500 h-[1.75rem] flex items-center'] span`; + this.dashboardFoundingAvailablePotentialValue = `css=span[class='text-2xl font-bold text-blue-600']`; + this.dashboardFoundingAvailableTotalValue = `css=span[class='ml-1 text-muted-foreground']`; + this.dashboardAllResearchersValue = `css=div[data-intro="dashboard-welcome"] div[data-intro="researchers-count"] div[class="p-6 pt-0"] div span`; + + this.dashboardFoundingScenarioRechart = `css=div[class='recharts-wrapper']`; + + this.dashboardNotificationTitle = `css=div[data-intro='recent-notifications'] div:nth-child(2) div:nth-child(2) div p:nth-child(1)`; + this.dashboardNotificationText = `css=div[data-intro='recent-notifications'] div:nth-child(2) div:nth-child(2) div p:nth-child(2)`; + this.dashboardNotificationViewButton = `css=body > div:nth-child(1) > div:nth-child(1) > main:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(6) > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > button:nth-child(1)`; + this.dashboardNotificationButtonIcon = `css=button[class='justify-center gap-2 whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-blue-600 text-white px-3 py-1 rounded-full hover:bg-blue-700 text-xs font-medium flex items-center h-auto'] svg[class='lucide lucide-eye ml-1']`; + + this.dashboardNotificationLists = `css=div[data-intro='recent-notifications'] div[class='pt-0 mt-5'] div[class='space-y-4'] div div p:first-child`; + this.dashboardNoNotificationText = `css=div[data-intro='recent-notifications'] div[class='flex flex-col items-center justify-center py-8 text-center']`; + + this.notificationModalHeader = `css=div[class='flex justify-between items-center'] h2[class='text-2xl font-bold']`; + this.notificationModalCloseIcon = `css=button[class='absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground']`; + this.notificationModalNameLabel = `xpath=//label[normalize-space()='Name']`; + this.notificationModalName = `css=input[name='name']`; + this.notificationModalNotificationTypeSelectLabel = `xpath=//label[normalize-space()='Notification Type']`; + this.notificationModalNotificationType = `css=button[id=':rmg:-form-item']`; + this.notificationModalUsersEmailSelectLabel = `xpath=//label[normalize-space()='Select Email Users']`; + this.notificationModalUsersEmail = `css=button[id=':rbt:-form-item']`; + this.notificationModalEmailCheckboxLabel = `xpath=//label[normalize-space()='Confirm receiving emails?']`; + this.notificationModalEmailCheckboxText = `css=div[class='space-y-1 leading-none'] p`; + this.notificationModalEmailCheckbox = `css=button[id=':rbv:-form-item']`; + this.notificationModalMatchTypeSelectLabel = `xpath=//label[normalize-space()='Match Type']`; + this.notificationModalMatchTypeSelect = `css=button[id=':rc0:-form-item']`; + this.notificationModalOpportunitiesSelectLabel = `xpath=//label[normalize-space()='Opportunity Search']`; + this.notificationModalOpportunitiesSelect = `css=button[id=':rc2:-form-item']`; + this.notificationModalSelectCollaborationLabel = `xpath=//label[normalize-space()='Select Collaboration Institutions']`; + this.notificationModalSelectCollaborationIcon = `css=svg[class='lucide lucide-circle-alert text-destructive mr-1']`; + this.notificationModalSelectCollaborationText = `css=p[id=':rfc:-form-item-description']`; + this.notificationModalFindCollaborationHeaderIcon = `css=svg[class='lucide lucide-building2 h-5 w-5 text-blue-600']`; + this.notificationModalFindCollaborationHeader = `css=h2[class='text-2xl font-semibold text-blue-600']`; + this.notificationModalFindCollaborationLabel = `css=label[class='text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 mb-2 block']`; + this.notificationModalFindCollaborationSelect = `css=.y-between.group.flex.w-full.rounded-md.border.border-input.bg-transparent.px-3.py-1.text-sm.shadow-sm.transition-colors.justify-between.min-h-10.h-auto.cursor-not-allowed.opacity-50.mb-2`; + this.notificationModalFindCollaborationText = `css=div[class='text-sm text-gray-500'] span`; + this.notificationModalCloseButton = `css=button[class='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2']`; + + this.dashboardCardLoader = `css=div span[class='flex-col items-center justify-center flex']`; + } +} diff --git a/pageObjectModel/homePage.ts b/pageObjectModel/homePage.ts new file mode 100644 index 0000000..7e63437 --- /dev/null +++ b/pageObjectModel/homePage.ts @@ -0,0 +1,41 @@ +import { Page } from "playwright"; + +export class homePage { + readonly homePageCanvas: string; + readonly loginButton: string; + readonly registerButton: string; + readonly headerImage: string; + readonly homePageHeroText: string; + readonly homePageSubHeroText: string; + readonly startTrialButton: string; + readonly viewPricingButton: string; + readonly pricingSectionHeroText: string; + readonly pricingSectionSubHeroText: string; + readonly researcherBlockHeader: string; + readonly administratorBlockHeader: string; + readonly researcherPriceText: string; + readonly pricingSectionLinkTexts: string; + readonly getStartedButton: string; + readonly contactSalesButton: string; + readonly footerBlock: string; + + constructor(page: Page) { + this.homePageCanvas = `css=canvas[width='1472']`; + this.loginButton = `css=a[data-intro="navbar-notification"][data-discover="true"][href="/login"]`; + this.registerButton = `css=a[data-intro="navbar-notification"][href="/register"][data-discover="true"]`; + this.headerImage = `css=div[class='flex items-center gap-4'] img[alt='FundFit Logo']`; + this.homePageHeroText = `css=h1[class='text-3xl md:text-4xl lg:text-5xl font-bold tracking-tight text-gray-900 break-normal']`; + this.homePageSubHeroText = `css=p[class='text-lg md:text-xl text-gray-600 leading-relaxed break-normal']`; + this.startTrialButton = `css=div[class='flex flex-col sm:flex-row gap-4']> a:nth-child(1)`; + this.viewPricingButton = `css=div[class='flex flex-col sm:flex-row gap-4']> button`; + this.researcherPriceText = `css=span[class='text-3xl font-bold text-orange-600']`; + this.pricingSectionHeroText = `css=h2[class='text-3xl md:text-4xl font-bold text-gray-900 mb-4']`; + this.pricingSectionSubHeroText = `css=.text-xl.text-gray-600`; + this.footerBlock = `css=footer[class='text-center py-4'] span:nth-child(1)`; + this.researcherBlockHeader = `css=div[class='grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto'] div:nth-child(1) div:nth-child(1) h4:nth-child(1)`; + this.administratorBlockHeader = `css=div[class='grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto'] div:nth-child(2) div:nth-child(1) h4:nth-child(1)`; + this.pricingSectionLinkTexts = `css=ul[class='space-y-3 mb-8'] li[class='flex items-start text-sm text-gray-600']`; + this.getStartedButton = `css=a[class='block'] button[class='inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 h-11 rounded-md px-8 w-full bg-primary text-white font-bold hover:bg-orange-600']`; + this.contactSalesButton = `css=a[href='https://streamlyne.com/fundfit/']`; + } +} diff --git a/pageObjectModel/loginPage.ts b/pageObjectModel/loginPage.ts new file mode 100644 index 0000000..174f082 --- /dev/null +++ b/pageObjectModel/loginPage.ts @@ -0,0 +1,41 @@ +import { Page } from "playwright"; + +export class loginPage { + readonly email: string; + readonly password: string; + readonly eyeIcon: string; + readonly loginButton: string; + readonly toastMessage: string; + readonly successfulLoginMessage: string; + readonly emailInputBoxHeader: string; + readonly passwordInputBoxHeader: string; + readonly emailInputBoxErrorMessage: string; + readonly passwordInputBoxErrorMessage: string; + readonly loginPageRegisterButton: string; + readonly loginPageHeaderText: string; + readonly forgetPasswordButton: string; + readonly forgetPasswordFormHeaderText: string; + readonly ForgetPasswordEmailHeader: string; + readonly ForgetPasswordEmailInputBox: string; + readonly forgetPasswordFormSubHeaderText: string; + + constructor(page: Page) { + this.loginPageHeaderText = + "css=div[class='flex flex-col space-y-1.5 p-6'] div:nth-child(2)"; + this.email = `css=input[type='email']`; + this.password = `css=input[name='password']`; + this.eyeIcon = `css=button[class='inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:text-accent-foreground h-9 rounded-md px-3 ml-auto hover:bg-transparent']`; + this.loginButton = `css=button[type='submit']`; + this.toastMessage = `css=div[class='grid gap-1']`; + this.emailInputBoxHeader = `xpath=//label[normalize-space()='Email Address']`; + this.passwordInputBoxHeader = `xpath=//label[normalize-space()='Password']`; + this.emailInputBoxErrorMessage = `xpath=(//p[@id=':re:-form-item-message'])[1]`; + this.passwordInputBoxErrorMessage = `xpath= //p[@id=':rf:-form-item-message']`; + this.loginPageRegisterButton = `css=div[class='text-sm text-muted-foreground m-auto'] a[href='/register']`; + this.forgetPasswordButton = `css=a[href='/forgot-password']`; + this.forgetPasswordFormHeaderText = `css=div[class='flex flex-col space-y-1.5 p-6'] div:nth-child(2)`; + this.forgetPasswordFormSubHeaderText = `css=div[class='flex flex-col space-y-1.5 p-6'] div:nth-child(3)`; + this.ForgetPasswordEmailHeader = `css=div[class="space-y-2"] label`; + this.ForgetPasswordEmailInputBox = `css=input[id=':ru:-form-item']`; + } +} diff --git a/pageObjectModel/matchesPage.ts b/pageObjectModel/matchesPage.ts new file mode 100644 index 0000000..4b12449 --- /dev/null +++ b/pageObjectModel/matchesPage.ts @@ -0,0 +1,9 @@ +import { Page } from "@playwright/test"; + +export class matchesPage { + readonly matchesHeroText: string; + + constructor(page: Page) { + this.matchesHeroText = `css=h1[class='text-2xl font-semibold']`; + } +} diff --git a/pageObjectModel/notificationPage.ts b/pageObjectModel/notificationPage.ts new file mode 100644 index 0000000..1dfd4ae --- /dev/null +++ b/pageObjectModel/notificationPage.ts @@ -0,0 +1,9 @@ +import { Page } from "@playwright/test"; + +export class notificationPage { + readonly notificationHeroText: string; + + constructor(page: Page) { + this.notificationHeroText = `css=h1[class='text-2xl font-semibold']`; + } +} diff --git a/pageObjectModel/profilePage.ts b/pageObjectModel/profilePage.ts new file mode 100644 index 0000000..83f2c15 --- /dev/null +++ b/pageObjectModel/profilePage.ts @@ -0,0 +1,389 @@ +import { Page } from "playwright"; + +export class profilePage { + readonly sidebarPhoto: string; + readonly sidebarFullNameText: string; + readonly sidebarTitleText: string; + + readonly sidebarGeneralInfoButton: string; + readonly sidebarBackgroundButton: string; + readonly sidebarWorksButton: string; + readonly sidebarProjectsButton: string; + readonly sidebarAttachmentsButton: string; + readonly sidebarORCIDButton: string; + + readonly sidebarGeneralInfoIcon: string; + readonly sidebarBackgroundIcon: string; + readonly sidebarWorksIcon: string; + readonly sidebarProjectsIcon: string; + readonly sidebarAttachmentsIcon: string; + readonly sidebarORCIDIcon: string; + + readonly sidebarGeneralInfoText: string; + readonly sidebarBackgroundText: string; + readonly sidebarWorksText: string; + readonly sidebarProjectsText: string; + readonly sidebarAttachmentsText: string; + readonly sidebarORCIDText: string; + + readonly sidebarBackgroundValue: string; + readonly sidebarWorksValue: string; + readonly sidebarProjectsValue: string; + readonly sidebarAttachmentsValue: string; + + readonly topBarGeneralButton: string; + readonly topBarBackgroundButton: string; + readonly topBarWorksButton: string; + readonly topBarProjectsButton: string; + readonly topBarAttachmentsButton: string; + + readonly profileHeaderText: string; + readonly editProfileIcon: string; + readonly editProfile: string; + readonly passwordSettingsIcon: string; + readonly passwordSettings: string; + readonly emailSettingsIcon: string; + readonly emailSettings: string; + + readonly firstName: string; + readonly lastName: string; + readonly email: string; + readonly institution: string; + readonly phoneNumber: string; + readonly biography: string; + + readonly firstNameHeader: string; + readonly lastNameHeader: string; + readonly emailHeader: string; + readonly institutionHeader: string; + readonly phoneNumberHeader: string; + readonly biographyHeader: string; + + readonly editProfileHeaderText: string; + readonly editProfileFirstName: string; + readonly editProfileLastName: string; + readonly editProfilePhoneNumber: string; + readonly editProfileBiography: string; + readonly editProfileFirstNameHeaderText: string; + readonly editProfileFirstNamePlaceHolderText: string; + readonly editProfileFirstNameErrorText: string; + readonly editProfileLastNameHeaderText: string; + readonly editProfileLastNameErrorText: string; + readonly editProfilePhoneNumberHeaderText: string; + readonly editProfilePhoneNumberErrorText: string; + readonly editProfilePhoneNumberErrorText2: string; + readonly editProfileBiographyHeaderText: string; + + readonly passwordSettingsHeaderText: string; + readonly passwordSettingsNewPasswordHeaderText: string; + readonly passwordSettingsNewPasswordInput: string; + readonly passwordSettingsNewPasswordEyeIconButton: string; + readonly passwordSettingsConfirmNewPasswordHeaderText: string; + readonly passwordSettingsConfirmNewPasswordInput: string; + readonly passwordSettingsConfirmNewPasswordErrorText: string; + readonly passwordSettingsConfirmNewPasswordErrorText2: string; + + readonly emailSettingsHeaderText: string; + readonly emailSettingsCurrentEmailHeaderText: string; + readonly emailSettingsCurrentEmailText: string; + readonly emailSettingsEditEmailButton: string; + readonly emailSettingsEditEmailHeaderText: string; + readonly emailSettingsNewEmailFrom: string; + readonly emailSettingsAlreadyVerifiedErrorText: string; + readonly emailSettingsInvalidErrorText: string; + + readonly backgroundHeaderText: string; + readonly backgroundPlusIconButton: string; + readonly noBackgroundIcon: string; + readonly noBackgroundHeaderText: string; + readonly noBackgroundText: string; + readonly noBackgroundAddButton: string; + + readonly createBackgroundHeaderText: string; + readonly createBackgroundTypeHeaderText: string; + readonly createBackgroundTypeButtonText: string; + readonly createBackgroundTypeErrorText: string; + readonly createBackgroundTypeTexts: string; + readonly createBackgroundRoleInput: string; + readonly createBackgroundDepartmentInput: string; + readonly createBackgroundOrganizationInput: string; + readonly createBackgroundOrganizationAddressInput: string; + readonly createBackgroundStartYearInput: string; + readonly createBackgroundStartMonthInput: string; + readonly createBackgroundStartDayInput: string; + readonly createBackgroundEndYearInput: string; + readonly createBackgroundEndMonthInput: string; + readonly createBackgroundEndDayInput: string; + + readonly worksHeaderText: string; + readonly worksPlusIconButton: string; + readonly noWorksIcon: string; + readonly noWorksHeaderText: string; + readonly noWorksText: string; + readonly noWorksAddButton: string; + + readonly createWorkHeaderText: string; + readonly createWorkTypeHeaderText: string; + readonly createWorkTypeButtonText: string; + readonly createWorkTypeErrorText: string; + readonly createWorkJournalTitleHeaderText: string; + readonly createWorkJournalTitleInput: string; + readonly createWorkJournalTitleErrorText: string; + readonly createWorkTitleHeaderText: string; + readonly createWorkTitleInput: string; + readonly createWorkTitleErrorText: string; + readonly createWorkPublicationYearHeaderText: string; + readonly createWorkPublicationYearInput: string; + readonly createWorkPublicationMonthHeaderText: string; + readonly createWorkPublicationMonthInput: string; + readonly createWorkPublicationDayHeaderText: string; + readonly createWorkPublicationDayInput: string; + + readonly projectsHeaderText: string; + readonly projectsPlusIconButton: string; + readonly noProjectsIcon: string; + readonly noProjectsHeaderText: string; + readonly noProjectsText: string; + readonly noProjectsAddButton: string; + + readonly createProjectsHeaderTextSection: string; + readonly createProjectsTypeHeaderText: string; + readonly createProjectsTypeButtonText: string; + readonly createProjectsTitleHeaderText: string; + readonly createProjectsTitleInput: string; + readonly createProjectsOrgHeaderText: string; + readonly createProjectsOrgInput: string; + readonly createProjectsOrgTypeHeaderText: string; + readonly createProjectsOrgTypeInput: string; + readonly createProjectsOrgLocationHeaderText: string; + readonly createProjectsOrgLocationInput: string; + readonly createProjectsOrgAmountHeaderText: string; + readonly createProjectsOrgAmountInput: string; + readonly createProjectsStartYearHeaderText: string; + readonly createProjectsStartYearInput: string; + readonly createProjectsStartMonthHeaderText: string; + readonly createProjectsStartMonthInput: string; + readonly createProjectsStartDayHeaderText: string; + readonly createProjectsStartDayInput: string; + readonly createProjectsEndYearHeaderText: string; + readonly createProjectsEndYearInput: string; + readonly createProjectsEndMonthHeaderText: string; + readonly createProjectsEndMonthInput: string; + readonly createProjectsEndDayHeaderText: string; + readonly createProjectsEndDayInput: string; + readonly selectProjectTypeComboBoxInput: string; + readonly selectProjectTypeComboBoxTextValues: string; + + readonly noDataFoundTexts: string; + readonly createTypeSearchInput: string; // Added for combobox search + readonly noResultsFoundMessage: string; // Added for "No results found" message + + readonly loadingRecordsText: string; + readonly table: string; + readonly tableUpdateButton: string; + readonly tableDeleteButton: string; + readonly deleteModalHeaderText: string; + readonly deleteModalButton: string; + + readonly toastMessageHeaderText: string; + readonly toastMessageText: string; + readonly toastCloseButton: string; + readonly backAndCancelButton: string; + readonly submitButton: string; + + constructor(page: Page) { + this.sidebarPhoto = `css=span[class='flex h-full w-full items-center justify-center rounded-full bg-primary text-primary-foreground text-xl font-semibold']`; + this.sidebarFullNameText = `css=h2[class='text-lg font-semibold mb-1']`; + this.sidebarTitleText = `css=p[class='text-sm text-muted-foreground mb-6']`; + + this.sidebarGeneralInfoButton = `css=nav[class='w-full space-y-1'] > button:nth-child(1)`; + this.sidebarBackgroundButton = `css=nav[class='w-full space-y-1'] > button:nth-child(3)`; + this.sidebarWorksButton = `css=nav[class='w-full space-y-1'] > button:nth-child(5)`; + this.sidebarProjectsButton = `css=nav[class='w-full space-y-1'] > button:nth-child(7)`; + this.sidebarAttachmentsButton = `css=nav[class='w-full space-y-1'] > button:nth-child(9)`; + this.sidebarORCIDButton = `css=div[class='w-full lg:w-72 shrink-0'] div[class='mt-auto pt-6 w-full'] button`; + + this.sidebarGeneralInfoText = `css=nav[class='w-full space-y-1'] > button:nth-child(1) > span:nth-child(2)`; + this.sidebarBackgroundText = `css=nav[class='w-full space-y-1'] > button:nth-child(3) > span:nth-child(2)`; + this.sidebarWorksText = `css=nav[class='w-full space-y-1'] > button:nth-child(5) > span:nth-child(2)`; + this.sidebarProjectsText = `css=nav[class='w-full space-y-1'] > button:nth-child(7) > span:nth-child(2)`; + this.sidebarAttachmentsText = `css=nav[class='w-full space-y-1'] > button:nth-child(9) > span:nth-child(2)`; + this.sidebarORCIDText = `css=div[class='w-full lg:w-72 shrink-0'] div[class='mt-auto pt-6 w-full'] button`; + + this.sidebarGeneralInfoIcon = `css=nav[class='w-full space-y-1'] > button:nth-child(1) > span:nth-child(1)`; + this.sidebarBackgroundIcon = `css=nav[class='w-full space-y-1'] > button:nth-child(3) > span:nth-child(1)`; + this.sidebarWorksIcon = `css=nav[class='w-full space-y-1'] > button:nth-child(5) > span:nth-child(1)`; + this.sidebarProjectsIcon = `css=nav[class='w-full space-y-1'] > button:nth-child(7) > span:nth-child(1)`; + this.sidebarAttachmentsIcon = `css=nav[class='w-full space-y-1'] > button:nth-child(9) > span:nth-child(1)`; + this.sidebarORCIDIcon = `css=div[class='w-full lg:w-72 shrink-0'] div[class='mt-auto pt-6 w-full'] button`; + + this.sidebarBackgroundValue = `css=nav[class='w-full space-y-1'] > button:nth-child(3) > span:nth-child(3)`; + this.sidebarWorksValue = `css=nav[class='w-full space-y-1'] > button:nth-child(5) > span:nth-child(3)`; + this.sidebarProjectsValue = `css=nav[class='w-full space-y-1'] > button:nth-child(7) > span:nth-child(3)`; + this.sidebarAttachmentsValue = `css=nav[class='w-full space-y-1'] > button:nth-child(9) > span:nth-child(3)`; + + this.topBarGeneralButton = `css=div[class='inline-flex items-center p-1 text-muted-foreground h-14 w-full justify-start rounded-none bg-transparent border-b px-6'] button:nth-child(1)`; + this.topBarBackgroundButton = `css=div[class='inline-flex items-center p-1 text-muted-foreground h-14 w-full justify-start rounded-none bg-transparent border-b px-6'] button:nth-child(2)`; + this.topBarWorksButton = `css=div[class='inline-flex items-center p-1 text-muted-foreground h-14 w-full justify-start rounded-none bg-transparent border-b px-6'] button:nth-child(3)`; + this.topBarProjectsButton = `css=div[class='inline-flex items-center p-1 text-muted-foreground h-14 w-full justify-start rounded-none bg-transparent border-b px-6'] button:nth-child(4)`; + this.topBarAttachmentsButton = `css=div[class='inline-flex items-center p-1 text-muted-foreground h-14 w-full justify-start rounded-none bg-transparent border-b px-6'] button:nth-child(5)`; + + this.profileHeaderText = `css=div[class='w-full max-w-3xl'] div h1`; + this.editProfileIcon = `css=div[class='flex gap-3'] svg[class='lucide lucide-square-pen']`; + this.editProfile = `css=div[class='flex gap-3'] a[href='/profile/settings']`; + this.passwordSettings = `css=div[class='flex gap-3'] svg[class='lucide lucide-lock']`; + this.passwordSettings = `css=div[class='flex gap-3'] a[href='/profile/settings/password']`; + this.emailSettings = `css=div[class='flex gap-3'] svg[class='lucide lucide-mail']`; + this.emailSettings = `css=div[class='flex gap-3'] a[href='/profile/settings/email']`; + + this.firstName = `css=div[class='space-y-6'] div div:nth-child(1) p`; + this.lastName = `css=div[class='space-y-6'] div div:nth-child(2) p`; + this.email = `css=div[class='space-y-6'] > div:nth-child(2) p`; + this.institution = `css=div[class='space-y-6'] > div:nth-child(3) p`; + this.phoneNumber = `css=div[class='space-y-6'] > div:nth-child(4) p`; + this.biography = `css=div[class='space-y-6'] > div:nth-child(5) p`; + + this.firstNameHeader = `css=div[class='space-y-6'] div div:nth-child(1) h3`; + this.lastNameHeader = `css=div[class='space-y-6'] div div:nth-child(2) h3`; + this.emailHeader = `css=div[class='space-y-6'] > div:nth-child(2) h3`; + this.institutionHeader = `css=div[class='space-y-6'] > div:nth-child(3) h3`; + this.phoneNumberHeader = `css=div[class='space-y-6'] > div:nth-child(4) h3`; + this.biographyHeader = `css=div[class='space-y-6'] > div:nth-child(5) h3`; + + this.editProfileHeaderText = `xpath=//h1[normalize-space()='Update Profile']`; + this.editProfileFirstName = `css=input[name='first_name']`; + this.editProfileLastName = `css=input[name='last_name']`; + this.editProfilePhoneNumber = `css=input[name='phone_number']`; + this.editProfileBiography = `css=textarea[name='biography']`; + this.editProfileFirstNameHeaderText = `xpath=//label[normalize-space()='First Name*']`; + this.editProfileFirstNamePlaceHolderText = `xpath=//p[normalize-space()='First name is required']`; + this.editProfileFirstNameErrorText = `css=div[class='flex space-x-4'] div[class='space-y-2 w-1/2'] p[class='text-sm font-medium text-destructive']`; + this.editProfileLastNameHeaderText = `xpath=//label[normalize-space()='Last Name*']`; + this.editProfileLastNameErrorText = `css=div[class='space-y-2 w-1/2'] p[class='text-sm font-medium text-destructive']`; + this.editProfilePhoneNumberHeaderText = `xpath=//label[normalize-space()='Phone Number']`; + this.editProfilePhoneNumberErrorText = `xpath=//p[normalize-space()='Phone number must contain only numbers']`; + this.editProfilePhoneNumberErrorText2 = `xpath=//p[normalize-space()='Ensure this field has no more than 15 characters.']`; + this.editProfileBiographyHeaderText = `xpath=//label[normalize-space()='Biography']`; + + this.passwordSettingsHeaderText = `xpath=//h1[normalize-space()='Change Password']`; + this.passwordSettingsNewPasswordHeaderText = `xpath=//label[normalize-space()='New Password*']`; + this.passwordSettingsNewPasswordInput = `css=input[name='new_password1']`; + this.passwordSettingsNewPasswordEyeIconButton = `"css=button[class='inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:text-accent-foreground h-9 rounded-md px-3 ml-2 hover:bg-transparent']"`; + this.passwordSettingsConfirmNewPasswordHeaderText = `xpath=//label[normalize-space()='Confirm New Password*']`; + this.passwordSettingsConfirmNewPasswordInput = `css=input[name='new_password2']`; + this.passwordSettingsConfirmNewPasswordErrorText = `css=div[class="space-y-2"] p[class="text-sm font-medium text-destructive"]`; + this.passwordSettingsConfirmNewPasswordErrorText2 = `css=div[class="text-sm text-red-500 font-medium"]`; + + this.emailSettingsHeaderText = `xpath=//h1[normalize-space()='Email Settings']`; + this.emailSettingsCurrentEmailHeaderText = `css=h2[class='text-sm font-medium']`; + this.emailSettingsCurrentEmailText = `css=span[class='text-sm']`; + this.emailSettingsEditEmailButton = `css=div[class='flex gap-3'] button`; + this.emailSettingsEditEmailHeaderText = `css=h2[class='text-lg font-semibold']`; + this.emailSettingsNewEmailFrom = `css=form[class='space-y-6'] div[class='space-y-2']`; + this.emailSettingsAlreadyVerifiedErrorText = `css=form[class='space-y-6'] div[class='text-sm text-red-500 font-medium']`; + this.emailSettingsInvalidErrorText = `css=form[class='space-y-6'] p[class='text-sm font-medium text-destructive']`; + + this.backgroundHeaderText = `xpath=//h1[normalize-space()='Background']`; + this.backgroundPlusIconButton = `css=div[class="flex justify-between items-center mb-6"] a[href="/profile/background/create"]`; + this.noBackgroundIcon = `css=div svg[class='lucide lucide-graduation-cap w-8 h-8']`; + this.noBackgroundHeaderText = `css=h2[class='text-xl font-semibold']`; + this.noBackgroundText = `css=p[class='text-gray-500 max-w-md break-normal']`; + this.noBackgroundAddButton = `css=div[class='bg-white rounded-lg p-8 border border-gray-200 flex flex-col items-center justify-center text-center space-y-4'] a[href="/profile/background/create"]`; + + this.createBackgroundHeaderText = `xpath=//h1[normalize-space()='Create Background']`; + this.createBackgroundTypeHeaderText = `css=div[class='flex space-x-4']:first-child div[class='space-y-2 w-1/3'] label`; + this.createBackgroundTypeButtonText = `css=button[role='combobox']`; + this.createBackgroundTypeErrorText = `css=div[class='space-y-2 w-1/3'] p`; + this.createBackgroundTypeTexts = `css=div[role='presentation'] div[role='group'] div`; + this.createBackgroundRoleInput = `css=input[name='role']`; + this.createBackgroundDepartmentInput = `css=input[name='department']`; + this.createBackgroundOrganizationInput = `css=input[name='organization']`; + this.createBackgroundOrganizationAddressInput = `css=input[name='organization_address']`; + this.createBackgroundStartYearInput = `css=input[name='start_year']`; + this.createBackgroundStartMonthInput = `css=input[name='start_month']`; + this.createBackgroundStartDayInput = `css=input[name='start_day']`; + this.createBackgroundEndYearInput = `css=input[name='end_year']`; + this.createBackgroundEndMonthInput = `css=input[name='end_month']`; + this.createBackgroundEndDayInput = `css=input[name='end_day']`; + + this.worksHeaderText = `xpath=//h1[normalize-space()='Research Works']`; + this.worksPlusIconButton = `css=div[class="flex justify-between items-center mb-6"] a[href="/profile/work/create"]`; + this.noWorksIcon = `css=div svg[class='lucide lucide-book-open w-8 h-8']`; + this.noWorksHeaderText = `css=h2[class='text-xl font-semibold']`; + this.noWorksText = `css=p[class='text-gray-500 max-w-md break-normal']`; + this.noWorksAddButton = `css=div[class='bg-white rounded-lg p-8 border border-gray-200 flex flex-col items-center justify-center text-center space-y-4'] a[href="/profile/work/create"]`; + + this.createWorkHeaderText = `xpath=//h1[normalize-space()='Create Research Work']`; + this.createWorkTypeHeaderText = `css=div[class='flex space-x-4']:nth-child(1) div[class='space-y-2 w-1/3'] label`; + this.createWorkTypeButtonText = `css=button[role='combobox']`; + this.createWorkTypeErrorText = `css=div[class='space-y-2 w-1/3'] p[class='text-sm font-medium text-destructive']`; + this.createWorkJournalTitleHeaderText = `css=div[class='flex space-x-4']:nth-child(1) div[class='space-y-2 w-2/3'] label`; + this.createWorkJournalTitleInput = `css=input[name='journal_title']`; + this.createWorkJournalTitleErrorText = `css=div[class='space-y-2 w-2/3'] p[class='text-sm font-medium text-destructive']`; + this.createWorkTitleHeaderText = `css=div[class='flex space-x-4']:nth-child(2) div[class='space-y-2 w-full'] label`; + this.createWorkTitleInput = `css=input[name='title']`; + this.createWorkTitleErrorText = `css=div[class='space-y-2 w-full'] p[class='text-sm font-medium text-destructive']`; + this.createWorkPublicationYearHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/3']:nth-child(1) label`; + this.createWorkPublicationYearInput = `css=input[name='publication_year']`; + this.createWorkPublicationMonthHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/3']:nth-child(2) label`; + this.createWorkPublicationMonthInput = `css=input[name='publication_month']`; + this.createWorkPublicationDayHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/3']:nth-child(3) label`; + this.createWorkPublicationDayInput = `css=input[name='publication_day']`; + + this.projectsHeaderText = `xpath=//h1[normalize-space()='Research Projects']`; + this.projectsPlusIconButton = `css=div[class="flex justify-between items-center mb-6"] a[href="/profile/funding/create"]`; + this.noProjectsIcon = `css=div[class='w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center text-gray-400'] svg`; + this.noProjectsHeaderText = `css=h2[class='text-xl font-semibold']`; + this.noProjectsText = `css=p[class='text-gray-500 max-w-md break-normal']`; + this.noProjectsAddButton = `css=div[class='bg-white rounded-lg p-8 border border-gray-200 flex flex-col items-center justify-center text-center space-y-4'] a[href='/profile/funding/create']`; + + this.createProjectsHeaderTextSection = `css=div[class='flex justify-between items-center mb-6']`; + this.createProjectsTypeHeaderText = `css=div[class='flex space-x-4']:nth-child(1) div[class='space-y-2 w-1/3'] label`; + this.createProjectsTypeButtonText = `css=button[role='combobox']`; + this.createProjectsTitleHeaderText = `css=div[class='flex space-x-4']:nth-child(1) div[class='space-y-2 w-2/3'] label`; + this.createProjectsTitleInput = `css=input[name='title']`; + this.createProjectsOrgHeaderText = `css=div[class='flex space-x-4']:nth-child(2) div[class='space-y-2 w-1/4']:nth-child(1) label`; + this.createProjectsOrgInput = `css=input[name='organization']`; + this.createProjectsOrgTypeHeaderText = `css=div[class='flex space-x-4']:nth-child(2) div[class='space-y-2 w-1/4']:nth-child(2) label`; + this.createProjectsOrgTypeInput = `css=input[name='organization_defined_type']`; + this.createProjectsOrgLocationHeaderText = `css=div[class='flex space-x-4']:nth-child(2) div[class='space-y-2 w-1/4']:nth-child(3) label`; + this.createProjectsOrgLocationInput = `css=input[name='organization_address']`; + this.createProjectsOrgAmountHeaderText = `css=div[class='flex space-x-4']:nth-child(2) div[class='space-y-2 w-1/4']:nth-child(4) label`; + this.createProjectsOrgAmountInput = `css=input[name='amount']`; + this.createProjectsStartYearHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(1) label`; + this.createProjectsStartYearInput = `css=input[name='start_year']`; + this.createProjectsStartMonthHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(2) label`; + this.createProjectsStartMonthInput = `css=input[name='start_month']`; + this.createProjectsStartDayHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(3) label`; + this.createProjectsStartDayInput = `css=input[name='start_day']`; + this.createProjectsEndYearHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(4) label`; + this.createProjectsEndYearInput = `css=input[name='end_year']`; + this.createProjectsEndMonthHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(5) label`; + this.createProjectsEndMonthInput = `css=input[name='end_month']`; + this.createProjectsEndDayHeaderText = `css=div[class='flex space-x-4']:nth-child(3) div[class='space-y-2 w-1/6']:nth-child(6) label`; + this.createProjectsEndDayInput = `css=input[name='end_day']`; + this.selectProjectTypeComboBoxInput = `css=`; + this.selectProjectTypeComboBoxTextValues = `css=`; + + this.noDataFoundTexts = `css=div[class='bg-white rounded-lg p-8 border border-gray-200 flex flex-col items-center justify-center text-center space-y-4']`; + this.createTypeSearchInput = `css=div[class='flex items-center border-b px-3'] input`; + this.noResultsFoundMessage = `css=div[class='py-6 text-center text-sm']`; + + this.loadingRecordsText = `css=div[class='flex-1 bg-white rounded-lg border shadow-sm overflow-hidden'] div[class='p-6'] p`; + this.table = `css=div[class='border-gray-200']`; + this.tableUpdateButton = `css=tbody tr td:last-child div button:first-child`; + this.tableDeleteButton = `css=tbody tr td:last-child div button:last-child`; + this.deleteModalHeaderText = `css=h2[class='text-lg font-semibold']`; + this.deleteModalButton = `css=div[role='alertdialog'] button:last-child`; + + this.toastMessageHeaderText = `css=div[class='grid gap-1'] div[class='text-sm font-semibold']`; + this.toastMessageText = `css=div[class='grid gap-1'] div[class='text-sm opacity-90']`; + this.toastCloseButton = `css=button[class='absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600']`; + this.backAndCancelButton = `css=div[class='flex justify-between items-center mb-6'] a[class='inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border bg-background hover:bg-accent h-10 px-4 py-2 border-gray-200 text-gray-700 hover:text-gray-900 flex items-center gap-2']`; + this.submitButton = `css=button[type='submit']`; + + //Create Projects + this.selectProjectTypeComboBoxInput = `css=div[class='flex flex-col'] button[role="combobox"]`; + this.selectProjectTypeComboBoxTextValues = `css=div[role="option"]`; + } +} diff --git a/pageObjectModel/registerPage.ts b/pageObjectModel/registerPage.ts new file mode 100644 index 0000000..e069f16 --- /dev/null +++ b/pageObjectModel/registerPage.ts @@ -0,0 +1,56 @@ +import { Page } from "playwright"; + +export class registerPage { + readonly heroText: string; + readonly heroText2: string; + readonly firstName: string; + readonly lastName: string; + readonly email: string; + readonly phoneNumber: string; + readonly password: string; + readonly eyeIcon: string; + readonly confirmPassword: string; + readonly privacyPolicyText: string; + readonly privacyPolicyCheckbox: string; + readonly submitButton: string; + readonly toastMessage: string; + readonly firstNameHeader: string; + readonly firstNameErrorMessage: string; + readonly lastNameHeader: string; + readonly lastNameErrorMessage: string; + readonly emailInputBoxHeader: string; + readonly phoneNumberHeader: string; + readonly passwordInputBoxHeader: string; + readonly confirmPasswordHeader: string; + readonly emailInputBoxErrorMessage: string; + readonly phoneNumberInputBoxErrorMessage: string; + readonly passwordInputBoxErrorMessage: string; + readonly confirmPasswordInputBoxErrorMessage: string; + + constructor(page: Page) { + this.heroText = `xpath=//div[normalize-space()='To register, please fill in the form below.']`; + this.heroText2 = `css=div[class='flex flex-col space-y-1.5 p-6'] div:nth-child(3)`; + this.firstNameHeader = `css=label[for=':re:-form-item']`; + this.firstName = `css=input[id=':re:-form-item']`; + this.firstNameErrorMessage = `css=p[id=':re:-form-item-message']`; + this.lastNameHeader = `css=label[for=':rf:-form-item']`; + this.lastName = `css=input[id=':rf:-form-item']`; + this.lastNameErrorMessage = `css=p[id=':rf:-form-item-message']`; + this.emailInputBoxHeader = `css=label[for=':rg:-form-item']`; + this.email = `css=input[id=':rg:-form-item']`; + this.emailInputBoxErrorMessage = `css=p[id=':rg:-form-item-message']`; + this.phoneNumberHeader = `css=label[for=':rh:-form-item']`; + this.phoneNumber = `css=input[id=':rh:-form-item']`; + this.phoneNumberInputBoxErrorMessage = `css=p[id=':rh:-form-item-message']`; + this.passwordInputBoxHeader = `css=label[for=':r1g:-form-item']`; + this.password = `css=input[id=':ri:-form-item']`; + this.passwordInputBoxErrorMessage = `css=p[id=':r1g:-form-item-message']`; + this.confirmPasswordHeader = `css=label[for=':rj:-form-item']`; + this.confirmPassword = `css=input[id=':rj:-form-item']`; + this.confirmPasswordInputBoxErrorMessage = `css=p[id=':rj:-form-item-message']`; + this.privacyPolicyText = `css=div[class='flex items-center space-x-2']`; + this.privacyPolicyCheckbox = `css=div[class='flex items-center space-x-2'] button[role='checkbox']`; + this.submitButton = `css=button[type='submit']`; + this.toastMessage = `css=div[class='grid gap-1']`; + } +} diff --git a/pageObjectModel/startMatchingPage.ts b/pageObjectModel/startMatchingPage.ts new file mode 100644 index 0000000..c2449ee --- /dev/null +++ b/pageObjectModel/startMatchingPage.ts @@ -0,0 +1,9 @@ +import { Page } from "@playwright/test"; + +export class startMatchingPage { + readonly startMatchingHeroText: string; + + constructor(page: Page) { + this.startMatchingHeroText = `css=h2[class='text-xl text-center text-blue-600 font-bold']`; + } +} diff --git a/playwright.config.ts b/playwright.config.ts index fefd8b3..c109648 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,28 +1,45 @@ import { defineConfig, devices } from "@playwright/test"; +import fs from "fs"; +import * as dotenv from "dotenv"; + +dotenv.config(); + +// We're Deleting the last run reports to create a new one +const LAST_RUN_FILE = ".last-run.json"; +if (fs.existsSync(LAST_RUN_FILE)) { + fs.unlinkSync(LAST_RUN_FILE); +} export default defineConfig({ testDir: "./tests", + + // disabling the parallelism for the test suite to avoid flaky tests due to shared state fullyParallel: true, + + // Forbidding test.only on CI forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + // Retry failed tests only on CI + retries: process.env.CI ? 1 : 0, - reporter: [ - ["html", { outputFolder: "reports/html" }], - // ["json", { outputFile: "reports/results.json" }], - // ["junit", { outputFile: "reports/results.xml" }], - ], + // Running all tests in a single worker + workers: 5, + + // Generating only HTML reporter/ Allure report will be in the test run in the upcoming time + reporter: [["allure-playwright", { resultsDir: "reports/allure-results" }]], use: { trace: "on-first-retry", + screenshot: "only-on-failure", }, + // Browser options for test execution + // Executing tests only in chrome headless mode now + // In the upcoming time, we will be adding more browsers and CI/CD pipeline projects: [ { name: "chromium", use: { ...devices["Desktop Chrome"] }, }, - ], }); diff --git a/pom/accountPage.ts b/pom/accountPage.ts deleted file mode 100644 index c23a6c9..0000000 --- a/pom/accountPage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Page } from "playwright"; - -export class AccountPage { - readonly loginPageText: string; - readonly accountMenus: string; - - constructor(page: Page) { - this.loginPageText = `[class='m-0 font-size-sm'] strong`; - this.accountMenus = `.dropdown-menu.mz-sub-menu-96 > li > .both.dropdown-item.icon-left > .info > .title`; - } -} diff --git a/pom/lambdaHomePage.ts b/pom/lambdaHomePage.ts deleted file mode 100644 index fcd4cc8..0000000 --- a/pom/lambdaHomePage.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Page } from "playwright"; - -export class LambdaHomePage { - readonly textInputField: string; - readonly searchButton: string; - readonly accountButton: string; - readonly loginButton: string; - readonly emailField: string; - readonly passwordField: string; - readonly accountLoginButton: string; - readonly rightColumnList: string; - - constructor(page: Page) { - this.textInputField = `#entry_217820 [type='text']`; - this.searchButton = `div#search .type-text`; - this.accountButton = `div#widget-navbar-217834 > ul > li:nth-of-type(6) > a[role='button'] .title`; - this.loginButton = `div#widget-navbar-217834 .dropdown-menu.mz-sub-menu-96 > li:nth-of-type(1) > .both.dropdown-item.icon-left`; - this.emailField = `input#input-email`; - this.passwordField = `#input-password`; - this.accountLoginButton = `[action] .btn-primary`; - this.rightColumnList = `aside#column-right > div > a`; - } -} diff --git a/pom/loginPage.ts b/pom/loginPage.ts deleted file mode 100644 index b491645..0000000 --- a/pom/loginPage.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Page } from "playwright"; - -export class LoginPage { - readonly textInputField: string; - readonly emailInputField: string; - readonly passwordInputField: string; - readonly loginButton: string; - - - constructor(page: Page) { - this.textInputField = `#entry_217820 [type='text']`; - this.emailInputField = `#input-email`; - this.passwordInputField = `#input-password`; - this.loginButton = `[action] .btn-primary`; - - } -} diff --git a/testData/dashboardPageData.json b/testData/dashboardPageData.json new file mode 100644 index 0000000..86d19d8 --- /dev/null +++ b/testData/dashboardPageData.json @@ -0,0 +1,68 @@ +{ + "dashboardNavButtonText": "Dashboard", + "dashboardNavStartMatchingText": "Start Matching", + "dashboardNavMatchesText": "Matches", + "dashboardNavNotificationsText": "Notifications", + "dashboardNavAccountText": "Account", + "dashboardNavAccountProfileText": "Profile", + "dashboardNavAccountAdminText": "Admin", + "dashboardNavAccountSubmitFeedbackText": "Submit Feedback", + "dashboardNavAccountLogoutText": "Logout", + + "navbarTexts": ["Dashboard", "Start Matching", "Matches", "Notifications"], + "dashboardNavAccountTexts": ["Profile", "Admin", "Logout"], + + "dashboardHrefValue": "/dashboard", + "dashboardStartMatchingHrefValue": "/matcher", + "dashboardMatchesHrefValue": "/match", + "dashboardNotificationsHrefValue": "/notifications", + "dashboardAccountProfileHrefValue": "/profile", + "dashboardAccountAdminHrefValue": "/organization-admin", + "dashboardAccountLogoutHrefValue": "/logout", + + "dashboardAllCardHeaderTexts": [ + "# Opportunities", + "Opportunities", + "Funding Available", + "# Researchers", + "Funding Scenario Status\nView all", + "Recent Match Notifications\nView all" + ], + + "dashboardAllOpportunitiesCardText": "All FundFit opportunities", + "dashboardNewResearchersOpportunitiesCardText": "New in last 7 days", + "dashboardExpireResearchersOpportunitiesCardText": "Expiring in next 7 days", + "dashboardFoundingAvailableCardText": "My potential / Total funding", + "dashboardAllResearchersCardText": "Researchers available for matching", + "dashboardFoundingScenarioText": "Your funding scenario distribution", + "dashboardRecentMatchNotificationsText": "Your configured notification triggers", + + "dashboardFoundingScenarioButton": "View all", + "dashboardRecentMatchNotificationsButton": "View all", + + "dashboardNotificationViewButton": "View", + "dashboardNotificationMatchTexts": [ + "Match Opportunities", + "Match Researchers" + ], + "dashboardNotificationTexts": ["Researchers", "Opportunities"], + + "notificationModalHeader": "Notification", + "notificationModalHeader2": "Bulk Notification", + "notificationModalNameLabel": "Name", + "notificationModalNotificationTypeSelectLabel": "Notification Type", + "notificationModalNotificationTypeSelectOption": ["Email", "RSS"], + "notificationModalUsersEmailSelectLabel": "Select Email Users", + "notificationModalEmailCheckboxLabel": "Confirm receiving emails?", + "notificationModalEmailCheckboxText": "You will receive daily emails with opportunities that match your search", + "notificationModalMatchTypeSelectLabel": "Match Type", + "notificationModalOpportunitiesSelectLabel": "Opportunity Search", + "notificationModalSelectCollaborationLabel": "Select Collaboration Institutions", + "notificationModalSelectCollaborationText": "If you leave this open, our AI will attempt to match you across all researchers!", + "notificationModalFindCollaborationHeader": "Find Collaboration Institutions", + "notificationModalFindCollaborationLabel": "Select Institutions", + "notificationModalFindCollaborationText": "Selected 1 institution", + "notificationModalCloseButton": "Close", + + "noNotificationText": "No notifications set up yetCreate Notification" +} diff --git a/testData/fundFit.json b/testData/fundFit.json new file mode 100644 index 0000000..e299659 --- /dev/null +++ b/testData/fundFit.json @@ -0,0 +1,17 @@ +{ + "textDecorationColorCss": "text-decoration-color", + "textColorCss": "color", + "backgroundColorCss": "background-color", + "hrefCss": "href", + "fundFitTitle": "FundFit", + "logoImageSource": "/assets/logo-BTFpVDFz.png", + "blackColorCode": "rgb(0, 0, 0)", + "secondBlackColorCode": "rgb(2, 8, 23)", + "grayColorCode": "rgb(100, 116, 139)", + "redColorCode": "rgb(217, 38, 38)", + "red500ColorCode": "rgb(239, 68, 68)", + "orangeColorCode": "rgb(255, 109, 0)", + "secondOrangeColor": "rgb(255, 255, 255)", + "blueColorCode": "rgb(37, 99, 235)", + "submitButtonText": "Submit" +} diff --git a/testData/globalData.json b/testData/globalData.json deleted file mode 100644 index 7a0441f..0000000 --- a/testData/globalData.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "landingPageUrl": "https://playwright.dev/", - "landingPageTitle":"Fast and reliable end-to-end testing for modern web apps | Playwright" -} \ No newline at end of file diff --git a/testData/homePageData.json b/testData/homePageData.json new file mode 100644 index 0000000..1bc0824 --- /dev/null +++ b/testData/homePageData.json @@ -0,0 +1,21 @@ +{ + "SuccessfulLoginToastText": "Login SuccessfulYou have been logged in successfully.", + "textDecorationColorCss": "text-decoration-color", + "fundFitTitle": "FundFit", + "logoImageSource": "/assets/logo-BTFpVDFz.png", + "blackColorCode": "rgb(0, 0, 0)", + "loginButtonText": "Login", + "emailInputBoxPlaceholderText": "Enter your email", + "homePageHeroText": "Better funding + teaming with AI insights", + "homePageHeroText2": "Learn why institutions across the country are adopting Streamlyne FundFit to locate funding opportunities and build best-fit research teams faster than ever", + "registerButtonText": "Register", + "homePageViewPricingButtonText": "View pricing", + "homePageStartTrialButtonText": "Start free trial", + "homePageFooterText": "© 2025 Streamlyne. All Rights Reserved. Version: latest", + "userTierHeroText": "Simple, transparent pricing", + "userTierSubHeroText": "Choose the plan that works best for you or your organization", + "researcherSectionHeaderText": "I'm a Researcher...", + "administratorSectionHeaderText": "I'm an Administrator...", + "loginButtonHrefValue": "/login", + "registerButtonHrefValue": "/register" +} diff --git a/testData/lambda.json b/testData/lambda.json deleted file mode 100644 index 5997dbc..0000000 --- a/testData/lambda.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "lambdaTestUrl": "https://ecommerce-playground.lambdatest.io/", - "loginPageUrl": "https://ecommerce-playground.lambdatest.io/index.php?route=account/login", - "accountPageUrl": "https://ecommerce-playground.lambdatest.io/index.php?route=account/account", - "pageTitle": "Your Store", - "expectedTexts": [ - " Login", - " Register", - " Forgotten Password", - " My Account", - " Address Book", - " Wish List", - " Order History", - " Downloads", - " Recurring payments", - " Reward Points", - " Returns", - " Transactions", - " Newsletter" - ], - "accountMenuTexts": [ - " Dashboard", - " My order", - " Return", - " Tracking", - " My voucher", - " Logout" - ] -} diff --git a/testData/loginPageData.json b/testData/loginPageData.json new file mode 100644 index 0000000..f75a653 --- /dev/null +++ b/testData/loginPageData.json @@ -0,0 +1,16 @@ +{ + "SuccessfulLoginToastText": "Login SuccessfulYou have been logged in successfully.", + "loginButtonText": "Login", + "invalidEmailOrPasswordErrorMessage": "ErrorLogin failed. Please try again.", + "loginPageEmailInputBoxHeader": "Email Address", + "loginPagePasswordInputBoxHeader": "Password", + "loginPageEmailPlaceholderText": "Enter your email", + "loginPagePasswordPlaceholderText": "Enter your password", + "loginPageEmailErrorMessage": "Invalid email address", + "loginPagePasswordErrorMessage": "Password is required", + "forgetPasswordButtonText": "Forgot password?", + "forgetPasswordHrefValue": "/forgot-password", + "forgetPasswordHeaderText": "To reset your password, please enter your email address.", + "submitButtonText": "Submit", + "forgetPasswordSuccessfulRequestText": "Forgot PasswordWe have sent you an email to set your password." +} diff --git a/testData/profilePageData.json b/testData/profilePageData.json new file mode 100644 index 0000000..6d9f09f --- /dev/null +++ b/testData/profilePageData.json @@ -0,0 +1,171 @@ +{ + "profileTitle": "Your Public Profile", + "sidebarGeneralInfoButtonText": "General Information", + "sidebarBackgroundButtonText": "Background", + "sidebarWorksButtonText": "Works", + "sidebarProjectsButtonText": "Projects", + "sidebarAttachmentsButtonText": "Attachments", + + "sidebarButtonTexts": [ + "General Information", + "Background", + "Works", + "Projects", + "Attachments" + ], + "sidebarORCIDButtonText": "Disconnect from ORCID", + + "topBarButtonTexts": [ + "General", + "Background", + "Works", + "Projects", + "Attachments" + ], + + "profileHeaderText": "General Information", + "EditProfileButtonText": "Edit Profile", + "passwordSettingsButtonText": "Password Settings", + "emailSettingsButtonText": "Email Settings", + + "firstNameHeaderText": "First Name", + "lastNameHeaderText": "Last Name", + "emailHeaderText": "Email", + "institutionHeaderText": "Institution", + "phoneNumberHeaderText": "Phone Number", + "biographyHeaderText": "Biography", + + "editProfileHeaderText": "Update Profile", + "editProfileFirstNameHeaderText": "First Name*", + "editProfileFirstNameErrorText": "First name is required", + "editProfileFirstNameErrorText2": "First Name should only contain letters, spaces, hyphens, and apostrophes.", + "editProfileLastNameHeaderText": "Last Name*", + "editProfileLastNameErrorText": "Last name is required", + "editProfileLastNameErrorText2": "Last Name should only contain letters, spaces, hyphens, and apostrophes.", + "editProfileFirstNameLastNameErrorText": "Max length is 50 characters", + "editProfilePhoneNumberHeaderText": "Phone Number", + "editProfilePhoneNumberPlaceholderText": "Please enter your phone number", + "editProfileNoValueProviderText": "Not Provided", + "editProfilePhoneNumberErrorText": "Phone number must contain only numbers", + "editProfilePhoneNumberErrorText2": "Ensure this field has no more than 15 characters.", + "editProfileBiographyPlaceholderText": "Biography", + + "passwordSettingsHeaderText": "Change Password", + "passwordSettingsNewPasswordHeaderText": "New Password*", + "passwordSettingsNewPasswordPlaceholderText": "Enter your new password", + "passwordSettingsConfirmNewPasswordHeaderText": "Confirm New Password*", + "passwordSettingsConfirmNewPasswordPlaceholderText": "Confirm your new password", + "passwordSettingsConfirmNewPasswordErrorText": "The two password fields didn’t match.", + "passwordSettingsConfirmNewPasswordErrorText2": "You have used this password before. Please choose a new one.", + + "emailSettingsHeaderText": "Email Settings", + "emailSettingsCurrentEmailHeaderText": "Current Email", + "emailSettingsEditEmailButtonText": "Edit Email", + "emailSettingsEditEmailHeaderText": "Edit Email", + "emailSettingsNewEmailHeaderText": "New Email*", + "emailSettingsAlreadyVerifiedErrorText": "Email is already verified.", + "emailSettingsInvalidErrorText": "Invalid email address", + "emailSettingsRequiredErrorText": "Email is required", + + "successfullyProfileUpdateToast": "Profile UpdatedYour profile has been updated successfully.", + + "backgroundHeaderText": "Background", + "noBackgroundHeaderText": "No background information", + "noBackgroundText": "Add your education and work experience to improve your matching results.", + "noBackgroundAddButtonText": "Add Background", + + "createBackgroundHeaderText": "Create Background", + "createBackgroundTypeHeaderText": "Background Type*", + "createBackgroundTypeButtonText": "Select background type...", + "createBackgroundTypeErrorText": "Background Type is required", + "successfullyBackgroundCreateToast": "Background CreatedThe background has been successfully created.", + "successfullyBackgroundUpdateToast": "Background UpdatedThe background has been successfully updated.", + "successfullyBackgroundDeleteToast": "Background DeletedThe background has been successfully deleted.", + + "worksHeaderText": "Research Works", + "noWorksText": "No research worksAdd your research works to improve your matching results.Add Work", + + "createWorkHeaderText": "Create Research Work", + "createWorkTypeHeaderText": "Work Type*", + "createWorkTypeButtonText": "Select work type...", + "createWorkTypeSearchPlaceholderText": "select work type...", + "createWorkTypeErrorText": "Work Type is required", + "createWorkTypeDropdownTexts": [ + "Annotation", + "Artistic Performance", + "Conference Abstract", + "Conference Paper", + "Conference Poster", + "Data Management Plan", + "Data Set", + "Dictionary Entry", + "Disclosure", + "Dissertation/Thesis", + "Edited Book", + "Encyclopedia Entry", + "Invention", + "Book", + "Book Chapter", + "Book Review", + "Edited Book", + "Encyclopedia Entry", + "Invention", + "Journal Article", + "Journal Issue", + "Lecture/Speech", + "License", + "Magazine Article", + "Manual", + "Newsletter Article", + "Newspaper Article", + "Online Resource", + "Other", + "Patent", + "Physical Object", + "Preprint", + "Registered Copyright", + "Report", + "Research Technique", + "Research Tool", + "Review", + "Software", + "Spin-off Company", + "Standards and Policy", + "Supervised Student Publication", + "Technical Standard", + "Test", + "Trademark", + "Translation", + "Website", + "Working Paper" + ], + "createWorkJournalTitleHeaderText": "Journal Title*", + "createWorkJournalTitleErrorText": "Journal Title is required", + "createWorkTitleHeaderText": "Title*", + "createWorkTitleErrorText": "Title is required", + "createWorkPublicationYearHeaderText": "Publication Year", + "createWorkPublicationMonthHeaderText": "Publication Month", + "createWorkPublicationDayHeaderText": "Publication Day", + "successfullyWorkCreateToast": "Research Work CreatedThe research work has been successfully created.", + "successfullyWorkUpdateToast": "Research Work UpdatedThe research work has been successfully updated.", + "successfullyWorkDeleteToast": "Research Work DeletedThe research work has been successfully deleted.", + + "projectHeaderText": "Research Projects", + "noProjectsHeaderText": "No research projects", + "noProjectsText": "No research projectsAdd your research projects and funding information to improve your matching results.Add Project", + "noProjectsAddButtonText": "Add Project", + + "noResultsFoundMessage": "No results found.", + "deleteModalHeaderText": "Are you sure you want to delete?", + "deleteModalDeleteButtonText": "Delete", + + "cancelButtonText": "Cancel", + "submitButtonText": "Submit", + "userFirstName": "Md Mahbubur", + "userLastName": "Rahman", + + "createProjectFormHeaderText": "Create Project", + "backButtonText": "Back", + + "comboBoxPlaceholderText": ["Contract", "Salary Award", "Grant", "Award"] +} diff --git a/testData/registerPageData.json b/testData/registerPageData.json new file mode 100644 index 0000000..2e4e3c3 --- /dev/null +++ b/testData/registerPageData.json @@ -0,0 +1,30 @@ +{ + "registerPageHeroText": "To register, please fill in the form below.", + "registerPageHeroText2": "No FundFit account? Request a Demo", + "registerPageFirstNamePlaceholderText": "Please enter your first name", + "registerPageLastNamePlaceholderText": "Please enter your last name", + "registerPageEmailPlaceholderText": "Enter your email", + "registerPagePhonePlaceholderText": "Please enter your phone number", + "registerPagePasswordPlaceholderText": "Enter your password", + "registerPageConfirmPasswordPlaceholderText": "Confirm your password", + "registerPagePrivacyPolicyText": "By registering, I agree to the Terms of Service and Privacy Policy", + "firstNameHeaderText": "First Name", + "lastNameHeaderText": "Last Name", + "emailHeaderText": "Email", + "phoneHeaderText": "Phone", + "passwordHeaderText": "Password", + "confirmPasswordHeaderText": "Confirm Password", + "registerButtonText": "Register", + "registerPageFirstNameErrorText": "First Name is required", + "registerPageLastNameErrorText": "Last Name is required", + "registerPageEmailErrorText": "Email is required", + "registerPagePhoneErrorText": "Phone number is required", + "registerPagePasswordErrorText": "Password is required", + "registerPageConfirmPasswordErrorText": "Confirm password is required", + "registerPagePasswordMismatchErrorText": "Passwords do not match", + "registerPageInvalidEmailErrorText": "Invalid email address", + "registerPageInvalidPhoneErrorText": "Please enter a valid phone number", + "registerPageInvalidPasswordErrorText": "Password must be at least 8 characters", + "registerPageInvalidPhoneNumberErrorText": "Phone number must contain only numbers", + "registerPageRegistrationSuccessMessage": "Registration SuccessfulPlease check your email for verification." +} diff --git a/testData/text.json b/testData/text.json new file mode 100644 index 0000000..18c390e --- /dev/null +++ b/testData/text.json @@ -0,0 +1,20 @@ +{ + "expectedTexts": [ + "Full access to all features", + "Unlimited match notifications", + "Unlimited opportunities", + "Match against 250,000+ Researchers", + "Priority support", + "Mobile app access", + "Setup Mobile Notifications", + "Full access to all features", + "Unlimited match notifications", + "Unlimited opportunities", + "Match against 250,000+ Researchers", + "Priority support", + "Mobile app access", + "Setup MS Teams/Slack integration", + "Assemble teams of Researchers", + "SAML2 Single Sign-On Integration" + ] +} diff --git a/tests/HomePageTest.spec.ts b/tests/HomePageTest.spec.ts new file mode 100644 index 0000000..9ba37c6 --- /dev/null +++ b/tests/HomePageTest.spec.ts @@ -0,0 +1,379 @@ +import { test } from "../utilities/fixtures"; +import { ExpectedTextProvider } from "../utilities/valueProvider"; +import { ENV } from "../utilities/env"; +import { FakerDataProvider } from "../utilities/fakerDataProvider"; +import fundFitData from "../testData/fundFit.json"; +import registerPageData from "../testData/registerPageData.json"; +import homePageData from "../testData/homePageData.json"; +import loginPageData from "../testData/loginPageData.json"; + +class HomePageTest extends ExpectedTextProvider { + private fakerData: FakerDataProvider; + constructor() { + super(); + this.fakerData = new FakerDataProvider(); + } + + runTests() { + const { firstName, lastName, email, phoneNumber, password } = + this.fakerData; + + test.describe("Validating User Landing Page Scenarios", () => { + test.beforeEach(async ({ runner, userLandingPage }) => { + await runner.navigateTo(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyPageTitle(fundFitData.fundFitTitle); + // await runner.waitForMilliseconds(10000) + // await runner.validateAttribute( + // userLandingPage.headerImage, + // "src", + // fundFitData.logoImageSource, + // ); + }); + + test("Validating Successful Navigation To Login Page", async ({ + runner, + userLandingPage, + userLoginPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.loginButton); + await runner.verifyElementToHaveCSSProperty( + userLandingPage.loginButton, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + await runner.verifyElementText( + userLandingPage.loginButton, + loginPageData.loginButtonText, + ); + await runner.validateTextAndClickOnElement( + userLandingPage.loginButton, + homePageData.loginButtonText, + ); + await runner.waitUntilElementIsInvisible( + userLandingPage.startTrialButton, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_LOGIN_PAGE_URL); + await runner.verifyElementIsVisible(userLoginPage.email); + + await runner.verifyElementPlaceholder( + userLoginPage.email, + homePageData.emailInputBoxPlaceholderText, + ); + await runner.clickOnElement(userLandingPage.headerImage); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyElementText( + userLandingPage.homePageHeroText, + homePageData.homePageHeroText, + ); + }); + + test("Validating Successful Navigation To Register Page", async ({ + runner, + userLandingPage, + userRegisterPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.registerButton); + await runner.validateTextAndClickOnElement( + userLandingPage.registerButton, + homePageData.registerButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_REGISTER_PAGE_URL); + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + await runner.verifyElementIsVisible(userRegisterPage.heroText); + + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.firstName); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.lastName); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyInputValue(userRegisterPage.lastName, lastName); + + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.email); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, phoneNumber); + await runner.verifyInputValue( + userRegisterPage.phoneNumber, + phoneNumber, + ); + + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText(userRegisterPage.submitButton, "Submit"); + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + + await runner.clickOnElement(userLandingPage.headerImage); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyElementText( + userLandingPage.homePageHeroText, + homePageData.homePageHeroText, + ); + }); + + test("Validating The Set Free Trial Button & Navigation", async ({ + runner, + userLandingPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.startTrialButton); + await runner.validateTextAndClickOnElement( + userLandingPage.startTrialButton, + homePageData.homePageStartTrialButtonText, + ); + await runner.waitUntilElementIsInvisible( + userLandingPage.startTrialButton, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_REGISTER_PAGE_URL); + await runner.verifyElementIsVisible(userLandingPage.headerImage); + await runner.clickOnElement(userLandingPage.headerImage); + await runner.verifyElementIsVisible(userLandingPage.homePageHeroText); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + }); + + test('Validating The "View Pricing Button & Navigation"', async ({ + runner, + userLandingPage, + }) => { + await runner.clickOnElement(userLandingPage.viewPricingButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyElementIsScrolledIntoView( + userLandingPage.pricingSectionHeroText, + ); + await runner.verifyElementIsVisible( + userLandingPage.pricingSectionHeroText, + ); + }); + + test("Validating Pricing Button and Scrolling", async ({ + runner, + userLandingPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.viewPricingButton); + await runner.validateTextAndClickOnElement( + userLandingPage.viewPricingButton, + homePageData.homePageViewPricingButtonText, + ); + await runner.verifyElementIsVisible( + userLandingPage.researcherPriceText, + ); + await runner.verifyElementText( + userLandingPage.pricingSectionHeroText, + "Simple, transparent pricing", + ); + + await runner.verifyElementIsVisible( + userLandingPage.researcherPriceText, + ); + await runner.verifyElementIsVisible( + userLandingPage.researcherPriceText, + ); + }); + + // Flaky Test + test("Validating Footer Block of The Landing Page", async ({ + runner, + userLandingPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.headerImage); + await runner.scrollToElement(userLandingPage.footerBlock); + await runner.verifyElementIsVisible(userLandingPage.footerBlock); + await runner.verifyElementIsScrolledIntoView( + userLandingPage.footerBlock, + ); + await runner.verifyElementTextContains( + userLandingPage.footerBlock, + homePageData.homePageFooterText, + ); + }); + + test("Validating Platform User Tier Details", async ({ + runner, + userLandingPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.homePageHeroText); + await runner.verifyElementText( + userLandingPage.homePageHeroText, + homePageData.homePageHeroText, + ); + + await runner.verifyElementIsVisible(userLandingPage.homePageHeroText); + await runner.verifyElementText( + userLandingPage.homePageHeroText, + homePageData.homePageHeroText, + ); + await runner.verifyElementText( + userLandingPage.homePageSubHeroText, + homePageData.homePageHeroText2, + ); + + await runner.verifyElementIsVisible(userLandingPage.startTrialButton); + await runner.verifyElementText( + userLandingPage.startTrialButton, + homePageData.homePageStartTrialButtonText, + ); + + await runner.verifyElementIsVisible(userLandingPage.viewPricingButton); + await runner.verifyElementText( + userLandingPage.viewPricingButton, + homePageData.homePageViewPricingButtonText, + ); + + await runner.scrollToElement(userLandingPage.pricingSectionHeroText); + await runner.verifyElementIsVisible( + userLandingPage.pricingSectionHeroText, + ); + await runner.verifyElementText( + userLandingPage.pricingSectionHeroText, + homePageData.userTierHeroText, + ); + + await runner.verifyElementIsVisible( + userLandingPage.pricingSectionSubHeroText, + ); + + await runner.verifyElementText( + userLandingPage.pricingSectionSubHeroText, + homePageData.userTierSubHeroText, + ); + }); + + test("Validating the pricing section", async ({ + runner, + userLandingPage, + userRegisterPage, + }) => { + await runner.verifyElementIsVisible(userLandingPage.homePageHeroText); + await runner.scrollToElement(userLandingPage.pricingSectionSubHeroText); + await runner.verifyElementIsVisible( + userLandingPage.researcherBlockHeader, + ); + await runner.verifyElementText( + userLandingPage.researcherBlockHeader, + homePageData.researcherSectionHeaderText, + ); + + await runner.verifyElementIsVisible( + userLandingPage.administratorBlockHeader, + ); + await runner.verifyElementText( + userLandingPage.administratorBlockHeader, + homePageData.administratorSectionHeaderText, + ); + + await runner.verifyLinksTexts( + userLandingPage.pricingSectionLinkTexts, + this.expectedTexts, + ); + + await runner.verifyElementIsVisible(userLandingPage.getStartedButton); + await runner.validateTextAndClickOnElement( + userLandingPage.getStartedButton, + "Get Started", + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_REGISTER_PAGE_URL); + await runner.verifyElementIsVisible(userRegisterPage.heroText); + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + + await runner.verifyElementIsVisible(userLandingPage.headerImage); + await runner.clickOnElement(userLandingPage.headerImage); + + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + + await runner.verifyElementIsVisible(userLandingPage.contactSalesButton); + await runner.validateAttribute( + userLandingPage.contactSalesButton, + "href", + ENV.FUND_FIT_LANDING_PAGE_URL, + ); + await runner.verifyElementToHaveCSSProperty( + userLandingPage.contactSalesButton, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.scrollToElement(userLandingPage.footerBlock); + await runner.verifyElementIsVisible(userLandingPage.footerBlock); + await runner.verifyElementIsScrolledIntoView( + userLandingPage.footerBlock, + ); + + await runner.verifyElementToHaveCSSProperty( + userLandingPage.footerBlock, + "width", + "auto", + ); + await runner.verifyElementToHaveCSSProperty( + userLandingPage.footerBlock, + "height", + "auto", + ); + + await runner.verifyElementToHaveCSSProperty( + userLandingPage.footerBlock, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + }); + }); // Describe block + } // End of runTests method +} // End of HomePageTest class +const testSuite = new HomePageTest(); +testSuite.runTests(); diff --git a/tests/UserDashboardPageTest.spec.ts b/tests/UserDashboardPageTest.spec.ts new file mode 100644 index 0000000..c62b8c8 --- /dev/null +++ b/tests/UserDashboardPageTest.spec.ts @@ -0,0 +1,537 @@ +import { test } from "../utilities/fixtures"; +import { ExpectedTextProvider } from "../utilities/valueProvider"; +import { ENV } from "../utilities/env"; +import fundFitData from "../testData/fundFit.json"; +import dashboardPageData from "../testData/dashboardPageData.json"; +import loginPageData from "../testData/loginPageData.json"; + +class UserDashboardPageTest extends ExpectedTextProvider { + constructor() { + super(); + } + + runTests() { + test.describe("Validating Dashboard Page Test", () => { + test.beforeEach( + async ({ + runner, + userLandingPage, + userLoginPage, + userDashboardPage, + }) => { + await runner.navigateTo(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyPageTitle(fundFitData.fundFitTitle); + // await runner.waitForMilliseconds(10000); + // await runner.validateAttribute( + // userLandingPage.headerImage, + // "src", + // fundFitData.logoImageSource, + // ); + + await runner.verifyElementIsVisible(userLandingPage.loginButton); + await runner.validateTextAndClickOnElement( + userLandingPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.enterText(userLoginPage.email, ENV.FUND_FIT_USER_EMAIL); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText( + userLoginPage.password, + ENV.FUND_FIT_USER_PASSWORD, + ); + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.SuccessfulLoginToastText, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.SuccessfulLoginToastText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_DASHBOARD_PAGE_URL); + await runner.verifyElementIsVisible( + userDashboardPage.dashboardButton, + ); + await runner.verifyElementText( + userDashboardPage.dashboardButton, + dashboardPageData.dashboardNavButtonText, + ); + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardButton, + fundFitData.textDecorationColorCss, + fundFitData.orangeColorCode, + ); + }, + ); + + test("Validating Dashboard Navigation Bar", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible(userDashboardPage.dashboardButton); + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardButton, + fundFitData.textDecorationColorCss, + fundFitData.orangeColorCode, + ); + await runner.validateAttribute( + userDashboardPage.dashboardButton, + "href", + dashboardPageData.dashboardHrefValue, + ); + await runner.validateAttribute( + userDashboardPage.dashboardNavbarStartMatchingButton, + "href", + dashboardPageData.dashboardStartMatchingHrefValue, + ); + await runner.validateAttribute( + userDashboardPage.dashboardNavbarMatchesButton, + "href", + dashboardPageData.dashboardMatchesHrefValue, + ); + await runner.validateAttribute( + userDashboardPage.dashboardNavbarNotificationsButton, + "href", + dashboardPageData.dashboardNotificationsHrefValue, + ); + + await runner.verifyLinksTexts( + userDashboardPage.dashboardNavbarText, + dashboardPageData.navbarTexts, + ); + + await runner.verifyElementText( + userDashboardPage.dashboardNavbarAccountButton, + dashboardPageData.dashboardNavAccountText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNavbarAccountButton, + ); + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardNavbarAccountButton, + fundFitData.textDecorationColorCss, + fundFitData.secondBlackColorCode, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarAccountButton, + dashboardPageData.dashboardNavAccountText, + ); + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNavbarAccountProfileText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardNavbarAccountProfileText, + dashboardPageData.dashboardNavAccountProfileText, + ); + await runner.validateAttribute( + userDashboardPage.dashboardNavbarAccountProfileText, + "href", + dashboardPageData.dashboardAccountProfileHrefValue, + ); + }); + + test("Validating Page Navigation From Dashboard", async ({ + runner, + userDashboardPage, + userStartMatchingPage, + userMatchesPage, + userNotificationPage, + }) => { + await runner.verifyElementIsVisible(userDashboardPage.dashboardButton); + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardButton, + fundFitData.textDecorationColorCss, + fundFitData.orangeColorCode, + ); + + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardNavbarMatchesButton, + fundFitData.textDecorationColorCss, + fundFitData.blackColorCode, + ); + + // Navigate Start Matching + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarStartMatchingButton, + dashboardPageData.dashboardNavStartMatchingText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_START_MATCHING_PAGE_URL); + await runner.verifyElementIsVisible( + userStartMatchingPage.startMatchingHeroText, + ); + + // Navigation Bar css validation problem + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardNavbarStartMatchingButton, + fundFitData.textColorCss, + "rgb(116, 123, 255)", + ); + + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardButton, + fundFitData.textDecorationColorCss, + fundFitData.blackColorCode, + ); + + // Navigate Matches + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarMatchesButton, + dashboardPageData.dashboardNavMatchesText, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_MATCHES_PAGE_URL); + await runner.verifyElementIsVisible(userMatchesPage.matchesHeroText); + + // Navigate Notifications + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarNotificationsButton, + dashboardPageData.dashboardNavNotificationsText, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_NOTIFICATIONS_PAGE_URL); + await runner.verifyElementIsVisible( + userNotificationPage.notificationHeroText, + ); + + // Account Menu Options + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarAccountButton, + dashboardPageData.dashboardNavAccountText, + ); + + await runner.verifyLinksTexts( + userDashboardPage.dashboardNavbarAccountTexts, + dashboardPageData.dashboardNavAccountTexts, + ); + + await runner.verifyElementText( + userDashboardPage.dashboardNavbarAccountSubmitFeedbackText, + "Submit Feedback", + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNavbarAccountLogoutText, + ); + await runner.clickOnElement( + userDashboardPage.dashboardNavbarAccountLogoutText, + ); + }); + + test("Validating Dashboard All Card Headers", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllOpportunitiesCard, + ); + await runner.verifyLinksTexts( + userDashboardPage.dashboardAllCardHeaderText, + dashboardPageData.dashboardAllCardHeaderTexts, + ); + }); + + test("Validating Dashboard All Opportunities Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllOpportunitiesCardText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardAllOpportunitiesCardText, + dashboardPageData.dashboardAllOpportunitiesCardText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllOpportunitiesIcon, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllOpportunitiesValue, + ); + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardAllOpportunitiesValue, + ); + }); + + test("Validating Dashboard Researchers Opportunities Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNewResearchersOpportunitiesCardText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardNewResearchersOpportunitiesCardText, + dashboardPageData.dashboardNewResearchersOpportunitiesCardText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardExpireResearchersOpportunitiesCardText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardExpireResearchersOpportunitiesCardText, + dashboardPageData.dashboardExpireResearchersOpportunitiesCardText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardResearchersOpportunitiesIcon, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNewResearchersOpportunitiesValue, + ); + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardNewResearchersOpportunitiesValue, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardExpireResearchersOpportunitiesValue, + ); + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardExpireResearchersOpportunitiesValue, + ); + }); + + test("Validating Dashboard Founding Available Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingAvailableCardText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardFoundingAvailableCardText, + dashboardPageData.dashboardFoundingAvailableCardText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingAvailableIcon, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingAvailablePotentialValue, + ); + + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardFoundingAvailablePotentialValue, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingAvailableTotalValue, + ); + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardFoundingAvailableTotalValue, + ); + }); + + test("Validating Dashboard All Researchers Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllResearchersCardText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardAllResearchersCardText, + dashboardPageData.dashboardAllResearchersCardText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllResearchersIcon, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardAllResearchersValue, + ); + await runner.validateNumericValueFromElement( + userDashboardPage.dashboardAllResearchersValue, + ); + }); + + test("Validating Dashboard Founding Scenario Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingScenarioButton, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardFoundingScenarioButton, + dashboardPageData.dashboardFoundingScenarioButton, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_MATCHES_PAGE_URL); + await runner.navigateBack(); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingScenarioIcon, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingScenarioText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardFoundingScenarioText, + dashboardPageData.dashboardFoundingScenarioText, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardFoundingScenarioRechart, + ); + }); + + test("Validating No Notification in Recent Notification Section", async ({ + runner, + userDashboardPage, + }) => { + // View All Notifications Button + await runner.verifyElementIsVisible( + userDashboardPage.dashboardRecentMatchNotificationsButton, + ); + await runner.verifyElementIsVisible( + userDashboardPage.dashboardRecentMatchNotificationsButton, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardRecentMatchNotificationsButton, + dashboardPageData.dashboardRecentMatchNotificationsButton, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_NOTIFICATIONS_PAGE_URL); + await runner.navigateBack(); + + // Validate No Notification Or Have Notifications + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + await runner.validateNoDataFound( + userDashboardPage.dashboardNoNotificationText, + userDashboardPage.dashboardNotificationLists, + dashboardPageData.noNotificationText, + ); + }); + + // No notification here, so some issue here + test("Validating Dashboard Recent Match Notifications Card", async ({ + runner, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible( + userDashboardPage.dashboardRecentMatchNotificationsButton, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardRecentMatchNotificationsButton, + dashboardPageData.dashboardRecentMatchNotificationsButton, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_NOTIFICATIONS_PAGE_URL); + await runner.navigateBack(); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardRecentMatchNotificationsIcon, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardRecentMatchNotificationsText, + ); + await runner.verifyElementText( + userDashboardPage.dashboardRecentMatchNotificationsText, + dashboardPageData.dashboardRecentMatchNotificationsText, + ); + + // Single Notification Validation + await runner.validateTitleByRegex( + userDashboardPage.dashboardNotificationTitle, + ); + await runner.validateNotificationTexts( + userDashboardPage.dashboardNotificationText, + dashboardPageData.dashboardNotificationMatchTexts, + dashboardPageData.dashboardNotificationTexts, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNotificationViewButton, + ); + await runner.verifyElementToHaveCSSProperty( + userDashboardPage.dashboardNotificationViewButton, + fundFitData.backgroundColorCss, + fundFitData.blueColorCode, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNotificationViewButton, + dashboardPageData.dashboardNotificationViewButton, + ); + + await runner.waitUntilElementIsInvisible( + userDashboardPage.dashboardCardLoader, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.notificationModalHeader, + ); + await runner.verifyElementText( + userDashboardPage.notificationModalHeader, + [ + dashboardPageData.notificationModalHeader, + dashboardPageData.notificationModalHeader2, + ], + ); + + await runner.verifyElementIsVisible( + userDashboardPage.notificationModalNameLabel, + ); + await runner.verifyElementText( + userDashboardPage.notificationModalNameLabel, + dashboardPageData.notificationModalNameLabel, + ); + await runner.verifyElementIsVisible( + userDashboardPage.notificationModalName, + ); + await runner.verifyInputIsDisable( + userDashboardPage.notificationModalName, + ); + + await runner.verifyElementIsVisible( + userDashboardPage.notificationModalNotificationTypeSelectLabel, + ); + await runner.verifyElementText( + userDashboardPage.notificationModalNotificationTypeSelectLabel, + dashboardPageData.notificationModalNotificationTypeSelectLabel, + ); + }); + }); // End of describe block + } // End of runTests method +} // End of HomePageTest class +const testSuite = new UserDashboardPageTest(); +testSuite.runTests(); diff --git a/tests/UserLoginTest.spec.ts b/tests/UserLoginTest.spec.ts new file mode 100644 index 0000000..0687daf --- /dev/null +++ b/tests/UserLoginTest.spec.ts @@ -0,0 +1,578 @@ +import { test } from "../utilities/fixtures"; +import { ExpectedTextProvider } from "../utilities/valueProvider"; +import { ENV } from "../utilities/env"; +import { FakerDataProvider } from "../utilities/fakerDataProvider"; +import fundFitData from "../testData/fundFit.json"; +import registerPageData from "../testData/registerPageData.json"; +import loginPageData from "../testData/loginPageData.json"; +import homePageData from "../testData/homePageData.json"; +import dashboardPageData from "../testData/dashboardPageData.json"; + +class UserLoginTest extends ExpectedTextProvider { + private fakerData: FakerDataProvider; + constructor() { + super(); + this.fakerData = new FakerDataProvider(); + } + + runTests() { + const { email, password } = this.fakerData; + + test.describe("Validating User Login Scenarios", () => { + test.beforeEach(async ({ runner, userLandingPage }) => { + await runner.navigateTo(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyPageTitle(fundFitData.fundFitTitle); + + await runner.verifyElementToHaveCSSProperty( + userLandingPage.loginButton, + fundFitData.textDecorationColorCss, + fundFitData.blackColorCode, + ); + await runner.validateAttribute( + userLandingPage.loginButton, + "href", + homePageData.loginButtonHrefValue, + ); + await runner.validateTextAndClickOnElement( + userLandingPage.loginButton, + homePageData.loginButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_LOGIN_PAGE_URL); + }); + + test("User Inputs Valid User Details To Login", async ({ + runner, + userLoginPage, + userDashboardPage, + }) => { + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.enterText(userLoginPage.email, ENV.FUND_FIT_USER_EMAIL); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText( + userLoginPage.password, + ENV.FUND_FIT_USER_PASSWORD, + ); + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + await runner.validateAttribute(userLoginPage.password, "type", "text"); + await runner.verifyInputValue( + userLoginPage.password, + ENV.FUND_FIT_USER_PASSWORD, + ); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.SuccessfulLoginToastText, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_DASHBOARD_PAGE_URL); + await runner.verifyElementIsVisible(userDashboardPage.dashboardButton); + await runner.verifyElementText( + userDashboardPage.dashboardButton, + dashboardPageData.dashboardNavButtonText, + ); + }); + + test("User tries to login with random email", async ({ + runner, + userLoginPage, + }) => { + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.enterText(userLoginPage.email, email); + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText(userLoginPage.password, password); + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.invalidEmailOrPasswordErrorMessage, + ); + }); + + test("User Tries To Login With Both Invalid Data ", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.verifyElementPlaceholder( + userLoginPage.email, + loginPageData.loginPageEmailPlaceholderText, + ); + await runner.enterText(userLoginPage.email, email); + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.verifyElementPlaceholder( + userLoginPage.password, + loginPageData.loginPagePasswordPlaceholderText, + ); + await runner.enterText(userLoginPage.password, password); + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.invalidEmailOrPasswordErrorMessage, + ); + }); + + test("User Tries Login With Empty Email & Password", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementText( + userLoginPage.emailInputBoxHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxHeader, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxHeader, + loginPageData.loginPagePasswordInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.clickOnElement(userLoginPage.email); + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.clickOnElement(userLoginPage.password); + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementText( + userLoginPage.emailInputBoxErrorMessage, + loginPageData.loginPageEmailErrorMessage, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxErrorMessage, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementText( + userLoginPage.passwordInputBoxErrorMessage, + loginPageData.loginPagePasswordErrorMessage, + ); + }); + + //User tries login with empty email and valid password + test("User Tries To Login With Empty Email & Valid Password", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementText( + userLoginPage.emailInputBoxHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxHeader, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxHeader, + loginPageData.loginPagePasswordInputBoxHeader, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText( + userLoginPage.password, + ENV.FUND_FIT_USER_PASSWORD, + ); + + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.loginButton, + fundFitData.backgroundColorCss, + fundFitData.orangeColorCode, + ); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + //User tries login with empty email and invalid password + test("User Tries To Login With Empty Email & Invalid Password", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementText( + userLoginPage.emailInputBoxHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxHeader, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxHeader, + loginPageData.loginPagePasswordInputBoxHeader, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.email); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText(userLoginPage.password, password); + + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + test("User Tries Login With Valid Email & Empty Password", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementText( + userLoginPage.emailInputBoxHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxHeader, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxHeader, + loginPageData.loginPagePasswordInputBoxHeader, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.verifyElementPlaceholder( + userLoginPage.email, + loginPageData.loginPageEmailPlaceholderText, + ); + await runner.enterText(userLoginPage.email, ENV.FUND_FIT_USER_EMAIL); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.verifyElementPlaceholder( + userLoginPage.password, + loginPageData.loginPagePasswordPlaceholderText, + ); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.clickOnElement(userLoginPage.loginButton); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxErrorMessage, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxErrorMessage, + loginPageData.loginPagePasswordErrorMessage, + ); + }); + + //User tries login with invalid email and empty password + test("User Tries Login With Invalid Email & Empty Password", async ({ + runner, + userLoginPage, + }) => { + // + + await runner.verifyElementIsVisible(userLoginPage.emailInputBoxHeader); + await runner.verifyElementText( + userLoginPage.emailInputBoxHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.emailInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxHeader, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxHeader, + loginPageData.loginPagePasswordInputBoxHeader, + ); + + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.verifyElementPlaceholder( + userLoginPage.email, + loginPageData.loginPageEmailPlaceholderText, + ); + await runner.enterText(userLoginPage.email, email); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.verifyElementPlaceholder( + userLoginPage.password, + loginPageData.loginPagePasswordPlaceholderText, + ); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.clickOnElement(userLoginPage.loginButton); + + await runner.verifyElementIsVisible( + userLoginPage.passwordInputBoxErrorMessage, + ); + await runner.verifyElementToHaveCSSProperty( + userLoginPage.passwordInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementText( + userLoginPage.passwordInputBoxErrorMessage, + loginPageData.loginPagePasswordErrorMessage, + ); + }); + + test("User Successfully Navigate To The Register Page ", async ({ + runner, + userLoginPage, + userRegisterPage, + }) => { + await runner.verifyElementIsVisible( + userLoginPage.loginPageRegisterButton, + ); + + await runner.validateTextAndClickOnElement( + userLoginPage.loginPageRegisterButton, + "Register", + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_REGISTER_PAGE_URL); + await runner.verifyElementIsVisible(userRegisterPage.heroText); + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + }); + + // Flaky Test + test("User Navigates to the Forget Password page", async ({ + runner, + userLoginPage, + }) => { + await runner.verifyElementToHaveCSSProperty( + userLoginPage.loginButton, + fundFitData.textDecorationColorCss, + fundFitData.secondOrangeColor, + ); + + await runner.verifyElementIsVisible(userLoginPage.loginPageHeaderText); + await runner.verifyElementText( + userLoginPage.loginPageHeaderText, + "To access your account, log in below.", + ); + + await runner.verifyElementIsVisible(userLoginPage.forgetPasswordButton); + await runner.validateAttribute( + userLoginPage.forgetPasswordButton, + fundFitData.hrefCss, + loginPageData.forgetPasswordHrefValue, + ); + + await runner.validateTextAndClickOnElement( + userLoginPage.forgetPasswordButton, + loginPageData.forgetPasswordButtonText, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_FORGET_PASSWORD_PAGE_URL); + + await runner.verifyElementIsVisible( + userLoginPage.forgetPasswordFormHeaderText, + ); + + await runner.verifyElementText( + userLoginPage.forgetPasswordFormSubHeaderText, + "No FundFit account? Request a Demo", + ); + + await runner.validateAttribute( + userLoginPage.forgetPasswordFormSubHeaderText, + fundFitData.hrefCss, + ENV.FUND_FIT_LANDING_PAGE_URL, + "a", + ); + + await runner.verifyElementText( + userLoginPage.ForgetPasswordEmailHeader, + loginPageData.loginPageEmailInputBoxHeader, + ); + + await runner.verifyElementPlaceholder( + userLoginPage.ForgetPasswordEmailInputBox, + loginPageData.loginPageEmailPlaceholderText, + ); + + await runner.enterText( + userLoginPage.ForgetPasswordEmailInputBox, + email, + ); + + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.submitButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.forgetPasswordSuccessfulRequestText, + ); + }); + }); //Describe block + } //End of runTests method +} //End of UserLoginTest class +const testSuite = new UserLoginTest(); +testSuite.runTests(); diff --git a/tests/UserProfileTest.spec.ts b/tests/UserProfileTest.spec.ts new file mode 100644 index 0000000..494aeab --- /dev/null +++ b/tests/UserProfileTest.spec.ts @@ -0,0 +1,2612 @@ +import { test } from "../utilities/fixtures"; +import { ExpectedTextProvider } from "../utilities/valueProvider"; +import { ENV } from "../utilities/env"; +import fundFitData from "../testData/fundFit.json"; +import loginPageData from "../testData/loginPageData.json"; +import dashboardPageData from "../testData/dashboardPageData.json"; +import profilePageData from "../testData/profilePageData.json"; +import { FakerDataProvider } from "../utilities/fakerDataProvider"; + +class UserProfileTest extends ExpectedTextProvider { + private fakerData: FakerDataProvider; + constructor() { + super(); + this.fakerData = new FakerDataProvider(); + } + + runTests() { + const { + firstName, + lastName, + phoneNumber, + password, + bio, + randomCharValue30Digits, + randomCharValuesForInputValueValidation, + numericValueOf20Digits, + + backgroundType, + role, + department, + organization, + organizationAddress, + startDate, + endDate, + + workType, + journalTitle, + title, + publicationYear, + publicationMonth, + publicationDay, + + projectTypes, + projectTitle, + organizationType, + amount, + } = this.fakerData; + + test.describe("Validating Profile Page Scenarios", () => { + test.beforeEach( + async ({ + runner, + userLandingPage, + userLoginPage, + userDashboardPage, + userProfilePage, + }) => { + await runner.navigateTo(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyPageTitle(fundFitData.fundFitTitle); + // await runner.waitForMilliseconds(10000); + // await runner.validateAttribute( + // userLandingPage.headerImage, + // "src", + // fundFitData.logoImageSource, + // ); + + await runner.verifyElementIsVisible(userLandingPage.loginButton); + await runner.validateTextAndClickOnElement( + userLandingPage.loginButton, + loginPageData.loginButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.email); + await runner.enterText(userLoginPage.email, ENV.FUND_FIT_USER_EMAIL); + + await runner.verifyElementIsVisible(userLoginPage.password); + await runner.enterText( + userLoginPage.password, + ENV.FUND_FIT_USER_PASSWORD, + ); + await runner.verifyElementIsVisible(userLoginPage.eyeIcon); + await runner.clickOnElement(userLoginPage.eyeIcon); + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.validateTextAndClickOnElement( + userLoginPage.loginButton, + loginPageData.loginButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + loginPageData.SuccessfulLoginToastText, + ); + + await runner.waitForMilliseconds(5); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNavbarAccountButton, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarAccountButton, + "Account", + ); + + await runner.verifyElementIsVisible( + userDashboardPage.dashboardNavbarAccountProfileText, + ); + await runner.validateAttribute( + userDashboardPage.dashboardNavbarAccountProfileText, + "href", + dashboardPageData.dashboardAccountProfileHrefValue, + ); + await runner.validateTextAndClickOnElement( + userDashboardPage.dashboardNavbarAccountProfileText, + dashboardPageData.dashboardNavAccountProfileText, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + await runner.verifyElementTextContains( + userProfilePage.profileHeaderText, + profilePageData.profileHeaderText, + ); + }, + ); + + // 🔹 Profile Page - Test Case Scenarios + test("Validating Profile Page Side Bar Profile Information", async ({ + runner, + userProfilePage, + }) => { + await runner.verifyElementIsVisible(userProfilePage.sidebarPhoto); + + await runner.verifyElementIsVisible( + userProfilePage.sidebarFullNameText, + ); + await runner.validateNameByRegex( + userProfilePage.sidebarFullNameText, + "FULL_NAME", + ); + + await runner.verifyElementIsVisible(userProfilePage.sidebarTitleText); + await runner.verifyElementText( + userProfilePage.sidebarTitleText, + profilePageData.profileTitle, + ); + }); + + test("Validating Profile Page Side Bar Navigation", async ({ + runner, + userProfilePage, + }) => { + // 🔸 Sidebar Navigation Scenarios + // 1. Verify that clicking on each tab (General, Background, Works, Projects, Attachments) switches the active view. + await runner.clickOnElement(userProfilePage.sidebarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Background']", + "Background", + ); + + await runner.clickOnElement(userProfilePage.sidebarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Research Works']", + "Research Works", + ); + + await runner.clickOnElement(userProfilePage.sidebarProjectsButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PROJECT_PAGE_URL); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Research Projects']", + "Research Projects", + ); + + await runner.clickOnElement(userProfilePage.sidebarAttachmentsButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_ATTACHMENT_PAGE_URL, + ); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Attachments']", + "Attachments", + ); + + await runner.clickOnElement(userProfilePage.sidebarGeneralInfoButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + + // 2. Verify that each section shows corresponding data or a "0" count if no entries are present. + + // Side Bar Button Visibility Check + await runner.verifyElementIsVisible([ + userProfilePage.sidebarGeneralInfoText, + userProfilePage.sidebarBackgroundText, + userProfilePage.sidebarWorksText, + userProfilePage.sidebarProjectsText, + userProfilePage.sidebarAttachmentsText, + ]); + await runner.verifyElementIsVisible([ + userProfilePage.sidebarGeneralInfoIcon, + userProfilePage.sidebarBackgroundIcon, + userProfilePage.sidebarWorksIcon, + userProfilePage.sidebarProjectsIcon, + userProfilePage.sidebarAttachmentsIcon, + ]); + await runner.verifyElementIsVisible([ + userProfilePage.sidebarBackgroundValue, + userProfilePage.sidebarWorksValue, + userProfilePage.sidebarProjectsValue, + userProfilePage.sidebarAttachmentsValue, + ]); + + // Side Bar Button Text Validity Check + await runner.verifyElementText( + userProfilePage.sidebarGeneralInfoText, + profilePageData.sidebarGeneralInfoButtonText, + ); + await runner.verifyElementText( + userProfilePage.sidebarBackgroundText, + profilePageData.sidebarBackgroundButtonText, + ); + await runner.verifyElementText( + userProfilePage.sidebarWorksText, + profilePageData.sidebarWorksButtonText, + ); + await runner.verifyElementText( + userProfilePage.sidebarProjectsText, + profilePageData.sidebarProjectsButtonText, + ); + await runner.verifyElementText( + userProfilePage.sidebarAttachmentsText, + profilePageData.sidebarAttachmentsButtonText, + ); + + // 3. Verify that inactive tabs are not highlighted and have no visible content shown by default. + // Side Bar + await runner.verifyElementToHaveCSSProperty( + userProfilePage.sidebarGeneralInfoText, + fundFitData.textColorCss, + fundFitData.orangeColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + [ + userProfilePage.sidebarBackgroundText, + userProfilePage.sidebarWorksText, + userProfilePage.sidebarProjectsText, + userProfilePage.sidebarAttachmentsText, + ], + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + // Top Bar + await runner.validateAttribute( + userProfilePage.topBarGeneralButton, + "data-state", + "active", + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.topBarGeneralButton, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.validateAttribute( + [ + userProfilePage.topBarBackgroundButton, + userProfilePage.topBarWorksButton, + userProfilePage.topBarProjectsButton, + userProfilePage.topBarAttachmentsButton, + ], + "data-state", + "inactive", + ); + await runner.verifyElementToHaveCSSProperty( + [ + userProfilePage.topBarBackgroundButton, + userProfilePage.topBarWorksButton, + userProfilePage.topBarProjectsButton, + userProfilePage.topBarAttachmentsButton, + ], + fundFitData.textColorCss, + fundFitData.grayColorCode, + ); + }); + + // ORCID Email & Password Not Found + test.skip("Validating Profile Page Side Bar ORCID Connection", async ({ + runner, + userProfilePage, + }) => { + // 🔸 ORCID Integration Scenario + // 1. Verify that clicking the “Disconnect from ORCID” button prompts the user for confirmation. + // 2. Verify that upon confirming disconnection, the ORCID link is removed and a success message is displayed. + + await runner.verifyElementIsVisible(userProfilePage.sidebarORCIDButton); + }); + + // ORCID Email & Password Not Found + test.skip("Validating Profile Page Side Bar ORCID Disconnection", async ({ + runner, + userProfilePage, + }) => { + await runner.verifyElementIsVisible(userProfilePage.sidebarORCIDButton); + }); + + test("Validating Profile Page Generale Information Section", async ({ + runner, + userProfilePage, + }) => { + // 🔸 General Tab Scenarios + // 1. Verify that all user profile fields (First Name, Last Name, Email, Institution) are displayed correctly. + await runner.verifyElementIsVisible([ + userProfilePage.firstNameHeader, + userProfilePage.lastNameHeader, + userProfilePage.emailHeader, + userProfilePage.institutionHeader, + userProfilePage.phoneNumberHeader, + userProfilePage.biographyHeader, + ]); + + await runner.verifyElementTextContains( + [ + userProfilePage.firstNameHeader, + userProfilePage.lastNameHeader, + userProfilePage.emailHeader, + userProfilePage.institutionHeader, + userProfilePage.phoneNumberHeader, + userProfilePage.biographyHeader, + ], + [ + profilePageData.firstNameHeaderText, + profilePageData.lastNameHeaderText, + profilePageData.emailHeaderText, + profilePageData.institutionHeaderText, + profilePageData.phoneNumberHeaderText, + profilePageData.biographyHeaderText, + ], + ); + + await runner.validateNameByRegex( + userProfilePage.firstName, + "FIRST_NAME", + ); + await runner.validateNameByRegex(userProfilePage.lastName, "LAST_NAME"); + await runner.verifyElementText( + userProfilePage.email, + ENV.FUND_FIT_USER_EMAIL, + ); + + // 2. Verify that optional fields (Phone Number, Biography) show a placeholder or “Not provided” if left empty. + await runner.validatePhoneNumberByRegex(userProfilePage.phoneNumber); + await runner.validateUserBiographyByRegex(userProfilePage.biography); + + // 3. Verify that clicking the "Edit Profile" button enables profile field editing or opens an editable form/modal. + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementTextContains( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.backAndCancelButton, + profilePageData.cancelButtonText, + ); + + // 4. Verify that the "Password Settings" button navigates to or opens the password update section. + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementTextContains( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.backAndCancelButton, + profilePageData.cancelButtonText, + ); + + // Website er profile "Email Settings" Button ta akhon hide kore dise. + // 5 Verify that the "Email Settings" button navigates to or opens the email update section. + // await runner.validateTextAndClickOnElement( + // userProfilePage.emailSettings, + // profilePageData.emailSettingsButtonText, + // ); + // await runner.verifyContainsUrl( + // ENV.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + // ); + // await runner.verifyElementTextContains( + // userProfilePage.emailSettingsHeaderText, + // profilePageData.emailSettingsHeaderText, + // ); + // await runner.validateTextAndClickOnElement( + // userProfilePage.cancelButton, + // profilePageData.cancelButtonText, + // ); + }); + + // 🔹 Update Profile Page – Test Case Scenarios + test("Validating Update User Profile Information", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + await runner.enterText(userProfilePage.editProfileFirstName, firstName); + await runner.enterText(userProfilePage.editProfileLastName, lastName); + await runner.enterText( + userProfilePage.editProfilePhoneNumber, + phoneNumber, + ); + await runner.enterText(userProfilePage.editProfileBiography, bio); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyProfileUpdateToast, + ); + await runner.waitForMilliseconds(5); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + test("Validating User Update Profile Page Fields Are Pre-Populated With the User's Existing Information", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click On Edit Profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // 1. Verify that the All fields are pre-populated with the user's existing information. + await runner.enterText( + userProfilePage.editProfileFirstName, + profilePageData.userFirstName, + ); + await runner.enterText( + userProfilePage.editProfileLastName, + profilePageData.userLastName, + ); + await runner.enterText( + userProfilePage.editProfilePhoneNumber, + phoneNumber, + ); + await runner.enterText(userProfilePage.editProfileBiography, bio); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyProfileUpdateToast, + ); + await runner.waitForMilliseconds(1000); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.profileHeaderText); + await runner.verifyElementText( + userProfilePage.profileHeaderText, + profilePageData.profileHeaderText, + ); + + // Click On Edit Profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + await runner.waitForMilliseconds(1000); + + await runner.verifyInputValue( + userProfilePage.editProfileFirstName, + profilePageData.userFirstName, + ); + await runner.verifyInputValue( + userProfilePage.editProfileLastName, + profilePageData.userLastName, + ); + await runner.verifyInputValue( + userProfilePage.editProfilePhoneNumber, + phoneNumber, + ); + await runner.verifyInputValue( + userProfilePage.editProfileBiography, + bio, + ); + }); + + test("Verify that the Biography field accepts text input and supports multiline entries", async ({ + runner, + userProfilePage, + }) => { + // Click On Edit Profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // 3. Verify that the "Biography" field accepts text input and supports multiline entries. + await runner.clearInputField(userProfilePage.editProfileBiography); + await runner.enterText(userProfilePage.editProfileBiography, bio); + await runner.clickOnElement(userProfilePage.submitButton); + + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.biography); + await runner.waitForMilliseconds(1000); + await runner.verifyElementText(userProfilePage.biography, bio); + }); + + // Flaky Test + test("User Interaction with First Name", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // 4. Verify that required fields (e.g., First Name, Last Name) show validation messages if left empty. + // Empty Check + await runner.clearInputField(userProfilePage.editProfileFirstName); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.editProfileFirstNameErrorText, + profilePageData.editProfileFirstNameErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + // Alphanumeric Check + await runner.enterText( + userProfilePage.editProfileFirstName, + randomCharValue30Digits, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.editProfileFirstNameErrorText, + profilePageData.editProfileFirstNameErrorText2, // Error Appears First Name + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + // Max Length Check + await runner.enterText( + userProfilePage.editProfileFirstName, + randomCharValuesForInputValueValidation, + ); + + // await runner.validateTextAndClickOnElement( + // userProfilePage.submitButton, + // profilePageData.submitButtonText, + // ); + + await runner.verifyElementIsVisible( + userProfilePage.editProfileFirstNameErrorText, + ); + await runner.verifyElementText( + userProfilePage.editProfileFirstNameErrorText, + profilePageData.editProfileFirstNameLastNameErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileFirstNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + // Valid First Name Check + await runner.enterText(userProfilePage.editProfileFirstName, firstName); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + // await runner.verifyElementText( + // userLoginPage.toastMessage, + // profilePageData., + // ); + await runner.waitForMilliseconds(500); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + // Flaky Test + test("User Interaction With Last Name", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + // Empty Check + await runner.clickOnElement(userProfilePage.editProfileLastName); + await runner.clearInputField(userProfilePage.editProfileLastName); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.editProfileLastNameErrorText, + profilePageData.editProfileLastNameErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + // Alphanumeric Check + await runner.enterText( + userProfilePage.editProfileLastName, + randomCharValue30Digits, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.editProfileLastNameErrorText, + profilePageData.editProfileLastNameErrorText2, // Error Appears Last Name + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + // Max Length Check + await runner.enterText( + userProfilePage.editProfileLastName, + randomCharValuesForInputValueValidation, + ); + // await runner.validateTextAndClickOnElement( + // userProfilePage.submitButton, + // profilePageData.submitButtonText, + // ); + + await runner.verifyElementIsVisible( + userProfilePage.editProfileLastNameErrorText, + ); + await runner.verifyElementText( + userProfilePage.editProfileLastNameErrorText, + profilePageData.editProfileFirstNameLastNameErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfileLastNameHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + // Valid Last Name Check + await runner.enterText(userProfilePage.editProfileLastName, lastName); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + // await runner.verifyElementText( + // userLoginPage.toastMessage, + // profilePageData.successfullyProfileUpdateToast, + // ); + await runner.waitForMilliseconds(1000); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + // Flaky Test + test("User Interaction With Phone Number", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementIsVisible( + userProfilePage.editProfileHeaderText, + ); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // Alphanumeric Check + await runner.enterText( + userProfilePage.editProfilePhoneNumber, + randomCharValuesForInputValueValidation, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.editProfilePhoneNumberErrorText, + profilePageData.editProfilePhoneNumberErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfilePhoneNumberErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfilePhoneNumberHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + // Max Length Check + await runner.enterText( + userProfilePage.editProfilePhoneNumber, + numericValueOf20Digits, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userProfilePage.editProfilePhoneNumberErrorText2, + ); + await runner.verifyElementText( + userProfilePage.editProfilePhoneNumberErrorText2, + profilePageData.editProfilePhoneNumberErrorText2, // Error Appears Phone Number + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfilePhoneNumberErrorText2, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.editProfilePhoneNumberHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + // Valid Phone Number Check + await runner.enterText( + userProfilePage.editProfilePhoneNumber, + phoneNumber, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.waitForMilliseconds(1000); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyProfileUpdateToast, + ); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + test("Validating User Update To Click On Cancel Button & Navigates Back To The Profile", async ({ + runner, + userProfilePage, + }) => { + // Click on user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // 7. Verify that clicking the "Cancel" button discards any changes and navigates back to the profile view page. + await runner.enterText(userProfilePage.editProfileFirstName, firstName); + await runner.validateTextAndClickOnElement( + userProfilePage.backAndCancelButton, + profilePageData.cancelButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + }); + + test("Validating User Update Profile Input Placeholders", async ({ + runner, + userProfilePage, + }) => { + // On click user edit profile + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + // Edit Profile Input Fields + await runner.clearInputField(userProfilePage.editProfilePhoneNumber); + await runner.clearInputField(userProfilePage.editProfileBiography); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + // After Click On Submit + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PAGE_URL); + await runner.wait(1); + await runner.verifyElementText( + userProfilePage.profileHeaderText, + profilePageData.profileHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.editProfile, + profilePageData.EditProfileButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_EDIT_PAGE_URL); + await runner.verifyElementText( + userProfilePage.editProfileHeaderText, + profilePageData.editProfileHeaderText, + ); + + await runner.waitForMilliseconds(1000); + + // Edit Profile Input Fields Placeholder + await runner.verifyElementPlaceholder( + userProfilePage.editProfilePhoneNumber, + profilePageData.editProfilePhoneNumberPlaceholderText, + ); + await runner.verifyElementPlaceholder( + userProfilePage.editProfileBiography, + profilePageData.editProfileBiographyPlaceholderText, + ); + }); + + // 🔹 Password Settings Page – Test Case Scenarios + test("Validating User Password Settings Header & Placeholder Texts", async ({ + runner, + userProfilePage, + }) => { + // Click on password settings + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + // Header Text + await runner.verifyElementIsVisible( + userProfilePage.passwordSettingsNewPasswordHeaderText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsNewPasswordHeaderText, + profilePageData.passwordSettingsNewPasswordHeaderText, + ); + // New Password + await runner.verifyElementIsVisible( + userProfilePage.passwordSettingsNewPasswordHeaderText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsNewPasswordHeaderText, + profilePageData.passwordSettingsNewPasswordHeaderText, + ); + await runner.verifyElementPlaceholder( + userProfilePage.passwordSettingsNewPasswordInput, + profilePageData.passwordSettingsNewPasswordPlaceholderText, + ); + // Confirm Password + await runner.verifyElementIsVisible( + userProfilePage.passwordSettingsConfirmNewPasswordHeaderText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsConfirmNewPasswordHeaderText, + profilePageData.passwordSettingsConfirmNewPasswordHeaderText, + ); + await runner.verifyElementPlaceholder( + userProfilePage.passwordSettingsConfirmNewPasswordInput, + profilePageData.passwordSettingsConfirmNewPasswordPlaceholderText, + ); + }); + + // Don't run this test. otherwise, it will change the password + test.skip("Validating User Password Successfully Changed", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on password settings + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + + await runner.enterText( + userProfilePage.passwordSettingsNewPasswordInput, + ENV.FUND_FIT_TEST_PASSWORD, + ); + await runner.enterText( + userProfilePage.passwordSettingsConfirmNewPasswordInput, + ENV.FUND_FIT_TEST_PASSWORD, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyProfileUpdateToast, + ); + await runner.waitForMilliseconds(5); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + test("User Tries To Change Password With Both Don't Match Password", async ({ + runner, + userProfilePage, + }) => { + // Click on password settings + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + // New Password + await runner.enterText( + userProfilePage.passwordSettingsNewPasswordInput, + password, + ); + // Confirm New Password + await runner.enterText( + userProfilePage.passwordSettingsConfirmNewPasswordInput, + randomCharValue30Digits, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText, + profilePageData.passwordSettingsConfirmNewPasswordErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + test("User Tries To Change Password With Previous Password Value", async ({ + runner, + userProfilePage, + }) => { + // Click on password settings + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + + // New Password + await runner.enterText( + userProfilePage.passwordSettingsNewPasswordInput, + ENV.FUND_FIT_USER_PREVIOUS_PASSWORD, + ); + // Confirm New Password + await runner.enterText( + userProfilePage.passwordSettingsConfirmNewPasswordInput, + ENV.FUND_FIT_USER_PREVIOUS_PASSWORD, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText2, + profilePageData.passwordSettingsConfirmNewPasswordErrorText2, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText2, + fundFitData.textColorCss, + fundFitData.red500ColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordHeaderText, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + }); + + test("User Tries To Change Password With Current Password Value", async ({ + runner, + userProfilePage, + }) => { + // Click on password settings + await runner.validateTextAndClickOnElement( + userProfilePage.passwordSettings, + profilePageData.passwordSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsHeaderText, + profilePageData.passwordSettingsHeaderText, + ); + + // New Password + await runner.enterText( + userProfilePage.passwordSettingsNewPasswordInput, + ENV.FUND_FIT_USER_PASSWORD, + ); + // Confirm New Password + await runner.enterText( + userProfilePage.passwordSettingsConfirmNewPasswordInput, + ENV.FUND_FIT_USER_PASSWORD, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText2, + profilePageData.passwordSettingsConfirmNewPasswordErrorText2, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordErrorText2, + fundFitData.textColorCss, + fundFitData.red500ColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.passwordSettingsConfirmNewPasswordHeaderText, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + }); + + // 🔹 Email Settings Page – Test Case Scenarios + // Don't run this test. otherwise, it will change the email + test.skip("Validating User Email Successfully Update", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on email settings + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettings, + profilePageData.emailSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsHeaderText, + profilePageData.emailSettingsHeaderText, + ); + + // Existing Email section + await runner.verifyElementText( + userProfilePage.emailSettingsCurrentEmailHeaderText, + profilePageData.emailSettingsCurrentEmailHeaderText, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsCurrentEmailText, + ENV.FUND_FIT_USER_EMAIL, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettingsEditEmailButton, + profilePageData.emailSettingsEditEmailButtonText, + ); + + // New Email setup section + await runner.verifyElementText( + userProfilePage.emailSettingsNewEmailFrom, + profilePageData.emailSettingsNewEmailHeaderText, + "label", + ); + await runner.verifyInputValue( + userProfilePage.emailSettingsNewEmailFrom, + ENV.FUND_FIT_USER_EMAIL, + "input", + ); + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + ENV.FUND_FIT_USER_NEW_EMAIL, // need to change new email (FUND_FIT_USER_NEW_EMAIL) in .env file + "input", + ); + + // Submit button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyProfileUpdateToast, + ); + await runner.waitForMilliseconds(2000); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementIsVisible(userProfilePage.toastCloseButton); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + test.skip("Validating User Email Change With Existing Email", async ({ + runner, + userProfilePage, + }) => { + // Click on email settings + await runner.verifyElementIsVisible(userProfilePage.emailSettings); + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettings, + profilePageData.emailSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsHeaderText, + profilePageData.emailSettingsHeaderText, + ); + + // Existing Email section + await runner.verifyElementText( + userProfilePage.emailSettingsCurrentEmailHeaderText, + profilePageData.emailSettingsCurrentEmailHeaderText, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsCurrentEmailText, + ENV.FUND_FIT_USER_EMAIL, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettingsEditEmailButton, + profilePageData.emailSettingsEditEmailButtonText, + ); + + // New Email section + await runner.verifyElementText( + userProfilePage.emailSettingsNewEmailFrom, + profilePageData.emailSettingsNewEmailHeaderText, + "label", + ); + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + ENV.FUND_FIT_USER_EMAIL, + "input", + ); + + // Submit button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsAlreadyVerifiedErrorText, + profilePageData.emailSettingsAlreadyVerifiedErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsAlreadyVerifiedErrorText, + fundFitData.textColorCss, + fundFitData.red500ColorCode, + ); + }); + + test.skip("Validating User Email Invalid Email Format", async ({ + runner, + userProfilePage, + }) => { + // Click on email settings + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettings, + profilePageData.emailSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsHeaderText, + profilePageData.emailSettingsHeaderText, + ); + + // New Email section + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettingsEditEmailButton, + profilePageData.emailSettingsEditEmailButtonText, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsEditEmailHeaderText, + profilePageData.emailSettingsEditEmailHeaderText, + ); + + // without @ value + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + `${randomCharValue30Digits}.com`, + "input", + ); + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsInvalidErrorText, + profilePageData.emailSettingsInvalidErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsInvalidErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + `${userProfilePage.emailSettingsNewEmailFrom} label`, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + // without .com value + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + `${firstName}@${lastName}`, + "input", + ); + await runner.clickOnElement(userProfilePage.submitButton); + await runner.verifyElementText( + userProfilePage.emailSettingsInvalidErrorText, + profilePageData.emailSettingsInvalidErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsInvalidErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + `${userProfilePage.emailSettingsNewEmailFrom} label`, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + // without domain value + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + `${firstName}@.com`, + "input", + ); + await runner.clickOnElement(userProfilePage.submitButton); + await runner.verifyElementText( + userProfilePage.emailSettingsInvalidErrorText, + profilePageData.emailSettingsInvalidErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsInvalidErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + `${userProfilePage.emailSettingsNewEmailFrom} label`, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + // without domain and .com value + await runner.enterText( + userProfilePage.emailSettingsNewEmailFrom, + `${firstName}`, + "input", + ); + await runner.clickOnElement(userProfilePage.submitButton); + await runner.verifyElementText( + userProfilePage.emailSettingsInvalidErrorText, + profilePageData.emailSettingsInvalidErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsInvalidErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + `${userProfilePage.emailSettingsNewEmailFrom} label`, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + test.skip("Validating Blank Email On User Email Field", async ({ + runner, + userProfilePage, + }) => { + // Click on email settings + await runner.validateTextAndClickOnElement( + userProfilePage.emailSettings, + profilePageData.emailSettingsButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsHeaderText, + profilePageData.emailSettingsHeaderText, + ); + + // New Email section + await runner.clickOnElement( + userProfilePage.emailSettingsEditEmailButton, + ); + await runner.verifyElementText( + userProfilePage.emailSettingsEditEmailHeaderText, + profilePageData.emailSettingsEditEmailHeaderText, + ); + + // Clear Email input field + await runner.clearInputField( + `${userProfilePage.emailSettingsNewEmailFrom} input`, + ); + await runner.clickOnElement(userProfilePage.submitButton); + await runner.verifyElementText( + userProfilePage.emailSettingsInvalidErrorText, + profilePageData.emailSettingsRequiredErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.emailSettingsInvalidErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + `${userProfilePage.emailSettingsNewEmailFrom} label`, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + // 🔹 Background Tab – Test Case Scenarios + test("Validating Navigation & Background Tab Activation Test", async ({ + runner, + userProfilePage, + }) => { + // 1. Verify that clicking the "Background" tab activates it and loads the correct content. + // Click on user background tab + await runner.clickOnElement(userProfilePage.topBarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.backgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.backgroundHeaderText, + profilePageData.backgroundHeaderText, + ); + + // 2. Verify that other tabs (General, Works, Projects, Attachments) are not active when "Background" is selected. + await runner.validateAttribute( + [ + userProfilePage.topBarGeneralButton, + userProfilePage.topBarWorksButton, + userProfilePage.topBarProjectsButton, + userProfilePage.topBarAttachmentsButton, + ], + "data-state", + "inactive", + ); + }); + + test("Validating No Background Data Exists", async ({ + runner, + userProfilePage, + }) => { + // Click on user background tab + await runner.clickOnElement(userProfilePage.topBarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.backgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.backgroundHeaderText, + profilePageData.backgroundHeaderText, + ); + + // Verify that the "No Background" message is displayed when there are no background entries. + await runner.verifyElementIsVisible([ + userProfilePage.noBackgroundIcon, + userProfilePage.noBackgroundHeaderText, + userProfilePage.noBackgroundText, + userProfilePage.noBackgroundAddButton, + ]); + await runner.verifyElementTextContains( + [ + userProfilePage.noBackgroundHeaderText, + userProfilePage.noBackgroundText, + userProfilePage.noBackgroundAddButton, + ], + [ + profilePageData.noBackgroundHeaderText, + profilePageData.noBackgroundText, + profilePageData.noBackgroundAddButtonText, + ], + ); + }); + + test("Validating Add Background Successfully & Remove Successfully", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user background tab + await runner.clickOnElement(userProfilePage.topBarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.backgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.backgroundHeaderText, + profilePageData.backgroundHeaderText, + ); + + // Click on Add Background + await runner.verifyElementIsVisible( + userProfilePage.backgroundPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.backgroundPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createBackgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createBackgroundHeaderText, + profilePageData.createBackgroundHeaderText, + ); + + // Select Background Type + await runner.selectDropdownOptionByText( + userProfilePage.createBackgroundTypeButtonText, + backgroundType, + ); + + // Add Role + await runner.enterText(userProfilePage.createBackgroundRoleInput, role); + // Add Department + await runner.enterText( + userProfilePage.createBackgroundDepartmentInput, + department, + ); + // Add Organization + await runner.enterText( + userProfilePage.createBackgroundOrganizationInput, + organization, + ); + // Add Organization Address + await runner.enterText( + userProfilePage.createBackgroundOrganizationAddressInput, + organizationAddress, + ); + // Add Start Year + await runner.enterText( + userProfilePage.createBackgroundStartYearInput, + startDate.year.toString(), + ); + // Add Start Month + await runner.enterText( + userProfilePage.createBackgroundStartMonthInput, + startDate.month.toString(), + ); + // Add Start Day + await runner.enterText( + userProfilePage.createBackgroundStartDayInput, + startDate.day.toString(), + ); + // Add End Year + await runner.enterText( + userProfilePage.createBackgroundEndYearInput, + endDate.year.toString(), + ); + // Add End Month + await runner.enterText( + userProfilePage.createBackgroundEndMonthInput, + endDate.month.toString(), + ); + // Add End Day + await runner.enterText( + userProfilePage.createBackgroundEndDayInput, + endDate.day.toString(), + ); + + // Click on Submit Button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.waitForMilliseconds(1000); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyBackgroundCreateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + await runner.verifyElementIsVisible( + userProfilePage.sidebarBackgroundValue, + ); + await runner.verifyElementText( + userProfilePage.sidebarBackgroundValue, + "1", + ); + + // Remove Background + await runner.clickOnElement(userProfilePage.tableDeleteButton); + + // Open Background Delete Modal + await runner.verifyElementIsVisible( + userProfilePage.deleteModalHeaderText, + ); + await runner.verifyElementText( + userProfilePage.deleteModalHeaderText, + profilePageData.deleteModalHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.deleteModalButton, + profilePageData.deleteModalDeleteButtonText, + ); + await runner.waitForMilliseconds(1000); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyBackgroundDeleteToast, + ); + }); + + test("Validating Background Data Update Successfully & Remove Successfully", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user background tab + await runner.clickOnElement(userProfilePage.topBarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.backgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.backgroundHeaderText, + profilePageData.backgroundHeaderText, + ); + + // Click on Add Background + await runner.verifyElementIsVisible( + userProfilePage.backgroundPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.backgroundPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createBackgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createBackgroundHeaderText, + profilePageData.createBackgroundHeaderText, + ); + + // Select Background Type + await runner.selectDropdownOptionByText( + userProfilePage.createBackgroundTypeButtonText, + backgroundType, + ); + + // Click on Submit Button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.waitForMilliseconds(1000); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyBackgroundCreateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + // Click on Update Background + await runner.clickOnElement(userProfilePage.tableUpdateButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_UPDATE_PAGE_URL, + true, + ); + await runner.waitUntilElementIsInvisible( + userProfilePage.loadingRecordsText, + ); + await runner.verifyElementIsVisible( + "xpath=//h1[normalize-space()='Update Background']", + ); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Update Background']", + "Update Background", + ); + + // Add Role + await runner.enterText(userProfilePage.createBackgroundRoleInput, role); + // Add Department + await runner.enterText( + userProfilePage.createBackgroundDepartmentInput, + department, + ); + // Add Organization + await runner.enterText( + userProfilePage.createBackgroundOrganizationInput, + organization, + ); + // Add Organization Address + await runner.enterText( + userProfilePage.createBackgroundOrganizationAddressInput, + organizationAddress, + ); + // Add Start Year + await runner.enterText( + userProfilePage.createBackgroundStartYearInput, + startDate.year.toString(), + ); + // Add Start Month + await runner.enterText( + userProfilePage.createBackgroundStartMonthInput, + startDate.month.toString(), + ); + // Add Start Day + await runner.enterText( + userProfilePage.createBackgroundStartDayInput, + startDate.day.toString(), + ); + // Add End Year + await runner.enterText( + userProfilePage.createBackgroundEndYearInput, + endDate.year.toString(), + ); + // Add End Month + await runner.enterText( + userProfilePage.createBackgroundEndMonthInput, + endDate.month.toString(), + ); + // Add End Day + await runner.enterText( + userProfilePage.createBackgroundEndDayInput, + endDate.day.toString(), + ); + + // Click on Submit Button + await runner.clickOnElement(userProfilePage.submitButton); + await runner.waitForMilliseconds(1000); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + true, + ); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyBackgroundUpdateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + // Remove Background + await runner.clickOnElement(userProfilePage.tableDeleteButton); + + // Open Background Delete Modal + await runner.verifyElementIsVisible( + userProfilePage.deleteModalHeaderText, + ); + await runner.verifyElementText( + userProfilePage.deleteModalHeaderText, + profilePageData.deleteModalHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.deleteModalButton, + profilePageData.deleteModalDeleteButtonText, + ); + await runner.waitForMilliseconds(1000); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyBackgroundDeleteToast, + ); + }); + + test("Should Display Validation Error For Create Background Required Fields", async ({ + runner, + userProfilePage, + }) => { + // Click on user background tab + await runner.clickOnElement(userProfilePage.topBarBackgroundButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.backgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.backgroundHeaderText, + profilePageData.backgroundHeaderText, + ); + + // Click on Add Background + await runner.verifyElementIsVisible( + userProfilePage.backgroundPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.backgroundPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createBackgroundHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createBackgroundHeaderText, + profilePageData.createBackgroundHeaderText, + ); + + // Click on submit + await runner.clickOnElement(userProfilePage.submitButton); + + await runner.verifyElementIsVisible( + userProfilePage.createBackgroundTypeHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createBackgroundTypeErrorText, + profilePageData.createBackgroundTypeErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createBackgroundTypeErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createBackgroundTypeHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + // 🔹 Works Tab – Test Case Scenarios + test("Validating The 'Works' Tab activated & No Research Work Data Exists", async ({ + runner, + userProfilePage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Other Tabs are not active when "Works" is selected. + await runner.validateAttribute( + [ + userProfilePage.topBarGeneralButton, + userProfilePage.topBarBackgroundButton, + userProfilePage.topBarProjectsButton, + userProfilePage.topBarAttachmentsButton, + ], + "data-state", + "inactive", + ); + + // Verify that the "No research works" message is displayed when there are no research work entries. + await runner.validateNoDataFound( + userProfilePage.noDataFoundTexts, + userProfilePage.table, + profilePageData.noWorksText, + ); + }); + + test("Validating Successfully Navigate To Research Work Creation Page", async ({ + runner, + userProfilePage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Click on Add Research Work + await runner.verifyElementIsVisible( + userProfilePage.worksPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + + await runner.verifyElementIsVisible([ + userProfilePage.createWorkHeaderText, + userProfilePage.backAndCancelButton, + userProfilePage.createWorkTypeHeaderText, + userProfilePage.createWorkJournalTitleHeaderText, + userProfilePage.createWorkTitleHeaderText, + userProfilePage.createWorkPublicationYearHeaderText, + userProfilePage.createWorkPublicationMonthHeaderText, + userProfilePage.createWorkPublicationDayHeaderText, + ]); + + await runner.verifyElementText( + userProfilePage.createWorkHeaderText, + profilePageData.createWorkHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkTypeHeaderText, + profilePageData.createWorkTypeHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkJournalTitleHeaderText, + profilePageData.createWorkJournalTitleHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkTitleHeaderText, + profilePageData.createWorkTitleHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkPublicationYearHeaderText, + profilePageData.createWorkPublicationYearHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkPublicationMonthHeaderText, + profilePageData.createWorkPublicationMonthHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkPublicationDayHeaderText, + profilePageData.createWorkPublicationDayHeaderText, + ); + + // Verify that the "Back" button is displayed and clickable. + await runner.validateTextAndClickOnElement( + userProfilePage.backAndCancelButton, + profilePageData.backButtonText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + }); + + test("Validating Add Research Works Successfully & Remove Successfully", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Click on Add Research Work + await runner.verifyElementIsVisible( + userProfilePage.worksPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createWorkHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkHeaderText, + profilePageData.createWorkHeaderText, + ); + + // Select Work Type + await runner.selectDropdownOptionByText( + userProfilePage.createWorkTypeButtonText, + workType, + ); + + // Add Journal Title + await runner.enterText( + userProfilePage.createWorkJournalTitleInput, + journalTitle, + ); + // Add Title + await runner.enterText(userProfilePage.createWorkTitleInput, title); + // Add Publication Year + await runner.enterText( + userProfilePage.createWorkPublicationYearInput, + publicationYear.toString(), + ); + // Add Publication Month + await runner.enterText( + userProfilePage.createWorkPublicationMonthInput, + publicationMonth.toString(), + ); + // Add Publication Day + await runner.enterText( + userProfilePage.createWorkPublicationDayInput, + publicationDay.toString(), + ); + + // Click on Submit Button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_PAGE_URL, + true, + ); + await runner.waitForMilliseconds(1000); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyWorkCreateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + await runner.verifyElementIsVisible(userProfilePage.sidebarWorksValue); + + // Remove Work + await runner.clickOnElement(userProfilePage.tableDeleteButton); + + // Open Work Delete Modal + await runner.verifyElementIsVisible( + userProfilePage.deleteModalHeaderText, + ); + await runner.verifyElementText( + userProfilePage.deleteModalHeaderText, + profilePageData.deleteModalHeaderText, + ); + await runner.validateTextAndClickOnElement( + userProfilePage.deleteModalButton, + profilePageData.deleteModalDeleteButtonText, + ); + await runner.waitForMilliseconds(1000); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyWorkDeleteToast, + ); + }); + + test("Validation Research Works Data Update Successfully & Remove Successfully", async ({ + runner, + userProfilePage, + userLoginPage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Click on Add Research Work + await runner.verifyElementIsVisible( + userProfilePage.worksPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createWorkHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkHeaderText, + profilePageData.createWorkHeaderText, + ); + + // Select Work Type + await runner.selectDropdownOptionByText( + userProfilePage.createWorkTypeButtonText, + workType, + ); + + // Add Journal Title + await runner.enterText( + userProfilePage.createWorkJournalTitleInput, + journalTitle, + ); + // Add Title + await runner.enterText(userProfilePage.createWorkTitleInput, title); + + // Click on Submit Button + await runner.validateTextAndClickOnElement( + userProfilePage.submitButton, + profilePageData.submitButtonText, + ); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_PAGE_URL, + true, + ); + await runner.validateElementInvisibility(userProfilePage.submitButton); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyWorkCreateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + + // Click on Update Work + await runner.clickOnElement(userProfilePage.tableUpdateButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_UPDATE_PAGE_URL, + true, + ); + await runner.waitUntilElementIsInvisible( + userProfilePage.loadingRecordsText, + ); + await runner.verifyElementIsVisible( + "xpath=//h1[normalize-space()='Update Research Work']", + ); + await runner.verifyElementText( + "xpath=//h1[normalize-space()='Update Research Work']", + "Update Research Work", + ); + + await runner.enterText( + userProfilePage.createWorkJournalTitleInput, + journalTitle, + ); + + // Add Publication Year + await runner.enterText( + userProfilePage.createWorkPublicationMonthInput, + publicationYear.toString(), + ); + // Add Publication Month + await runner.enterText( + userProfilePage.createWorkPublicationMonthInput, + publicationMonth.toString(), + ); + // Add Publication Day + await runner.enterText( + userProfilePage.createWorkPublicationDayInput, + publicationDay.toString(), + ); + + // Click on Submit Button + await runner.clickOnElement(userProfilePage.submitButton); + await runner.waitForMilliseconds(1000); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_PAGE_URL, + true, + ); + await runner.verifyElementIsVisible(userProfilePage.table); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.verifyElementText( + userLoginPage.toastMessage, + profilePageData.successfullyWorkUpdateToast, + ); + await runner.verifyElementIsVisible(userLoginPage.toastMessage); + await runner.hoverOnElement(userLoginPage.toastMessage); + await runner.clickOnElement(userProfilePage.toastCloseButton); + }); + + test("Validating The Select Works Type Combobox Values", async ({ + runner, + userProfilePage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Click on Add Research Work + await runner.verifyElementIsVisible( + userProfilePage.worksPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createWorkHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkHeaderText, + profilePageData.createWorkHeaderText, + ); + + // validating Work Type combobox values + await runner.selectAllMatchingDropdownOptions( + userProfilePage.createWorkTypeButtonText, + profilePageData.createWorkTypeDropdownTexts, + false, + ); + }); + + test("Validating Work Type Combobox Search Functionality", async ({ + runner, + userProfilePage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createWorkHeaderText, + ); + + // Click to open the Work Type dropdown + await runner.clickOnElement(userProfilePage.createWorkTypeButtonText); + await runner.verifyElementIsVisible( + userProfilePage.createTypeSearchInput, + ); + await runner.validateAttribute( + userProfilePage.createTypeSearchInput, + "placeholder", + profilePageData.createWorkTypeSearchPlaceholderText, + ); + + // Scenario 1: Search with matching results + await runner.enterText( + userProfilePage.createTypeSearchInput, + workType?.replace(/[\s/-]/g, ""), // Remove spaces, slashes, and dashes + ); + await runner.verifyExpectedOptionInDropdown( + userProfilePage.createTypeSearchInput, + ); + + // Scenario 2: Search with no matching results + await runner.clearInputField(userProfilePage.createTypeSearchInput); + await runner.enterText( + userProfilePage.createTypeSearchInput, + randomCharValue30Digits, + ); + + // Verify that "No Results Found" message is displayed + await runner.verifyElementIsVisible( + userProfilePage.noResultsFoundMessage, + ); + await runner.verifyElementText( + userProfilePage.noResultsFoundMessage, + profilePageData.noResultsFoundMessage, + ); + + // Optionally, click outside or press escape to close the dropdown + await runner.pressKeyWithDelay("Escape"); + }); + + test("Should Display Validation Errors For Research Works Required Fields", async ({ + runner, + userProfilePage, + }) => { + // Click on user Works tab + await runner.clickOnElement(userProfilePage.topBarWorksButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_WORK_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.worksHeaderText); + await runner.verifyElementText( + userProfilePage.worksHeaderText, + profilePageData.worksHeaderText, + ); + + // Click on Add Research Work + await runner.verifyElementIsVisible( + userProfilePage.worksPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.worksPlusIconButton); + await runner.verifyContainsUrl( + ENV.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + ); + await runner.verifyElementIsVisible( + userProfilePage.createWorkHeaderText, + ); + await runner.verifyElementText( + userProfilePage.createWorkHeaderText, + profilePageData.createWorkHeaderText, + ); + + // Click on Submit Button + await runner.clickOnElement(userProfilePage.submitButton); + await runner.verifyElementIsVisible([ + userProfilePage.createWorkTypeErrorText, + userProfilePage.createWorkJournalTitleErrorText, + userProfilePage.createWorkTitleErrorText, + ]); + + // Work Type Required + await runner.verifyElementText( + userProfilePage.createWorkTypeErrorText, + profilePageData.createWorkTypeErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkTypeErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkTypeHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + // Journal Title Required + await runner.verifyElementText( + userProfilePage.createWorkJournalTitleErrorText, + profilePageData.createWorkJournalTitleErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkJournalTitleErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkJournalTitleHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + // Title Required + await runner.verifyElementText( + userProfilePage.createWorkTitleErrorText, + profilePageData.createWorkTitleErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkTitleErrorText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userProfilePage.createWorkTitleHeaderText, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + // 🔹 Projects Tab – Test Case Scenarios + test("Validating The 'Projects' Tab activated & No Research Work Data Exists", async ({ + runner, + userProfilePage, + }) => { + // Click on user Projects tab + await runner.clickOnElement(userProfilePage.topBarProjectsButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PROJECT_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.projectsHeaderText); + await runner.verifyElementText( + userProfilePage.projectsHeaderText, + profilePageData.projectHeaderText, + ); + + // Other Tabs are not active when "Projects" is selected. + await runner.validateAttribute( + [ + userProfilePage.topBarGeneralButton, + userProfilePage.topBarBackgroundButton, + userProfilePage.topBarWorksButton, + userProfilePage.topBarAttachmentsButton, + ], + "data-state", + "inactive", + ); + + // Verify that the "No research projects" message is displayed when there are no research work entries. + await runner.validateNoDataFound( + userProfilePage.noDataFoundTexts, + userProfilePage.table, + profilePageData.noProjectsText, + ); + }); + + test("Validating Successfully Navigate To Project Creation Page", async ({ + runner, + userProfilePage, + }) => { + // Click on user Projects tab + await runner.clickOnElement(userProfilePage.topBarProjectsButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PROJECT_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.projectsHeaderText); + await runner.verifyElementText( + userProfilePage.projectsHeaderText, + profilePageData.projectHeaderText, + ); + + // Click on Add + await runner.verifyElementIsVisible( + userProfilePage.projectsPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.projectsPlusIconButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_CREATE_PROJECT_PAGE_URL); + + await runner.waitUntilElementIsInvisible( + userProfilePage.projectsPlusIconButton, + ); + + await runner.verifyElementIsVisible( + userProfilePage.createProjectsHeaderTextSection, + ); + + await runner.verifyElementText( + userProfilePage.createProjectsHeaderTextSection, + profilePageData.createProjectFormHeaderText, + "h1", + ); + + await runner.verifyElementText( + userProfilePage.createProjectsHeaderTextSection, + profilePageData.backButtonText, + "a", + ); + + await runner.verifyElementIsVisible( + userProfilePage.selectProjectTypeComboBoxInput, + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "closed", + ); + + await runner.clickOnElement( + userProfilePage.selectProjectTypeComboBoxInput, + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "open", + ); + + await runner.selectDropdownOptionByText( + userProfilePage.selectProjectTypeComboBoxInput, + "contract", + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "closed", + ); + }); + + test("Validating The Select Project Type Combobox Values", async ({ + runner, + userProfilePage, + }) => { + // Click on user Projects tab + await runner.clickOnElement(userProfilePage.topBarProjectsButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_PROFILE_PROJECT_PAGE_URL); + await runner.verifyElementIsVisible(userProfilePage.projectsHeaderText); + await runner.verifyElementText( + userProfilePage.projectsHeaderText, + profilePageData.projectHeaderText, + ); + + // Click on Add + await runner.verifyElementIsVisible( + userProfilePage.projectsPlusIconButton, + ); + await runner.clickOnElement(userProfilePage.projectsPlusIconButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_CREATE_PROJECT_PAGE_URL); + + await runner.waitUntilElementIsInvisible( + userProfilePage.projectsPlusIconButton, + ); + + await runner.verifyElementIsVisible( + userProfilePage.createProjectsHeaderTextSection, + ); + + await runner.verifyElementText( + userProfilePage.createProjectsHeaderTextSection, + profilePageData.createProjectFormHeaderText, + "h1", + ); + + await runner.verifyElementText( + userProfilePage.createProjectsHeaderTextSection, + profilePageData.backButtonText, + "a", + ); + + await runner.verifyElementIsVisible( + userProfilePage.selectProjectTypeComboBoxInput, + ); + + await runner.clickOnElement( + userProfilePage.selectProjectTypeComboBoxInput, + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "open", + ); + + await runner.verifyLinksTexts( + userProfilePage.selectProjectTypeComboBoxTextValues, + profilePageData.comboBoxPlaceholderText, + ); + + await runner.selectDropdownOptionByText( + userProfilePage.selectProjectTypeComboBoxInput, + "contract", + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "closed", + ); + + await runner.selectDropdownOptionByText( + userProfilePage.selectProjectTypeComboBoxInput, + "salary award", + ); + + await runner.selectDropdownOptionByText( + userProfilePage.selectProjectTypeComboBoxInput, + "grant", + ); + + await runner.selectDropdownOptionByText( + userProfilePage.selectProjectTypeComboBoxInput, + "award", + ); + + await runner.validateAttribute( + userProfilePage.selectProjectTypeComboBoxInput, + "data-state", + "closed", + ); + }); + }); // End of describe block + } // End of runTests method +} // End of UserProfileTest class +const testSuite = new UserProfileTest(); +testSuite.runTests(); diff --git a/tests/UserRegisterTest.spec.ts b/tests/UserRegisterTest.spec.ts new file mode 100644 index 0000000..a1d89c0 --- /dev/null +++ b/tests/UserRegisterTest.spec.ts @@ -0,0 +1,1012 @@ +import { test } from "../utilities/fixtures"; +import { ExpectedTextProvider } from "../utilities/valueProvider"; +import { ENV } from "../utilities/env"; +import { FakerDataProvider } from "../utilities/fakerDataProvider"; +import fundFitData from "../testData/fundFit.json"; +import homePageData from "../testData/homePageData.json"; +import registerPageData from "../testData/registerPageData.json"; + +class UserRegisterTest extends ExpectedTextProvider { + private fakerData: FakerDataProvider; + constructor() { + super(); + this.fakerData = new FakerDataProvider(); + } + + runTests() { + const { firstName, lastName, phoneNumber, email, password } = + this.fakerData; + + test.describe("Validating User Login Scenarios", () => { + test.beforeEach(async ({ runner, userLandingPage, userRegisterPage }) => { + await runner.navigateTo(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyContainsUrl(ENV.FUND_FIT_TEST_ENV_URL); + await runner.verifyPageTitle(fundFitData.fundFitTitle); + await runner.verifyElementIsVisible(userLandingPage.homePageHeroText); + await runner.verifyElementIsVisible(userLandingPage.registerButton); + + await runner.validateTextAndClickOnElement( + userLandingPage.registerButton, + homePageData.registerButtonText, + ); + + await runner.waitUntilElementIsInvisible( + userLandingPage.homePageHeroText, + ); + await runner.verifyContainsUrl(ENV.FUND_FIT_REGISTER_PAGE_URL); + await runner.verifyElementIsVisible(userRegisterPage.firstName); + }); + + test("Validating Register Form Contents", async ({ + runner, + userRegisterPage, + }) => { + await runner.verifyElementIsVisible(userRegisterPage.heroText); + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.validateAttribute( + userRegisterPage.heroText2, + "href", + ENV.FUND_FIT_LANDING_PAGE_URL, + "a", + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + }); + + //Skipping this test case to avoid unwanted user creation + test.skip("User Completes & Submits A Registration", async ({ + runner, + userRegisterPage, + userLoginPage, + }) => { + await runner.verifyElementIsVisible(userRegisterPage.heroText); + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.validateAttribute( + userRegisterPage.heroText2, + "href", + ENV.FUND_FIT_LANDING_PAGE_URL, + "a", + ); + + await runner.verifyElementText( + userRegisterPage.firstNameHeader, + "First Name", + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.waitUntilElementIsInvisible( + userRegisterPage.firstNameErrorMessage, + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + + await runner.clickOnElement(userRegisterPage.firstName); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.lastName); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyInputValue(userRegisterPage.lastName, lastName); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.email); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, phoneNumber); + await runner.verifyInputValue( + userRegisterPage.phoneNumber, + phoneNumber, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.clickOnElement(userRegisterPage.submitButton); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + await runner.verifyContainsUrl(ENV.FUND_FIT_LOGIN_PAGE_URL); + }); + + test("User Register Form Should Not Be Submit-able Without First Name", async ({ + runner, + userRegisterPage, + }) => { + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyInputValue(userRegisterPage.lastName, lastName); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.enterText(userRegisterPage.phoneNumber, phoneNumber); + await runner.verifyInputValue( + userRegisterPage.phoneNumber, + phoneNumber, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.firstNameErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.firstNameErrorMessage, + registerPageData.registerPageFirstNameErrorText, + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + test("User Register Form Should Not Be Submit-able Without Last Name", async ({ + runner, + userRegisterPage, + }) => { + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementText( + userRegisterPage.lastNameHeader, + registerPageData.lastNameHeaderText, + ); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.email, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, phoneNumber); + await runner.verifyInputValue( + userRegisterPage.phoneNumber, + phoneNumber, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumber, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.password, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + await runner.clickOnElement(userRegisterPage.submitButton); + await runner.verifyElementIsVisible( + userRegisterPage.lastNameErrorMessage, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.lastNameErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.lastNameErrorMessage, + registerPageData.registerPageLastNameErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.lastNameErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + //Skipping this test case to avoid unwanted user creation + test.skip("User Register Form Should Not Be Submit-able Without Proper Email Format", async ({ + runner, + userRegisterPage, + userLoginPage, + }) => { + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementText( + userRegisterPage.lastNameHeader, + registerPageData.lastNameHeaderText, + ); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, phoneNumber); + await runner.verifyInputValue( + userRegisterPage.phoneNumber, + phoneNumber, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumber, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.password, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.confirmPassword, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + //Email Block + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.enterText(userRegisterPage.email, ""); + await runner.verifyInputValue(userRegisterPage.email, ""); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.email, + fundFitData.textColorCss, + fundFitData.blackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.emailInputBoxErrorMessage, + registerPageData.registerPageEmailErrorText, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.emailInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.enterText(userRegisterPage.email, "test"); + await runner.verifyInputValue(userRegisterPage.email, "test"); + + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.emailInputBoxErrorMessage, + registerPageData.registerPageInvalidEmailErrorText, + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.emailInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.enterText(userRegisterPage.email, "test@"); + await runner.verifyInputValue(userRegisterPage.email, "test@"); + + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.emailInputBoxErrorMessage, + registerPageData.registerPageInvalidEmailErrorText, + ); + + await runner.enterText(userRegisterPage.email, "test@gmail"); + await runner.verifyInputValue(userRegisterPage.email, "test@gmail"); + + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.verifyElementIsVisible( + userRegisterPage.emailInputBoxErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.emailInputBoxErrorMessage, + registerPageData.registerPageInvalidEmailErrorText, + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.emailInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + + await runner.validateTextAndClickOnElement( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + + await runner.waitUntilElementIsInvisible( + userRegisterPage.emailInputBoxErrorMessage, + ); + + await runner.verifyElementIsVisible(userLoginPage.loginButton); + + await runner.verifyContainsUrl(ENV.FUND_FIT_LOGIN_PAGE_URL); + await runner.verifyElementIsVisible(userLoginPage.email); + + await runner.verifyElementIsVisible(userRegisterPage.toastMessage); + await runner.verifyElementText( + userRegisterPage.toastMessage, + "Registration SuccessfulPlease check your email for verification.", + ); + }); + + test("User Tries To Register User With Blank Phone Number", async ({ + runner, + userRegisterPage, + }) => { + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementText( + userRegisterPage.lastNameHeader, + registerPageData.lastNameHeaderText, + ); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyInputValue(userRegisterPage.lastName, lastName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.lastNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.email, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, ""); + await runner.verifyInputValue(userRegisterPage.phoneNumber, ""); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumber, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.password, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.confirmPassword, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + await runner.clickOnElement(userRegisterPage.submitButton); + + await runner.waitUntilElementIsInvisible( + userRegisterPage.phoneNumberInputBoxErrorMessage, + ); + + await runner.verifyContainsUrl(ENV.FUND_FIT_LOGIN_PAGE_URL); + await runner.verifyElementIsVisible(userRegisterPage.toastMessage); + await runner.verifyElementText( + userRegisterPage.toastMessage, + "Registration SuccessfulPlease check your email for verification.", + ); + }); + + test("User Tries To Register With Alphabetic Value In Phone Number", async ({ + runner, + userRegisterPage, + }) => { + await runner.verifyElementText( + userRegisterPage.heroText, + registerPageData.registerPageHeroText, + ); + + await runner.verifyElementText( + userRegisterPage.heroText2, + registerPageData.registerPageHeroText2, + ); + + await runner.verifyElementIsVisible(userRegisterPage.firstName); + await runner.verifyElementPlaceholder( + userRegisterPage.firstName, + registerPageData.registerPageFirstNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.firstName, firstName); + await runner.verifyInputValue(userRegisterPage.firstName, firstName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.firstNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.lastName); + await runner.verifyElementText( + userRegisterPage.lastNameHeader, + registerPageData.lastNameHeaderText, + ); + await runner.verifyElementPlaceholder( + userRegisterPage.lastName, + registerPageData.registerPageLastNamePlaceholderText, + ); + await runner.enterText(userRegisterPage.lastName, lastName); + await runner.verifyInputValue(userRegisterPage.lastName, lastName); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.lastNameHeader, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.email); + await runner.verifyElementPlaceholder( + userRegisterPage.email, + registerPageData.registerPageEmailPlaceholderText, + ); + await runner.enterText(userRegisterPage.email, email); + await runner.verifyInputValue(userRegisterPage.email, email); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.email, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.phoneNumber); + await runner.verifyElementPlaceholder( + userRegisterPage.phoneNumber, + registerPageData.registerPagePhonePlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.phoneNumber); + await runner.enterText(userRegisterPage.phoneNumber, "test"); + await runner.verifyInputValue(userRegisterPage.phoneNumber, "test"); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumber, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.password); + await runner.verifyElementPlaceholder( + userRegisterPage.password, + registerPageData.registerPagePasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.password); + await runner.enterText(userRegisterPage.password, password); + await runner.verifyInputValue(userRegisterPage.password, password); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.password, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.confirmPassword); + await runner.verifyElementPlaceholder( + userRegisterPage.confirmPassword, + registerPageData.registerPageConfirmPasswordPlaceholderText, + ); + await runner.clickOnElement(userRegisterPage.confirmPassword); + await runner.enterText(userRegisterPage.confirmPassword, password); + await runner.verifyInputValue( + userRegisterPage.confirmPassword, + password, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.confirmPassword, + fundFitData.textColorCss, + fundFitData.secondBlackColorCode, + ); + + await runner.verifyElementIsVisible(userRegisterPage.privacyPolicyText); + await runner.verifyElementText( + userRegisterPage.privacyPolicyText, + registerPageData.registerPagePrivacyPolicyText, + ); + await runner.verifyElementIsVisible( + userRegisterPage.privacyPolicyCheckbox, + ); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "unchecked", + ); + await runner.clickOnElement(userRegisterPage.privacyPolicyCheckbox); + await runner.verifyCheckboxDataState( + userRegisterPage.privacyPolicyCheckbox, + "checked", + ); + + await runner.verifyElementIsVisible(userRegisterPage.submitButton); + await runner.verifyElementText( + userRegisterPage.submitButton, + fundFitData.submitButtonText, + ); + await runner.clickOnElement(userRegisterPage.submitButton); + + await runner.verifyElementIsVisible( + userRegisterPage.phoneNumberInputBoxErrorMessage, + ); + await runner.verifyElementText( + userRegisterPage.phoneNumberInputBoxErrorMessage, + registerPageData.registerPageInvalidPhoneNumberErrorText, + ); + + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumberHeader, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + await runner.verifyElementToHaveCSSProperty( + userRegisterPage.phoneNumberInputBoxErrorMessage, + fundFitData.textColorCss, + fundFitData.redColorCode, + ); + }); + + test("User Tries To Register With Wrong Password Combination", async ({ + runner, + userRegisterPage, + }) => {}); + }); //Describe block + } //End of runTests method +} //End of UserRegisterTest class +const testSuite = new UserRegisterTest(); +testSuite.runTests(); diff --git a/tests/lambdaTestPlayground.spec.ts b/tests/lambdaTestPlayground.spec.ts deleted file mode 100644 index d8d6c12..0000000 --- a/tests/lambdaTestPlayground.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { test } from "../utilities/fixtures"; -import lambdaData from "../testData/lambda.json"; -import { ExpectedTextProvider } from "../utilities/valueProvider"; - -class MenuValidationTests extends ExpectedTextProvider { - constructor() { - super(); - } - - runTests() { - test.describe.skip("Validating Menu Click", () => { - test.beforeEach(async ({ runner }) => { - await runner.navigateTo(lambdaData.lambdaTestUrl); - }); - - test("Validating Navigation with URL & Title", async ({ runner }) => { - await runner.verifyContainsUrl(lambdaData.lambdaTestUrl); - await runner.verifyTitle(lambdaData.pageTitle); - }); - - test("Validating and Entering value to text input field", async ({ - runner, - lambdaPage, - }) => { - await runner.typeInputBox(lambdaPage.textInputField, "cameras"); - await runner.clickOnElement(lambdaPage.searchButton); - await runner.mouseHover(lambdaPage.accountButton); - await runner.clickOnElement(lambdaPage.loginButton); - await runner.clickOnElement(lambdaPage.accountButton); - }); - - test("Validating Login Form & Successful Login Attempt", async ({ - runner, - lambdaPage, - }) => { - await runner.mouseHover(lambdaPage.accountButton); - await runner.clickOnElement(lambdaPage.loginButton); - await runner.clickOnElement(lambdaPage.accountButton); - - await runner.verifyLinksText( - lambdaPage.rightColumnList, - this.expectedTexts, - ); - }); - }); - } -} - -// Run the tests -const testSuite = new MenuValidationTests(); -testSuite.runTests(); diff --git a/tests/loginTest.spec.ts b/tests/loginTest.spec.ts deleted file mode 100644 index dbb681d..0000000 --- a/tests/loginTest.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { test } from "../utilities/fixtures"; -import lambdaData from "../testData/lambda.json"; -import { ExpectedTextProvider } from "../utilities/valueProvider"; - -class LoginTest extends ExpectedTextProvider { - constructor() { - super(); - } - - runTests() { - test.describe("Validating User Login Scenarios", () => { - test.beforeEach(async ({ runner }) => { - await runner.navigateTo(lambdaData.lambdaTestUrl); - await runner.verifyContainsUrl(lambdaData.lambdaTestUrl); - await runner.verifyTitle(lambdaData.pageTitle); - }); - - test.skip("Validating the input fields", async ({ - runner, - loginPage, - lambdaPage, - }) => { - await runner.mouseHover(lambdaPage.accountButton); - await runner.clickOnElement(lambdaPage.loginButton); - await runner.verifyContainsUrl(lambdaData.loginPageUrl); - await runner.verifyElementIsVisible(loginPage.emailInputField); - await runner.clearInputField(loginPage.emailInputField); - await runner.typeInputBox( - loginPage.emailInputField, - "testUser@gmail.com", - ); - await runner.verifyElementIsVisible(loginPage.passwordInputField); - await runner.clearInputField(loginPage.passwordInputField); - await runner.typeInputBox(loginPage.passwordInputField, "12345678"); - await runner.clickOnElement(loginPage.loginButton); - }); - - test("Validating Login Attempts With Valid Credentials", async ({ - runner, - loginPage, - lambdaPage, - accountPage, - }) => { - await runner.mouseHover(lambdaPage.accountButton); - await runner.clickOnElement(lambdaPage.loginButton); - await runner.verifyContainsUrl(lambdaData.loginPageUrl); - await runner.verifyElementIsVisible(loginPage.emailInputField); - await runner.clearInputField(loginPage.emailInputField); - await runner.typeInputBox( - loginPage.emailInputField, - "mahbubasr091@gmail.com", - ); - await runner.verifyElementIsVisible(loginPage.passwordInputField); - await runner.clearInputField(loginPage.passwordInputField); - await runner.typeInputBox(loginPage.passwordInputField, "1234567889"); - await runner.clickOnElement(loginPage.loginButton); - await runner.verifyElementIsVisible(accountPage.loginPageText); - await runner.verifyContainText( - accountPage.loginPageText, - "This is a dummy website for Web Automation Testing", - ); - await runner.verifyContainsUrl(lambdaData.accountPageUrl); - await runner.mouseHover(lambdaPage.accountButton); - await runner.clickOnElement(lambdaPage.accountButton); - }); - }); //exit - } -} -const testSuite = new LoginTest(); -testSuite.runTests(); diff --git a/utilities/env.ts b/utilities/env.ts new file mode 100644 index 0000000..2d61ca0 --- /dev/null +++ b/utilities/env.ts @@ -0,0 +1,83 @@ +import * as dotenv from "dotenv"; +import { z } from "zod"; + +dotenv.config(); + +const EnvSchema = z.object({ + FUND_FIT_TEST_ENV_URL: z.string().url(), + FUND_FIT_USER_EMAIL: z.string().email(), + FUND_FIT_USER_NEW_EMAIL: z.string().email(), + FUND_FIT_USER_PASSWORD: z.string().min(8), + FUND_FIT_USER_PREVIOUS_PASSWORD: z.string().min(8), + FUND_FIT_TEST_PASSWORD: z.string().min(8), + FUND_FIT_LOGIN_PAGE_URL: z.string().url(), + FUND_FIT_REGISTER_PAGE_URL: z.string().url(), + FUND_FIT_LANDING_PAGE_URL: z.string().url(), + FUND_FIT_FORGET_PASSWORD_PAGE_URL: z.string().url(), + FUND_FIT_DASHBOARD_PAGE_URL: z.string().url(), + FUND_FIT_START_MATCHING_PAGE_URL: z.string().url(), + FUND_FIT_MATCHES_PAGE_URL: z.string().url(), + FUND_FIT_NOTIFICATIONS_PAGE_URL: z.string().url(), + + FUND_FIT_PROFILE_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_EDIT_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL: z.string().url(), + + FUND_FIT_PROFILE_BACKGROUND_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_BACKGROUND_UPDATE_PAGE_URL: z.string().url(), + + FUND_FIT_PROFILE_WORK_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_WORK_UPDATE_PAGE_URL: z.string().url(), + + FUND_FIT_PROFILE_PROJECT_PAGE_URL: z.string().url(), + FUND_FIT_PROFILE_ATTACHMENT_PAGE_URL: z.string().url(), + FUND_FIT_CREATE_PROJECT_PAGE_URL: z.string().url(), +}); + +export const ENV = EnvSchema.parse({ + FUND_FIT_TEST_ENV_URL: process.env.FUND_FIT_TEST_ENV_URL, + FUND_FIT_USER_EMAIL: process.env.FUND_FIT_USER_EMAIL, + FUND_FIT_USER_NEW_EMAIL: process.env.FUND_FIT_USER_NEW_EMAIL, + FUND_FIT_USER_PASSWORD: process.env.FUND_FIT_USER_PASSWORD, + FUND_FIT_USER_PREVIOUS_PASSWORD: process.env.FUND_FIT_USER_PREVIOUS_PASSWORD, + FUND_FIT_TEST_PASSWORD: process.env.FUND_FIT_TEST_PASSWORD, + FUND_FIT_LOGIN_PAGE_URL: process.env.FUND_FIT_LOGIN_PAGE_URL, + FUND_FIT_REGISTER_PAGE_URL: process.env.FUND_FIT_REGISTER_PAGE_URL, + FUND_FIT_LANDING_PAGE_URL: process.env.FUND_FIT_LANDING_PAGE_URL, + FUND_FIT_FORGET_PASSWORD_PAGE_URL: + process.env.FUND_FIT_FORGET_PASSWORD_PAGE_URL, + FUND_FIT_DASHBOARD_PAGE_URL: process.env.FUND_FIT_DASHBOARD_PAGE_URL, + FUND_FIT_START_MATCHING_PAGE_URL: + process.env.FUND_FIT_START_MATCHING_PAGE_URL, + FUND_FIT_MATCHES_PAGE_URL: process.env.FUND_FIT_MATCHES_PAGE_URL, + FUND_FIT_NOTIFICATIONS_PAGE_URL: process.env.FUND_FIT_NOTIFICATIONS_PAGE_URL, + + FUND_FIT_PROFILE_PAGE_URL: process.env.FUND_FIT_PROFILE_PAGE_URL, + FUND_FIT_PROFILE_EDIT_PAGE_URL: process.env.FUND_FIT_PROFILE_EDIT_PAGE_URL, + FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL: + process.env.FUND_FIT_PROFILE_PASSWORD_SETTING_PAGE_URL, + FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL: + process.env.FUND_FIT_PROFILE_EMAIL_SETTING_PAGE_URL, + + FUND_FIT_PROFILE_BACKGROUND_PAGE_URL: + process.env.FUND_FIT_PROFILE_BACKGROUND_PAGE_URL, + FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL: + process.env.FUND_FIT_PROFILE_BACKGROUND_CREATE_PAGE_URL, + FUND_FIT_PROFILE_BACKGROUND_UPDATE_PAGE_URL: + process.env.FUND_FIT_PROFILE_BACKGROUND_UPDATE_PAGE_URL, + + FUND_FIT_PROFILE_WORK_PAGE_URL: process.env.FUND_FIT_PROFILE_WORK_PAGE_URL, + FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL: + process.env.FUND_FIT_PROFILE_WORK_CREATE_PAGE_URL, + FUND_FIT_PROFILE_WORK_UPDATE_PAGE_URL: + process.env.FUND_FIT_PROFILE_WORK_UPDATE_PAGE_URL, + + FUND_FIT_PROFILE_PROJECT_PAGE_URL: + process.env.FUND_FIT_PROFILE_PROJECT_PAGE_URL, + FUND_FIT_PROFILE_ATTACHMENT_PAGE_URL: + process.env.FUND_FIT_PROFILE_ATTACHMENT_PAGE_URL, + FUND_FIT_CREATE_PROJECT_PAGE_URL: process.env.FUND_FIT_CREATE_PROJECT_PAGE_URL, +}); diff --git a/utilities/fakerDataProvider.ts b/utilities/fakerDataProvider.ts new file mode 100644 index 0000000..ec44cb6 --- /dev/null +++ b/utilities/fakerDataProvider.ts @@ -0,0 +1,150 @@ +import { faker } from "@faker-js/faker"; + +export class FakerDataProvider { + firstName: string; + lastName: string; + email: string; + phoneNumber: string; + password: string; + bio: string; + randomCharValue30Digits: string; + randomCharValuesForInputValueValidation: string; + numericValueOf20Digits: string; + backgroundType: (typeof backgroundTypes)[number]; + role: string; + department: string; + organization: string; + organizationAddress: string; + startDate: { + year: number; + month: number; + day: number; + }; + endDate: { + year: number; + month: number; + day: number; + }; + + workType: (typeof workTypes)[number]; + journalTitle: string; + title: string; + publicationYear: number; + publicationMonth: number; + publicationDay: number; + + projectTypes: (typeof projectTypes)[number]; + projectTitle: string; + organizationType: string + amount: string; + + constructor() { + this.firstName = faker.person.firstName(); + this.lastName = faker.person.lastName(); + this.email = faker.internet.email(); + this.phoneNumber = faker.string.numeric(10); + this.password = faker.internet.password(); + this.bio = faker.lorem.paragraphs(2); + this.randomCharValue30Digits = faker.string.alphanumeric(30); + this.randomCharValuesForInputValueValidation = + faker.string.alphanumeric(70); + this.numericValueOf20Digits = faker.string.numeric(20); + + this.backgroundType = faker.helpers.arrayElement(backgroundTypes); + this.role = faker.person.jobTitle(); + this.department = faker.commerce.department(); + this.organization = faker.company.name(); + this.organizationAddress = faker.location.streetAddress(); + + const start = faker.date.past({ years: 10 }); + const end = faker.date.recent({ days: 30 }); + + this.startDate = { + year: start.getFullYear(), + month: start.getMonth() + 1, + day: start.getDate(), + }; + + this.endDate = { + year: end.getFullYear(), + month: end.getMonth() + 1, + day: end.getDate(), + }; + + this.workType = faker.helpers.arrayElement(workTypes); + this.journalTitle = faker.company.name() + " Journal"; + this.title = faker.lorem.sentence(); + + const publicationDate = faker.date.past({ years: 5 }); + this.publicationYear = publicationDate.getFullYear(); + this.publicationMonth = publicationDate.getMonth() + 1; + this.publicationDay = publicationDate.getDate(); + + this.projectTypes = faker.helpers.arrayElement(projectTypes); + this.projectTitle= faker.company.catchPhrase(); + this.organizationType=faker.company.buzzVerb(); + this.amount = faker.string.numeric({ length: 5, allowLeadingZeros: false }); + } +} + +const backgroundTypes = [ + "Distinction", + "Education", + "Employment", + "Invited Position", + "Membership", + "Qualification", + "Service", +] as const; + +const workTypes = [ + "Annotation", + "Artistic Performance", + "Conference Abstract", + "Conference Paper", + "Conference Poster", + "Data Management Plan", + "Data Set", + "Dictionary Entry", + "Disclosure", + "Dissertation/Thesis", + "Edited Book", + "Encyclopedia Entry", + "Invention", + "Book", + "Book Chapter", + "Book Review", + "Edited Book", + "Encyclopedia Entry", + "Invention", + "Journal Article", + "Journal Issue", + "Lecture/Speech", + "License", + "Magazine Article", + "Manual", + "Newsletter Article", + "Newspaper Article", + "Online Resource", + "Other", + "Patent", + "Physical Object", + "Preprint", + "Registered Copyright", + "Report", + "Research Technique", + "Research Tool", + "Review", + "Software", + "Spin-off Company", + "Standards and Policy", + "Supervised Student Publication", + "Technical Standard", + "Test", + "Trademark", + "Translation", + "Website", + "Working Paper", +] as const; + +const projectTypes = ["Contract", "Salary Award", "Grant", "Award"] as const; diff --git a/utilities/fixtures.ts b/utilities/fixtures.ts index 3ad7c75..c0586fa 100644 --- a/utilities/fixtures.ts +++ b/utilities/fixtures.ts @@ -1,31 +1,72 @@ import { test as base, Page } from "@playwright/test"; -import { LambdaHomePage } from "../pom/lambdaHomePage"; -import { Utils } from "./utils"; -import { LoginPage } from "../pom/LoginPage"; -import { AccountPage } from "../pom/accountPage"; +import { Utils } from "./utility"; +import { loginPage } from "../pageObjectModel/loginPage"; +import { homePage } from "../pageObjectModel/homePage"; +import { registerPage } from "../pageObjectModel/registerPage"; +import { dashboardPage } from "../pageObjectModel/dashboardPage"; +import { startMatchingPage } from "../pageObjectModel/startMatchingPage"; +import { matchesPage } from "../pageObjectModel/matchesPage"; +import { notificationPage } from "../pageObjectModel/notificationPage"; +import { profilePage } from "../pageObjectModel/profilePage"; -const test = base.extend<{ +interface TestFixtures { runner: Utils; - lambdaPage: LambdaHomePage; - loginPage: LoginPage; - accountPage: AccountPage; -}>({ - runner: async ({ page }: { page: Page }, use) => { + userLoginPage: loginPage; + userLandingPage: homePage; + userRegisterPage: registerPage; + userDashboardPage: dashboardPage; + userStartMatchingPage: startMatchingPage; + userMatchesPage: matchesPage; + userNotificationPage: notificationPage; + userProfilePage: profilePage; +} + +interface PageFixture { + page: Page; +} + +const test = base.extend({ + runner: async ({ page }: PageFixture, use) => { const utilsInstance = new Utils(page); await use(utilsInstance); }, - lambdaPage: async ({ page }: { page: Page }, use) => { - const lambdaPageInstance = new LambdaHomePage(page); - await use(lambdaPageInstance); + userLoginPage: async ({ page }: PageFixture, use) => { + const userLoginPageInstance = new loginPage(page); + await use(userLoginPageInstance); + }, + + userLandingPage: async ({ page }: PageFixture, use) => { + const userHomePageInstance = new homePage(page); + await use(userHomePageInstance); + }, + + userRegisterPage: async ({ page }: PageFixture, use) => { + const userRegisterPageInstance = new registerPage(page); + await use(userRegisterPageInstance); + }, + + userDashboardPage: async ({ page }: PageFixture, use) => { + const userDashboardPageInstance = new dashboardPage(page); + await use(userDashboardPageInstance); + }, + + userStartMatchingPage: async ({ page }: PageFixture, use) => { + const userStartMatchingInstance = new startMatchingPage(page); + await use(userStartMatchingInstance); }, - loginPage: async ({ page }: { page: Page }, use) => { - const loginPageInstance = new LoginPage(page); - await use(loginPageInstance); + + userMatchesPage: async ({ page }: PageFixture, use) => { + const userMatchesInstance = new matchesPage(page); + await use(userMatchesInstance); }, - accountPage: async ({ page }: { page: Page }, use) => { - const accountPageInstance = new AccountPage(page); - await use(accountPageInstance); + userNotificationPage: async ({ page }: PageFixture, use) => { + const userNotificationInstance = new notificationPage(page); + await use(userNotificationInstance); + }, + userProfilePage: async ({ page }: PageFixture, use) => { + const userProfileInstance = new profilePage(page); + await use(userProfileInstance); }, }); diff --git a/utilities/logger.ts b/utilities/logger.ts index dd18db4..824911a 100644 --- a/utilities/logger.ts +++ b/utilities/logger.ts @@ -1,15 +1,14 @@ import { createLogger, format, transports } from "winston"; import fs from "fs"; +import { allure } from "allure-playwright"; -// Define log file path -const LOG_FILE_PATH = "test-logs.log"; +const LOG_FILE_PATH = "reports/test_log/test-logs.log"; -// Delete log file before each execution if (fs.existsSync(LOG_FILE_PATH)) { fs.unlinkSync(LOG_FILE_PATH); } -const logger = createLogger({ +const baseLogger = createLogger({ level: "info", format: format.combine( format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), @@ -23,4 +22,21 @@ const logger = createLogger({ ], }); +const logger = { + info: (message: string | string[]) => { + baseLogger.info(message); + allure.step( + Array.isArray(message) ? message.join(", ") : message, + async () => Promise.resolve(), + ); + }, + error: (message: string | string[]) => { + baseLogger.info(message); + allure.step( + Array.isArray(message) ? message.join(", ") : message, + async () => Promise.resolve(), + ); + }, +}; + export default logger; diff --git a/utilities/utility.ts b/utilities/utility.ts new file mode 100644 index 0000000..19b1137 --- /dev/null +++ b/utilities/utility.ts @@ -0,0 +1,1415 @@ +import { expect, Page } from "@playwright/test"; +import { allure } from "allure-playwright"; +import logger from "./logger"; +import { number } from "zod"; + +export class Utils { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + private async captureScreenshotOnFailure(testName: string): Promise { + try { + const screenshot = await this.page.screenshot(); + allure.attachment(`${testName} Screenshot`, screenshot, "image/png"); + logger.error(`${testName} failed. Screenshot captured.`); + } catch (error) { + logger.error(`❌ Error capturing screenshot: ${error}`); + } + } + + private logMessage( + message: string | string[], + level: "info" | "error" = "info", + ): void { + if (level === "info") { + logger.info(message); + } else { + logger.error(message); + } + } + + async navigateTo(url: string): Promise { + try { + await this.page.goto(url); + this.logMessage(`✅ Navigated to ${url}`); + } catch (error) { + const errorMsg = `❌ Failed to navigate to ${url}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("navigateTo"); + throw new Error(errorMsg); + } + } + + async navigateBack(): Promise { + try { + await this.page.goBack(); + this.logMessage(`✅ Navigated back`); + } catch (error) { + const errorMsg = `❌ Failed to navigate back`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("navigateBack"); + throw new Error(errorMsg); + } + } + + async navigateForward(): Promise { + try { + await this.page.goForward(); + this.logMessage(`✅ Navigated forward`); + } catch (error) { + const errorMsg = `❌ Failed to navigate forward`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("navigateForward"); + throw new Error(errorMsg); + } + } + + async verifyContainsUrl( + url: string, + isPartialUrl: boolean = false, + timeout: number = 20000, + ): Promise { + try { + await this.page.waitForLoadState("load", { timeout: timeout }); + if (!isPartialUrl) { + await expect(this.page).toHaveURL(url); + } else { + expect(this.page.url()).toContain(url); + } + this.logMessage(`✅ Verified URL contains: "${url}"`); + } catch (error) { + const errorMsg = `❌ Failed to verify URL contains: "${url}" within ${timeout}ms`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyContainsUrl"); + throw new Error(errorMsg); + } + } + + async verifyPageTitle(title: string, timeout: number = 20000): Promise { + try { + await this.page.waitForLoadState("load", { timeout: timeout }); + const pageTitle = await this.page.title(); + expect(pageTitle).toContain(title); + this.logMessage(`✅ Verified page title contains: "${title}"`); + } catch (error) { + const errorMsg = `❌ Failed to verify page title contains: "${title}" within ${timeout}ms`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyPageTitle"); + throw new Error(errorMsg); + } + } + + async clickOnElement( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout: timeout }); + await this.page.isVisible(identifier, { timeout: timeout }); + this.logMessage(`✅ Element is visible: ${identifier}`); + await this.page.click(identifier); + this.logMessage(`✅ Clicked on element: ${identifier}`); + } catch (error) { + const errorMsg = `❌ Failed to click on element: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("clickOnElement"); + throw new Error(errorMsg); + } + } + + async verifyElementIsVisible( + identifier: string | string[], + timeout: number = 10000, + isText: boolean = false, + ): Promise { + const identifiers = Array.isArray(identifier) ? identifier : [identifier]; + + try { + await Promise.all( + identifiers.map(async (id) => { + const element = isText + ? this.page.getByText(id) + : this.page.locator(id); + + await expect(element).toBeVisible({ timeout }); + + this.logMessage(`✅ Element is visible: ${id}`); + }), + ); + } catch (error) { + const errorMsg = `❌ One or more elements are not visible: ${JSON.stringify( + identifier, + )}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementIsVisible"); + throw new Error( + `${errorMsg} | Reason: ${ + error instanceof Error ? error.message : error + }`, + ); + } + } + + async validateElementInvisibility( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + const locator = this.page.locator(identifier); + + await expect(locator).not.toBeVisible({ timeout }); + + this.logMessage(`✅ Element is invisible as expected: ${identifier}`); + } catch (error) { + const errorMsg = `❌ Element "${identifier}" is visible when it should be invisible.`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("validateElementInvisibility"); + throw new Error(errorMsg); + } + } + + async verifyElementIsScrolledIntoView( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + const elementHandle = await this.page.waitForSelector(identifier, { + timeout, + }); + + await this.wait(1); + + const isInViewport = await elementHandle.evaluate((el) => { + const rect = el.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.bottom <= + (window.innerHeight || document.documentElement.clientHeight) + ); + }); + + if (!isInViewport) { + const errorMsg = `Element is not in viewport after scroll: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure( + "verifyElementIsScrolledIntoView", + ); + throw new Error(errorMsg); + } + + this.logMessage(`✅ Element is scrolled into view: ${identifier}`); + } catch (error) { + const errorMsg = `❌ Failed to verify scroll into view for: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementIsScrolledIntoView"); + throw new Error(errorMsg); + } + } + + async validateElementDimensions( + identifier: string, + expectedWidth: number, + expectedHeight: number, + timeout: number = 10000, + ): Promise { + try { + const element = this.page.locator(identifier); + await expect(element).toBeVisible({ timeout }); + + const box = await element.boundingBox(); + if (!box) { + throw new Error(`Could not retrieve dimensions for: ${identifier}`); + } + + const { width, height } = box; + + if ( + Math.round(width) !== expectedWidth || + Math.round(height) !== expectedHeight + ) { + throw new Error( + `Dimension mismatch for ${identifier} - Expected: ${expectedWidth}x${expectedHeight}, Got: ${Math.round( + width, + )}x${Math.round(height)}`, + ); + } + + this.logMessage( + `✅ Element dimensions validated: ${identifier} - ${Math.round( + width, + )}x${Math.round(height)}`, + ); + } catch (error) { + const errorMsg = `❌ Failed to validate dimensions for: ${identifier}`; + this.logMessage(`${errorMsg} - ${error}`, "error"); + await this.captureScreenshotOnFailure("validateElementDimensions"); + throw new Error(errorMsg); + } + } + + async verifyElementPlaceholder( + identifier: string, + expectedPlaceholder: string, + timeout: number = 10000, + childSelector?: string, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + let target = this.page.locator(identifier); + if (childSelector) { + target = target.locator(childSelector); + await target.first().waitFor({ timeout }); + } + + const actualPlaceholder = + (await target.getAttribute("placeholder"))?.trim() ?? ""; + + if (actualPlaceholder !== expectedPlaceholder.trim()) { + const errorTarget = childSelector + ? `${identifier} > ${childSelector}` + : identifier; + + const errorMsg = `❌ Placeholder mismatch for "${errorTarget}" - Expected: "${expectedPlaceholder.trim()}", Actual: "${actualPlaceholder}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementPlaceholder"); + throw new Error(errorMsg); + } + + const successTarget = childSelector + ? `${identifier} > ${childSelector}` + : identifier; + + this.logMessage( + `✅ Placeholder of ${successTarget} is as expected: "${actualPlaceholder}"`, + ); + } catch (error) { + if (!(error instanceof Error)) throw error; + throw new Error(`verifyElementPlaceholder failed: ${error.message}`); + } + } + + async wait( + time: number, + Options: { + waitForSelector?: string; + waitForNetworkIdle?: boolean; + waitForLoadState?: "load" | "domcontentloaded" | "networkidle"; + } = {}, + ): Promise { + const { waitForSelector, waitForNetworkIdle, waitForLoadState } = Options; + + try { + await this.page.waitForTimeout(time * 1000); + if (waitForSelector) { + await this.page.waitForSelector(waitForSelector, { + state: "visible", + timeout: 10000, + }); + this.logMessage(`Waited for selector: ${waitForSelector}`); + } + + if (waitForNetworkIdle) { + await this.page.waitForLoadState("networkidle"); + this.logMessage(`Waited for network idle`); + } + if (waitForLoadState) { + await this.page.waitForLoadState(waitForLoadState); + this.logMessage(`Waited for load state: ${waitForLoadState}`); + } + } catch (error) { + const errorMsg = `❌ Failed to wait for ${time} seconds to match specified conditions`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("wait"); + throw new Error(errorMsg); + } + } + + async enterText( + identifier: string, + text: string, + childSelector?: string, + ): Promise { + try { + let target = this.page.locator(identifier); + + if (childSelector) { + target = target.locator(childSelector); + await target.first().waitFor(); + } + + await this.verifyElementIsVisible( + childSelector ? `${identifier} >> ${childSelector}` : identifier, + ); + await target.fill(""); + await target.fill(text); + + const targetName = childSelector + ? `${identifier} > ${childSelector}` + : identifier; + + this.logMessage(`✅ Typed "${text}" into element: ${targetName}`); + } catch (error) { + const targetName = childSelector + ? `${identifier} > ${childSelector}` + : identifier; + + const errorMsg = `❌ Failed to type text "${text}" into element: ${targetName}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("enterText"); + throw new Error(errorMsg); + } + } + + async validateAttribute( + identifier: string | string[], + attribute: string | "href" | "src" | "type", + expectedValue: string, + childSelector?: string, + timeout: number = 10000, + ): Promise { + const identifiers = Array.isArray(identifier) ? identifier : [identifier]; + + try { + await Promise.all( + identifiers.map(async (id) => { + await this.page.waitForSelector(id, { timeout }); + + let target = this.page.locator(id); + if (childSelector) { + target = target.locator(childSelector); + await target.first().waitFor({ timeout }); + } + + const attributeValue = await target.getAttribute(attribute); + expect(attributeValue).toBe(expectedValue); + + const logTarget = childSelector ? `${id} > ${childSelector}` : id; + this.logMessage( + `✅ Attribute "${attribute}" of ${logTarget} is as expected: ${attributeValue}`, + ); + }), + ); + } catch (error) { + const errorMsg = `❌ Failed to validate attribute "${attribute}" for one or more elements`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("validateAttribute"); + throw new Error( + `${errorMsg} | Reason: ${ + error instanceof Error ? error.message : error + }`, + ); + } + } + + async verifyElementText( + identifier: string, + expectedText: string | string[], + childSelector?: string, + placeholderText: "-" | "Not provided" = "-", + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + let target = this.page.locator(identifier); + if (childSelector) { + target = target.locator(childSelector); + await target.first().waitFor({ timeout }); + } + + let textContent: string; + + if (!childSelector) { + // Wait for the field value to change from "placeholderText" to something valid + await expect(async () => { + textContent = (await target.textContent())?.trim() ?? ""; + expect(textContent && textContent !== placeholderText).toBeTruthy(); + }).toPass({ timeout }); + } + + const errorTarget = childSelector + ? `${identifier} > ${childSelector}` + : identifier; + + if (typeof expectedText === "string") { + textContent = (await target.textContent())?.trim() ?? ""; + const trimmedExpected = expectedText.trim(); + if (textContent !== trimmedExpected) { + const errorMsg = `❌ Text mismatch for element "${errorTarget}" - Expected: "${trimmedExpected}", Actual: "${textContent}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementText"); + throw new Error(errorMsg); + } + } else { + textContent = (await target.textContent())?.trim() ?? ""; + const match = expectedText.map((e) => e.trim()).includes(textContent); + if (!match) { + const expectedStr = expectedText + .map((e) => `"${e.trim()}"`) + .join(" | "); + const errorMsg = `❌ Text mismatch for element "${errorTarget}" - Expected one of: ${expectedStr}, Actual: "${textContent}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementText"); + throw new Error(errorMsg); + } + } + + this.logMessage( + `✅ Text of ${errorTarget} is as expected: "${textContent}"`, + ); + } catch (error) { + if (!(error instanceof Error)) throw error; + throw new Error(`❌ verifyElementText failed: ${error.message}`); + } + } + + async verifyElementTextDoesNotMatch( + identifier: string, + unexpectedText: string, + placeholderText: "-" | "Not provided" = "-", + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + let target = this.page.locator(identifier); + + await expect(async () => { + const text = (await target.textContent())?.trim(); + expect(text && text !== placeholderText).toBeTruthy(); + }).toPass({ timeout }); + + const actualText = (await target.textContent())?.trim() ?? ""; + const trimmedUnexpected = unexpectedText.trim(); + + if (actualText === trimmedUnexpected) { + const errorMsg = `❌ Unexpected match: Element "${actualText}" should NOT have text "${trimmedUnexpected}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementTextDoesNotMatch"); + throw new Error(errorMsg); + } + + this.logMessage( + `✅ Element "${actualText}" does NOT match unexpected text: "${trimmedUnexpected}"`, + ); + } catch (error) { + if (!(error instanceof Error)) throw error; + throw new Error( + `❌ verifyElementTextDoesNotMatch failed: ${error.message}`, + ); + } + } + + async verifyElementTextContains( + identifier: string | string[], + expectedText: string | string[], + timeout: number = 10000, + ): Promise { + const identifiers = Array.isArray(identifier) ? identifier : [identifier]; + const expectedTexts = Array.isArray(expectedText) + ? expectedText + : [expectedText]; + + if (identifiers.length !== expectedTexts.length) { + throw new Error( + "❌ The number of identifiers must match the number of expected text values.", + ); + } + + await Promise.all( + identifiers.map(async (id, i) => { + const expected = expectedTexts[i]; + try { + await this.page.waitForSelector(id, { timeout }); + const element = this.page.locator(id); + const textContent = await element.textContent(); + + expect(textContent?.trim()).toContain(expected.trim()); + + this.logMessage( + `✅ Text of ${id} contains expected text: "${expected}"`, + ); + } catch (error) { + const errorMsg = `❌ Failed to verify text contains for: ${id}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementTextContains"); + throw new Error( + `${errorMsg} | Reason: ${ + error instanceof Error ? error.message : error + }`, + ); + } + }), + ); + } + + async validateTextAndClickOnElement( + identifier: string, + expectedText: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.locator(identifier).focus(); + await expect(this.page.locator(identifier)).toBeVisible(); + const actualText = await this.page.locator(identifier).textContent(); + + if (actualText && actualText.trim() === expectedText.trim()) { + this.logMessage( + `✅ Text of ${identifier} is as expected: "${actualText}"`, + ); + await this.page.locator(identifier).click(); + this.logMessage(`✅ Clicked on element: ${identifier}`); + } else { + const errorMsg = `❌ Text mismatch for element "${identifier}" - Expected: "${expectedText}", Actual: "${actualText}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("validateTextAndClickOnElement"); + throw new Error(errorMsg); + } + } catch (error) { + throw error; + } + } + + async verifyCheckboxDataState( + identifier: string, + expectedState: "checked" | "unchecked", + timeout: number = 10000, + ): Promise { + try { + if (expectedState !== "checked" && expectedState !== "unchecked") { + throw new Error(`Invalid expectedState value: "${expectedState}"`); + } + + await this.page.waitForSelector(identifier, { timeout }); + const element = this.page.locator(identifier); + const actualState = await element.getAttribute("data-state"); + + if (actualState !== expectedState) { + throw new Error( + `❌ Expected data-state to be "${expectedState}", but found "${actualState}"`, + ); + } + + this.logMessage( + `✅ data-state of ${identifier} is correctly "${actualState}"`, + ); + } catch (error) { + const errorMsg = `❌ Failed to verify data-state for: ${identifier} — Expected: "${expectedState}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyCheckboxDataState"); + throw new Error(errorMsg); + } + } + + async verifyInputValue( + identifier: string, + expectedValue: string, + childSelector?: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + const target = this.page.locator(identifier); + const element = childSelector ? target.locator(childSelector) : target; + const actualValue = await element.inputValue(); + + if (actualValue !== expectedValue) { + throw new Error( + `Expected input value to be "${expectedValue}", but found "${actualValue}"`, + ); + } + + this.logMessage( + `✅ Input value of ${ + childSelector ? `${identifier} >> ${childSelector}` : identifier + } is as expected: "${actualValue}"`, + ); + } catch (error) { + const target = childSelector + ? `${identifier} >> ${childSelector}` + : identifier; + const errorMsg = `❌ Failed to verify input value for: ${target}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyInputValue"); + throw new Error(errorMsg); + } + } + + async verifyInputIsDisable( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const element = this.page.locator(identifier); + await expect(element).toBeDisabled(); + + this.logMessage(`✅ Input field is disable.`); + } catch (error) { + const errorMsg = `❌ Failed to verify input field disable.`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyInputIsDisable"); + throw new Error(errorMsg); + } + } + + async clearInputField( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + const input = this.page.locator(identifier); + await input.fill(""); // Clears the input + + this.logMessage(`✅ Cleared input field: ${identifier}`); + } catch (error) { + this.logMessage(`❌ Failed to clear input field: ${identifier}`, "error"); + await this.captureScreenshotOnFailure("clearInputField"); + throw new Error( + `❌ Input field clearing failed | Reason: ${error.message}`, + ); + } + } + + async verifyButtonIsDisable( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const element = this.page.locator(identifier); + await expect(element).toBeDisabled(); + + this.logMessage(`✅ Button is disable.`); + } catch (error) { + const errorMsg = `❌ Failed to verify button is disable.`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyButtonIsDisable"); + throw new Error(errorMsg); + } + } + + async verifyElementToHaveCSSProperty( + identifier: string | string[], + property: string, + expectedValue: string, + ): Promise { + const identifiers = Array.isArray(identifier) ? identifier : [identifier]; + + for (const id of identifiers) { + try { + await this.page.waitForSelector(id, { state: "visible" }); + const element = this.page.locator(id); + + try { + await expect(element).toHaveCSS(property, expectedValue, { + timeout: 3000, + }); + this.logMessage( + `✅ CSS property "${property}" of ${id} is as expected: "${expectedValue}"`, + ); + + continue; + } catch { + const actualValue = await element.evaluate( + (el, prop) => window.getComputedStyle(el).getPropertyValue(prop), + property, + ); + + if (actualValue.trim() !== expectedValue.trim()) { + const errorMsg = `❌ Expected CSS property "${property}" to be "${expectedValue}", but found "${actualValue}" for "${id}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure( + "verifyElementToHaveCSSProperty", + ); + throw new Error(errorMsg); + } + + this.logMessage( + `✅ CSS property "${property}" of ${id} is as expected (fallback method): "${actualValue.trim()}"`, + ); + } + } catch (error) { + const errorMsg = `❌ Failed to verify CSS property "${property}" for: ${id} | Reason: ${ + error instanceof Error ? error.message : error + }`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("verifyElementToHaveCSSProperty"); + throw error instanceof Error ? error : new Error(String(error)); + } + } + } + + async scrollToElement( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const targetElement = this.page.locator(identifier); + await targetElement.scrollIntoViewIfNeeded(); + await this.wait(1); + await this.page.isVisible(identifier); + this.logMessage(` ✅Scrolled to element: ${identifier}`); + } catch (error) { + const errorMsg = ` ❌ Failed to scroll to element: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("scrollToElement"); + throw new Error(errorMsg); + } + } + + async verifyLinksTexts( + identifier: string, + expectedTexts: string | string[], + ): Promise { + try { + const elements = this.page.locator(identifier); + const count = await elements.count(); + + const textsArray = Array.isArray(expectedTexts) + ? expectedTexts + : new Array(count).fill(expectedTexts); + + if (textsArray.length !== count) { + const msg = `❌ Expected ${textsArray.length} texts but found ${count} elements for "${identifier}"`; + this.logMessage(msg, "error"); + await this.captureScreenshotOnFailure("verifyLinksTexts"); + throw new Error(msg); + } + + for (let i = 0; i < count; i++) { + const element = elements.nth(i); + + const isVisible = await element.isVisible(); + if (!isVisible) { + const visibilityErrorMsg = `❌ Element at index ${i} for "${identifier}" is not visible.`; + this.logMessage(visibilityErrorMsg, "error"); + await this.captureScreenshotOnFailure( + `verifyLinksTexts_visibility_${i}`, + ); + throw new Error(visibilityErrorMsg); + } + + const actualText = (await element.innerText())?.trim(); + const expectedText = textsArray[i]?.trim(); + + if (actualText !== expectedText) { + const errorMsg = `❌ Mismatch at index ${i} for "${identifier}" - Expected: "${expectedText}", Actual: "${actualText}"`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure(`verifyLinksTexts_index_${i}`); + throw new Error(errorMsg); + } + + this.logMessage(`✅ Text match at index ${i}: "${actualText}"`); + } + + this.logMessage( + `✅ All link texts verified successfully for: ${identifier}`, + ); + } catch (error) { + const errorMessage = `❌ Failed to verify link texts for ${identifier} | Reason: ${ + error instanceof Error ? error.message : error + }`; + this.logMessage(errorMessage, "error"); + await this.captureScreenshotOnFailure("verifyLinksTexts_final"); + throw new Error(errorMessage); + } + } + + async validateNumericValueFromElement(identifier: string): Promise { + try { + const locator = this.page.locator(identifier); + await expect(locator).toBeVisible(); + + const rawText = await locator.textContent(); + if (!rawText) { + throw new Error(`Element "${identifier}" has no text content.`); + } + const cleaned = rawText.replace(/[^\d-]/g, "").trim(); + + this.logMessage( + `✅ Cleaned numeric text from "${identifier}": "${cleaned}"`, + ); + + const parsedValue = Number(cleaned); + if (isNaN(parsedValue)) { + const errorMsg = `❌ Failed to parse number from cleaned string: "${cleaned}"`; + this.logMessage(errorMsg, "error"); + throw new Error(errorMsg); + } + + const isInteger = Number.isInteger(parsedValue); + const isFloat = !isInteger; + + this.logMessage( + `✅ Parsed number: ${parsedValue} (Detected as ${ + isInteger ? "Integer" : "Float" + })`, + ); + + if (isInteger) { + const integerRegex = /^-?\d+$/; + if (!integerRegex.test(cleaned)) { + const errorMsg = `❌ Integer regex validation failed for "${cleaned}"`; + this.logMessage(errorMsg, "error"); + throw new Error(errorMsg); + } + this.logMessage(`✅ Integer regex validation passed for "${cleaned}"`); + } else { + const floatRegex = /^-?\d+\.\d+$/; + if (!floatRegex.test(cleaned)) { + const errorMsg = `❌ Float regex validation failed for "${cleaned}"`; + this.logMessage(errorMsg, "error"); + throw new Error(errorMsg); + } + this.logMessage(`✅ Float regex validation passed for "${cleaned}"`); + } + } catch (error) { + const errorMsg = `❌ Failed to validate numeric value from "${identifier}"`; + this.logMessage( + errorMsg + ` — ${error instanceof Error ? error.message : error}`, + "error", + ); + await this.captureScreenshotOnFailure("validateNumericValueFromElement"); + throw new Error(errorMsg); + } + } + + async validateTitleByRegex( + identifier: string, + text?: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + const notifications = await this.page + .locator(identifier) + .allTextContents(); + + if (notifications.length !== 0) { + this.logMessage(`✅ ${notifications.length} Notifications found.`); + } else { + this.logMessage("✅ Notification not found."); + } + + const titleRegex = /^[\w\s\-.']+$/; + + for (const [index, title] of notifications.entries()) { + if (!titleRegex.test(title)) { + throw new Error( + `❌ Invalid notification title at index ${index}: "${title}"`, + ); + } + } + + if (text !== undefined) { + const matchFound = notifications.includes(text); + if (!matchFound) { + throw new Error( + `❌ Provided text "${text}" not found in notifications: ${JSON.stringify( + notifications, + )}`, + ); + } + this.logMessage( + `✅ Text "${text}" was found in notification titles: ${notifications}.`, + ); + } + + this.logMessage( + `✅ All ${notifications.length} notification titles matched the expected format.`, + ); + } catch (error) { + this.logMessage("❌ Failed to validate notification titles.", "error"); + await this.captureScreenshotOnFailure("validateNotificationTitles"); + throw new Error( + `❌ Notification title validation failed | Reason: ${error.message}`, + ); + } + } + + async validateNotificationTexts( + identifier: string, + matchTexts: string[], + withoutMatchTexts: string[], + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + + const texts = await this.page.locator(identifier).allTextContents(); + + if (texts.length !== 0) { + this.logMessage(`✅ ${texts.length} texts found.`); + } else { + this.logMessage("✅ text not found."); + } + + const matchArray: string[] = []; + const withoutMatchArray: string[] = []; + + for (const entry of texts) { + const parts = entry.split("→").map((part) => part.trim()); + + if (parts.length !== 2) { + throw new Error( + `❌ Invalid format (must contain exactly one '→'): "${entry}"`, + ); + } + + if (parts[0] === parts[1]) { + throw new Error(`❌ ${parts[0]} and ${parts[1]} was same value.`); + } + + if (matchTexts.includes(parts[0])) { + matchArray.push(parts[0]); + } else { + throw new Error(`❌ ${parts[0]} was not found in ${matchTexts}.`); + } + + if (withoutMatchTexts.includes(parts[1])) { + withoutMatchArray.push(parts[1]); + } else { + throw new Error( + `❌ ${parts[1]} was not found in ${withoutMatchTexts}.`, + ); + } + } + } catch (error) { + this.logMessage("❌ Failed to validate notification texts", "error"); + await this.captureScreenshotOnFailure("validateNotificationTexts"); + throw new Error( + `❌ Notification texts validation failed | Reason: ${error.message}`, + ); + } + } + + async validateNoDataFound( + identifier: string, + fallbackIdentifier: string, + expectedText: string, + timeout: number = 10000, + ): Promise { + try { + const identifierLocator = this.page.locator(identifier); + const identifierCount = await identifierLocator.count(); + + if (identifierCount > 0) { + const fullText = (await identifierLocator.allTextContents()) + .join(" ") + .trim(); + + if (fullText.includes(expectedText)) { + this.logMessage( + `✅ "No data found" matched expected text: "${expectedText}"`, + ); + } else { + const msg = `❌ "No data found" but text did not match.\nFound: "${fullText}"\nExpected: "${expectedText}"`; + this.logMessage(msg, "error"); + await this.captureScreenshotOnFailure("validateNoDataFound"); + throw new Error(msg); + } + return; + } + + // Identifier not found — wait briefly for fallback + try { + await this.page.waitForSelector(fallbackIdentifier, { timeout }); + const fallbackCount = await this.page + .locator(fallbackIdentifier) + .count(); + this.logMessage( + `⚠️ Identifier not found, but ${fallbackCount} fallback data(s) exist.`, + ); + return; + } catch { + const errorMsg = `❌ Neither "${identifier}" nor fallback data(s) found.`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("missingData"); + throw new Error(errorMsg); + } + } catch (error) { + const errorMsg = `❌ Failed during "no data found" validation: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("validateNoDataFound"); + throw error; + } + } + + async validateNameByRegex( + identifier: string, + mode: "FULL_NAME" | "FIRST_NAME" | "LAST_NAME", + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const fieldLocator = this.page.locator(identifier); + const placeholderText = "-"; + + // Wait until value is fetched from backend and is no longer a placeholder + await expect(async () => { + const text = (await fieldLocator.textContent())?.trim(); + expect(text && text !== placeholderText).toBeTruthy(); + }).toPass({ timeout }); + + const name = (await fieldLocator.textContent())?.trim() as string; + + let regex: RegExp; + let label: string; + + switch (mode) { + case "FULL_NAME": + regex = /^[A-Za-zÀ-ÖØ-öø-ÿ'’-]+(?: [A-Za-zÀ-ÖØ-öø-ÿ'’-]+)+$/; + label = "Full Name"; + break; + case "FIRST_NAME": + regex = /^[A-Za-zÀ-ÖØ-öø-ÿ'’-]+(?: [A-Za-zÀ-ÖØ-öø-ÿ'’-]+)?$/; + label = "First Name"; + break; + case "LAST_NAME": + regex = /^[A-Za-zÀ-ÖØ-öø-ÿ'’-]+(?: [A-Za-zÀ-ÖØ-öø-ÿ'’-]+)?$/; + label = "Last Name"; + break; + default: + throw new Error(`Unsupported validation mode: ${mode}`); + } + + if (regex.test(name)) { + this.logMessage(`✅ ${label} "${name}" matches the regex.`); + } else { + throw new Error(`❌ ${label} "${name}" does not match the regex.`); + } + } catch (error) { + this.logMessage(`❌ Failed to validate ${mode} name text`, "error"); + await this.captureScreenshotOnFailure("validateNameByRegex"); + throw new Error( + `❌ ${mode} name validation failed | Reason: ${error.message}`, + ); + } + } + + async validatePhoneNumberByRegex( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const phoneNumber = await this.page.locator(identifier).textContent(); + const trimmed = phoneNumber?.trim(); + + // Case 1: If the phone number is "Not provided" or empty + if (!trimmed || trimmed === "Not provided" || trimmed === "-") { + this.logMessage(`ℹ️ Phone number: Not provided`); + return; + } + + // Case 2: Validate phone number format + const regex = + /^(\+?\d{1,3}[-.\s]?)?(\(?\d{2,4}\)?[-.\s]?)?[\d]{3,4}[-.\s]?\d{3,4}$/; + + if (regex.test(trimmed)) { + this.logMessage(`✅ Phone number "${trimmed}" matches the regex.`); + } else { + throw new Error( + `❌ Phone number "${trimmed}" does not match the regex.`, + ); + } + } catch (error) { + this.logMessage("❌ Failed to validate phone number text", "error"); + await this.captureScreenshotOnFailure("validatePhoneNumberByRegex"); + throw new Error( + `❌ Phone number validation failed | Reason: ${error.message}`, + ); + } + } + + async validateUserBiographyByRegex( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const biography = await this.page.locator(identifier).textContent(); + const trimmed = biography?.trim(); + + if (trimmed === "Not provided") { + this.logMessage(`✅ Biography: Not provided`); + return; + } + + const regex = /^[A-Za-zÀ-ÖØ-öø-ÿ0-9,.'’!?"\s-]+$/; + + if (regex.test(trimmed as string)) { + this.logMessage(`✅ Biography "${trimmed}" matches the regex.`); + } else { + throw new Error(`❌ Biography "${trimmed}" does not match the regex.`); + } + } catch (error) { + this.logMessage("❌ Failed to validate biography text", "error"); + await this.captureScreenshotOnFailure("validateUserBiography"); + throw new Error( + `❌ Biography validation failed | Reason: ${error.message}`, + ); + } + } + + async hoverOnElement( + identifier: string, + timeout: number = 10000, + ): Promise { + try { + await this.page.waitForSelector(identifier, { timeout }); + const isVisible = await this.page.isVisible(identifier, { timeout }); + if (!isVisible) { + throw new Error(`Element not visible: ${identifier}`); + } + this.logMessage(`✅ Element is visible: ${identifier}`); + await this.page.hover(identifier); + this.logMessage(`✅ Hovered over element: ${identifier}`); + } catch (error) { + const errorMsg = `❌ Failed to hover over element: ${identifier}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("hoverOnElement"); + throw new Error(errorMsg); + } + } + + async waitForMilliseconds(ms: number): Promise { + await new Promise((resolve) => setTimeout(resolve, ms)); + const message = `Waited for ${ms} milliseconds.`; + this.logMessage(message); + } + + async selectDropdownOptionByText( + identifier: string, + value: string, + timeout = 10000, + ): Promise { + try { + this.logMessage( + `✅ Selecting value "${value}" from dropdown with button: "${identifier}"`, + ); + + const button = this.page.locator(identifier); + await button.waitFor({ state: "visible", timeout }); + + const state = await button.getAttribute("data-state"); + if (state !== "open") { + await button.click(); + this.logMessage(`✅ Clicked dropdown button to open`); + } + + // Find all visible options and filter for exact text match + const allOptions = this.page.locator( + '[role="option"], [data-selectable="true"]', + ); // Adjust selector as needed for your app + const exactMatchOption = allOptions.filter({ + hasText: value, + }); + + await exactMatchOption.first().waitFor({ state: "visible", timeout }); + await exactMatchOption.first().click(); + + this.logMessage(`✅ Selected option "${value}" successfully`); + } catch (error) { + const errorMsg = `❌ Failed to select "${value}" from dropdown "${identifier}": ${error.message}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("selectDropdownOptionByText"); + throw new Error(errorMsg); + } + } + + async waitUntilElementIsInvisible( + identifier: string, + timeout: number = 10000, + ): Promise { + const locator = this.page.locator(identifier); + try { + await expect(async () => { + const isVisible = await locator.isVisible(); + expect(isVisible).toBeFalsy(); + }).toPass({ timeout }); + + this.logMessage(`✅ Element is invisible as expected: ${identifier}`); + } catch { + const errorMsg = `❌ Element "${identifier}" is visible when it should be invisible.`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("validateElementInvisibility"); + throw new Error(errorMsg); + } + } + + async selectAllMatchingDropdownOptions( + identifier: string, + values: string[], + isOptionClickable: boolean = true, + timeout = 10000, + ): Promise { + try { + const button = this.page.locator(identifier); + await button.waitFor({ state: "visible", timeout }); + + // Open dropdown if not already open + const state = await button.getAttribute("data-state"); + if (state !== "open") { + await button.click(); + this.logMessage(`Clicked dropdown button to open`); + } + + const allOptions = this.page.locator( + 'css=[role="option"], [data-selectable="true"]', + ); + await allOptions.first().waitFor({ state: "visible", timeout }); + + const optionElements = await allOptions.all(); + const dropdownCount = optionElements.length; + + // Extract text content from dropdown options + const optionTexts = await Promise.all( + optionElements.map(async (option) => ({ + element: option, + text: (await option.textContent())?.trim() || "", + })), + ); + + const dropdownTexts = optionTexts.map(({ text }) => text).filter(Boolean); + const jsonValues = values.map((v) => v.trim()); + + // Compare sets + const missingInDropdown = jsonValues.filter( + (v) => !dropdownTexts.includes(v), + ); + const extraInDropdown = dropdownTexts.filter( + (v) => !jsonValues.includes(v), + ); + + if (missingInDropdown.length > 0 || extraInDropdown.length > 0) { + const errorDetails: string[] = []; + + if (missingInDropdown.length > 0) { + errorDetails.push( + `🔴 Missing in dropdown:\n- ${missingInDropdown.join("\n- ")}`, + ); + } + + if (extraInDropdown.length > 0) { + errorDetails.push( + `🟠 Extra in dropdown:\n- ${extraInDropdown.join("\n- ")}`, + ); + } + + const mismatchMessage = `❌ Dropdown mismatch:\n${errorDetails.join( + "\n\n", + )}`; + this.logMessage(mismatchMessage, "error"); + await this.captureScreenshotOnFailure("dropdownMismatch"); + + throw new Error(mismatchMessage); + } + + // All matched — proceed to selection + for (const text of jsonValues) { + const match = optionTexts.find((opt) => opt.text === text); + if (match) { + const dropdownState = await button.getAttribute("data-state"); + if (dropdownState !== "open") { + await button.click(); + this.logMessage(`Reopened dropdown for next selection`); + } + + if (isOptionClickable) { + await match.element.click(); // Uncomment if you want to click each option + this.logMessage(`✅ Selected: "${text}"`); + await this.page.waitForTimeout(300); // optional delay + } + } + } + } catch (error) { + const errorMsg = `❌ Failed to select dropdown values from "${identifier}": ${error.message}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("selectAllMatchingDropdownOptions"); + throw new Error(errorMsg); + } + } + + async verifyExpectedOptionInDropdown( + inputSelector: string, + timeout = 10000, + ): Promise { + try { + const input = this.page.locator(inputSelector); + await input.waitFor({ state: "visible", timeout }); + + const value = (await input.inputValue())?.trim(); + if (!value) throw new Error("❌ Input value is empty"); + + // Wait for dropdown to show results + const options = this.page.locator( + 'css=[role="option"], [data-selectable="true"]', + ); + await options + .first() + .waitFor({ state: "visible", timeout }) + .catch(() => {}); + + const optionElements = await options.all(); + const texts = await Promise.all( + optionElements.map(async (opt) => { + const text = await opt.textContent(); + return text?.trim(); + }), + ); + + // Case-insensitive match + const matchFound = texts.some( + (text) => text?.replace(/[\s/-]/g, "") === value, + ); + + if (!matchFound) { + // Check if a "No results found." message is shown + const noResults = await this.page + .locator("css=div[class='py-6 text-center text-sm']") + .isVisible() + .catch(() => false); + + const errorMessage = noResults + ? `❌ Expected value "${value}" not found — "${texts}" is shown.` + : `❌ Expected value "${value}" not found in options:\n- ${texts.join( + "\n- ", + )}`; + + this.logMessage(errorMessage, "error"); + await this.captureScreenshotOnFailure("dropdownOptionMissing"); + throw new Error(errorMessage); + } + + this.logMessage(`✅ Dropdown contains expected option: "${value}"`); + } catch (error) { + const msg = `❌ Failed to verify dropdown option for "${inputSelector}": ${error.message}`; + this.logMessage(msg, "error"); + await this.captureScreenshotOnFailure("verifyExpectedOptionInDropdown"); + throw new Error(msg); + } + } + + async pressKeyWithDelay(key: string): Promise { + try { + await this.page.keyboard.press(key); + this.logMessage(`✅ Pressed key: ${key}`); + } catch (error) { + const errorMsg = `❌ Failed to press key: ${key}`; + this.logMessage(errorMsg, "error"); + await this.captureScreenshotOnFailure("pressKeyWithDelay"); + throw new Error(errorMsg); + } + } +} // This is the class curly braces diff --git a/utilities/utils.ts b/utilities/utils.ts deleted file mode 100644 index d6dafcc..0000000 --- a/utilities/utils.ts +++ /dev/null @@ -1,357 +0,0 @@ -import { expect, Page } from "@playwright/test"; -import { allure } from "allure-playwright"; -import logger from "./logger"; // Import Winston Logger - -export class Utils { - private page: Page; - - constructor(page: Page) { - this.page = page; - } - - private async captureScreenshotOnFailure(testName: string): Promise { - try { - const screenshot = await this.page.screenshot(); - allure.attachment(`${testName} Screenshot`, screenshot, "image/png"); - logger.error(`${testName} failed. Screenshot captured.`); - } catch (error) { - logger.error("Error capturing screenshot:", error); - } - } - - private logMessage(message: string, level: "info" | "error" = "info"): void { - if (level === "info") { - logger.info(message); - } else { - logger.error(message); - } - } - - async navigateTo(url: string): Promise { - try { - await this.page.goto(url); - this.logMessage(`Navigated to ${url}`); - } catch (error) { - const errorMsg = `Failed to navigate to ${url}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("navigateTo"); - throw new Error(errorMsg); - } - } - - async clickOnElement(identifier: string): Promise { - try { - await this.page.isVisible(identifier); - await this.page.locator(identifier).click(); - this.logMessage(`Clicked on element with identifier: ${identifier}`); - } catch (error) { - const errorMsg = `Failed to click on element with identifier: ${identifier}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("clickOnElement"); - throw new Error(errorMsg); - } - } - - async mouseHover(identifier: string): Promise { - try { - await this.page.locator(identifier).hover(); - this.logMessage(`Hovered over element with identifier: ${identifier}`); - } catch (error) { - const errorMsg = `Failed to hover over element with identifier: ${identifier}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("mouseHover"); - throw new Error(errorMsg); - } - } - - async fillInputBox(identifier: string, text: string): Promise { - try { - await this.page.locator(identifier).fill(text); - this.logMessage(`Filled input box (${identifier}) with text: "${text}"`); - } catch (error) { - const errorMsg = `Failed to fill input box (${identifier}) with text: "${text}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("fillInputBox"); - throw new Error(errorMsg); - } - } - - async typeInputBox(identifier: string, text: string): Promise { - try { - const inputField = this.page.locator(identifier); - await inputField.fill(""); // Clear first - await inputField.fill(text); - this.logMessage(`Typed text: "${text}" in input box (${identifier})`); - } catch (error) { - const errorMsg = `Failed to type text: "${text}" in input box (${identifier})`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("typeInputBox"); - throw new Error(errorMsg); - } - } - - async dblClickOnElement(identifier: string): Promise { - try { - await this.page.locator(identifier).dblclick(); - this.logMessage(`Double-clicked on element (${identifier})`); - } catch (error) { - const errorMsg = `Failed to double-click on element (${identifier})`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("dblClickOnElement"); - throw new Error(errorMsg); - } - } - - async focusOnElement(identifier: string): Promise { - try { - await this.page.locator(identifier).focus(); - this.logMessage(`Focused on element (${identifier})`); - } catch (error) { - const errorMsg = `Failed to focus on element (${identifier})`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("focusOnElement"); - throw new Error(errorMsg); - } - } - - async verifyTitle(title: string): Promise { - try { - await expect(this.page).toHaveTitle(title); - this.logMessage(`Verified page title: "${title}"`); - } catch (error) { - const errorMsg = `Failed to verify title: "${title}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyTitle"); - throw new Error(errorMsg); - } - } - - async verifyContainsUrl(url: string, timeout: number = 20000): Promise { - try { - await this.page.waitForLoadState("load", { timeout: timeout }); - await expect(this.page).toHaveURL(url); - this.logMessage(`Verified URL contains: "${url}"`); - } catch (error) { - const errorMsg = `Failed to verify URL contains: "${url}" within ${timeout}ms`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyContainsUrl"); - throw new Error(errorMsg); - } - } - - async verifyContainText( - identifier: string, - expectedText: string, - ): Promise { - try { - await expect - .soft(this.page.locator(identifier)) - .toContainText(expectedText); - this.logMessage( - `Verified element with identifier ${identifier} contains text: "${expectedText}"`, - ); - } catch (error) { - const errorMsg = `Failed to verify element with identifier ${identifier} contains text: "${expectedText}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyContainText"); - throw new Error(errorMsg); - } - } - - async verifyToHaveValue( - identifier: string, - inputFieldText: string, - ): Promise { - try { - await expect - .soft(this.page.locator(identifier)) - .toHaveValue(inputFieldText); - this.logMessage( - `Verified element (${identifier}) has value: "${inputFieldText}"`, - ); - } catch (error) { - const errorMsg = `Failed to verify element (${identifier}) has value: "${inputFieldText}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyToHaveValue"); - throw new Error(errorMsg); - } - } - - async verifyToHaveCss( - identifier: string, - key: string, - value: string, - ): Promise { - try { - await expect.soft(this.page.locator(identifier)).toHaveCSS(key, value); - this.logMessage( - `Verified ${identifier} has CSS property "${key}": "${value}"`, - ); - } catch (error) { - const errorMsg = `Failed to verify ${identifier} has CSS property "${key}": "${value}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyToHaveCss"); - throw new Error(errorMsg); - } - } - - async verifyElementIsVisible(identifier: string): Promise { - try { - await expect.soft(this.page.locator(identifier)).toBeVisible(); - this.logMessage( - `Verified element with identifier ${identifier} is visible`, - ); - } catch (error) { - const errorMsg = `Failed to verify element with identifier ${identifier} is visible`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyElementIsVisible"); - throw new Error(errorMsg); - } - } - - async verifyLinksText( - identifier: string, - expectedTexts: string | string[], - ): Promise { - try { - const elements = this.page.locator(identifier); - const count = await elements.count(); - - const textsArray = Array.isArray(expectedTexts) - ? expectedTexts - : new Array(count).fill(expectedTexts); - - if (textsArray.length !== count) { - throw new Error( - `Number of expected texts does not match the number of elements`, - ); - } - - for (let i = 0; i < count; i++) { - const text = await elements.nth(i).innerText(); - expect.soft(text).toBe(textsArray[i]); - } - - this.logMessage(`Verified link texts for ${identifier}`); - } catch (error) { - const errorMsg = `Failed to verify link texts for ${identifier}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("verifyLinksText"); - throw new Error(errorMsg); - } - } - - async validateAndClick( - identifier: string, - expectedText: string, - ): Promise { - try { - await this.page.locator(identifier).focus(); - await expect.soft(this.page.locator(identifier)).toBeVisible(); - const actualText = await this.page.locator(identifier).textContent(); - - if (actualText && actualText.trim() === expectedText) { - await this.page.locator(identifier).click(); - this.logMessage( - `Validated and clicked on ${identifier} with expected text "${expectedText}"`, - ); - } else { - const errorMsg = `Text mismatch on ${identifier}. Expected: "${expectedText}", Found: "${actualText}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("validateAndClick"); - throw new Error(errorMsg); - } - } catch (error) { - throw error; - } - } - - async validateButtonAttribute( - identifier: string, - hrefAttribute: string, - ): Promise { - try { - const button = this.page.locator(identifier); - await expect(button).toBeVisible(); - const hrefValue = await button.getAttribute("href"); - expect(hrefValue).toBe(hrefAttribute); - this.logMessage(`Verified ${identifier} has href: "${hrefValue}"`); - } catch (error) { - const errorMsg = `Failed to verify ${identifier} has href: "${hrefAttribute}"`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("validateButtonAttribute"); - throw new Error(errorMsg); - } - } - - async scrollAndClick(identifier: string): Promise { - try { - const targetElement = this.page.locator(identifier); - await targetElement.scrollIntoViewIfNeeded(); - await expect(targetElement).toBeVisible(); - await targetElement.click(); - this.logMessage(`Scrolled and clicked on ${identifier}`); - } catch (error) { - const errorMsg = `Failed to scroll and click ${identifier}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("scrollAndClick"); - throw new Error(errorMsg); - } - } - - async wait( - time: number, - options: { - waitForSelector?: string; - waitForNetworkIdle?: boolean; - waitForLoadState?: "load" | "domcontentloaded" | "networkidle"; - } = {}, - ): Promise { - const { waitForSelector, waitForNetworkIdle, waitForLoadState } = options; - - try { - await this.page.waitForTimeout(time * 1000); - - if (waitForSelector) { - await this.page.waitForSelector(waitForSelector, { - state: "visible", - timeout: time * 1000, - }); - this.logMessage(`Waited for selector: ${waitForSelector}`); - } - - if (waitForNetworkIdle) { - await this.page.waitForLoadState("networkidle", { - timeout: time * 1000, - }); - this.logMessage("Waited for network idle"); - } - - if (waitForLoadState) { - await this.page.waitForLoadState(waitForLoadState, { - timeout: time * 1000, - }); - this.logMessage(`Waited for page load state: ${waitForLoadState}`); - } - } catch (error) { - const errorMsg = "Failed to wait for the specified conditions"; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("wait"); - throw new Error(errorMsg); - } - } - - async clearInputField(identifier: string): Promise { - try { - const inputField = this.page.locator(identifier); - await expect(inputField).toBeVisible(); - await inputField.fill(""); - this.logMessage(`Cleared input field ${identifier}`); - } catch (error) { - const errorMsg = `Failed to clear input field ${identifier}`; - this.logMessage(errorMsg, "error"); - await this.captureScreenshotOnFailure("clearInputField"); - throw new Error(errorMsg); - } - } -} // This is the class curly braces diff --git a/utilities/valueProvider.ts b/utilities/valueProvider.ts index 3b00cdd..73cc64f 100644 --- a/utilities/valueProvider.ts +++ b/utilities/valueProvider.ts @@ -1,23 +1,26 @@ import * as fs from "fs"; import * as path from "path"; -interface LambdaData { +interface UserData { expectedTexts: string[]; - accountMenuTexts: string[]; + navbarTexts: string[]; + dashboardAllCardHeaderTexts: []; } export class ExpectedTextProvider { protected expectedTexts: string[]; - protected accountMenuTexts: string[]; + protected navbarTexts: string[]; + protected dashboardAllCardHeaderTexts: string[]; constructor() { - const data = this.loadLambdaData(); + const data = this.loadUserData(); this.expectedTexts = data.expectedTexts; - this.accountMenuTexts = data.accountMenuTexts; + this.navbarTexts = data.navbarTexts; + this.dashboardAllCardHeaderTexts = data.dashboardAllCardHeaderTexts; } - private loadLambdaData(): LambdaData { - const jsonFilePath = path.resolve(__dirname, "../testData/lambda.json"); + private loadUserData(): UserData { + const jsonFilePath = path.resolve(__dirname, "../testData/text.json"); try { const data = fs.readFileSync(jsonFilePath, "utf-8");