From 1d5ed0816ae1d43eff62b21bc256fa4889557047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Thu, 16 Jan 2025 14:48:28 +0100 Subject: [PATCH 1/8] Updated flaky tests to always run --- tests/base/account.spec.ts | 37 +++++++++++++++-------------- tests/base/fixtures/account.page.ts | 22 +++++++++++++++++ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index a1ecf4b..7eca410 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -11,6 +11,8 @@ import verify from './config/expected/expected.json'; // no resetting storageState, mainmenu has more functionalities when logged in. // TODO: remove this beforeEach() once authentication as project set-up/fixture works. +let testLock = false; + // Before each test, log in test.beforeEach(async ({ page, browserName }) => { const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; @@ -67,12 +69,8 @@ test.describe('Account information actions', {annotation: {type: 'Account Dashbo }); }); - -// TODO: Add tests to check address can't be added/updated if the supplied information is incorrect -// TODO: Add tests to check address can't be deleted if it's the last/only one. -test.describe('Account address book actions', { annotation: {type: 'Account Dashboard', description: 'Tests for the Address Book'},}, () => { +test.describe.serial('Account address book actions', { annotation: {type: 'Account Dashboard', description: 'Tests for the Address Book'},}, () => { test.beforeEach(async ({page}) => { - // go to the Adress Book page await page.goto(slugs.account.addressBookSlug); await page.waitForLoadState(); }); @@ -91,12 +89,16 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash */ test('I can add my first address',{ tag: '@address-actions', }, async ({page}, testInfo) => { - // If account has no address, Address Book redirects to the 'Add New Address' page. - // We expect this to be true before continuing. - let addNewAddressTitle = page.getByRole('heading', {level: 1, name: selectors.newAddress.addNewAddressTitle}); - testInfo.skip(await addNewAddressTitle.isHidden(), `Heading "Add New Addres" is not found, please check if an address has already been added.`); const accountPage = new AccountPage(page); + let addNewAddressTitle = page.getByRole('heading', {level: 1, name: selectors.newAddress.addNewAddressTitle}); + if(await addNewAddressTitle.isHidden()) { + testLock = true; + await accountPage.deleteAllAddresses(); + await page.goto(slugs.account.addressNewSlug); + testLock = false; + } + let phoneNumberValue = inputvalues.firstAddress.firstPhoneNumberValue; let addressValue = inputvalues.firstAddress.firstStreetAddressValue; let zipCodeValue = inputvalues.firstAddress.firstZipCodeValue; @@ -104,7 +106,6 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash let stateValue = inputvalues.firstAddress.firstProvinceValue; await accountPage.addNewAddress(phoneNumberValue, addressValue, zipCodeValue, cityNameValue, stateValue); - }); /** @@ -128,7 +129,6 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash let stateValue = inputvalues.secondAddress.secondProvinceValue; await accountPage.addNewAddress(phoneNumberValue, addressValue, zipCodeValue, cityNameValue, stateValue); - }); /** @@ -153,11 +153,12 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash let newState = inputvalues.editedAddress.editStateValue; let editAddressButton = page.getByRole('link', {name: selectors.accountDashboard.editAddressIconButton}).first(); - testInfo.skip(await editAddressButton.isHidden(), `Button to edit Address is not found, please check if an address has been added.`); + if(await editAddressButton.isHidden()) { + await page.goto(slugs.account.addressNewSlug); + await accountPage.addNewAddress(inputvalues.firstAddress.firstPhoneNumberValue, inputvalues.firstAddress.firstStreetAddressValue, inputvalues.firstAddress.firstZipCodeValue, inputvalues.firstAddress.firstCityValue, inputvalues.firstAddress.firstProvinceValue); + } - await page.goto(slugs.account.addressBookSlug); await accountPage.editExistingAddress(newFirstName, newLastName, newStreet, newZipCode, newCity, newState); - }); /** @@ -175,8 +176,11 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash const accountPage = new AccountPage(page); let deleteAddressButton = page.getByRole('link', {name: selectors.accountDashboard.addressDeleteIconButton}).first(); - testInfo.skip(await deleteAddressButton.isHidden(), `Button to delete Address is not found, please check if an address has been added.`); + if(await deleteAddressButton.isHidden()) { + await page.goto(slugs.account.addressNewSlug); + await accountPage.addNewAddress(inputvalues.firstAddress.firstPhoneNumberValue, inputvalues.firstAddress.firstStreetAddressValue, inputvalues.firstAddress.firstZipCodeValue, inputvalues.firstAddress.firstCityValue, inputvalues.firstAddress.firstProvinceValue); + } await accountPage.deleteFirstAddressFromAddressBook(); }); }); @@ -184,7 +188,6 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash // TODO: move this to new spec file. test.describe('Newsletter actions', { annotation: {type: 'Account Dashboard', description: 'Newsletter tests'},}, () => { test.beforeEach(async ({page}) => { - // go to the Dashboard page await page.goto(slugs.account.accountOverviewSlug); }); @@ -220,6 +223,4 @@ test.describe('Newsletter actions', { annotation: {type: 'Account Dashboard', de await expect(newsletterCheckElement).not.toBeChecked(); } }); - }); - diff --git a/tests/base/fixtures/account.page.ts b/tests/base/fixtures/account.page.ts index 69aabd9..a1535bc 100644 --- a/tests/base/fixtures/account.page.ts +++ b/tests/base/fixtures/account.page.ts @@ -170,4 +170,26 @@ export class AccountPage { await this.accountCreationPasswordRepeatField.fill(password); await this.accountCreationConfirmButton.click(); } + + async deleteAllAddresses() { + let addressDeletedNotification = verify.address.addressDeletedNotification; + + // Handle the confirmation dialog for deleting addresses + this.page.on('dialog', async (dialog) => { + if (dialog.type() === 'confirm') { + await dialog.accept(); + } + }); + + // Continuously attempt to delete addresses until none are left + while (await this.deleteAddressButton.isVisible()) { + await this.deleteAddressButton.click(); + await this.page.waitForLoadState(); + + // Verify the notification for address deletion + await expect(this.page.getByText(addressDeletedNotification)).toBeVisible(); + } + + console.log("All addresses have been deleted."); + } } From 50ed5ea10ca812c6fb0ea7b53cdb960011be49ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Thu, 16 Jan 2025 14:51:54 +0100 Subject: [PATCH 2/8] Removed unnecessary change --- tests/base/account.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index 7eca410..e94ebaa 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -11,8 +11,6 @@ import verify from './config/expected/expected.json'; // no resetting storageState, mainmenu has more functionalities when logged in. // TODO: remove this beforeEach() once authentication as project set-up/fixture works. -let testLock = false; - // Before each test, log in test.beforeEach(async ({ page, browserName }) => { const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; From 86413064620ec27ccbc85eb7fdfddb532460339d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Thu, 16 Jan 2025 15:08:02 +0100 Subject: [PATCH 3/8] Removed unused testLock var --- tests/base/account.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index e94ebaa..4efca87 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -91,10 +91,8 @@ test.describe.serial('Account address book actions', { annotation: {type: 'Accou let addNewAddressTitle = page.getByRole('heading', {level: 1, name: selectors.newAddress.addNewAddressTitle}); if(await addNewAddressTitle.isHidden()) { - testLock = true; await accountPage.deleteAllAddresses(); await page.goto(slugs.account.addressNewSlug); - testLock = false; } let phoneNumberValue = inputvalues.firstAddress.firstPhoneNumberValue; From f42a725adbb43ad5b90fdebf4ccfd930841b10dd Mon Sep 17 00:00:00 2001 From: Shay Date: Mon, 20 Jan 2025 12:12:47 +0100 Subject: [PATCH 4/8] Removed TODO comments (tickets created), remove empty tests, and add Gherkin description to a test --- bypass-captcha.config.example.ts | 1 - playwright.config.example.ts | 2 -- tests/base/account.spec.ts | 12 ---------- tests/base/cart.spec.ts | 2 -- tests/base/checkout.spec.ts | 24 +++++++++---------- tests/base/config/selectors/selectors.json | 5 +++- tests/base/fixtures/account.page.ts | 28 ++++------------------ tests/base/fixtures/checkout.page.ts | 3 --- tests/base/fixtures/login.page.ts | 4 ---- tests/base/fixtures/mainmenu.page.ts | 2 +- tests/base/fixtures/newsletter.page.ts | 2 -- tests/base/fixtures/product.page.ts | 1 - tests/base/home.spec.ts | 1 - tests/base/login.spec.ts | 25 ------------------- tests/base/mainmenu.spec.ts | 1 - tests/base/minicart.spec.ts | 2 -- tests/base/register.spec.ts | 23 ------------------ 17 files changed, 21 insertions(+), 117 deletions(-) diff --git a/bypass-captcha.config.example.ts b/bypass-captcha.config.example.ts index f9b0545..699cdeb 100644 --- a/bypass-captcha.config.example.ts +++ b/bypass-captcha.config.example.ts @@ -3,7 +3,6 @@ * It will set the global cookie to bypass CAPTCHA for Magento 2. * See: https://github.com/elgentos/magento2-bypass-captcha-cookie * - * //TODO The extension is WIP. Currently being developed. */ import { FullConfig } from '@playwright/test'; import * as playwright from 'playwright'; diff --git a/playwright.config.example.ts b/playwright.config.example.ts index 880e5e3..7760585 100644 --- a/playwright.config.example.ts +++ b/playwright.config.example.ts @@ -64,12 +64,10 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ - // TODO: uncomment the setup line once authentication works! // Import our auth.setup.ts file //{ name: 'setup', testMatch: /.*\.setup\.ts/ }, { - // TODO: uncomment dependency and storage state once authentication works! name: 'chromium', testMatch: testFiles, use: { diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index a1ecf4b..ea5d14d 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -8,9 +8,6 @@ import inputvalues from './config/input-values/input-values.json'; import selectors from './config/selectors/selectors.json'; import verify from './config/expected/expected.json'; -// 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 test.beforeEach(async ({ page, browserName }) => { const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; @@ -32,8 +29,6 @@ test.describe('Account information actions', {annotation: {type: 'Account Dashbo await page.waitForLoadState(); }); - // TODO: add test to update e-mail address - /** * @feature Magento 2 Change Password * @scenario User changes their password @@ -46,8 +41,6 @@ test.describe('Account information actions', {annotation: {type: 'Account Dashbo * @then I should see a notification that my password has been updated * @and I should be able to login with my new credentials. */ - - //TODO: Remove the skip when all issues are fixed. test.skip('I can change my password',{ tag: '@account-credentials', }, async ({page}) => { const accountPage = new AccountPage(page); let changedPasswordValue = process.env.MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD; @@ -68,8 +61,6 @@ test.describe('Account information actions', {annotation: {type: 'Account Dashbo }); -// TODO: Add tests to check address can't be added/updated if the supplied information is incorrect -// TODO: Add tests to check address can't be deleted if it's the last/only one. test.describe('Account address book actions', { annotation: {type: 'Account Dashboard', description: 'Tests for the Address Book'},}, () => { test.beforeEach(async ({page}) => { // go to the Adress Book page @@ -181,15 +172,12 @@ test.describe('Account address book actions', { annotation: {type: 'Account Dash }); }); -// TODO: move this to new spec file. test.describe('Newsletter actions', { annotation: {type: 'Account Dashboard', description: 'Newsletter tests'},}, () => { test.beforeEach(async ({page}) => { // go to the Dashboard page await page.goto(slugs.account.accountOverviewSlug); }); - // TODO: What if website offers multiple subscriptions? - /** * @feature Magento 2 newsletter subscriptions * @scenario User (un)subscribes from a newsletter diff --git a/tests/base/cart.spec.ts b/tests/base/cart.spec.ts index 5022ca2..84f5163 100644 --- a/tests/base/cart.spec.ts +++ b/tests/base/cart.spec.ts @@ -24,7 +24,6 @@ test.describe('Cart functionalities (guest)', () => { const mainMenu = new MainMenuPage(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(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); await mainMenu.openMiniCart(); @@ -127,7 +126,6 @@ test.describe('Cart functionalities (guest)', () => { 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(); }); diff --git a/tests/base/checkout.spec.ts b/tests/base/checkout.spec.ts index 3589312..f53d661 100644 --- a/tests/base/checkout.spec.ts +++ b/tests/base/checkout.spec.ts @@ -9,8 +9,6 @@ import selectors from './config/selectors/selectors.json'; import verify from './config/expected/expected.json'; import { CheckoutPage } from './fixtures/checkout.page'; -// no resetting storageState, mainmenu has more functionalities when logged in. - /** * @feature BeforeEach runs before each test in this group. * @scenario Add product to the cart, confirm it's there, then move to checkout. @@ -25,7 +23,6 @@ import { CheckoutPage } from './fixtures/checkout.page'; 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(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); await page.goto(slugs.checkoutSlug); @@ -34,7 +31,6 @@ test.beforeEach(async ({ page }) => { 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}`]; @@ -48,20 +44,26 @@ test.describe('Checkout (login required)', () => { await loginPage.login(emailInputValue, passwordInputValue); }); - //TODO: Add Gherkin feature description + + /** + * @feature Place order for simple product + * @scenario User places an order for a simple product + * @given I have a product in my cart + * @and I am on any page + * @when I navigate to the checkout + * @and I fill in the required fields + * @and I click the button to place my order + * @then I should see a confirmation that my order has been placed + * @and a order number should be created and show to me + */ 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 @@ -75,7 +77,6 @@ test.describe('Checkout (guest)', () => { * @and a discount should be applied to the product */ test('Add coupon code in checkout',{ tag: ['@checkout', '@coupon-code']}, async ({page, browserName}) => { - //TODO: Write tests to ensure code also works if user is NOT logged in. const checkout = new CheckoutPage(page); const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; let discountCode = process.env[`MAGENTO_COUPON_CODE_${browserEngine}`]; @@ -110,7 +111,6 @@ test.describe('Checkout (guest)', () => { 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(); }); diff --git a/tests/base/config/selectors/selectors.json b/tests/base/config/selectors/selectors.json index 04b3a8a..b3a684e 100644 --- a/tests/base/config/selectors/selectors.json +++ b/tests/base/config/selectors/selectors.json @@ -16,8 +16,11 @@ "nameFieldLabel": "Name", "emailFieldLabel": "Email", "passwordFieldLabel": "Password", + "newPasswordFieldLabel": "New Password", "passwordConfirmFieldLabel": "Confirm Password", - "loginButtonLabel": "Sign In" + "newPasswordConfirmFieldLabel": "Confirm New Password", + "loginButtonLabel": "Sign In", + "currentPasswordFieldLabel": "Current Password" }, "personalInformation": { "firstNameLabel": "First Name", diff --git a/tests/base/fixtures/account.page.ts b/tests/base/fixtures/account.page.ts index 69aabd9..9a16e8e 100644 --- a/tests/base/fixtures/account.page.ts +++ b/tests/base/fixtures/account.page.ts @@ -31,14 +31,6 @@ export class AccountPage { readonly accountCreationPasswordRepeatField: Locator; readonly accountCreationConfirmButton: Locator; - // fields below are not required when adding an address. - /* - readonly companyField: Locator; - readonly streetAddressFieldTwo: Locator; - readonly streetAddressFieldThree: Locator; - */ - - // TODO: Update these functionalities to be able to take in non-required fields. constructor(page: Page){ this.page = page; @@ -49,20 +41,15 @@ export class AccountPage { this.streetAddressField = page.getByLabel(selectors.newAddress.streetAddressLabel, {exact:true}); this.zipCodeField = page.getByLabel(selectors.newAddress.zipCodeLabel); this.cityField = page.getByLabel(selectors.newAddress.cityNameLabel); - //TODO: countrySelect is used to change the country so it's not US. - //TODO: provinceSelect should be provinceField if country is, for example, Netherlands. this.countrySelectorField = page.getByLabel(selectors.newAddress.countryLabel); this.stateSelectorField = page.getByLabel(selectors.newAddress.provinceSelectLabel).filter({hasText: selectors.newAddress.provinceSelectFilterLabel}); this.saveAddressButton = page.getByRole('button',{name: selectors.newAddress.saveAdressButton}); - //unrequired fields - // this.companyField = page.getByLabel(selectors.newAddress.companyNameLabel); // Account Information elements this.changePasswordCheck = page.getByRole('checkbox', {name: selectors.personalInformation.changePasswordCheckLabel}); - //TODO: Fix these once I can log in again - this.currentPasswordField = page.getByLabel('Current Password'); - this.newPasswordField = page.getByLabel('New Password', {exact:true}); - this.confirmNewPasswordField = page.getByLabel('Confirm New Password') + this.currentPasswordField = page.getByLabel(selectors.credentials.currentPasswordFieldLabel); + this.newPasswordField = page.getByLabel(selectors.credentials.newPasswordFieldLabel, {exact:true}); + this.confirmNewPasswordField = page.getByLabel(selectors.credentials.newPasswordConfirmFieldLabel); this.genericSaveButton = page.getByRole('button', { name: selectors.general.genericSaveButtonLabel }); // Account Creation elements @@ -79,11 +66,9 @@ export class AccountPage { this.editAddressButton = page.getByRole('link', {name: selectors.accountDashboard.editAddressIconButton}).first(); } - //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(); await expect(this.lastNameField).not.toBeEmpty(); @@ -104,7 +89,6 @@ export class AccountPage { // the notification for a modified address is the same as the notification for a new address. let addressModifiedNotification = verify.address.newAddressAddedNotifcation; - // .click() replaced by .press("Enter") as a workaround for webkit issues await this.editAddressButton.click(); // Name should be filled in automatically, but editable. @@ -117,7 +101,7 @@ export class AccountPage { await this.zipCodeField.fill(zipCode); await this.cityField.fill(cityName); await this.stateSelectorField.selectOption(state); - // .click() replaced by .press("Enter") as a workaround for webkit issues + await this.saveAddressButton.click(); await this.page.waitForLoadState(); @@ -126,8 +110,6 @@ export class AccountPage { } - // TODO: Update function to remove random address from address book? - // deleteAddressButton is currently the first instance it finds. async deleteFirstAddressFromAddressBook(){ let addressDeletedNotification = verify.address.addressDeletedNotification; // Dialog function to click confirm @@ -137,7 +119,6 @@ export class AccountPage { } }); - // .click() replaced by .press("Enter") as a workaround for webkit issues await this.deleteAddressButton.click(); await this.page.waitForLoadState(); await expect(this.page.getByText(addressDeletedNotification)).toBeVisible(); @@ -151,7 +132,6 @@ export class AccountPage { await this.newPasswordField.fill(newPassword); await this.confirmNewPasswordField.fill(newPassword); - // .click() replaced by .press("Enter") as a workaround for webkit issues await this.genericSaveButton.click(); await this.page.waitForLoadState(); diff --git a/tests/base/fixtures/checkout.page.ts b/tests/base/fixtures/checkout.page.ts index ce1cab1..bafebb8 100644 --- a/tests/base/fixtures/checkout.page.ts +++ b/tests/base/fixtures/checkout.page.ts @@ -6,7 +6,6 @@ import slugs from '../config/slugs.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; @@ -53,8 +52,6 @@ export class CheckoutPage { let orderNumber = await this.page.locator('p').filter({ hasText: 'Your order number is:' }).getByRole('link').innerText(); console.log(`Your ordernumer is: ${orderNumber}`); - // This await only exists to report order number to the HTML reporter. - // TODO: replace this with a proper way to write something to the HTML reporter. await expect(this.continueShoppingButton, `Your order number is: ${orderNumber}`).toBeVisible(); return orderNumber; } diff --git a/tests/base/fixtures/login.page.ts b/tests/base/fixtures/login.page.ts index d1bdcf2..0f0ffe2 100644 --- a/tests/base/fixtures/login.page.ts +++ b/tests/base/fixtures/login.page.ts @@ -17,8 +17,6 @@ export class LoginPage { this.loginButton = page.getByRole('button', { name: selectors.credentials.loginButtonLabel }); } - // Note: this login function is simply written to quickly log in for tests which require you to be logged in. - // TODO: this login function should be moved to an auth file. see login.spec.ts line 15 and 16 for more info. async login(email: string, password: string){ await this.page.goto(slugs.account.loginSlug); await this.loginEmailField.fill(email); @@ -26,8 +24,6 @@ export class LoginPage { // usage of .press("Enter") to prevent webkit issues with button.click(); await this.loginButton.press("Enter"); - // this element cannot be defined in the constructor, since the sign out button only appears after logging in. - // Using a selector rather than expected, since it's we're locating a button rather than an expected notification. await expect(this.page.getByRole('link', { name: selectors.mainMenu.myAccountLogoutItem })).toBeVisible(); } } \ No newline at end of file diff --git a/tests/base/fixtures/mainmenu.page.ts b/tests/base/fixtures/mainmenu.page.ts index 85b0f74..475e005 100644 --- a/tests/base/fixtures/mainmenu.page.ts +++ b/tests/base/fixtures/mainmenu.page.ts @@ -29,7 +29,7 @@ export class MainMenuPage { } async gotoAddressBook() { - // TODO: create function to navigate to Address Book through the header menu links + // create function to navigate to Address Book through the header menu links } async openMiniCart() { diff --git a/tests/base/fixtures/newsletter.page.ts b/tests/base/fixtures/newsletter.page.ts index bcf8d04..55d310b 100644 --- a/tests/base/fixtures/newsletter.page.ts +++ b/tests/base/fixtures/newsletter.page.ts @@ -13,8 +13,6 @@ export class NewsletterSubscriptionPage { this.saveSubscriptionsButton = page.getByRole('button', {name:selectors.newsletterSubscriptions.saveSubscriptionsButton}); } - //TODO: the text of the notification can differ sometimes. Update this function to take this into account. - // newsletterRemovedNotification should sometimes be newsletterUpdatedNotification. async updateNewsletterSubscription(){ if(await this.newsletterCheckElement.isChecked()) { diff --git a/tests/base/fixtures/product.page.ts b/tests/base/fixtures/product.page.ts index 8d5e108..bf16337 100644 --- a/tests/base/fixtures/product.page.ts +++ b/tests/base/fixtures/product.page.ts @@ -42,6 +42,5 @@ export class ProductPage { await this.simpleProductAddToCartButon.click(); await this.page.waitForLoadState(); - //TODO: add notification check to ensure product has been added. } } \ No newline at end of file diff --git a/tests/base/home.spec.ts b/tests/base/home.spec.ts index e0ec114..a256146 100644 --- a/tests/base/home.spec.ts +++ b/tests/base/home.spec.ts @@ -8,7 +8,6 @@ test('Add product on homepage to cart',{ tag: '@homepage',}, async ({page}) => { const homepage = new HomePage(page); const mainmenu = new MainMenuPage(page); - // TODO: rework adding product to cart into API call function to reduce test time await page.goto(''); await homepage.addHomepageProductToCart(); await mainmenu.openMiniCart(); diff --git a/tests/base/login.spec.ts b/tests/base/login.spec.ts index 15e9a1a..e990163 100644 --- a/tests/base/login.spec.ts +++ b/tests/base/login.spec.ts @@ -1,24 +1,6 @@ import {test as base} from '@playwright/test'; import {LoginPage} from './fixtures/login.page'; -/* -const test = base.extend<{ loginPage:LoginPage }>({ - loginPage: async ({ page }, use) => { - const loginPage = new LoginPage(page); - await loginPage.login(); - } -}); -*/ - -//TODO: create an auth.setup.ts file to handle authentication (see login function on login.page.ts) -//TODO: link that setup file in project dependencies to be logged for all tests. -// See: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests - -//TODO: login.spec.ts can be removed when storageState functionality has been implemented. Relevant functions should be moved to mainmenu, footer, search, or other page spec files. - -// 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, browserName}) => { const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; @@ -31,10 +13,3 @@ base('User can log in with valid credentials', async ({page, browserName}) => { const loginPage = new LoginPage(page); await loginPage.login(emailInputValue, passwordInputValue); }); - -//TODO: Add test to ensure user cannot log in with invalid credentials. -// Basically, we log in with wrong credentials, then expect a failure message. -// These tests should have a specific "error checker" tag. - - -//TODO: Add test to test 'Forgot your Password' functionality diff --git a/tests/base/mainmenu.spec.ts b/tests/base/mainmenu.spec.ts index 86b521c..c2e4e1a 100644 --- a/tests/base/mainmenu.spec.ts +++ b/tests/base/mainmenu.spec.ts @@ -4,7 +4,6 @@ import {MainMenuPage} from './fixtures/mainmenu.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 test.beforeEach(async ({ page, browserName }) => { const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; diff --git a/tests/base/minicart.spec.ts b/tests/base/minicart.spec.ts index 42066b0..8309d45 100644 --- a/tests/base/minicart.spec.ts +++ b/tests/base/minicart.spec.ts @@ -25,7 +25,6 @@ test.describe('Minicart Actions', {annotation: {type: 'Minicart', description: ' const mainMenu = new MainMenuPage(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(selectors.productPage.simpleProductTitle, slugs.productpage.simpleProductSlug); await mainMenu.openMiniCart(); @@ -115,7 +114,6 @@ test.describe('Minicart Actions', {annotation: {type: 'Minicart', description: ' const mainMenu = new MainMenuPage(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.configurableProductSlug); await productPage.addConfigurableProductToCart(); await mainMenu.openMiniCart(); diff --git a/tests/base/register.spec.ts b/tests/base/register.spec.ts index 23e1021..9b643b5 100644 --- a/tests/base/register.spec.ts +++ b/tests/base/register.spec.ts @@ -16,29 +16,13 @@ test.use({ storageState: { cookies: [], origins: [] } }); * @then I should see a messsage confirming my account was created */ test('User can register an account', { tag: '@setup', }, async ({page, browserName}) => { -// TODO: remove the 'skip' when done. We don't always want to create accounts. const registerPage = new RegisterPage(page); // Retrieve desired password from .env file const existingAccountPassword = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; var firstName = inputvalues.accountCreation.firstNameValue; var lastName = inputvalues.accountCreation.lastNameValue; - - - // TODO: Once setup script works, uncomment this section (uniqueEmailSection) and remove the one below - //Create unique email with custom handle and host, adding a number between 0 - 100 - /* let randomNumber = Math.floor(Math.random() * 100); - let emailHandle = inputvalues.accountCreation.emailHandleValue; - let emailHost = inputvalues.accountCreation.emailHostValue; - const uniqueEmail = `${emailHandle}${randomNumber}@${emailHost}`; - - if(!existingAccountPassword){ - throw new Error("MAGENTO_EXISTING_ACCOUNT_PASSWORD has not been defined in the .env file."); - } */ - // end of uniqueEmailSection - - //TODO: Once setup script works, remove this section (browserNameEmailSection) and use uniqueEmail from above const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; let randomNumber = Math.floor(Math.random() * 100); let emailHandle = inputvalues.accountCreation.emailHandleValue; @@ -57,10 +41,3 @@ test('User can register an account', { tag: '@setup', }, async ({page, browserNa // password is retrieved from .env file in createNewAccount() function await registerPage.createNewAccount(firstName, lastName, accountEmail, existingAccountPassword); }); - - -test.skip('Account creation fails if required fields are not filled in', { tag: '@error-checker', }, async ({page}) => { - // TODO: registration should not work if mistakes are made, and proper messages should be displayed. - // These tests should have a specific "error checker" tag. - test.fixme(true,'Skipped, test will be created later'); -}); From 0c1b8c6acce3799a6268a93f9201d560885a9ab4 Mon Sep 17 00:00:00 2001 From: Shay Date: Tue, 21 Jan 2025 11:28:31 +0100 Subject: [PATCH 5/8] Reworked password change test to not interfere with other tests --- tests/base/account.spec.ts | 39 +++++++++++++++++++++++++++++------- tests/base/config/slugs.json | 3 ++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index a1ecf4b..ffeada7 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -1,5 +1,7 @@ import {test, expect} from '@playwright/test'; +import {MainMenuPage} from './fixtures/mainmenu.page'; import {LoginPage} from './fixtures/login.page'; +import {RegisterPage} from './fixtures/register.page'; import {AccountPage} from './fixtures/account.page'; import {NewsletterSubscriptionPage} from './fixtures/newsletter.page'; @@ -46,24 +48,47 @@ test.describe('Account information actions', {annotation: {type: 'Account Dashbo * @then I should see a notification that my password has been updated * @and I should be able to login with my new credentials. */ + test('I can change my password',{ tag: '@account-credentials', }, async ({page, browserName}) => { - //TODO: Remove the skip when all issues are fixed. - test.skip('I can change my password',{ tag: '@account-credentials', }, async ({page}) => { + // Create instances and set variables + const mainMenu = new MainMenuPage(page); + const registerPage = new RegisterPage(page); const accountPage = new AccountPage(page); - let changedPasswordValue = process.env.MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD; + const loginPage = new LoginPage(page); + + const browserEngine = browserName?.toUpperCase() || "UNKNOWN"; + let randomNumberforEmail = Math.floor(Math.random() * 101); + let emailPasswordUpdatevalue = `passwordupdate-${randomNumberforEmail}-${browserEngine}@example.com`; let passwordInputValue = process.env.MAGENTO_EXISTING_ACCOUNT_PASSWORD; + let changedPasswordValue = process.env.MAGENTO_EXISTING_ACCOUNT_CHANGED_PASSWORD; + + // Log out of current account + if(await page.getByRole('link', { name: selectors.mainMenu.myAccountLogoutItem }).isVisible()){ + await mainMenu.logout(); + } + // Create account if(!changedPasswordValue || !passwordInputValue) { throw new Error("Changed password or original password in your .env file is not defined or could not be read."); } - // Navigate to Account Information, confirm by checking heading above sidebar - const sidebarAccountInfoLink = page.getByRole('link', { name: 'Account Information' }); - sidebarAccountInfoLink.click(); - await expect(page.getByRole('heading', { name: 'Account Information' }).locator('span')).toBeVisible(); + await registerPage.createNewAccount(inputvalues.accountCreation.firstNameValue, inputvalues.accountCreation.lastNameValue, emailPasswordUpdatevalue, passwordInputValue); + // Update password + await page.goto(slugs.account.changePasswordSlug); + await page.waitForLoadState(); await accountPage.updatePassword(passwordInputValue, changedPasswordValue); + // If login with changePasswordValue is possible, then password change was succesful. + await loginPage.login(emailPasswordUpdatevalue, changedPasswordValue); + + // Logout again, login with original account + await mainMenu.logout(); + let emailInputValue = process.env[`MAGENTO_EXISTING_ACCOUNT_EMAIL_${browserEngine}`]; + if(!emailInputValue) { + 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); }); }); diff --git a/tests/base/config/slugs.json b/tests/base/config/slugs.json index 20673df..d6293a5 100644 --- a/tests/base/config/slugs.json +++ b/tests/base/config/slugs.json @@ -5,7 +5,8 @@ "createAccountSlug": "/customer/account/create", "addressBookSlug": "/customer/address", "addressIndexSlug": "/customer/address/index", - "addressNewSlug": "customer/address/new" + "addressNewSlug": "customer/address/new", + "changePasswordSlug": "/customer/account/edit/changepass/1/" }, "productpage": { "simpleProductSlug": "/push-it-messenger-bag.html", From be958dd9c1604acee27abf5f23172a631a0fd643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Wed, 22 Jan 2025 14:36:58 +0100 Subject: [PATCH 6/8] Fixed comments --- tests/base/account.spec.ts | 1 + tests/base/fixtures/account.page.ts | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/base/account.spec.ts b/tests/base/account.spec.ts index 4efca87..cbd3664 100644 --- a/tests/base/account.spec.ts +++ b/tests/base/account.spec.ts @@ -92,6 +92,7 @@ test.describe.serial('Account address book actions', { annotation: {type: 'Accou let addNewAddressTitle = page.getByRole('heading', {level: 1, name: selectors.newAddress.addNewAddressTitle}); if(await addNewAddressTitle.isHidden()) { await accountPage.deleteAllAddresses(); + testInfo.annotations.push({ type: 'Notification: deleted addresses', description: `All addresses are deleted to recreate the first address flow.` }); await page.goto(slugs.account.addressNewSlug); } diff --git a/tests/base/fixtures/account.page.ts b/tests/base/fixtures/account.page.ts index a1535bc..a5ae2f5 100644 --- a/tests/base/fixtures/account.page.ts +++ b/tests/base/fixtures/account.page.ts @@ -174,22 +174,17 @@ export class AccountPage { async deleteAllAddresses() { let addressDeletedNotification = verify.address.addressDeletedNotification; - // Handle the confirmation dialog for deleting addresses this.page.on('dialog', async (dialog) => { if (dialog.type() === 'confirm') { await dialog.accept(); } }); - // Continuously attempt to delete addresses until none are left while (await this.deleteAddressButton.isVisible()) { await this.deleteAddressButton.click(); await this.page.waitForLoadState(); - // Verify the notification for address deletion await expect(this.page.getByText(addressDeletedNotification)).toBeVisible(); } - - console.log("All addresses have been deleted."); } } From d944d9517dcc114f4d71413efa9ca8c130da349f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Wed, 22 Jan 2025 15:25:18 +0100 Subject: [PATCH 7/8] Also add support for subdirectories --- playwright.config.example.ts | 41 ++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/playwright.config.example.ts b/playwright.config.example.ts index 880e5e3..c9e79db 100644 --- a/playwright.config.example.ts +++ b/playwright.config.example.ts @@ -5,22 +5,41 @@ import fs from "node:fs"; dotenv.config({ path: path.resolve(__dirname, '.env') }); function getTestFiles(baseDir: string, customDir: string): string[] { - const baseFiles = new Set(fs.readdirSync(baseDir).filter(file => file.endsWith('.spec.ts'))); - const customFiles = fs.readdirSync(customDir).filter(file => file.endsWith('.spec.ts')); const testFiles = new Set(); - // Get base files that have an override in custom - for (const file of baseFiles) { - const baseFilePath = path.join(baseDir, file); - const customFilePath = path.join(customDir, file); + function getAllFiles(dir: string): string[] { + const files = fs.readdirSync(dir, { withFileTypes: true }); + const allFiles: string[] = []; + for (const file of files) { + const fullPath = path.join(dir, file.name); + if (file.isDirectory()) { + allFiles.push(...getAllFiles(fullPath)); + } else { + allFiles.push(fullPath); + } + } + return allFiles; + } + + const baseFiles = getAllFiles(baseDir); + const customFiles = getAllFiles(customDir); + + const baseRelativePaths = new Map( + baseFiles.map((file) => [path.relative(baseDir, file), file]) + ); + + const customRelativePaths = new Map( + customFiles.map((file) => [path.relative(customDir, file), file]) + ); - testFiles.add(fs.existsSync(customFilePath) ? customFilePath : baseFilePath); + for (const [relativePath, baseFilePath] of baseRelativePaths.entries()) { + const customFilePath = customRelativePaths.get(relativePath); + testFiles.add(customFilePath || baseFilePath); } - // Add custom tests that aren't in base - for (const file of customFiles) { - if (!baseFiles.has(file)) { - testFiles.add(path.join(customDir, file)); + for (const [relativePath, customFilePath] of customRelativePaths.entries()) { + if (!baseRelativePaths.has(relativePath)) { + testFiles.add(customFilePath); } } From 6fbe1335daf9b88249f1c67f9f51f9b70c852ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Heesen?= Date: Wed, 22 Jan 2025 15:27:48 +0100 Subject: [PATCH 8/8] Remove wrong commit --- playwright.config.example.ts | 41 ++++++++++-------------------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/playwright.config.example.ts b/playwright.config.example.ts index c9e79db..880e5e3 100644 --- a/playwright.config.example.ts +++ b/playwright.config.example.ts @@ -5,41 +5,22 @@ import fs from "node:fs"; dotenv.config({ path: path.resolve(__dirname, '.env') }); function getTestFiles(baseDir: string, customDir: string): string[] { + const baseFiles = new Set(fs.readdirSync(baseDir).filter(file => file.endsWith('.spec.ts'))); + const customFiles = fs.readdirSync(customDir).filter(file => file.endsWith('.spec.ts')); const testFiles = new Set(); - function getAllFiles(dir: string): string[] { - const files = fs.readdirSync(dir, { withFileTypes: true }); - const allFiles: string[] = []; - for (const file of files) { - const fullPath = path.join(dir, file.name); - if (file.isDirectory()) { - allFiles.push(...getAllFiles(fullPath)); - } else { - allFiles.push(fullPath); - } - } - return allFiles; - } - - const baseFiles = getAllFiles(baseDir); - const customFiles = getAllFiles(customDir); - - const baseRelativePaths = new Map( - baseFiles.map((file) => [path.relative(baseDir, file), file]) - ); - - const customRelativePaths = new Map( - customFiles.map((file) => [path.relative(customDir, file), file]) - ); + // Get base files that have an override in custom + for (const file of baseFiles) { + const baseFilePath = path.join(baseDir, file); + const customFilePath = path.join(customDir, file); - for (const [relativePath, baseFilePath] of baseRelativePaths.entries()) { - const customFilePath = customRelativePaths.get(relativePath); - testFiles.add(customFilePath || baseFilePath); + testFiles.add(fs.existsSync(customFilePath) ? customFilePath : baseFilePath); } - for (const [relativePath, customFilePath] of customRelativePaths.entries()) { - if (!baseRelativePaths.has(relativePath)) { - testFiles.add(customFilePath); + // Add custom tests that aren't in base + for (const file of customFiles) { + if (!baseFiles.has(file)) { + testFiles.add(path.join(customDir, file)); } }