# Lab 05 : Web layer test automation

# Useful commands 

```mvn test -Dtest=HelloWorldChromeJupiterTest```


<br>

# 1. Selenium WebDriver

Selenium WebDriver is a browser automation framework that simulates a web browser (Chrome, Firefox, or Edge) from code. Its used for automated testing of web applications.

It allows to: 
- Open a web page (driver.get("https://..."))
- Find and click elements (driver.findElement(By.id("button")).click())
- Type into input fields (sendKeys("hello"))
- Read text from the page and make assertions
- Simulate real user behavior
  
<br>


# 1.1 Jupiter end-to-end tests

- The following example uses JUnit 5 as the unit testing framework to embed the call to Selenium WebDriver


```java
class HelloWorldChromeJupiterTest {

    static final Logger log = getLogger(lookup().lookupClass());

    private WebDriver driver; //1

    @BeforeAll
    static void setupClass() {
        WebDriverManager.chromedriver().setup(); //2
    }

    @BeforeEach
    void setup() {
        driver = new ChromeDriver(); //3
    }

    @Test
    void test() {
        // Exercise //4
        String sutUrl = "https://bonigarcia.dev/selenium-webdriver-java/";
        driver.get(sutUrl); 
        String title = driver.getTitle(); 
        log.debug("The title of {} is {}", sutUrl, title); 

        // Verify
        assertThat(title).isEqualTo("Hands-On Selenium WebDriver with Java"); 
    }

    @AfterEach
    void teardown() {
        driver.quit(); //5
    }

}
```

- 1 We declare a Java attribute using the interface WebDriver. We use this variable in tests to control web browsers with Selenium WebDriver.

- 2 In the setup for all tests within this class, we call WebDriverManager to manage the required driver (downloads the correct version of the ChromeDriver)

- 3 This starts a new Chrome browser before each test

- 4 Opens Chrome,   
navigates to the link,   
reads the title page,  
asserts that the title equals "Hands-On Selenium WebDriver with Java",  
logs the title for debugging.  

- 5 Closes the browser after the test finishes


To run: ```mvn test -Dtest=HelloWorldChromeJupiterTest```

<br>

## 1.2 Using Selenium Jupiter (refactoring)

```
<dependency>
    <groupId>io.github.bonigarcia</groupId>
    <artifactId>selenium-jupiter</artifactId>
    <version>5.0.0</version>
    <scope>test</scope>
</dependency>
```

```java
@ExtendWith(SeleniumJupiter.class)
class HelloWorldSeleniumJupiterTest {

    @Test
    void testNavigateToSlowCalculator(WebDriver driver) {
        //Step 1: Open main page
        String sutUrl = "https://bonigarcia.dev/selenium-webdriver-java/";
        driver.get(sutUrl);

        //Assert main page title
        String title = driver.getTitle();
        assertThat(title).isEqualTo("Hands-On Selenium WebDriver with Java");

        //Step 2: Click the “Slow calculator” link
        WebElement slowCalcLink = driver.findElement(By.linkText("Slow calculator"));
        slowCalcLink.click();

        //Step 3: Assert navigation
        String currentUrl = driver.getCurrentUrl();
        assertThat(currentUrl).contains("slow-calculator");

        //No need for driver.quit()
    }
}
```

<br>

# 2. Interactive Test Recording

Interactive test recording tools like Selenium IDE or Katalon Recorder allow you to automatically record browser interactions and generate test scripts without manually writing code.

