diff --git a/data/deliveryDetails.js b/data/deliveryDetails.js new file mode 100644 index 0000000..23d2d24 --- /dev/null +++ b/data/deliveryDetails.js @@ -0,0 +1,36 @@ +const delivery1 = { + firstName: "First", + lastName: "LastOne", + address: "123 Address Avenue", + postcode: "1020301", + city: "Major City One", + country: "United States of America" + +} + +const delivery2 = { + firstName: "Second", + lastName: "LastTwo", + address: "123 Address Avenue", + postcode: "1020302", + city: "Major City Two", + country: "United States of America" + +} + +const delivery3 = { + firstName: "Third", + lastName: "LastThree", + address: "123 Address Avenue3", + postcode: "1020303", + city: "Major City Three", + country: "United States of America" + +} + +export const deliveryDetails = [ + delivery1, + delivery2, + delivery3 + +] diff --git a/data/paymentDetails.js b/data/paymentDetails.js new file mode 100644 index 0000000..7aa047a --- /dev/null +++ b/data/paymentDetails.js @@ -0,0 +1,6 @@ +export const paymentDetails = { + owner: "First LastOne", + number: "1234123412341234", + date: "12/47", + cvc: "456", +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b88b38d..e744ba5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "automated-web-testing-with-javascript-and-playwright", "version": "1.0.0", "license": "ISC", + "dependencies": { + "uuid": "^9.0.0" + }, "devDependencies": { "@playwright/test": "^1.37.1" } @@ -62,6 +65,14 @@ "engines": { "node": ">=16" } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } } } } diff --git a/package.json b/package.json index d2c8376..31027f2 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,15 @@ "description": "", "main": "index.js", "scripts": { - "test":"playwright test --headed" + "test": "playwright test --headed" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@playwright/test": "^1.37.1" + }, + "dependencies": { + "uuid": "^9.0.0" } } diff --git a/page-objects/CheckoutPage.js b/page-objects/CheckoutPage.js new file mode 100644 index 0000000..eb6649a --- /dev/null +++ b/page-objects/CheckoutPage.js @@ -0,0 +1,43 @@ +import { expect } from "@playwright/test" + +export class CheckoutPage { + + constructor(page) { + this.page = page + this.basketCards = page.locator('[data-qa="basket-card"]') + this.basketItemPrices = page.locator('[data-qa="basket-item-price"]') + this.basketItemRemoveButtons = page.locator('[data-qa="basket-card-remove-item"]') + this.continueToCheckoutButton = page.locator('[data-qa="continue-to-checkout"]') + + + } + + removeCheapestProduct = async () => { + await this.basketCards.first().waitFor() + await this.basketItemPrices.first().waitFor() + + const itemsBefore = await this.basketCards.count() + const allPriceTexts = await this.basketItemPrices.allInnerTexts() + const convertToNumbers = allPriceTexts.map((text) => { + const formatText = text.replace("$","") + return parseInt(formatText, 10) + }) + + const smallestPrice = Math.min(convertToNumbers) + const smallestPriceIndex = convertToNumbers.indexOf(smallestPrice) + const specificRemoveButton = this.basketItemRemoveButtons.nth(smallestPriceIndex) + + await specificRemoveButton.waitFor() + await specificRemoveButton.click() + + await expect(this.basketCards).toHaveCount(itemsBefore - 1) + } + + continueToCheckout = async () => { + await this.continueToCheckoutButton.waitFor() + await this.continueToCheckoutButton.click() + await this.page.waitForURL(/\/login/, {timeout: 3000}) + } + + +} \ No newline at end of file diff --git a/page-objects/DeliveryPage.js b/page-objects/DeliveryPage.js new file mode 100644 index 0000000..416d042 --- /dev/null +++ b/page-objects/DeliveryPage.js @@ -0,0 +1,83 @@ +import { expect } from "@playwright/test" + +export class DeliveryPage { + + constructor(page) { + this.page = page + + this.firstNameField = page.locator('[data-qa="delivery-first-name"]') + this.lastNameField = page.locator('[data-qa="delivery-last-name"]') + this.addressField = page.locator('[data-qa="delivery-address-street"]') + this.postcodeField = page.locator('[data-qa="delivery-postcode"]') + this.cityField = page.locator('[data-qa="delivery-city"]') + this.countryDropdown = page.locator('[data-qa="country-dropdown"]') + + this.paymentButton = page.getByRole('button', { name: 'Continue to payment' }) + this.saveButton = page.locator('[data-qa="save-address-button"]') + this.savedAddressContainer = page.locator('[data-qa="saved-address-container"]') + + this.savedFirstName = page.locator('[data-qa="saved-address-firstName"]') + this.savedLastName = page.locator('[data-qa="saved-address-lastName"]') + this.savedAddress = page.locator('[data-qa="saved-address-street"]') + this.savedPostcode = page.locator('[data-qa="saved-address-postcode"]') + this.savedCity = page.locator('[data-qa="saved-address-city"]') + this.savedCountry = page.locator('[data-qa="saved-address-country"]') + + } + + fillDeliveryDetails = async (deliveryDetails) =>{ + + await this.firstNameField.waitFor() + await this.firstNameField.fill(deliveryDetails.firstName) + await this.lastNameField.waitFor() + await this.lastNameField.fill(deliveryDetails.lastName) + await this.addressField.waitFor() + await this.addressField.fill(deliveryDetails.address) + await this.postcodeField.waitFor() + await this.postcodeField.fill(deliveryDetails.postcode) + await this.cityField.waitFor() + await this.cityField.fill(deliveryDetails.city) + await this.countryDropdown.waitFor() + await this.countryDropdown.selectOption(deliveryDetails.country) + + } + + saveAddress = async () => { + const addrCountBefore = await this.savedAddressContainer.count() + + await this.saveButton.waitFor() + await this.saveButton.click() + + await expect(this.savedAddressContainer).toHaveCount(addrCountBefore + 1) + + await this.savedFirstName.first().waitFor() + expect(await this.savedFirstName.first().innerText()).toBe(await this.firstNameField.inputValue()) + expect(await this.savedLastName.first().innerText()).toBe(await this.lastNameField.inputValue()) + expect(await this.savedAddress.first().innerText()).toBe(await this.addressField.inputValue()) + expect(await this.savedPostcode.first().innerText()).toBe(await this.postcodeField.inputValue()) + expect(await this.savedCity.first().innerText()).toBe(await this.cityField.inputValue()) + expect(await this.savedCountry.first().innerText()).toBe(await this.countryDropdown.inputValue()) + } + + clearDetails = async () => { + await this.firstNameField.waitFor() + await this.firstNameField.fill("") + await this.lastNameField.waitFor() + await this.lastNameField.fill("") + await this.addressField.waitFor() + await this.addressField.fill("") + await this.postcodeField.waitFor() + await this.postcodeField.fill("") + await this.cityField.waitFor() + await this.cityField.fill("") + } + + continueToPayment = async () => { + await this.paymentButton.waitFor() + await this.paymentButton.click() + + await this.page.waitForURL(/\/payment/, {timeout: 3000}) + + } + +} \ No newline at end of file diff --git a/page-objects/LoginPage.js b/page-objects/LoginPage.js new file mode 100644 index 0000000..c1ee555 --- /dev/null +++ b/page-objects/LoginPage.js @@ -0,0 +1,15 @@ +import { expect } from "@playwright/test" + +export class LoginPage { + + constructor(page) { + this.page = page + this.signupButton = page.locator('[data-qa="go-to-signup-button"]') + } + + moveToSignup = async () => { + await this.signupButton.waitFor() + await this.signupButton.click() + await this.page.waitForURL(/\/signup/, {timeout: 3000}) + } +} \ No newline at end of file diff --git a/page-objects/Navigation.js b/page-objects/Navigation.js new file mode 100644 index 0000000..1ce4f55 --- /dev/null +++ b/page-objects/Navigation.js @@ -0,0 +1,22 @@ +export class Navigation { + constructor(page){ + this.page = page + this.basketCounter = page.locator('[data-qa="header-basket-count"]') + this.checkoutLink = page.getByRole('link', { name: 'Checkout' }) + + } + + getBasketCount = async () => { + await this.basketCounter.waitFor() + const text = await this.basketCounter.innerText() + return parseInt(text, 10) + } + + goToCheckout = async () => { + this.checkoutLink.waitFor() + + await this.checkoutLink.click() + await this.page.waitForURL("/basket") + + } +} diff --git a/page-objects/PaymentPage.js b/page-objects/PaymentPage.js new file mode 100644 index 0000000..f84ee42 --- /dev/null +++ b/page-objects/PaymentPage.js @@ -0,0 +1,77 @@ +import { expect } from "@playwright/test" + +export class PaymentPage { + + constructor(page) { + this.page = page + + // Discount + this.discountField = page.getByPlaceholder('Discount code') + this.discountCode = page + .frameLocator('[data-qa="active-discount-container"]') + .locator('[data-qa="discount-code"]') + this.applyDiscount = page.getByRole('button', { name: 'Submit discount' }) + this.priceTotal = page.locator('[data-qa="total-value"]') + this.discountTotal = page.locator('[data-qa="total-with-discount-value"]') + this.discountText = page.locator('[data-qa="discount-active-message"]') + + // Card + this.cardOwner = page.locator('[data-qa="credit-card-owner"]') + this.cardNumber = page.locator('[data-qa="credit-card-number"]') + this.cardDate = page.locator('[data-qa="valid-until"]') + this.cardCVC = page.locator('[data-qa="credit-card-cvc"]') + + // Pay + this.payButton = page.getByRole('button', { name: 'Pay' }) + + } + + activateDiscount = async () => { + await this.discountCode.waitFor() + const code = await this.discountCode.innerText() + await this.discountField.waitFor() + //await this.discountText.waitFor() + + expect (await this.discountTotal.isVisible()).toBe(false) + expect (await this.discountText.isVisible()).toBe(false) + + // 1. Using .full with await expect() due to laggy input + // await this.discountField.fill(code) + // expect (this.discountField).toHaveValue(code) + + // 2. Using page.keyboard.type to manually enter in code / simulate real world + await this.discountField.focus() + await this.page.keyboard.type(code, {delay: 500}) + expect (await this.discountField.inputValue()).toBe(code) + + await this.priceTotal.waitFor() + const totalValueText = await this.priceTotal.innerText() + const totalNumber = totalValueText.replace("$","") + const totalValue = parseInt(totalNumber, 10) + + await this.applyDiscount.waitFor() + await this.applyDiscount.click() + + await this.discountTotal.waitFor() + const discountValueText = await this.discountTotal.innerText() + const discountNumber = discountValueText.replace("$","") + const discountValue = parseInt(discountNumber, 10) + + expect(discountValue).toBeLessThan(totalValue) + + } + + fillPaymentDetails = async (paymentDetails) => { + + await this.cardOwner.waitFor() + await this.cardOwner.fill(paymentDetails.owner) + await this.cardNumber.waitFor() + await this.cardNumber.fill(paymentDetails.number) + await this.cardDate.waitFor() + await this.cardDate.fill(paymentDetails.date) + await this.cardCVC.waitFor() + await this.cardCVC.fill(paymentDetails.cvc) + + } + +} \ No newline at end of file diff --git a/page-objects/ProductsPage.js b/page-objects/ProductsPage.js index 0f5cf82..0ac2588 100644 --- a/page-objects/ProductsPage.js +++ b/page-objects/ProductsPage.js @@ -1,27 +1,46 @@ import { expect } from "@playwright/test" +import { Navigation } from "./Navigation.js" export class ProductsPage { constructor(page) { this.page = page this.addButtons = page.locator('[data-qa="product-button"]') + this.sortDropdown = page.locator('[data-qa="sort-dropdown"]') + this.productTitle = page.locator('[data-qa="product-title"]') } visit = async () => { await this.page.goto("/") - //console.log("Buttons --- " + await this.page.locator('[data-qa="product-button"]').count()) } - addProductToBasket = async (productIndex) => { - //data-qa="product-button" - console.log("--- addProductToBasket("+ productIndex +")") + + addProductToBasket = async (productIndex) => { const specificAddButton = this.addButtons.nth(productIndex) await specificAddButton.waitFor() - await expect(specificAddButton).toHaveText("Add To Basket") - await specificAddButton.click() - await expect(specificAddButton).toHaveText("Remove from Basket") + await expect(specificAddButton).toHaveText("Add to Basket") + const navigation = new Navigation(this.page) + const basketCountBefore = await navigation.getBasketCount() + + await specificAddButton.click() + + await expect(specificAddButton).toHaveText("Remove from Basket") + const basketCountAfter = await navigation.getBasketCount() + + expect(basketCountAfter).toBeGreaterThan(basketCountBefore) + } + + sortByCheapest = async () => { + await this.sortDropdown.waitFor() + await this.productTitle.first().waitFor() + const productTitleBefore = await this.productTitle.allInnerTexts() + + await this.sortDropdown.selectOption("price-asc") + const productTitleAfter = await this.productTitle.allInnerTexts() + + expect(productTitleAfter).not.toEqual(productTitleBefore) } } \ No newline at end of file diff --git a/page-objects/RegisterPage.js b/page-objects/RegisterPage.js new file mode 100644 index 0000000..31d6d44 --- /dev/null +++ b/page-objects/RegisterPage.js @@ -0,0 +1,20 @@ +export class RegisterPage { + + constructor(page) { + this.page = page + this.emailInput = page.getByPlaceholder('e-mail') + this.passwordInput = page.getByPlaceholder('password') + this.registerButton = page.getByRole('button', { name: 'Register' }) + } + + signupNewUser = async (email, password) => { + await this.emailInput.waitFor() + await this.emailInput.fill(email) + + await this.passwordInput.waitFor() + await this.passwordInput.fill(password) + + await this.registerButton.waitFor() + await this.registerButton.click() + } +} \ No newline at end of file diff --git a/shopping-store-windows-386.exe b/shopping-store-windows-386.exe new file mode 100644 index 0000000..32a208c Binary files /dev/null and b/shopping-store-windows-386.exe differ diff --git a/tests/new_user_full_journey.spec.js b/tests/new_user_full_journey.spec.js index 5c2a332..11f5fda 100644 --- a/tests/new_user_full_journey.spec.js +++ b/tests/new_user_full_journey.spec.js @@ -1,19 +1,63 @@ import {test} from "@playwright/test" +import { v4 as uuidv4 } from "uuid"; + import {ProductsPage} from "../page-objects/ProductsPage.js" +import { Navigation } from "../page-objects/Navigation.js" +import { CheckoutPage } from "../page-objects/CheckoutPage.js" +import { LoginPage } from "../page-objects/LoginPage.js" +import { RegisterPage } from "../page-objects/RegisterPage.js" +import { DeliveryPage } from "../page-objects/DeliveryPage.js" +import { deliveryDetails } from "../data/deliveryDetails.js"; +import { PaymentPage } from "../page-objects/PaymentPage.js" +import { paymentDetails } from "../data/paymentDetails.js"; + test.only("New user full end-to-end test journey", async ({page}) => { // init const productsPage = new ProductsPage(page) + const navigation = new Navigation(page) + const checkoutPage = new CheckoutPage(page) + const loginPage = new LoginPage(page) + const registerPage = new RegisterPage(page) + const deliveryPage = new DeliveryPage(page) + const paymentPage = new PaymentPage(page) // 1. Visit await productsPage.visit() + await productsPage.sortByCheapest() // 2. Add to Basket await productsPage.addProductToBasket(0) await productsPage.addProductToBasket(1) await productsPage.addProductToBasket(2) + // 3. Navigate to Checkout + await navigation.goToCheckout() + + await checkoutPage.removeCheapestProduct() + await checkoutPage.continueToCheckout() + // 4. Navigate to Signup + await loginPage.moveToSignup() + + const email = uuidv4() + "@gmail.com" + const password = "secret" + // 5. Register New User + await registerPage.signupNewUser(email, password) + + await deliveryPage.fillDeliveryDetails(deliveryDetails[0]) + await deliveryPage.saveAddress() + // await deliveryPage.clearDetails() + // await deliveryPage.fillDetails(deliveryDetails[1]) + // await deliveryPage.saveAddress() + // await deliveryPage.clearDetails() + // await deliveryPage.fillDetails(deliveryDetails[2]) + // await deliveryPage.saveAddress() + + await deliveryPage.continueToPayment() + + await paymentPage.activateDiscount() + await paymentPage.fillPaymentDetails(paymentDetails) // Hold - await page.pause(); + //await page.pause(); }) \ No newline at end of file