├── .github ├── java-maven-testng-test-action │ └── action.yml ├── publish-github-page-action │ └── action.yml └── workflows │ └── open-cart-functional-test.yml ├── .gitignore ├── README.md ├── changelog.md ├── github-pages └── index.html ├── pom.xml ├── readme-screenshots ├── EmailAFriend.png ├── Extent-Report-Screenshot.png ├── FaceBookXFrameOptions.png ├── github-action-workflow-execution.png ├── github-action-workflow-run.png └── sample-logs.PNG ├── selenium-grid-docker-compose.yml └── src ├── main ├── java │ ├── base │ │ └── PlaywrightFactory.java │ ├── pages │ │ ├── HomePage.java │ │ ├── LoginPage.java │ │ ├── ProductPage.java │ │ └── ShoppingCartPage.java │ └── utils │ │ ├── ExtentReporter.java │ │ └── TestProperties.java └── resources │ ├── config.properties │ ├── extent-report-config.xml │ └── log4j2.xml └── test ├── java ├── testUtils │ └── RetryAnalyzer.java └── tests │ ├── TS_01_VerifyOpenCartLoginTests.java │ ├── TS_02_VerifyAddProductToCartTests.java │ ├── TS_03_VerifyCheckoutCartTests.java │ ├── TS_04_VerifyProductsPageTest.java │ └── TestBase.java └── resources └── testrunners └── testng.xml /.github/java-maven-testng-test-action/action.yml: -------------------------------------------------------------------------------- 1 | name: Run Java Maven TestNG Test 2 | description: "Action to Run Maven Test" 3 | inputs: 4 | browser: 5 | description: "Browser" 6 | default: "chrome" 7 | headless: 8 | description: "Headless mode" 9 | default: "true" 10 | enableRecordVideo: 11 | description: "Enable Video Recording" 12 | default: "false" 13 | enableTracing: 14 | description: "Enable Tracing" 15 | default: "false" 16 | tests: 17 | description: "Mention specific test class or method to run (i.e., TestClass1, TestClass1#testMethod1)" 18 | selenium-grid-url: 19 | description: "Selenium Grid Nde URL" 20 | 21 | runs: 22 | using: "composite" 23 | steps: 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: "17" 28 | distribution: "temurin" 29 | cache: maven 30 | 31 | # This step is required to avoid failure of scripts before the browser binaries installed 32 | - name: Download playwright dependencies 33 | shell: bash 34 | run : mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install" 35 | 36 | - name: Run Test with Maven 37 | shell: bash 38 | run: SELENIUM_REMOTE_URL=${{inputs.selenium-grid-url}} mvn test --file pom.xml -Dbrowser=${{inputs.browser}} -Dheadless=${{inputs.headless}} -DenableRecordVideo=${{inputs.enableRecordVideo}} -DenableTracing=${{inputs.enableRecordVideo}} -Dtest=${{inputs.tests}} 39 | 40 | 41 | - name: Build Javadocs 42 | shell: bash 43 | run: mvn javadoc:javadoc 44 | if: always() 45 | 46 | - name: Publish Artifacts 47 | uses: actions/upload-artifact@v3 48 | if: always() 49 | with: 50 | name: test-results 51 | path: test-results/ 52 | -------------------------------------------------------------------------------- /.github/publish-github-page-action/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish Test Report to Github Pages 2 | description: "Publish the Test Report to the Github Pages" 3 | 4 | outputs: 5 | page_url: 6 | description: "Test Report Github page" 7 | value: ${{ steps.publish.outputs.page_url }} 8 | 9 | runs: 10 | using: "composite" 11 | 12 | steps: 13 | - name: Prepare Javadocs and Test Report for Github Pages 14 | shell: bash 15 | run: | 16 | scp -r target/site/apidocs github-pages/javadocs 17 | scp test-results/TestExecutionReport.html github-pages/TestExecutionReport.html 18 | 19 | - name: Setup Github Pages 20 | uses: actions/configure-pages@v2 21 | 22 | - name: Upload Test execution Extent Report HTML to Github Pages 23 | uses: actions/upload-pages-artifact@v1 24 | with: 25 | path: "github-pages/" 26 | 27 | - name: Publish HTML Report to Github Pages 28 | id: publish 29 | uses: actions/deploy-pages@v1 -------------------------------------------------------------------------------- /.github/workflows/open-cart-functional-test.yml: -------------------------------------------------------------------------------- 1 | name: Functional Test - Open cart UI 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | os: 7 | description: Select the OS 8 | type: choice 9 | required: false 10 | options: 11 | - "ubuntu-latest" 12 | - "windows-latest" 13 | - "macos-latest" 14 | default: "ubuntu-latest" 15 | 16 | browser: 17 | description: "Browser" 18 | type: choice 19 | options: 20 | - "chrome" 21 | - "chromium" 22 | - "edge" 23 | - "firefox" 24 | - "safari" 25 | default: "chrome" 26 | required: false 27 | 28 | selenium-grid-url: 29 | description: "Selenium Grid URL (Required for headed mode execution, leave blank for headless mode)" 30 | type: string 31 | required: false 32 | 33 | enableRecordVideo: 34 | description: "Enable Video Recording" 35 | type: choice 36 | options: 37 | - "true" 38 | - "false" 39 | default: "false" 40 | required: false 41 | 42 | enableTracing: 43 | description: "Enable Tracing" 44 | type: choice 45 | options: 46 | - "true" 47 | - "false" 48 | default: "false" 49 | required: false 50 | 51 | tests: 52 | description: "Mention specific test class or method to run (i.e., TestClass1, TestClass1#testMethod1)" 53 | type: string 54 | required: false 55 | 56 | permissions: 57 | contents: read 58 | pages: write 59 | id-token: write 60 | 61 | jobs: 62 | functional-test-execution: 63 | runs-on: ${{ github.event.inputs.os }} 64 | 65 | steps: 66 | - name: Checkout the Repository to Runner 67 | uses: actions/checkout@v3 68 | 69 | - name: Action - Run Maven Test 70 | uses: ./.github/java-maven-testng-test-action 71 | with: 72 | browser: ${{ github.event.inputs.browser }} 73 | headless: ${{ ! github.events.inputs.selenium-grid-url }} 74 | selenium-grid-url: ${{ github.events.inputs.selenium-grid-url}} 75 | enableTracing: ${{ github.event.inputs.enableTracing }} 76 | enableRecordVideo: ${{ github.event.inputs.enableRecordVideo }} 77 | tests: ${{ github.event.inputs.tests }} 78 | 79 | - name: Action - Publish Test Report to Github Pages 80 | uses: ./.github/publish-github-page-action 81 | id: report 82 | if: always() 83 | 84 | environment: 85 | name: github-pages 86 | url: ${{ steps.report.outputs.page_url }} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/ 3 | test-results/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Functional UI Automation Framework - Open Cart Website 2 | 3 | This UI Automation framework repository has some basic functional tests for [Open Cart UI Website](https://https://naveenautomationlabs.com/opencart/). It covers the login, add products to cart and checkout cart functionality. The reports of test execution published in [Github Page](https://nayeemjohny.github.io/open-cart-ui-automation-playwright/) when the tests are executed from Github Actions. 4 | 5 | This framework built with the following: 6 | 7 | | | | 8 | | ----------------- | --------------------- | 9 | | Language | **Java** | 10 | | Build Tool | **Maven** | 11 | | UI Framework | **Playwright** | 12 | | Testing Framework | **TestNG** | 13 | | Reporting | **ExtentReports** | 14 | | Logging | **Log4j** | 15 | | Design Pattern | **Page Object Model** | 16 | | CI | **Github Actions** | 17 | | Remote Execution | **Selenium Grid** | 18 | 19 | 20 | **Github page with javadoc and Test Execution Report can be found [here](https://nayeemjohny.github.io/open-cart-ui-automation-playwright/)** 21 | 22 | 23 | # 24 | ## Key Notes 25 | 26 | ### Framework Design 27 | - [PlaywrightFactory](./src/main/java/base/PlaywrightFactory.java) - Base class to create the playwright objects (Page, BrowserContext, Browser, Playwright) 28 | - Use the Test configuration to set up the browser and playwright Browser context with tracing, video recording, SessionState, View Port 29 | - This exposes only few public methods (createPage(), takeScreenshot, saveSessionState()) 30 | - **No ThreadLocal static variables** used for the playwright objects instead all are encapsulated in this class, only Page object is returned, still it supports the parallel execution, This has been improved the framework design. 31 | - [The pages package](./src/main/java/pages/) contains page objects and functional methods of each page 32 | - Login page - login page objects and login functional method 33 | - Home page - home page objects and add to cart functional method 34 | - Shopping Cart page - Cart page objects and checkout functionality method 35 | - The script can take screenshots of specific step and can save the SessionState using playwright feature. 36 | - Tried to reduce the static variables as much as possible. Only the methods, variables can be shared across all tests are created with static 37 | - Code has been analyzed for code smell, code duplication and best practices using **SonarLint** on fly as well as **SonarCloud** 38 | 39 | # 40 | ### Test Design 41 | - Each test in the [tests package](./src/test/java/tests/) is independent and complete. 42 | - [TestBase](./src/test/java/tests/TestBase.java) class uses the TestNG configuration annotations for set up and tear down. 43 | - **@BeforeSuite** : clean up results directory, Initialize the extent reports, logger and read test properties. 44 | - **@AfterSuite** : tear down method to write (flush) the results to extent reports and assert all the soft asserts. 45 | - **@BeforeMethod** : Start the playwright server, instantiate the page and navigate to the website. 46 | - **@AfterMethod** : Stop the tracing (if enabled), Take screenshots (if test not success) and close the page instance. 47 | - **@BeforeClass** : This method used in each Test class to create the ExtentTest for reporting. 48 | - For each Test new playwright server is launched which is isolated from other playwright instance. 49 | - The current test design supports the **parallel execution** of TestNG test, This has been achieved by reducing the scope of the variables and objects are used. 50 | - [TestRunner](./src/test/resources/testrunners/testng.xml) with the test class configuration. More test runners can be added here and same should be updated in the [pom.xml](pom.xml) surefire plugin. 51 | - The test [addMoreProductToCartAndCheckoutTest](./src/test/java/tests/TS_03_VerifyCheckoutCartTests.java) are designed to use the playwright feature **Storage State**. The previous login state is used in this test 52 | 53 | # 54 | ### Test Configuration 55 | - The test configuration such as URL, username, Base64 encoded password, flags to enable/disable the video recording , tracing and location to store test results and artifacts are provided in the [config.properties](./src/main/resources/config.properties) file. This properties can be override by runtime properties if provided. 56 | - To read and update the properties, single instance of [TestProperties](./src/main/java/utils/TestProperties.java) class is used throughout the entire test execution. This has removed the usage of static variable of Properties class and passing of this variable across methods and classes. 57 | 58 | # 59 | ### Test Execution 60 | - The tests can be executed from maven test command or individual TestNG test from local after cloning the repo. 61 | - sample maven commands 62 | ```command 63 | mvn clean test (OR) 64 | 65 | mvn clean test -DProperty=value (OR) 66 | 67 | SELENIUM_REMOTE_URL="http://localhost:4444/wd/hub" mvn clean test 68 | ``` 69 | # 70 | ### Logger 71 | - log4j2 logging framework is used. Logs were printed to console as well as saved to the file. The log configuration file with log pattern, Appenders is available at [src/main/resources/log4j2.xml](./src/main/resources/log4j2.xml). 72 | - Logger is designed to support parallel execution and the logs will be printed with the **Thread Id**. 73 | ![Sample-Log4j-logs](./readme-screenshots/sample-logs.PNG) 74 | 75 | # 76 | ### Reporting 77 | - [Extent Spark reporter](./src/main/java/utils/ExtentReporter.java) is used for test reports. Configuration (Theme, timestamp, report name, document title) for report is available at [src/main/resources/extent-report-config.xml](./src/main/resources/extent-report-config.xml) 78 | - Reports will be generated at the end of test execution. i.e, **@AfterSuite** 79 | - All test in a single class captured in single ExtentTest with multiple test modes 80 | - The system/environment variables in report are captured from the runtime/config properties. 81 | - For each test class (considered as scenario) one ExtentTest is created and for each test under the scenario class, the Extent testNode is created. As a result the report looks like below. 82 | ![Extent Report](./readme-screenshots/Extent-Report-Screenshot.png) 83 | 84 | # 85 | ### GitHub Actions 86 | - Workflow: workflow_dispatch is used in workflow, so the test can be executed triggered from actions ui 87 | By default, the test will be executed on Ubuntu runner with headless mode. 88 | 89 | - Few input parameters (os, browser, video recording, tracing, selenium grid (headed mode)) are exposed in Actions UI, which can be passed at runtime, to execute the test with different configuration. 90 | 91 | - [Main workflow file](./.github/workflows/open-cart-functional-test.yml) checkout the repository to runner host and uses the actions for maven test execution and GitHub page publish.. 92 | 93 | - [Run Maven Test](./.github/java-maven-testng-test-action/action.yml) action configure the Java and Maven in the runner host and then execute the test and prepare the test-results to be uploaded as artifact. 94 | 95 | - [Publish Report to Github Page ](./.github/publish-github-page-action/action.yml) action prepare the test report for publish, configure the github page and publish the report to the github page. 96 | 97 | ![Github Actions](./readme-screenshots/github-action-workflow-run.png) 98 | 99 | ![Github Actions](./readme-screenshots/github-action-workflow-execution.png) 100 | 101 | # 102 | ### Selenium Grid using Docker: 103 | 104 | * [docker-compose file](./selenium-grid-docker-compose.yml) to start the selenium grid with hub and node. It will pull latest images of hub, browsers, chrome, chromium, firefox and edge. Start the containers using docker-compose command and while triggering the test specify the grid node url in order the scripts to run on browsers with headed mode. 105 | 106 | # 107 | Please find the changelog for the latest updates [changelog](./changelog.md) 108 | 109 | # 110 | 111 | Prepared by 112 | 113 | Nayeem John 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## October 24 2022 2 | 3 | - **Playwright features covered:** 4 | - Frames, SelectOption, scrollIntoViewIfNeeded, hover, nested locator and locator Options (SetHas) and SetHasText()) 5 | - Added waitForLoadState() for page to load completely before retrieve the page title. Used isVisible, textContext methods 6 | - Handled the **Page popup** (Windows/Tab) opened 7 | 8 | - **Application feature test coverage:** 9 | - **Sort Products By Price**. Verified the sorting feature using **Java Map and Streams** 10 | - **Share Product** to multiple Social Platforms using **TestNG DataProvider** 11 | 12 | - **Others** 13 | - Updated logger configuration to use **MarkerPatternSelector** 14 | - Updated extentLog with marker to log messages with caller class and method 15 | - Updated closePage @AfterMethod to use the **testName** from **ExtentTest** instead of ITestResult from TestNG to save tracing 16 | - Used **RetryAnalyzer** to Retry failed test cases 17 | - Discovered the known issue of playwright **[issue-scrolling-to-frame-not-in-view](https://github.com/microsoft/playwright/issues/3166)** 18 | 19 | - **Application issues** 20 | - Product share feature (addthis toolbox) for facebook option in page view (not in hover option) fails to launch sometimes due to **X-Frame-Options** is set to Deny. 21 | - Facing above issue manually on first page load, after refresh this issue not appears. But in automation this issue persists even after refresh. 22 | 23 | - ![FaceBookXFrameOptions](./readme-screenshots/FaceBookXFrameOptions.png) 24 | 25 | - The share count is not increased for the **facebook & twitter option in page view**. 26 | - **EmailAFriend** option is still available as a service, even the option is no more supported. 27 | 28 | - ![EmailAFriend](./readme-screenshots/EmailAFriend.png) 29 | 30 | 31 | # 32 | ## Initial 33 | - This framework not covered entire playwright features. It covers only few features like Browser context options ( tracing, recording, session storage and view port) and elements handle methods (click(), fill() and waitFor()) 34 | - Added javadoc for classes and methods 35 | - Added github actions to deploy javadoc as github page -------------------------------------------------------------------------------- /github-pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Page Title 8 | 9 | 10 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | Test Execution Report 53 | 54 | 55 | Javadocs 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | pom.playwright 4 | opencart-ui-automation 5 | jar 6 | 1.0.0 7 | open-cart-ui-automation 8 | http://maven.apache.org 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | com.microsoft.playwright 17 | playwright 18 | 1.26.0 19 | 20 | 21 | org.testng 22 | testng 23 | 7.6.1 24 | 25 | 26 | org.apache.logging.log4j 27 | log4j-api 28 | 2.19.0 29 | 30 | 31 | org.apache.logging.log4j 32 | log4j-core 33 | 2.19.0 34 | 35 | 36 | com.aventstack 37 | extentreports 38 | 5.0.9 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | 1.18.24 44 | true 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-surefire-plugin 52 | 3.0.0-M7 53 | 54 | 55 | ./src/test/resources/testrunners/testng.xml 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-javadoc-plugin 62 | 3.4.1 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /readme-screenshots/EmailAFriend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/EmailAFriend.png -------------------------------------------------------------------------------- /readme-screenshots/Extent-Report-Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/Extent-Report-Screenshot.png -------------------------------------------------------------------------------- /readme-screenshots/FaceBookXFrameOptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/FaceBookXFrameOptions.png -------------------------------------------------------------------------------- /readme-screenshots/github-action-workflow-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/github-action-workflow-execution.png -------------------------------------------------------------------------------- /readme-screenshots/github-action-workflow-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/github-action-workflow-run.png -------------------------------------------------------------------------------- /readme-screenshots/sample-logs.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NayeemJohnY/open-cart-ui-automation-playwright/6a849a7bdcca36d94947a1a0dafe556b55be6d43/readme-screenshots/sample-logs.PNG -------------------------------------------------------------------------------- /selenium-grid-docker-compose.yml: -------------------------------------------------------------------------------- 1 | # To execute this docker-compose yml file use `docker-compose -f docker-compose-v3.yml up` 2 | # Add the `-d` flag at the end for detached execution 3 | # To stop the execution, hit Ctrl+C, and then `docker-compose -f docker-compose-v3.yml down` 4 | version: "3" 5 | services: 6 | chrome: 7 | image: selenium/node-chrome:latest 8 | shm_size: 2gb 9 | depends_on: 10 | - selenium-hub 11 | environment: 12 | - SE_EVENT_BUS_HOST=selenium-hub 13 | - SE_EVENT_BUS_PUBLISH_PORT=4442 14 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 15 | - SE_NODE_MAX_SESSIONS=3 16 | - SE_NODE_OVERRIDE_MAX_SESSIONS=true 17 | - SE_NODE_GRID_URL=http://localhost:4444 18 | 19 | chromium: 20 | image: seleniarm/node-chromium:latest 21 | shm_size: 2gb 22 | depends_on: 23 | - selenium-hub 24 | environment: 25 | - SE_EVENT_BUS_HOST=selenium-hub 26 | - SE_EVENT_BUS_PUBLISH_PORT=4442 27 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 28 | - SE_NODE_MAX_SESSIONS=3 29 | - SE_NODE_OVERRIDE_MAX_SESSIONS=true 30 | - SE_NODE_GRID_URL=http://localhost:4444 31 | 32 | edge: 33 | image: selenium/node-edge:latest 34 | shm_size: 2gb 35 | depends_on: 36 | - selenium-hub 37 | environment: 38 | - SE_EVENT_BUS_HOST=selenium-hub 39 | - SE_EVENT_BUS_PUBLISH_PORT=4442 40 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 41 | - SE_NODE_MAX_SESSIONS=3 42 | - SE_NODE_OVERRIDE_MAX_SESSIONS=true 43 | - SE_NODE_GRID_URL=http://localhost:4444 44 | 45 | firefox: 46 | image: selenium/node-firefox:latest 47 | shm_size: 2gb 48 | depends_on: 49 | - selenium-hub 50 | environment: 51 | - SE_EVENT_BUS_HOST=selenium-hub 52 | - SE_EVENT_BUS_PUBLISH_PORT=4442 53 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 54 | - SE_NODE_MAX_SESSIONS=3 55 | - SE_NODE_OVERRIDE_MAX_SESSIONS=true 56 | - SE_NODE_GRID_URL=http://localhost:4444 57 | 58 | selenium-hub: 59 | image: selenium/hub:latest 60 | container_name: selenium-hub 61 | ports: 62 | - "4442:4442" 63 | - "4443:4443" 64 | - "4444:4444" 65 | -------------------------------------------------------------------------------- /src/main/java/base/PlaywrightFactory.java: -------------------------------------------------------------------------------- 1 | package base; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | import java.util.Base64; 6 | 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | import com.microsoft.playwright.Browser; 11 | import com.microsoft.playwright.Browser.NewContextOptions; 12 | import com.microsoft.playwright.BrowserContext; 13 | import com.microsoft.playwright.BrowserType; 14 | import com.microsoft.playwright.BrowserType.LaunchOptions; 15 | import com.microsoft.playwright.Page; 16 | import com.microsoft.playwright.Playwright; 17 | import com.microsoft.playwright.Tracing; 18 | 19 | import utils.TestProperties; 20 | 21 | /** 22 | * The class PlaywrightFactory provides a constructor which starts the 23 | * playwright server. 24 | * It has private and public methods to create a playwright page. 25 | * 26 | * @author Nayeem John 27 | */ 28 | public class PlaywrightFactory { 29 | 30 | private static Logger log = LogManager.getLogger(); 31 | private Playwright playwright; 32 | private TestProperties testProperties; 33 | 34 | /** 35 | * Constructor to initialize the test properties and playwright server 36 | * 37 | * @param testProperties - {@link TestProperties} 38 | */ 39 | public PlaywrightFactory(TestProperties testProperties) { 40 | this.testProperties = testProperties; 41 | playwright = Playwright.create(); 42 | } 43 | 44 | /** 45 | * Method is to get playwright {@link Browser} instance of browser property in 46 | * config file with headless mode property 47 | * 48 | * @return Browser - Returns playwright {@link String} instance 49 | * @throws IllegalArgumentException - Throws Exception when no matching browser 50 | * is available for property 51 | */ 52 | private Browser getBrowser() throws IllegalArgumentException { 53 | String browserName = testProperties.getProperty("browser"); 54 | boolean headless = Boolean.parseBoolean(testProperties.getProperty("headless")); 55 | LaunchOptions launchOptions = new BrowserType.LaunchOptions().setHeadless(headless); 56 | BrowserType browserType; 57 | switch (browserName.toLowerCase()) { 58 | case "chromium": 59 | browserType = playwright.chromium(); 60 | break; 61 | case "firefox": 62 | browserType = playwright.firefox(); 63 | break; 64 | case "safari": 65 | browserType = playwright.webkit(); 66 | break; 67 | case "chrome": 68 | browserType = playwright.chromium(); 69 | launchOptions.setChannel("chrome"); 70 | break; 71 | case "edge": 72 | browserType = playwright.chromium(); 73 | launchOptions.setChannel("msedge"); 74 | break; 75 | default: 76 | String message = "Browser Name '" + browserName + "' specified in Invalid."; 77 | message += " Please specify one of the supported browsers [chromium, firefox, safari, chrome, edge]."; 78 | log.debug(message); 79 | throw new IllegalArgumentException(message); 80 | } 81 | log.info("Browser Selected for Test Execution '{}' with headless mode as '{}'", browserName, headless); 82 | return browserType.launch(launchOptions); 83 | } 84 | 85 | /** 86 | * Method to get the playwright {@link BrowserContext} with the video recording, 87 | * tracing. storage context and view port 88 | * These properties are set based on values on config properties 89 | * 90 | * @return BrowserContext - Returns playwright {@link BrowserContext} instance 91 | */ 92 | private BrowserContext getBrowserContext() { 93 | BrowserContext browserContext; 94 | Browser browser = getBrowser(); 95 | NewContextOptions newContextOptions = new Browser.NewContextOptions(); 96 | 97 | if (Boolean.parseBoolean(testProperties.getProperty("enableRecordVideo"))) { 98 | Path path = Paths.get(testProperties.getProperty("recordVideoDirectory")); 99 | newContextOptions.setRecordVideoDir(path); 100 | log.info("Browser Context - Video Recording is enabled at location '{}'", path.toAbsolutePath()); 101 | } 102 | 103 | int viewPortHeight = Integer.parseInt(testProperties.getProperty("viewPortHeight")); 104 | int viewPortWidth = Integer.parseInt(testProperties.getProperty("viewPortWidth")); 105 | newContextOptions.setViewportSize(viewPortWidth, viewPortHeight); 106 | log.info("Browser Context - Viewport Width '{}' and Height '{}'", viewPortWidth, viewPortHeight); 107 | 108 | if (Boolean.parseBoolean(testProperties.getProperty("useSessionState"))) { 109 | Path path = Paths.get(testProperties.getProperty("sessionState")); 110 | newContextOptions.setStorageStatePath(path); 111 | log.info("Browser Context - Used the Session Storage State at location '{}'", path.toAbsolutePath()); 112 | } 113 | 114 | browserContext = (browser.newContext(newContextOptions)); 115 | 116 | if (Boolean.parseBoolean(testProperties.getProperty("enableTracing"))) { 117 | browserContext.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true)); 118 | log.info("Browser Context - Tracing is enabled with Screenshots and Snapshots"); 119 | } 120 | return browserContext; 121 | } 122 | 123 | /** 124 | * Method to create a new playwright {@link Page} for the browser 125 | * 126 | * @return Page - Returns playwright {@link Page} instance or null if any 127 | * exception occurs while retrieving {@link BrowserContext} 128 | */ 129 | public Page createPage() { 130 | Page page = null; 131 | try { 132 | page = (getBrowserContext().newPage()); 133 | } catch (Exception e) { 134 | log.error("Unable to create Page : ", e); 135 | } 136 | return page; 137 | } 138 | 139 | /** 140 | * Method to save the session state from the {@link BrowserContext} in a file 141 | * provided in 'sessionState' property 142 | * 143 | * @param page - playwright {@link Page} instance 144 | * @param filename - {@link String} name of the file to store session state 145 | */ 146 | public static void saveSessionState(Page page, String filename) { 147 | page.context().storageState(new BrowserContext.StorageStateOptions() 148 | .setPath(Paths.get(filename))); 149 | } 150 | 151 | /** 152 | * Method to take screenshot of the {@link Page} 153 | * It saves the screenshots with file name of ${currentTimeMillis}.png 154 | * 155 | * @param page - playwright {@link Page} instance 156 | * @return String - Returns encoded {@link Base64} String of image 157 | */ 158 | public static String takeScreenshot(Page page) { 159 | String path = System.getProperty("user.dir") + "/test-results/screenshots/" + System.currentTimeMillis() 160 | + ".png"; 161 | 162 | byte[] buffer = page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get(path)).setFullPage(true)); 163 | String base64Path = Base64.getEncoder().encodeToString(buffer); 164 | 165 | log.debug("Screenshot is taken and saved at the location {}", path); 166 | return base64Path; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/pages/HomePage.java: -------------------------------------------------------------------------------- 1 | package pages; 2 | 3 | import static base.PlaywrightFactory.takeScreenshot; 4 | import static utils.ExtentReporter.extentLog; 5 | import static utils.ExtentReporter.extentLogWithScreenshot; 6 | 7 | import com.aventstack.extentreports.ExtentTest; 8 | import com.aventstack.extentreports.Status; 9 | import com.microsoft.playwright.Locator; 10 | import com.microsoft.playwright.Page; 11 | 12 | /** 13 | * Page Object Class for Home Page 14 | * 15 | * @author Nayeem John 16 | */ 17 | public class HomePage { 18 | 19 | private Page page; 20 | private ExtentTest extentTest; 21 | 22 | private String search = "input[name='search']"; 23 | private String searchIcon = "div#search button"; 24 | private String searchPageHeader = "div#content h1"; 25 | private String loginLink = "a:text('Login')"; 26 | private String myAccountLink = "a[title='My Account']"; 27 | private String productSearchResult = "div.product-thumb"; 28 | private String addToCartSelector = "text='Add to Cart'"; 29 | private String alertSelector = "div.alert"; 30 | private String productCaption = ".caption h4 a"; 31 | private String shoppingCartLink = "//a[contains(text(), 'shopping cart')]"; 32 | private String shoppingCartIcon = "div#cart"; 33 | private String viewCartLink = "text='View Cart'"; 34 | private String menuBarId = "#menu"; 35 | private String seeAllClass = ".see-all"; 36 | private String productHeaderCss = "#content h2"; 37 | 38 | /** 39 | * Constructor to initialize the page objects with the {@link Page} instance and 40 | * {@link ExtentTest} instance 41 | * 42 | * @param page - {@link Page} 43 | * @param extentTest - {@link ExtentTest} 44 | */ 45 | public HomePage(Page page, ExtentTest extentTest) { 46 | this.page = page; 47 | this.extentTest = extentTest; 48 | } 49 | 50 | /** 51 | * Method to retrieve the Home Page title 52 | * 53 | * @return String - Returns page title 54 | */ 55 | public String getHomePageTitle() { 56 | page.waitForLoadState(); 57 | return page.title(); 58 | } 59 | 60 | /** 61 | * Method to search item in the portal for productName 62 | * 63 | * @param productName - Name of the product to search 64 | * @return boolean - Returns true if search found results else false 65 | */ 66 | public boolean searchProduct(String productName) { 67 | page.fill(search, productName); 68 | page.click(searchIcon); 69 | String header = page.textContent(searchPageHeader); 70 | extentLog(extentTest, Status.PASS, "Search of '" + header + "' Product is successful"); 71 | if (page.locator(productSearchResult).count() > 0) { 72 | extentLog(extentTest, Status.PASS, "Search of '" + productName + "' Product is successful"); 73 | return true; 74 | } 75 | extentLogWithScreenshot(extentTest, Status.FAIL, "No Product is available for the search '" + productName + "'", 76 | takeScreenshot(page)); 77 | return false; 78 | } 79 | 80 | /** 81 | * Method to add a product to the cart. Product will be added to cart and 82 | * screenshot is taken 83 | * 84 | * @return String - Returns actual product catalog name 85 | */ 86 | public String addProductToCart() { 87 | Locator productLocator = page.locator(productSearchResult).nth(0); 88 | productLocator.locator(addToCartSelector).click(); 89 | String product = productLocator.locator(productCaption).textContent(); 90 | if (page.textContent(alertSelector).contains("You have added " + product + " to your shopping cart!")) { 91 | extentLogWithScreenshot(extentTest, Status.PASS, "The '" + product + "' product is added to the cart.", 92 | takeScreenshot(page)); 93 | return product; 94 | } 95 | extentLog(extentTest, Status.FAIL, "Unable to add the product to the cart"); 96 | return null; 97 | } 98 | 99 | /** 100 | * Method to navigate from Homepage to Login page 101 | * 102 | * @return LoginPage - Returns {@link LoginPage} instance 103 | */ 104 | public LoginPage navigateToLoginPage() { 105 | page.click(myAccountLink); 106 | page.click(loginLink); 107 | return new LoginPage(page, extentTest); 108 | } 109 | 110 | /** 111 | * Method to navigate from Homepage to Shopping cart page 112 | * 113 | * @return ShoppingCartPage - Returns {@link ShoppingCartPage} instance 114 | */ 115 | public ShoppingCartPage navigateToShoppingCartPage() { 116 | if (page.isVisible(shoppingCartLink)) { 117 | page.click(shoppingCartLink); 118 | } else { 119 | page.click(shoppingCartIcon); 120 | page.click(viewCartLink); 121 | } 122 | return new ShoppingCartPage(page, extentTest); 123 | } 124 | 125 | /** 126 | * Method to navigate to specific products menu from Homepage 127 | * 128 | * @param productOption - {@link String} 129 | * @return ProductPage - Returns {@link ProductPage} instance 130 | */ 131 | public ProductPage navigateToProductsFromMenu(String productOption) { 132 | Locator productMenu = page.locator(menuBarId).locator("li", 133 | new Locator.LocatorOptions().setHas(page.locator("text='" + productOption + "'"))); 134 | productMenu.hover(); 135 | productMenu.locator(seeAllClass).click(); 136 | String productTitle = page.textContent(productHeaderCss); 137 | if (productTitle.equals(productOption)) { 138 | extentLog(extentTest, Status.PASS, "Navigated to product page from navigation menu"); 139 | return new ProductPage(page, extentTest); 140 | } else { 141 | String message = "Incorrect Landing Page -
Expected product page: " + productOption 142 | + " Actual product page: " + productTitle; 143 | extentLogWithScreenshot(extentTest, Status.FAIL, message, takeScreenshot(page)); 144 | throw new IllegalStateException(message); 145 | } 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/pages/LoginPage.java: -------------------------------------------------------------------------------- 1 | package pages; 2 | 3 | import static base.PlaywrightFactory.takeScreenshot; 4 | import static utils.ExtentReporter.extentLogWithScreenshot; 5 | 6 | import java.util.Base64; 7 | 8 | import static utils.ExtentReporter.extentLog; 9 | 10 | import com.aventstack.extentreports.ExtentTest; 11 | import com.aventstack.extentreports.Status; 12 | import com.microsoft.playwright.Page; 13 | 14 | /** 15 | * Page Object Class for Login Page 16 | * 17 | * @author Nayeem John 18 | */ 19 | public class LoginPage { 20 | 21 | private Page page; 22 | private ExtentTest extentTest; 23 | private String emailId = "//input[@id='input-email']"; 24 | private String password = "//input[@id='input-password']"; 25 | private String loginBtn = "//input[@value='Login']"; 26 | private String logoutLink = "//a[@class='list-group-item'][normalize-space()='Logout']"; 27 | private String alertErrorSelector = "div.alert"; 28 | 29 | /** 30 | * Constructor to initialize the page objects with the {@link Page} instance and 31 | * {@link ExtentTest} instance 32 | * 33 | * @param page - {@link Page} 34 | * @param extentTest - {@link ExtentTest} 35 | */ 36 | public LoginPage(Page page, ExtentTest extentTest) { 37 | this.page = page; 38 | this.extentTest = extentTest; 39 | } 40 | 41 | /** 42 | * Method to get Login page title 43 | * 44 | * @return String - Returns page title 45 | */ 46 | public String getLoginPageTitle() { 47 | page.waitForLoadState(); 48 | return page.title(); 49 | } 50 | 51 | /** 52 | * Method to Login using the username and password 53 | * 54 | * @param appUserName - {@link String} username for the App 55 | * @param appPassword - {@link String} username for the password 56 | * @return boolean - Returns true after successful login else false 57 | */ 58 | public boolean doLogin(String appUserName, String appPassword) { 59 | extentLog(extentTest, Status.INFO, "Login to Application using username " + appUserName); 60 | page.fill(emailId, appUserName); 61 | page.fill(password, new String(Base64.getDecoder().decode(appPassword))); 62 | page.click(loginBtn); 63 | if (page.locator(logoutLink).isVisible()) { 64 | extentLog(extentTest, Status.PASS, "User login to the Application successful."); 65 | return true; 66 | } 67 | boolean isErrorDisplayed = page.textContent(alertErrorSelector) 68 | .contains("Warning: No match for E-Mail Address and/or Password."); 69 | extentLogWithScreenshot(extentTest, Status.FAIL, "User login to the Application is unsuccessful.", 70 | takeScreenshot(page)); 71 | return !isErrorDisplayed; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/pages/ProductPage.java: -------------------------------------------------------------------------------- 1 | package pages; 2 | 3 | import static base.PlaywrightFactory.takeScreenshot; 4 | import static utils.ExtentReporter.extentLog; 5 | import static utils.ExtentReporter.extentLogWithScreenshot; 6 | 7 | import java.util.HashMap; 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | import org.apache.logging.log4j.LogManager; 13 | import org.apache.logging.log4j.Logger; 14 | 15 | import com.aventstack.extentreports.ExtentTest; 16 | import com.aventstack.extentreports.Status; 17 | import com.microsoft.playwright.Locator; 18 | import com.microsoft.playwright.Page; 19 | import com.microsoft.playwright.TimeoutError; 20 | import com.microsoft.playwright.options.LoadState; 21 | 22 | /** 23 | * Page Object Class for Product Page 24 | * 25 | * @author Nayeem John 26 | */ 27 | public class ProductPage { 28 | 29 | private Page page; 30 | private ExtentTest extentTest; 31 | private String productItem = ".product-thumb .caption"; 32 | private String priceParagraphSelector = "p.price"; 33 | private String priceExTaxSelector = "span.price-tax"; 34 | private String priceNew = "span.price-new"; 35 | private String sortInput = "#input-sort"; 36 | private String shareCountSelector = "a.addthis_button_expanded"; 37 | private static Logger log = LogManager.getLogger(); 38 | 39 | public ProductPage(Page page, ExtentTest extentTest) { 40 | this.page = page; 41 | this.extentTest = extentTest; 42 | } 43 | 44 | /** 45 | * Method to get the Products and price from products page 46 | * The locator name and price is captured using locator chaining 47 | * The currency character in price will be replaced with empty string in order 48 | * to parse price 49 | * 50 | * @return Map - Returns {@link Map} of the Product name and 51 | * price value (Double) 52 | */ 53 | private Map getProductsAndPrice() { 54 | Map mapOfProductAndPrice = new HashMap<>(); 55 | Locator productItems = page.locator(productItem); 56 | for (int i = 0; i < productItems.count(); i++) { 57 | 58 | String productName = productItems.nth(i).locator("a").textContent(); 59 | // Get the new price if available else price 60 | Locator priceParagraphLocator = productItems.nth(i).locator(priceParagraphSelector); 61 | String priceString = priceParagraphLocator.textContent() 62 | .replace(priceParagraphLocator.locator(priceExTaxSelector).textContent(), ""); 63 | if (priceParagraphLocator.locator(priceNew).isVisible()) { 64 | priceString = priceParagraphLocator.locator(priceNew).textContent(); 65 | } 66 | // Replace any currency characters and parse the string to Double 67 | Double price = Double.parseDouble(priceString.replaceAll("[^0-9.]", "")); 68 | mapOfProductAndPrice.put(productName, price); 69 | } 70 | return mapOfProductAndPrice; 71 | } 72 | 73 | /** 74 | * Method to sort the products by price from UI and verify the sorting from code 75 | * Using playwright selectOption to select drop down. The value from drop down 76 | * is selected based on elementHandle of text locator 77 | * 78 | * @return boolean - Returns the verification of result of sorting - 79 | * {@link Boolean} 80 | */ 81 | public boolean checkProductSortedByPrice() { 82 | Map mapProductsAndPriceSortedFromCode = sortMapByValue(getProductsAndPrice()); 83 | page.selectOption(sortInput, page.locator("text='Price (Low > High)'").elementHandle()); 84 | Map mapProductsAndPriceSortedFromUI = getProductsAndPrice(); 85 | if (mapProductsAndPriceSortedFromUI.equals(mapProductsAndPriceSortedFromCode)) { 86 | extentLog(extentTest, Status.PASS, "The products are sorted by price in UI"); 87 | return true; 88 | } else { 89 | extentLogWithScreenshot(extentTest, Status.FAIL, "The Products are not sorted by price in UI", 90 | takeScreenshot(page)); 91 | return false; 92 | } 93 | } 94 | 95 | /** 96 | * Method to sort the Map based on the value (ascending order) 97 | * 98 | * @param mapOfProductAndPrice - {@link Map} 99 | * @return Map - Returns the sorted {@link Map} 100 | */ 101 | private Map sortMapByValue(Map mapOfProductAndPrice) { 102 | return mapOfProductAndPrice.entrySet().stream().sorted(Map.Entry.comparingByValue()) 103 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, 104 | LinkedHashMap::new)); 105 | } 106 | 107 | /** 108 | * Method to click and view the product item from product list 109 | * 110 | * @param productName - {@link String} 111 | */ 112 | public void viewProduct(String productName) { 113 | page.locator("text='" + productName + "'").click(); 114 | log.info("Clicked on Product to view the Product: '{}'", productName); 115 | } 116 | 117 | /** 118 | * Method to share the product to the social platform and verify the share count 119 | * is incremented 120 | * 121 | * @param socialPlatformName - {@link String} 122 | * @return - Returns {@code true} of product share count is incremented else 123 | * {@code false} 124 | */ 125 | public boolean shareProductToSocialPlatform(String socialPlatformName, int retryAttempt) { 126 | int shareCountBefore = getShareCount(); 127 | extentLog(extentTest, Status.INFO, "The share count Before share: " + shareCountBefore); 128 | 129 | try { 130 | handleSocialPlatformService(socialPlatformName, retryAttempt); 131 | } catch (Exception e) { 132 | extentTest.fail(e); 133 | log.error("Exception : ", e); 134 | return false; 135 | } 136 | 137 | int shareCountAfter = getShareCount(); 138 | extentLog(extentTest, Status.INFO, "The share score After share: " + shareCountAfter); 139 | 140 | if (shareCountAfter == (shareCountBefore + 1)) { 141 | extentLog(extentTest, Status.PASS, "The share score is incremented by 1 score After share."); 142 | return true; 143 | } 144 | 145 | extentLog(extentTest, Status.FAIL, "Mismatch in share score After share.
Expected: " 146 | + (shareCountBefore + 1) + " Actual: " + shareCountAfter); 147 | return false; 148 | } 149 | 150 | /** 151 | * Method to handle the share of product in 3 ways. Default services, 152 | * services on Hover and services on Mask Dialog 153 | * This method will handle if any pop up (Window/tab) opened for share. This 154 | * method will mark warning if no pop up is opened 155 | * 156 | * @param socialPlatformName - {@link String} 157 | * @param retryAttempt - {@link Integer} 158 | */ 159 | private void handleSocialPlatformService(String socialPlatformName, int retryAttempt) { 160 | try { 161 | Page popupPage = page.context().waitForPage(() -> { 162 | 163 | if (retryAttempt == 0) 164 | selectFromDefaultPlatformService(socialPlatformName); 165 | 166 | if (retryAttempt == 1) 167 | selectPlatformServiceFromHoverDialog(socialPlatformName); 168 | 169 | if (retryAttempt == 2) 170 | selectPlatformServiceFromShareMaskDialog(socialPlatformName); 171 | 172 | }); 173 | popupPage.waitForLoadState(LoadState.NETWORKIDLE); 174 | extentLogWithScreenshot(extentTest, Status.PASS, 175 | "Page Pop up(Window/Tab) is opened for share. Page title: " + popupPage.title(), 176 | takeScreenshot(popupPage)); 177 | popupPage.close(); 178 | 179 | } catch (TimeoutError e) { 180 | extentLogWithScreenshot(extentTest, Status.WARNING, 181 | "No Page Pop up(Window/Tab) is opened for share. Check the number of shares validation step", 182 | takeScreenshot(page)); 183 | log.error("Page Popup Timeout Error", e); 184 | } 185 | } 186 | 187 | /** 188 | * Method to get the current share count of the product 189 | * 190 | * @return shareCount - Returns the share count of the product 191 | */ 192 | private int getShareCount() { 193 | int shareCount = 0; 194 | log.info("Get the number of shares for the product"); 195 | 196 | try { 197 | String shareTextContent = page.textContent(shareCountSelector).trim(); 198 | if (!shareTextContent.isEmpty()) 199 | shareCount = Integer.parseInt(shareTextContent); 200 | } catch (TimeoutError e) { 201 | log.error("Share count selector option is not visible", e); 202 | } 203 | 204 | return shareCount; 205 | } 206 | 207 | /** 208 | * Method to share the product on default social platform visible on page. 209 | * The Method will handle the default platforms are available in Frames. 210 | * Here scrollIntoViewIfNeeded() is used to used to mitigate the known issue 211 | * {@link https://github.com/microsoft/playwright/issues/3166} 212 | * of playwright not auto scrolls to frame element not in the view. 213 | * 214 | * @param socialPlatformName - {@link String} 215 | */ 216 | private void selectFromDefaultPlatformService(String socialPlatformName) { 217 | boolean isPlatformServiceFound = false; 218 | log.info("Checking the '{}' social platform is available in Default Social Platform Service", 219 | socialPlatformName); 220 | if (socialPlatformName.equalsIgnoreCase("facebook")) { 221 | String facebookSelector = "//*[contains(@title, 'Facebook')]"; 222 | page.waitForSelector(facebookSelector).scrollIntoViewIfNeeded(); 223 | page.frameLocator(facebookSelector).locator("//button[@title='Like']").click(); 224 | isPlatformServiceFound = true; 225 | } 226 | 227 | if (socialPlatformName.equalsIgnoreCase("twitter")) { 228 | String twitterSelector = "//*[@id='twitter-widget-0']"; 229 | page.waitForSelector(twitterSelector).scrollIntoViewIfNeeded(); 230 | page.frameLocator(twitterSelector).locator("//*[@id='l']").click(); 231 | isPlatformServiceFound = true; 232 | } 233 | if (!isPlatformServiceFound) 234 | throw new IllegalStateException( 235 | "No matching social platform is available in Default Services for : " + socialPlatformName); 236 | 237 | } 238 | 239 | /** 240 | * Method to share the product on social platform which found on Hover dialog 241 | * This method will be called when there is no matching platform on default 242 | * services 243 | * 244 | * @param socialPlatformName - {@link String} 245 | */ 246 | private void selectPlatformServiceFromHoverDialog(String socialPlatformName) { 247 | String shareIconSelector = "//a[text()='Share']"; 248 | String platformSelectorString = "//span[contains(@class,'at-label')]"; 249 | log.info("Checking the '{}' social platform is available in Hover Dialog", socialPlatformName); 250 | 251 | page.hover(shareIconSelector); 252 | try { 253 | Locator platformServiceLocator = page.locator(platformSelectorString, 254 | new Page.LocatorOptions().setHas(page.locator("text='" + socialPlatformName + "'"))); 255 | platformServiceLocator.waitFor(); 256 | platformServiceLocator.click(); 257 | } catch (Exception e) { 258 | throw new IllegalStateException("No matching social platform is available in Hover Dialog :" + socialPlatformName); 259 | } 260 | } 261 | 262 | /** 263 | * Method to share the product on social platform which opened on Mask dialog 264 | * This method will be called when there is no matching platform on default 265 | * services and Hover dialog 266 | * 267 | * @param socialPlatformName - {@link String} 268 | */ 269 | private void selectPlatformServiceFromShareMaskDialog(String socialPlatformName) { 270 | String shareIconSelector = "//a[text()='Share']"; 271 | String moreOption = " #atic_more"; 272 | String searchFieldString = "#at-expanded-menu-service-filter"; 273 | String platformSelectorString = "//span[@class='at-icon-name']"; 274 | log.info("Checking the '{}' social platform is available in Mask Dialog", socialPlatformName); 275 | 276 | page.hover(shareIconSelector); 277 | page.click(moreOption); 278 | page.waitForSelector(searchFieldString).fill(socialPlatformName); 279 | Locator socialIconLocator = page.locator(platformSelectorString); 280 | socialIconLocator.last().waitFor(); 281 | log.info("Social platforms available for search: '{}'", socialIconLocator.allTextContents()); 282 | 283 | if (socialIconLocator.count() == 0) { 284 | extentLogWithScreenshot(extentTest, Status.FAIL, 285 | "No Social Platform service found for search: '" + socialPlatformName + "'", takeScreenshot(page)); 286 | } 287 | 288 | if (socialIconLocator.count() == 1) { 289 | extentLog(extentTest, Status.PASS, 290 | "Social Platform service found for search: '" + socialPlatformName + "'"); 291 | socialIconLocator.click(); 292 | } else { 293 | String message = ""; 294 | if (socialIconLocator.count() == 0) 295 | message = "No Social Platform service found for search: '" + socialPlatformName + "'"; 296 | 297 | if (socialIconLocator.count() > 1) 298 | message = "More than 1 Social Platform services found for search: '" + socialPlatformName + "'. Services found: " + socialIconLocator.allTextContents(); 299 | 300 | String closePlatformServiceMask = "button.at-expanded-menu-close"; 301 | if (page.isVisible(closePlatformServiceMask)) 302 | page.click(closePlatformServiceMask); 303 | 304 | throw new IllegalStateException(message); 305 | } 306 | } 307 | 308 | } 309 | -------------------------------------------------------------------------------- /src/main/java/pages/ShoppingCartPage.java: -------------------------------------------------------------------------------- 1 | package pages; 2 | 3 | import static base.PlaywrightFactory.takeScreenshot; 4 | import static utils.ExtentReporter.extentLogWithScreenshot; 5 | 6 | import java.util.List; 7 | 8 | import com.aventstack.extentreports.ExtentTest; 9 | import com.aventstack.extentreports.Status; 10 | import com.microsoft.playwright.Page; 11 | 12 | /** 13 | * Page Object Class for shopping cart Page 14 | * 15 | * @author Nayeem John 16 | */ 17 | public class ShoppingCartPage { 18 | 19 | private Page page; 20 | private ExtentTest extentTest; 21 | 22 | private String checkout = "//a[text()='Checkout']"; 23 | private String login = "//input[@value='Login']"; 24 | private String billingAddress = "//a[@href='#collapse-payment-address']"; 25 | 26 | /** 27 | * Constructor to initialize the page objects with the {@link Page} instance and 28 | * {@link ExtentTest} instance 29 | * 30 | * @param page - {@link Page} 31 | * @param extentTest - {@link ExtentTest} 32 | */ 33 | public ShoppingCartPage(Page page, ExtentTest extentTest) { 34 | this.page = page; 35 | this.extentTest = extentTest; 36 | } 37 | 38 | /** 39 | * Method to verify the product added to cart is available in cart 40 | * 41 | * @param products - {@link List} Of products to verify 42 | * @return boolean - Returns true if all products is available in cart else 43 | * false if any one product is not available 44 | */ 45 | public boolean checkProductInCart(List products) { 46 | for (String product : products) { 47 | String productInCartSelector = "//div[@id='content']//a[text()='" + product + "']"; 48 | if (!page.locator(productInCartSelector).isVisible()) { 49 | extentLogWithScreenshot(extentTest, Status.FAIL, 50 | "The '" + product + "' Product is not available to the cart", takeScreenshot(page)); 51 | return false; 52 | } 53 | } 54 | extentLogWithScreenshot(extentTest, Status.PASS, 55 | "The '" + products.toString() + "' Products is available to the cart", takeScreenshot(page)); 56 | return true; 57 | } 58 | 59 | /** 60 | * Method to checkout the cart with the product items 61 | * 62 | * @param isUserLoggedIn - {@link Boolean} - User Login state 63 | * @return boolean - Returns true if checkout was successful else @throws {@link 64 | * com.microsoft.playwright.TimeoutError} 65 | * Exception 66 | */ 67 | public boolean checkoutCart(boolean isUserLoggedIn) { 68 | page.click(checkout); 69 | if (isUserLoggedIn) { 70 | return page.waitForSelector(billingAddress).isVisible(); 71 | } else { 72 | return page.waitForSelector(login).isVisible(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/utils/ExtentReporter.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Paths; 5 | 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.Logger; 8 | import org.apache.logging.log4j.Marker; 9 | import org.apache.logging.log4j.MarkerManager; 10 | 11 | import com.aventstack.extentreports.ExtentReports; 12 | import com.aventstack.extentreports.ExtentTest; 13 | import com.aventstack.extentreports.Status; 14 | import com.aventstack.extentreports.reporter.ExtentSparkReporter; 15 | 16 | /** 17 | * Extent Report class for the Report generation 18 | * @author Nayeem John 19 | */ 20 | public class ExtentReporter { 21 | 22 | private ExtentReporter() { 23 | throw new IllegalStateException("Extent Reporter class instantiation is not allowed"); 24 | } 25 | 26 | /** 27 | * Method to configure and get the ExtentReporter instance 28 | * 29 | * @param testProperties - {@link TestProperties} 30 | * @return ExtentReports - Returns {@link ExtentReports} instance 31 | * @throws IOException - Throws {@link IOException} 32 | */ 33 | public static ExtentReports getExtentReporter(TestProperties testProperties) throws IOException { 34 | ExtentSparkReporter reporter = new ExtentSparkReporter(testProperties.getProperty("extentReportPath")); 35 | reporter.loadXMLConfig("./src/main/resources/extent-report-config.xml"); 36 | 37 | reporter.config().setCss("img.r-img { width: 30%; }"); 38 | ExtentReports extentReports = new ExtentReports(); 39 | extentReports.attachReporter(reporter); 40 | 41 | String applicationURL = "Open cart Demo Application"; 43 | extentReports.setSystemInfo("Application", applicationURL); 44 | 45 | extentReports.setSystemInfo("OS", System.getProperties().getProperty("os.name")); 46 | extentReports.setSystemInfo("Browser", testProperties.getProperty("browser")); 47 | 48 | if (Boolean.getBoolean(testProperties.getProperty("enableRecordVideo"))) { 49 | String filePath = Paths.get(testProperties.getProperty("recordVideoDirectory")).toAbsolutePath() 50 | .toString(); 51 | String recordedVideoFilePath = ""; 53 | extentReports.setSystemInfo("Execution Recorded Video", recordedVideoFilePath); 54 | } 55 | return extentReports; 56 | } 57 | 58 | /** 59 | * Method to add the log the step to extent report 60 | * 61 | * @param extentTest - {@link ExtentTest} 62 | * @param status - {@link Status} 63 | * @param message - {@link String} log message 64 | */ 65 | public static void extentLog(ExtentTest extentTest, Status status, String message) { 66 | extentTest.log(status, message); 67 | log(status, message); 68 | } 69 | 70 | /** 71 | * Method to add the log step with the screenshot to the extent report 72 | * 73 | * @param extentTest - {@link ExtentTest} 74 | * @param status - {@link Status} 75 | * @param message - {@link String} log message 76 | * @param base64Path - {@link java.util.Base64} {@link String} of screenshot 77 | */ 78 | public static void extentLogWithScreenshot(ExtentTest extentTest, Status status, String message, 79 | String base64Path) { 80 | String imageElement = "
"; 82 | extentTest.log(status, message + imageElement); 83 | log(status, message); 84 | } 85 | 86 | /** 87 | * Method to log the message to console and log file. 88 | * It removes any HTML element in the message before printing logging 89 | * 90 | * @param status - {@link Status} 91 | * @param message - {@link String} log message 92 | */ 93 | private static void log(Status status, String message) { 94 | message = message.replaceAll("\\<.*?\\>", ""); 95 | Logger log = LogManager.getLogger(Thread.currentThread().getStackTrace()[3].getClassName().split("\\.")[1]+ "." + Thread.currentThread().getStackTrace()[3].getMethodName()); 96 | Marker marker = MarkerManager.getMarker("ReportLog"); 97 | switch (status) { 98 | case FAIL: 99 | log.warn(marker, message); 100 | break; 101 | case WARNING: 102 | log.warn(marker, message); 103 | break; 104 | case SKIP: 105 | log.warn(marker, message); 106 | break; 107 | case INFO: 108 | log.info(marker, message); 109 | break; 110 | default: 111 | log.debug(marker, message); 112 | break; 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/utils/TestProperties.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.util.Properties; 6 | 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | /** 11 | * Class to load, get, set and update the test properties 12 | * @author Nayeem John 13 | */ 14 | public class TestProperties { 15 | 16 | private Logger log = LogManager.getLogger(); 17 | private Properties prop; 18 | 19 | /** 20 | * Constructor - Load the config properties for Test 21 | */ 22 | public TestProperties() { 23 | this.prop = new Properties(); 24 | try (FileInputStream fileInputStream = new FileInputStream("./src/main/resources/config.properties")) { 25 | prop.load(fileInputStream); 26 | } catch (IOException e) { 27 | log.error("Error while reading properties file ", e); 28 | } 29 | } 30 | 31 | /** 32 | * Utility method to get the property value after trim 33 | * 34 | * @param key - {@link String} - key of the property 35 | * @return String - Returns value {@link String} of the property 36 | */ 37 | public String getProperty(String key) { 38 | return prop.getProperty(key) != null ? prop.getProperty(key).trim() : null; 39 | } 40 | 41 | /** 42 | * Method to set the property with the value 43 | * 44 | * @param key - {@link String} - key of the property 45 | * @param value - {@link String} - value of the property 46 | */ 47 | public void setProperty(String key, String value) { 48 | prop.setProperty(key, value); 49 | } 50 | 51 | /** 52 | * Method to update the Test properties with Run time System Property 53 | * 54 | */ 55 | public void updateTestProperties() { 56 | prop.keySet().forEach(key -> { 57 | String propKey = (String) key; 58 | if (System.getProperty(propKey) != null) 59 | prop.setProperty(propKey, System.getProperty(propKey)); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | # Page url 2 | url = https://naveenautomationlabs.com/opencart/ 3 | 4 | # Browser - allowed values [chrome, chromium, edge, firefox, safari] 5 | browser = chrome 6 | 7 | # To Run test in headless mode 8 | headless = true 9 | 10 | # App credentails - username 11 | username = nayeem.john@outlook.com 12 | 13 | # App credentails - Base64 encoded password 14 | password = Vmlub3RoQDE3MDI= 15 | 16 | # Option to enable video recording of execution 17 | enableRecordVideo = false 18 | 19 | # Path to save video recording files 20 | recordVideoDirectory = test-results/scriptRecordVideos/ 21 | 22 | # Option to enable tracing (Capture HAR, Screenshots, events) of execution 23 | enableTracing = false 24 | 25 | # Path to save tracing zip file 26 | tracingDirectory= test-results/traces/ 27 | 28 | # To set View Port - Width 29 | viewPortWidth = 1280 30 | 31 | # To set View Port - Height 32 | viewPortHeight = 720 33 | 34 | # Test execution Extent report file name 35 | extentReportPath = test-results/TestExecutionReport.html 36 | 37 | # Option to enable session state storage 38 | useSessionState = false 39 | 40 | # Session state storage file name 41 | sessionState = src/main/resources/session-state.json -------------------------------------------------------------------------------- /src/main/resources/extent-report-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | STANDARD 8 | 9 | 10 | 11 | UTF-8 12 | 13 | 14 | 15 | 16 | HTTPS 17 | 18 | 19 | true 20 | 21 | 22 | false 23 | 24 | 25 | 26 | false 27 | 28 | 29 | Open Cart UI Test Execution Report 30 | 31 | 32 | Open Cart UI Test Execution Report 33 | 34 | 35 | yyyy-MMM-dd HH:mm:ss 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test-results/logs/opencart-ui-automation.log 5 | %style{[%date{yyyy-MMM-dd HH:mm:ss.SSS zzz}]}{yellow} %style{[Thread ID: %tid]}{white} %style{[%5class{1}.%method]}{bright_blue} %highlight{[%level]} %msg%n%throwable 6 | %style{[%date{yyyy-MMM-dd HH:mm:ss.SSS zzz}]}{yellow} %style{[Thread ID: %tid]}{white} %style{[%logger]}{bright_blue} %highlight{[%level]} %msg%n%throwable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/java/testUtils/RetryAnalyzer.java: -------------------------------------------------------------------------------- 1 | package testutils; 2 | 3 | import org.testng.IRetryAnalyzer; 4 | import org.testng.ITestResult; 5 | 6 | public class RetryAnalyzer implements IRetryAnalyzer { 7 | 8 | private int retryCount = 1; 9 | private int maxRetryCount = 2; 10 | 11 | @Override 12 | public boolean retry(ITestResult iTestResult) { 13 | 14 | if (retryCount <= maxRetryCount) { 15 | // Add custom attribute for retry 16 | iTestResult.getTestContext().setAttribute("retryCount", retryCount); 17 | retryCount++; 18 | iTestResult.setStatus(ITestResult.FAILURE); 19 | return true; 20 | } else { 21 | iTestResult.setStatus(ITestResult.FAILURE); 22 | } 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/tests/TS_01_VerifyOpenCartLoginTests.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import java.util.UUID; 4 | 5 | import org.testng.Assert; 6 | import org.testng.annotations.BeforeClass; 7 | import org.testng.annotations.Test; 8 | 9 | import pages.HomePage; 10 | 11 | /** 12 | * TestNG Test class - Login Functionality 13 | */ 14 | public class TS_01_VerifyOpenCartLoginTests extends TestBase { 15 | 16 | /** 17 | * BeforeClass Method to create ExtentTest in Extent Report 18 | */ 19 | @BeforeClass 20 | public void setupBeforeClass() { 21 | extentTest = reporter.createTest("TS_01 Verify Open Cart Login", "Verify login functionality of Open Cart"); 22 | } 23 | 24 | /** 25 | * Test the login functionality of the application with valid credentials 26 | * This test will soft assert the home page title and validate the login 27 | */ 28 | @Test 29 | public void loginWithValidCredentialsTest() { 30 | testNode = extentTest.createNode("TC_01 Verify Open Cart Login with Valid Credentials"); 31 | testNode.assignCategory("TS_01_Open-Cart-Login"); 32 | homePage = new HomePage(page, testNode); 33 | softAssert.assertEquals(homePage.getHomePageTitle(), HOME_PAGE_TITLE); 34 | loginPage = homePage.navigateToLoginPage(); 35 | Assert.assertTrue(loginPage.doLogin(testProperties.getProperty("username"), 36 | testProperties.getProperty("password"))); 37 | } 38 | 39 | /** 40 | * Test the login functionality of the application with invalid credentials 41 | * This test will soft assert the home page title and validate the login 42 | */ 43 | @Test 44 | public void loginWithInvalidCredentialsTest() { 45 | testNode = extentTest.createNode("TC_02 Verify Open Cart Login with Invalid Credentials"); 46 | testNode.assignCategory("TS_01_Open-Cart-Login"); 47 | homePage = new HomePage(page, testNode); 48 | softAssert.assertEquals(homePage.getHomePageTitle(), HOME_PAGE_TITLE); 49 | loginPage = homePage.navigateToLoginPage(); 50 | Assert.assertFalse(loginPage.doLogin(UUID.randomUUID().toString().replace("-", ""), "InvalidPassword")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/tests/TS_02_VerifyAddProductToCartTests.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import org.testng.Assert; 4 | import org.testng.annotations.BeforeClass; 5 | import org.testng.annotations.Test; 6 | 7 | import pages.HomePage; 8 | 9 | /** 10 | * TestNG Test class - Add Product to Cart 11 | */ 12 | public class TS_02_VerifyAddProductToCartTests extends TestBase { 13 | 14 | /** 15 | * BeforeClass Method to create ExtentTest in Extent Report 16 | */ 17 | @BeforeClass 18 | public void setupBeforeClass() { 19 | extentTest = reporter.createTest("TS_02 Verify Add Product to Cart", "Verify Add Products to Cart"); 20 | } 21 | 22 | /** 23 | * Test the product search which not exists in inventory 24 | */ 25 | @Test 26 | public void searchProductWhichNotExistsTest() { 27 | testNode = extentTest.createNode("TC_01 Verify Product search which not exists", 28 | "Test Case 02 Expected: No results should be displayed for the invalid product search"); 29 | testNode.assignCategory("TS_02_Open-Cart-Add-Product-to-Cart"); 30 | homePage = new HomePage(page, testNode); 31 | Assert.assertFalse(homePage.searchProduct("InvalidProduct")); 32 | } 33 | 34 | /** 35 | * Test the product search and add the product the Inventory without login 36 | */ 37 | @Test 38 | public void searchAndAddProductToCartWithoutLoginTest() { 39 | testNode = extentTest.createNode("TC_02 Verify Search And Add Product to Cart Without Login"); 40 | testNode.assignCategory("TS_02_Open-Cart-Add-Product-to-Cart"); 41 | homePage = new HomePage(page, testNode); 42 | Assert.assertTrue(homePage.searchProduct("Macbook")); 43 | Assert.assertNotNull(homePage.addProductToCart()); 44 | } 45 | 46 | /** 47 | * Test the product search and add the product the Inventory with login 48 | */ 49 | @Test 50 | public void searchAndAddProductToCartWithLoginTest() { 51 | testNode = extentTest.createNode("TC_03 Verify Search And Add Product to Cart With Login"); 52 | testNode.assignCategory("TS_02_Open-Cart-Add-Product-to-Cart"); 53 | homePage = new HomePage(page, testNode); 54 | loginPage = homePage.navigateToLoginPage(); 55 | Assert.assertTrue(loginPage.doLogin(testProperties.getProperty("username"), 56 | testProperties.getProperty("password"))); 57 | Assert.assertTrue(homePage.searchProduct("Macbook")); 58 | Assert.assertNotNull(homePage.addProductToCart()); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/tests/TS_03_VerifyCheckoutCartTests.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.testng.Assert; 7 | import org.testng.annotations.BeforeClass; 8 | import org.testng.annotations.Test; 9 | 10 | import base.PlaywrightFactory; 11 | import pages.HomePage; 12 | import pages.ShoppingCartPage; 13 | 14 | /** 15 | * TestNG Test class - Checkout cart 16 | */ 17 | public class TS_03_VerifyCheckoutCartTests extends TestBase { 18 | 19 | private ShoppingCartPage shoppingCartPage; 20 | private List productList = new ArrayList<>(); 21 | 22 | /** 23 | * BeforeClass Method to create ExtentTest in Extent Report 24 | */ 25 | @BeforeClass 26 | public void setupBeforeClass() { 27 | extentTest = reporter.createTest("TS_03 Verify Cart and Checkout the Cart", "Verify Add Products to Cart"); 28 | } 29 | 30 | /** 31 | * Test the cart checkout functionality without login 32 | */ 33 | @Test(priority = 1) 34 | public void checkoutCartWithoutLoginTest() { 35 | testNode = extentTest.createNode("TC_01 Verify Cart Checkout Without Login"); 36 | testNode.assignCategory("TS_03_Open-Cart-Checkout-Cart"); 37 | homePage = new HomePage(page, testNode); 38 | softAssert.assertEquals(homePage.getHomePageTitle(), HOME_PAGE_TITLE); 39 | Assert.assertTrue(homePage.searchProduct("Macbook")); 40 | String product = homePage.addProductToCart(); 41 | Assert.assertNotNull(product); 42 | productList.add(product); 43 | shoppingCartPage = homePage.navigateToShoppingCartPage(); 44 | Assert.assertTrue(shoppingCartPage.checkProductInCart(productList)); 45 | Assert.assertTrue(shoppingCartPage.checkoutCart(false)); 46 | productList.remove(product); 47 | } 48 | 49 | /** 50 | * Test the cart checkout functionality with login and save the session state 51 | */ 52 | @Test(priority = 2) 53 | public void checkoutCartWithLoginTest() { 54 | testNode = extentTest.createNode("TC_01 Verify Checkout with With Login"); 55 | testNode.assignCategory("TS_03_Open-Cart-Checkout-Cart"); 56 | homePage = new HomePage(page, testNode); 57 | softAssert.assertEquals(homePage.getHomePageTitle(), HOME_PAGE_TITLE); 58 | loginPage = homePage.navigateToLoginPage(); 59 | Assert.assertTrue(loginPage.doLogin(testProperties.getProperty("username"), 60 | testProperties.getProperty("password"))); 61 | PlaywrightFactory.saveSessionState(page, testProperties.getProperty("sessionState")); 62 | testProperties.setProperty("useSessionState", "true"); 63 | Assert.assertTrue(homePage.searchProduct("Macbook")); 64 | String product = homePage.addProductToCart(); 65 | productList.add(product); 66 | Assert.assertNotNull(product); 67 | shoppingCartPage = homePage.navigateToShoppingCartPage(); 68 | Assert.assertTrue(shoppingCartPage.checkProductInCart(productList)); 69 | Assert.assertTrue(shoppingCartPage.checkoutCart(true)); 70 | } 71 | 72 | /** 73 | * Test the cart checkout functionality with adding more product and using login 74 | * state saved in previous Test 75 | */ 76 | @Test(priority = 3, dependsOnMethods = { "checkoutCartWithLoginTest" }) 77 | public void addMoreProductToCartAndCheckoutTest() { 78 | testNode = extentTest.createNode("TC_01 Verify Add More Product to cart and checkout"); 79 | testNode.assignCategory("TS_03_Open-Cart-Checkout-Cart"); 80 | homePage = new HomePage(page, testNode); 81 | softAssert.assertEquals(homePage.getHomePageTitle(), HOME_PAGE_TITLE); 82 | Assert.assertTrue(homePage.searchProduct("Samsung")); 83 | String product = homePage.addProductToCart(); 84 | productList.add(product); 85 | Assert.assertNotNull(product); 86 | shoppingCartPage = homePage.navigateToShoppingCartPage(); 87 | Assert.assertTrue(shoppingCartPage.checkProductInCart(productList)); 88 | Assert.assertTrue(shoppingCartPage.checkoutCart(true)); 89 | testProperties.setProperty("useSessionState", "false"); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/tests/TS_04_VerifyProductsPageTest.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import org.testng.Assert; 4 | import org.testng.Reporter; 5 | import org.testng.annotations.BeforeClass; 6 | import org.testng.annotations.DataProvider; 7 | import org.testng.annotations.Test; 8 | 9 | import pages.HomePage; 10 | import pages.ProductPage; 11 | import testutils.RetryAnalyzer; 12 | 13 | /** 14 | * TestNG Test class - View Products 15 | */ 16 | public class TS_04_VerifyProductsPageTest extends TestBase { 17 | 18 | /** 19 | * BeforeClass Method to create ExtentTest in Extent Report 20 | */ 21 | @BeforeClass 22 | public void setupBeforeClass() { 23 | extentTest = reporter.createTest("TS_04 Verify Products Page", 24 | "Verify Products page functionalities like view products, sort products, share product"); 25 | } 26 | 27 | /** 28 | * Test the sort products by price (Low to High) feature in all products page 29 | */ 30 | @Test 31 | public void sortProductsByPriceTest() { 32 | testNode = extentTest.createNode("TC_01 Verify Product Sort By Price"); 33 | testNode.assignCategory("TS_04_Open-Cart-Product-Page"); 34 | HomePage homePage = new HomePage(page, testNode); 35 | ProductPage productPage = homePage.navigateToProductsFromMenu("Desktops"); 36 | Assert.assertTrue(productPage.checkProductSortedByPrice()); 37 | } 38 | 39 | /** 40 | * Test the view and share product to social platforms 41 | * 42 | * @param socialPlatformName - {@link String} - social platform name from the 43 | * data provider 44 | */ 45 | @Test(dataProvider = "getProductAndSocialPlatform", retryAnalyzer = RetryAnalyzer.class) 46 | public void shareProductToSocialPlatformTest(String productName, String socialPlatformName) { 47 | String testNodeName = "TC_01 Verify Share '" + productName + "' Product to Social Platform - " + socialPlatformName; 48 | Object retryCountAttribute = Reporter.getCurrentTestResult().getTestContext().removeAttribute("retryCount"); 49 | int retryAttempt = 0; 50 | if (retryCountAttribute != null){ 51 | retryAttempt = (int)retryCountAttribute; 52 | testNodeName = "Retry " + retryAttempt + " : " + testNodeName; 53 | } 54 | testNode = extentTest.createNode(testNodeName); 55 | testNode.assignCategory("TS_04_Open-Cart-Product-Page"); 56 | 57 | HomePage homePage = new HomePage(page, testNode); 58 | ProductPage productPage = homePage.navigateToProductsFromMenu("Desktops"); 59 | productPage.viewProduct(productName); 60 | Assert.assertTrue(productPage.shareProductToSocialPlatform(socialPlatformName, retryAttempt)); 61 | } 62 | 63 | /** 64 | * Data provider method for the test 65 | * 66 | * @return Returns social platform names Object array 67 | */ 68 | @DataProvider(name = "getProductAndSocialPlatform") 69 | public Object[][] getProductAndSocialPlatform() { 70 | return new Object[][] { 71 | { "MacBook Air", "Facebook" }, 72 | { "Canon EOS 5D", "Twitter" }, 73 | { "MacBook Air", "Link" }, 74 | { "Apple Cinema 30\"", "LinkedIn" }, 75 | { "Product 8", "Email" }, 76 | { "Sony VAIO", "Tumblr" }, 77 | { "HP LP3065", "NoPlatform" }, 78 | { "MacBook", "Pinterest" } 79 | }; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/tests/TestBase.java: -------------------------------------------------------------------------------- 1 | package tests; 2 | 3 | import static base.PlaywrightFactory.takeScreenshot; 4 | import static utils.ExtentReporter.extentLogWithScreenshot; 5 | 6 | import java.io.File; 7 | import java.nio.file.Paths; 8 | 9 | import org.apache.logging.log4j.LogManager; 10 | import org.apache.logging.log4j.Logger; 11 | import org.testng.ITestResult; 12 | import org.testng.annotations.AfterMethod; 13 | import org.testng.annotations.AfterSuite; 14 | import org.testng.annotations.BeforeMethod; 15 | import org.testng.annotations.BeforeSuite; 16 | import org.testng.asserts.SoftAssert; 17 | 18 | import com.aventstack.extentreports.ExtentReports; 19 | import com.aventstack.extentreports.ExtentTest; 20 | import com.aventstack.extentreports.Status; 21 | import com.microsoft.playwright.Page; 22 | import com.microsoft.playwright.Tracing; 23 | 24 | import base.PlaywrightFactory; 25 | import pages.HomePage; 26 | import pages.LoginPage; 27 | import utils.ExtentReporter; 28 | import utils.TestProperties; 29 | 30 | /** 31 | * Base Class for the TestNG test 32 | */ 33 | public class TestBase { 34 | 35 | protected Page page; 36 | protected SoftAssert softAssert = new SoftAssert(); 37 | protected ExtentTest extentTest, testNode; 38 | protected HomePage homePage; 39 | protected LoginPage loginPage; 40 | protected static final String HOME_PAGE_TITLE = "Your Store"; 41 | protected static ExtentReports reporter; 42 | protected static TestProperties testProperties; 43 | private static Logger log; 44 | 45 | /** 46 | * BeforeSuite method to clean up the test-results directory and initialize the 47 | * extent reporter, logger and read test properties 48 | * 49 | * @throws Exception 50 | */ 51 | @BeforeSuite 52 | public void setupBeforeTestSuite() throws Exception { 53 | File file = new File("test-results"); 54 | if (file.exists() && !deleteDirectory(file)) { 55 | throw new Exception("Exception occurred while deleting test-results directory"); 56 | } 57 | log = LogManager.getLogger(); 58 | testProperties = new TestProperties(); 59 | testProperties.updateTestProperties(); 60 | reporter = ExtentReporter.getExtentReporter(testProperties); 61 | } 62 | 63 | /** 64 | * AfterSuite method to assert all the soft assertions and flush(write) the 65 | * extent report 66 | */ 67 | @AfterSuite 68 | public void teardownAfterTestSuite() { 69 | try { 70 | softAssert.assertAll(); 71 | reporter.flush(); 72 | } catch (Exception e) { 73 | log.error("Error in AfterSuite Method ", e); 74 | } 75 | } 76 | 77 | /** 78 | * BeforeMethod to start the playwright server, create page and navigate to the 79 | * base URL 80 | */ 81 | @BeforeMethod 82 | public void startPlaywrightServer() { 83 | PlaywrightFactory pf = new PlaywrightFactory(testProperties); 84 | page = pf.createPage(); 85 | page.navigate(testProperties.getProperty("url")); 86 | } 87 | 88 | /** 89 | * AfterMethod to stop the tracing if enabled and save the tracing 90 | * and add screenshot for tests which result is not SUCCESS 91 | * 92 | * @param result - {@link ITestResult} of current Test 93 | */ 94 | @AfterMethod 95 | public void closePage(ITestResult result) { 96 | String testName = testNode.getModel().getName().replaceAll("[^A-Za-z0-9_\\-\\.\\s]", ""); 97 | if (Boolean.parseBoolean(testProperties.getProperty("enableTracing"))) { 98 | String fileName = testProperties.getProperty("tracingDirectory") + "Trace_" 99 | + testName + ".zip"; 100 | page.context().tracing().stop(new Tracing.StopOptions() 101 | .setPath(Paths.get(fileName))); 102 | } 103 | if (!result.isSuccess()) 104 | extentLogWithScreenshot(testNode, Status.WARNING, "The test is not Passed. Please refer the previous step.", 105 | takeScreenshot(page)); 106 | page.context().browser().close(); 107 | reporter.flush(); 108 | } 109 | 110 | /** 111 | * Method to delete the directory recursively 112 | * 113 | * @param directoryToBeDeleted - {@link File} to be deleted 114 | * @return boolean - Returns {@link Boolean} of delete operation 115 | */ 116 | private boolean deleteDirectory(File directoryToBeDeleted) { 117 | File[] allContents = directoryToBeDeleted.listFiles(); 118 | if (allContents != null) { 119 | for (File file : allContents) { 120 | deleteDirectory(file); 121 | } 122 | } 123 | return directoryToBeDeleted.delete(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/resources/testrunners/testng.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------