## 2.1 Using Katalon Recorder
1- Installed Katalon Recorder (plugin via chrome web store)
2- Recorded a test on website (in this exercise we used https://blazedemo.com, making use of the sites features)  
3- Added Assertions (Verified the confirmation page contains "Thank you for your purchase today!", manually added an assertion to check the page title equals "BlazeDemo Confirmation")
4- Replayed the test (Ran the test within Katalon Recorder to confirm it performs all steps correctly)

<br>

## 2.2 Exporting the Recorded Test
Within the extension, the test can be exported to Java (JUnit + Selenium), generating a maven project with the ```pom.xml``` and a java test file (with Selenium WebDriver commands)

```java
driver.get("https://blazedemo.com/");
driver.findElement(By.name("fromPort")).click();
new Select(driver.findElement(By.name("fromPort"))).selectByVisibleText("Philadelphia");
driver.findElement(By.name("toPort")).click();
new Select(driver.findElement(By.name("toPort"))).selectByVisibleText("New York");
driver.findElement(By.xpath("//input[@value='Find Flights']")).click();
driver.findElement(By.xpath("//input[@value='Choose This Flight']")).click();
assertEquals("BlazeDemo Confirmation", driver.getTitle());
```

## 2.3 Refactoring the test 

The exported test used JUnit 4 annotations (@Before, @After, @Test)
To make it compatible with JUnit 5, it was refactored to:
- Use @BeforeAll, @BeforeEach, @AfterEach, @Test
- Replace Assert.assertEquals() with Assertions.assertEquals().
- Simplify WebDriver setup using WebDriverManager or Selenium-Jupiter.  
  
This allows the test to run seamlessly in a modern JUnit 5 + Maven project.

<br>

## 2.4 Selecting Elements with Locators

| Locator Type                                        | Example                                  | Notes                                                                  |
| --------------------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| `By.name("search")`                                 | Used for the search box                  | Simple and robust if HTML `name` attributes are stable                 |
| `By.xpath("//*[contains(text(), 'Harry Potter')]")` | Used for finding book titles             | Works, but **fragile** – can easily break if text or structure changes |
| `By.cssSelector("[data-testid=book-search-item]")`  | Recommended for identifying book results | More **robust**, as test IDs are designed to remain stable             |

<br>

For UI tests, prefer:
- By.id()
- By.name()
- By.cssSelector() 

Refer to the website https://www.selenium.dev/documentation/webdriver/elements/locators for more examples

```java 
@ExtendWith(SeleniumJupiter.class)
class BookSearchTest {

    @Test
    @DisplayName("Search for 'Harry Potter' and verify J.K. Rowling book appears")
    void testSearchBook(ChromeDriver driver) {
        driver.get("https://cover-bookstore.onrender.com");

        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

        //Locate search bar
        WebElement searchBox = wait.until(ExpectedConditions.visibilityOfElementLocated(
                By.cssSelector("[data-testid='book-search-input']")));

        searchBox.sendKeys("Harry Potter");
        searchBox.sendKeys(Keys.ENTER);

        //wait
        wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(
                By.cssSelector("[data-testid='book-search-item']")));

        List<WebElement> results = driver.findElements(By.cssSelector("[data-testid='book-search-item']"));

        System.out.println("Found " + results.size() + " results:");

        boolean found = false;
        //each book card found
        for (WebElement bookCard : results) {
            WebElement img = bookCard.findElement(By.tagName("img"));
            String title = img.getAttribute("alt");
            String cardText = bookCard.getText();

            System.out.println(title + " | Info: " + cardText);

            if (title.toLowerCase().contains("harry potter and the sorcerer")
                    && cardText.toLowerCase().contains("j.k. rowling")) {
                found = true;
                break;
            }
        }

        assertTrue(found, "Expected to find 'Harry Potter and the Sorcerer's Stone' by J.K. Rowling.");
    }
}
```
<br>

# 3 Page Object Pattern

“Page Object model is an object design pattern where webpages are represented as classes, and
the various elements [of interest] on the page are defined as variables on the class. All possible user interactions [on a page] can then be implemented as methods on the class.”


Instead using a Selenium test script that does everything (open page, fill form,  click, assert), the web pages are now split into their own Java classes:
- Each page class represents one screen in the web app.
- Each element (input, button, or dropdown) is a field.
- Each action (like "search flight" or "book flight") is a method.

<br>

## 3.1 File Structure and Examples

### **Homepage:**
```java
public class HomePage {

    private WebDriver driver;

    @FindBy(name = "fromPort")
    private WebElement fromPort;

    @FindBy(name = "toPort")
    private WebElement toPort;

    @FindBy(xpath = "//input[@value='Find Flights']")
    private WebElement findFlightsButton;

    public HomePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void selectFromPort(String city) {
        new Select(fromPort).selectByVisibleText(city);
    }

    public void selectToPort(String city) {
        new Select(toPort).selectByVisibleText(city);
    }

    public void clickFindFlights() {
        findFlightsButton.click();
    }
}
```

<br>

### **PurchasePage:**

```java
public class PurchasePage {

    private WebDriver driver;

    @FindBy(id = "inputName")
    private WebElement nameInput;

    @FindBy(id = "address")
    private WebElement addressInput;

    @FindBy(id = "city")
    private WebElement cityInput;

    @FindBy(id = "state")
    private WebElement stateInput;

    @FindBy(id = "zipCode")
    private WebElement zipInput;

    @FindBy(id = "cardType")
    private WebElement cardTypeSelect;

    @FindBy(id = "creditCardNumber")
    private WebElement creditCardNumber;

    @FindBy(xpath = "//input[@value='Purchase Flight']")
    private WebElement purchaseButton;

    public PurchasePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void fillForm(String name, String address, String city, String state, String zip, String cardType, String cardNumber) {
        nameInput.sendKeys(name);
        addressInput.sendKeys(address);
        cityInput.sendKeys(city);
        stateInput.sendKeys(state);
        zipInput.sendKeys(zip);
        new Select(cardTypeSelect).selectByVisibleText(cardType);
        creditCardNumber.sendKeys(cardNumber);
    }

    public void submit() {
        purchaseButton.click();
    }
}
```

<br>

### **ConfirmationPage:**
```java 
public class ConfirmationPage {

    private WebDriver driver;

    @FindBy(tagName = "h1")
    private WebElement heading;

    public ConfirmationPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void assertConfirmation() {
        assertEquals("Thank you for your purchase today!", heading.getText());
        assertEquals("BlazeDemo Confirmation", driver.getTitle());
    }
}
```

<br>

### **Test:**

```java
@ExtendWith(SeleniumJupiter.class)
public class BlazeDemoPOMTest {
 
    @Test
    @DisplayName("Book a flight using Page Object Model")
    void testBookFlight(ChromeDriver driver) {
        driver.get("https://blazedemo.com/");

        // Page objects
        HomePage home = new HomePage(driver);
        home.selectFromPort("Paris");
        home.selectToPort("London");
        home.clickFindFlights();

        FlightsPage flights = new FlightsPage(driver);
        flights.chooseFlight();

        PurchasePage purchase = new PurchasePage(driver);
        purchase.fillForm("Ana", "Rua da Uni", "Aveiro", "A", "3810", "Visa", "12121212121212121");
        purchase.submit();

        ConfirmationPage confirmation = new ConfirmationPage(driver);
        confirmation.assertConfirmation();
    }
}
```

<br>

# 4 Playwright

Playwright uses a different approach to automate the target browsers, by using CDP (Chrome DevTools Protocol) and provides auto-waiting capabilities avoid many
of the flakiness usually found in Selenium tests.  
Whenever using Playwright it will install local versions of Chromium, Firefox,
WebKit for the sole purpose of testing.  
Playwright uses 3 core concepts: 
- browsers - an instance of a real browser (e.g., Chromium). 
- contexts - provide a way to operate multiple independent, non-persistent browser sessions  
- pages -  single tab or a popup window within a browser context

<br>

## 4.1 File Structure and Examples

### **Test:**
```java

@UsePlaywright
@DisplayName("Playwright JUnit5 Test using @UsePlaywright Extension")
@Tag("playwright")
class HelloWorldPlaywrightTest {

    @Test
    @DisplayName("Navigate to Slow Calculator and verify page URL")
    void testNavigateToSlowCalculator(Page page) {
        //Open page
        page.navigate("https://bonigarcia.dev/selenium-webdriver-java/");

        //Assert title
        assertEquals("Hands-On Selenium WebDriver with Java", page.title());

        //Click the “Slow calculator” link
        page.click("text=Slow calculator");

        //Assert navigation
        assertTrue(page.url().contains("slow-calculator"),
                "The page URL should contain 'slow-calculator'");
    }
}
```

**Note:** to see whats happening (headless=false)
```java
    @BeforeAll
    static void configure(PlaywrightTestOptions options) {
        options.setHeadless(false);
        options.setSlowMo(400);}
```

