diff --git a/.github/workflows/playwright-tests.yml b/.github/workflows/playwright-tests.yml new file mode 100644 index 0000000..25ff2df --- /dev/null +++ b/.github/workflows/playwright-tests.yml @@ -0,0 +1,52 @@ +name: Playwright Tests + +# Trigger the workflow on every push to any branch +on: + push: + branches: + - '**' # Run on every branch for every commit + pull_request: + branches: + - '**' # Run on every pull request for any branch + +jobs: + test: + runs-on: ubuntu-latest + + steps: + # Step 1: Check out the repository code + - name: Checkout repository + uses: actions/checkout@v3 + + # Step 2: Set up JDK 17 (adjust if you're using a different version) + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + # Step 3: Install Maven 3.9.9 + - name: Setup Maven Action + uses: s4u/setup-maven-action@v1.7.0 + with: + checkout-fetch-depth: 0 + java-version: 17 + java-distribution: temurin + maven-version: 3.9.9 + + # Step 4: Verify Maven installation + - name: Verify Maven version + run: mvn --version + + # Step 5: Cache Maven dependencies + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven + + # Step 4: Run Maven to execute Playwright tests + - name: Run Playwright Tests + run: mvn verify diff --git a/.gitignore b/.gitignore index 2f43530..4ef14e9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ buildNumber.properties .project # JDT-specific (Eclipse Java Development Tools) .classpath +.idea +.allure \ No newline at end of file diff --git a/README.md b/README.md index d3ab88b..5936f54 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,131 @@ -# playwright-in-java-sample-code -Sample code for the Serenity Dojo Playwright In Java course. +# Mastering Modern Test Automation With Playwright In Java + +This repository contains the sample code for the **[Mastering Modern Test Automation With Playwright In Java](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/?referralCode=06560D474D519B88409D)** course. It is designed to complement your learning experience, providing hands-on examples and exercises for each module of the course. + +## About the Course + +Modern web testing demands modern solutions, and Playwright has emerged as the go-to testing framework for dynamic web applications. By combining the power of Playwright with Java's enterprise-grade ecosystem, this course will equip you with the skills needed to build reliable, maintainable test automation frameworks. + +**Key Highlights:** +- Superior auto-wait and asynchronous testing capabilities +- Multi-browser support for Chromium, Firefox, and WebKit +- Network interception and mocking capabilities +- Rich debugging and codegen tools +- Seamless integration with Java tools like JUnit and Cucumber + +You’ll learn: +- Playwright fundamentals, architecture, and best practices +- Writing robust tests for modern web applications +- Using advanced features like API mocking, parallel execution, and CI/CD integration +- Behavior-Driven Development (BDD) using Cucumber +- Generating professional test reports with Allure +- Leveraging AI tools to accelerate test writing + +### Access the Course Material +Enroll in the course and access all the learning resources here: [Mastering Modern Test Automation With Playwright In Java](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/?referralCode=06560D474D519B88409D) + +--- + +## Git Branches and Corresponding Udemy Course Modules + +The branch structure of this repository follows the convention `sample-code/`. Each branch corresponds to a module in the course and contains the sample code for that specific topic. Some of the module branches include: + +The table below maps each Git branch in this repository to its corresponding Udemy course module. Click the links to access the relevant lecture directly. + +| **Module** | **Git Branch** | **Udemy Lecture Link** | +|------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| Start Here | `sample-code/start-here` | [Start Here](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46063725#overview) | +| Module 3 | `sample-code/module-3-my-first-playwright-test` | [My First Playwright Test](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46077299#overview) | +| Module 4 | `sample-code/module-4-interacting-with-elements` | [Interacting With Field Elements](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46649323#overview) | +| Module 5 | `sample-code/module-5-refactoring` | [Refactoring](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46077309#overview) | +| Module 6 | `sample-code/module-6-browser-options` | [Browser Options](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46110387#overview) | +| Module 7 | `sample-code/module-7-browser-contexts` | [Browser Contexts](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46145035#overview) | +| Module 8 | `sample-code/module-8-locators` | [Locators](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46178143#overview) | +| Module 9 | `sample-code/module-9-forms` | [Forms](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46649323#overview) | +| Module 10 | `sample-code/module-10-assertions` | [Assertions](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46280267#overview) | +| Module 11 | `sample-code/module-11-waits` | [Waits](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46680609#overview) | +| Module 12 | `sample-code/module-12-mocking-api-calls` | [Mocking API Calls](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46728327#overview) | +| Module 13 | `sample-code/module-13-page-objects` | [Page Objects](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46723143#overview) | +| Module 14 | `sample-code/module-14-organizing-your-tests` | [Organizing Your Tests](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46833089#overview) | +| Module 15 | `sample-code/module-15-parallel-execution` | [Parallel Execution](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46835495#overview) | +| Module 16 | `sample-code/module-16-allure-reporting` | [Allure Reporting](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46849301#overview) | +| Module 17 | `sample-code/module-17-cucumber` | [Cucumber](https://www.udemy.com/course/mastering-modern-test-automation-with-playwright-in-java/learn/lecture/46906341#overview) | + +--- + +## How to Use This Repository + +The exercises in this repository are designed to build on each other in a sequential order, providing you with a progressive learning experience. Here's how you can make the most of the exercises: + +### Step 1: Starting the Exercises +Begin your journey into Playwright and Java test automation by setting up your workspace. You have two options to start: + +1. **Use the `sample-code/start-here` branch:** + This branch contains a basic starting point with all the necessary configuration files (like a Maven pom.xml file) to help you get started quickly. +2. **Start with an empty Maven project:** + If you prefer to set up everything from scratch, you can create your own Maven project and follow along with the course to configure it step by step. This approach is great for reinforcing your understanding of the setup process. + +### Step 2: Follow Along With the Coding Exercises +Each course module introduces new concepts and techniques, with corresponding exercises for hands-on practice. Follow these steps as you progress: + +1. **Follow the Exercises in Order:** + The modules are designed to build upon each other. Completing them in order ensures you gain a solid understanding of each concept before moving to the next. +2. **Reference the Module-Specific Sample Code:** + Each module's sample code is available in the corresponding branch, named sample-code/. For example: + - sample-code/module-3-my-first-playwright-test + - sample-code/module-6-browser-options + You can check out these branches to view the sample solution for each module if you get stuck or want to see how the exercises are implemented. + +3. **Work on the Exercises in Your Own Branch:** + Create your own branch to experiment with the exercises. For example: +```bash +git checkout -b module-3-exercises +``` +This approach allows you to practice and experiment without affecting the main codebase or other branches. + +Feel free to submit pull requests or raise issues if you find bugs or areas for improvement. + +### Step 3: Using Sample Solutions to Unblock Yourself +If you encounter challenges while working through an exercise: + +1. Check the Sample Solution: + Switch to the branch for the current module to review the sample code and compare it with your own: +```bash +git checkout sample-code/ +``` + +2. **Learn From the Solution:** + Pay attention to how the concepts are applied and implemented. Take note of any differences between your code and the solution, and try to understand why those differences exist. + +### **Step 4: Starting Fresh at Any Point** +If you want to start over or reset your work for a specific module: + +1. **Checkout the Previous Module's Sample Code:** + Each module builds on the previous one, and the starting point for a module is the final state of the previous module. For example: + If you are working on Module 5 and want to start fresh, check out the code from sample-code/module-4-interacting-with-elements. + Use the following command: +```bash +git checkout sample-code/ +``` + +2. **Create a New Branch From the Previous Module:** + After checking out the previous module's sample code, create a new branch to start your work: +```bash +git checkout -b module-5-exercises +``` + +3. **Continue From the Reset State:** + Use the previous module's code as the starting point for the new module, and continue with the exercises. + + +### Step 5: Submitting Your Work +If you are following along as part of the [Serenity Dojo coaching program](http://serenitydojo.academy), you will be able to get feedback about your work from one of the Serenity Dojo coaches. Here is how you do that: + +1. Push Your Changes to Your Repository: + Push your completed exercises to your forked repository to save your work: +```bash +git push origin +``` + +2. Share Your Branch: + If requested, share the branch link with your instructor or team for review. diff --git a/pom.xml b/pom.xml index b57a6f5..8376942 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ com.microsoft.playwright playwright - 1.47.0 + 1.48.0 test @@ -27,6 +27,16 @@ 5.11.1 test + + org.assertj + assertj-core + 3.26.3 + + + com.deque.html.axe-core + playwright + 4.10.0 + \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java b/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java index 2cca58e..424d939 100644 --- a/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java +++ b/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java @@ -1,25 +1,56 @@ package com.serenitydojo.playwright; -import com.microsoft.playwright.Browser; -import com.microsoft.playwright.Page; -import com.microsoft.playwright.Playwright; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.*; +import org.junit.jupiter.api.*; + +import java.util.Arrays; public class ASimplePlaywrightTest { + private static Playwright playwright; + private static Browser browser; + private static BrowserContext browserContext; + + Page page; + + @BeforeAll + public static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions() + .setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox","--disable-extensions","--disable-gpu")) + ); + } + + @BeforeEach + public void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterAll + public static void tearDown() { + browser.close(); + playwright.close(); + } + @Test void shouldShowThePageTitle() { - Playwright playwright = Playwright.create(); - Browser browser = playwright.chromium().launch(); - Page page = browser.newPage(); - page.navigate("https://practicesoftwaretesting.com"); String title = page.title(); - Assertions.assertTrue(title.contains("Practice Software Testing")); + } - browser.close(); - playwright.close(); + @Test + void shouldShowSearchTermsInTheTitle() { + page.navigate("https://practicesoftwaretesting.com"); + page.locator("[placeholder=Search]").fill("Pliers"); + page.locator("button:has-text('Search')").click(); + + int matchingProductCount = page.locator(".card-title").count(); + + Assertions.assertTrue(matchingProductCount > 0); } + } diff --git a/src/test/java/com/serenitydojo/playwright/AccessibilityTest.java b/src/test/java/com/serenitydojo/playwright/AccessibilityTest.java new file mode 100644 index 0000000..d283c46 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/AccessibilityTest.java @@ -0,0 +1,63 @@ +package com.serenitydojo.playwright; + +import com.deque.html.axecore.playwright.AxeBuilder; +import com.deque.html.axecore.results.AxeResults; +import com.microsoft.playwright.*; +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AccessibilityTest { + + private static Playwright playwright; + private static Browser browser; + private static BrowserContext browserContext; + + Page page; + + @BeforeAll + public static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions() + .setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox","--disable-extensions","--disable-gpu")) + ); + browserContext = browser.newContext(); + } + + @BeforeEach + public void setUp() { + page = browserContext.newPage(); + } + + @AfterAll + public static void tearDown() { + browser.close(); + playwright.close(); + } + + // This test will fail + @Test + @Disabled + void homePageShouldNotHaveAccessibilityIssues() { + page.navigate("https://practicesoftwaretesting.com"); + + AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); + + assertThat(accessibilityScanResults.getViolations()).isEmpty(); + } + + @Test + void navBarShouldNotHaveAnyAccessibilityIssues() { + AxeResults accessibilityScanResults = new AxeBuilder(page) + .include(List.of(".navbar-nav")) + .analyze(); + + assertThat(accessibilityScanResults.getViolations()).isEmpty(); + } + +} diff --git a/src/test/java/com/serenitydojo/playwright/AddingItemsToTheCartTest.java b/src/test/java/com/serenitydojo/playwright/AddingItemsToTheCartTest.java new file mode 100644 index 0000000..56e63bb --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/AddingItemsToTheCartTest.java @@ -0,0 +1,95 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.assertions.PlaywrightAssertions; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.LoadState; +import com.microsoft.playwright.options.SelectOption; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +public class AddingItemsToTheCartTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions() + .setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + playwright.selectors().setTestIdAttribute("data-test"); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @DisplayName("Search for pliers") + @Test + void searchForPliers() { + page.onConsoleMessage(msg -> System.out.println(msg.text())); + + page.navigate("https://practicesoftwaretesting.com"); + page.getByPlaceholder("Search").fill("Pliers"); + page.getByPlaceholder("Search").press("Enter"); + + page.waitForLoadState(); + page.waitForCondition( () -> page.getByTestId("product-name").count() > 0); + + List products = page.getByTestId("product-name").allTextContents(); + Assertions.assertThat(products.get(0)).containsIgnoringCase("Pliers"); + + assertThat(page.locator(".card")).hasCount(4); + + List productNames = page.getByTestId("product-name").allTextContents(); + Assertions.assertThat(productNames).allMatch(name -> name.contains("Pliers")); + + Locator outOfStockItem = page.locator(".card") + .filter(new Locator.FilterOptions().setHasText("Out of stock")) + .getByTestId("product-name"); + + assertThat(outOfStockItem).hasCount(1); + assertThat(outOfStockItem).hasText("Long Nose Pliers"); + } + + @Test + @DisplayName("Chaining assertions on product search results with AssertJ") + void chainedAssertionsOnProductSearchResults() { + page.navigate("https://practicesoftwaretesting.com"); + + // Using AssertJ assertions to chain multiple checks on product search results + Locator searchField = page.getByPlaceholder("Search"); + searchField.fill("Pliers"); + searchField.press("Enter"); + + + } + + +} diff --git a/src/test/java/com/serenitydojo/playwright/AnAnnotatedPlaywrightTest.java b/src/test/java/com/serenitydojo/playwright/AnAnnotatedPlaywrightTest.java new file mode 100644 index 0000000..df51af7 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/AnAnnotatedPlaywrightTest.java @@ -0,0 +1,46 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.junit.Options; +import com.microsoft.playwright.junit.OptionsFactory; +import com.microsoft.playwright.junit.UsePlaywright; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +@UsePlaywright(AnAnnotatedPlaywrightTest.MyOptions.class) +public class AnAnnotatedPlaywrightTest { + + public static class MyOptions implements OptionsFactory { + + @Override + public Options getOptions() { + return new Options() +// .setHeadless(true) + .setLaunchOptions( + new BrowserType.LaunchOptions() + .setArgs(Arrays.asList("--no-sandbox","--disable-gpu")) + ); + } + } + @Test + void shouldShowThePageTitle(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + String title = page.title(); + Assertions.assertTrue(title.contains("Practice Software Testing")); + } + + @Test + void shouldShowSearchTermsInTheTitle(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + page.locator("[placeholder=Search]").fill("Pliers"); + page.locator("button:has-text('Search')").click(); + + int matchingProductCount = page.locator(".card-title").count(); + + Assertions.assertTrue(matchingProductCount > 0); + } + +} diff --git a/src/test/java/com/serenitydojo/playwright/MockSearchResponses.java b/src/test/java/com/serenitydojo/playwright/MockSearchResponses.java new file mode 100644 index 0000000..a442d50 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/MockSearchResponses.java @@ -0,0 +1,49 @@ +package com.serenitydojo.playwright; + +public class MockSearchResponses { + public static final String RESPONSE_WITH_A_SINGLE_ENTRY = """ + { + "current_page": 1, + "data": [ + { + "id": "01JBSC2JBTD1HY15BZQR9RMBB8", + "name": "Super Pliers", + "description": "A really good pair of pliers", + "price": 14.15, + "is_location_offer": false, + "is_rental": false, + "in_stock": true, + "product_image": { + "id": "01JBSC2JBJ445KGXKVSE1VDE69", + "by_name": "Helinton Fantin", + "by_url": "https://unsplash.com/@fantin", + "source_name": "Unsplash", + "source_url": "https://unsplash.com/photos/W8BNwvOvW4M", + "file_name": "pliers01.avif", + "title": "Super pliers" + } + } + ], + "from": 1, + "last_page": 1, + "per_page": 9, + "to": 1, + "total": 1 + } + """; + + public static final String RESPONSE_WITH_NO_ENTRIES = """ + { + "current_page": 1, + "data": [], + "from": 1, + "last_page": 1, + "per_page": 9, + "to": 1, + "total": 0 + } + """; + + + +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightAssertionsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightAssertionsTest.java new file mode 100644 index 0000000..8183110 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightAssertionsTest.java @@ -0,0 +1,152 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.assertions.LocatorAssertions; +import com.microsoft.playwright.assertions.PlaywrightAssertions; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.LoadState; +import com.microsoft.playwright.options.SelectOption; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightAssertionsTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + playwright.selectors().setTestIdAttribute("data-test"); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @DisplayName("Making assertions about the contents of a field") + @Nested + class LocatingElementsUsingCSS { + + @BeforeEach + void openContactPage() { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + @DisplayName("Checking the value of a field") + @Test + void fieldValues() { + var firstNameField = page.getByLabel("First name"); + + firstNameField.fill("Sarah-Jane"); + + assertThat(firstNameField).hasValue("Sarah-Jane"); + + assertThat(firstNameField).not().isDisabled(); + assertThat(firstNameField).isVisible(); + assertThat(firstNameField).isEditable(); + } + + @DisplayName("Checking the value of a text field") + @Test + void textFieldValues() { + var messageField = page.getByLabel("Message"); + + messageField.fill("This is my message"); + + assertThat(messageField).hasValue("This is my message"); + } + + @DisplayName("Checking the value of a dropdown field") + @Test + void dropdownFieldValues() { + var subjectField = page.getByLabel("Subject"); + + subjectField.selectOption("Warranty"); + + assertThat(subjectField).hasValue("warranty"); + } + } + + @DisplayName("Making assertions about data values") + @Nested + class MakingAssertionsAboutDataValues { + + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForCondition(() -> page.getByTestId("product-name").count() > 0); + } + + @Test + void allProductPricesShouldBeCorrectValues() { + List prices = page.getByTestId("product-price") + .allInnerTexts() + .stream() + .map(price -> Double.parseDouble(price.replace("$",""))) + .toList(); + + Assertions.assertThat(prices) + .isNotEmpty() + .allMatch(price -> price > 0) + .doesNotContain(0.0) + .allMatch(price -> price < 1000) + .allSatisfy(price -> + Assertions.assertThat(price) + .isGreaterThan(0.0) + .isLessThan(1000.0)); + } + + + @Test + void shouldSortInAlphabeticalOrder() { + page.getByLabel("Sort").selectOption("Name (A - Z)"); + page.waitForLoadState(LoadState.NETWORKIDLE); + + List productNames = page.getByTestId("product-name").allTextContents(); + + Assertions.assertThat(productNames).isSortedAccordingTo(String.CASE_INSENSITIVE_ORDER); + } + + @Test + void shouldSortInReverseAlphabeticalOrder() { + page.getByLabel("Sort").selectOption("Name (Z - A)"); + page.waitForLoadState(LoadState.NETWORKIDLE); + + List productNames = page.getByTestId("product-name").allTextContents(); + + Assertions.assertThat(productNames).isSortedAccordingTo(Comparator.reverseOrder()); + } + + } +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightCollectionsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightCollectionsTest.java new file mode 100644 index 0000000..4f5d555 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightCollectionsTest.java @@ -0,0 +1,129 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.SelectOption; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +public class PlaywrightCollectionsTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + playwright.selectors().setTestIdAttribute("data-test"); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + openPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + private void openPage() { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForCondition(() -> page.getByTestId("product-name").count() > 0); + } + + @DisplayName("Counting items in a list") + @Test + void countingItemsOnThePage() { + + int itemsOnThePage = page.locator(".card").count(); + + Assertions.assertThat(itemsOnThePage).isGreaterThan(0); + } + + @DisplayName("Finding the first matching item") + @Test + void findingTheFirstMatchingItem() { + + page.locator(".card").first().click(); + + } + + @DisplayName("Finding the nth matching item") + @Test + void findingNthMatchingItem() { + + page.locator(".card").nth(2).click(); + + } + + @DisplayName("Finding the last matching item") + @Test + void findingLastMatchingItem() { + + page.locator(".card").last().click(); + + } + + @DisplayName("Finding text in a list") + @Nested + class FindingTheTextInAList { + + @DisplayName("and finding all the text values ") + @Test + void withAllTextContents() { + + List itemNames = page.getByTestId("product-name").allTextContents(); + + + Assertions.assertThat(itemNames).contains(" Combination Pliers ", + " Pliers ", + " Bolt Cutters ", + " Long Nose Pliers ", + " Slip Joint Pliers ", + " Claw Hammer with Shock Reduction Grip ", + " Hammer ", + " Claw Hammer ", + " Thor Hammer "); + } + + @DisplayName("and asserting with hasText") + @Test + void withHasText() { + assertThat(page.getByTestId("product-name")) + .hasText(new String[]{ + " Combination Pliers ", + " Pliers ", + " Bolt Cutters ", + " Long Nose Pliers ", + " Slip Joint Pliers ", + " Claw Hammer with Shock Reduction Grip ", + " Hammer ", + " Claw Hammer ", + " Thor Hammer " + }); + } + } + +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightFormsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightFormsTest.java new file mode 100644 index 0000000..69680bc --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightFormsTest.java @@ -0,0 +1,174 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.options.AriaRole; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightFormsTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @DisplayName("Interacting with text fields") + @Nested + class WhenInteractingWithTextFields { + + @BeforeEach + void openContactPage() { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + @DisplayName("Complete the form") + @Test + void completeForm() throws URISyntaxException { + var firstNameField = page.getByLabel("First name"); + var lastNameField = page.getByLabel("Last name"); + var emailNameField = page.getByLabel("Email"); + var messageField = page.getByLabel("Message"); + var subjectField = page.getByLabel("Subject"); + var uploadField = page.getByLabel("Attachment"); + + firstNameField.fill("Sarah-Jane"); + lastNameField.fill("Smith"); + emailNameField.fill("sarah-jane@example.com"); + messageField.fill("Hello, world!"); + subjectField.selectOption("Warranty"); + + Path fileToUpload = Paths.get(ClassLoader.getSystemResource("data/sample-data.txt").toURI()); + + page.setInputFiles("#attachment", fileToUpload); + + assertThat(firstNameField).hasValue("Sarah-Jane"); + assertThat(lastNameField).hasValue("Smith"); + assertThat(emailNameField).hasValue("sarah-jane@example.com"); + assertThat(messageField).hasValue("Hello, world!"); + assertThat(subjectField).hasValue("warranty"); + + String uploadedFile = uploadField.inputValue(); + org.assertj.core.api.Assertions.assertThat(uploadedFile).endsWith("sample-data.txt"); + } + + @DisplayName("Mandatory fields") + @ParameterizedTest + @ValueSource(strings = {"First name", "Last name", "Email", "Message"}) + void mandatoryFields(String fieldName) { + var firstNameField = page.getByLabel("First name"); + var lastNameField = page.getByLabel("Last name"); + var emailNameField = page.getByLabel("Email"); + var messageField = page.getByLabel("Message"); + var subjectField = page.getByLabel("Subject"); + var sendButton = page.getByText("Send"); + + // Fill in the field values + firstNameField.fill("Sarah-Jane"); + lastNameField.fill("Smith"); + emailNameField.fill("sarah-jane@example.com"); + messageField.fill("Hello, world!"); + subjectField.selectOption("Warranty"); + + // Clear one of the fields + page.getByLabel(fieldName).clear(); + + sendButton.click(); + + // Check the error message for that field + var errorMessage = page.getByRole(AriaRole.ALERT).getByText(fieldName + " is required"); + + assertThat(errorMessage).isVisible(); + } + + @DisplayName("Text fields") + @Test + void textFieldValues() { + var messageField = page.getByLabel("Message"); + + messageField.fill("This is my message"); + + assertThat(messageField).hasValue("This is my message"); + } + + @DisplayName("Dropdown lists") + @Test + void dropdownFieldValues() { + var subjectField = page.getByLabel("Subject"); + + subjectField.selectOption("Warranty"); + + assertThat(subjectField).hasValue("warranty"); + } + + @DisplayName("File uploads") + @Test + void fileUploads() throws URISyntaxException { + var attachmentField = page.getByLabel("Attachment"); + + Path attachment = Paths.get(ClassLoader.getSystemResource("data/sample-data.txt").toURI()); + + page.setInputFiles("#attachment", attachment); + + String uploadedFile = attachmentField.inputValue(); + + org.assertj.core.api.Assertions.assertThat(uploadedFile).endsWith("sample-data.txt"); + } + + + @DisplayName("By CSS class") + @Test + void locateTheSendButtonByCssClass() { + page.locator("#first_name").fill("Sarah-Jane"); + page.locator(".btnSubmit").click(); + List alertMessages = page.locator(".alert").allTextContents(); + Assertions.assertTrue(!alertMessages.isEmpty()); + + } + + @DisplayName("By attribute") + @Test + void locateTheSendButtonByAttribute() { + page.locator("input[placeholder='Your last name *']").fill("Smith"); + assertThat(page.locator("#last_name")).hasValue("Smith"); + } + } +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightLocatorsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightLocatorsTest.java new file mode 100644 index 0000000..6507e53 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightLocatorsTest.java @@ -0,0 +1,396 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.assertions.PlaywrightAssertions; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.LoadState; +import com.microsoft.playwright.options.SelectOption; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightLocatorsTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @DisplayName("Locating elements using CSS") + @Nested + class LocatingElementsUsingCSS { + + @BeforeEach + void openContactPage() { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + @DisplayName("By id") + @Test + void locateTheFirstNameFieldByID() { + page.locator("#first_name").fill("Sarah-Jane"); + assertThat(page.locator("#first_name")).hasValue("Sarah-Jane"); + } + + @DisplayName("By CSS class") + @Test + void locateTheSendButtonByCssClass() { + page.locator("#first_name").fill("Sarah-Jane"); + page.locator(".btnSubmit").click(); + List alertMessages = page.locator(".alert").allTextContents(); + Assertions.assertTrue(!alertMessages.isEmpty()); + + } + + @DisplayName("By attribute") + @Test + void locateTheSendButtonByAttribute() { + page.locator("input[placeholder='Your last name *']").fill("Smith"); + assertThat(page.locator("#last_name")).hasValue("Smith"); + } + } + + @DisplayName("Locating elements by text using CSS") + @Nested + class LocatingElementsByTextUsingCSS { + + @BeforeEach + void openContactPage() { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + // :has-text matches any element containing specified text somewhere inside. + @DisplayName("Using :has-text") + @Test + void locateTheSendButtonByText() { + page.locator("#first_name").fill("Sarah-Jane"); + page.locator("#last_name").fill("Smith"); + page.locator("input:has-text('Send')").click(); + } + + // :text matches the smallest element containing specified text. + @DisplayName("Using :text") + @Test + void locateAProductItemByText() { + page.locator(".navbar :text('Home')").click(); + page.locator(".card :text('Bolt')").click(); + assertThat(page.locator("[data-test=product-name]")).hasText("Bolt Cutters"); + } + + // Exact matches + @DisplayName("Using :text-is") + @Test + void locateAProductItemByTextIs() { + page.locator(".navbar :text('Home')").click(); + page.locator(".card :text-is('Bolt Cutters')").click(); + assertThat(page.locator("[data-test=product-name]")).hasText("Bolt Cutters"); + } + + // matching with regular expressions + @DisplayName("Using :text-matches") + @Test + void locateAProductItemByTextMatches() { + page.locator(".navbar :text('Home')").click(); + page.locator(".card :text-matches('Bolt \\\\w+')").click(); + assertThat(page.locator("[data-test=product-name]")).hasText("Bolt Cutters"); + } + } + + @DisplayName("Locating visible elements") + @Nested + class LocatingVisibleElements { + @BeforeEach + void openContactPage() { + openPage(); + } + + @DisplayName("Finding visible and invisible elements") + @Test + void locateVisibleAndInvisibleItems() { + int dropdownItems = page.locator(".dropdown-item").count(); + Assertions.assertTrue(dropdownItems > 0); + } + + @DisplayName("Finding only visible elements") + @Test + void locateVisibleItems() { + int dropdownItems = page.locator(".dropdown-item:visible").count(); + Assertions.assertTrue(dropdownItems == 0); + } + } + + @DisplayName("Locating elements by role") + @Nested + class LocatingElementsByRole { + + @DisplayName("Using the BUTTON role") + @Test + void byButton() { + page.navigate("https://practicesoftwaretesting.com/contact"); + + + page.getByRole(AriaRole.BUTTON, + new Page.GetByRoleOptions().setName("Send")) + .click(); + + List errorMessages = page.getByRole(AriaRole.ALERT).allTextContents(); + Assertions.assertTrue(!errorMessages.isEmpty()); + } + + @DisplayName("Using the HEADING role") + @Test + void byHeaderRole() { + openPage(); + + page.locator("#search-query").fill("Pliers"); + + page.getByRole(AriaRole.BUTTON, + new Page.GetByRoleOptions().setName("Search")) + .click(); + + Locator searchHeading = page.getByRole(AriaRole.HEADING, + new Page.GetByRoleOptions().setName(Pattern.compile("Searched for:.*"))); + + assertThat(searchHeading).isVisible(); + assertThat(searchHeading).hasText("Searched for: Pliers"); + } + + @DisplayName("Using the HEADING role and level") + @Test + void byHeaderRoleLevel() { + openPage(); + + List level4Headings + = page.getByRole(AriaRole.HEADING, + new Page.GetByRoleOptions() + .setName("Pliers") + .setLevel(5)) + .allTextContents(); + + org.assertj.core.api.Assertions.assertThat(level4Headings).isNotEmpty(); + } + + @DisplayName("Identifying checkboxes") + @Test + void byCheckboxes() { + playwright.selectors().setTestIdAttribute("data-test"); + + openPage(); + page.getByLabel("Hammer").click(); + page.getByLabel("Chisels").click(); + page.getByLabel("Wrench").click(); + + int selectedCount = + page.getByTestId("filters"). + getByRole(AriaRole.CHECKBOX, + new Locator.GetByRoleOptions().setChecked(true)) + .count(); + + org.assertj.core.api.Assertions.assertThat(selectedCount).isEqualTo(3); + + List selectedOptions = + page.getByTestId("filters"). + getByRole(AriaRole.CHECKBOX, + new Locator.GetByRoleOptions().setChecked(true)) + .all() + .stream() + .map(Locator::inputValue) + .toList(); + + org.assertj.core.api.Assertions.assertThat(selectedOptions).hasSize(3); + } + } + + @DisplayName("Locating elements by placeholders and labels") + @Nested + class LocatingElementsByPlaceholdersAndLabels { + + @DisplayName("Using a label") + @Test + void byLabel() { + page.navigate("https://practicesoftwaretesting.com/contact"); + + page.getByLabel("First name").fill("Obi-Wan"); + page.getByLabel("Last name").fill("Kenobi"); + page.getByLabel("Email address").fill("obi-wan@kenobi.com"); + page.getByLabel("Subject").selectOption(new SelectOption().setLabel("Customer service")); + page.getByLabel("Message *").fill("Hello there"); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Send")); + } + + @DisplayName("Using a placeholder text") + @Test + void byPlaceholder() { + page.navigate("https://practicesoftwaretesting.com/contact"); + + page.getByPlaceholder("Your first name").fill("Obi-Wan"); + + page.getByPlaceholder("Your last name").fill("Kenobi"); + page.getByPlaceholder("Your email").fill("obi-wan@kenobi.com"); + page.getByLabel("Subject").selectOption(new SelectOption().setLabel("Customer service")); + page.getByLabel("Message *").fill("Hello there"); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Send")); + } + } + + @DisplayName("Locating elements by text") + @Nested + class LocatingElementsByText { + + @BeforeEach + void openTheCatalogPage() { + openPage(); + } + + @DisplayName("Locating an element by text contents") + @Test + void byText() { + page.getByText("Bolt Cutters").click(); + + PlaywrightAssertions.assertThat(page.getByText("MightyCraft Hardware")).isVisible(); + } + + @DisplayName("Using alt text") + @Test + void byAltText() { + page.getByAltText("Combination Pliers").click(); + + PlaywrightAssertions.assertThat(page.getByText("ForgeFlex Tools")).isVisible(); + } + + @DisplayName("Using title") + @Test + void byTitle() { + page.getByAltText("Combination Pliers").click(); + + page.getByTitle("Practice Software Testing - Toolshop").click(); + } + } + + @DisplayName("Locating elements by test Id") + @Nested + class LocatingElementsByTestID { + + @BeforeAll + static void setTestId() { + playwright.selectors().setTestIdAttribute("data-test"); + } + + @DisplayName("Using a custom data-test field") + @Test + void byTestId() { + openPage(); + + playwright.selectors().setTestIdAttribute("data-test"); + + page.getByTestId("search-query").fill("Pliers"); + page.getByTestId("search-submit").click(); + } + + } + + @DisplayName("Nested locators") + @Nested + class NestedLocators { + + @BeforeAll + static void setTestId() { + playwright.selectors().setTestIdAttribute("data-test"); + } + + @DisplayName("Using roles") + @Test + void locatingAMenuItemUsingRoles() { + openPage(); + + page.getByRole(AriaRole.MENUBAR, new Page.GetByRoleOptions().setName("Main Menu")) + .getByRole(AriaRole.MENUITEM, new Locator.GetByRoleOptions().setName("Home")) + .click(); + } + + @DisplayName("Using roles with other strategies") + @Test + void locatingAMenuItemUsingRolesAndOtherStrategies() { + openPage(); + + page.getByRole(AriaRole.MENUBAR, new Page.GetByRoleOptions().setName("Main Menu")) + .getByText("Home") + .click(); + } + + @DisplayName("filtering locators by text") + @Test + void filteringMenuItems() { + openPage(); + + page.getByRole(AriaRole.MENUBAR, new Page.GetByRoleOptions().setName("Main Menu")) + .getByText("Categories") + .click(); + + page.getByRole(AriaRole.MENUBAR, new Page.GetByRoleOptions().setName("Main Menu")) + .getByText("Power Tools") + .click(); + + page.waitForCondition(() -> page.getByTestId("product-name").count() > 0); + + List allProducts = page.getByTestId("product-name") + .filter(new Locator.FilterOptions().setHasText("Sander")) + .allTextContents(); + + org.assertj.core.api.Assertions.assertThat(allProducts).allMatch(name -> name.contains("Sander")); + } + + @DisplayName("filtering locators by locator") + @Test + void filteringMenuItemsByLocator() { + openPage();; + + List allProducts = page.locator(".card") + .filter(new Locator.FilterOptions().setHas(page.getByText("Out of stock"))) + .getByTestId("product-name") + .allTextContents(); + + org.assertj.core.api.Assertions.assertThat(allProducts).hasSize(1) + .allMatch(name -> name.contains("Long Nose Pliers")); + } + } + + private void openPage() { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForLoadState(LoadState.NETWORKIDLE); + } +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightRestAPITest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightRestAPITest.java new file mode 100644 index 0000000..0d83694 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightRestAPITest.java @@ -0,0 +1,155 @@ +package com.serenitydojo.playwright; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.microsoft.playwright.*; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + + +import java.util.Arrays; +import java.util.HashMap; +import java.util.stream.Stream; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightRestAPITest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + playwright.selectors().setTestIdAttribute("data-test"); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + + page.navigate("https://practicesoftwaretesting.com"); + page.getByPlaceholder("Search").waitFor(); + + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @DisplayName("Playwright allows us to mock out API responses") + @Nested + class MockingAPIResponses { + + @Test + @DisplayName("When a search returns a single product") + void whenASingleItemIsFound() { + page.route("**/products/search?q=pliers", + route -> route.fulfill(new Route.FulfillOptions() + .setBody(MockSearchResponses.RESPONSE_WITH_A_SINGLE_ENTRY) + .setStatus(200)) + ); + + var searchBox = page.getByPlaceholder("Search"); + searchBox.fill("pliers"); + searchBox.press("Enter"); + + assertThat(page.getByTestId("product-name")).hasCount(1); + assertThat(page.getByTestId("product-name") + .filter(new Locator.FilterOptions().setHasText("Super Pliers"))) + .isVisible(); + } + + @Test + @DisplayName("When a search returns no products") + void whenNoItemsAreFound() { + page.route("**/products/search?q=pliers", + route -> route.fulfill(new Route.FulfillOptions() + .setBody(MockSearchResponses.RESPONSE_WITH_NO_ENTRIES) + .setStatus(200)) + ); + var searchBox = page.getByPlaceholder("Search"); + searchBox.fill("pliers"); + searchBox.press("Enter"); + + assertThat(page.getByTestId("product-name")).isHidden(); + assertThat(page.getByTestId("search_completed")).hasText("There are no products found."); + } + } + + @Nested + class MakingAPICalls { + + record Product(String name, Double price) {} + + private static APIRequestContext requestContext; + + @BeforeAll + public static void setupRequestContext() { + requestContext = playwright.request().newContext( + new APIRequest.NewContextOptions() + .setBaseURL("https://api.practicesoftwaretesting.com") + .setExtraHTTPHeaders(new HashMap<>() {{ + put("Accept", "application/json"); + }}) + ); + } + + @DisplayName("Check presence of known products") + @ParameterizedTest(name = "Checking product {0}") + @MethodSource("products") + void checkKnownProduct(Product product) { + page.fill("[placeholder='Search']", product.name); + page.click("button:has-text('Search')"); + + // Check that the product appears with the correct name and price + + Locator productCard = page.locator(".card") + .filter( + new Locator.FilterOptions() + .setHasText(product.name) + .setHasText(Double.toString(product.price)) + ); + assertThat(productCard).isVisible(); + } + + static Stream products() { + APIResponse response = requestContext.get("/products?page=2"); + Assertions.assertThat(response.status()).isEqualTo(200); + + JsonObject jsonObject = new Gson().fromJson(response.text(), JsonObject.class); + JsonArray data = jsonObject.getAsJsonArray("data"); + + return data.asList().stream() + .map(jsonElement -> { + JsonObject productJson = jsonElement.getAsJsonObject(); + return new Product( + productJson.get("name").getAsString(), + productJson.get("price").getAsDouble() + ); + }); + } + + } +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightWaitsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightWaitsTest.java new file mode 100644 index 0000000..9463a15 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightWaitsTest.java @@ -0,0 +1,158 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.WaitForSelectorState; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +import java.util.Arrays; +import java.util.List; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightWaitsTest { + + protected static Playwright playwright; + protected static Browser browser; + protected static BrowserContext browserContext; + + Page page; + + @BeforeAll + static void setUpBrowser() { + playwright = Playwright.create(); + playwright.selectors().setTestIdAttribute("data-test"); + browser = playwright.chromium().launch( + new BrowserType.LaunchOptions().setHeadless(true) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ); + } + + @BeforeEach + void setUp() { + browserContext = browser.newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + browserContext.close(); + } + + @AfterAll + static void tearDown() { + browser.close(); + playwright.close(); + } + + @Nested + class WaitingForState { + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForSelector(".card-img-top"); + } + + @Test + void shouldShowAllProductNames() { + List productNames = page.getByTestId("product-name").allInnerTexts(); + Assertions.assertThat(productNames).contains("Pliers", "Bolt Cutters", "Hammer"); + } + + @Test + void shouldShowAllProductImages() { + List productImageTitles = page.locator(".card-img-top").all() + .stream() + .map(img -> img.getAttribute("alt")) + .toList(); + + Assertions.assertThat(productImageTitles).contains("Pliers", "Bolt Cutters", "Hammer"); + } + } + + @Nested + class AutomaticWaits { + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + // Automatic wait + @Test + @DisplayName("Should wait for the filter checkbox options to appear before clicking") + void shouldWaitForTheFilterCheckboxes() { + + var screwdriverFilter = page.getByLabel("Screwdriver"); + + screwdriverFilter.click(); + + assertThat(screwdriverFilter).isChecked(); + } + + @Test + @DisplayName("Should filter products by category") + void shouldFilterProductsByCategory() { + page.getByRole(AriaRole.MENUBAR).getByText("Categories").click(); + page.getByRole(AriaRole.MENUBAR).getByText("Power Tools").click(); + + page.waitForSelector(".card", + new Page.WaitForSelectorOptions().setState(WaitForSelectorState.VISIBLE).setTimeout(2000) + ); + + var filteredProducts = page.getByTestId("product-name").allInnerTexts(); + + Assertions.assertThat(filteredProducts).contains("Sheet Sander", "Belt Sander","Random Orbit Sander"); + + } + } + + @Nested + class WaitingForElementsToAppearAndDisappear { + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + @Test + @DisplayName("It should display a toaster message when an item is added to the cart") + void shouldDisplayToasterMessage() { + page.getByText("Bolt Cutters").click(); + page.getByText("Add to cart").click(); + + // Wait for the toaster message to appear + assertThat(page.getByRole(AriaRole.ALERT)).isVisible(); + assertThat(page.getByRole(AriaRole.ALERT)).hasText("Product added to shopping cart."); + + page.waitForCondition( () -> page.getByRole(AriaRole.ALERT).isHidden() ); + + } + + @Test + @DisplayName("Should update the cart item count") + void shouldUpdateCartItemCount() { + page.getByText("Bolt Cutters").click(); + page.getByText("Add to cart").click(); + + page.waitForCondition( () -> page.getByTestId("cart-quantity").textContent().equals("1")); + // page.waitForSelector("[data-test=cart-quantity]:has-text('1')"); + } + + // Wait for an element to have a particular state + @Test + @DisplayName("It should display a toaster message when an item is added to the cart") + void shouldDisplayTheCartItemCount() { + page.getByText("Bolt Cutters").click(); + page.getByText("Add to cart").click(); + + // Wait for the cart quantity to update + page.waitForCondition(() -> page.getByTestId("cart-quantity").textContent().equals("1")); + // Or + page.waitForSelector("[data-test='cart-quantity']:has-text('1')"); + } + + } +} \ No newline at end of file diff --git a/src/test/resources/data/sample-data.txt b/src/test/resources/data/sample-data.txt new file mode 100644 index 0000000..e69de29 diff --git a/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowSearchTermsInTheTitle-chromium/trace.zip b/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowSearchTermsInTheTitle-chromium/trace.zip new file mode 100644 index 0000000..a8d5997 Binary files /dev/null and b/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowSearchTermsInTheTitle-chromium/trace.zip differ diff --git a/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowThePageTitle-chromium/trace.zip b/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowThePageTitle-chromium/trace.zip new file mode 100644 index 0000000..e8c7aa3 Binary files /dev/null and b/test-results/com.serenitydojo.playwright.ASimplePlaywrightTest.shouldShowThePageTitle-chromium/trace.zip differ