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/.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 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 c13d9cf..7985c20 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 946eb09..6efcf50 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -1,13 +1,14 @@ 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 { CartPage } from './fixtures/cart.page'; 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. @@ -25,19 +26,49 @@ test.describe('Cart functionalities', () => { //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('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 Remove product from cart + * @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, browserName}) => { + 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 browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + const loginPage = new LoginPage(page); + 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_${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); + }); + + 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(); + }); + + /** @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 @@ -50,7 +81,71 @@ test.describe('Cart functionalities', () => { await cart.removeProduct(selectors.productPage.simpleProductTitle); }); -}); + /** + * @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, browserName}) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + const cart = new CartPage(page); + let discountCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; + + if(!discountCode) { + throw new Error(`MAGENTO_COUPON_CODE_${browserEngine} 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, browserName}) => { + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + const cart = new CartPage(page); + let discountCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; + + if(!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. + 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 coupon code should give an error',{ tag: ['@cart', '@coupon-code'] }, async ({page}) => { + const cart = new CartPage(page); + await cart.enterWrongCouponCode("Incorrect Couon Code"); + }); +}) test.describe('Price checking tests', () => { diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index e5b1677..9d7f96a 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -10,49 +10,121 @@ 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(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); + 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.beforeEach(async ({ page }) => { - const productPage = new ProductPage(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 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, 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_${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); + 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. -}); \ No newline at end of file + /** + * @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.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); + }); + + /** + * @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.MAGENTO_COUPON_CODE; + + if(!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. + 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 coupon code should give an error',{ tag: ['@checkout', '@coupon-code'] }, async ({page}) => { + const checkout = new CheckoutPage(page); + await checkout.enterWrongCouponCode("incorrect discount code"); + }); +}); + diff --git a/tests/base/config/expected/expected.json b/tests/base/config/expected/expected.json index 133920e..7dc1301 100644 --- a/tests/base/config/expected/expected.json +++ b/tests/base/config/expected/expected.json @@ -17,7 +17,14 @@ "logoutConfirmationText": "You have signed out" } , "productPage": { - "simpleProductAddedNotification": "You added Push It Messenger" + "simpleProductAddedNotification": "You added" + }, + "cart": { + "discountAppliedNotification": "You used coupon code", + "discountRemovedNotification": "You canceled the coupon code.", + "priceReducedSymbols": "- $", + "incorrectCouponCodeNotificationOne": "The coupon code", + "incorrectCouponCodeNotificationTwo": "is not valid." }, "miniCart": { "miniCartTitle": "My Cart", @@ -26,6 +33,12 @@ "productRemovedConfirmation": "You removed the item.", "productQuantityChangedConfirmation": "was updated in your shopping cart" }, + "checkout": { + "couponAppliedNotification": "Your coupon was successfully applied", + "couponRemovedNotification": "Your coupon was successfully removed", + "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/config/input-values/input-values.json b/tests/base/config/input-values/input-values.json index 8cdae67..d42d849 100644 --- a/tests/base/config/input-values/input-values.json +++ b/tests/base/config/input-values/input-values.json @@ -35,5 +35,15 @@ "editZipCodeValue": "00151", "editCityValue": "Pallet Town", "editStateValue": "Kansas" + }, + "coupon": { + "couponCodeRuleName": "Test coupon", + "couponType": "Specific Coupon" + }, + "captcha": { + "captchaDisabled": "No" + }, + "adminLogins": { + "allowMultipleLogins": "Yes" } -} \ No newline at end of file +} diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 862a600..42b5cd5 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -54,6 +54,7 @@ }, "productPage": { "simpleProductTitle": "Push It Messenger Bag", + "secondSimpleProducTitle": "Aim Analog Watch", "simpleProductPrice": ".final-price .price-wrapper .price", "configurableProductTitle": "Inez Full Zip Jacket", "configurableProductSizeLabel": "Size", @@ -65,6 +66,20 @@ "cart": { "updateItemButtonLabel": "shopping-cart Update item", "cartQuantityLabel": "Qty", + "showDiscountFormButtonLabel": "Apply Discount Code", + "discountInputFieldLabel": "Enter discount code", + "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", "applyDiscountCodeLabel": "Apply Discount Code", "remove": "Remove" }, @@ -85,5 +100,42 @@ }, "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", + "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", + "saveConfigButtonLabel": "Save Config" } -} \ No newline at end of file +} diff --git a/tests/base/config/slugs.json b/tests/base/config/slugs.json index e0977d1..20673df 100644 --- a/tests/base/config/slugs.json +++ b/tests/base/config/slugs.json @@ -9,6 +9,7 @@ }, "productpage": { "simpleProductSlug": "/push-it-messenger-bag.html", + "secondSimpleProductSlug": "/aim-analog-watch.html", "configurableProductSlug": "/inez-full-zip-jacket.html" }, "checkoutSlug": "/checkout/", 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/cart.page.ts b/tests/base/fixtures/cart.page.ts index b0607e4..be4de33 100644 --- a/tests/base/fixtures/cart.page.ts +++ b/tests/base/fixtures/cart.page.ts @@ -1,9 +1,11 @@ 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 showDiscountButton: Locator; readonly applyDiscountButton: Locator; productPagePrice: string; productPageAmount: string; @@ -12,11 +14,55 @@ export class CartPage { constructor(page: Page) { this.page = page; - this.applyDiscountButton = this.page.getByRole('button', { name: selectors.cart.applyDiscountCodeLabel }); + 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 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. + 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(); } 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(); } diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index 54acfe8..bc439dd 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -1,20 +1,25 @@ 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; constructor(page: Page){ this.page = page; - this.shippingMethodOptionFixed = this.page.getByLabel('Fixed'); - this.paymentMethodOptionCheck = this.page.getByLabel('Check / Money order Free'); - 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(){ @@ -51,4 +56,53 @@ 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(); + } + + if(await this.page.getByText(verify.cart.priceReducedSymbols).isVisible()){ + // discount is already active. + let cancelCouponButton = this.page.getByRole('button', { name: selectors.checkout.cancelDiscountButtonLabel }); + await cancelCouponButton.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.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 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. + 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 diff --git a/tests/base/fixtures/magentoAdmin.page.ts b/tests/base/fixtures/magentoAdmin.page.ts new file mode 100644 index 0000000..54615fd --- /dev/null +++ b/tests/base/fixtures/magentoAdmin.page.ts @@ -0,0 +1,96 @@ +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'; + +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_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.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); + + 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 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(); + 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: new RegExp(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/fixtures/product.page.ts b/tests/base/fixtures/product.page.ts index 7703fdb..753bb66 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(); diff --git a/tests/base/login.spec.ts b/tests/base/login.spec.ts index e2842af..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("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); @@ -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 067708c..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("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); @@ -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/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 diff --git a/tests/base/setup.spec.ts b/tests/base/setup.spec.ts new file mode 100644 index 0000000..f32edab --- /dev/null +++ b/tests/base/setup.spec.ts @@ -0,0 +1,86 @@ +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('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) { + 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"; + + if (browserEngine === "CHROMIUM") { + await magentoAdminPage.enableMultipleAdminLogins(); + } + }); + + 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); + + 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, + accountEmail, + 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)) { + 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.'); + } + } catch (error) { + throw new Error(`Failed to update .env file: ${error.message}`); + } + }); +}