diff --git a/tests/boxed-steps-pom/boxed-step-pom.spec.ts b/tests/boxed-steps-pom/boxed-step-pom.spec.ts new file mode 100644 index 0000000..7b47fcb --- /dev/null +++ b/tests/boxed-steps-pom/boxed-step-pom.spec.ts @@ -0,0 +1,52 @@ +import { test } from '@playwright/test'; +import { MainPage } from './boxed-step-pom' + + +// various test scenarios for adding items to the cart +// comment out the line before the `addAndViewCart` function in one of the tests +// Then run the test to see how box steps hide the implementation details of the step + +test.describe('add to cart scenarios', () => { + test.beforeEach(async ({ page }) => { + const pom = new MainPage(page); + await pom.goto(); + }); + + test('buy now from carousel using pom', async ({ page }) => { + const pom = new MainPage(page); + await page.getByRole('button', { name: 'Buy Now' }).click(); + await pom.addToCart(); + await pom.openCart(); + await pom.expectToBeInCart('Xbox Wireless Controller Lunar Shift Special Edition') + }); + + test('add to cart from search using pom', async ({ page }) => { + const pom = new MainPage(page); + const product = 'Xbox Wireless Controller Mineral Camo Special Edition' + await pom.searchForProduct('xbox'); + await pom.clickOnProductByImage(product); + await pom.addToCart(); + await pom.openCart(); + await pom.expectToBeInCart(product); + }); + + test('add to cart from all products page using pom', async ({ page }) => { + const pom = new MainPage(page); + const product = 'Xbox Wireless Controller Mineral Camo Special Edition' + await pom.clickOnProductPage('All Products'); + await pom.clickOnProductByImage(product); + await pom.addToCart(); + await pom.openCart(); + await pom.expectToBeInCart(product); + }); + + test('add to cart from all laptops page using pom', async ({ page }) => { + const pom = new MainPage(page); + const product = 'Microsoft Surface Pro X 1876 13 Inches Laptop' + await pom.clickOnProductPage('Laptops'); + await pom.clickOnProductByImage(product); + await pom.addToCart(); + await pom.openCart(); + await pom.expectToBeInCart(product); + }); +}); diff --git a/tests/boxed-steps-pom/boxed-step-pom.ts b/tests/boxed-steps-pom/boxed-step-pom.ts new file mode 100644 index 0000000..ec9ec6a --- /dev/null +++ b/tests/boxed-steps-pom/boxed-step-pom.ts @@ -0,0 +1,60 @@ +import { test, expect, Page } from '@playwright/test'; + +export class MainPage { + constructor(private readonly page: Page) { } + + async goto() { + await this.page.goto('https://cloudtesting.contosotraders.com/'); + } + + // using the boxedStep decorator defined below + @boxedStep + async searchForProduct(query: string) { + await this.page.getByPlaceholder('Search by product name or search by image').click(); + await this.page.getByPlaceholder('Search by product name or search by image').fill(query); + await this.page.getByPlaceholder('Search by product name or search by image').press('Enter'); + } + + // using the boxedStep decorator defined below + @boxedStep + async clickOnProductPage(link: string) { + await this.page.getByRole('link', { name: link }).first().click(); + } + + // using the boxedStep decorator defined below + @boxedStep + async clickOnProductByImage(title: string) { + await this.page.getByRole('img', { name: title }).click(); + } + + // using the boxedStep decorator defined below + @boxedStep + async addToCart() { + await this.page.getByRole('button', { name: 'Add To Bag' }).click(); + } + + // using the boxedStep decorator defined below + @boxedStep + async openCart() { + await this.page.getByLabel('cart').click(); + } + + // using the boxedStep decorator defined below + @boxedStep + async expectToBeInCart(title: string) { + await expect(this.page.getByText(title)).toBeVisible(); + } +} + + +// Leveraging TypeScript decorators to wrap functions +// https://www.typescriptlang.org/docs/handbook/decorators.html +// A boxed test.step() gets defined with the name of the method +function boxedStep(target: Function, context: ClassMethodDecoratorContext) { + return function replacementMethod(...args: any) { + const name = this.constructor.name + '.' + (context.name as string); + return test.step(name, async () => { + return await target.call(this, ...args); + }, { box: true }); // Note the "box" option here. + }; +} diff --git a/tests/boxed-steps-pom/readme.md b/tests/boxed-steps-pom/readme.md new file mode 100644 index 0000000..5360c2b --- /dev/null +++ b/tests/boxed-steps-pom/readme.md @@ -0,0 +1,20 @@ +--- +name: Playwright - Boxed Steps Page Object Model +description: "This sample demonstrates how to box your test steps to hide implementation details when using the Page Object Model" +page_type: sample +languages: +- typescript +- javascript +products: +- playwright +urlFragment: boxed-steps-pom +--- + +# Box Steps with Page Object Model + +This sample demonstrates how to box your test steps to hide implementation details when using the Page Object Model. + +For more information about the sample see: + +- [Box steps docs](https://playwright.dev/docs/api/class-test#test-step) +- [Release video on box steps](https://youtu.be/KqVuRAlOkm0) diff --git a/tests/boxed-steps/boxed-step.spec.ts b/tests/boxed-steps/boxed-step.spec.ts index 4bc0d77..6b53273 100644 --- a/tests/boxed-steps/boxed-step.spec.ts +++ b/tests/boxed-steps/boxed-step.spec.ts @@ -1,7 +1,8 @@ import { test, expect, Page } from '@playwright/test'; // various test scenarios for adding items to the cart -// comment out line 20 and run the second test to see how box steps work +// comment out the line before the `addAndViewCart` function in one of the tests +// Then run the test to see how box steps hide the implementation details of the step // reusable function to add an item to the cart and view the cart // we wrap our actions in a test step and set box to true