From da844df3e1d49d7dc35c598d8eb08f1de6076f1c Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 10 Dec 2024 11:07:50 +0100 Subject: [PATCH 01/22] Reworked previous commit to comply with refactored main branch --- tests/base/cart.spec.ts | 47 +++++++++++++++++++--- tests/base/config/expected/expected.json | 2 +- tests/base/config/selectors/selectors.json | 1 + tests/base/config/slugs.json | 3 +- tests/base/fixtures/product.page.ts | 12 +++--- 5 files changed, 53 insertions(+), 12 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index d4512ec..2bc0da1 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -1,11 +1,13 @@ import {test, expect} from '@playwright/test'; import {ProductPage} from './fixtures/product.page'; import { MainMenuPage } from './fixtures/mainmenu.page'; +import {LoginPage} from './fixtures/login.page'; import slugs from './config/slugs.json'; +import selectors from './config/selectors/selectors.json'; import verify from './config/expected/expected.json'; -test.describe('Coupon Code tests', () => { +test.describe('Cart functionalities', () => { /** * @feature BeforeEach runs before each test in this group. * @scenario Add a product to the cart and confirm it's there. @@ -23,16 +25,51 @@ test.describe('Coupon Code tests', () => { //TODO: Use a storagestate or API call to add product to the cart so shorten test time await page.goto(slugs.productpage.simpleProductSlug); - await productPage.addSimpleProductToCart(); + await productPage.addSimpleProductToCart(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); await mainMenu.openMiniCart(); await expect(page.getByText(verify.miniCart.simpleProductInCartTitle)).toBeVisible(); await page.goto(slugs.cartSlug); }); test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { - + //TODO: Write test to add coupon }); + + test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { + //TODO: Write test to remove coupon + }); + + /** + * @feature Product permanence after login + * @scenario A product added to the cart should still be there after user has logged in + * @given I have a product in my cart + * @when I log in + * @then I should still have that product in my cart + */ + test('Product should remain in cart after logging in',{ tag: ['@cart', '@account']}, async ({page}) => { + await test.step('Add another product to cart', async () =>{ + const productpage = new ProductPage(page); + await page.goto(slugs.productpage.secondSimpleProductSlug); + await productpage.addSimpleProductToCart(selectors.productPage.secondSimpleProducTitle, slugs.productpage.secondSimpleProductSlug); + }); + + await test.step('Log in with account', async () =>{ + const login = new LoginPage(page); + let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; + let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; + + if(!emailInputValue || !passwordInputValue) { + throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + } + + await login.login(emailInputValue, passwordInputValue); + }); + + await page.goto(slugs.cartSlug); + await expect(page.getByRole('strong').getByRole('link', { name: selectors.productPage.simpleProductTitle }),`${selectors.productPage.simpleProductTitle} should still be in cart`).toBeVisible(); + await expect(page.getByRole('strong').getByRole('link', { name: selectors.productPage.secondSimpleProducTitle }),`${selectors.productPage.secondSimpleProducTitle} should still be in cart`).toBeVisible(); + }); + }); -//TODO: Write test to add coupon -//TODO: Write test to remove coupon \ No newline at end of file + diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 692eead..abf2145 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -17,7 +17,7 @@ "logoutConfirmationText": "You have signed out" } , "productPage": { - "simpleProductAddedNotification": "You added Push It Messenger" + "simpleProductAddedNotification": "You added" }, "miniCart": { "miniCartTitle": "My Cart", diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 0c5e47c..316c3ce 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -53,6 +53,7 @@ }, "productPage": { "simpleProductTitle": "Push It Messenger Bag", + "secondSimpleProducTitle": "Aim Analog Watch", "simpleProductPrice": ".final-price .price-wrapper .price", "configurableProductTitle": "Inez Full Zip Jacket", "configurableProductSizeLabel": "Size", diff --git a/tests/base/config/slugs.json b/tests/base/config/slugs.json index d684c77..20673df 100644 --- a/tests/base/config/slugs.json +++ b/tests/base/config/slugs.json @@ -9,10 +9,11 @@ }, "productpage": { "simpleProductSlug": "/push-it-messenger-bag.html", + "secondSimpleProductSlug": "/aim-analog-watch.html", "configurableProductSlug": "/inez-full-zip-jacket.html" }, "checkoutSlug": "/checkout/", - "cartSlug": "/cart/", + "cartSlug": "/checkout/cart/", "cartProductChangeSlug": "/cart/configure/", "contact": "/contact" } \ No newline at end of file diff --git a/tests/base/fixtures/product.page.ts b/tests/base/fixtures/product.page.ts index c3f17bb..5349707 100644 --- a/tests/base/fixtures/product.page.ts +++ b/tests/base/fixtures/product.page.ts @@ -5,17 +5,19 @@ import verify from '../config/expected/expected.json'; export class ProductPage { readonly page: Page; - readonly simpleProductTitle: Locator; - readonly simpleProductAddToCartButon: Locator; + simpleProductTitle: Locator; + simpleProductAddToCartButon: Locator; constructor(page: Page) { this.page = page; - this.simpleProductTitle = page.getByRole('heading', {name: selectors.productPage.simpleProductTitle, exact:true}); this.simpleProductAddToCartButon = page.getByRole('button', { name: 'shopping-cart Add to Cart' }); } - async addSimpleProductToCart(){ - let productAddedNotification = verify.productPage.simpleProductAddedNotification; + async addSimpleProductToCart(product: string, url: string){ + await this.page.goto(url); + this.simpleProductTitle = this.page.getByRole('heading', {name: product, exact:true}); + let productAddedNotification = `${verify.productPage.simpleProductAddedNotification} ${product}`; + await expect(this.simpleProductTitle.locator('span')).toBeVisible(); await this.simpleProductAddToCartButon.click(); From 5eb64d1c7463c14b81d59cde6c52d63a630a8180 Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 10 Dec 2024 14:14:11 +0100 Subject: [PATCH 02/22] Tests to add and remove coupon code from cart --- tests/base/cart.spec.ts | 53 ++++++++++++++++++++-- tests/base/config/expected/expected.json | 5 ++ tests/base/config/selectors/selectors.json | 6 ++- tests/base/config/slugs.json | 2 +- tests/base/fixtures/cart.page.ts | 34 +++++++++++++- 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index d4512ec..b8d67cb 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -1,6 +1,7 @@ import {test, expect} from '@playwright/test'; import {ProductPage} from './fixtures/product.page'; import { MainMenuPage } from './fixtures/mainmenu.page'; +import { CartPage} from './fixtures/cart.page'; import slugs from './config/slugs.json'; import verify from './config/expected/expected.json'; @@ -29,10 +30,54 @@ test.describe('Coupon Code tests', () => { await page.goto(slugs.cartSlug); }); + /** + * @feature Discount Code + * @scenario User adds a discount code to their cart + * @given I have a product in my cart + * @and I am on my cart page + * @when I click on the 'add discount code' button + * @then I fill in a code + * @and I click on 'apply code' + * @then I should see a confirmation that my code has been added + * @and the code should be visible in the cart + * @and a discount should be applied to the product + */ test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { - + const cart = new CartPage(page); + let discountCode = process.env.DISCOUNT_CODE; + + if(!discountCode) { + throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + } + + await cart.applyDiscountCode(discountCode); + }); + + /** + * @feature Remove discount code from cart + * @scenario User has added a discount code, then removes it + * @given I have a product in my cart + * @and I am on my cart page + * @when I add a discount code + * @then I should see a notification + * @and the code should be visible in the cart + * @and a discount should be applied to a product + * @when I click the 'cancel coupon' button + * @then I should see a notification the discount has been removed + * @and the discount should no longer be visible. + */ + + test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { + const cart = new CartPage(page); + let discountCode = process.env.DISCOUNT_CODE; + + if(!discountCode) { + throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + } + + // TODO: create API call to quickly add discount code rather than run a test again. + await cart.applyDiscountCode(discountCode); + await cart.removeDiscountCode(); }); -}); -//TODO: Write test to add coupon -//TODO: Write test to remove coupon \ No newline at end of file +}); \ No newline at end of file diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 133920e..cf17640 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -19,6 +19,11 @@ "productPage": { "simpleProductAddedNotification": "You added Push It Messenger" }, + "cart": { + "discountAppliedNotification": "You used coupon code", + "discountRemovedNotification": "You canceled the coupon code.", + "priceReducedSymbols": "- $" + }, "miniCart": { "miniCartTitle": "My Cart", "simpleProductInCartTitle": "x Push It Messenger Bag", diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 32030f2..313a281 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -63,7 +63,11 @@ }, "cart": { "updateItemButtonLabel": "shopping-cart Update item", - "cartQuantityLabel": "Qty" + "cartQuantityLabel": "Qty", + "showDiscountFormButtonLabel": "Apply Discount Code", + "discountInputFieldLabel": "Enter discount code", + "applyDiscountButtonLabel": "Apply Discount", + "cancelCouponButtonLabel": "Cancel Coupon" }, "miniCart": { "checkOutButtonLabel": "Checkout", diff --git a/tests/base/config/slugs.json b/tests/base/config/slugs.json index d684c77..e0977d1 100644 --- a/tests/base/config/slugs.json +++ b/tests/base/config/slugs.json @@ -12,7 +12,7 @@ "configurableProductSlug": "/inez-full-zip-jacket.html" }, "checkoutSlug": "/checkout/", - "cartSlug": "/cart/", + "cartSlug": "/checkout/cart/", "cartProductChangeSlug": "/cart/configure/", "contact": "/contact" } \ No newline at end of file diff --git a/tests/base/fixtures/cart.page.ts b/tests/base/fixtures/cart.page.ts index f8e2207..b001f60 100644 --- a/tests/base/fixtures/cart.page.ts +++ b/tests/base/fixtures/cart.page.ts @@ -1,12 +1,42 @@ import {expect, type Locator, type Page} from '@playwright/test'; +import selectors from '../config/selectors/selectors.json'; +import verify from '../config/expected/expected.json'; export class CartPage { readonly page: Page; - readonly applyDiscountButton: Locator; + readonly showDiscountButton: Locator; constructor(page: Page) { this.page = page; - this.applyDiscountButton = this.page.getByRole('button', { name: 'Apply Discount Code' }); + this.showDiscountButton = this.page.getByRole('button', { name: selectors.cart.showDiscountFormButtonLabel }); + } + + async applyDiscountCode(code: string){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountButton.click(); + } + + let applyDiscoundButton = this.page.getByRole('button', {name: selectors.cart.applyDiscountButtonLabel, exact:true}); + let discountField = this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel); + await discountField.fill(code); + await applyDiscoundButton.click(); + + await expect(this.page.getByText(`${verify.cart.discountAppliedNotification} "${code}"`),`Notification that discount code ${code} has been applied`).toBeVisible(); + await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should be visible on the page`).toBeVisible(); + } + + async removeDiscountCode(){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountButton.click(); + } + + let cancelCouponButton = this.page.getByRole('button', {name: selectors.cart.cancelCouponButtonLabel}); + await cancelCouponButton.click(); + + await expect(this.page.getByText(verify.cart.discountRemovedNotification),`Notification should be visible`).toBeVisible(); + await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should not be on the page`).toBeHidden(); } } \ No newline at end of file From 5482921390516167caf5aaefd779f5cd3ffc995e Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 10 Dec 2024 14:48:36 +0100 Subject: [PATCH 03/22] WIP: Coupon codes in checkout --- tests/base/cart.spec.ts | 2 +- tests/base/checkout.spec.ts | 25 ++++++++++++++++++++++++ tests/base/config/expected/expected.json | 3 +++ tests/base/fixtures/checkout.page.ts | 22 +++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index b8d67cb..af8b3fd 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -6,7 +6,7 @@ import { CartPage} from './fixtures/cart.page'; import slugs from './config/slugs.json'; import verify from './config/expected/expected.json'; -test.describe('Coupon Code tests', () => { +test.describe('Cart functionalities', () => { /** * @feature BeforeEach runs before each test in this group. * @scenario Add a product to the cart and confirm it's there. diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index e5b1677..ccff905 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -47,6 +47,31 @@ test.describe('Checkout actions', () => { await page.goto(slugs.checkoutSlug); }); + /** + * @feature Discount Code + * @scenario User adds a discount code to their cart + * @given I have a product in my cart + * @and I am on my cart page + * @when I click on the 'add discount code' button + * @then I fill in a code + * @and I click on 'apply code' + * @then I should see a confirmation that my code has been added + * @and the code should be visible in the cart + * @and a discount should be applied to the product + */ + test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { + //TODO: Write tests to ensure code also works if user is NOT logged in. + const checkout = new CheckoutPage(page); + let discountCode = process.env.DISCOUNT_CODE; + + if(!discountCode) { + throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + } + + await checkout.applyDiscountCodeCheckout(discountCode); + }); + + //TODO: Add Gherkin feature description test('Place order for simple product',{ tag: '@simple-product-order',}, async ({page}) => { const checkoutPage = new CheckoutPage(page); await checkoutPage.placeOrder(); diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index cf17640..67db013 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -31,6 +31,9 @@ "productRemovedConfirmation": "You removed the item.", "productQuantityChangedConfirmation": "was updated in your shopping cart" }, + "checkout": { + "couponAppliedNotification": "Your coupon was succesfully applied" + }, "contactPage": { "messageSentConfirmationText": "Thanks for contacting us with" }, diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index 54acfe8..93c3b3f 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -1,11 +1,15 @@ import {expect, type Locator, type Page} from '@playwright/test'; +import selectors from '../config/selectors/selectors.json'; +import verify from '../config/expected/expected.json'; + export class CheckoutPage { //TODO: Expand with fields for when user is not logged in or has not provided an address readonly page: Page; readonly shippingMethodOptionFixed: Locator; readonly paymentMethodOptionCheck: Locator; + readonly showDiscountFormButton: Locator; readonly placeOrderButton: Locator; readonly continueShoppingButton: Locator; @@ -13,6 +17,7 @@ export class CheckoutPage { this.page = page; this.shippingMethodOptionFixed = this.page.getByLabel('Fixed'); this.paymentMethodOptionCheck = this.page.getByLabel('Check / Money order Free'); + this.showDiscountFormButton = this.page.getByRole('button', {name: 'Apply Discount Code'}); this.placeOrderButton = this.page.getByRole('button', { name: 'Place Order' }); this.continueShoppingButton = this.page.getByRole('link', { name: 'Continue Shopping' }); } @@ -51,4 +56,21 @@ export class CheckoutPage { await expect(this.continueShoppingButton, `Your order number is: ${orderNumber}`).toBeVisible(); return orderNumber; } + + async applyDiscountCodeCheckout(code: string){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountFormButton.click(); + } + + //TODO: Add check here: if user is logged in and code has already been applied, then it carries over into the checkout. + // This happens to all cart actions when user logs in before running the test. + let applyCouponCheckoutButton = this.page.getByRole('button', { name: 'Apply Coupon' }); + let checkoutDiscountField = this.page.getByPlaceholder('Enter discount code'); + await checkoutDiscountField.fill(code); + await applyCouponCheckoutButton.click(); + + await expect(this.page.getByText(`${verify.checkout.couponAppliedNotification}`),`Notification that discount code ${code} has been applied`).toBeVisible(); + await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should be visible on the page`).toBeVisible(); + } } \ No newline at end of file From 18b6274a613f99f8216983c0d30804c9c4c76be5 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 09:14:47 +0100 Subject: [PATCH 04/22] WIP: update coupon in checkout test to check if coupon has already been applied --- tests/base/fixtures/checkout.page.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index 93c3b3f..daccb1a 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -63,8 +63,12 @@ export class CheckoutPage { await this.showDiscountFormButton.click(); } - //TODO: Add check here: if user is logged in and code has already been applied, then it carries over into the checkout. - // This happens to all cart actions when user logs in before running the test. + if(await this.page.getByText(verify.cart.priceReducedSymbols).isVisible()){ + // discount is already active. + let cancelCouponButton = this.page.getByRole('button', { name: 'Cancel Coupon' }); + await cancelCouponButton.click(); + } + let applyCouponCheckoutButton = this.page.getByRole('button', { name: 'Apply Coupon' }); let checkoutDiscountField = this.page.getByPlaceholder('Enter discount code'); await checkoutDiscountField.fill(code); From 625ca8b031c49d162c970804f4cef72aafd055fe Mon Sep 17 00:00:00 2001 From: Shay Faber Date: Wed, 11 Dec 2024 09:56:15 +0100 Subject: [PATCH 05/22] Remove empty tests from cart.spec.ts --- tests/base/cart.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 2bc0da1..e765d42 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -31,14 +31,6 @@ test.describe('Cart functionalities', () => { await page.goto(slugs.cartSlug); }); - test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { - //TODO: Write test to add coupon - }); - - test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { - //TODO: Write test to remove coupon - }); - /** * @feature Product permanence after login * @scenario A product added to the cart should still be there after user has logged in From 518531fd3e73714aeb260a632bc11d68bcffa780 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 11:28:44 +0100 Subject: [PATCH 06/22] Fixed test to add coupon code in checkout --- tests/base/checkout.spec.ts | 103 ++++++++++++----------- tests/base/config/expected/expected.json | 3 +- tests/base/fixtures/checkout.page.ts | 5 +- 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index ccff905..f55dc04 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -10,43 +10,57 @@ import verify from './config/expected/expected.json'; import { CheckoutPage } from './fixtures/checkout.page'; // no resetting storageState, mainmenu has more functionalities when logged in. -// TODO: remove this beforeEach() once authentication as project set-up/fixture works. - -// Before each test, log in +/** + * @feature BeforeEach runs before each test in this group. + * @scenario Add product to the cart, confirm it's there, then move to checkout. + * @given I am on any page + * @when I navigate to a (simple) product page + * @and I add it to my cart + * @then I should see a notification + * @when I navigate to the checkout + * @then the checkout page should be shown + * @and I should see the product in the minicart + */ test.beforeEach(async ({ page }) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; - let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; - - if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); - } + const productPage = new ProductPage(page); - const loginPage = new LoginPage(page); - await loginPage.login(emailInputValue, passwordInputValue); + //TODO: Use a storagestate or API call to add product to the cart so shorten test time + await page.goto(slugs.productpage.simpleProductSlug); + await productPage.addSimpleProductToCart(); + await page.goto(slugs.checkoutSlug); }); -test.describe('Checkout actions', () => { - /** - * @feature BeforeEach runs before each test in this group. - * @scenario Add product to the cart, confirm it's there, then move to checkout. - * @given I am on any page - * @when I navigate to a (simple) product page - * @and I add it to my cart - * @then I should see a notification - * @when I navigate to the checkout - * @then the checkout page should be shown - * @and I should see the product in the minicart - */ + +test.describe('Checkout (login required)', () => { + // Before each test, log in + // TODO: remove this beforeEach() once authentication as project set-up/fixture works. test.beforeEach(async ({ page }) => { - const productPage = new ProductPage(page); + let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; + let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; - //TODO: Use a storagestate or API call to add product to the cart so shorten test time - await page.goto(slugs.productpage.simpleProductSlug); - await productPage.addSimpleProductToCart(); - await page.goto(slugs.checkoutSlug); + if(!emailInputValue || !passwordInputValue) { + throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + } + + const loginPage = new LoginPage(page); + await loginPage.login(emailInputValue, passwordInputValue); }); + //TODO: Add Gherkin feature description + test('Place order for simple product',{ tag: '@simple-product-order',}, async ({page}) => { + const checkoutPage = new CheckoutPage(page); + await checkoutPage.placeOrder(); + }); + + + +}); + +test.describe('Checkout (guest)', () => { + // TODO: Write test to confirm order can be placed without an account + // TODO: Write test for logged-in user who hasn't added an address yet. + /** * @feature Discount Code * @scenario User adds a discount code to their cart @@ -59,25 +73,16 @@ test.describe('Checkout actions', () => { * @and the code should be visible in the cart * @and a discount should be applied to the product */ - test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { - //TODO: Write tests to ensure code also works if user is NOT logged in. - const checkout = new CheckoutPage(page); - let discountCode = process.env.DISCOUNT_CODE; - - if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); - } - - await checkout.applyDiscountCodeCheckout(discountCode); - }); - - //TODO: Add Gherkin feature description - test('Place order for simple product',{ tag: '@simple-product-order',}, async ({page}) => { - const checkoutPage = new CheckoutPage(page); - await checkoutPage.placeOrder(); - }); - - // TODO: Write test to confirm order can be placed without an account - // TODO: Write test for logged-in user who hasn't added an address yet. + test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { + //TODO: Write tests to ensure code also works if user is NOT logged in. + const checkout = new CheckoutPage(page); + let discountCode = process.env.DISCOUNT_CODE; + + if(!discountCode) { + throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + } + + await checkout.applyDiscountCodeCheckout(discountCode); + }); +}); -}); \ No newline at end of file diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 67db013..23d8d7d 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -32,7 +32,8 @@ "productQuantityChangedConfirmation": "was updated in your shopping cart" }, "checkout": { - "couponAppliedNotification": "Your coupon was succesfully applied" + "couponAppliedNotification": "Your coupon was successfully applied", + "checkoutPriceReducedSymbol": "-$" }, "contactPage": { "messageSentConfirmationText": "Thanks for contacting us with" diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index daccb1a..20fa673 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -71,10 +71,11 @@ export class CheckoutPage { let applyCouponCheckoutButton = this.page.getByRole('button', { name: 'Apply Coupon' }); let checkoutDiscountField = this.page.getByPlaceholder('Enter discount code'); + await checkoutDiscountField.fill(code); await applyCouponCheckoutButton.click(); - await expect(this.page.getByText(`${verify.checkout.couponAppliedNotification}`),`Notification that discount code ${code} has been applied`).toBeVisible(); - await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should be visible on the page`).toBeVisible(); + await expect(this.page.getByText(`${verify.checkout.couponAppliedNotification}`),`Notification that discount code ${code} has been applied`).toBeVisible({timeout: 30000}); + await expect(this.page.getByText(verify.checkout.checkoutPriceReducedSymbol),`'-$' should be visible on the page`).toBeVisible(); } } \ No newline at end of file From eb810c40184cc608b21a8e9035daf52d79844e56 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 11:43:59 +0100 Subject: [PATCH 07/22] Add test to remove coupon code from checkout, move labels to JSON files --- tests/base/checkout.spec.ts | 27 ++++++++++++++++++++ tests/base/config/expected/expected.json | 1 + tests/base/config/selectors/selectors.json | 10 ++++++++ tests/base/fixtures/checkout.page.ts | 29 ++++++++++++++++------ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index f55dc04..252cde7 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -84,5 +84,32 @@ test.describe('Checkout (guest)', () => { await checkout.applyDiscountCodeCheckout(discountCode); }); + + /** + * @feature Remove discount code from checkout + * @scenario User has added a discount code, then removes it + * @given I have a product in my cart + * @and I am on the checkout page + * @when I add a discount code + * @then I should see a notification + * @and the code should be visible in the cart + * @and a discount should be applied to a product + * @when I click the 'cancel coupon' button + * @then I should see a notification the discount has been removed + * @and the discount should no longer be visible. + */ + + test('Remove coupon code from checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { + const checkout = new CheckoutPage(page); + let discountCode = process.env.DISCOUNT_CODE; + + if(!discountCode) { + throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + } + + // TODO: create API call to quickly add discount code rather than run a test again. + await checkout.applyDiscountCodeCheckout(discountCode); + await checkout.removeDiscountCode(); + }); }); diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 23d8d7d..b643096 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -33,6 +33,7 @@ }, "checkout": { "couponAppliedNotification": "Your coupon was successfully applied", + "couponRemovedNotification": "Your coupon was successfully removed", "checkoutPriceReducedSymbol": "-$" }, "contactPage": { diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 313a281..533b586 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -69,6 +69,16 @@ "applyDiscountButtonLabel": "Apply Discount", "cancelCouponButtonLabel": "Cancel Coupon" }, + "checkout": { + "shippingMethodFixedLabel": "Fixed", + "paymentOptionCheckLabel": "Check / Money order Free", + "openDiscountFormLabel": "Apply Discount Code", + "placeOrderButtonLabel": "Place Order", + "cancelDiscountButtonLabel": "Cancel Coupon", + "applyDiscountButtonLabel": "Apply Coupon", + "discountInputFieldLabel": "Enter discount code", + "continueShoppingLabel": "Continue Shopping" + }, "miniCart": { "checkOutButtonLabel": "Checkout", "toCartLinkLabel": "View and Edit Cart", diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index 20fa673..2c2ebd9 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -15,11 +15,11 @@ export class CheckoutPage { constructor(page: Page){ this.page = page; - this.shippingMethodOptionFixed = this.page.getByLabel('Fixed'); - this.paymentMethodOptionCheck = this.page.getByLabel('Check / Money order Free'); - this.showDiscountFormButton = this.page.getByRole('button', {name: 'Apply Discount Code'}); - this.placeOrderButton = this.page.getByRole('button', { name: 'Place Order' }); - this.continueShoppingButton = this.page.getByRole('link', { name: 'Continue Shopping' }); + this.shippingMethodOptionFixed = this.page.getByLabel(selectors.checkout.shippingMethodFixedLabel); + this.paymentMethodOptionCheck = this.page.getByLabel(selectors.checkout.paymentOptionCheckLabel); + this.showDiscountFormButton = this.page.getByRole('button', {name: selectors.checkout.openDiscountFormLabel}); + this.placeOrderButton = this.page.getByRole('button', { name: selectors.checkout.placeOrderButtonLabel }); + this.continueShoppingButton = this.page.getByRole('link', { name: selectors.checkout.continueShoppingLabel }); } async placeOrder(){ @@ -65,12 +65,12 @@ export class CheckoutPage { if(await this.page.getByText(verify.cart.priceReducedSymbols).isVisible()){ // discount is already active. - let cancelCouponButton = this.page.getByRole('button', { name: 'Cancel Coupon' }); + let cancelCouponButton = this.page.getByRole('button', { name: selectors.checkout.cancelDiscountButtonLabel }); await cancelCouponButton.click(); } - let applyCouponCheckoutButton = this.page.getByRole('button', { name: 'Apply Coupon' }); - let checkoutDiscountField = this.page.getByPlaceholder('Enter discount code'); + let applyCouponCheckoutButton = this.page.getByRole('button', { name: selectors.checkout.applyDiscountButtonLabel }); + let checkoutDiscountField = this.page.getByPlaceholder(selectors.checkout.discountInputFieldLabel); await checkoutDiscountField.fill(code); await applyCouponCheckoutButton.click(); @@ -78,4 +78,17 @@ export class CheckoutPage { await expect(this.page.getByText(`${verify.checkout.couponAppliedNotification}`),`Notification that discount code ${code} has been applied`).toBeVisible({timeout: 30000}); await expect(this.page.getByText(verify.checkout.checkoutPriceReducedSymbol),`'-$' should be visible on the page`).toBeVisible(); } + + async removeDiscountCode(){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountFormButton.click(); + } + + let cancelCouponButton = this.page.getByRole('button', {name: selectors.cart.cancelCouponButtonLabel}); + await cancelCouponButton.click(); + + await expect(this.page.getByText(verify.checkout.couponRemovedNotification),`Notification should be visible`).toBeVisible(); + await expect(this.page.getByText(verify.checkout.checkoutPriceReducedSymbol),`'-$' should not be on the page`).toBeHidden(); + } } \ No newline at end of file From b35bdede45f34a83eef93186bd9d85a7db497d10 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 13:03:23 +0100 Subject: [PATCH 08/22] Minor reworks of errors caused by pulling from remote branch --- tests/base/cart.spec.ts | 30 +++++++++++++++--------------- tests/base/fixtures/cart.page.ts | 4 +--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 8119c23..9d9d527 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -2,7 +2,6 @@ import { test, expect } from '@playwright/test'; import { ProductPage } from './fixtures/product.page'; import { MainMenuPage } from './fixtures/mainmenu.page'; import { CartPage} from './fixtures/cart.page'; -import { CartPage } from './fixtures/cart.page'; import slugs from './config/slugs.json'; import selectors from './config/selectors/selectors.json'; @@ -32,6 +31,20 @@ test.describe('Cart functionalities', () => { await page.goto(slugs.cartSlug); }); + /** + * @feature Remove product from cart + * @scenario User has added a product and wants to remove it from the cart page + * @given I have added a product to my cart + * @and I am on the cart page + * @when I click the delete button + * @then I should see a notification that the product has been removed from my cart + * @and I should no longer see the product in my cart + */ + test('Remove product from cart',{ tag: '@cart',}, async ({page}) => { + const cart = new CartPage(page); + await cart.removeProduct(selectors.productPage.simpleProductTitle); + }); + /** * @feature Discount Code * @scenario User adds a discount code to their cart @@ -68,7 +81,6 @@ test.describe('Cart functionalities', () => { * @then I should see a notification the discount has been removed * @and the discount should no longer be visible. */ - test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { const cart = new CartPage(page); let discountCode = process.env.DISCOUNT_CODE; @@ -80,17 +92,5 @@ test.describe('Cart functionalities', () => { // TODO: create API call to quickly add discount code rather than run a test again. await cart.applyDiscountCode(discountCode); await cart.removeDiscountCode(); - - /** @feature Remove product from cart - * @scenario User has added a product and wants to remove it from the cart page - * @given I have added a product to my cart - * @and I am on the cart page - * @when I click the delete button - * @then I should see a notification that the product has been removed from my cart - * @and I should no longer see the product in my cart - */ - test('Remove product from cart',{ tag: '@cart',}, async ({page}) => { - const cart = new CartPage(page); - await cart.removeProduct(selectors.productPage.simpleProductTitle); }); - +}) diff --git a/tests/base/fixtures/cart.page.ts b/tests/base/fixtures/cart.page.ts index 28f39d0..d040230 100644 --- a/tests/base/fixtures/cart.page.ts +++ b/tests/base/fixtures/cart.page.ts @@ -38,12 +38,10 @@ export class CartPage { await expect(this.page.getByText(verify.cart.discountRemovedNotification),`Notification should be visible`).toBeVisible(); await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should not be on the page`).toBeHidden(); - - this.applyDiscountButton = this.page.getByRole('button', { name: selectors.cart.applyDiscountCodeLabel }); } async removeProduct(name: string){ - let removeButton = this.page.getByLabel(`${selectors.cart.remove} ${name}`); + let removeButton = this.page.getByLabel(`${selectors.cart.cancelCouponButtonLabel} ${name}`); await removeButton.click(); await expect(removeButton,`Button to remove product is no longer visible`).toBeHidden(); } From 52fb1a0c74cca94de41fb0ed58dcb0acf677c4f9 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 13:24:36 +0100 Subject: [PATCH 09/22] Test to check for error message when using invalid coupon code in cart --- tests/base/cart.spec.ts | 19 ++++++++++++++++++- tests/base/config/expected/expected.json | 4 +++- tests/base/fixtures/cart.page.ts | 16 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 9d9d527..bc7dfe5 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -81,7 +81,7 @@ test.describe('Cart functionalities', () => { * @then I should see a notification the discount has been removed * @and the discount should no longer be visible. */ - test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { + test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { const cart = new CartPage(page); let discountCode = process.env.DISCOUNT_CODE; @@ -93,4 +93,21 @@ test.describe('Cart functionalities', () => { await cart.applyDiscountCode(discountCode); await cart.removeDiscountCode(); }); + + + /** + * @feature Incorrect discount code check + * @scenario The user provides an incorrect discount code, the system should reflect that + * @given I have a product in my cart + * @and I am on the cart page + * @when I enter a wrong discount code + * @then I should get a notification that the code did not work. + */ + + test('Using an invalid discountcode should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { + const cart = new CartPage(page); + let wrongDiscountCode = "incorrect discount code"; + + await cart.enterWrongCouponCode(wrongDiscountCode); + }); }) diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index b643096..92a9a0f 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -22,7 +22,9 @@ "cart": { "discountAppliedNotification": "You used coupon code", "discountRemovedNotification": "You canceled the coupon code.", - "priceReducedSymbols": "- $" + "priceReducedSymbols": "- $", + "incorrectCouponCodeNotificationOne": "The coupon code", + "incorrectCouponCodeNotificationTwo": "is not valid." }, "miniCart": { "miniCartTitle": "My Cart", diff --git a/tests/base/fixtures/cart.page.ts b/tests/base/fixtures/cart.page.ts index d040230..7d85920 100644 --- a/tests/base/fixtures/cart.page.ts +++ b/tests/base/fixtures/cart.page.ts @@ -27,6 +27,22 @@ export class CartPage { await expect(this.page.getByText(verify.cart.priceReducedSymbols),`'- $' should be visible on the page`).toBeVisible(); } + async enterWrongCouponCode(code: string){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountButton.click(); + } + + let applyDiscoundButton = this.page.getByRole('button', {name: selectors.cart.applyDiscountButtonLabel, exact:true}); + let discountField = this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel); + await discountField.fill(code); + await applyDiscoundButton.click(); + + let incorrectNotification = `${verify.cart.incorrectCouponCodeNotificationOne} "${code}" ${verify.cart.incorrectCouponCodeNotificationTwo}`; + + await expect(this.page.getByText(incorrectNotification), `Code should not work`).toBeVisible(); + } + async removeDiscountCode(){ if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ // discount field is not open. From 5e93d6a4188532c1206fed04313f0e2feafa3f35 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 11 Dec 2024 13:28:01 +0100 Subject: [PATCH 10/22] Created test: using invalid coupon code in checkout should give an error --- tests/base/checkout.spec.ts | 16 ++++++++++++++++ tests/base/config/expected/expected.json | 3 ++- tests/base/fixtures/checkout.page.ts | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 252cde7..1675aba 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -111,5 +111,21 @@ test.describe('Checkout (guest)', () => { await checkout.applyDiscountCodeCheckout(discountCode); await checkout.removeDiscountCode(); }); + + /** + * @feature Incorrect discount code check + * @scenario The user provides an incorrect discount code, the system should reflect that + * @given I have a product in my cart + * @and I am on the cart page + * @when I enter a wrong discount code + * @then I should get a notification that the code did not work. + */ + + test('Using an invalid discountcode should give an error',{ tag: ['@checkout', '@coupon-code'] }, async ({page}) => { + const checkout = new CheckoutPage(page); + let wrongDiscountCode = "incorrect discount code"; + + await checkout.enterWrongCouponCode(wrongDiscountCode); + }); }); diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 92a9a0f..89f4eb1 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -36,7 +36,8 @@ "checkout": { "couponAppliedNotification": "Your coupon was successfully applied", "couponRemovedNotification": "Your coupon was successfully removed", - "checkoutPriceReducedSymbol": "-$" + "checkoutPriceReducedSymbol": "-$", + "incorrectDiscountNotification": "The coupon code isn't valid. Verify the code and try again." }, "contactPage": { "messageSentConfirmationText": "Thanks for contacting us with" diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index 2c2ebd9..bc439dd 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -79,6 +79,20 @@ export class CheckoutPage { await expect(this.page.getByText(verify.checkout.checkoutPriceReducedSymbol),`'-$' should be visible on the page`).toBeVisible(); } + async enterWrongCouponCode(code: string){ + if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ + // discount field is not open. + await this.showDiscountFormButton.click(); + } + + let applyCouponCheckoutButton = this.page.getByRole('button', { name: selectors.checkout.applyDiscountButtonLabel }); + let checkoutDiscountField = this.page.getByPlaceholder(selectors.checkout.discountInputFieldLabel); + await checkoutDiscountField.fill(code); + await applyCouponCheckoutButton.click(); + + await expect(this.page.getByText(verify.checkout.incorrectDiscountNotification), `Code should not work`).toBeVisible(); + } + async removeDiscountCode(){ if(await this.page.getByPlaceholder(selectors.cart.discountInputFieldLabel).isHidden()){ // discount field is not open. From 8eb223e3b8c3c4edde65e659cccdebdf98d9243f Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 16 Dec 2024 09:18:18 +0100 Subject: [PATCH 11/22] Fixed .env.example var --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 3d537f2..45cbffe 100644 --- a/.env.example +++ b/.env.example @@ -11,5 +11,5 @@ MAGENTO_EXISTING_ACCOUNT_EMAIL= MAGENTO_EXISTING_ACCOUNT_PASSWORD= MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD= -MAGENTO_COUPON_CODE= +DISCOUNT_CODE= CAPTCHA_BYPASS=false From 451c48c35059568ba558fa1785875a53dce5e11f Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 16 Dec 2024 09:27:53 +0100 Subject: [PATCH 12/22] Reworked DISCOUNT_CODE variable --- .env.example | 2 +- tests/base/cart.spec.ts | 6 +++--- tests/base/checkout.spec.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 45cbffe..3d537f2 100644 --- a/.env.example +++ b/.env.example @@ -11,5 +11,5 @@ MAGENTO_EXISTING_ACCOUNT_EMAIL= MAGENTO_EXISTING_ACCOUNT_PASSWORD= MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD= -DISCOUNT_CODE= +MAGENTO_COUPON_CODE= CAPTCHA_BYPASS=false diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index a80f1da..bdf199a 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -7,7 +7,7 @@ import slugs from './config/slugs.json'; import selectors from './config/selectors/selectors.json'; import verify from './config/expected/expected.json'; -test.describe('Cart functionalities', () => { +test.describe('Cart functionalities (guest)', () => { /** * @feature BeforeEach runs before each test in this group. * @scenario Add a product to the cart and confirm it's there. @@ -65,7 +65,7 @@ test.describe('Cart functionalities', () => { */ test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { const cart = new CartPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); @@ -89,7 +89,7 @@ test.describe('Cart functionalities', () => { */ test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { const cart = new CartPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 252cde7..edb1530 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -76,7 +76,7 @@ test.describe('Checkout (guest)', () => { test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { //TODO: Write tests to ensure code also works if user is NOT logged in. const checkout = new CheckoutPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); @@ -101,7 +101,7 @@ test.describe('Checkout (guest)', () => { test('Remove coupon code from checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { const checkout = new CheckoutPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); From b3da3d63a83c09d993fdc043070b8fcaea355b89 Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 16 Dec 2024 09:56:19 +0100 Subject: [PATCH 13/22] Solved various comments --- tests/base/cart.spec.ts | 8 ++++---- tests/base/checkout.spec.ts | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index c96a68f..7e60bf0 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -73,10 +73,10 @@ test.describe('Cart functionalities', () => { */ test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { const cart = new CartPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } await cart.applyDiscountCode(discountCode); @@ -97,7 +97,7 @@ test.describe('Cart functionalities', () => { */ test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { const cart = new CartPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); @@ -118,7 +118,7 @@ test.describe('Cart functionalities', () => { * @then I should get a notification that the code did not work. */ - test('Using an invalid discountcode should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { + test('Using an invalid coupon code should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { const cart = new CartPage(page); let wrongDiscountCode = "incorrect discount code"; diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 1675aba..6dfb233 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -76,7 +76,7 @@ test.describe('Checkout (guest)', () => { test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { //TODO: Write tests to ensure code also works if user is NOT logged in. const checkout = new CheckoutPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); @@ -101,7 +101,7 @@ test.describe('Checkout (guest)', () => { test('Remove coupon code from checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page}) => { const checkout = new CheckoutPage(page); - let discountCode = process.env.DISCOUNT_CODE; + let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); @@ -121,11 +121,9 @@ test.describe('Checkout (guest)', () => { * @then I should get a notification that the code did not work. */ - test('Using an invalid discountcode should give an error',{ tag: ['@checkout', '@coupon-code'] }, async ({page}) => { + test('Using an invalid coupon code should give an error',{ tag: ['@checkout', '@coupon-code'] }, async ({page}) => { const checkout = new CheckoutPage(page); - let wrongDiscountCode = "incorrect discount code"; - - await checkout.enterWrongCouponCode(wrongDiscountCode); + await checkout.enterWrongCouponCode("incorrect discount code"); }); }); From c7f1b415e38863e423ac03e7fd0f1781695aa6d4 Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 18 Dec 2024 10:20:59 +0100 Subject: [PATCH 14/22] Rework error messages to mention the .env variables names --- tests/base/cart.spec.ts | 4 ++-- tests/base/checkout.spec.ts | 6 +++--- tests/base/login.spec.ts | 2 +- tests/base/mainmenu.spec.ts | 2 +- tests/base/register.spec.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index bdf199a..d7588be 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -68,7 +68,7 @@ test.describe('Cart functionalities (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } await cart.applyDiscountCode(discountCode); @@ -92,7 +92,7 @@ test.describe('Cart functionalities (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } // TODO: create API call to quickly add discount code rather than run a test again. diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index edb1530..4fca459 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -40,7 +40,7 @@ test.describe('Checkout (login required)', () => { let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); @@ -79,7 +79,7 @@ test.describe('Checkout (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } await checkout.applyDiscountCodeCheckout(discountCode); @@ -104,7 +104,7 @@ test.describe('Checkout (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } // TODO: create API call to quickly add discount code rather than run a test again. diff --git a/tests/base/login.spec.ts b/tests/base/login.spec.ts index e2842af..bc1bb07 100644 --- a/tests/base/login.spec.ts +++ b/tests/base/login.spec.ts @@ -24,7 +24,7 @@ base('User can log in with valid credentials', async ({page}) => { let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); diff --git a/tests/base/mainmenu.spec.ts b/tests/base/mainmenu.spec.ts index 067708c..8e447ee 100644 --- a/tests/base/mainmenu.spec.ts +++ b/tests/base/mainmenu.spec.ts @@ -11,7 +11,7 @@ test.beforeEach(async ({ page }) => { let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); diff --git a/tests/base/register.spec.ts b/tests/base/register.spec.ts index dc63480..28b389b 100644 --- a/tests/base/register.spec.ts +++ b/tests/base/register.spec.ts @@ -29,7 +29,7 @@ test('User can register an account', async ({page}) => { const uniqueEmail = `${emailHandle}${randomNumber}@${emailHost}`; if(!existingAccountPassword){ - throw new Error("Password variable not defined in .env"); + throw new Error("MAGENTO_EXISTING_ACCOUNT_PASSWORD has not been defined in the .env file."); } // password is retrieved from .env file in createNewAccount() function From 8f7f96e2c0138d8fd3db8042407f35d47bfb37de Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 18 Dec 2024 10:38:49 +0100 Subject: [PATCH 15/22] Renames of 'coupon code' to avoid issues with other pull request --- tests/base/cart.spec.ts | 8 +++----- tests/base/checkout.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 7e60bf0..eefc9de 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -100,7 +100,7 @@ test.describe('Cart functionalities', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } // TODO: create API call to quickly add discount code rather than run a test again. @@ -119,9 +119,7 @@ test.describe('Cart functionalities', () => { */ test('Using an invalid coupon code should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { - const cart = new CartPage(page); - let wrongDiscountCode = "incorrect discount code"; - - await cart.enterWrongCouponCode(wrongDiscountCode); + const cart = new CartPage(page); + await cart.enterWrongCouponCode("Incorrect Couon Code"); }); }) diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 6dfb233..d33cd49 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -79,7 +79,7 @@ test.describe('Checkout (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } await checkout.applyDiscountCodeCheckout(discountCode); @@ -104,7 +104,7 @@ test.describe('Checkout (guest)', () => { let discountCode = process.env.MAGENTO_COUPON_CODE; if(!discountCode) { - throw new Error(`discountCode appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } // TODO: create API call to quickly add discount code rather than run a test again. From 75cfdb089416e062a49b18b9979c889e5cfd892f Mon Sep 17 00:00:00 2001 From: Shay Date: Wed, 18 Dec 2024 10:47:42 +0100 Subject: [PATCH 16/22] Comment fixes --- tests/base/cart.spec.ts | 4 ++-- tests/base/checkout.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 2bad8fe..2cb52a1 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -52,7 +52,7 @@ test.describe('Cart functionalities (guest)', () => { }); await test.step('Log in with account', async () =>{ - const login = new LoginPage(page); + const loginPage = new LoginPage(page); let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; @@ -60,7 +60,7 @@ test.describe('Cart functionalities (guest)', () => { throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); } - await login.login(emailInputValue, passwordInputValue); + await loginPage.login(emailInputValue, passwordInputValue); }); await page.goto(slugs.cartSlug); diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 4fca459..11fc42e 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -27,7 +27,7 @@ test.beforeEach(async ({ page }) => { //TODO: Use a storagestate or API call to add product to the cart so shorten test time await page.goto(slugs.productpage.simpleProductSlug); - await productPage.addSimpleProductToCart(); + await productPage.addSimpleProductToCart(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); await page.goto(slugs.checkoutSlug); }); From b9438ae9254ac0711e45aa888b6a9ba5267ad786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Mon, 30 Dec 2024 16:00:59 +0100 Subject: [PATCH 17/22] Added setup script --- .../config/input-values/input-values.json | 9 ++- tests/base/config/selectors/selectors.json | 34 +++++++- tests/base/fixtures/account.page.ts | 34 ++++++-- tests/base/fixtures/magentoAdmin.page.ts | 81 +++++++++++++++++++ tests/base/setup.spec.ts | 32 ++++++++ 5 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 tests/base/fixtures/magentoAdmin.page.ts create mode 100644 tests/base/setup.spec.ts diff --git a/tests/base/config/input-values/input-values.json b/tests/base/config/input-values/input-values.json index 8cdae67..5b40bd6 100644 --- a/tests/base/config/input-values/input-values.json +++ b/tests/base/config/input-values/input-values.json @@ -35,5 +35,12 @@ "editZipCodeValue": "00151", "editCityValue": "Pallet Town", "editStateValue": "Kansas" + }, + "coupon": { + "couponCodeRuleName": "Test coupon", + "couponType": "Specific Coupon" + }, + "captcha": { + "captchaDisabled": "No" } -} \ No newline at end of file +} diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 28d47f0..0c85a2e 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -92,5 +92,37 @@ }, "contactPage": { "messageFieldLabel": "What’s on your mind?" + }, + "magentoAdminPage": { + "usernameFieldLabel": "Username", + "passwordFieldLabel": "Password", + "loginButtonLabel": "Sign In", + "navigation": { + "marketingButtonLabel": "Marketing", + "storesButtonLabel": "Stores" + }, + "subNavigation": { + "cartPriceRulesButtonLabel": "Cart Price Rules", + "configurationButtonLabel": "Configuration" + } + }, + "cartPriceRulesPage": { + "addCartPriceRuleButtonLabel": "Add New Rule", + "ruleNameFieldLabel": "Rule Name", + "websitesSelectLabel": "Websites", + "customerGroupsSelectLabel": "Customer Groups", + "couponTypeSelectField": "select[name='coupon_type']", + "couponCodeFieldLabel": "Coupon Code", + "actionsSubtitleLabel": "Actions", + "discountAmountFieldLabel": "Discount Amount", + "saveRuleButtonLabel": "Save" + }, + "configurationPage": { + "customersTabLabel": "Customers", + "customerConfigurationTabLabel": "Customer Configuration", + "captchaSectionLabel": "CAPTCHA", + "captchaSettingSelectField": "#customer_captcha_enable", + "captchaSettingSystemCheckbox": "#customer_captcha_enable_inherit", + "saveConfigButtonLabel": "Save Config" } -} \ No newline at end of file +} diff --git a/tests/base/fixtures/account.page.ts b/tests/base/fixtures/account.page.ts index e965eee..026cf8e 100644 --- a/tests/base/fixtures/account.page.ts +++ b/tests/base/fixtures/account.page.ts @@ -2,6 +2,7 @@ import {expect, type Locator, type Page} from '@playwright/test'; import selectors from '../config/selectors/selectors.json'; import verify from '../config/expected/expected.json'; +import slugs from '../config/slugs.json'; export class AccountPage { readonly page: Page; @@ -13,7 +14,7 @@ export class AccountPage { readonly zipCodeField: Locator; readonly cityField: Locator; readonly countrySelectorField: Locator; - readonly stateSelectorField: Locator; + readonly stateSelectorField: Locator; readonly saveAddressButton: Locator; readonly addNewAddressButton: Locator; readonly deleteAddressButton: Locator; @@ -23,6 +24,12 @@ export class AccountPage { readonly newPasswordField: Locator; readonly confirmNewPasswordField: Locator; readonly genericSaveButton: Locator; + readonly accountCreationFirstNameField: Locator; + readonly accountCreationLastNameField: Locator; + readonly accountCreationEmailField: Locator; + readonly accountCreationPasswordField: Locator; + readonly accountCreationPasswordRepeatField: Locator; + readonly accountCreationConfirmButton: Locator; // fields below are not required when adding an address. /* @@ -58,6 +65,13 @@ export class AccountPage { this.confirmNewPasswordField = page.getByLabel('Confirm New Password') this.genericSaveButton = page.getByRole('button', { name: selectors.general.genericSaveButtonLabel }); + // Account Creation elements + this.accountCreationFirstNameField = page.getByLabel(selectors.personalInformation.firstNameLabel); + this.accountCreationLastNameField = page.getByLabel(selectors.personalInformation.lastNameLabel); + this.accountCreationEmailField = page.getByLabel(selectors.credentials.emailFieldLabel, { exact: true}); + this.accountCreationPasswordField = page.getByLabel(selectors.credentials.passwordFieldLabel, { exact: true }); + this.accountCreationPasswordRepeatField = page.getByLabel(selectors.credentials.passwordConfirmFieldLabel); + this.accountCreationConfirmButton = page.getByRole('button', {name: selectors.accountCreation.createAccountButtonLabel}); // Address Book elements this.addNewAddressButton = page.getByRole('button',{name: selectors.accountDashboard.addAddressButtonLabel}); @@ -68,7 +82,7 @@ export class AccountPage { //TODO: Add ability to choose different country other than US async addNewAddress(phonenumber: string,streetName: string, zipCode: string, cityName: string, state: string){ let addressAddedNotification = verify.address.newAddressAddedNotifcation; - + // Name should be filled in automatically. await expect(this.firstNameField).not.toBeEmpty(); @@ -90,7 +104,7 @@ export class AccountPage { let addressModifiedNotification = verify.address.newAddressAddedNotifcation; await this.editAddressButton.click(); - + // Name should be filled in automatically, but editable. await expect(this.firstNameField).not.toBeEmpty(); await expect(this.lastNameField).not.toBeEmpty(); @@ -123,7 +137,6 @@ export class AccountPage { await expect(this.page.getByText(addressDeletedNotification)).toBeVisible(); } - async updatePassword(currentPassword:string, newPassword: string){ let passwordUpdatedNotification = verify.account.changedPasswordNotificationText; await this.changePasswordCheck.check(); @@ -137,4 +150,15 @@ export class AccountPage { await expect(this.page.getByText(passwordUpdatedNotification)).toBeVisible(); console.log(`Password has been changed! Please update your .env file password with "${newPassword}"`); } -} \ No newline at end of file + + async create(firstName: string, lastName: string, email: string, password: string){ + await this.page.goto(slugs.account.createAccountSlug); + + await this.accountCreationFirstNameField.fill(firstName); + await this.accountCreationLastNameField.fill(lastName); + await this.accountCreationEmailField.fill(email); + await this.accountCreationPasswordField.fill(password); + await this.accountCreationPasswordRepeatField.fill(password); + await this.accountCreationConfirmButton.click(); + } +} diff --git a/tests/base/fixtures/magentoAdmin.page.ts b/tests/base/fixtures/magentoAdmin.page.ts new file mode 100644 index 0000000..0ca78cd --- /dev/null +++ b/tests/base/fixtures/magentoAdmin.page.ts @@ -0,0 +1,81 @@ +import {expect, type Locator, type Page} from '@playwright/test'; + +import selectors from '../config/selectors/selectors.json'; +import values from '../config/input-values/input-values.json'; + +import account from '../fixtures/account.page'; + +export class MagentoAdminPage { + readonly page: Page; + readonly adminLoginEmailField: Locator; + readonly adminLoginPasswordField: Locator; + readonly adminLoginButton: Locator; + + constructor(page: Page) { + this.page = page; + this.adminLoginEmailField = page.getByLabel(selectors.magentoAdminPage.usernameFieldLabel); + this.adminLoginPasswordField = page.getByLabel(selectors.magentoAdminPage.passwordFieldLabel); + this.adminLoginButton = page.getByRole('button', {name: selectors.magentoAdminPage.loginButtonLabel}); + } + + async login(username: string, password: string){ + if(!process.env.MAGENTO_ADMIN_SLUG) { + throw new Error("MAGENTO_ADMIN_SLUG is not defined in your .env file."); + } + + await this.page.goto(process.env.MAGENTO_ADMIN_SLUG); + await this.adminLoginEmailField.fill(username); + await this.adminLoginPasswordField.fill(password); + await this.adminLoginButton.click(); + } + + async addCartPriceRule(magentoCouponCode: string){ + if(!process.env.MAGENTO_COUPON_CODE) { + throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); + } + + await this.page.getByRole('link', {name: selectors.magentoAdminPage.navigation.marketingButtonLabel}).click(); + await this.page.getByRole('link', {name: selectors.magentoAdminPage.subNavigation.cartPriceRulesButtonLabel}).click(); + await this.page.getByRole('button', {name: selectors.cartPriceRulesPage.addCartPriceRuleButtonLabel}).click(); + await this.page.getByLabel(selectors.cartPriceRulesPage.ruleNameFieldLabel).fill(values.coupon.couponCodeRuleName); + + const websiteSelector = await this.page.getByLabel(selectors.cartPriceRulesPage.websitesSelectLabel); + await websiteSelector.evaluate(select => { + for (const option of select.options) { + option.selected = true; + } + select.dispatchEvent(new Event('change')); + }); + + const customerGroupsSelector = await this.page.getByLabel(selectors.cartPriceRulesPage.customerGroupsSelectLabel, { exact: true }); + await customerGroupsSelector.evaluate(select => { + for (const option of select.options) { + option.selected = true; + } + select.dispatchEvent(new Event('change')); + }); + + await this.page.locator(selectors.cartPriceRulesPage.couponTypeSelectField).selectOption({ label: values.coupon.couponType }); + await this.page.getByLabel(selectors.cartPriceRulesPage.couponCodeFieldLabel).fill(magentoCouponCode); + + await this.page.getByText(selectors.cartPriceRulesPage.actionsSubtitleLabel, { exact: true }).click(); + await this.page.getByLabel(selectors.cartPriceRulesPage.discountAmountFieldLabel).fill('10'); + + await this.page.getByRole('button', { name: 'Save', exact: true }).click(); + } + + async disableLoginCaptcha() { + await this.page.getByRole('link', { name: selectors.magentoAdminPage.navigation.storesButtonLabel }).click(); + await this.page.getByRole('link', { name: selectors.magentoAdminPage.subNavigation.configurationButtonLabel }).click(); + await this.page.getByRole('tab', { name: selectors.configurationPage.customersTabLabel }).click(); + await this.page.getByRole('link', { name: selectors.configurationPage.customerConfigurationTabLabel }).click(); + + if (!await this.page.locator(selectors.configurationPage.captchaSettingSystemCheckbox).isVisible()) { + await this.page.getByRole('link', { name: /${selectors.configurationPage.captchaSectionLabel}/ }).click(); + } + + await this.page.locator(selectors.configurationPage.captchaSettingSystemCheckbox).uncheck(); + await this.page.locator(selectors.configurationPage.captchaSettingSelectField).selectOption({ label: values.captcha.captchaDisabled }); + await this.page.getByRole('button', { name: selectors.configurationPage.saveConfigButtonLabel }).click(); + } +} diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts new file mode 100644 index 0000000..7f3b51b --- /dev/null +++ b/tests/base/setup.spec.ts @@ -0,0 +1,32 @@ +import {test as base} from '@playwright/test'; +import {MagentoAdminPage} from './fixtures/magentoAdmin.page'; +import {AccountPage} from './fixtures/account.page'; + +import values from './config/input-values/input-values.json'; + +base('Setup Magento environment for tests', async ({page}) => { + let magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; + let magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; + + if(!magentoAdminUsername || !magentoAdminPassword) { + throw new Error("MAGENTO_ADMIN_USERNAME or MAGENTO_ADMIN_PASSWORD is not defined in your .env file."); + } + + const magentoAdminPage = new MagentoAdminPage(page); + await magentoAdminPage.login(magentoAdminUsername, magentoAdminPassword); + + let magentoCouponCode = process.env.MAGENTO_COUPON_CODE; + if(!magentoCouponCode) { + throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); + } + + await magentoAdminPage.addCartPriceRule(magentoCouponCode); + await magentoAdminPage.disableLoginCaptcha(); + + const accountPage = new AccountPage(page); + + if(!process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL || !process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD) { + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL or process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD is not defined in your .env file."); + } + await accountPage.create(values.accountCreation.firstNameValue, values.accountCreation.lastNameValue, process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL, process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD); +}); From 7eb13304e1bd3e5e68a2c698e717a3571331879f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Mon, 30 Dec 2024 16:11:07 +0100 Subject: [PATCH 18/22] Added setup flag to prevent double setups --- tests/base/setup.spec.ts | 70 ++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts index 7f3b51b..5e9a6fb 100644 --- a/tests/base/setup.spec.ts +++ b/tests/base/setup.spec.ts @@ -3,30 +3,46 @@ import {MagentoAdminPage} from './fixtures/magentoAdmin.page'; import {AccountPage} from './fixtures/account.page'; import values from './config/input-values/input-values.json'; - -base('Setup Magento environment for tests', async ({page}) => { - let magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; - let magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; - - if(!magentoAdminUsername || !magentoAdminPassword) { - throw new Error("MAGENTO_ADMIN_USERNAME or MAGENTO_ADMIN_PASSWORD is not defined in your .env file."); - } - - const magentoAdminPage = new MagentoAdminPage(page); - await magentoAdminPage.login(magentoAdminUsername, magentoAdminPassword); - - let magentoCouponCode = process.env.MAGENTO_COUPON_CODE; - if(!magentoCouponCode) { - throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); - } - - await magentoAdminPage.addCartPriceRule(magentoCouponCode); - await magentoAdminPage.disableLoginCaptcha(); - - const accountPage = new AccountPage(page); - - if(!process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL || !process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD) { - throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL or process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD is not defined in your .env file."); - } - await accountPage.create(values.accountCreation.firstNameValue, values.accountCreation.lastNameValue, process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL, process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD); -}); +import fs from 'fs'; +import path from 'path'; + +if(!process.env.SETUP_COMPLETE) { + base('Setup Magento environment for tests', async ({page}) => { + let magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; + let magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; + + if(!magentoAdminUsername || !magentoAdminPassword) { + throw new Error("MAGENTO_ADMIN_USERNAME or MAGENTO_ADMIN_PASSWORD is not defined in your .env file."); + } + + const magentoAdminPage = new MagentoAdminPage(page); + await magentoAdminPage.login(magentoAdminUsername, magentoAdminPassword); + + let magentoCouponCode = process.env.MAGENTO_COUPON_CODE; + if(!magentoCouponCode) { + throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); + } + + await magentoAdminPage.addCartPriceRule(magentoCouponCode); + await magentoAdminPage.disableLoginCaptcha(); + + const accountPage = new AccountPage(page); + + if(!process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL || !process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD) { + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL or process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD is not defined in your .env file."); + } + await accountPage.create(values.accountCreation.firstNameValue, values.accountCreation.lastNameValue, process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL, process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD); + + const envPath = path.resolve(__dirname, '../../.env'); + console.log(envPath) + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + if (!envContent.includes('SETUP_COMPLETE=true')) { + fs.appendFileSync(envPath, '\nSETUP_COMPLETE=true'); + console.log("Environment setup completed successfully. 'SETUP_COMPLETE=true' added to .env file."); + } + } else { + throw new Error('.env file not found. Please ensure it exists in the root directory.'); + } + }); +} From fbce1726fb8bdadaf417f32454c293d8a184e96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Tue, 31 Dec 2024 10:45:07 +0100 Subject: [PATCH 19/22] Fixed small regex bug --- tests/base/fixtures/magentoAdmin.page.ts | 2 +- tests/base/setup.spec.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/base/fixtures/magentoAdmin.page.ts b/tests/base/fixtures/magentoAdmin.page.ts index 0ca78cd..374a9b1 100644 --- a/tests/base/fixtures/magentoAdmin.page.ts +++ b/tests/base/fixtures/magentoAdmin.page.ts @@ -71,7 +71,7 @@ export class MagentoAdminPage { await this.page.getByRole('link', { name: selectors.configurationPage.customerConfigurationTabLabel }).click(); if (!await this.page.locator(selectors.configurationPage.captchaSettingSystemCheckbox).isVisible()) { - await this.page.getByRole('link', { name: /${selectors.configurationPage.captchaSectionLabel}/ }).click(); + await this.page.getByRole('link', { name: new RegExp(selectors.configurationPage.captchaSectionLabel) }).click(); } await this.page.locator(selectors.configurationPage.captchaSettingSystemCheckbox).uncheck(); diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts index 5e9a6fb..c524092 100644 --- a/tests/base/setup.spec.ts +++ b/tests/base/setup.spec.ts @@ -34,7 +34,6 @@ if(!process.env.SETUP_COMPLETE) { await accountPage.create(values.accountCreation.firstNameValue, values.accountCreation.lastNameValue, process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL, process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD); const envPath = path.resolve(__dirname, '../../.env'); - console.log(envPath) if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, 'utf-8'); if (!envContent.includes('SETUP_COMPLETE=true')) { From 1b68bfb2aecfa226269698df89246565e550c030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Tue, 31 Dec 2024 12:22:21 +0100 Subject: [PATCH 20/22] Added separation of browser engines over coupons and login functionality --- .env.example | 11 ++- tests/auth.setup.ts | 19 ++--- tests/base/account.spec.ts | 25 +++--- tests/base/cart.spec.ts | 27 ++++--- tests/base/checkout.spec.ts | 15 ++-- .../config/input-values/input-values.json | 3 + tests/base/config/selectors/selectors.json | 5 ++ tests/base/fixtures/magentoAdmin.page.ts | 27 +++++-- tests/base/login.spec.ts | 11 +-- tests/base/mainmenu.spec.ts | 9 ++- tests/base/setup.spec.ts | 78 +++++++++++++------ 11 files changed, 150 insertions(+), 80 deletions(-) diff --git a/.env.example b/.env.example index 3d537f2..fbcfe25 100644 --- a/.env.example +++ b/.env.example @@ -7,9 +7,14 @@ MAGENTO_ADMIN_USERNAME= MAGENTO_ADMIN_PASSWORD= MAGENTO_NEW_ACCOUNT_PASSWORD= -MAGENTO_EXISTING_ACCOUNT_EMAIL= +MAGENTO_EXISTING_ACCOUNT_EMAIL_CHROMIUM= +MAGENTO_EXISTING_ACCOUNT_EMAIL_FIREFOX= +MAGENTO_EXISTING_ACCOUNT_EMAIL_WEBKIT= MAGENTO_EXISTING_ACCOUNT_PASSWORD= MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD= -MAGENTO_COUPON_CODE= -CAPTCHA_BYPASS=false +MAGENTO_COUPON_CODE_CHROMIUM= +MAGENTO_COUPON_CODE_FIREFOX= +MAGENTO_COUPON_CODE_WEBKIT= + +CAPTCHA_BYPASS= diff --git a/tests/auth.setup.ts b/tests/auth.setup.ts index 55a4dc4..a5ebba9 100644 --- a/tests/auth.setup.ts +++ b/tests/auth.setup.ts @@ -1,23 +1,24 @@ import { test as setup, expect } from '@playwright/test'; import path from 'path'; -import slugs from './refactor/config/slugs.json'; -import selectors from './refactor/config/selectors/selectors.json'; +import slugs from './base/config/slugs.json'; +import selectors from './base/config/selectors/selectors.json'; const authFile = path.join(__dirname, '../playwright/.auth/user.json'); -setup('authenticate', async ({ page }) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; +setup('authenticate', async ({ page, browserName }) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } // Perform authentication steps. Replace these actions with your own. await page.goto(slugs.account.loginSlug); - await page.getByLabel(selectors.login.emailFieldLabel, {exact: true}).fill(emailInputValue); - await page.getByLabel(selectors.login.PasswordFieldLabel, {exact: true}).fill(passwordInputValue); - await page.getByRole('button', { name: selectors.login.loginButtonLabel }).click(); + await page.getByLabel(selectors.credentials.emailFieldLabel, {exact: true}).fill(emailInputValue); + await page.getByLabel(selectors.credentials.passwordFieldLabel, {exact: true}).fill(passwordInputValue); + await page.getByRole('button', { name: selectors.credentials.loginButtonLabel }).click(); // Wait until the page receives the cookies. // // Sometimes login flow sets cookies in the process of several redirects. @@ -29,4 +30,4 @@ setup('authenticate', async ({ page }) => { // End of authentication steps. await page.context().storageState({ path: authFile }); -}); \ No newline at end of file +}); diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index 9ad295f..33c15ff 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -13,12 +13,13 @@ import verify from './config/expected/expected.json'; // Before each test, log in -test.beforeEach(async ({ page }) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; +test.beforeEach(async ({ page, browserName }) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); @@ -26,7 +27,7 @@ test.beforeEach(async ({ page }) => { }); test.describe('Account information actions', {annotation: {type: 'Account Dashboard', description: 'Test for Account Information'},}, () => { - + test.beforeEach(async ({page}) => { await page.goto(slugs.account.accountOverviewSlug); }); @@ -85,7 +86,7 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash * @and I haven't added an address yet * @when I fill in the required information * @and I click the save button - * @then I should see a notification my address has been updated. + * @then I should see a notification my address has been updated. * @and The new address should be selected as default and shipping address */ @@ -111,7 +112,7 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash * @when I go to the page where I can add another address * @when I fill in the required information * @and I click the save button - * @then I should see a notification my address has been updated. + * @then I should see a notification my address has been updated. * @and The new address should be listed */ test('I can add another address',{ tag: '@address-actions', }, async ({page}) => { @@ -138,7 +139,7 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash * @when I click on the button to edit the address * @and I fill in the required information correctly * @then I click the save button - * @then I should see a notification my address has been updated. + * @then I should see a notification my address has been updated. * @and The updated address should be visible in the addres book page. */ test('I can edit an existing address',{ tag: '@address-actions', }, async ({page}) => { @@ -149,7 +150,7 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash let newZipCode = inputvalues.editedAddress.editZipCodeValue; let newCity = inputvalues.editedAddress.editCityValue; let newState = inputvalues.editedAddress.editStateValue; - + await page.goto(slugs.account.addressBookSlug); await accountPage.editExistingAddress(newFirstName, newLastName, newStreet, newZipCode, newCity, newState); @@ -163,7 +164,7 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash * @when I go to the page where I can see my address(es) * @when I click the trash button for the address I want to delete * @and I click the confirmation button - * @then I should see a notification my address has been deleted. + * @then I should see a notification my address has been deleted. * @and The address should be removed from the overview. */ test('I can delete an address',{ tag: '@address-actions', }, async ({page}) => { @@ -178,7 +179,7 @@ test.describe('Newsletter actions', { annotation: {type: 'Account Dashboard', de // go to the Dashboard page await page.goto(slugs.account.accountOverviewSlug); }); - + // TODO: What if website offers multiple subscriptions? /** @@ -205,9 +206,9 @@ test.describe('Newsletter actions', { annotation: {type: 'Account Dashboard', de await newsletterLink.click(); if(updateSubscription){ - await expect(newsletterCheckElement).toBeChecked(); + await expect(newsletterCheckElement).toBeChecked(); } else { - await expect(newsletterCheckElement).not.toBeChecked(); + await expect(newsletterCheckElement).not.toBeChecked(); } }); diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index d9b23b1..b53dc38 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -35,7 +35,7 @@ test.describe('Cart functionalities (guest)', () => { test('Product can be added to cart',{ tag: '@cart',}, async ({page}) => { await expect(page.getByRole('strong').getByRole('link', {name: selectors.productPage.simpleProductTitle}), `Product is visible in cart`).toBeVisible(); }); - + /** * @feature Product permanence after login * @scenario A product added to the cart should still be there after user has logged in @@ -43,7 +43,7 @@ test.describe('Cart functionalities (guest)', () => { * @when I log in * @then I should still have that product in my cart */ - test('Product should remain in cart after logging in',{ tag: ['@cart', '@account']}, async ({page}) => { + test('Product should remain in cart after logging in',{ tag: ['@cart', '@account']}, async ({page, browserName}) => { await test.step('Add another product to cart', async () =>{ const productpage = new ProductPage(page); await page.goto(slugs.productpage.secondSimpleProductSlug); @@ -51,12 +51,13 @@ test.describe('Cart functionalities (guest)', () => { }); await test.step('Log in with account', async () =>{ + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; const loginPage = new LoginPage(page); - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("Your password variable and/or your email variable have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } await loginPage.login(emailInputValue, passwordInputValue); @@ -93,12 +94,13 @@ test.describe('Cart functionalities (guest)', () => { * @and the code should be visible in the cart * @and a discount should be applied to the product */ - test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page}) => { + test('Add coupon code in cart',{ tag: ['@cart', '@coupon-code']}, async ({page, browserName}) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; const cart = new CartPage(page); - let discountCode = process.env.MAGENTO_COUPON_CODE; + let discountCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; if(!discountCode) { - throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE_${browserEngine} appears to not be set in .env file. Value reported: ${discountCode}`); } await cart.applyDiscountCode(discountCode); @@ -117,12 +119,13 @@ test.describe('Cart functionalities (guest)', () => { * @then I should see a notification the discount has been removed * @and the discount should no longer be visible. */ - test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { + test('Remove coupon code from cart',{ tag: ['@cart', '@coupon-code'] }, async ({page, browserName}) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; const cart = new CartPage(page); - let discountCode = process.env.MAGENTO_COUPON_CODE; + let discountCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; if(!discountCode) { - throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); + throw new Error(`MAGENTO_COUPON_CODE_${browserEngine} appears to not be set in .env file. Value reported: ${discountCode}`); } // TODO: create API call to quickly add discount code rather than run a test again. @@ -140,7 +143,7 @@ test.describe('Cart functionalities (guest)', () => { */ test('Using an invalid coupon code should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { - const cart = new CartPage(page); + const cart = new CartPage(page); await cart.enterWrongCouponCode("Incorrect Couon Code"); }); -}) \ No newline at end of file +}) diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index e10c910..9d7f96a 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -28,19 +28,20 @@ test.beforeEach(async ({ page }) => { //TODO: Use a storagestate or API call to add product to the cart so shorten test time await page.goto(slugs.productpage.simpleProductSlug); await productPage.addSimpleProductToCart(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); - await page.goto(slugs.checkoutSlug); + await page.goto(slugs.checkoutSlug); }); test.describe('Checkout (login required)', () => { // Before each test, log in // TODO: remove this beforeEach() once authentication as project set-up/fixture works. - test.beforeEach(async ({ page }) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; + test.beforeEach(async ({ page, browserName }) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); @@ -53,7 +54,7 @@ test.describe('Checkout (login required)', () => { await checkoutPage.placeOrder(); }); - + }); @@ -77,11 +78,11 @@ test.describe('Checkout (guest)', () => { //TODO: Write tests to ensure code also works if user is NOT logged in. const checkout = new CheckoutPage(page); let discountCode = process.env.MAGENTO_COUPON_CODE; - + if(!discountCode) { throw new Error(`MAGENTO_COUPON_CODE appears to not be set in .env file. Value reported: ${discountCode}`); } - + await checkout.applyDiscountCodeCheckout(discountCode); }); diff --git a/tests/base/config/input-values/input-values.json b/tests/base/config/input-values/input-values.json index 5b40bd6..d42d849 100644 --- a/tests/base/config/input-values/input-values.json +++ b/tests/base/config/input-values/input-values.json @@ -42,5 +42,8 @@ }, "captcha": { "captchaDisabled": "No" + }, + "adminLogins": { + "allowMultipleLogins": "Yes" } } diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 0c85a2e..da774af 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -120,6 +120,11 @@ "configurationPage": { "customersTabLabel": "Customers", "customerConfigurationTabLabel": "Customer Configuration", + "advancedTabLabel": "Advanced", + "advancedAdministrationTabLabel": "Admin", + "securitySectionLabel": "Security", + "allowMultipleLoginsSystemCheckbox": "#admin_security_admin_account_sharing_inherit", + "allowMultipleLoginsSelectField": "#admin_security_admin_account_sharing", "captchaSectionLabel": "CAPTCHA", "captchaSettingSelectField": "#customer_captcha_enable", "captchaSettingSystemCheckbox": "#customer_captcha_enable_inherit", diff --git a/tests/base/fixtures/magentoAdmin.page.ts b/tests/base/fixtures/magentoAdmin.page.ts index 374a9b1..54615fd 100644 --- a/tests/base/fixtures/magentoAdmin.page.ts +++ b/tests/base/fixtures/magentoAdmin.page.ts @@ -3,8 +3,6 @@ import {expect, type Locator, type Page} from '@playwright/test'; import selectors from '../config/selectors/selectors.json'; import values from '../config/input-values/input-values.json'; -import account from '../fixtures/account.page'; - export class MagentoAdminPage { readonly page: Page; readonly adminLoginEmailField: Locator; @@ -30,11 +28,12 @@ export class MagentoAdminPage { } async addCartPriceRule(magentoCouponCode: string){ - if(!process.env.MAGENTO_COUPON_CODE) { - throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); + if(!process.env.MAGENTO_COUPON_CODE_CHROMIUM || !process.env.MAGENTO_COUPON_CODE_FIREFOX || !process.env.MAGENTO_COUPON_CODE_WEBKIT) { + throw new Error("MAGENTO_COUPON_CODE_CHROMIUM, MAGENTO_COUPON_CODE_FIREFOX or MAGENTO_COUPON_CODE_WEBKIT is not defined in your .env file."); } - - await this.page.getByRole('link', {name: selectors.magentoAdminPage.navigation.marketingButtonLabel}).click(); + await this.page.waitForLoadState('networkidle'); + await this.page.getByRole('link', {name: selectors.magentoAdminPage.navigation.marketingButtonLabel}).click({ force: true }); + await this.page.getByRole('link', {name: selectors.magentoAdminPage.subNavigation.cartPriceRulesButtonLabel}).waitFor(); await this.page.getByRole('link', {name: selectors.magentoAdminPage.subNavigation.cartPriceRulesButtonLabel}).click(); await this.page.getByRole('button', {name: selectors.cartPriceRulesPage.addCartPriceRuleButtonLabel}).click(); await this.page.getByLabel(selectors.cartPriceRulesPage.ruleNameFieldLabel).fill(values.coupon.couponCodeRuleName); @@ -64,7 +63,23 @@ export class MagentoAdminPage { await this.page.getByRole('button', { name: 'Save', exact: true }).click(); } + async enableMultipleAdminLogins() { + await this.page.getByRole('link', { name: selectors.magentoAdminPage.navigation.storesButtonLabel }).click(); + await this.page.getByRole('link', { name: selectors.magentoAdminPage.subNavigation.configurationButtonLabel }).click(); + await this.page.getByRole('tab', { name: selectors.configurationPage.advancedTabLabel }).click(); + await this.page.getByRole('link', { name: selectors.configurationPage.advancedAdministrationTabLabel, exact: true }).click(); + + if (!await this.page.locator(selectors.configurationPage.allowMultipleLoginsSystemCheckbox).isVisible()) { + await this.page.getByRole('link', { name: selectors.configurationPage.securitySectionLabel }).click(); + } + + await this.page.locator(selectors.configurationPage.allowMultipleLoginsSystemCheckbox).uncheck(); + await this.page.locator(selectors.configurationPage.allowMultipleLoginsSelectField).selectOption({ label: values.adminLogins.allowMultipleLogins }); + await this.page.getByRole('button', { name: selectors.configurationPage.saveConfigButtonLabel }).click(); + } + async disableLoginCaptcha() { + await this.page.waitForLoadState('networkidle'); await this.page.getByRole('link', { name: selectors.magentoAdminPage.navigation.storesButtonLabel }).click(); await this.page.getByRole('link', { name: selectors.magentoAdminPage.subNavigation.configurationButtonLabel }).click(); await this.page.getByRole('tab', { name: selectors.configurationPage.customersTabLabel }).click(); diff --git a/tests/base/login.spec.ts b/tests/base/login.spec.ts index bc1bb07..15e9a1a 100644 --- a/tests/base/login.spec.ts +++ b/tests/base/login.spec.ts @@ -6,7 +6,7 @@ const test = base.extend<{ loginPage:LoginPage }>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await loginPage.login(); - } + } }); */ @@ -19,12 +19,13 @@ const test = base.extend<{ loginPage:LoginPage }>({ // Reset storageState to ensure we're not logged in before running these tests. base.use({ storageState: { cookies: [], origins: [] } }); -base('User can log in with valid credentials', async ({page}) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; +base('User can log in with valid credentials', async ({page, browserName}) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); @@ -36,4 +37,4 @@ base('User can log in with valid credentials', async ({page}) => { // These tests should have a specific "error checker" tag. -//TODO: Add test to test 'Forgot your Password' functionality \ No newline at end of file +//TODO: Add test to test 'Forgot your Password' functionality diff --git a/tests/base/mainmenu.spec.ts b/tests/base/mainmenu.spec.ts index 8e447ee..86b521c 100644 --- a/tests/base/mainmenu.spec.ts +++ b/tests/base/mainmenu.spec.ts @@ -6,12 +6,13 @@ import {MainMenuPage} from './fixtures/mainmenu.page'; // TODO: remove this beforeEach() once authentication as project set-up/fixture works. // Before each test, log in -test.beforeEach(async ({ page }) => { - let emailInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL; +test.beforeEach(async ({ page, browserName }) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; if(!emailInputValue || !passwordInputValue) { - throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); + throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} and/or MAGENTO_EXISTING_ACCOUNT_PASSWORD have not defined in the .env file, or the account hasn't been created yet."); } const loginPage = new LoginPage(page); @@ -36,4 +37,4 @@ test('User can log out', { tag: '@mainmenu', }, async ({page}) => { test('Navigate to account page', { tag: '@mainmenu', }, async ({page}) => { const mainMenu = new MainMenuPage(page); await mainMenu.gotoMyAccount(); -}); \ No newline at end of file +}); diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts index c524092..2ce8d63 100644 --- a/tests/base/setup.spec.ts +++ b/tests/base/setup.spec.ts @@ -1,47 +1,81 @@ -import {test as base} from '@playwright/test'; -import {MagentoAdminPage} from './fixtures/magentoAdmin.page'; -import {AccountPage} from './fixtures/account.page'; +import { test as base } from '@playwright/test'; +import { MagentoAdminPage } from './fixtures/magentoAdmin.page'; +import { AccountPage } from './fixtures/account.page'; import values from './config/input-values/input-values.json'; import fs from 'fs'; import path from 'path'; -if(!process.env.SETUP_COMPLETE) { - base('Setup Magento environment for tests', async ({page}) => { - let magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; - let magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; +if (!process.env.SETUP_COMPLETE) { + base('Enable multiple Magento admin logins', async ({ page, browserName }) => { + const magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; + const magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; - if(!magentoAdminUsername || !magentoAdminPassword) { + if (!magentoAdminUsername || !magentoAdminPassword) { throw new Error("MAGENTO_ADMIN_USERNAME or MAGENTO_ADMIN_PASSWORD is not defined in your .env file."); } const magentoAdminPage = new MagentoAdminPage(page); await magentoAdminPage.login(magentoAdminUsername, magentoAdminPassword); - let magentoCouponCode = process.env.MAGENTO_COUPON_CODE; - if(!magentoCouponCode) { - throw new Error("MAGENTO_COUPON_CODE is not defined in your .env file."); + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + + if (browserEngine === "CHROMIUM") { + await magentoAdminPage.enableMultipleAdminLogins(); } + }); - await magentoAdminPage.addCartPriceRule(magentoCouponCode); + base('Setup Magento environment for tests', async ({ page, browserName }) => { + const magentoAdminUsername = process.env.MAGENTO_ADMIN_USERNAME; + const magentoAdminPassword = process.env.MAGENTO_ADMIN_PASSWORD; + + if (!magentoAdminUsername || !magentoAdminPassword) { + throw new Error("MAGENTO_ADMIN_USERNAME or MAGENTO_ADMIN_PASSWORD is not defined in your .env file."); + } + + const magentoAdminPage = new MagentoAdminPage(page); + await magentoAdminPage.login(magentoAdminUsername, magentoAdminPassword); + + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + + const couponCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; + if (!couponCode) { + throw new Error(`MAGENTO_COUPON_CODE_${browserEngine} is not defined in your .env file.`); + } + await magentoAdminPage.addCartPriceRule(couponCode); await magentoAdminPage.disableLoginCaptcha(); const accountPage = new AccountPage(page); - if(!process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL || !process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD) { - throw new Error("MAGENTO_EXISTING_ACCOUNT_EMAIL or process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD is not defined in your .env file."); + const accountEmail = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; + const accountPassword = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; + + if (!accountEmail || !accountPassword) { + throw new Error( + `MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine} or MAGENTO_EXISTING_ACCOUNT_PASSWORD is not defined in your .env file.` + ); } - await accountPage.create(values.accountCreation.firstNameValue, values.accountCreation.lastNameValue, process.env.MAGENTO_EXISTING_ACCOUNT_EMAIL, process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD); + + await accountPage.create( + values.accountCreation.firstNameValue, + values.accountCreation.lastNameValue, + accountEmail, + accountPassword + ); const envPath = path.resolve(__dirname, '../../.env'); - if (fs.existsSync(envPath)) { - const envContent = fs.readFileSync(envPath, 'utf-8'); - if (!envContent.includes('SETUP_COMPLETE=true')) { - fs.appendFileSync(envPath, '\nSETUP_COMPLETE=true'); - console.log("Environment setup completed successfully. 'SETUP_COMPLETE=true' added to .env file."); + try { + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + if (!envContent.includes('SETUP_COMPLETE=true')) { + fs.appendFileSync(envPath, '\nSETUP_COMPLETE=true'); + console.log("Environment setup completed successfully. 'SETUP_COMPLETE=true' added to .env file."); + } + } else { + throw new Error('.env file not found. Please ensure it exists in the root directory.'); } - } else { - throw new Error('.env file not found. Please ensure it exists in the root directory.'); + } catch (error) { + throw new Error(`Failed to update .env file: ${error.message}`); } }); } From cad9c80bcfbf9ad22ebfaf5431f6237bc7477669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Tue, 31 Dec 2024 14:02:15 +0100 Subject: [PATCH 21/22] Added workflow --- .github/workflows/main.yml | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9201e5d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,69 @@ +name: Magento 2 BDD E2E Testing Suite Self Test + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + env: + BASE_URL: https://hyva-demo.elgentos.io/ + PRODUCTION_URL: https://hyva-demo.elgentos.io/ + STAGING_URL: https://hyva-demo.elgentos.io/ + + MAGENTO_ADMIN_SLUG: ${{ secrets.MAGENTO_ADMIN_SLUG }} + MAGENTO_ADMIN_USERNAME: ${{ secrets.MAGENTO_ADMIN_USERNAME }} + MAGENTO_ADMIN_PASSWORD: ${{ secrets.MAGENTO_ADMIN_PASSWORD }} + + MAGENTO_NEW_ACCOUNT_PASSWORD: ${{ secrets.MAGENTO_NEW_ACCOUNT_PASSWORD }} + MAGENTO_EXISTING_ACCOUNT_EMAIL_CHROMIUM: ${{ secrets.MAGENTO_EXISTING_ACCOUNT_EMAIL_CHROMIUM }} + MAGENTO_EXISTING_ACCOUNT_EMAIL_FIREFOX: ${{ secrets.MAGENTO_EXISTING_ACCOUNT_EMAIL_FIREFOX }} + MAGENTO_EXISTING_ACCOUNT_EMAIL_WEBKIT: ${{ secrets.MAGENTO_EXISTING_ACCOUNT_EMAIL_WEBKIT }} + MAGENTO_EXISTING_ACCOUNT_PASSWORD: ${{ secrets.MAGENTO_EXISTING_ACCOUNT_PASSWORD }} + MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD: ${{ secrets.MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD }} + + MAGENTO_COUPON_CODE_CHROMIUM: ${{ secrets.MAGENTO_COUPON_CODE_CHROMIUM }} + MAGENTO_COUPON_CODE_FIREFOX: ${{ secrets.MAGENTO_COUPON_CODE_FIREFOX }} + MAGENTO_COUPON_CODE_WEBKIT: ${{ secrets.MAGENTO_COUPON_CODE_WEBKIT }} + + CAPTCHA_BYPASS: true + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Install dependencies + run: npm install + + - name: Install Playwright browsers + run: npx playwright install + + - name: Install Playwright dependencies + run: npx playwright install-deps + + + - name: Copy config files + run: | + cp playwright.config.example.ts playwright.config.ts + cp bypass-captcha.config.example.ts bypass-captcha.config.ts + + - name: Run Playwright setup test + run: npx playwright test --reporter=line --workers=4 tests/base/setup.spec.ts + env: + CI: true + + - name: Run Playwright tests + run: npx playwright test --reporter=line --workers=4 --exclude=tests/base/setup.spec.ts + env: + CI: true From 88d3c76636441a2148a40b7898dd2a64ae2c472a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Tue, 31 Dec 2024 14:03:29 +0100 Subject: [PATCH 22/22] Added skip when in CI mode --- tests/base/setup.spec.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts index 2ce8d63..f32edab 100644 --- a/tests/base/setup.spec.ts +++ b/tests/base/setup.spec.ts @@ -63,6 +63,11 @@ if (!process.env.SETUP_COMPLETE) { accountPassword ); + if (process.env.CI === 'true') { + console.log("Running in CI environment. Skipping .env update."); + process.exit(0); + } + const envPath = path.resolve(__dirname, '../../.env'); try { if (fs.existsSync(envPath)) {