Zum Inhalt springen

Automating Tests with Playwright and PageObject: A Practical Approach

Embark on a transformative journey into the realm of test automation with our latest article: „Automating Tests with Playwright and PageObject: A Practical Approach.“ Discover the synergy between Playwright and PageObject, unraveling a powerhouse combination that not only streamlines operational processes but also empowers the creation of resilient and easily maintainable tests.

intro

Introduction to Playwright and PageObject

Automating web tests can be a challenging task, especially when dealing with complex and ever-evolving applications. That’s where „pageobject“ comes into play an approach aimed at simplifying the structuring and organization of tests. By adopting „pageobject,“ we can create a clear and reusable framework where each web page and its elements are represented as objects, making test writing and maintenance a breeze.
In this article, we will delve into how „pageobject,“ combined with the powerful Playwright library, can revolutionize the way we automate tests, making them more practical and efficient. Get ready to explore an approach that can accelerate your workflow and deliver consistent results in your web test automations.
I’d like to remind you that this article is a continuation of Getting Started with Playwright — Introduction to Web Testing Automation, and it would be beneficial to have a basic understanding of Playwright for a more in-depth grasp of what will be explained here. Therefore, I recommend reading the first article.

PageObject?

The concept of „PageObject“ is an approach aimed at organizing and structuring automated tests in a more intuitive and reusable manner. Essentially, each web page is mapped as an object, containing the elements and actions that can be performed on that page. This mapping allows tests to interact with page elements in a simple and consistent way, making test writing and maintenance easier.

In practical terms, imagine you’re automating a login process on a website. With „PageObject,“ instead of repeating code for locating and interacting with login elements in each test, you can create a specific object for the login page. This object will have methods to fill in the username field, the password field, and click the login button. This way, in every test involving login, you simply invoke these methods from the page object, making the code cleaner, more readable, and easy to maintain. If there’s any change to the login page, you only need to update the page object, not all tests using that page. This enhances the efficiency and scalability of your automated tests.

Structuring the Project in the „PageObject“

Alright, let’s kick off, setting up the „PageObject“ structure.

The most common idea is to split this into two folders: one for Page files (stuff related to the page itself) and another for tests. This keeps things tidy, separating the code for interacting with the app from the actual tests. So, let’s get our hands dirty and build this structure!

So, we’ll have the following folder architecture:

Simple Structure of PageObject

  • pages: Folder, where you’ll find files with classes that map the actions of each „Page“ and its „Objects.“
  • tests: Folder, where you’ll find the „specs“ files for our project.
  • utils: Folder, where you’ll find functions and code snippets that we can reuse in various scenarios.

Now let’s get practical:

Here we can see the first „Page“ created; it will be responsible for all the actions that the Amazon home page can perform.

const { expect } = require('@playwright/test');

class AmazonHomePage {
    constructor() {
        this.ObjectsPage = {
            "url": "https://www.amazon.com.br/",
            "SearchField": '[id="twotabsearchtextbox"]',
            "SearchButton": '[id="nav-search-submit-button"]',
            "ProductTarget": "Batman - A Corte das Corujas",
            "titlePage": "Amazon.com.br | Tudo pra você, de A a Z.",
            "boxResults":`[data-component-type="s-search-result"]`,
            "TitleResult":'[class="a-size-base-plus a-color-base a-text-normal"]'
        }
    }

    async navegatTo() {
        await page.goto(this.ObjectsPage.url)
    }

    async getSearchField() {
        let SearchField = await page.locator(this.ObjectsPage.SearchField)
        await SearchField.waitFor('visible')
        return SearchField
    }

    async getSearchButton() {
        let SearchButton = await page.locator(this.ObjectsPage.SearchButton)
        await SearchButton.waitFor('visible')
        return SearchButton
    }

    async clickSearchButton() {
        let SearchButton = await this.getSearchButton()
        await SearchButton.click()
    }

    async searchProduct(productName) {
        let SearchField = await this.getSearchField()
        await SearchField.click()
        await SearchField.fill(productName)
    }

    async getBoxsResult(){
        let boxResult = await page.locator(this.ObjectsPage.boxResults).first()
        await boxResult.waitFor('visible')
        return boxResult
    }

    async validateHomePage(){
        const currentUrl = page.url();
        expect(currentUrl).toBe('https://www.amazon.com.br/')
    }

