From 8ffedd47583164ac9f331ba3351ba0a5435428e5 Mon Sep 17 00:00:00 2001 From: Joanna Dyraga Date: Sun, 19 Jan 2025 23:25:49 +0100 Subject: [PATCH 1/3] Introduced LoginPage class LoginPage is a class representing Page Object Model for the login page. As of now, it contains one method allowing to perform successful authentication. --- pages/login.page.ts | 34 ++++++++++++++++++++++++++++++++++ test-utils/utils.ts | 11 ----------- tests/logout.spec.ts | 4 ++-- tests/payment.spec.ts | 4 ++-- tests/session.spec.ts | 4 ++-- tests/transfer.spec.ts | 4 ++-- 6 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 pages/login.page.ts delete mode 100644 test-utils/utils.ts diff --git a/pages/login.page.ts b/pages/login.page.ts new file mode 100644 index 0000000..bbab282 --- /dev/null +++ b/pages/login.page.ts @@ -0,0 +1,34 @@ +import { Locator, Page } from '@playwright/test'; +import { loginData } from '../test-data/login.data'; + +export class LoginPage { + + private loginInput: Locator + private passwordInput: Locator + private loginButton: Locator + + constructor(private page: Page) { + this.loginInput = this.page.getByTestId('login-input') + this.passwordInput = this.page.getByTestId('password-input') + this.loginButton = this.page.getByTestId('login-button') + } + + async fillLogin(login: string) { + return this.loginInput.fill(login) + } + + async fillPassword(password: string) { + return this.passwordInput.fill(password) + } + + async clickButton() { + return this.loginButton.click() + } + + async loginSuccesfully() { + await this.page.goto('/') + await this.fillLogin(loginData.userId) + await this.fillPassword(loginData.userPassword) + await this.clickButton() + } +} diff --git a/test-utils/utils.ts b/test-utils/utils.ts deleted file mode 100644 index 6c440d8..0000000 --- a/test-utils/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Page } from '@playwright/test'; -import { loginData } from '../test-data/login.data'; - -export async function logIn(page: Page) { - const userId = loginData.userId; - const userPassword = loginData.userPassword; - await page.goto('/') - await page.getByTestId('login-input').fill(userId); - await page.getByTestId('password-input').fill(userPassword); - await page.getByTestId('login-button').click(); -} \ No newline at end of file diff --git a/tests/logout.spec.ts b/tests/logout.spec.ts index 9fc9e77..89b239e 100644 --- a/tests/logout.spec.ts +++ b/tests/logout.spec.ts @@ -1,10 +1,10 @@ import { test, expect } from '@playwright/test'; -import { logIn } from '../test-utils/utils'; +import { LoginPage } from '../pages/login.page'; test.describe('User logout', () => { test('successful logout', async ({ page }) => { // Act - await logIn(page); + await new LoginPage(page).loginSuccesfully(); await page.getByTestId('logout-button').click(); // Assert diff --git a/tests/payment.spec.ts b/tests/payment.spec.ts index 525974a..b0b3bfa 100644 --- a/tests/payment.spec.ts +++ b/tests/payment.spec.ts @@ -1,9 +1,9 @@ import { test, expect, Page } from '@playwright/test'; -import { logIn } from '../test-utils/utils'; +import { LoginPage } from '../pages/login.page'; test.describe('User payments (from menu)', () => { test.beforeEach(async ({ page }) => { - await logIn(page); + await new LoginPage(page).loginSuccesfully(); await page.getByRole('link', { name: 'płatności' }).click(); }) diff --git a/tests/session.spec.ts b/tests/session.spec.ts index 9de435d..1c60c58 100644 --- a/tests/session.spec.ts +++ b/tests/session.spec.ts @@ -1,9 +1,9 @@ import { test, expect, Page } from '@playwright/test'; -import { logIn } from '../test-utils/utils'; +import { LoginPage } from '../pages/login.page'; test.describe('session time', () => { test.beforeEach(async ({ page }) => { - await logIn(page) + await new LoginPage(page).loginSuccesfully() }); async function getSessionLeftMinutes(page: Page) { diff --git a/tests/transfer.spec.ts b/tests/transfer.spec.ts index 70c94fd..416c75e 100644 --- a/tests/transfer.spec.ts +++ b/tests/transfer.spec.ts @@ -1,9 +1,9 @@ import { test, expect, Page } from '@playwright/test'; -import { logIn } from '../test-utils/utils'; +import { LoginPage } from '../pages/login.page'; test.describe('User quick money transfer', () => { test.beforeEach(async ({ page }) => { - await logIn(page) + await new LoginPage(page).loginSuccesfully() }); async function readBalance(page: Page) { From 34fa3aa10cf59df4975c5b7702a0240b0cd32d07 Mon Sep 17 00:00:00 2001 From: Joanna Dyraga Date: Sun, 19 Jan 2025 23:27:32 +0100 Subject: [PATCH 2/3] Refactored login tests to use POM Instead of repeating the same playwright calls to fetch the locators we use the Page Object Model pattern for the login page. I am still not sure if this is the correct approach. LoginPage class nicely encapsulates all playwright calls but there is quite a lot of boilerplate code. --- pages/login.page.ts | 24 ++++++++++++++++++++---- tests/login.spec.ts | 27 +++++++++++++-------------- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/pages/login.page.ts b/pages/login.page.ts index bbab282..2af7057 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -1,4 +1,4 @@ -import { Locator, Page } from '@playwright/test'; +import { Locator, Page, expect } from '@playwright/test'; import { loginData } from '../test-data/login.data'; export class LoginPage { @@ -21,14 +21,30 @@ export class LoginPage { return this.passwordInput.fill(password) } - async clickButton() { - return this.loginButton.click() + async clickPasswordInput(){ + return this.passwordInput.click() + } + + async leavePasswordInput() { + return this.passwordInput.blur() } + async clickSubmit() { + return this.loginButton.click() + } + async loginSuccesfully() { await this.page.goto('/') await this.fillLogin(loginData.userId) await this.fillPassword(loginData.userPassword) - await this.clickButton() + await this.clickSubmit() + } + + async expectLoginInputError(error: string) { + return expect(this.page.getByTestId('error-login-id')).toHaveText(error); + } + + async expectPasswordInputError(error: string) { + return expect(this.page.getByTestId('error-login-password')).toHaveText(error); } } diff --git a/tests/login.spec.ts b/tests/login.spec.ts index 686133b..1269108 100644 --- a/tests/login.spec.ts +++ b/tests/login.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from '@playwright/test'; import { loginData } from '../test-data/login.data'; +import { LoginPage } from '../pages/login.page'; test.describe('User login', () => { test.beforeEach(async ({ page }) => { @@ -8,16 +9,12 @@ test.describe('User login', () => { test('successful login with correct credentials', async ({ page }) => { // Arrange - const userId = loginData.userId; - const userPassword = loginData.userPassword; const expectedUserName = loginData.expectedUserName; // Act - await page.getByTestId('login-input').fill(userId); - await page.getByTestId('password-input').fill(userPassword); - await page.getByTestId('login-button').click(); + await new LoginPage(page).loginSuccesfully(); - //Assert + // Assert await expect(page.getByTestId('user-name')).toHaveText(expectedUserName); }); @@ -27,11 +24,12 @@ test.describe('User login', () => { const expectedTextForShortUsername = 'identyfikator ma min. 8 znaków'; // Act - await page.getByTestId('login-input').fill(userId); - await page.getByTestId('password-input').click(); + const loginPage = new LoginPage(page) + await loginPage.fillLogin(userId); + await loginPage.clickPasswordInput(); - //Assert - await expect(page.getByTestId('error-login-id')).toHaveText(expectedTextForShortUsername); + // Assert + await loginPage.expectLoginInputError(expectedTextForShortUsername); }); test('unsuccessful login with too short password', async ({ page }) => { @@ -41,11 +39,12 @@ test.describe('User login', () => { const expectedTextForTooShortPassword = 'hasło ma min. 8 znaków'; // Act - await page.getByTestId('login-input').fill(userId); - await page.getByTestId('password-input').fill(userPassword); - await page.getByTestId('password-input').blur(); + const loginPage = new LoginPage(page) + await loginPage.fillLogin(userId) + await loginPage.fillPassword(userPassword) + await loginPage.leavePasswordInput() // Assert - await expect(page.getByTestId('error-login-password')).toHaveText(expectedTextForTooShortPassword); + await loginPage.expectPasswordInputError(expectedTextForTooShortPassword); }); }); \ No newline at end of file From e3787d330ee4daedd2e937f3806ef418e637f7ef Mon Sep 17 00:00:00 2001 From: Joanna Dyraga Date: Sat, 8 Feb 2025 23:06:52 +0100 Subject: [PATCH 3/3] Refactored tests to follow page object pattern Instead of repeating the same Playwright calls to fetch the locators we use the Page Object Model pattern for all currently used pages. I experimented with two approaches. The first approach was to reflect all possible actions with dedicated functions in page object. This ended up being a lot of boilerplate code. The second approach was to create the most popular selectors and save them as page object fields. Those locators that are used only one time, usually specific to the given test, are not included in the page object but created manually. This approach feels to be better for the current state of the project as it allows to avoid re-creating the same locators over and over again and at the same time we are not introducing too much boilerplate code. --- pages/login.page.ts | 41 ++++------------------ pages/payment.page.ts | 43 +++++++++++++++++++++++ test-data/payment.data.ts | 8 +++++ tests/login.spec.ts | 14 ++++---- tests/payment.spec.ts | 73 ++++++++++++++------------------------- tests/session.spec.ts | 4 +-- 6 files changed, 92 insertions(+), 91 deletions(-) create mode 100644 pages/payment.page.ts create mode 100644 test-data/payment.data.ts diff --git a/pages/login.page.ts b/pages/login.page.ts index 2af7057..eacaac8 100644 --- a/pages/login.page.ts +++ b/pages/login.page.ts @@ -2,10 +2,9 @@ import { Locator, Page, expect } from '@playwright/test'; import { loginData } from '../test-data/login.data'; export class LoginPage { - - private loginInput: Locator - private passwordInput: Locator - private loginButton: Locator + loginInput: Locator + passwordInput: Locator + loginButton: Locator constructor(private page: Page) { this.loginInput = this.page.getByTestId('login-input') @@ -13,38 +12,10 @@ export class LoginPage { this.loginButton = this.page.getByTestId('login-button') } - async fillLogin(login: string) { - return this.loginInput.fill(login) - } - - async fillPassword(password: string) { - return this.passwordInput.fill(password) - } - - async clickPasswordInput(){ - return this.passwordInput.click() - } - - async leavePasswordInput() { - return this.passwordInput.blur() - } - - async clickSubmit() { - return this.loginButton.click() - } - async loginSuccesfully() { await this.page.goto('/') - await this.fillLogin(loginData.userId) - await this.fillPassword(loginData.userPassword) - await this.clickSubmit() - } - - async expectLoginInputError(error: string) { - return expect(this.page.getByTestId('error-login-id')).toHaveText(error); - } - - async expectPasswordInputError(error: string) { - return expect(this.page.getByTestId('error-login-password')).toHaveText(error); + await this.loginInput.fill(loginData.userId); + await this.passwordInput.fill(loginData.userPassword) + await this.loginButton.click(); } } diff --git a/pages/payment.page.ts b/pages/payment.page.ts new file mode 100644 index 0000000..f09b4af --- /dev/null +++ b/pages/payment.page.ts @@ -0,0 +1,43 @@ +import { Locator, Page } from "@playwright/test"; +import { paymentData } from "../test-data/payment.data"; + +export class PaymentPage { + userAccount: Locator + paymentReceiver: Locator + receiverAccount: Locator + amountOfPayment: Locator + titleOfPayment: Locator + userEmail: Locator + checkboxEmail: Locator + checkboxListOfReceiver: Locator + + constructor(private page: Page) { + this.userAccount = this.page.locator('#form_account_from') + this.paymentReceiver = this.page.getByTestId('transfer_receiver') + this.receiverAccount = this.page.getByTestId('form_account_to') + this.amountOfPayment = this.page.getByTestId('form_amount') + this.titleOfPayment = this.page.getByTestId('form_title') + this.userEmail = this.page.locator('#form_email') + this.checkboxEmail = this.page.locator('#uniform-form_is_email span') + this.checkboxListOfReceiver = this.page.locator('#uniform-form_add_receiver span') + } + + async fillPaymentForm( + userAccount: string, + paymentReceiver: string, + receiverAccount: string, + amountOfPayment: string, + titleOfPayment: string, + userEmail: string + ) { + await this.userAccount.selectOption(userAccount); + await this.paymentReceiver.fill(paymentReceiver); + await this.receiverAccount.fill(receiverAccount); + await this.amountOfPayment.fill(amountOfPayment); + await this.titleOfPayment.fill(titleOfPayment); + await this.checkboxEmail.click(); + await this.userEmail.fill(userEmail); + await this.checkboxListOfReceiver.click(); + + } +} \ No newline at end of file diff --git a/test-data/payment.data.ts b/test-data/payment.data.ts new file mode 100644 index 0000000..f98bdc2 --- /dev/null +++ b/test-data/payment.data.ts @@ -0,0 +1,8 @@ +export const paymentData = { + userAccount: '[KO] konto na życie [13 159,20 PLN] 4141...0000', + paymentReceiver: 'Anna Kowalska', + reciverAccount: '34 5676 6767 6769 8798 7897 9878', + amountOfPayment: '350', + titleOfPayment: 'na prezent', + userEmail: 'jan.demobankowy@gmail.com', +} \ No newline at end of file diff --git a/tests/login.spec.ts b/tests/login.spec.ts index 1269108..426a1e0 100644 --- a/tests/login.spec.ts +++ b/tests/login.spec.ts @@ -25,11 +25,11 @@ test.describe('User login', () => { // Act const loginPage = new LoginPage(page) - await loginPage.fillLogin(userId); - await loginPage.clickPasswordInput(); + await loginPage.loginInput.fill(userId); + await loginPage.passwordInput.click(); // Assert - await loginPage.expectLoginInputError(expectedTextForShortUsername); + await expect(page.getByTestId('error-login-id')).toHaveText(expectedTextForShortUsername); }); test('unsuccessful login with too short password', async ({ page }) => { @@ -40,11 +40,11 @@ test.describe('User login', () => { // Act const loginPage = new LoginPage(page) - await loginPage.fillLogin(userId) - await loginPage.fillPassword(userPassword) - await loginPage.leavePasswordInput() + await loginPage.loginInput.fill(userId); + await loginPage.passwordInput.fill(userPassword); + await loginPage.passwordInput.blur(); // Assert - await loginPage.expectPasswordInputError(expectedTextForTooShortPassword); + await expect(page.getByTestId('error-login-password')).toHaveText(expectedTextForTooShortPassword); }); }); \ No newline at end of file diff --git a/tests/payment.spec.ts b/tests/payment.spec.ts index b0b3bfa..20bb9f4 100644 --- a/tests/payment.spec.ts +++ b/tests/payment.spec.ts @@ -1,51 +1,32 @@ import { test, expect, Page } from '@playwright/test'; import { LoginPage } from '../pages/login.page'; +import { paymentData } from '../test-data/payment.data'; +import { PaymentPage } from '../pages/payment.page'; -test.describe('User payments (from menu)', () => { +test.describe('User payments from menu', () => { test.beforeEach(async ({ page }) => { await new LoginPage(page).loginSuccesfully(); await page.getByRole('link', { name: 'płatności' }).click(); - }) - - async function fillPaymentForm( - page: Page, - userAccount: string, - paymentReceiver: string, - receiverAccount: string, - amountOfPayment: string, - titleOfPayment: string, - userEmail: string - ) { - await page.locator('#form_account_from').selectOption(userAccount); - await page.getByTestId('transfer_receiver').fill(paymentReceiver); - await page.getByTestId('form_account_to').fill(receiverAccount); - await page.getByTestId('form_amount').fill(amountOfPayment); - await page.getByTestId('form_title').fill(titleOfPayment); - await page.getByLabel('ekspresowy').click(); - await page.locator('#uniform-form_is_email span').click(); - await page.locator('#form_email').fill(userEmail); - await page.locator('#uniform-form_add_receiver span').click(); - } + }); test('successful payment with required form data', async ({ page }) => { // Arrange - const userAccount = '[KO] konto na życie [13 159,20 PLN] 4141...0000'; - const paymentReceiver = 'Anna Kowalska'; - const reciverAccount = '34 5676 6767 6769 8798 7897 9878'; - const amountOfPayment = '350'; - const titleOfPayment = 'na prezent'; - const userEmail = 'jan.demobankowy@gmail.com'; + const userAccount = paymentData.userAccount; + const paymentReceiver = paymentData.paymentReceiver; + const receiverAccount = paymentData.reciverAccount; + const amountOfPayment = paymentData.amountOfPayment; + const titleOfPayment = paymentData.titleOfPayment; + const userEmail = paymentData.userEmail; // Act - await fillPaymentForm( - page, + await new PaymentPage(page).fillPaymentForm( userAccount, paymentReceiver, - reciverAccount, + receiverAccount, amountOfPayment, titleOfPayment, userEmail - ); + ) await page.getByRole('button', { name: 'wykonaj przelew' }).click(); // Assert @@ -58,16 +39,15 @@ test.describe('User payments (from menu)', () => { test('unsuccessful payment with missing receiver', async ({ page }) => { // Arrange - const userAccount = '[KO] konto na życie [13 159,20 PLN] 4141...0000'; + const userAccount = paymentData.userAccount; const paymentReceiver = ''; - const receiverAccount = '34 5676 6767 6769 8798 7897 9878'; - const amountOfPayment = '350'; - const titleOfPayment = 'na prezent'; - const userEmail = 'jan.demobankowy@gmail.com'; + const receiverAccount = paymentData.reciverAccount; + const amountOfPayment = paymentData.amountOfPayment; + const titleOfPayment = paymentData.titleOfPayment; + const userEmail = paymentData.userEmail; // Act - await fillPaymentForm( - page, + await new PaymentPage(page).fillPaymentForm( userAccount, paymentReceiver, receiverAccount, @@ -83,16 +63,15 @@ test.describe('User payments (from menu)', () => { test('unsuccessful payment with missing receiver account', async ({ page }) => { // Arrange - const userAccount = '[KO] konto na życie [13 159,20 PLN] 4141...0000'; - const paymentReceiver = 'Anna Kowalska'; + const userAccount = paymentData.userAccount; + const paymentReceiver = paymentData.paymentReceiver; const receiverAccount = ''; - const amountOfPayment = '350'; - const titleOfPayment = 'na prezent'; - const userEmail = 'jan.demobankowy@gmail.com'; + const amountOfPayment = paymentData.amountOfPayment; + const titleOfPayment = paymentData.titleOfPayment; + const userEmail = paymentData.userEmail; // Act - await fillPaymentForm( - page, + await new PaymentPage(page).fillPaymentForm( userAccount, paymentReceiver, receiverAccount, @@ -105,4 +84,4 @@ test.describe('User payments (from menu)', () => { await expect(page.getByTestId('error-widget-2-transfer-account')) .toContainText('pole wymagane'); }); -}); \ No newline at end of file +}); diff --git a/tests/session.spec.ts b/tests/session.spec.ts index 1c60c58..aaf57b9 100644 --- a/tests/session.spec.ts +++ b/tests/session.spec.ts @@ -29,5 +29,5 @@ test.describe('session time', () => { await page.waitForTimeout(1500) const timeAfter = await getSessionLeft(page) await expect(timeAfter).toBe('09:59') - }) -}) \ No newline at end of file + }); +}); \ No newline at end of file