diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 32d87842..0c3ae542 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -15,6 +15,7 @@ concurrency: jobs: end-2-end-tests: strategy: + fail-fast: false matrix: include: - MAGENTO_VERSION: 2.4.5 @@ -56,16 +57,14 @@ jobs: - name: Prepare logs if: always() run: | - mkdir -p logs + mkdir -p logs/magento docker-compose logs > logs/container_logs.log - docker cp magento-plugin-magento-1:/bitnami/magento/var/log logs + docker cp magento-plugin-magento-1:/bitnami/magento/var/log logs/magento - uses: actions/upload-artifact@v4 if: always() with: name: results-${{ matrix.MAGENTO_VERSION }}-${{ github.run_number }} path: | playwright-report/ - var/log/ - magento.log - nginx-proxy.log + logs/ diff --git a/Test/End-2-End/Components/Cart.js b/Test/End-2-End/Components/Cart.js index 8e244f09..7f8925ec 100644 --- a/Test/End-2-End/Components/Cart.js +++ b/Test/End-2-End/Components/Cart.js @@ -1,13 +1,16 @@ -import {expect} from "@playwright/test"; +import { expect } from "@playwright/test"; +import GoTo from "./GoTo"; export default class Cart { - constructor(page) { - this.page = page; - } + constructor(page) { + this.page = page; + } - async addStandardItemToCart() { - await this.page.goto('./affirm-water-bottle.html'); - await this.page.getByRole("button", {name: "Add to cart"}).click(); - await expect(this.page.getByText(/You added [A-Za-z0-9 ]+ to your shopping cart/i)).toBeVisible(); - } + async addStandardItemToCart() { + await new GoTo(this.page).product.standard(); + await this.page.getByRole("button", { name: "Add to cart" }).click(); + await expect( + this.page.getByText(/You added [A-Za-z0-9 ]+ to your shopping cart/i), + ).toBeVisible(); + } } diff --git a/Test/End-2-End/Components/CheckoutPage.js b/Test/End-2-End/Components/CheckoutPage.js index 2c56b8f3..295f0ce5 100644 --- a/Test/End-2-End/Components/CheckoutPage.js +++ b/Test/End-2-End/Components/CheckoutPage.js @@ -1,22 +1,50 @@ -import {expect} from "@playwright/test"; +import { expect } from "@playwright/test"; export default class CheckoutPage { - constructor(page) { - this.page = page; - } - - async getGrandTotalElement() { - return await this.page.getByRole('row', { name: 'Order Total £' }).locator('span') - } - async getGrandTotalValue(stripSign = false) { - const value = await (await this.getGrandTotalElement()).innerText(); - return stripSign ? value.replace('£', '') : value; - } - - async applyDiscountCode(discountCode) { - await this.page.getByText('Apply Discount Code').click(); - await this.page.getByPlaceholder('Enter discount code').fill(discountCode); - await this.page.getByRole('button', { name: 'Apply Discount' }).click(); - await expect(await this.page.getByText('Your coupon was successfully applied.')).toBeVisible(); - } + constructor(page) { + this.page = page; + } + + async getGrandTotalElement() { + return await this.page + .getByRole("row", { name: "Order Total £" }) + .locator("span"); + } + async getGrandTotalValue(stripSign = false) { + const value = await (await this.getGrandTotalElement()).innerText(); + return stripSign ? value.replace("£", "") : value; + } + + async applyDiscountCode(discountCode) { + await this.page.getByText("Apply Discount Code").click(); + await this.page.getByPlaceholder("Enter discount code").fill(discountCode); + await this.page.getByRole("button", { name: "Apply Discount" }).click(); + await expect( + await this.page.getByText("Your coupon was successfully applied."), + ).toBeVisible(); + } + + async selectPayByBank() { + await this.page.getByLabel("Pay by Bank").click(); + } + + async selectRvvupTestMethod() { + await this.page.getByLabel("Rvvup Payment Method").click(); + } + + async selectClearpay() { + await this.page.getByLabel("Clearpay").click(); + } + + async selectCard() { + await this.page.getByLabel("Pay by Card").click(); + } + + async selectPaypal() { + await this.page.getByLabel("PayPal", { exact: true }).click(); + } + + async pressPlaceOrder() { + await this.page.getByRole("button", { name: "Place order" }).click(); + } } diff --git a/Test/End-2-End/Components/GoTo.js b/Test/End-2-End/Components/GoTo.js new file mode 100644 index 00000000..9c54314d --- /dev/null +++ b/Test/End-2-End/Components/GoTo.js @@ -0,0 +1,23 @@ +export default class GoTo { + constructor(page) { + this.page = page; + this.product = new GoToProduct(page); + } + + async checkout() { + await this.page.goto("./checkout"); + } + + async cart() { + await this.page.goto("./checkout/cart"); + } +} +class GoToProduct { + constructor(page) { + this.page = page; + } + + async standard() { + await this.page.goto("./affirm-water-bottle.html"); + } +} diff --git a/Test/End-2-End/Components/OrderConfirmation.js b/Test/End-2-End/Components/OrderConfirmation.js new file mode 100644 index 00000000..9dda4493 --- /dev/null +++ b/Test/End-2-End/Components/OrderConfirmation.js @@ -0,0 +1,20 @@ +import { expect } from "@playwright/test"; + +export default class OrderConfirmation { + constructor(page) { + this.page = page; + } + + async expectOnOrderConfirmation(isPending = false) { + await expect( + this.page.getByRole("heading", { name: "Thank you for your purchase!" }), + ).toBeVisible(); + if (isPending) { + await expect( + this.page.getByText( + "Your payment is being processed and is pending confirmation. You will receive an email confirmation when the payment is confirmed.", + ), + ).toBeVisible(); + } + } +} diff --git a/Test/End-2-End/Components/PayByBankCheckout.js b/Test/End-2-End/Components/PayByBankCheckout.js deleted file mode 100644 index 8f678896..00000000 --- a/Test/End-2-End/Components/PayByBankCheckout.js +++ /dev/null @@ -1,23 +0,0 @@ -import {expect} from "@playwright/test"; - -export default class PayByBankCheckout { - constructor(page) { - this.page = page; - } - - /* - * On the checkout page, place a pay by bank order and complete it - */ - async checkout() { - await this.page.getByLabel('Pay by Bank').click(); - await this.page.getByRole('button', {name: 'Place order'}).click(); - const frame = this.page.frameLocator('#rvvup_iframe-rvvup_YAPILY'); - await frame.getByLabel('Mock Bank').click(); - await frame.getByRole('button', {name: 'Log in on this device'}).click(); - - await this.page.waitForURL("**/checkout/onepage/success/"); - await expect(this.page.getByRole('heading', {name: 'Thank you for your purchase!'})).toBeVisible(); - await expect(this.page.getByText("Your payment is being processed and is pending confirmation. You will receive an email confirmation when the payment is confirmed.")).toBeVisible(); - - } -} diff --git a/Test/End-2-End/Components/PaymentMethods/CardCheckout.js b/Test/End-2-End/Components/PaymentMethods/CardCheckout.js new file mode 100644 index 00000000..22b29eab --- /dev/null +++ b/Test/End-2-End/Components/PaymentMethods/CardCheckout.js @@ -0,0 +1,72 @@ +import CheckoutPage from "../CheckoutPage"; + +export default class CardCheckout { + constructor(page) { + this.page = page; + this.checkoutPage = new CheckoutPage(page); + } + + async checkout() { + await this.checkoutPage.selectCard(); + // Credit card form + await this.page + .frameLocator(".st-card-number-iframe") + .getByLabel("Card Number") + .fill("4111 1111 1111 1111"); + await this.page + .frameLocator(".st-expiration-date-iframe") + .getByLabel("Expiration Date") + .fill("1233"); + await this.page + .frameLocator(".st-security-code-iframe") + .getByLabel("Security Code") + .fill("123"); + + await this.checkoutPage.pressPlaceOrder(); + // OTP form + await this.page.frameLocator('iframe[title="Bank Authentication"]').getByPlaceholder(' Enter Code Here') + .fill("1234"); + await this.page + .frameLocator('iframe[title="Bank Authentication"]') + .getByRole("button", { name: "SUBMIT" }) + .click(); + } + + async checkoutUsingFrictionless3DsCard() { + await this.checkoutPage.selectCard(); + // Credit card form + await this.page + .frameLocator(".st-card-number-iframe") + .getByLabel("Card Number") + .fill("4000 0000 0000 2701"); + await this.page + .frameLocator(".st-expiration-date-iframe") + .getByLabel("Expiration Date") + .fill("1233"); + await this.page + .frameLocator(".st-security-code-iframe") + .getByLabel("Security Code") + .fill("123"); + + await this.checkoutPage.pressPlaceOrder(); + } + + async checkoutUsingInvalidCard() { + await this.checkoutPage.selectCard(); + // Credit card form + await this.page + .frameLocator(".st-card-number-iframe") + .getByLabel("Card Number") + .fill("4000 0000 0000 2537"); + await this.page + .frameLocator(".st-expiration-date-iframe") + .getByLabel("Expiration Date") + .fill("1233"); + await this.page + .frameLocator(".st-security-code-iframe") + .getByLabel("Security Code") + .fill("123"); + + await this.checkoutPage.pressPlaceOrder(); + } +} diff --git a/Test/End-2-End/Components/PaymentMethods/ClearpayCheckout.js b/Test/End-2-End/Components/PaymentMethods/ClearpayCheckout.js new file mode 100644 index 00000000..42b579c8 --- /dev/null +++ b/Test/End-2-End/Components/PaymentMethods/ClearpayCheckout.js @@ -0,0 +1,27 @@ +import CheckoutPage from "../CheckoutPage"; + +export default class ClearpayCheckout { + constructor(page) { + this.page = page; + this.checkoutPage = new CheckoutPage(page); + } + + /* + * On the checkout page, place a pay by bank order and complete it + */ + async checkout() { + await this.checkoutPage.selectClearpay(); + await this.checkoutPage.pressPlaceOrder(); + + const clearpayFrame = this.page.frameLocator( + "#rvvup_iframe-rvvup_CLEARPAY", + ); + await clearpayFrame.getByRole("button", { name: "Accept All" }).click(); + + await clearpayFrame + .getByTestId("login-password-input") + .fill("XHvZsaUWh6K-BPWgXY!NJBwG"); + await clearpayFrame.getByTestId('login-password-button').click(); + await clearpayFrame.getByTestId('summary-button').click(); + } +} diff --git a/Test/End-2-End/Components/PaymentMethods/PayByBankCheckout.js b/Test/End-2-End/Components/PaymentMethods/PayByBankCheckout.js new file mode 100644 index 00000000..380fe2a1 --- /dev/null +++ b/Test/End-2-End/Components/PaymentMethods/PayByBankCheckout.js @@ -0,0 +1,89 @@ +import { expect } from "@playwright/test"; +import CheckoutPage from "../CheckoutPage"; + +export default class PayByBankCheckout { + constructor(page) { + this.page = page; + this.checkoutPage = new CheckoutPage(page); + } + + /* + * On the checkout page, place a pay by bank order and complete it + */ + async checkout() { + await this.checkoutPage.selectPayByBank(); + await this.checkoutPage.pressPlaceOrder(); + + const frame = this.page.frameLocator("#rvvup_iframe-rvvup_YAPILY"); + await frame.getByRole("button", { name: "Mock Bank" }).click(); + await frame.getByRole("button", { name: "Log in on this device" }).click(); + } + + async decline() { + await this.checkoutPage.selectPayByBank(); + await this.checkoutPage.pressPlaceOrder(); + + const frame = this.page.frameLocator("#rvvup_iframe-rvvup_YAPILY"); + await frame.getByRole("button", { name: "Natwest" }).click(); + await frame.getByRole("button", { name: "Log in on this device" }).click(); + + await this.page.getByRole("button", { name: "Cancel" }).click(); + await expect(this.page.getByText("Payment Declined")).toBeVisible(); + } + + async declineInsufficientFunds() { + await this.checkoutPage.selectPayByBank(); + await this.checkoutPage.pressPlaceOrder(); + + const frame = this.page.frameLocator("#rvvup_iframe-rvvup_YAPILY"); + await frame.getByRole("button", { name: "Natwest" }).click(); + await frame.getByRole("button", { name: "Log in on this device" }).click(); + + await this.page + .locator("input#customer-number") + .pressSequentially("123456789012"); + await this.page.locator("button#customer-number-login").click(); + + await this.page.locator("input#pin-1").pressSequentially("5"); + await this.page.locator("input#pin-2").pressSequentially("7"); + await this.page.locator("input#pin-3").pressSequentially("2"); + await this.page.locator("input#password-1").pressSequentially("4"); + await this.page.locator("input#password-2").pressSequentially("3"); + await this.page.locator("input#password-3").pressSequentially("6"); + await this.page.getByRole("button", { name: "Continue" }).click(); + + await this.page + .locator("dl") + .filter({ hasText: "Account NameSydney Beard (Personal Savings)" }) + .getByRole("button", { name: "Select account" }) + .click(); + await this.page.getByRole("button", { name: "Confirm payment" }).click(); + + await expect(this.page.getByText(/Insufficient funds/)).toBeVisible(); + } + + async exitModalBeforeCompletingTransaction() { + await this.checkoutPage.selectPayByBank(); + await this.checkoutPage.pressPlaceOrder(); + + const frame = this.page.frameLocator("#rvvup_iframe-rvvup_YAPILY"); + await frame.getByRole("button", { name: "Natwest" }).click(); + + // we want to open modal, open natwest in a new tab, close modal, + // complete natwest transaction, see where the redirect sends us to + await frame.getByRole("button", { name: "Log in on this device" }).click(); + + await this.page + .locator("input#customer-number") + .pressSequentially("123456789012"); + await this.page.locator("button#customer-number-login").click(); + + await this.page.locator("input#pin-1").pressSequentially("5"); + await this.page.locator("input#pin-2").pressSequentially("7"); + await this.page.locator("input#pin-3").pressSequentially("2"); + await this.page.locator("input#password-1").pressSequentially("4"); + await this.page.locator("input#password-2").pressSequentially("3"); + await this.page.locator("input#password-3").pressSequentially("6"); + await this.page.getByRole("button", { name: "Continue" }).click(); + } +} diff --git a/Test/End-2-End/Components/PaymentMethods/PaypalCheckout.js b/Test/End-2-End/Components/PaymentMethods/PaypalCheckout.js new file mode 100644 index 00000000..608685f2 --- /dev/null +++ b/Test/End-2-End/Components/PaymentMethods/PaypalCheckout.js @@ -0,0 +1,58 @@ +export default class PaypalCheckout { + constructor(page) { + this.page = page; + } + + async pressPaypalButton() { + const popupPromise = this.page.waitForEvent("popup"); + const paypalFrame = this.page.frameLocator("[title='PayPal']").first(); + await paypalFrame.getByRole("link", { name: "PayPal" }).click(); + + const popup = await popupPromise; + await this.acceptPayment(await popup); + + await popup.waitForEvent("close"); + } + + async acceptPayment(popup) { + await popup + .getByPlaceholder("Email") + .fill("sb-uqeqf29136249@personal.example.com"); + if (await popup.getByRole("button", { name: "Next" }).isVisible()) { + await popup.getByRole("button", { name: "Next" }).click(); + } + await popup.getByPlaceholder("Password").fill("h5Hc/b8M"); + await popup.getByRole("button", { name: "Log In" }).click(); + + await popup.getByTestId("submit-button-initial").click(); + } + + async checkoutUsingCard() { + const paypalFrame = this.page.frameLocator("[title='PayPal']").first(); + await paypalFrame + .getByRole("link", { name: "Debit or Credit Card" }) + .click(); + + const paypalCardForm = this.page.frameLocator("[title='paypal_card_form']"); + await paypalCardForm.getByLabel("Card number").fill("4698 4665 2050 8153"); + await paypalCardForm.getByLabel("Expires").fill("1125"); + await paypalCardForm.getByLabel("Security code").fill("141"); + await paypalCardForm.getByLabel("Mobile").fill("1234567890"); + + if (await paypalCardForm.getByPlaceholder("First name").isVisible()) { + await paypalCardForm.getByPlaceholder("First name").fill("John"); + await paypalCardForm.getByPlaceholder("Last name").fill("Doe"); + await paypalCardForm + .getByPlaceholder("Address line 1") + .fill("123 Main St"); + await paypalCardForm.getByPlaceholder("Town/City").fill("London"); + await paypalCardForm.getByPlaceholder("Postcode").fill("SW1A 1AA"); + await paypalCardForm + .getByPlaceholder("Email") + .fill("johndoe@example.com"); + } + await paypalCardForm + .getByRole("button", { name: /Buy Now|Continue/i }) + .click(); + } +} diff --git a/Test/End-2-End/Components/PaymentMethods/RvvupMethodCheckout.js b/Test/End-2-End/Components/PaymentMethods/RvvupMethodCheckout.js new file mode 100644 index 00000000..9bc11b43 --- /dev/null +++ b/Test/End-2-End/Components/PaymentMethods/RvvupMethodCheckout.js @@ -0,0 +1,19 @@ +import { expect } from "@playwright/test"; +import CheckoutPage from "../CheckoutPage"; + +export default class RvvupMethodCheckout { + constructor(page) { + this.page = page; + this.checkoutPage = new CheckoutPage(page); + } + + async checkout() { + await this.checkoutPage.selectRvvupTestMethod(); + await this.checkoutPage.pressPlaceOrder(); + + const frame = this.page.frameLocator( + "#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD", + ); + await frame.getByRole("button", { name: "Pay now" }).click(); + } +} diff --git a/Test/End-2-End/Components/RvvupMethodCheckout.js b/Test/End-2-End/Components/RvvupMethodCheckout.js deleted file mode 100644 index 70091f6d..00000000 --- a/Test/End-2-End/Components/RvvupMethodCheckout.js +++ /dev/null @@ -1,22 +0,0 @@ -import {expect} from "@playwright/test"; - -export default class RvvupMethodCheckout { - constructor(page) { - this.page = page; - } - - /* - * On the checkout page, place a pay by bank order and complete it - */ - async checkout() { - await this.page.getByLabel('Rvvup Payment Method').click(); - await this.page.getByRole('button', {name: 'Place order'}).click(); - const frame = this.page.frameLocator('#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD'); - await frame.getByRole('button', {name: 'Pay now'}).click(); - - await this.page.waitForURL("**/checkout/onepage/success/"); - await expect(this.page.getByRole('heading', {name: 'Thank you for your purchase!'})).toBeVisible(); - await expect(this.page.getByText("Your payment is being processed and is pending confirmation. You will receive an email confirmation when the payment is confirmed.")).toBeHidden(); - - } -} diff --git a/Test/End-2-End/Pages/VisitCheckoutPayment.js b/Test/End-2-End/Pages/VisitCheckoutPayment.js index dae53728..f09364a3 100644 --- a/Test/End-2-End/Pages/VisitCheckoutPayment.js +++ b/Test/End-2-End/Pages/VisitCheckoutPayment.js @@ -1,30 +1,121 @@ -import {expect} from "@playwright/test"; +import { expect } from "@playwright/test"; import Cart from "../Components/Cart"; +import GoTo from "../Components/GoTo"; export default class VisitCheckoutPayment { - constructor(page) { - this.page = page; - } + constructor(page) { + this.page = page; + } - async visit() { - const cart = new Cart(this.page); - await cart.addStandardItemToCart(); + async visit() { + await new Cart(this.page).addStandardItemToCart(); - await this.page.goto('./checkout'); + await new GoTo(this.page).checkout(); - await this.page.getByRole('textbox', { name: 'Email Address' }).fill('johndoe@example.com'); - await this.page.getByLabel('First name').fill('John'); - await this.page.getByLabel('Last name').fill('Doe'); - await this.page.getByLabel('Street Address: Line 1').fill('123 Main St'); - await this.page.getByLabel('City').fill('London'); - await this.page.getByLabel('Country').selectOption('United Kingdom'); - await this.page.getByLabel('ZIP').fill('SW1A 1AA'); - await this.page.getByLabel('Phone number').fill('+447500000000'); + await this.page + .getByRole("textbox", { name: "Email Address" }) + .fill("johndoe@example.com"); + await this.page.getByLabel("First name").fill("John"); + await this.page.getByLabel("Last name").fill("Doe"); + await this.page.getByLabel("Street Address: Line 1").fill("123 Main St"); + await this.page.getByLabel("City").fill("London"); + await this.page.getByLabel("Country").selectOption("United Kingdom"); + await this.page.getByLabel("ZIP").fill("SW1A 1AA"); + await this.page.getByLabel("Phone number").fill("+447500000000"); - await this.page.getByLabel('Fixed').click(); + await this.page.getByLabel("Fixed").click(); - await this.page.getByRole('button', { name: 'Next' }).click(); + await this.page.getByRole("button", { name: "Next" }).click(); - await expect(this.page.getByText('Payment Method', { exact: true })).toBeVisible(); - } + await expect( + this.page.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + } + + async visitCartWithMultipleProducts() { + const cart = new Cart(this.page); + await cart.addStandardItemToCart(); + await cart.addStandardItemToCart(); + await cart.addStandardItemToCart(); + + await new GoTo(this.page).cart(); + } + + async visitCheckoutWithMultipleProducts() { + const cart = new Cart(this.page); + await cart.addStandardItemToCart(); + await cart.addStandardItemToCart(); + await cart.addStandardItemToCart(); + + await new GoTo(this.page).checkout(); + + await this.page + .getByRole("textbox", { name: "Email Address" }) + .fill("johndoe@example.com"); + await this.page.getByLabel("First name").fill("John"); + await this.page.getByLabel("Last name").fill("Doe"); + await this.page.getByLabel("Street Address: Line 1").fill("123 Main St"); + await this.page.getByLabel("City").fill("London"); + await this.page.getByLabel("Country").selectOption("United Kingdom"); + await this.page.getByLabel("ZIP").fill("SW1A 1AA"); + await this.page.getByLabel("Phone number").fill("+447500000000"); + + await this.page.getByLabel("Free").click(); + + await this.page.getByRole("button", { name: "Next" }).click(); + + await expect( + this.page.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + } + + async visitWithoutShippingFee() { + await new Cart(this.page).addStandardItemToCart(); + + await new GoTo(this.page).checkout(); + + await this.page + .getByRole("textbox", { name: "Email Address" }) + .fill("johndoe@example.com"); + await this.page.getByLabel("First name").fill("John"); + await this.page.getByLabel("Last name").fill("Doe"); + await this.page.getByLabel("Street Address: Line 1").fill("123 Main St"); + await this.page.getByLabel("City").fill("London"); + await this.page.getByLabel("Country").selectOption("United Kingdom"); + await this.page.getByLabel("ZIP").fill("SW1A 1AA"); + await this.page.getByLabel("Phone number").fill("+447500000000"); + + await this.page.getByLabel("Free").click(); + + await this.page.getByRole("button", { name: "Next" }).click(); + + await expect( + this.page.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + } + + async visitAsClearpayUser() { + await new Cart(this.page).addStandardItemToCart(); + + await new GoTo(this.page).checkout(); + + await this.page + .getByRole("textbox", { name: "Email Address" }) + .fill("debim90109@fretice.com"); + await this.page.getByLabel("First name").fill("John"); + await this.page.getByLabel("Last name").fill("Doe"); + await this.page.getByLabel("Street Address: Line 1").fill("123 Main St"); + await this.page.getByLabel("City").fill("London"); + await this.page.getByLabel("Country").selectOption("United Kingdom"); + await this.page.getByLabel("ZIP").fill("SW1A 1AA"); + await this.page.getByLabel("Phone number").fill("+447500000000"); + + await this.page.getByLabel("Fixed").click(); + + await this.page.getByRole("button", { name: "Next" }).click(); + + await expect( + this.page.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + } } diff --git a/Test/End-2-End/checkout_infographics.spec.js b/Test/End-2-End/checkout_infographics.spec.js index 30d412fd..b4a474e9 100644 --- a/Test/End-2-End/checkout_infographics.spec.js +++ b/Test/End-2-End/checkout_infographics.spec.js @@ -1,36 +1,43 @@ -import {expect, test} from '@playwright/test'; +import { expect, test } from "@playwright/test"; import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; import CheckoutPage from "./Components/CheckoutPage"; -test('Clearpay infographic updates when total changes on checkout page', async ({page}) => { - const visitCheckoutPayment = new VisitCheckoutPayment(page); - const checkoutPage = new CheckoutPage(page); +test("Clearpay infographic updates when total changes on checkout page", async ({ + page, +}) => { + const checkoutPage = new CheckoutPage(page); - await visitCheckoutPayment.visit(); - await page.getByLabel('Clearpay').click(); + await new VisitCheckoutPayment(page).visit(); + await page.getByLabel("Clearpay").click(); - const initialGrandTotal = await checkoutPage.getGrandTotalValue(); + const initialGrandTotal = await checkoutPage.getGrandTotalValue(); - await checkoutPage.applyDiscountCode('H20'); + await checkoutPage.applyDiscountCode("H20"); - await expect(await checkoutPage.getGrandTotalElement()).not.toHaveText(initialGrandTotal); + await expect(await checkoutPage.getGrandTotalElement()).not.toHaveText( + initialGrandTotal, + ); - const currentGrandTotal = await checkoutPage.getGrandTotalValue(true); - let clearpayPayBy4Amount = (Math.round(parseFloat(currentGrandTotal) / 4 * 100) / 100).toString(); + const currentGrandTotal = await checkoutPage.getGrandTotalValue(true); + let clearpayPayBy4Amount = ( + Math.round((parseFloat(currentGrandTotal) / 4) * 100) / 100 + ).toString(); - await expect(await page.frameLocator('#placeholder-payment-iframe-rvvup_CLEARPAY') - .frameLocator('iframe').first() - .getByRole('heading', {name: '£'})).toContainText(clearpayPayBy4Amount); + await expect( + await page + .frameLocator("#placeholder-payment-iframe-rvvup_CLEARPAY") + .frameLocator("iframe") + .first() + .getByRole("heading", { name: "£" }), + ).toContainText(clearpayPayBy4Amount); }); -test('Pay by bank infographic is loaded', async ({page}) => { - const visitCheckoutPayment = new VisitCheckoutPayment(page); +test("Pay by bank infographic is loaded", async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); - await visitCheckoutPayment.visit(); + await page.getByLabel("Pay by Bank").click(); - await page.getByLabel('Pay by Bank').click(); - - await expect(await page.locator('#placeholder-payment-iframe-rvvup_YAPILY')) - .toHaveAttribute('src', new RegExp('.*/info/pay-by-bank', 'i')); + await expect( + await page.locator("#placeholder-payment-iframe-rvvup_YAPILY"), + ).toHaveAttribute("src", new RegExp(".*/info/pay-by-bank", "i")); }); - diff --git a/Test/End-2-End/clearpay.spec.js b/Test/End-2-End/clearpay.spec.js new file mode 100644 index 00000000..14c70011 --- /dev/null +++ b/Test/End-2-End/clearpay.spec.js @@ -0,0 +1,106 @@ +import { expect, test } from "@playwright/test"; +import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; +import ClearpayCheckout from "./Components/PaymentMethods/ClearpayCheckout"; +import OrderConfirmation from "./Components/OrderConfirmation"; +import GoTo from "./Components/GoTo"; +import Cart from "./Components/Cart"; + +test("Can place a Clearpay order", async ({ page, browser }) => { + await new VisitCheckoutPayment(page).visitAsClearpayUser(); + + await new ClearpayCheckout(page).checkout(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); +}); + +test("Renders the Clearpay widget on the product page", async ({ page }) => { + await new GoTo(page).product.standard(); + + await expect(page.locator(".afterpay-modal-overlay")).toBeHidden(); + + await expect( + page.getByRole("button", { name: "Clearpay logo - Opens a dialog" }), + ).toBeVisible(); + + await page + .getByRole("button", { name: "Clearpay logo - Opens a dialog" }) + .click(); + await expect(page.locator(".afterpay-modal-overlay")).toBeVisible(); +}); + +test("Renders the Clearpay widget on the cart page", async ({ page }) => { + await new Cart(page).addStandardItemToCart(); + await new GoTo(page).product.standard(); + + await expect(page.locator(".afterpay-modal-overlay")).toBeHidden(); + + await expect( + page.getByRole("button", { name: "Clearpay logo - Opens a dialog" }), + ).toBeVisible(); + + await page + .getByRole("button", { name: "Clearpay logo - Opens a dialog" }) + .click(); + await expect(page.locator(".afterpay-modal-overlay")).toBeVisible(); +}); + +// TODO: Add test back in once we add a Clearpay restricted product to test against +test.skip("Clearpay not available for restricted products", async ({ + page, +}) => { + await new GoTo(page).product.standard(); + + await expect( + page.getByText( + "This item has restrictions so not all payment methods may be available", + ), + ).toBeVisible(); + + await expect( + page.getByRole("button", { name: "Clearpay logo - Opens a dialog" }), + ).not.toBeVisible(); +}); + +// TODO: Add test back in once we add a below price threshold product to test against +test.skip("Clearpay not available for products below price threshold", async ({ + page, +}) => { + await new GoTo(page).product.standard(); + + await expect( + page.getByText( + "This item has restrictions so not all payment methods may be available", + ), + ).not.toBeVisible(); + + await expect( + page.getByRole("button", { name: "Clearpay logo - Opens a dialog" }), + ).not.toBeVisible(); +}); + +// TODO: Add test back in once we add a product above price threshold to test against +test.skip("Clearpay not available for products above price threshold", async ({ + page, +}) => { + await new GoTo(page).product.standard(); + + await expect( + page.getByText( + "This item has restrictions so not all payment methods may be available", + ), + ).not.toBeVisible(); + + await expect( + page.getByRole("button", { name: "Clearpay logo - Opens a dialog" }), + ).not.toBeVisible(); +}); + +test("Clearpay shows correct instalment amounts on product page", async ({ + page, +}) => { + await new GoTo(page).product.standard(); + await expect( + page.getByText("or 4 interest-free payments of £1.75 with"), + ).toBeVisible(); +}); +``; diff --git a/Test/End-2-End/pay_by_bank.spec.js b/Test/End-2-End/pay_by_bank.spec.js index a4364270..89c69163 100644 --- a/Test/End-2-End/pay_by_bank.spec.js +++ b/Test/End-2-End/pay_by_bank.spec.js @@ -1,13 +1,34 @@ -import {expect, test} from '@playwright/test'; +import { test } from "@playwright/test"; import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; -import PayByBankCheckout from "./Components/PayByBankCheckout"; +import PayByBankCheckout from "./Components/PaymentMethods/PayByBankCheckout"; +import OrderConfirmation from "./Components/OrderConfirmation"; -test('Can place an order using pay by bank', async ({page, browser}) => { - const visitCheckoutPayment = new VisitCheckoutPayment(page); - await visitCheckoutPayment.visit(); +test("Can place an order using pay by bank", async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); + await new PayByBankCheckout(page).checkout(); + await new OrderConfirmation(page).expectOnOrderConfirmation(true); +}); + +test("The customer can decline the payment", async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); + + await new PayByBankCheckout(page).decline(); +}); - const payByBankCheckout = new PayByBankCheckout(page); - await payByBankCheckout.checkout(); +test.skip("Payment declined if the customer has insufficient funds", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + // TODO: Testing insufficient funds does not work and needs fixing + await new PayByBankCheckout(page).declineInsufficientFunds(); }); +test.skip("Payment fails if the customer exits the modal before completing the transaction on their banking app", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + + // TODO: Read the QR code and use that to generate the second page for this test + await new PayByBankCheckout(page).exitModalBeforeCompletingTransaction(); +}); diff --git a/Test/End-2-End/pay_by_card.spec.js b/Test/End-2-End/pay_by_card.spec.js new file mode 100644 index 00000000..3839b595 --- /dev/null +++ b/Test/End-2-End/pay_by_card.spec.js @@ -0,0 +1,37 @@ +import test, { expect } from "@playwright/test"; +import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; +import OrderConfirmation from "./Components/OrderConfirmation"; +import CheckoutPage from "./Components/CheckoutPage"; +import CardCheckout from "./Components/PaymentMethods/CardCheckout"; + +test("Can place an inline pay by card order with 3DS challenge", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + + await new CardCheckout(page).checkout(); + await new OrderConfirmation(page).expectOnOrderConfirmation(); +}); + +test("Can place an inline pay by card order without 3DS challenge", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + + await new CardCheckout(page).checkoutUsingFrictionless3DsCard(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); +}); + +test("Cannot place pay by card order using invalid card details", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + + await new CheckoutPage(page).selectCard(); + + // Credit card form + await new CardCheckout(page).checkoutUsingInvalidCard(); + + await expect(page.getByText("3DSecure failed")).toBeVisible(); +}); diff --git a/Test/End-2-End/paypal.spec.js b/Test/End-2-End/paypal.spec.js new file mode 100644 index 00000000..5e3003cd --- /dev/null +++ b/Test/End-2-End/paypal.spec.js @@ -0,0 +1,95 @@ +import { expect, test } from "@playwright/test"; +import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; +import OrderConfirmation from "./Components/OrderConfirmation"; +import PaypalCheckout from "./Components/PaymentMethods/PaypalCheckout"; +import CheckoutPage from "./Components/CheckoutPage"; +import GoTo from "./Components/GoTo"; + +test("Can place a PayPal order on checkout", async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); + + await new CheckoutPage(page).selectPaypal(); + await new PaypalCheckout(page).pressPaypalButton(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); +}); + +test.fixme( + "Can place a PayPal order on checkout using debit or credit cards", + async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); + + await new CheckoutPage(page).selectPaypal(); + + await new PaypalCheckout(page).checkoutUsingCard(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); + }, +); + +test("Can place a PayPal express order", async ({ page }) => { + await new GoTo(page).product.standard(); + await new PaypalCheckout(page).pressPaypalButton(); + + // Shipping page + await page.getByLabel("Phone number").fill("+447500000000"); + await page.getByLabel("Free").click(); + + await page.getByRole("button", { name: "Next" }).click(); + + // Checkout page + await expect(page.getByText("Payment Method", { exact: true })).toBeVisible(); + + const numOfMethods = await page.locator(".payment-method").count(); + await expect(numOfMethods).toBe(1); + await page + .getByText( + "You are currently paying with PayPal. If you want to cancel this process", + ) + .isVisible(); + await new CheckoutPage(page).pressPlaceOrder(); + await new OrderConfirmation(page).expectOnOrderConfirmation(); +}); + +test.fixme( + "Can place a PayPal express order using debit or credit cards", + async ({ page }) => { + await new GoTo(page).product.standard(); + + await new PaypalCheckout(page).checkoutUsingCard(); + + // Continue to shipping and checkout + await page.getByLabel("Phone number").fill("+441234567890"); + await page.getByRole("button", { name: "Next" }).click(); + + await expect( + page.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + + await new CheckoutPage(page).pressPlaceOrder(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); + }, +); + +test("PayPal replaces the Place Order button with a PayPal button", async ({ + page, +}) => { + await new VisitCheckoutPayment(page).visit(); + + await new CheckoutPage(page).selectPayByBank(); + + await expect(page.getByRole("button", { name: "Place Order" })).toBeVisible(); + + await new CheckoutPage(page).selectPaypal(); + await expect( + page.getByRole("button", { name: "Place Order" }), + ).not.toBeVisible(); + + await expect( + page + .frameLocator("[title='PayPal']") + .first() + .getByRole("link", { name: "PayPal" }), + ).toBeVisible(); +}); diff --git a/Test/End-2-End/scenario_testing.spec.js b/Test/End-2-End/scenario_testing.spec.js index 2b85a2cf..9760c84b 100644 --- a/Test/End-2-End/scenario_testing.spec.js +++ b/Test/End-2-End/scenario_testing.spec.js @@ -1,50 +1,196 @@ -import {expect, test} from '@playwright/test'; +import { expect, test } from "@playwright/test"; import VisitCheckoutPayment from "./Pages/VisitCheckoutPayment"; -import RvvupMethodCheckout from "./Components/RvvupMethodCheckout"; +import RvvupMethodCheckout from "./Components/PaymentMethods/RvvupMethodCheckout"; import Cart from "./Components/Cart"; +import OrderConfirmation from "./Components/OrderConfirmation"; +import GoTo from "./Components/GoTo"; +import CheckoutPage from "./Components/CheckoutPage"; -test('Can place an order using different billing and shipping address', async ({ page, browser }) => { - const visitCheckoutPayment = new VisitCheckoutPayment(page); - await visitCheckoutPayment.visit(); - await page.getByLabel('Rvvup Payment Method').click(); - await page.locator('#billing-address-same-as-shipping-rvvup_FAKE_PAYMENT_METHOD').setChecked(false); - - const billingForm = page.locator('#payment_method_rvvup_FAKE_PAYMENT_METHOD'); - if (await page.getByRole('button', {name: 'Edit', exact: true}).isVisible()) { - await page.getByRole('button', {name: 'Edit', exact: true}).click(); - } - await billingForm.getByLabel('First Name').fill('Liam'); - await billingForm.getByLabel('Last Name').fill('Fox'); - await billingForm.getByLabel('Street Address: Line 1').fill('123 Small St'); - await billingForm.getByLabel('City').fill('Derby'); - await billingForm.getByLabel('Country').selectOption('United Kingdom'); - await billingForm.getByLabel('ZIP').fill('SW1B 1BB'); - await billingForm.getByLabel('Phone number').fill('+447599999999'); - await page.getByRole('button', { name: 'Update' }).click(); - await billingForm.getByLabel('First Name').isHidden(); - await billingForm.getByText("Liam Fox").isVisible(); - const rvvupMethodCheckout = new RvvupMethodCheckout(page); - await rvvupMethodCheckout.checkout(); +test("Can place an order using different billing and shipping address", async ({ + page, + browser, +}) => { + await new VisitCheckoutPayment(page).visit(); + await page.getByLabel("Rvvup Payment Method").click(); + await page + .locator("#billing-address-same-as-shipping-rvvup_FAKE_PAYMENT_METHOD") + .setChecked(false); + + const billingForm = page.locator("#payment_method_rvvup_FAKE_PAYMENT_METHOD"); + if ( + await page.getByRole("button", { name: "Edit", exact: true }).isVisible() + ) { + await page.getByRole("button", { name: "Edit", exact: true }).click(); + } + await billingForm.getByLabel("First Name").fill("Liam"); + await billingForm.getByLabel("Last Name").fill("Fox"); + await billingForm.getByLabel("Street Address: Line 1").fill("123 Small St"); + await billingForm.getByLabel("City").fill("Derby"); + await billingForm.getByLabel("Country").selectOption("United Kingdom"); + await billingForm.getByLabel("ZIP").fill("SW1B 1BB"); + await billingForm.getByLabel("Phone number").fill("+447599999999"); + await page.getByRole("button", { name: "Update" }).click(); + await billingForm.getByLabel("First Name").isHidden(); + await billingForm.getByText("Liam Fox").isVisible(); + const rvvupMethodCheckout = new RvvupMethodCheckout(page); + await rvvupMethodCheckout.checkout(); + + await new OrderConfirmation(page).expectOnOrderConfirmation(); }); -test('Changing qoute on a different tab for an in progress order makes the payment invalid', async ({ browser }) => { +test.describe("multiple tabs", () => { + test("Changing quote on a different tab for an in progress order makes the payment invalid", async ({ + browser, + }) => { const context = await browser.newContext(); const mainPage = await context.newPage(); const visitCheckoutPayment = new VisitCheckoutPayment(mainPage); await visitCheckoutPayment.visit(); - await mainPage.getByLabel('Rvvup Payment Method').click(); - await mainPage.getByRole('button', {name: 'Place order'}).click(); - const frame = mainPage.frameLocator('#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD'); - await frame.getByRole('button', {name: 'Pay now'}).isVisible(); + await mainPage.getByLabel("Rvvup Payment Method").click(); + await mainPage.getByRole("button", { name: "Place order" }).click(); + const frame = mainPage.frameLocator( + "#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD", + ); + await frame.getByRole("button", { name: "Pay now" }).isVisible(); const duplicatePage = await context.newPage(); const cart = new Cart(duplicatePage); await cart.addStandardItemToCart("Rvvup Crypto Future"); - await frame.getByRole('button', {name: 'Pay now'}).click(); + await frame.getByRole("button", { name: "Pay now" }).click(); await mainPage.waitForURL("**/checkout/cart/"); - await expect(mainPage.getByText("Your cart was modified after making payment request, please place order again.")).toBeVisible(); + await expect( + mainPage.getByText( + "Your cart was modified after making payment request, please place order again.", + ), + ).toBeVisible(); + }); + + test.fixme( + "Cannot complete the same order in multiple tabs simultaneously", + async ({ browser }) => { + const context = await browser.newContext(); + + // Start the checkout on the first tab + const mainPage = await context.newPage(); + await new VisitCheckoutPayment(mainPage).visit(); + + await mainPage.getByLabel("Rvvup Payment Method").click(); + await mainPage.getByRole("button", { name: "Place order" }).click(); + const mainFrame = mainPage.frameLocator( + "#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD", + ); + + // Start the checkout on the second tab + const duplicatePage = await context.newPage(); + await new GoTo(duplicatePage).checkout(); + + await duplicatePage.getByRole("button", { name: "Next" }).click(); + + await duplicatePage.getByLabel("Rvvup Payment Method").click(); + await duplicatePage.getByRole("button", { name: "Place order" }).click(); + const duplicateFrame = duplicatePage.frameLocator( + "#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD", + ); + + // Complete order in the first tab, and then in the second tab shortly after + await duplicateFrame.getByRole("button", { name: "Pay now" }).click(); + + await new OrderConfirmation(duplicatePage).expectOnOrderConfirmation(); + + await mainFrame.getByRole("button", { name: "Pay now" }).click(); + await expect( + mainPage.getByText(/An error has happened during application run.*/), + ).toBeVisible(); + }, + ); + + test("Cannot place order in one tab and then place the same order again in another tab", async ({ + browser, + }) => { + const context = await browser.newContext(); + + // Start the checkout on the first tab + const mainPage = await context.newPage(); + await new VisitCheckoutPayment(mainPage).visit(); + await mainPage.getByLabel("Rvvup Payment Method").click(); + + // Open the checkout page on the second tab + const duplicatePage = await context.newPage(); + await new GoTo(duplicatePage).checkout(); + + await duplicatePage.getByRole("button", { name: "Next" }).click(); + await expect( + duplicatePage.getByText("Payment Method", { exact: true }), + ).toBeVisible(); + await duplicatePage.getByLabel("Rvvup Payment Method").click(); + + // Complete order in the first tab + await mainPage.getByRole("button", { name: "Place order" }).click(); + const mainFrame = mainPage.frameLocator( + "#rvvup_iframe-rvvup_FAKE_PAYMENT_METHOD", + ); + await mainFrame.getByRole("button", { name: "Pay now" }).click(); + + await new OrderConfirmation(mainPage).expectOnOrderConfirmation(); + + // Complete order in the second tab + await duplicatePage.getByRole("button", { name: "Place order" }).click(); + await expect( + duplicatePage.getByText("No such entity with cartId ="), + ).toBeVisible(); + }); +}); + +test.describe("discounts", () => { + test("Can place an order using discount codes", async ({ page }) => { + await new VisitCheckoutPayment(page).visit(); + + await new CheckoutPage(page).applyDiscountCode("H20"); + + await new RvvupMethodCheckout(page).checkout(); + await new OrderConfirmation(page).expectOnOrderConfirmation(); + }); + + // TODO: Need to add Free shipping option to test this + test.skip("Cannot place order when discount is 100% and cart value is £0", async ({ + page, + }) => { + await new VisitCheckoutPayment(page).visitWithoutShippingFee(); + + await new CheckoutPage(page).applyDiscountCode("100"); + + await expect( + page.getByText("No Payment Information Required"), + ).toBeVisible(); + await expect(page.getByLabel("Pay by Bank")).not.toBeVisible(); + }); }); +test.describe("rounding", () => { + test.skip("No PayPal rounding errors when paying for 20% VAT products", async ({ + page, + }) => { + await new VisitCheckoutPayment(page).visitCheckoutWithMultipleProducts(); + + await expect( + page + .getByRole("row", { name: "Order Total £" }) + .locator("span") + .getByText("£6.00"), + ).toBeVisible(); + await expect(page.getByText("PayPal", { exact: true })).toBeEnabled(); + }); + + test.skip("No Clearpay rounding errors when paying for 20% VAT products", async ({ + page, + }) => { + await new VisitCheckoutPayment(page).visitCartWithMultipleProducts(); + + await expect( + page.getByText("or 4 interest-free payments of £1.50 with ⓘ"), + ).toBeVisible(); + }); +}); diff --git a/docker/scripts/configure-base-store.sh b/docker/scripts/configure-base-store.sh index 88dd6115..4f7dd7c2 100755 --- a/docker/scripts/configure-base-store.sh +++ b/docker/scripts/configure-base-store.sh @@ -8,6 +8,7 @@ bin/magento config:set currency/options/base GBP bin/magento config:set currency/options/default GBP bin/magento config:set general/locale/timezone Europe/London bin/magento config:set general/locale/code en_GB +bin/magento config:set carriers/freeshipping/active 1 echo "Configuring SMTP settings to point to $MAGENTO_SMTP_HOST:$MAGENTO_SMTP_PORT" bin/magento config:set system/smtp/disable 0 diff --git a/package.json b/package.json index f2fd9529..cefedd35 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,15 @@ "version": "1.0.0", "description": "", "main": "index.js", - "scripts": {}, + "scripts": { + "format:test": "prettier --write 'Test/**/*.{js,jsx,ts,tsx}'" + }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@playwright/test": "^1.41.1", - "@types/node": "^20.11.8" + "@types/node": "^20.11.8", + "prettier": "3.2.5" } } diff --git a/playwright.config.js b/playwright.config.js index a4a3efa6..20ac2dc6 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -11,8 +11,9 @@ const { defineConfig, devices } = require('@playwright/test'); * @see https://playwright.dev/docs/test-configuration */ module.exports = defineConfig({ - expect: { - timeout: 15000, + timeout: 120000, + expect: { + timeout: 20000, }, testDir: './Test/End-2-End', @@ -20,10 +21,9 @@ module.exports = defineConfig({ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: 2, + retries: 3, /* Opt out of parallel tests. */ - workers: 1, + workers: 3, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ @@ -40,44 +40,6 @@ module.exports = defineConfig({ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, - }, - - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, + }] });