    async validateSearchProduct(productName) {
        await this.searchProduct(productName)
        await this.clickSearchButton()
        let boxResult = await this.getBoxsResult()

        await expect(boxResult.locator(this.ObjectsPage.TitleResult)).toContainText(productName)

    }

    async clickBoxResultAfterSearch(productName){
        await this.validateSearchProduct(productName)
        let boxResult = await this.getBoxsResult()
        await boxResult.locator('[href*="/Batman-Corte-Corujas-Christa-Faust/"]').first().click()

    }
}

module.exports = { AmazonHomePage: AmazonHomePage }


We can observe in the code that the Amazon home page has been transformed into a class, while its actions have become functions of that class, and its elements have also become objects. This is the necessary abstraction to create a „Page Object.“

We can notice that some functions are generic, making them reusable for other tests. We also see that we’ll use the same search function for any desired product without additional code. Even for a test covering a different scenario, these functions can be leveraged.

This level of reusability wouldn’t have been possible without using the „Page Object,“ as we can see in my previous article.

Getting Started with Playwright — Introduction to Web Testing Automation

Now, we will create the „page“ for the product:


const { expect } = require('@playwright/test');


class ProductPage{
    constructor(){
        this.ObjectsPage = {
            "ProductTitle": '[id="productTitle"]',
            "ProductDesc": '[id="bookDescription_feature_div"]',
            "ProductValue": '[id="price"]',
            "addCartButton": '[id="addToCart_feature_div"]',
            "BuyNow":'[id="buyNow"]',

        }
    }

    async getTitleProduct(){
        let productName = await page.locator(this.ObjectsPage.ProductTitle) 
        await productName.waitFor('visible')
        return productName
    }
    async getDescProduct(){
        let ProductDesc = await page.locator(this.ObjectsPage.ProductDesc) 
        await ProductDesc.waitFor('visible')
        return ProductDesc

    }
    async getValueProduct(){
        let ProductValue = await page.locator(this.ObjectsPage.ProductValue) 
        await ProductValue.waitFor('visible')
        return ProductValue

    }
    async getAddCart(){

        let addCartButton = await page.locator(this.ObjectsPage.addCartButton).first()
        await addCartButton.waitFor('visible')
        return addCartButton

    }
    async getBuyNow(){
        let BuyNow = await page.locator(this.ObjectsPage.BuyNow) 
        await BuyNow.waitFor('visible')
        return BuyNow
    }

    async clickAddCart(){
        let addCart = await this.getAddCart()
        await addCart.click()
    }

    async validateProductPage(ProducName,ProductDesc){
        let elementName = await this.getTitleProduct()
        let elementDesc = await this.getDescProduct()

        let addCartButton = await this.getAddCart()
        let BuyNow = await this.getBuyNow()

        expect(elementName).toHaveText(ProducName)
        expect(elementDesc).toContainText(ProductDesc)

        expect(addCartButton).toContainText("Adicionar ao carrinho")
        expect(BuyNow).toContainText(" Comprar agora ")
    }
}



module.exports = { ProductPage: ProductPage }

And „page“ for the cart


const { expect } = require('@playwright/test');


class CartPage {
    constructor() {
        this.ObjectsPage = {
            "CarTitle": '[id="NATC_SMART_WAGON_CONF_MSG_SUCCESS"]',
            "closeBuy": '[id="sc-buy-box-ptc-button"] [class="sc-with-multicart"]',
            "goToCart": '[data-csa-c-content-id="sw-gtc_CONTENT"]',
            "ProductTitle": '[data-a-max-rows="2"] [class="a-truncate-full a-offscreen"]',

        }
    }

    async getcartTitle() {
        let CarTitle = await page.locator(this.ObjectsPage.CarTitle) 
        await CarTitle.waitFor('visible')
        return CarTitle
     }
    async getcloseBuy() { 
        let closeBuy = await page.locator(this.ObjectsPage.closeBuy) 
        await closeBuy.waitFor('visible')
        return closeBuy

    }
    async getgoToCart() { 

        let goToCart = await page.locator(this.ObjectsPage.goToCart) 
        await goToCart.waitFor('visible')
        return goToCart
    }
    async getProductTitle() { 

        let ProductTitle = await page.locator(this.ObjectsPage.ProductTitle).first()
        await ProductTitle.waitFor('visible')
        return ProductTitle
    }

    async clickGotoCart(){
        let goToCart = await this.getgoToCart()
        await goToCart.click()
    }

