diff --git a/pom.xml b/pom.xml index 497ee081..9d040976 100644 --- a/pom.xml +++ b/pom.xml @@ -9,16 +9,20 @@ 1.0-SNAPSHOT - 21 - 21 + 21 UTF-8 + + 2.35.2 + 2.15.2 + 1.60.0 + 1.9.24 com.microsoft.playwright playwright - 1.60.0 + ${playwright.version} test @@ -27,6 +31,131 @@ 5.11.3 test + + org.assertj + assertj-core + 3.27.7 + test + + + io.qameta.allure + allure-jupiter + test + + + + net.datafaker + datafaker + 2.7.0 + test + + + org.aspectj + aspectjweaver + ${aspectj.version} + test + + + + + + io.qameta.allure + allure-bom + ${allure.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + + ${maven.compiler.release} + + + -parameters + + + true + + + /Users/erhanbatu/Library/Java/JavaVirtualMachines/temurin-21.0.11/Contents/Home/bin/javac + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.5.6 + + + + + integration-test + verify + + + + + + + /Users/erhanbatu/Library/Java/JavaVirtualMachines/temurin-21.0.11/Contents/Home/bin/java + + + + -javaagent:${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar + + + + **/*Test.java + **/*Tests.java + + + + + ${project.build.directory}/allure-results + + + + + + + io.qameta.allure + allure-maven + ${allure.maven.version} + + 2.44.0 + ${project.build.directory}/allure-results + + + + allure-reports + post-integration-test + + report + + + + + + + + \ 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 c50e7ea0..5fc6f5f9 100644 --- a/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java +++ b/src/test/java/com/serenitydojo/playwright/ASimplePlaywrightTest.java @@ -1,15 +1,67 @@ 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 com.microsoft.playwright.junit.UsePlaywright; +import org.junit.jupiter.api.*; +import java.util.Arrays; + +//This annotation allows you to do test without creating playwright environment and browser +//@UsePlaywright 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(false) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions","--disable-gpu")) + ); + //this allows paralel execution + browserContext = browser.newContext(); + + } + + @BeforeEach + public void setUp(){ + page = browser.newPage(); + } + + @AfterAll + public static void tearDown(){ + browser.close(); + playwright.close(); + } + @Test void shouldShowThePageTitle() { // TODO: Write your first playwright test here + + page.navigate("https://practicesoftwaretesting.com/"); + String title = page.title(); + Assertions.assertTrue(title.contains("Practice Software Testing")); + + } + + @Test + void shouldSearchByKeyword(){ + + page.navigate("https://practicesoftwaretesting.com/"); + page.locator("[placeholder=Search]").fill("Pliers"); + //this is basically sort of xpath + page.locator("button:has-text('Search')").click(); + + //this will look for the element with card + int matchingSearchResults = page.locator(".card").count(); + System.out.println(matchingSearchResults); + + Assertions.assertTrue(matchingSearchResults>0); + } } 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 00000000..4293f096 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/AddingItemsToTheCartTest.java @@ -0,0 +1,47 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.AriaRole; +import org.junit.jupiter.api.*; + +import java.util.Arrays; +import java.util.List; + +@UsePlaywright(HeadlessChromeOptions.class) +public class AddingItemsToTheCartTest { + + @DisplayName("Search for pliers") + @Test + void searchForPliers(Page page){ + page.navigate("https://practicesoftwaretesting.com/"); +// page.locator("[placeholder='Search']").fill("pliers"); + page.getByPlaceholder("Search").fill("pliers"); + page.getByRole(AriaRole.BUTTON, + new Page.GetByRoleOptions().setName("Search")).click(); + + Locator products = page.locator(".card").filter( + new Locator.FilterOptions() + .setHas(page.getByAltText("Pliers")) + .setHasNot(page.getByText("Out of stock")) + ); + + List productNames = products.locator("h5").allTextContents(); + System.out.println(productNames); + + } + + + + + + + + + + + + + + +} 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 00000000..a7622d38 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/AnAnnotatedPlaywrightTest.java @@ -0,0 +1,57 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.junit.Options; +import com.microsoft.playwright.junit.OptionsFactory; +import com.microsoft.playwright.junit.UsePlaywright; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +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(false) + .setLaunchOptions( + new BrowserType.LaunchOptions() + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions","--disable-gpu")) + ); + } + } + @Test + void shouldShowThePageTitle(Page page) { + // TODO: Write your first playwright test here + + page.navigate("https://practicesoftwaretesting.com/"); + String title = page.title(); + Assertions.assertTrue(title.contains("Practice Software Testing")); + + } + + @Test + void shouldSearchByKeyword(Page page){ + + page.navigate("https://practicesoftwaretesting.com/"); + page.locator("[placeholder=Search]").fill("Pliers"); + //this is basically sort of xpath + page.locator("button:has-text('Search')").click(); + + //this will look for the element with card + int matchingSearchResults = page.locator(".card").count(); + System.out.println(matchingSearchResults); + + Assertions.assertTrue(matchingSearchResults>0); + + } +} diff --git a/src/test/java/com/serenitydojo/playwright/HeadlessChromeOptions.java b/src/test/java/com/serenitydojo/playwright/HeadlessChromeOptions.java new file mode 100644 index 00000000..44ac2a39 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/HeadlessChromeOptions.java @@ -0,0 +1,23 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.junit.Options; +import com.microsoft.playwright.junit.OptionsFactory; + +import java.util.Arrays; + +public class HeadlessChromeOptions implements OptionsFactory { + + + @Override + public Options getOptions() { + return new Options().setLaunchOptions( + new BrowserType.LaunchOptions().setHeadless(false) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ) + .setTestIdAttribute("data-test"); + } + + + +} 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 00000000..56e44a12 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightAssertionsTest.java @@ -0,0 +1,112 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.LoadState; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Comparator; +import java.util.List; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@UsePlaywright(HeadlessChromeOptions.class) +public class PlaywrightAssertionsTest { + + @DisplayName("Making assertions about the contents of a field") + @Nested + class LocationgElementsUsingCSS { + + @BeforeEach + void openContactPage(Page page) { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + @DisplayName("Checking the value of a field") + @Test + void fieldValues(Page page){ + + var firstNameField = page.getByLabel("First Name"); + + firstNameField.fill("Erhan"); + + assertThat(firstNameField).hasValue("Erhan"); + + assertThat(firstNameField).not().isDisabled(); + assertThat(firstNameField).isVisible(); + assertThat(firstNameField).isEditable(); + + } + + + + } + + + @DisplayName("Making assertions about data values") + @Nested + class MakingAssertionsAboutDataValues { + + @BeforeEach + void openHomePage(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForCondition(() -> page.getByTestId("product-name").count() > 0); + } + + @Test + void allProductPricesShouldBeCorrectValues(Page page){ + + List prices = page.getByTestId("product-price") + .allTextContents() + .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 page){ + + page.getByLabel("Sort").selectOption("Name (A - Z)"); + page.waitForLoadState(LoadState.NETWORKIDLE); + + List productNames = page.getByTestId("product-name").allTextContents(); + +// Assertions.assertThat(productNames).isSortedAccordingTo(Comparator.naturalOrder()); + Assertions.assertThat(productNames).isSortedAccordingTo(String.CASE_INSENSITIVE_ORDER); + + } + + @Test + void shouldSortInReverseAlphabeticalOrder(Page page){ + + 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/PlaywrightFormsTest.java b/src/test/java/com/serenitydojo/playwright/PlaywrightFormsTest.java new file mode 100644 index 00000000..73f0dbf4 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightFormsTest.java @@ -0,0 +1,161 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.AriaRole; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@UsePlaywright(HeadlessChromeOptions.class) +public class PlaywrightFormsTest { + + @DisplayName("Interacting with text fields") + @Nested + class WhenInteractingWithTextFields { + + @BeforeEach + void openContactPage(Page page) { + page.navigate("https://practicesoftwaretesting.com/contact"); + } + + @DisplayName("Complete the form") + @Test + void completeForm(Page page) 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("Erhan"); + lastNameField.fill("Batu"); + emailNameField.fill("erbatu@gmail.com"); + messageField.fill("Hello World!"); + subjectField.selectOption("Warranty"); + + Path fileToUpload = Path.of( + ClassLoader.getSystemResource("data/sample-data.txt").toURI() + ); + + page.setInputFiles("#attachment", fileToUpload); + + + assertThat(firstNameField).hasValue("Erhan"); + assertThat(lastNameField).hasValue("Batu"); + assertThat(subjectField).hasValue("warranty"); + + String uploadFile = uploadField.inputValue(); + org.assertj.core.api.Assertions.assertThat(uploadFile).endsWith("sample-data.txt"); + + + + } + + @DisplayName("Mandatory Fields") + @ParameterizedTest + @ValueSource(strings = {"First name", "Last name"}) + void mandatoryFields(String fieldName, Page page){ + 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"); + + firstNameField.fill("Erhan"); + lastNameField.fill("Batu"); + emailNameField.fill("erbatu@gmail.com"); + messageField.fill("Hello World!"); + subjectField.selectOption("Warranty"); + + page.getByLabel(fieldName).clear(); + + sendButton.click(); + + var errorMessage = page.getByRole(AriaRole.ALERT).getByText(fieldName + " is required"); + + assertThat(errorMessage).isVisible(); + + + } + + @DisplayName("Categories Tests") + @Test + void categoriesTests(Page page){ + + page.getByRole(AriaRole.BUTTON).getByText("Categories").click(); + page.getByLabel("nav-categories").getByText("Power Tools").click(); + + assertThat(page).hasTitle(Pattern.compile(".*Power Tools.*")); + + String circularSawPrice = page.locator(".card").filter( + new Locator.FilterOptions() + .setHasText("circular saw")) + .locator("[data-test='product-price']").innerText(); + System.out.println(circularSawPrice); + + Assertions.assertEquals(circularSawPrice, "$80.19"); + + + } + + @DisplayName("CheckBox Test") + @Test + void checkBoxTest(Page page){ + + page.getByRole(AriaRole.BUTTON).getByText("Categories").click(); + page.getByLabel("nav-categories").getByText("Power Tools").click(); + + page.getByRole(AriaRole.LIST).locator(".checkbox") + .filter( + new Locator.FilterOptions().setHasText("Grinder") + ).locator("input[type='checkbox']").check(); + + + assertThat(page.getByText("There are no products found")).isVisible(); + + page.getByRole(AriaRole.LIST).locator(".checkbox").filter( + new Locator.FilterOptions().setHasText("Sander") + ).locator("[name='category_id']").check(); + + Locator productCards = page.locator(".card-img-top"); + + assertThat(productCards).hasCount(3); + + System.out.println(productCards.count()); + + } + + @DisplayName("Dropdowns Test") + @Test + void dropDownTesting(Page page){ + + page.getByRole(AriaRole.BUTTON).getByText("Categories").click(); + page.getByLabel("nav-categories").getByText("Power Tools").click(); + + page.getByRole(AriaRole.COMBOBOX).selectOption("Price (High - Low)"); + + } + + + + + + } + + + + + + + +} diff --git a/src/test/java/com/serenitydojo/playwright/PlaywrightLocators.java b/src/test/java/com/serenitydojo/playwright/PlaywrightLocators.java new file mode 100644 index 00000000..c09d4eb5 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightLocators.java @@ -0,0 +1,175 @@ +package com.serenitydojo.playwright; + +import com.microsoft.playwright.*; +import com.microsoft.playwright.assertions.PlaywrightAssertions; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.Nested; + +import java.util.Arrays; +import java.util.List; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@Execution(ExecutionMode.SAME_THREAD) +public class PlaywrightLocators { + + + 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("[placeholder='Your last name *']").fill("Smith"); + PlaywrightAssertions.assertThat(page.locator("#last_name")).hasValue("Smith"); + } + + + + + + + + + + + + } + + + + + + + + + + + + + + + + + + + @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(); + + assertThat(page.getByText("MightyCraft Hardware")).isVisible(); + } + + @DisplayName("Using alt text") + @Test + void byAltText() { + page.getByAltText("Combination Pliers").click(); + + 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(); + } + + } + + + + private void openPage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + +} 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 00000000..90397442 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/PlaywrightWaitsTest.java @@ -0,0 +1,172 @@ +package com.serenitydojo.playwright; + + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.AriaRole; +import com.microsoft.playwright.options.WaitForSelectorState; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Comparator; +import java.util.List; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@UsePlaywright(HeadlessChromeOptions.class) +public class PlaywrightWaitsTest { + + + @DisplayName("Explicit Wait") + @Nested + class ExplicitWait { + @BeforeEach + void openContactPage(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + page.waitForSelector("[data-test=product-name]"); + page.waitForSelector(".card-img-top"); + } + + @Test + void shouldShowAllProductName(Page page){ + List productNames = page.getByTestId("product-name").allInnerTexts(); + Assertions.assertThat(productNames).contains("Pliers", "Bolt Cutters"); + + } + + @Test + void shouldShowAllProductImages(Page page){ + List productImageTitles = page.locator(".card-img-top").all() + .stream() + .map(img -> img.getAttribute("alt")) + .toList(); + + Assertions.assertThat(productImageTitles).contains("Bolt Cutters"); + } + + } + + @DisplayName("Implicit Wait") + @Nested + class ImplicitWait{ + + @BeforeEach + void openContactPage(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + + } + + @Test + void shouldWaitForTheFilterCheckboxes(Page page){ + + var screwDriverFilter = page.getByLabel("Screwdriver"); + + screwDriverFilter.click(); + + assertThat(screwDriverFilter).isChecked(); + } + + @Test + void shouldFilterProductsByCategory(Page page){ + + 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"); + + } + + + + } + + + @Nested + class WaitingForElementsToAppearAndDisappear{ + + @BeforeEach + void openContactPage(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + + } + + @Test + void shouldDisplayToasterMessage(Page page){ + + page.getByText("Bolt Cutters").click(); + page.getByText("Add to cart").click(); + + 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 + void shoudlUpdateTheCartItemCount(Page page){ + + 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')"); + + } + + + + + } + + + @Nested + class WaitingForAPICalls { + + @Test + void sortByDescendingPrice(Page page) { + page.navigate("https://practicesoftwaretesting.com"); + + // Sort by descending price + // (Note: The API endpoint has evolved and is slightly different to the one in the video; + // it now sends the sort/page criteria in the request body rather than as URL query params) + page.waitForResponse( + response -> response.url().contains("/products") + && response.request().postData() != null + && response.request().postData().contains("\"sort\":\"price,desc\""), + () -> { + page.getByTestId("sort").selectOption("Price (High - Low)"); + }); + + // Find all the prices on the page + var productPrices = page.getByTestId("product-price") + .allInnerTexts() + .stream() + .map(WaitingForAPICalls::extractPrice) + .toList(); + + // Are the prices in the correct order + Assertions.assertThat(productPrices) + .isNotEmpty() + .isSortedAccordingTo(Comparator.reverseOrder()); + } + + private static double extractPrice(String price) { + return Double.parseDouble(price.replace("$", "")); + } + } + + + + + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginPage.java b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginPage.java new file mode 100644 index 00000000..f9fec9d2 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginPage.java @@ -0,0 +1,37 @@ +package com.serenitydojo.playwright.toolshop.apiLogin; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.AriaRole; +import com.serenitydojo.playwright.toolshop.domain.User; + +public class LoginPage { + + private final Page page; + + public LoginPage(Page page) { + this.page = page; + } + + public void open(){ + page.navigate("https://practicesoftwaretesting.com/auth/login"); + } + + public void loginAs(User user) { + + page.getByPlaceholder("Your email").fill(user.email()); + page.getByPlaceholder("Your password").fill(user.password()); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Login")).click(); + + + } + + + + public String title() { + return page.getByTestId("page-title").textContent(); + } + + public String loginErrorMessage() { + return page.getByTestId("login-error").textContent(); + } +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginWithRegisteredUserTest.java b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginWithRegisteredUserTest.java new file mode 100644 index 00000000..6bb9b3a1 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/LoginWithRegisteredUserTest.java @@ -0,0 +1,43 @@ +package com.serenitydojo.playwright.toolshop.apiLogin; + +import com.serenitydojo.playwright.toolshop.domain.User; +import com.serenitydojo.playwright.toolshop.fixtures.PlaywrightTestCase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LoginWithRegisteredUserTest extends PlaywrightTestCase { + + @Test + @DisplayName("Should be able to login with a registered user") + void should_login_with_registered_user(){ + + User user = User.randomUser(); + UserAPIClient userAPIClient = new UserAPIClient(page); + userAPIClient.registerUser(user); + + LoginPage loginPage = new LoginPage(page); + loginPage.open(); + loginPage.loginAs(user); + + assertThat(loginPage.title()).isEqualTo("My account"); + + } + + @Test + void should_reject_user_with_invalid_password(){ + + User user = User.randomUser(); + UserAPIClient userAPIClient = new UserAPIClient(page); + userAPIClient.registerUser(user); + + LoginPage loginPage = new LoginPage(page); + loginPage.open(); + loginPage.loginAs(user.withPassword("wrong-password")); + + assertThat(loginPage.loginErrorMessage()).isEqualTo("Invalid email or password"); + } + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/RegisterAPITest.java b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/RegisterAPITest.java new file mode 100644 index 00000000..f1194cb7 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/RegisterAPITest.java @@ -0,0 +1,127 @@ +package com.serenitydojo.playwright.toolshop.apiLogin; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.microsoft.playwright.APIRequest; +import com.microsoft.playwright.APIRequestContext; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.RequestOptions; +import com.serenitydojo.playwright.toolshop.domain.Address; +import com.serenitydojo.playwright.toolshop.domain.User; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +@UsePlaywright +public class RegisterAPITest { + + private APIRequestContext request; + private Gson gson = new Gson(); + @BeforeEach + void setup(Playwright playwright){ + request = playwright.request().newContext( + new APIRequest.NewContextOptions() + .setBaseURL("https://api.practicesoftwaretesting.com") + ); + } + + @AfterEach + void tearDown(){ + if (request != null){ + request.dispose(); + } + } + + + @Test + void should_register_user(){ + User validUser = User.randomUser(); + + var response = request.post("/users/register", + RequestOptions.create() + .setHeader("Content-Type", "application/json") + .setData(validUser) + ); + + String responseBody = response.text(); + + System.out.println("Request user:"); + System.out.println(new Gson().toJson(validUser)); + + System.out.println("Response status: " + response.status()); + System.out.println("Response body:"); + System.out.println(responseBody); + + + User createdUser = gson.fromJson(responseBody, User.class); + + JsonObject responseObject = gson.fromJson(responseBody, JsonObject.class); + + + assertSoftly(softly -> { + softly.assertThat(response.status()) + .as("Registration should return 201 created status code") + .isEqualTo(201); + + softly.assertThat(createdUser) + .isEqualTo(validUser.withPassword(null)); + + //response object doesn't have password field + assertThat(responseObject.has("password")) + .isFalse(); + + softly.assertThat(responseObject.get("id").getAsString()) + .as("Register user Id") + .isNotEmpty(); + + + } + ); + + + } + + @Test + void firstNameIsMandatory(){ + + Address address = new Address("street", "44","mly","ttt","nl","77kk"); + + User userWithNoName = new User( + null, + "Smith", + address, + "+316878996789", + "1990-01-01", + "78678kjhkAs#$", + "tyfteyt@gmail.com" + ); + + var response = request.post("/users/register", + RequestOptions.create() + .setHeader("Content-Type", "application/json") + .setData(userWithNoName) + ); + + assertSoftly(softly -> { + softly.assertThat(response.status()).isEqualTo(422); + + JsonObject responseObject = gson.fromJson(response.text(), JsonObject.class); + softly.assertThat(responseObject.has("first_name")).isTrue(); + + String errorMessage = responseObject.get("first_name").getAsString(); + softly.assertThat(errorMessage).isEqualTo("The first name field is required."); + + }); + + + + } + + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/UserAPIClient.java b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/UserAPIClient.java new file mode 100644 index 00000000..8f201352 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/apiLogin/UserAPIClient.java @@ -0,0 +1,32 @@ +package com.serenitydojo.playwright.toolshop.apiLogin; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.RequestOptions; +import com.serenitydojo.playwright.toolshop.domain.User; + +import java.util.Arrays; + +public class UserAPIClient { + + private final Page page; + private static final String REGISTER_USER = "https://api.practicesoftwaretesting.com/users/register"; + public UserAPIClient(Page page) { + this.page = page; + } + + public void registerUser(User user) { + + var response = page.request().post( + REGISTER_USER, + RequestOptions.create() + .setData(user) + .setHeader("Content-Type", "application/json") + .setHeader("accept","application/json") + + ); + System.out.println(response.text()); + if(response.status() != 201){ + throw new IllegalStateException("Unexpected result: " + response.status()); + } + } +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/AddToCartTest.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/AddToCartTest.java new file mode 100644 index 00000000..ae6ba4ce --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/AddToCartTest.java @@ -0,0 +1,95 @@ +package com.serenitydojo.playwright.toolshop.catalog; + +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.*; +import com.serenitydojo.playwright.toolshop.fixtures.PlaywrightTestCase; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +@DisplayName("Shopping Cart") +@Feature("Shopping Cart") + +public class AddToCartTest extends PlaywrightTestCase { + + SearchComponent searchComponent; + ProductList productList; + ProductDetails productDetails; + NavBar navBar; + CheckoutCart checkoutCart; + + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + @BeforeEach + void setUp() { + searchComponent = new SearchComponent(page); + productList = new ProductList(page); + productDetails = new ProductDetails(page); + navBar = new NavBar(page); + checkoutCart = new CheckoutCart(page); + } + + + @Test + @Story("Check out") + @DisplayName("Checking out a single item") + void whenCheckingOutASingleItem() { + searchComponent.searchBy("pliers"); + productList.viewProductDetails("Combination Pliers"); + + productDetails.increaseQuanityBy(2); + productDetails.addToCart(); + + navBar.openCart(); + + List lineItems = checkoutCart.getLineItems(); + + Assertions.assertThat(lineItems) + .hasSize(1) + .first() + .satisfies(item -> { + Assertions.assertThat(item.title()).contains("Combination Pliers"); + Assertions.assertThat(item.quantity()).isEqualTo(3); + Assertions.assertThat(item.total()).isEqualTo(item.quantity() * item.price()); + }); + } + + @Test + @Story("Check out") + @DisplayName("Checking out multiple items") + void whenCheckingOutMultipleItems() { + navBar.openHomePage(); + + productList.viewProductDetails("Bolt Cutters"); + productDetails.increaseQuanityBy(2); + productDetails.addToCart(); + + navBar.openHomePage(); + productList.viewProductDetails("Slip Joint Pliers"); + productDetails.addToCart(); + + navBar.openCart(); + + List lineItems = checkoutCart.getLineItems(); + + Assertions.assertThat(lineItems).hasSize(2); + List productNames = lineItems.stream().map(CartLineItem::title).toList(); + Assertions.assertThat(productNames).contains("Bolt Cutters", "Slip Joint Pliers"); + + Assertions.assertThat(lineItems) + .allSatisfy(item -> { + Assertions.assertThat(item.quantity()).isGreaterThanOrEqualTo(1); + Assertions.assertThat(item.price()).isGreaterThan(0.0); + Assertions.assertThat(item.total()).isGreaterThan(0.0); + Assertions.assertThat(item.total()).isEqualTo(item.quantity() * item.price()); + }); + + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/SearchForProductsTest.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/SearchForProductsTest.java new file mode 100644 index 00000000..437f793a --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/SearchForProductsTest.java @@ -0,0 +1,74 @@ +package com.serenitydojo.playwright.toolshop.catalog; + +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.ProductList; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.SearchComponent; +import com.serenitydojo.playwright.toolshop.fixtures.PlaywrightTestCase; +import com.serenitydojo.playwright.toolshop.fixtures.ScreenshotManager; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("Searching for products") +@Feature("Searching for products") +public class SearchForProductsTest extends PlaywrightTestCase { + + @BeforeEach + void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + @Nested + @DisplayName("Searching by keyword") + @Story("Searching by keyword") + class SearchingByKeyword { + + @Test + @DisplayName("When there are matching results") + void whenSearchingByKeyword() { + SearchComponent searchComponent = new SearchComponent(page); + ProductList productList = new ProductList(page); + + ScreenshotManager.takeScreenshot(page, "ss1"); + searchComponent.searchBy("tape"); + ScreenshotManager.takeScreenshot(page, "ss2"); + + var matchingProducts = productList.getProductNames(); + + Assertions.assertThat(matchingProducts).contains("Tape Measure 7.5m", "Measuring Tape", "Tape Measure 5m"); + } + + @Test + @DisplayName("When there are no matching results") + void whenThereIsNoMatchingProduct() { + SearchComponent searchComponent = new SearchComponent(page); + ProductList productList = new ProductList(page); + searchComponent.searchBy("unknown"); + + var matchingProducts = productList.getProductNames(); + + Assertions.assertThat(matchingProducts).isEmpty(); + Assertions.assertThat(productList.getSearchCompletedMessage()).contains("There are no products found."); + } + } + + @Test + @Story("Clearing the previous search results") + @DisplayName("When the user clears a previous search results") + void clearingTheSearchResults() { + SearchComponent searchComponent = new SearchComponent(page); + ProductList productList = new ProductList(page); + searchComponent.searchBy("saw"); + + var matchingFilteredProducts = productList.getProductNames(); + Assertions.assertThat(matchingFilteredProducts).hasSize(2); + + searchComponent.clearSearch(); + + var matchingProducts = productList.getProductNames(); + Assertions.assertThat(matchingProducts).hasSize(9); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/WithTracing.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/WithTracing.java new file mode 100644 index 00000000..73c5ae00 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/WithTracing.java @@ -0,0 +1,33 @@ +package com.serenitydojo.playwright.toolshop.fixtures; + +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.Tracing; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInfo; + +import java.nio.file.Paths; + +public interface WithTracing { + + + @BeforeEach + default void setupTrace(BrowserContext context) { + context.tracing().start( + new Tracing.StartOptions() + .setScreenshots(true) + .setSnapshots(true) + .setSources(true) + ); + } + + @AfterEach + default void recordTrace(TestInfo testInfo, BrowserContext context) { + String traceName = testInfo.getDisplayName().replace(" ","-").toLowerCase(); + context.tracing().stop( + new Tracing.StopOptions() + .setPath(Paths.get("target/traces/trace-" + traceName + ".zip")) + ); + } + +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CartLineItem.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CartLineItem.java new file mode 100644 index 00000000..c8810308 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CartLineItem.java @@ -0,0 +1,3 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +public record CartLineItem(String title, int quantity, double price, double total) {} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CheckoutCart.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CheckoutCart.java new file mode 100644 index 00000000..f2df7c77 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/CheckoutCart.java @@ -0,0 +1,39 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +import com.microsoft.playwright.Page; +import io.qameta.allure.Step; + +import java.util.List; + +public class CheckoutCart { + private final Page page; + + public CheckoutCart(Page page) { + this.page = page; + } + + public List getLineItems() { + page.locator("app-cart tbody tr").first().waitFor(); + return page.locator("app-cart tbody tr") + .all() + .stream() + .map( + row -> { + String title = trimmed(row.getByTestId("product-title").innerText()); + int quantity = Integer.parseInt(row.getByTestId("product-quantity").inputValue()); + double price = Double.parseDouble(price(row.getByTestId("product-price").innerText())); + double linePrice = Double.parseDouble(price(row.getByTestId("line-price").innerText())); + return new CartLineItem(title, quantity, price, linePrice); + } + ).toList(); + } + + private String trimmed(String value) { + return value.strip().replaceAll("\u00A0", ""); + } + + private String price(String value) { + return value.replace("$", ""); + } + +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/NavBar.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/NavBar.java new file mode 100644 index 00000000..6197234e --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/NavBar.java @@ -0,0 +1,27 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +import com.microsoft.playwright.Page; +import io.qameta.allure.Step; + +public class NavBar { + private final Page page; + + public NavBar(Page page) { + this.page = page; + } + + @Step("Open cart") + public void openCart() { + page.getByTestId("nav-cart").click(); + } + + @Step("Open the home page") + public void openHomePage() { + page.navigate("https://practicesoftwaretesting.com"); + } + + @Step("Open the Contact page") + public void toTheContactPage() { + page.navigate("https://practicesoftwaretesting.com/contact"); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductDetails.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductDetails.java new file mode 100644 index 00000000..5259c46e --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductDetails.java @@ -0,0 +1,34 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.AriaRole; +import com.serenitydojo.playwright.toolshop.fixtures.PlaywrightTestCase; +import io.qameta.allure.Step; + + +public class ProductDetails { + private final Page page; + + public ProductDetails(Page page) { + this.page = page; + } + + @Step("Increase product quantity") + public void increaseQuanityBy(int increment) { + for (int i = 1; i <= increment; i++) { + page.getByTestId("increase-quantity").click(); + } + } + + @Step("Add to cart") + public void addToCart() { + page.waitForResponse( + response -> response.url().contains("/carts") && response.request().method().equals("POST"), + () -> { + page.getByText("Add to cart").click(); + page.getByRole(AriaRole.ALERT).click(); + } + ); + + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductList.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductList.java new file mode 100644 index 00000000..d742609d --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/ProductList.java @@ -0,0 +1,41 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +import com.microsoft.playwright.Page; +import com.serenitydojo.playwright.toolshop.fixtures.ProductSummary; +import com.serenitydojo.playwright.toolshop.fixtures.ScreenshotManager; +import io.qameta.allure.Step; + +import java.util.List; + +public class ProductList { + private final Page page; + + public ProductList(Page page) { + this.page = page; + } + + + public List getProductNames() { + return page.getByTestId("product-name").allInnerTexts(); + } + + public List getProductSummaries() { + return page.locator(".card").all() + .stream() + .map(productCard -> { + String productName = productCard.getByTestId("product-name").textContent().strip(); + String productPrice = productCard.getByTestId("product-price").textContent(); + return new ProductSummary(productName, productPrice); + }).toList(); + } + + @Step("View product details") + public void viewProductDetails(String productName) { + page.locator(".card").getByText(productName).click(); + ScreenshotManager.takeScreenshot(page,"view product details"); + } + + public String getSearchCompletedMessage() { + return page.getByTestId("search_completed").textContent(); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/SearchComponent.java b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/SearchComponent.java new file mode 100644 index 00000000..2e91c763 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/catalog/pageobjects/SearchComponent.java @@ -0,0 +1,43 @@ +package com.serenitydojo.playwright.toolshop.catalog.pageobjects; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.AriaRole; + +public class SearchComponent { + private final Page page; + + public SearchComponent(Page page) { + this.page = page; + } + + public void searchBy(String keyword) { + page.waitForResponse(response -> + response.url().endsWith("/products/search") + && "QUERY".equalsIgnoreCase( + response.request().method() + ) + && response.status() == 200, + () -> { + page.getByPlaceholder("Search").fill(keyword); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Search")).click(); + }); + } + + public void clearSearch() { + page.waitForResponse("**/products**", () -> { + page.getByTestId("search-reset").click(); + }); + } + + public void filterBy(String filterName) { + page.waitForResponse("**/products?**by_category=**", () -> { + page.getByLabel(filterName).click(); + }); + } + + public void sortBy(String sortFilter) { + page.waitForResponse("**/products?page=0&sort=**", () -> { + page.getByTestId("sort").selectOption(sortFilter); + }); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactForm.java b/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactForm.java new file mode 100644 index 00000000..09dd4d52 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactForm.java @@ -0,0 +1,63 @@ +package com.serenitydojo.playwright.toolshop.contact; + +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.options.AriaRole; + +import java.nio.file.Path; + +public class ContactForm { + private final Page page; + private Locator firstNameField; + private Locator lastNameField; + private Locator emailNameField; + private Locator messageField; + private Locator subjectField; + private Locator sendButton; + + public ContactForm(Page page) { + this.page = page; + this.firstNameField = page.getByLabel("First name"); + this.lastNameField = page.getByLabel("Last name"); + this.emailNameField = page.getByLabel("Email"); + this.messageField = page.getByLabel("Message"); + this.subjectField = page.getByLabel("Subject"); + this.sendButton = page.getByText("Send"); + } + + public void setFirstName(String firstName) { + firstNameField.fill(firstName); + } + + public void setLastName(String lastName) { + lastNameField.fill(lastName); + } + + public void setEmail(String email) { + emailNameField.fill(email); + } + + public void setMessage(String message) { + messageField.fill(message); + } + + public void selectSubject(String subject) { + subjectField.selectOption(subject); + } + + public void setAttachment(Path fileToUpload) { + page.setInputFiles("#attachment", fileToUpload); + } + + public void submitForm() { + sendButton.click(); + } + + public String getAlertMessage() { + return page.getByRole(AriaRole.ALERT).textContent(); + } + + public void clearField(String fieldName) { + page.getByLabel(fieldName).clear(); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactFormTest.java b/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactFormTest.java new file mode 100644 index 00000000..6eefe35a --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/contact/ContactFormTest.java @@ -0,0 +1,113 @@ +package com.serenitydojo.playwright.toolshop.contact; + +import com.microsoft.playwright.assertions.LocatorAssertions; +import com.microsoft.playwright.options.AriaRole; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.NavBar; +import com.serenitydojo.playwright.toolshop.fixtures.PlaywrightTestCase; +import io.qameta.allure.Allure; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +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 static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + +@DisplayName("Contact form") +@Feature("Contact form") +@Execution(ExecutionMode.SAME_THREAD) +public class ContactFormTest extends PlaywrightTestCase { + + ContactForm contactForm; + NavBar navigate; + + @BeforeEach + void openContactPage() { + contactForm = new ContactForm(page); + navigate = new NavBar(page); + navigate.toTheContactPage(); + } + + @Story("Submitting a request") + @DisplayName("Customers can use the contact form to contact us") + @Test + void completeForm() throws URISyntaxException { + contactForm.setFirstName("Sarah-Jane"); + contactForm.setLastName("Smith"); + contactForm.setEmail("sarah@example.com"); + contactForm.setMessage("A very long message to the warranty service about a warranty on a product!"); + contactForm.selectSubject("Warranty"); + + Path fileToUpload = Paths.get(ClassLoader.getSystemResource("data/sample-data.txt").toURI()); + contactForm.setAttachment(fileToUpload); + + contactForm.submitForm(); + + Assertions.assertThat(contactForm.getAlertMessage()) + .contains("Thanks for your message! We will contact you shortly."); + } + + @Story("Submitting a request") + @DisplayName("First name, last name, email and message are mandatory") + @ParameterizedTest(name = "{arguments} is a mandatory field") + @ValueSource(strings = {"First name", "Last name", "Email", "Message"}) + void mandatoryFields(String fieldName) { + // Fill in the field values + contactForm.setFirstName("Sarah-Jane"); + contactForm.setLastName("Smith"); + contactForm.setEmail("sarah@example.com"); + contactForm.setMessage("A very long message to the warranty service about a warranty on a product!"); + contactForm.selectSubject("Warranty"); + + // Clear one of the fields + contactForm.clearField(fieldName); + page.waitForTimeout(250); + contactForm.submitForm(); + + // Check the error message for that field + var errorMessage = page.getByRole(AriaRole.ALERT).getByText(fieldName + " is required"); + + assertThat(errorMessage).isVisible(); + } + + @Story("Submitting a request") + @DisplayName("The message must be at least 50 characters long") + @Test + void messageTooShort() { + + contactForm.setFirstName("Sarah-Jane"); + contactForm.setLastName("Smith"); + contactForm.setEmail("sarah@example.com"); + contactForm.setMessage("A short long message."); + contactForm.selectSubject("Warranty"); + + contactForm.submitForm(); + + assertThat(page.getByRole(AriaRole.ALERT)).hasText("Message must be minimal 50 characters"); + } + + @Story("Submitting a request") + @DisplayName("The email address must be correctly formatted") + @ParameterizedTest(name = "'{arguments}' should be rejected") + @ValueSource(strings = {"not-an-email", "not-an.email.com", "notanemail"}) + void invalidEmailField(String invalidEmail) { + contactForm.setFirstName("Sarah-Jane"); + contactForm.setLastName("Smith"); + contactForm.setEmail(invalidEmail); + contactForm.setMessage("A very long message to the warranty service about a warranty on a product!"); + contactForm.selectSubject("Warranty"); + + contactForm.submitForm(); + + assertThat(page.getByRole(AriaRole.ALERT)).hasText("Email format is invalid"); + } +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/domain/Address.java b/src/test/java/com/serenitydojo/playwright/toolshop/domain/Address.java new file mode 100644 index 00000000..e5d49bb5 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/domain/Address.java @@ -0,0 +1,11 @@ +package com.serenitydojo.playwright.toolshop.domain; + +public record Address( + String street, + String house_number, + String city, + String state, + String country, + String postal_code +) { +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/domain/User.java b/src/test/java/com/serenitydojo/playwright/toolshop/domain/User.java new file mode 100644 index 00000000..4ebfeb92 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/domain/User.java @@ -0,0 +1,51 @@ +package com.serenitydojo.playwright.toolshop.domain; + +import net.datafaker.Faker; + +public record User(String first_name, + String last_name, + Address address, + String phone, + String dob, + String password, + String email + ) { + + public static User randomUser(){ + + Faker fake = new Faker(); + + Address address = new Address( + fake.address().streetName(), + fake.address().buildingNumber(), + fake.address().city(), + fake.address().state(), + fake.address().country(), + fake.address().postcode() + ); + + return new User( + fake.name().firstName(), + fake.name().lastName(), + address, + fake.phoneNumber().phoneNumber(), + "1990-01-01", + "Agfh1234!XIHD", + fake.internet().emailAddress() + ); + } + + public User withPassword(String password){ + + return new User( first_name, + last_name, + address, + phone, + dob, + password, + email); + } + + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/PlaywrightTestCase.java b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/PlaywrightTestCase.java new file mode 100644 index 00000000..c61f988f --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/PlaywrightTestCase.java @@ -0,0 +1,63 @@ +package com.serenitydojo.playwright.toolshop.fixtures; + +import com.microsoft.playwright.*; +import io.qameta.allure.Allure; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; + +public abstract class PlaywrightTestCase { + + //this is for parallel execution + protected static ThreadLocal playwright + = ThreadLocal.withInitial(() -> { + Playwright playwright = Playwright.create(); + playwright.selectors().setTestIdAttribute("data-test"); + return playwright; + } + ); + + protected static ThreadLocal browser = ThreadLocal.withInitial(() -> + playwright.get().chromium().launch( + new BrowserType.LaunchOptions() + .setHeadless(false) + .setArgs(Arrays.asList("--no-sandbox", "--disable-extensions", "--disable-gpu")) + ) + ); + + protected BrowserContext browserContext; + + protected Page page; + + @BeforeEach + void setUpBrowserContext() { + browserContext = browser.get().newContext(); + page = browserContext.newPage(); + } + + @AfterEach + void closeContext() { + takeScreenshot("End of test"); + browserContext.close(); + } + + protected void takeScreenshot(String name){ + var screenshot = page.screenshot( + new Page.ScreenshotOptions().setFullPage(true) + ); + Allure.addAttachment(name, new ByteArrayInputStream(screenshot)); + } + + @AfterAll + static void tearDown() { + browser.get().close(); + browser.remove(); + + playwright.get().close(); + playwright.remove(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ProductSummary.java b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ProductSummary.java new file mode 100644 index 00000000..41148bb3 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ProductSummary.java @@ -0,0 +1,3 @@ +package com.serenitydojo.playwright.toolshop.fixtures; + +public record ProductSummary(String name, String price) {} \ No newline at end of file diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ScreenshotManager.java b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ScreenshotManager.java new file mode 100644 index 00000000..ed18bc10 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/fixtures/ScreenshotManager.java @@ -0,0 +1,16 @@ +package com.serenitydojo.playwright.toolshop.fixtures; + +import com.microsoft.playwright.Page; +import io.qameta.allure.Allure; + +import java.io.ByteArrayInputStream; + +public class ScreenshotManager { + + public static void takeScreenshot(Page page, String name){ + var screenshot = page.screenshot( + new Page.ScreenshotOptions().setFullPage(true) + ); + Allure.addAttachment(name, new ByteArrayInputStream(screenshot)); + } +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/MockSearchResponses.java b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/MockSearchResponses.java new file mode 100644 index 00000000..1104b31b --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/MockSearchResponses.java @@ -0,0 +1,53 @@ +package com.serenitydojo.playwright.toolshop.restAPITest; + +public class MockSearchResponses { + + public static final String RESPONSE_WITH_A_SINGLE_ENTRY = """ + { + "current_page": 1, + "data": [ + { + "id": "01JFG8Q5XKZJY4BEYQ87PC2Q1Y", + "name": "Super Pliers", + "description": "Lorum ipsum", + "price": 9.99, + "is_location_offer": 1, + "is_rental": 0, + "in_stock": 0, + "co2_rating": "A", + "is_eco_friendly": true, + "brand": { + "id": "string", + "name": "new brand", + "slug": "new-brand" + }, + "category": { + "id": "string", + "parent_id": "string", + "name": "new category", + "slug": "new-category", + "sub_categories": [ + "string" + ] + }, + "product_image": { + "by_name": "string", + "by_url": "string", + "source_name": "string", + "source_url": "string", + "file_name": "string", + "title": "string", + "id": "string" + } + } + ], + "from": 1, + "last_page": 1, + "per_page": 1, + "to": 1, + "total": 1 + } + """; + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightPageObjectTest.java b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightPageObjectTest.java new file mode 100644 index 00000000..44b28081 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightPageObjectTest.java @@ -0,0 +1,180 @@ +package com.serenitydojo.playwright.toolshop.restAPITest; + + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.options.AriaRole; +import com.serenitydojo.playwright.HeadlessChromeOptions; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.CartLineItem; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.CheckoutCart; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.NavBar; +import com.serenitydojo.playwright.toolshop.catalog.pageobjects.ProductDetails; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@UsePlaywright(HeadlessChromeOptions.class) +public class PlaywrightPageObjectTest { + + SearchComponent searchComponent; + ProductList productList; + ProductDetails productDetails; + NavBar navBar; + CheckoutCart checkoutCart; + + @BeforeEach + void setup(Page page){ + searchComponent = new SearchComponent(page); + productList = new ProductList(page); + productDetails = new ProductDetails(page); + navBar = new NavBar(page); + checkoutCart = new CheckoutCart(page); + + } + + @Test + void withoutPageObject(Page page){ + + page.navigate("https://practicesoftwaretesting.com"); + + page.waitForResponse(response -> + response.url().endsWith("/products/search"), + + ()->{ + page.getByPlaceholder("Search").fill("tape"); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Search")).click(); + }); + + List matchingProducts = page.getByTestId("product-name").allInnerTexts(); + assertThat(matchingProducts) + .contains("Tape Measure 7.5m", "Measuring Tape"); + + } + + @Test + void withPageObjects(Page page){ + + SearchComponent searchComponent = new SearchComponent(page); + ProductList productList = new ProductList(page); + + searchComponent.seacrhBy("tape"); + + var matchingProducts = productList.getProductNames(); + + assertThat(matchingProducts) + .contains("Tape Measure 7.5m", "Measuring Tape"); + + + } + + @Test + void withPageObjectAddCart(Page page){ + + searchComponent.seacrhBy("pliers"); + productList.viewProductDetails("Combination Pliers"); + productDetails.increaseQuanityBy(2); + productDetails.addToCart(); + + navBar.openCart(); + + List lineItems = checkoutCart.getLineItems(); + assertThat(lineItems) + .hasSize(1) + .first() + .satisfies(item -> { + assertThat(item.title()).contains("Combination Pliers"); + assertThat(item.quantity()).isEqualTo(3); + assertThat(item.total()).isEqualTo(item.quantity()* item.price()); + }); + } + + @Test + void whenCheckingOutMultipleItems(){ + navBar.openHomePage(); + productList.viewProductDetails("Bolt Cutters"); + productDetails.increaseQuanityBy(2); + productDetails.addToCart(); + + navBar.openHomePage(); + productList.viewProductDetails("Slip Joint Pliers"); + productDetails.addToCart(); + + navBar.openCart(); + + List lineItems = checkoutCart.getLineItems(); + assertThat(lineItems) + .hasSize(2); + + List productNames = lineItems.stream().map(CartLineItem::title).toList(); + assertThat(productNames).contains("Bolt Cutters", "Slip Joint Pliers"); + + assertThat(lineItems) + .allSatisfy(item -> { + assertThat(item.total()).isEqualTo(item.quantity()*item.price()); + }); + + } + + + + + + + + + + + + + + + + + + class SearchComponent{ + private final Page page; + + SearchComponent(Page page) { + this.page = page; + } + + + public void seacrhBy(String keyword) { + + page.navigate("https://practicesoftwaretesting.com"); + + page.waitForResponse(response -> + response.url().endsWith("/products/search"), + + ()->{ + page.getByPlaceholder("Search").fill(keyword); + page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Search")).click(); + }); + + } + } + + class ProductList{ + + private final Page page; + + ProductList(Page page) { + this.page = page; + } + + public List getProductNames() { + + return page.getByTestId("product-name").allInnerTexts(); + } + + public void viewProductDetails(String productName) { + + page.locator(".card").getByText(productName).click(); + } + } + + +} diff --git a/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightRestAPITest.java b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightRestAPITest.java new file mode 100644 index 00000000..d0622b36 --- /dev/null +++ b/src/test/java/com/serenitydojo/playwright/toolshop/restAPITest/PlaywrightRestAPITest.java @@ -0,0 +1,37 @@ +package com.serenitydojo.playwright.toolshop.restAPITest; + +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Route; +import com.microsoft.playwright.junit.UsePlaywright; +import com.serenitydojo.playwright.HeadlessChromeOptions; +import org.junit.jupiter.api.Test; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; + + +@UsePlaywright(HeadlessChromeOptions.class) +public class PlaywrightRestAPITest { + + @Test + void whenASingleItemIsFound(Page page){ + + //this will mock out the product and make it up + page.route("**/products/search", route -> { + route.fulfill( + new Route.FulfillOptions() + .setBody(MockSearchResponses.RESPONSE_WITH_A_SINGLE_ENTRY) + .setStatus(200) + ); + }); + + page.navigate("https://practicesoftwaretesting.com"); + page.getByPlaceholder("Search").fill("Pliers"); + page.getByPlaceholder("Search").press("Enter"); + + page.waitForTimeout(5000); + + assertThat(page.getByTestId("product-name")).hasCount(1); + assertThat(page.getByTestId("product-name")).hasText("Super Pliers"); + + } +} diff --git a/src/test/resources/allure.properties b/src/test/resources/allure.properties new file mode 100644 index 00000000..6c1e0bb1 --- /dev/null +++ b/src/test/resources/allure.properties @@ -0,0 +1 @@ +allure.results.directory=target/allure-results \ 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 00000000..5e4f2565 --- /dev/null +++ b/src/test/resources/data/sample-data.txt @@ -0,0 +1 @@ +test1234 \ No newline at end of file diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..8d6b06e7 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,8 @@ +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=concurrent +junit.jupiter.execution.parallel.mode.classes.default=concurrent +junit.jupiter.execution.parallel.console.mode=verbose + +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=4 +junit.jupiter.execution.parallel.config.fixed.parallelism=4 \ No newline at end of file