Best practice for maintainability in Automation

Hello!

This question will be long, so i understand if you will close this post :rofl:

I’m really torn because I can’t understand what the best compromise is between the different techniques for writing automatic tests using playwright.

My first doubt lies in where to put the steps, and whether putting them is correct. For example,
I have identified two approaches:


class Login {
     private readonly page: Page

     constructor(page: Page) {
         this.page = page
     }

     private get usernameInput() {
         return this.page.getByPlaceholder('Username')
     }

     private get passwordInput() {
         return this.page.getByPlaceholder('Password')
     }

     private get loginSubmit() {
         return this.page.getByRole('button', { name: 'Login' })
     }

     private get foo(): Locator{
         return this.page.locator("#foo")
     }

     public async performLoginSteps(username: string, password: string): Promise<void> {
         await test.step('inserting username', async () => {
             await this.usernameInput.fill(username)
         })

         await test.step('inserting password', async () => {
             await this.passwordInput.fill(password)
         })

         await test.step('Click login button', async () => {
             return await this.loginSubmit.click()
         })
     }

     async isLoggedSuccess(str: string): Promise<void>{
         await test.step('Validation', async () => {
             expect(this.foo).toBe(str)
         })
     }
}

test('Testing login', async ({ login }) => {
    await login.performLoginSteps("hello", "world")
    await isLoggedSuccess("something")
})

In this scenario, in the page object model, the methods encapsulate the steps, and the methods contain multiple actions (for example performloginsteps contains 3).

Furthermore, the assertion is placed inside the class.

In this second scenario instead:


class Login {
     private readonly page: Page

     constructor(page: Page) {
         this.page = page
     }

     private get usernameInput() {
         return this.page.getByPlaceholder('Username')
     }

     private get passwordInput() {
         return this.page.getByPlaceholder('Password')
     }

     private get loginSubmit() {
         return this.page.getByRole('button', { name: 'Login' })
     }

     private get foo(): Locator {
         return this.page.locator("#foo")
     }

     async fillUsername(str: string): Promise<void> {
         await this.fillUsername.fill(str)
     }

     async fillPassword(str: string): Promise<void> {
         await this.passwordInput.fill(str)
     }

     async clickLogin(): Promise<void> {
         await this.loginSubmit.click()
     }

     async getFooValue(): Promise<void> {
         await this.foo.innerText()
     }
}

test('Testing login', async ({ login }) => {
     await test.step('Filling username field', async () => {
         await login.fillUsername("hello")
     })

     await test.step('Filling password field', async () => {
         await login.fillPassword("hello")
     })

     await test.step('Click login', async () => {
         await login.clickLogin()
     })

     await test.step('Validation', async () => {
         expect(login.getFooValue()).toBe("something")
     })
})

The steps are defined in the test, leaving the POM cleaner.

In this case however, if I were to have another test that carries out the same steps, there would be a repetition of the code (and therefore also of the steps).

Here, the assertion is carried out in the test, and a POM method is called which returns the value of what we want to check/assert.

I just want to know your thoughts about this dilemma (for me is still a dilemma)
Thank you :smiley:

1 Like