    async validateFirstCart(){
        let cartTitle = await this.getcartTitle()
        let closeBuy = await this.getcloseBuy()
        let goToCart = await this.getgoToCart()

        await expect(cartTitle).toContainText("Adicionado ao carrinho")
        await expect(closeBuy).toContainText("Finalizar carrinho da Amazon")
        await expect(goToCart).toContainText("Ir para o carrinho")

    }

    async validateSecondCart(ProducName){
        await this.clickGotoCart()
        let producTitle = await this.getProductTitle()

        await expect(producTitle).toContainText(ProducName)
    }


}



module.exports = { CartPage: CartPage }

Now, let’s see how code reuse has benefited us. We can observe that each „Page“ has its specific actions, allowing us to reuse them in all tests we perform from now on.

The part of accessing the website, performing the search, and validating the search is handled in the home page class. On the other hand, functions to validate the product and add it to the cart are executed in the product page class. Finally, the cart validation functions are concentrated on this last page.

Now, let’s check how the test file turned out:


// @ts-check
const { test, expect } = require('@playwright/test');
const { AmazonHomePage } = require("../pages/homePageAmazon")
const { ProductPage } = require("../pages/ProductPageAmazon")
const { CartPage } = require("../pages/CartPageAmazon")

const HomePage = new AmazonHomePage()
const productPage = new ProductPage()
const cartPage = new CartPage()

test(`Successfully Search for ${HomePage.ObjectsPage.ProductTarget} on Amazon`, async ({ page }) => {
  global.page = page

  await test.step('Navigate to the main screen of amazon.com', async () => {
    await HomePage.navegatTo();
  })

  await test.step('Main screen of Amazon is displayed', async () => {
    await HomePage.validateHomePage()
  })

  await test.step(`Search for the product  ${HomePage.ObjectsPage.ProductTarget}`, async () => {
    await HomePage.validateSearchProduct(HomePage.ObjectsPage.ProductTarget)
  })

  await test.step(`Validate the found product`, async () => {
    await HomePage.validateSearchProduct(HomePage.ObjectsPage.ProductTarget)
  })

});

test('Successfully Add Product to Cart', async ({ page }) => {
  global.page = page

  await test.step('Navigate to the main screen of amazon.com', async () => {
    await HomePage.navegatTo();
  })

  await test.step('Main screen of Amazon is displayed', async () => {
    await HomePage.validateHomePage()
  })

  await test.step(`Access the  ${HomePage.ObjectsPage.ProductTarget}  product page.`, async () => {
    await HomePage.clickBoxResultAfterSearch(HomePage.ObjectsPage.ProductTarget)
  })

  await test.step(`Validate the product page.`, async () => {
    const descProduct = "Uma sociedade secreta, traiçoeira e liderada por famílias ricas e influentes assombra os cidadãos de Gotham City e coloca em risco a segurança da cidade e do próprio Batman. A Corte das Corujas, há muito escondida sob a sombras do submundo, vai mostrar suas garras novamente, e desta vez, da maneira mais impiedosa possível. Um romance original de Greg Cox, autor best-seller de diversas novelizações de filmes, entre as quais Batman – O Cavaleiro das Trevas Ressurge." 
    await productPage.validateProductPage(HomePage.ObjectsPage.ProductTarget,descProduct )
    await productPage.clickAddCart()
  })

  await test.step(`Validate the product in the cart.`, async () => {
    await cartPage.validateFirstCart()
    await cartPage.validateSecondCart(HomePage.ObjectsPage.ProductTarget)
  })

});

The crucial aspect for using PageObject with Playwright is to define the Playwright “Page” class as a global variable, thereby enabling its use throughout your code.

We can observe in the test file that we have reused functions from the pages in the tests. Both test cases use various functions, leading to a reduction in the amount of code lines, improving future maintenance and reducing potential errors arising from redundancy.

Conclusion

We conclude our exploration of the integration of PageObject with Playwright, a partnership that has proven to be crucial in the field of test automation. Throughout this article, various ways have been seen in which this combination not only optimizes operational processes but also strengthens the development of robust and easily maintainable tests. May this knowledge contribute to continuous improvements in test quality and simplify the complexity of software development.

Sources and Useful Links

(Fast and reliable end-to-end testing for modern web apps | Playwright)https://playwright.dev/[https://nodejs.org/en/download]
LinkedIn: (Rodrigo Cabral | LinkedIn)[https://www.linkedin.com/in/rodrigo-cabral-0280b3121/]
GitHub: ( RodrigoOBC· GitHub)[https://github.com/RodrigoOBC/]

conclusion

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert