├── .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 | 
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 | 
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 | 
98 |
99 | 
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 | - 
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 | - 
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 |
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 |
--------------------------------------------------------------------------------