├── .gitignore ├── tests ├── visual │ ├── visual.spec.ts-snapshots │ │ ├── menu-visual-on-Pixel-5-linux.png │ │ ├── menu-visual-on-Pixel-5-win32.png │ │ ├── menu-visual-on-iPhone-12-linux.png │ │ ├── menu-visual-on-iPhone-12-win32.png │ │ ├── cartPage-visual-on-Pixel-5-linux.png │ │ ├── cartPage-visual-on-Pixel-5-win32.png │ │ ├── cartPage-visual-on-iPhone-12-linux.png │ │ ├── cartPage-visual-on-iPhone-12-win32.png │ │ ├── loginPage-visual-on-Pixel-5-linux.png │ │ ├── loginPage-visual-on-Pixel-5-win32.png │ │ ├── loginPage-visual-on-iPhone-12-linux.png │ │ ├── loginPage-visual-on-iPhone-12-win32.png │ │ ├── menu-visual-on-Desktop-Chrome-linux.png │ │ ├── menu-visual-on-Desktop-Chrome-win32.png │ │ ├── cartPage-empty-visual-on-Pixel-5-linux.png │ │ ├── cartPage-empty-visual-on-Pixel-5-win32.png │ │ ├── cartPage-visual-on-Desktop-Chrome-linux.png │ │ ├── cartPage-visual-on-Desktop-Chrome-win32.png │ │ ├── inventoryPage-visual-on-Pixel-5-linux.png │ │ ├── inventoryPage-visual-on-Pixel-5-win32.png │ │ ├── inventoryPage-visual-on-iPhone-12-linux.png │ │ ├── inventoryPage-visual-on-iPhone-12-win32.png │ │ ├── loginPage-error-visual-on-Pixel-5-linux.png │ │ ├── loginPage-error-visual-on-Pixel-5-win32.png │ │ ├── cartPage-empty-visual-on-iPhone-12-linux.png │ │ ├── cartPage-empty-visual-on-iPhone-12-win32.png │ │ ├── checkoutPage-one-visual-on-Pixel-5-linux.png │ │ ├── checkoutPage-one-visual-on-Pixel-5-win32.png │ │ ├── checkoutPage-two-visual-on-Pixel-5-linux.png │ │ ├── checkoutPage-two-visual-on-Pixel-5-win32.png │ │ ├── inventoryItemPage-visual-on-Pixel-5-linux.png │ │ ├── inventoryItemPage-visual-on-Pixel-5-win32.png │ │ ├── loginPage-error-visual-on-iPhone-12-linux.png │ │ ├── loginPage-error-visual-on-iPhone-12-win32.png │ │ ├── loginPage-visual-on-Desktop-Chrome-linux.png │ │ ├── loginPage-visual-on-Desktop-Chrome-win32.png │ │ ├── checkoutPage-one-visual-on-iPhone-12-linux.png │ │ ├── checkoutPage-one-visual-on-iPhone-12-win32.png │ │ ├── checkoutPage-two-visual-on-iPhone-12-linux.png │ │ ├── checkoutPage-two-visual-on-iPhone-12-win32.png │ │ ├── inventoryItemPage-visual-on-iPhone-12-linux.png │ │ ├── inventoryItemPage-visual-on-iPhone-12-win32.png │ │ ├── inventoryPage-added-visual-on-Pixel-5-linux.png │ │ ├── inventoryPage-added-visual-on-Pixel-5-win32.png │ │ ├── inventoryPage-visual-on-Desktop-Chrome-linux.png │ │ ├── inventoryPage-visual-on-Desktop-Chrome-win32.png │ │ ├── cartPage-empty-visual-on-Desktop-Chrome-linux.png │ │ ├── cartPage-empty-visual-on-Desktop-Chrome-win32.png │ │ ├── checkoutPage-complete-visual-on-Pixel-5-linux.png │ │ ├── checkoutPage-complete-visual-on-Pixel-5-win32.png │ │ ├── inventoryPage-added-visual-on-iPhone-12-linux.png │ │ ├── inventoryPage-added-visual-on-iPhone-12-win32.png │ │ ├── loginPage-error-visual-on-Desktop-Chrome-linux.png │ │ ├── loginPage-error-visual-on-Desktop-Chrome-win32.png │ │ ├── checkoutPage-complete-visual-on-iPhone-12-linux.png │ │ ├── checkoutPage-complete-visual-on-iPhone-12-win32.png │ │ ├── checkoutPage-one-visual-on-Desktop-Chrome-linux.png │ │ ├── checkoutPage-one-visual-on-Desktop-Chrome-win32.png │ │ ├── checkoutPage-two-visual-on-Desktop-Chrome-linux.png │ │ ├── checkoutPage-two-visual-on-Desktop-Chrome-win32.png │ │ ├── inventoryItemPage-added-visual-on-Pixel-5-linux.png │ │ ├── inventoryItemPage-added-visual-on-Pixel-5-win32.png │ │ ├── inventoryItemPage-added-visual-on-iPhone-12-linux.png │ │ ├── inventoryItemPage-added-visual-on-iPhone-12-win32.png │ │ ├── inventoryItemPage-visual-on-Desktop-Chrome-linux.png │ │ ├── inventoryItemPage-visual-on-Desktop-Chrome-win32.png │ │ ├── inventoryPage-added-visual-on-Desktop-Chrome-linux.png │ │ ├── inventoryPage-added-visual-on-Desktop-Chrome-win32.png │ │ ├── checkoutPage-complete-visual-on-Desktop-Chrome-linux.png │ │ ├── checkoutPage-complete-visual-on-Desktop-Chrome-win32.png │ │ ├── inventoryItemPage-added-visual-on-Desktop-Chrome-linux.png │ │ └── inventoryItemPage-added-visual-on-Desktop-Chrome-win32.png │ └── visual.spec.ts ├── fixtures │ ├── models.ts │ ├── data │ │ ├── checkout.json │ │ ├── cart.json │ │ ├── users.json │ │ └── products.json │ └── dimensions.json ├── ui │ ├── menu.spec.ts │ ├── login.spec.ts │ ├── cart.spec.ts │ ├── inventory.spec.ts │ ├── inventory-item.spec.ts │ └── checkout.spec.ts ├── pages │ ├── BasePage.ts │ ├── CheckoutStepOnePage.ts │ ├── CheckoutCompletePage.ts │ ├── LoginPage.ts │ ├── InventoryPage.ts │ ├── CartPage.ts │ ├── InventoryItemPage.ts │ └── CheckoutStepTwoPage.ts ├── components │ ├── ErrorComponent.ts │ ├── CheckoutItemComponent.ts │ ├── HeaderComponent.ts │ ├── FooterComponent.ts │ ├── CartItemComponent.ts │ ├── FormComponent.ts │ ├── ProductItemComponent.ts │ ├── LoginFormComponent.ts │ ├── MenuComponent.ts │ └── CheckoutFormComponent.ts ├── e2e │ ├── cart.spec.ts │ ├── inventoryItem.spec.ts │ ├── menu.spec.ts │ ├── inventory.spec.ts │ ├── login.spec.ts │ └── checkoutOne.spec.ts └── auth.setup.ts ├── package.json ├── .github └── workflows │ ├── playwright-updatesnapshots.yml │ └── playwright.yml ├── ui.md ├── playwright.config.ts ├── readme.md └── e2e.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | /playwright/.auth/ 7 | -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/menu-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/menu-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/cartPage-empty-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/loginPage-error-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-one-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-two-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Pixel-5-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Pixel-5-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Pixel-5-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Pixel-5-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-iPhone-12-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-iPhone-12-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-iPhone-12-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-iPhone-12-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryPage-added-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/checkoutPage-complete-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Desktop-Chrome-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Desktop-Chrome-linux.png -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Desktop-Chrome-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecureuill/saucedemo-playwright/HEAD/tests/visual/visual.spec.ts-snapshots/inventoryItemPage-added-visual-on-Desktop-Chrome-win32.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sauce-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "keywords": [], 8 | "author": "", 9 | "license": "ISC", 10 | "devDependencies": { 11 | "@playwright/test": "^1.40.0", 12 | "@types/node": "^20.10.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/fixtures/models.ts: -------------------------------------------------------------------------------- 1 | export interface IProduct { 2 | name: string, 3 | description: string, 4 | price: number 5 | } 6 | export interface ICartProduct { 7 | qty: number, 8 | product: IProduct 9 | } 10 | 11 | export interface ILogin { 12 | username: string, 13 | password: string 14 | } 15 | 16 | export interface IUser { 17 | firstName: string, 18 | lastName: string, 19 | zipCode: string 20 | } 21 | export enum SortOptions { 22 | AZ = "az", 23 | ZA = "za", 24 | LoHi = "lohi", 25 | HiLo = "hilo" 26 | } -------------------------------------------------------------------------------- /tests/fixtures/data/checkout.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": { 3 | "firstName": "Jhon", 4 | "lastName": "Doe", 5 | "zipCode": "0123" 6 | }, 7 | "empty": { 8 | "firstName": "", 9 | "lastName": "", 10 | "zipCode": "" 11 | }, 12 | "empty_firstName": { 13 | "firstName": "", 14 | "lastName": "Doe", 15 | "zipCode": "0123" 16 | }, 17 | "empty_lastName": { 18 | "firstName": "Jhon", 19 | "lastName": "", 20 | "zipCode": "0123" 21 | }, 22 | "empty_zipCode": { 23 | "firstName": "Jhon", 24 | "lastName": "Doe", 25 | "zipCode": "" 26 | } 27 | } -------------------------------------------------------------------------------- /tests/fixtures/dimensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "24Inch": { 3 | "width": 1920, 4 | "height": 1200 5 | }, 6 | "15Inch": { 7 | "width": 1366, 8 | "height": 768 9 | }, 10 | "13Inch": { 11 | "width": 1024, 12 | "height": 800 13 | }, 14 | "iphone4": { 15 | "width": 320, 16 | "height": 480 17 | }, 18 | "iphone5": { 19 | "width": 320, 20 | "height": 568 21 | }, 22 | "iphone7": { 23 | "width": 375, 24 | "height": 667 25 | }, 26 | "iphoneX": { 27 | "width": 375, 28 | "height": 812 29 | }, 30 | "iphoneXr": { 31 | "width": 414, 32 | "height": 896 33 | }, 34 | "samsungS10": { 35 | "width": 360, 36 | "height": 760 37 | } 38 | } -------------------------------------------------------------------------------- /tests/ui/menu.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import { IProduct, SortOptions } from "../fixtures/models"; 5 | import data from '../fixtures/data/products.json'; 6 | 7 | let inventoryPage: InventoryPage; 8 | 9 | test.beforeEach( async ({page}) => { 10 | inventoryPage = new InventoryPage(page); 11 | await inventoryPage.visit(); 12 | }); 13 | 14 | test.describe('Menu, Header, Footer', () => { 15 | test('Should have default UI', async () => { 16 | await inventoryPage.header.validateDefaultLayout(); 17 | await inventoryPage.footer.validateDefaultLayout(); 18 | await inventoryPage.header.openMenu(); 19 | await inventoryPage.header.menu.validateDefaultLayout(); 20 | }); 21 | }) 22 | -------------------------------------------------------------------------------- /.github/workflows/playwright-updatesnapshots.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Update Snapshots 2 | on: 3 | push: 4 | tags: [ 'update-snapshots' ] 5 | # Give the workflow permission to deploy the Pages site 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | updatesnapshots: 11 | timeout-minutes: 60 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 18 18 | - name: Install dependencies 19 | run: npm ci 20 | - name: Install Playwright Browsers 21 | run: npx playwright install --with-deps 22 | - name: Update snapshots 23 | run: npx playwright test --grep "@create-snapshots" --update-snapshots 24 | - uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | commit_message: "[CI] Update Snapshots" 27 | branch: main -------------------------------------------------------------------------------- /tests/fixtures/data/cart.json: -------------------------------------------------------------------------------- 1 | { 2 | "1": { 3 | "qty": 1, 4 | "product": { 5 | "name": "Sauce Labs Backpack", 6 | "description": "carry.allTheThings() with the sleek, streamlined Sly Pack that melds uncompromising style with unequaled laptop and tablet protection.", 7 | "price": 29.99 8 | } 9 | }, 10 | "2": { 11 | "qty": 1, 12 | "product": { 13 | "name": "Sauce Labs Bolt T-Shirt", 14 | "description": "Get your testing superhero on with the Sauce Labs bolt T-shirt. From American Apparel, 100% ringspun combed cotton, heather gray with red bolt.", 15 | "price": 15.99 16 | } 17 | }, 18 | "3": { 19 | "qty": 1, 20 | "product": { 21 | "name": "Sauce Labs Onesie", 22 | "description": "Rib snap infant onesie for the junior automation engineer in development. Reinforced 3-snap bottom closure, two-needle hemmed sleeved and bottom won't unravel.", 23 | "price": 7.99 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /tests/fixtures/data/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": { 3 | "username": "standard_user", 4 | "password": "secret_sauce" 5 | }, 6 | "bad_credential":{ 7 | "username": "standard_user", 8 | "password": "secret_sauc" 9 | }, 10 | "empty_password": { 11 | "username": "standard_user", 12 | "password": "" 13 | }, 14 | "empty_user": { 15 | "username": "", 16 | "password": "secret_sauc" 17 | }, 18 | "empty_credential": { 19 | "username": "", 20 | "password": "" 21 | }, 22 | "locked_user": { 23 | "username": "locked_out_user", 24 | "password": "secret_sauce" 25 | }, 26 | "problem_user": { 27 | "username": "problem_user", 28 | "password": "secret_sauce" 29 | }, 30 | "performance_user": { 31 | "username": "performance_glitch_user", 32 | "password": "secret_sauce" 33 | }, 34 | "error_user": { 35 | "username": "error_user", 36 | "password": "secret_sauce" 37 | }, 38 | "visual_user": { 39 | "username": "visual_user", 40 | "password": "secret_sauce" 41 | } 42 | } -------------------------------------------------------------------------------- /tests/pages/BasePage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | 4 | export abstract class BasePage { 5 | readonly page: Page; 6 | readonly url: string; 7 | 8 | constructor(page:Page, url: string){ 9 | this.page = page; 10 | this.url = url; 11 | } 12 | 13 | toBe = async () => { 14 | const regex = new RegExp(`${this.url}`) 15 | await expect(this.page).toHaveURL(regex); 16 | } 17 | 18 | visit = async () => { 19 | await this.page.goto(this.url); 20 | await this.page.waitForLoadState('networkidle') 21 | } 22 | 23 | abstract validateDefaultLayout(); 24 | 25 | validateViewportResize = async () => { 26 | for (let width = this.page.viewportSize()!.width; width >= 320; width -= 320) { 27 | await this.page.setViewportSize({ width, height: this.page.viewportSize()!.height }); 28 | 29 | const screenWidth = await this.page.evaluate(() => { 30 | return document.documentElement.clientWidth; 31 | }); 32 | 33 | expect(screenWidth).toBe(width); 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /tests/ui/login.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from '@playwright/test'; 2 | import { LoginPage } from '../pages/LoginPage'; 3 | import data from '../fixtures/data/users.json'; 4 | import { InventoryPage } from '../pages/InventoryPage'; 5 | let loginPage: LoginPage; 6 | 7 | test.use({ storageState: { cookies: [], origins: [] } }); 8 | 9 | test.beforeEach(async ({page}) => { 10 | loginPage = new LoginPage(page); 11 | await loginPage.visit(); 12 | await loginPage.toBe(); 13 | }) 14 | 15 | test.describe('login UI', () => { 16 | test('Viewport should resize correctly @responsive', async () => { 17 | await loginPage.validateViewportResize(); 18 | }) 19 | 20 | test('should have default Layout on start', async ({page}) => { 21 | await loginPage.validateDefaultLayout(); 22 | }) 23 | 24 | test('should have Error Layout on invalid form', async ({page}) => { 25 | await loginPage.formComponent.submit() 26 | await loginPage.validateErrorLayout(); 27 | }) 28 | 29 | test('Viewport should resize correctly', async () => { 30 | await loginPage.validateViewportResize(); 31 | }) 32 | }); 33 | -------------------------------------------------------------------------------- /tests/ui/cart.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { InventoryPage } from '../pages/InventoryPage'; 3 | import { CartPage } from '../pages/CartPage'; 4 | import data from '../fixtures/data/cart.json'; 5 | import { CheckoutStepOnePage } from '../pages/CheckoutStepOnePage'; 6 | 7 | let inventoryPage: InventoryPage; 8 | let cartPage: CartPage; 9 | 10 | 11 | test.beforeEach(async ({page}) => { 12 | inventoryPage = new InventoryPage(page); 13 | cartPage = new CartPage(page, [data[1], data[2], data[3]]); 14 | 15 | await inventoryPage.visit(); 16 | await inventoryPage.items[0].addToCart(); 17 | await inventoryPage.items[2].addToCart(); 18 | await inventoryPage.items[4].addToCart(); 19 | await inventoryPage.header.visitCart(); 20 | await cartPage.toBe(); 21 | 22 | }) 23 | 24 | test.describe('Cart UI', () => { 25 | 26 | test('Viewport should resize correctly @responsive', async () => { 27 | await cartPage.validateViewportResize(); 28 | }) 29 | 30 | test('Should have default Layout', async () => { 31 | await cartPage.validateDefaultLayout(); 32 | }) 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /tests/pages/CheckoutStepOnePage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright-core"; 2 | import { BasePage } from "./BasePage"; 3 | import { CheckoutFormComponent } from "../components/CheckoutFormComponent"; 4 | import { Locator, expect } from "@playwright/test"; 5 | 6 | export class CheckoutStepOnePage extends BasePage{ 7 | 8 | readonly form: CheckoutFormComponent; 9 | readonly locatorHeaderTitle: Locator; 10 | readonly locatorCancelButton: Locator; 11 | 12 | constructor(page: Page) { 13 | super(page, "/checkout-step-one"); 14 | 15 | this.locatorHeaderTitle = this.page.locator('#header_container').getByText('Checkout: Your Information'); 16 | this.locatorCancelButton = this.page.getByRole('button', {name: 'Cancel'}); 17 | this.form = new CheckoutFormComponent(this.page); 18 | } 19 | 20 | validateDefaultLayout = async () => { 21 | await expect(this.locatorHeaderTitle).toBeVisible(); 22 | await expect(this.locatorCancelButton).toBeVisible(); 23 | await this.form.validateDefaultLayout(); 24 | } 25 | 26 | validateErrorLayout = async () => { 27 | await this.form.validateErrorLayout(); 28 | } 29 | 30 | cancel = async () => { 31 | await this.locatorCancelButton.click(); 32 | await this.page.waitForLoadState('networkidle'); 33 | } 34 | } -------------------------------------------------------------------------------- /tests/components/ErrorComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | 4 | export class ErrorComponent { 5 | readonly page: Page; 6 | readonly locatorMessage: Locator; 7 | readonly locatorDismissButton: Locator; 8 | errorMessagePrefix: string; 9 | 10 | constructor(page: Page, errorMessagePrefix = 'Epic sadface: '){ 11 | this.page = page; 12 | this.errorMessagePrefix = errorMessagePrefix; 13 | this.locatorMessage = this.page.locator('[data-test=error]'); 14 | this.locatorDismissButton = this.locatorMessage.getByRole('button'); 15 | } 16 | 17 | dismiss = async () => { 18 | await this.locatorDismissButton.click(); 19 | } 20 | 21 | isVisible = async () => { 22 | await expect(this.locatorDismissButton).toBeVisible(); 23 | await expect(this.locatorDismissButton.locator('svg')).toBeVisible(); 24 | await expect(this.locatorMessage).toBeVisible(); 25 | await expect(this.locatorMessage).toContainText(this.errorMessagePrefix); 26 | } 27 | 28 | isNotVisible = async () => { 29 | await expect(this.locatorMessage).not.toBeVisible(); 30 | } 31 | 32 | hasMessage = async (message: string) => { 33 | await expect(this.locatorMessage).toHaveText(`${this.errorMessagePrefix} ${message}`); 34 | } 35 | } -------------------------------------------------------------------------------- /tests/ui/inventory.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import { IProduct, SortOptions } from "../fixtures/models"; 5 | import data from '../fixtures/data/products.json'; 6 | 7 | let inventoryPage: InventoryPage; 8 | 9 | test.beforeEach( async ({page}) => { 10 | inventoryPage = new InventoryPage(page); 11 | await inventoryPage.visit(); 12 | }); 13 | 14 | test.describe('Inventory UI', () => { 15 | test('Viewport should resize correctly @responsive', async () => { 16 | await inventoryPage.validateViewportResize(); 17 | }) 18 | 19 | test('Should have default Layout', async ({page}) => { 20 | await inventoryPage.validateDefaultLayout(); 21 | }) 22 | 23 | test('Should alternate Add To Cart button and Remove button when add/remove item to/from cart', async () => { 24 | const index = Math.floor(Math.random()*inventoryPage.items.length); 25 | await inventoryPage.items[index].addToCart(); 26 | await inventoryPage.items[index].validateRemoveUX(); 27 | await inventoryPage.items[index].removeFromCart(); 28 | await inventoryPage.items[index].validateAddLayout(); 29 | }) 30 | 31 | test('Viewport should resize correctly', async () => { 32 | await inventoryPage.validateViewportResize(); 33 | }) 34 | 35 | }) -------------------------------------------------------------------------------- /tests/components/CheckoutItemComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { ICartProduct, IProduct } from "../fixtures/models"; 3 | import { expect } from "playwright/test"; 4 | 5 | export class CheckoutItemComponent { 6 | readonly page: Page; 7 | readonly locatorQuantity: Locator; 8 | readonly locatorProductTitle: Locator; 9 | readonly locatorProductDescription: Locator; 10 | readonly locatorProductPrice: Locator; 11 | 12 | constructor(page: Page, cart: ICartProduct){ 13 | this.page = page; 14 | 15 | this.locatorProductTitle = this.page.getByRole('link', {name: `${cart.product.name}`}); 16 | 17 | const container = this.page.locator('.cart_item', {hasText: `${cart.product.name}`}); 18 | this.locatorProductDescription = container.getByText(cart.product.description); 19 | this.locatorQuantity = container.locator('.cart_quantity', {hasText: `${cart.qty}`}); 20 | this.locatorProductPrice = container.locator('.inventory_item_price', {hasText: `$${cart.product.price}`}); 21 | 22 | } 23 | 24 | validateDefaultUX = async () => { 25 | await expect(this.locatorQuantity).toBeVisible(); 26 | await expect(this.locatorProductTitle).toBeVisible(); 27 | await expect(this.locatorProductDescription).toBeVisible(); 28 | await expect(this.locatorProductPrice).toBeVisible(); 29 | } 30 | 31 | visitProductDetails = async () => { 32 | await this.locatorProductTitle.click(); 33 | await this.page.waitForLoadState('networkidle'); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /tests/components/HeaderComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | import { MenuComponent } from "./MenuComponent"; 4 | 5 | export class HeaderComponent { 6 | readonly page: Page; 7 | readonly locatorHeading: Locator; 8 | readonly locatorCartLink: Locator; 9 | readonly locatorMenuButton: Locator; 10 | readonly menu: MenuComponent; 11 | 12 | constructor(page: Page){ 13 | this.page = page; 14 | 15 | this.menu = new MenuComponent(this.page); 16 | 17 | this.locatorHeading = this.page.locator('#header_container').locator('.app_logo', { hasText: 'Swag Labs'}); 18 | this.locatorCartLink = this.page.locator('#shopping_cart_container a'); 19 | this.locatorMenuButton = this.page.getByRole('button', { name: 'Open Menu' }); 20 | } 21 | 22 | validateDefaultLayout = async () => { 23 | await expect(this.locatorHeading).toBeVisible(); 24 | await expect(this.locatorCartLink).toBeVisible(); 25 | await expect(this.locatorMenuButton).toBeVisible(); 26 | } 27 | 28 | openMenu = async () => { 29 | await this.locatorMenuButton.click(); 30 | } 31 | 32 | visitCart = async () => { 33 | await this.locatorCartLink.click(); 34 | await this.page.waitForLoadState('networkidle'); 35 | } 36 | 37 | cartCounter = async (count: number) => { 38 | if(count === 0) 39 | { 40 | await expect(this.locatorCartLink.locator('.shopping_cart_badge')).not.toBeVisible(); 41 | } 42 | else{ 43 | await expect(this.locatorCartLink).toContainText(count.toString()); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/ui/inventory-item.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import data from '../fixtures/data/products.json'; 5 | 6 | let inventoryPage: InventoryPage; 7 | let inventoryItemInPage: InventoryItemPage; 8 | let inventoryItemPage: InventoryItemPage; 9 | const PROD_INDEX_IN_CART = 2; 10 | const PROD_INDEX_OUT_CART = 1; 11 | 12 | test.beforeEach(async ({page}) => { 13 | inventoryItemPage = new InventoryItemPage(page, data.az[PROD_INDEX_OUT_CART]); 14 | inventoryItemInPage = new InventoryItemPage(page, data.az[PROD_INDEX_IN_CART]); 15 | inventoryPage = new InventoryPage(page); 16 | inventoryPage.visit(); 17 | await inventoryPage.items[PROD_INDEX_IN_CART].addToCart(); 18 | }); 19 | 20 | test.describe('Inventory Item UI', () => { 21 | test('Viewport should resize correctly @responsive', async () => { 22 | await inventoryItemPage.validateViewportResize(); 23 | }) 24 | 25 | test('Should have default Layout when accessing a product that is not added', async () => { 26 | await inventoryPage.items[PROD_INDEX_OUT_CART].openDetailsClickingOnPhoto(); 27 | await inventoryItemPage.validateDefaultLayout(); 28 | 29 | 30 | }); 31 | 32 | test('Should have Remove-Layout when accessing a product that is added', async () => { 33 | await inventoryPage.items[PROD_INDEX_IN_CART].openDetailsClickingOnPhoto(); 34 | await inventoryItemInPage.validateRemoveLayout(); 35 | }); 36 | 37 | test('Viewport should resize correctly', async () => { 38 | await inventoryItemInPage.validateViewportResize(); 39 | }) 40 | }); 41 | -------------------------------------------------------------------------------- /tests/e2e/cart.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { InventoryPage } from '../pages/InventoryPage'; 3 | import { CartPage } from '../pages/CartPage'; 4 | import data from '../fixtures/data/cart.json'; 5 | import { CheckoutStepOnePage } from '../pages/CheckoutStepOnePage'; 6 | 7 | let inventoryPage: InventoryPage; 8 | let cartPage: CartPage; 9 | 10 | 11 | test.beforeEach(async ({page}) => { 12 | inventoryPage = new InventoryPage(page); 13 | cartPage = new CartPage(page, [data[1], data[2], data[3]]); 14 | 15 | await inventoryPage.visit(); 16 | await inventoryPage.items[0].addToCart(); 17 | await inventoryPage.items[2].addToCart(); 18 | await inventoryPage.items[4].addToCart(); 19 | await inventoryPage.header.visitCart(); 20 | await cartPage.toBe(); 21 | 22 | }) 23 | 24 | test.describe('Cart features', () => { 25 | 26 | test('Should remove item and decrease cart counter', async () => { 27 | await cartPage.header.cartCounter(3); 28 | await cartPage.products[0].remove(); 29 | await cartPage.products[0].isRemoved(); 30 | await cartPage.header.cartCounter(2); 31 | }) 32 | 33 | test('Should remove all items', async () => { 34 | await cartPage.header.cartCounter(3); 35 | await cartPage.products[0].remove(); 36 | await cartPage.products[1].remove(); 37 | await cartPage.products[2].remove(); 38 | await cartPage.header.cartCounter(0); 39 | }) 40 | 41 | test('Should continue shopping', async () => { 42 | await cartPage.continueShopping(); 43 | await inventoryPage.toBe(); 44 | }) 45 | 46 | test('Should checkout', async ({page}) => { 47 | await cartPage.checkout(); 48 | await new CheckoutStepOnePage(page).toBe(); 49 | }) 50 | 51 | }); -------------------------------------------------------------------------------- /tests/e2e/inventoryItem.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import data from '../fixtures/data/products.json'; 5 | 6 | let inventoryPage: InventoryPage; 7 | let inventoryItemInPage: InventoryItemPage; 8 | let inventoryItemPage: InventoryItemPage; 9 | const PROD_INDEX_IN_CART = 2; 10 | const PROD_INDEX_OUT_CART = 1; 11 | 12 | test.beforeEach(async ({page}) => { 13 | inventoryItemPage = new InventoryItemPage(page, data.az[PROD_INDEX_OUT_CART]); 14 | inventoryItemInPage = new InventoryItemPage(page, data.az[PROD_INDEX_IN_CART]); 15 | inventoryPage = new InventoryPage(page); 16 | inventoryPage.visit(); 17 | await inventoryPage.items[PROD_INDEX_IN_CART].addToCart(); 18 | }); 19 | 20 | test.describe('Inventory Item features', async () => { 21 | test('Should add to cart and update cart counter', async () => { 22 | await inventoryPage.items[PROD_INDEX_OUT_CART].openDetailsClickingOnPhoto(); 23 | await inventoryItemPage.header.cartCounter(1); 24 | await inventoryItemPage.addToCart(); 25 | await inventoryItemPage.header.cartCounter(2); 26 | }); 27 | 28 | test('Should remove from cart and update cart counter', async () => { 29 | await inventoryPage.items[PROD_INDEX_IN_CART].openDetailsClickingOnPhoto(); 30 | await inventoryItemInPage.header.cartCounter(1); 31 | await inventoryItemInPage.removeFromCart(); 32 | await inventoryItemInPage.header.cartCounter(0); 33 | }); 34 | 35 | test('Should return to Inventory page', async () => { 36 | await inventoryPage.items[PROD_INDEX_IN_CART].openDetailsClickingOnPhoto(); 37 | await inventoryItemInPage.backToProducts(); 38 | await inventoryPage.toBe(); 39 | }) 40 | }); -------------------------------------------------------------------------------- /tests/e2e/menu.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import { IProduct, SortOptions } from "../fixtures/models"; 5 | import data from '../fixtures/data/products.json'; 6 | 7 | let inventoryPage: InventoryPage; 8 | 9 | test.beforeEach( async ({page}) => { 10 | inventoryPage = new InventoryPage(page); 11 | await inventoryPage.visit(); 12 | }); 13 | test.describe('Menu features', () => { 14 | test('Should open and close menu', async () => { 15 | await inventoryPage.header.menu.isClosed(); 16 | await inventoryPage.header.openMenu(); 17 | await inventoryPage.header.menu.isOpened(); 18 | await inventoryPage.header.menu.close(); 19 | await inventoryPage.header.menu.isClosed(); 20 | }); 21 | 22 | test('Should reset app state', async () => { 23 | await inventoryPage.items[0].addToCart(); 24 | await inventoryPage.header.cartCounter(1); 25 | await inventoryPage.header.openMenu(); 26 | await inventoryPage.header.menu.reset(); 27 | await inventoryPage.header.cartCounter(0); 28 | }) 29 | 30 | test('Should visit about', async () => { 31 | await inventoryPage.header.openMenu(); 32 | await inventoryPage.header.menu.visitAbout(); 33 | }) 34 | 35 | test('Should visit All Items', async () => { 36 | await inventoryPage.header.visitCart(); 37 | await inventoryPage.header.openMenu(); 38 | await inventoryPage.header.menu.visitItems(); 39 | await inventoryPage.toBe(); 40 | }) 41 | 42 | test('Should logout', async ({page}) => { 43 | await inventoryPage.header.openMenu(); 44 | await inventoryPage.header.menu.visitLogout(); 45 | await expect(page).toHaveURL('/'); 46 | }) 47 | }) -------------------------------------------------------------------------------- /tests/pages/CheckoutCompletePage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright-core"; 2 | import { BasePage } from "./BasePage"; 3 | import { Locator, expect } from "@playwright/test"; 4 | import { HeaderComponent } from "../components/HeaderComponent"; 5 | import { FooterComponent } from "../components/FooterComponent"; 6 | 7 | export class CheckoutCompletePage extends BasePage{ 8 | 9 | readonly locatorHeaderTitle: Locator; 10 | readonly locatorCheckoutImg: Locator; 11 | readonly locatorHeading: Locator; 12 | readonly locatorDescription: Locator; 13 | readonly locatorHomeButton: Locator; 14 | readonly header: HeaderComponent; 15 | readonly footer: FooterComponent; 16 | 17 | constructor(page: Page) { 18 | super(page, "/checkout-complete"); 19 | 20 | this.header = new HeaderComponent(this.page); 21 | this.footer = new FooterComponent(this.page); 22 | 23 | this.locatorHeaderTitle = this.page.locator('#header_container').getByText('Checkout: Complete!'); 24 | this.locatorCheckoutImg = this.page.getByRole('img', { name: 'Pony Express' }); 25 | this.locatorHeading = this.page.getByRole('heading', { name: 'Thank you for your order!' }); 26 | this.locatorDescription = this.page.getByText('Your order has been'); 27 | this.locatorHomeButton = this.page.getByRole('button', {name: 'Back Home'}); 28 | } 29 | 30 | validateDefaultLayout = async () => { 31 | await expect(this.locatorCheckoutImg).toBeVisible(); 32 | await expect(this.locatorHeading).toBeVisible(); 33 | await expect(this.locatorDescription).toBeVisible(); 34 | await expect(this.locatorHomeButton).toBeVisible(); 35 | 36 | await this.footer.validateDefaultLayout(); 37 | await this.header.validateDefaultLayout(); 38 | } 39 | 40 | backHome = async () => { 41 | this.locatorHomeButton.click(); 42 | this.page.waitForLoadState('networkidle'); 43 | } 44 | } -------------------------------------------------------------------------------- /tests/pages/LoginPage.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | import { LoginFormComponent } from "../components/LoginFormComponent"; 4 | import { BasePage } from "./BasePage"; 5 | 6 | export class LoginPage extends BasePage{ 7 | 8 | readonly formComponent: LoginFormComponent; 9 | readonly locatorUserNameList: Locator; 10 | readonly locatorPasswordList: Locator; 11 | 12 | readonly acceptedUsernames = 'Accepted usernames are:standard_userlocked_out_userproblem_userperformance_glitch_usererror_uservisual_user'; 13 | readonly passwordForAll = 'Password for all users:secret_sauce'; 14 | 15 | constructor(page: Page) { 16 | super(page, "/"); 17 | 18 | this.formComponent = new LoginFormComponent(this.page); 19 | this.locatorUserNameList = this.page.locator('#login_credentials'); 20 | this.locatorPasswordList = this.page.locator('div.login_password'); 21 | 22 | } 23 | 24 | validateDefaultLayout = async () => { 25 | await expect(this.page.getByText('Swag Labs')).toBeVisible(); 26 | 27 | await expect(this.locatorUserNameList).toBeVisible(); 28 | await expect(this.locatorUserNameList).toContainText(this.acceptedUsernames); 29 | await expect(this.locatorPasswordList).toBeVisible(); 30 | await expect(this.locatorPasswordList).toContainText(this.passwordForAll); 31 | 32 | await this.formComponent.validateDefaultLayout(); 33 | } 34 | 35 | validateErrorLayout = async () => { 36 | await expect(this.page.getByText('Swag Labs')).toBeVisible(); 37 | 38 | await expect(this.locatorUserNameList).toBeVisible(); 39 | await expect(this.locatorUserNameList).toContainText(this.acceptedUsernames); 40 | await expect(this.locatorPasswordList).toBeVisible(); 41 | await expect(this.locatorPasswordList).toContainText(this.passwordForAll); 42 | 43 | await this.formComponent.validateErrorLayout(); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/components/FooterComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | 4 | export class FooterComponent { 5 | readonly page: Page; 6 | readonly locatorFooter: Locator; 7 | readonly locatorCopyRights: Locator; 8 | readonly locatorTwitterLink: Locator; 9 | readonly locatorFacebookLink: Locator; 10 | readonly locatorLinkedinLink: Locator; 11 | 12 | constructor(page: Page){ 13 | this.page = page; 14 | 15 | this.locatorFooter = this.page.locator('footer'); 16 | this.locatorCopyRights = this.locatorFooter.getByText('© 2023 Sauce Labs. All Rights'); 17 | this.locatorTwitterLink = this.locatorFooter.getByRole('link', { name: 'Twitter' }); 18 | this.locatorFacebookLink = this.locatorFooter.getByRole('link', { name: 'Facebook' }); 19 | this.locatorLinkedinLink = this.locatorFooter.getByRole('link', { name: 'Linkedin' }); 20 | } 21 | 22 | validateDefaultLayout = async () => { 23 | await expect(this.locatorCopyRights).toBeVisible(); 24 | await expect(this.locatorTwitterLink).toBeVisible(); 25 | await expect(this.locatorFacebookLink).toBeVisible(); 26 | await expect(this.locatorLinkedinLink).toBeVisible(); 27 | } 28 | 29 | visitTwitter = async () => { 30 | const page2Promise = this.page.waitForEvent('popup'); 31 | await this.locatorTwitterLink.click(); 32 | const page2 = await page2Promise; 33 | await expect(page2).toHaveURL('twitter.com') 34 | } 35 | 36 | visitFacebook = async () => { 37 | const page2Promise = this.page.waitForEvent('popup'); 38 | await this.locatorFacebookLink.click(); 39 | const page2 = await page2Promise; 40 | await expect(page2).toHaveURL('facebook.com') 41 | } 42 | 43 | visitLinkedin = async () => { 44 | const page2Promise = this.page.waitForEvent('popup'); 45 | await this.locatorLinkedinLink.click(); 46 | const page2 = await page2Promise; 47 | await expect(page2).toHaveURL('linkedin.com') 48 | } 49 | } -------------------------------------------------------------------------------- /tests/components/CartItemComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { ICartProduct, IProduct } from "../fixtures/models"; 3 | import { expect } from "playwright/test"; 4 | 5 | export class CartItemComponent { 6 | readonly page: Page; 7 | readonly locatorQuantity: Locator; 8 | readonly locatorProductTitle: Locator; 9 | readonly locatorProductDescription: Locator; 10 | readonly locatorProductPrice: Locator; 11 | readonly locatorRemoveButton: Locator; 12 | 13 | constructor(page: Page, cart: ICartProduct){ 14 | this.page = page; 15 | 16 | this.locatorProductTitle = this.page.getByRole('link', {name: `${cart.product.name}`}); 17 | 18 | const container = this.page.locator('.cart_item', {hasText: `${cart.product.name}`}); 19 | this.locatorProductDescription = container.getByText(cart.product.description); 20 | this.locatorQuantity = container.locator('.cart_quantity', {hasText: `${cart.qty}`}); 21 | this.locatorProductPrice = container.locator('.inventory_item_price', {hasText: `$${cart.product.price}`}); 22 | this.locatorRemoveButton = container.getByRole('button', { name: 'Remove'}); 23 | 24 | } 25 | 26 | validateDefaultUX = async () => { 27 | await expect(this.locatorQuantity).toBeVisible(); 28 | await expect(this.locatorProductTitle).toBeVisible(); 29 | await expect(this.locatorProductDescription).toBeVisible(); 30 | await expect(this.locatorProductPrice).toBeVisible(); 31 | await expect(this.locatorRemoveButton).toBeVisible(); 32 | } 33 | 34 | remove = async () => { 35 | await this.locatorRemoveButton.click(); 36 | } 37 | 38 | isRemoved = async () => { 39 | await expect(this.locatorQuantity).not.toBeVisible(); 40 | await expect(this.locatorProductTitle).not.toBeVisible(); 41 | await expect(this.locatorProductDescription).not.toBeVisible(); 42 | await expect(this.locatorProductPrice).not.toBeVisible(); 43 | await expect(this.locatorRemoveButton).not.toBeVisible(); 44 | } 45 | } -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [ main ] 5 | tags-ignore: [ 'update-snapshots' ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_run: 9 | workflows: [Playwright Update Snapshots] 10 | types: 11 | - completed 12 | 13 | # Give the workflow permission to deploy the Pages site 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | jobs: 24 | test: 25 | timeout-minutes: 60 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 18 32 | - name: Install dependencies 33 | run: npm ci 34 | - name: Install Playwright Browsers 35 | run: npx playwright install --with-deps 36 | - name: Run Playwright tests 37 | run: npx playwright test 38 | - uses: actions/upload-artifact@v3 39 | if: always() 40 | with: 41 | name: playwright-report 42 | path: playwright-report/ 43 | retention-days: 30 44 | # Configure Pages 45 | - name: Configure GitHub Pages 46 | if: always() 47 | uses: actions/configure-pages@v3 48 | # Upload the report as a pages artifact 49 | - name: Upload Pages Artifact 50 | if: always() 51 | uses: actions/upload-pages-artifact@v2 52 | with: 53 | path: playwright-report/ 54 | # Deploy the Pages site 55 | deploy: 56 | if: always() 57 | needs: test 58 | environment: 59 | name: github-pages 60 | url: ${{ steps.deployment.outputs.page_url }} 61 | runs-on: ubuntu-latest 62 | steps: 63 | - name: Deploy to GitHub Pages 64 | uses: actions/deploy-pages@v2 65 | -------------------------------------------------------------------------------- /tests/pages/InventoryPage.ts: -------------------------------------------------------------------------------- 1 | import { Locator, expect } from "@playwright/test"; 2 | import { Page } from "playwright-core"; 3 | import { BasePage } from "./BasePage"; 4 | import { ProductItemComponent } from "../components/ProductItemComponent"; 5 | import data from '../fixtures/data/products.json'; 6 | import { FooterComponent } from "../components/FooterComponent"; 7 | import { HeaderComponent } from "../components/HeaderComponent"; 8 | import { SortOptions } from "../fixtures/models"; 9 | 10 | export class InventoryPage extends BasePage{ 11 | 12 | readonly locatorHeaderTitle: Locator; 13 | readonly locatorHeaderFilterSelect: Locator; 14 | readonly items: ProductItemComponent[]; 15 | readonly footer: FooterComponent; 16 | readonly header: HeaderComponent; 17 | 18 | constructor(page: Page) { 19 | super(page, "/inventory.html"); 20 | 21 | this.locatorHeaderTitle = this.page.locator('#header_container').getByText('Products'); 22 | this.locatorHeaderFilterSelect = this.page.locator('[data-test="product_sort_container"]'); 23 | 24 | this.footer = new FooterComponent(this.page); 25 | this.header = new HeaderComponent(this.page); 26 | this.items = new Array(); 27 | 28 | for(const prod of data.az){ 29 | this.items.push(new ProductItemComponent(this.page, prod)) 30 | } 31 | } 32 | 33 | validateDefaultLayout = async () => { 34 | await expect(this.locatorHeaderFilterSelect).toBeVisible(); 35 | await expect(this.locatorHeaderTitle).toBeVisible(); 36 | 37 | for(const item of this.items){ 38 | await item.validateDefaultUX (); 39 | } 40 | 41 | await this.header.validateDefaultLayout(); 42 | await this.footer.validateDefaultLayout(); 43 | } 44 | 45 | sort = async (sort: SortOptions) => { 46 | await this.locatorHeaderFilterSelect.selectOption(sort.toString()); 47 | 48 | for(let i = 0; i < (data[sort.toString()] as []).length; i++){ 49 | await expect(this.page.locator(`div:nth-child(${i+1}) > .inventory_item_description`, { hasText: data[sort.toString()][i].name})).toBeVisible(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/components/FormComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { ErrorComponent } from "./ErrorComponent"; 3 | import { expect } from "@playwright/test"; 4 | 5 | export abstract class FormComponent { 6 | readonly page: Page; 7 | readonly locatorForm: Locator; 8 | readonly locatorSubmitButton: Locator; 9 | readonly errorComponent: ErrorComponent; 10 | 11 | constructor(page: Page, errorMessagePrefix = 'Epic sadface:'){ 12 | this.page = page; 13 | this.errorComponent = new ErrorComponent(this.page, errorMessagePrefix); 14 | 15 | this.locatorForm = this.page.locator('form'); 16 | this.locatorSubmitButton = this.locatorForm.locator('input[type=submit]'); 17 | } 18 | 19 | abstract fill(data: any); 20 | 21 | submit = async () => { 22 | await this.locatorSubmitButton.click(); 23 | } 24 | 25 | /** 26 | * When form is invalid, at least one input field should have error styles and a error icon 27 | * ```html 28 | * 29 | * 30 | * ``` 31 | */ 32 | async validateErrorLayout(){ 33 | await expect(this.locatorForm).toBeVisible(); 34 | await expect(this.locatorForm.locator('input:not([type=submit]).error')).not.toHaveCount(0); 35 | await expect(this.locatorForm.locator('svg.error_icon')).not.toHaveCount(0); 36 | await expect(this.locatorSubmitButton).toBeVisible(); 37 | 38 | await this.errorComponent.isVisible(); 39 | } 40 | 41 | /** 42 | * When form is valid, no input field should have error styles not error icon 43 | */ 44 | async validateDefaultLayout() { 45 | await expect(this.locatorForm).toBeVisible(); 46 | await expect(this.locatorForm.locator('input:not([type=submit]).error')).toHaveCount(0); 47 | await expect(this.locatorForm.locator('svg.error_icon')).toHaveCount(0); 48 | await this.errorComponent.isNotVisible(); 49 | await expect(this.locatorSubmitButton).toBeVisible(); 50 | } 51 | 52 | validateFormFieldsAreNotOverlapped = async () => { 53 | const locatorFormFields = await this.locatorForm.locator('input').all(); 54 | 55 | for(const field of locatorFormFields){ 56 | await expect(field.evaluate(node => (node as HTMLElement).offsetParent !== null)).toBe(true); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /tests/auth.setup.ts: -------------------------------------------------------------------------------- 1 | import {test as setup} from "@playwright/test"; 2 | import { LoginPage } from "./pages/LoginPage"; 3 | import data from "./fixtures/data/users.json"; 4 | import { STORAGE_STATE_DEFAULT_USER, STORAGE_STATE_PERFORMANCE_USER, STORAGE_STATE_ERROR_USER, STORAGE_STATE_PROBLEM_USER, STORAGE_STATE_VISUAL_USER } from "../playwright.config"; 5 | let loginPage: LoginPage; 6 | 7 | setup('authenticate default user', async({page}) => { 8 | loginPage = new LoginPage(page); 9 | await loginPage.visit(); 10 | await loginPage.formComponent.fill(data.success); 11 | await loginPage.formComponent.submit(); 12 | await page.waitForLoadState('networkidle'); 13 | 14 | await page.context().storageState({path: STORAGE_STATE_DEFAULT_USER}); 15 | }) 16 | 17 | // setup('authenticate visual user', async({page}) => { 18 | // loginPage = new LoginPage(page); 19 | // await loginPage.visit(); 20 | // await loginPage.formComponent.fill(data.visual_user); 21 | // await loginPage.formComponent.submit(); 22 | // await page.waitForLoadState('networkidle'); 23 | 24 | // await page.context().storageState({path: STORAGE_STATE_VISUAL_USER}); 25 | // }) 26 | 27 | // setup('authenticate problem user', async({page}) => { 28 | // loginPage = new LoginPage(page); 29 | // await loginPage.visit(); 30 | // await loginPage.formComponent.fill(data.problem_user); 31 | // await loginPage.formComponent.submit(); 32 | // await page.waitForLoadState('networkidle'); 33 | 34 | // await page.context().storageState({path: STORAGE_STATE_PROBLEM_USER}); 35 | // }) 36 | 37 | setup('authenticate error user', async({page}) => { 38 | loginPage = new LoginPage(page); 39 | await loginPage.visit(); 40 | await loginPage.formComponent.fill(data.error_user); 41 | await loginPage.formComponent.submit(); 42 | await page.waitForLoadState('networkidle'); 43 | 44 | await page.context().storageState({path: STORAGE_STATE_ERROR_USER}); 45 | }) 46 | 47 | // setup('authenticate performance user', async({page}) => { 48 | // loginPage = new LoginPage(page); 49 | // await loginPage.visit(); 50 | // await loginPage.formComponent.fill(data.performance_user); 51 | // await loginPage.formComponent.submit(); 52 | // await page.waitForLoadState('networkidle'); 53 | 54 | // await page.context().storageState({path: STORAGE_STATE_PERFORMANCE_USER}); 55 | // }) -------------------------------------------------------------------------------- /tests/e2e/inventory.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import { IProduct, SortOptions } from "../fixtures/models"; 5 | import data from '../fixtures/data/products.json'; 6 | 7 | let inventoryPage: InventoryPage; 8 | 9 | test.beforeEach( async ({page}) => { 10 | inventoryPage = new InventoryPage(page); 11 | await inventoryPage.visit(); 12 | }); 13 | 14 | test.describe('Inventory features', () => { 15 | test('Should list by Name (Z to A)', async () => { 16 | await inventoryPage.sort(SortOptions.ZA); 17 | }); 18 | test('Should list by Name (A to Z)', async () => { 19 | await inventoryPage.sort(SortOptions.AZ); 20 | }); 21 | test('Should list by Price (Low to High)', async () => { 22 | await inventoryPage.sort(SortOptions.LoHi); 23 | }); 24 | test('Should list by Price (High to Low)', async () => { 25 | await inventoryPage.sort(SortOptions.HiLo); 26 | }); 27 | 28 | test('Should add to cart and then remove from cart', async () => { 29 | const index = Math.floor(Math.random()*inventoryPage.items.length); 30 | await inventoryPage.items[index].addToCart(); 31 | await inventoryPage.items[index].removeFromCart(); 32 | }); 33 | 34 | test('Should increase cart counter while adding product and decrease while removing product', async () => { 35 | await inventoryPage.header.cartCounter(0); 36 | 37 | for(let i = 0; i < inventoryPage.items.length; i++){ 38 | await inventoryPage.items[i].addToCart(); 39 | await inventoryPage.header.cartCounter(i + 1); 40 | } 41 | 42 | for(let i = inventoryPage.items.length -1; i >= 0; i--){ 43 | await inventoryPage.items[i].removeFromCart(); 44 | await inventoryPage.header.cartCounter(i); 45 | } 46 | }); 47 | 48 | test('Should open product details from image', async ({page}) => { 49 | const index = Math.floor(Math.random()*inventoryPage.items.length); 50 | await inventoryPage.items[index].openDetailsClickingOnPhoto(); 51 | await new InventoryItemPage(page, data.az[0]).toBe(); 52 | }); 53 | 54 | test('Should open product details from title', async ({page}) => { 55 | const index = Math.floor(Math.random()*inventoryPage.items.length); 56 | await inventoryPage.items[index].openDetailsClickingOnTitle(); 57 | await new InventoryItemPage(page, data.az[0]).toBe(); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/components/ProductItemComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page, expect } from "@playwright/test"; 2 | import { IProduct } from "../fixtures/models"; 3 | 4 | export class ProductItemComponent { 5 | readonly page: Page; 6 | 7 | readonly locatorProductPhoto: Locator; 8 | readonly locatorProductTitle: Locator; 9 | readonly locatorProductDescription: Locator; 10 | readonly locatorProductPrice: Locator; 11 | readonly locatorAddCartButton: Locator; 12 | readonly locatorRemoveCartButton: Locator; 13 | 14 | constructor(page: Page, product: IProduct){ 15 | this.page = page; 16 | 17 | this.locatorProductTitle = this.page.locator('a[id$=title_link]', {hasText: product.name}); 18 | const locatorContainer = this.page.locator('#inventory_container .inventory_item', { has: this.locatorProductTitle}); 19 | this.locatorProductPhoto = locatorContainer.getByAltText(product.name); 20 | this.locatorProductDescription = locatorContainer.getByText(product.description); 21 | this.locatorProductPrice = locatorContainer.getByText(`$${product.price}`); 22 | this.locatorAddCartButton = locatorContainer.getByRole('button', {name: 'Add to cart'}); 23 | this.locatorRemoveCartButton = locatorContainer.getByRole('button', {name: 'Remove'}); 24 | } 25 | 26 | validateDefaultUX = async () => { 27 | await expect(this.locatorProductPhoto).toBeVisible(); 28 | await expect(this.locatorProductTitle).toBeVisible(); 29 | await expect(this.locatorProductDescription).toBeVisible(); 30 | await expect(this.locatorProductPrice).toBeVisible(); 31 | await expect(this.locatorAddCartButton).toBeVisible(); 32 | } 33 | 34 | validateRemoveUX = async () => { 35 | await expect(this.locatorAddCartButton).not.toBeVisible(); 36 | await expect(this.locatorRemoveCartButton).toBeVisible(); 37 | } 38 | 39 | validateAddLayout =async () => { 40 | await expect(this.locatorRemoveCartButton).not.toBeVisible(); 41 | await expect(this.locatorAddCartButton).toBeVisible(); 42 | } 43 | 44 | addToCart = async () => { 45 | await this.locatorAddCartButton.click(); 46 | } 47 | 48 | removeFromCart = async () => { 49 | await this.locatorRemoveCartButton.click(); 50 | } 51 | 52 | openDetailsClickingOnPhoto = async () => { 53 | await this.locatorProductPhoto.click(); 54 | await this.page.waitForLoadState('networkidle'); 55 | } 56 | 57 | openDetailsClickingOnTitle = async () => { 58 | await this.locatorProductTitle.click(); 59 | await this.page.waitForLoadState('networkidle'); 60 | } 61 | } -------------------------------------------------------------------------------- /tests/components/LoginFormComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page, expect } from "@playwright/test"; 2 | import { FormComponent } from "./FormComponent"; 3 | import { ILogin } from "../fixtures/models"; 4 | 5 | export class LoginFormComponent extends FormComponent { 6 | 7 | readonly locatorUsernameInput: Locator; 8 | readonly locatorPasswordInput: Locator; 9 | 10 | constructor(page: Page){ 11 | super(page); 12 | 13 | this.locatorUsernameInput = this.locatorForm.locator('input[data-test="username"]'); 14 | this.locatorPasswordInput = this.locatorForm.locator('input[data-test="password"]'); 15 | 16 | } 17 | fill = async (data: ILogin) => { 18 | await this.locatorUsernameInput.fill(data.username); 19 | await this.locatorPasswordInput.fill(data.password); 20 | } 21 | 22 | override validateErrorLayout = async (): Promise => { 23 | await expect(this.locatorSubmitButton).toHaveText('Login'); 24 | 25 | await expect(this.locatorUsernameInput).toBeVisible(); 26 | await expect(this.locatorUsernameInput).toHaveAttribute('type', 'text'); 27 | await expect(this.locatorUsernameInput).toHaveAttribute('placeholder', 'Username', {ignoreCase: false}); 28 | await expect(this.locatorUsernameInput).toHaveClass(/\berror\b/); 29 | 30 | await expect(this.locatorPasswordInput).toBeVisible(); 31 | await expect(this.locatorPasswordInput).toHaveAttribute('type', 'password'); 32 | await expect(this.locatorPasswordInput).toHaveAttribute('placeholder', 'Password', {ignoreCase: false}); 33 | await expect(this.locatorPasswordInput).toHaveClass(/\berror\b/); 34 | 35 | super.validateErrorLayout(); 36 | } 37 | 38 | override validateDefaultLayout = async (): Promise => { 39 | await expect(this.locatorSubmitButton).toHaveText('Login'); 40 | 41 | await expect(this.locatorUsernameInput).toBeVisible(); 42 | await expect(this.locatorUsernameInput).toHaveAttribute('type', 'text'); 43 | await expect(this.locatorUsernameInput).toHaveAttribute('placeholder', 'Username', {ignoreCase: false}); 44 | await expect(this.locatorUsernameInput).not.toHaveClass(/\berror\b/); 45 | 46 | 47 | await expect(this.locatorPasswordInput).toBeVisible(); 48 | await expect(this.locatorPasswordInput).toHaveAttribute('type', 'password'); 49 | await expect(this.locatorPasswordInput).toHaveAttribute('placeholder', 'Password', {ignoreCase: false}); 50 | await expect(this.locatorPasswordInput).not.toHaveClass(/\berror\b/); 51 | 52 | return super.validateDefaultLayout(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /tests/components/MenuComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { expect } from "playwright/test"; 3 | 4 | export class MenuComponent { 5 | readonly page: Page; 6 | readonly locatorCloseMenuButton: Locator; 7 | readonly locatorAllItemsLink: Locator; 8 | readonly locatorAboutLink: Locator; 9 | readonly locatorLogoutLink: Locator; 10 | readonly locatorResetLink: Locator; 11 | readonly locatorMenuNav: Locator; 12 | 13 | constructor(page:Page){ 14 | this.page = page; 15 | this.locatorCloseMenuButton = this.page.getByRole('button', { name: 'Close Menu' }); 16 | this.locatorMenuNav = this.page.getByRole('navigation'); 17 | this.locatorAllItemsLink = this.locatorMenuNav.getByRole('link', { name: 'All Items' }); 18 | this.locatorAboutLink = this.locatorMenuNav.getByRole('link', { name: 'About' }); 19 | this.locatorResetLink = this.locatorMenuNav.getByRole('link', { name: 'Reset App State' }); 20 | this.locatorLogoutLink = this.locatorMenuNav.getByRole('link', { name: 'Logout' }); 21 | } 22 | validateDefaultLayout = async () => { 23 | await expect(this.locatorMenuNav).toBeVisible(); 24 | await expect(this.locatorCloseMenuButton).toBeVisible(); 25 | await expect(this.locatorAllItemsLink).toBeVisible(); 26 | await expect(this.locatorAboutLink).toBeVisible(); 27 | await expect(this.locatorResetLink).toBeVisible(); 28 | await expect(this.locatorLogoutLink).toBeVisible(); 29 | }; 30 | 31 | visitItems = async () => { 32 | await this.locatorAllItemsLink.click(); 33 | await this.page.waitForLoadState('networkidle'); 34 | } 35 | 36 | visitAbout = async () => { 37 | await this.locatorAboutLink.click(); 38 | await this.page.waitForLoadState('networkidle'); 39 | await expect(this.page).toHaveURL('https://saucelabs.com/'); 40 | } 41 | 42 | reset = async () => { 43 | await this.locatorResetLink.click(); 44 | } 45 | 46 | visitLogout = async () => { 47 | await this.locatorLogoutLink.click(); 48 | await this.page.waitForLoadState('networkidle'); 49 | } 50 | 51 | close = async () => { 52 | await this.locatorCloseMenuButton.click(); 53 | } 54 | 55 | isClosed = async () => { 56 | await expect(this.locatorMenuNav).toBeHidden(); 57 | await expect(this.locatorCloseMenuButton).toBeHidden(); 58 | await expect(this.locatorAllItemsLink).toBeHidden(); 59 | await expect(this.locatorAboutLink).toBeHidden(); 60 | await expect(this.locatorResetLink).toBeHidden(); 61 | await expect(this.locatorLogoutLink).toBeHidden(); 62 | } 63 | 64 | isOpened = async () => { 65 | await expect(this.locatorMenuNav).toBeVisible(); 66 | await expect(this.locatorCloseMenuButton).toBeVisible(); 67 | await expect(this.locatorAllItemsLink).toBeVisible(); 68 | await expect(this.locatorAboutLink).toBeVisible(); 69 | await expect(this.locatorResetLink).toBeVisible(); 70 | await expect(this.locatorLogoutLink).toBeVisible(); 71 | } 72 | } -------------------------------------------------------------------------------- /tests/ui/checkout.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { CartPage } from "../pages/CartPage"; 3 | import { InventoryPage } from "../pages/InventoryPage"; 4 | import data from '../fixtures/data/cart.json'; 5 | import dataCheckout from '../fixtures/data/checkout.json'; 6 | import { CheckoutStepOnePage } from "../pages/CheckoutStepOnePage"; 7 | import { CheckoutStepTwoPage } from "../pages/CheckoutStepTwoPage"; 8 | import { CheckoutCompletePage } from "../pages/CheckoutCompletePage"; 9 | 10 | let inventoryPage: InventoryPage; 11 | let cartPage: CartPage; 12 | let checkoutOnePage: CheckoutStepOnePage; 13 | let checkoutTwoPage: CheckoutStepTwoPage; 14 | let checkoutCompletePage: CheckoutCompletePage; 15 | 16 | test.beforeEach(async ({page})=> { 17 | inventoryPage = new InventoryPage(page); 18 | checkoutOnePage = new CheckoutStepOnePage(page); 19 | checkoutTwoPage = new CheckoutStepTwoPage(page, [data[1], data[2], data[3]]); 20 | checkoutCompletePage = new CheckoutCompletePage(page); 21 | 22 | cartPage = new CartPage(page, [data[1], data[2], data[3]]); 23 | 24 | await inventoryPage.visit(); 25 | await inventoryPage.items[0].addToCart(); 26 | await inventoryPage.items[2].addToCart(); 27 | await inventoryPage.items[4].addToCart(); 28 | await inventoryPage.header.visitCart(); 29 | await cartPage.checkout(); 30 | }) 31 | 32 | test.describe('Checkout Step One UI', () => { 33 | 34 | test('Viewport should resize correctly @responsive', async () => { 35 | await checkoutOnePage.validateViewportResize(); 36 | }) 37 | 38 | test('Should have default UI', async () => { 39 | await checkoutOnePage.validateDefaultLayout(); 40 | }) 41 | 42 | test('Should have Error UI on invalid form', async () => { 43 | await checkoutOnePage.form.submit(); 44 | await checkoutOnePage.validateErrorLayout(); 45 | }) 46 | 47 | test('Viewport should resize correctly', async () => { 48 | await checkoutOnePage.validateViewportResize(); 49 | }) 50 | }); 51 | 52 | test.describe('Checkout Step Two UI', () => { 53 | test('Viewport should resize correctly @responsive', async () => { 54 | await checkoutTwoPage.validateViewportResize(); 55 | }) 56 | 57 | test('Should have default UI', async () => { 58 | await checkoutOnePage.form.fill(dataCheckout.success); 59 | await checkoutOnePage.form.submit(); 60 | await checkoutTwoPage.validateDefaultLayout(); 61 | }) 62 | 63 | test('Viewport should resize correctly', async () => { 64 | await checkoutTwoPage.validateViewportResize(); 65 | }) 66 | }); 67 | 68 | test.describe('Checkout Complete UI', () => { 69 | test('Viewport should resize correctly @responsive', async () => { 70 | await checkoutCompletePage.validateViewportResize(); 71 | }) 72 | 73 | test('Should have default UI', async () => { 74 | await checkoutOnePage.form.fill(dataCheckout.success); 75 | await checkoutOnePage.form.submit(); 76 | await checkoutTwoPage.finish(); 77 | await checkoutCompletePage.validateDefaultLayout(); 78 | }) 79 | 80 | test('Viewport should resize correctly', async () => { 81 | await checkoutCompletePage.validateViewportResize(); 82 | }) 83 | }); -------------------------------------------------------------------------------- /tests/pages/CartPage.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page } from "playwright-core"; 2 | import { BasePage } from "./BasePage"; 3 | import { CartItemComponent } from "../components/CartItemComponent"; 4 | import { ICartProduct, IProduct } from "../fixtures/models"; 5 | import { expect } from "playwright/test"; 6 | import { FooterComponent } from "../components/FooterComponent"; 7 | import { HeaderComponent } from "../components/HeaderComponent"; 8 | 9 | export class CartPage extends BasePage{ 10 | 11 | readonly products: CartItemComponent[]; 12 | readonly locatorYourCart: Locator; 13 | readonly locatorQty: Locator; 14 | readonly locatorDescription: Locator; 15 | readonly locatorContinueShoppingButton: Locator; 16 | readonly locatorCheckoutButton: Locator; 17 | readonly footer: FooterComponent; 18 | readonly header: HeaderComponent; 19 | 20 | constructor(page: Page, products: ICartProduct[]) { 21 | super(page, "/cart"); 22 | 23 | this.footer = new FooterComponent(page); 24 | this.header = new HeaderComponent(page); 25 | 26 | this.products = new Array(); 27 | for(const product of products){ 28 | this.products.push(new CartItemComponent(this.page, product)); 29 | } 30 | 31 | this.locatorYourCart = this.page.getByText('Your Cart'); 32 | this.locatorQty = this.page.getByText('Qty'); 33 | this.locatorDescription = this.page.getByText('Description'); 34 | this.locatorContinueShoppingButton = this.page.getByRole('button', { name: 'Continue Shopping'}); 35 | this.locatorCheckoutButton = this.page.getByRole('button', { name: 'Checkout'}); 36 | } 37 | 38 | validateDefaultLayout = async () => { 39 | await expect(this.locatorYourCart).toBeVisible(); 40 | await expect(this.locatorQty).toBeVisible(); 41 | await expect(this.locatorDescription).toBeVisible(); 42 | await expect(this.locatorContinueShoppingButton).toBeVisible(); 43 | await expect(this.locatorCheckoutButton).toBeVisible(); 44 | 45 | for(const product of this.products){ 46 | await product.validateDefaultUX(); 47 | } 48 | } 49 | 50 | validateEmptyLayout = async () => { 51 | await expect(this.locatorYourCart).toBeVisible(); 52 | await expect(this.locatorQty).toBeVisible(); 53 | await expect(this.locatorDescription).toBeVisible(); 54 | await expect(this.locatorContinueShoppingButton).toBeVisible(); 55 | await expect(this.locatorCheckoutButton).toBeVisible(); 56 | } 57 | 58 | // remove = async (product: IProduct) => { 59 | // const prod = this.products.find(p => product.name === p.product.name); 60 | // if(prod !== undefined){ 61 | // const cartItem = new CartItemComponent(this.page, prod); 62 | // await cartItem.remove(); 63 | // await cartItem.isRemoved(); 64 | // } 65 | // else{ 66 | // throw new Error(); 67 | // } 68 | 69 | // } 70 | 71 | checkout = async () => { 72 | await this.locatorCheckoutButton.click(); 73 | await this.page.waitForLoadState('networkidle'); 74 | } 75 | 76 | continueShopping = async () => { 77 | await this.locatorContinueShoppingButton.click(); 78 | await this.page.waitForLoadState('networkidle'); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/pages/InventoryItemPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright-core"; 2 | import { BasePage } from "./BasePage"; 3 | import { Locator, expect } from "@playwright/test"; 4 | import { IProduct } from "../fixtures/models"; 5 | import { FooterComponent } from "../components/FooterComponent"; 6 | import { HeaderComponent } from "../components/HeaderComponent"; 7 | 8 | export class InventoryItemPage extends BasePage{ 9 | 10 | readonly locatorHeaderBackButton: Locator; 11 | readonly locatorProductPhoto: Locator; 12 | readonly locatorProductTitle: Locator; 13 | readonly locatorProductDescription: Locator; 14 | readonly locatorProductPrice: Locator; 15 | readonly locatorAddCartButton: Locator; 16 | readonly locatorRemoveCartButton: Locator; 17 | readonly footer: FooterComponent; 18 | readonly header: HeaderComponent; 19 | 20 | constructor(page: Page, product: IProduct) { 21 | super(page, "/inventory-item"); 22 | 23 | this.locatorHeaderBackButton = this.page.locator('[data-test="back-to-products"]'); 24 | 25 | this.locatorProductTitle = this.page.getByText(product.name, { exact: true }); 26 | this.locatorProductPhoto = this.page.getByAltText(product.name); 27 | this.locatorProductDescription = this.page.getByText(product.description); 28 | this.locatorProductPrice = this.page.getByText(`$${product.price}`); 29 | this.locatorAddCartButton = this.page.getByRole('button', {name: 'Add to cart'}); 30 | this.locatorRemoveCartButton = this.page.getByRole('button', {name: 'Remove'}); 31 | 32 | this.footer = new FooterComponent(this.page); 33 | this.header = new HeaderComponent(this.page); 34 | } 35 | 36 | validateDefaultLayout = async () => { 37 | await expect(this.locatorProductPhoto).toBeVisible(); 38 | await expect(this.locatorProductTitle).toBeVisible(); 39 | await expect(this.locatorProductDescription).toBeVisible(); 40 | await expect(this.locatorProductPrice).toBeVisible(); 41 | await expect(this.locatorAddCartButton).toBeVisible(); 42 | await expect(this.locatorRemoveCartButton).not.toBeVisible(); 43 | await expect(this.locatorHeaderBackButton).toBeVisible(); 44 | this.footer.validateDefaultLayout(); 45 | this.header.validateDefaultLayout(); 46 | } 47 | 48 | validateRemoveLayout = async () => { 49 | await expect(this.locatorProductPhoto).toBeVisible(); 50 | await expect(this.locatorProductTitle).toBeVisible(); 51 | await expect(this.locatorProductDescription).toBeVisible(); 52 | await expect(this.locatorProductPrice).toBeVisible(); 53 | await expect(this.locatorRemoveCartButton).toBeVisible(); 54 | await expect(this.locatorAddCartButton).not.toBeVisible(); 55 | await expect(this.locatorHeaderBackButton).toBeVisible(); 56 | 57 | this.footer.validateDefaultLayout(); 58 | this.header.validateDefaultLayout(); 59 | } 60 | 61 | addToCart = async () => { 62 | await this.locatorAddCartButton.click(); 63 | } 64 | 65 | removeFromCart = async () => { 66 | await this.locatorRemoveCartButton.click(); 67 | } 68 | 69 | backToProducts = async () => { 70 | await this.locatorHeaderBackButton.click(); 71 | await this.page.waitForLoadState('networkidle'); 72 | } 73 | } -------------------------------------------------------------------------------- /tests/components/CheckoutFormComponent.ts: -------------------------------------------------------------------------------- 1 | import { Locator, Page, expect } from "@playwright/test"; 2 | import { FormComponent } from "./FormComponent"; 3 | import { IUser } from "../fixtures/models"; 4 | 5 | export class CheckoutFormComponent extends FormComponent { 6 | 7 | readonly locatorFirstNameInput: Locator; 8 | readonly locatorLastNameInput: Locator; 9 | readonly locatorZipCodeInput: Locator; 10 | 11 | constructor(page: Page){ 12 | super(page, 'Error:'); 13 | 14 | this.locatorFirstNameInput = this.locatorForm.locator('[data-test="firstName"]'); 15 | this.locatorLastNameInput = this.locatorForm.locator('[data-test="lastName"]'); 16 | this.locatorZipCodeInput = this.locatorForm.locator('[data-test="postalCode"]'); 17 | 18 | } 19 | 20 | fill = async (data: IUser) => { 21 | await this.locatorFirstNameInput.fill(data.firstName); 22 | await this.locatorLastNameInput.fill(data.lastName); 23 | await this.locatorZipCodeInput.fill(data.zipCode); 24 | } 25 | 26 | override validateErrorLayout = async (): Promise => { 27 | await expect(this.locatorSubmitButton).toHaveText('Continue'); 28 | 29 | await expect(this.locatorFirstNameInput).toBeVisible(); 30 | await expect(this.locatorFirstNameInput).toHaveAttribute('type', 'text'); 31 | await expect(this.locatorFirstNameInput).toHaveAttribute('placeholder', 'First Name'); 32 | await expect(this.locatorFirstNameInput).toHaveClass(/\berror\b/); 33 | 34 | await expect(this.locatorLastNameInput).toBeVisible(); 35 | await expect(this.locatorLastNameInput).toHaveAttribute('type', 'text'); 36 | await expect(this.locatorLastNameInput).toHaveAttribute('placeholder', 'Last Name'); 37 | await expect(this.locatorLastNameInput).toHaveClass(/\berror\b/); 38 | 39 | await expect(this.locatorZipCodeInput).toBeVisible(); 40 | await expect(this.locatorZipCodeInput).toHaveAttribute('type', 'text'); 41 | await expect(this.locatorZipCodeInput).toHaveAttribute('placeholder', 'Zip/Postal Code'); 42 | await expect(this.locatorZipCodeInput).toHaveClass(/\berror\b/); 43 | 44 | return super.validateErrorLayout(); 45 | } 46 | 47 | override validateDefaultLayout = async (): Promise => { 48 | await expect(this.locatorSubmitButton).toHaveText('Continue'); 49 | 50 | await expect(this.locatorFirstNameInput).toBeVisible(); 51 | await expect(this.locatorFirstNameInput).toHaveAttribute('type', 'text'); 52 | await expect(this.locatorFirstNameInput).toHaveAttribute('placeholder', 'First Name'); 53 | await expect(this.locatorFirstNameInput).not.toHaveClass(/\berror\b/); 54 | 55 | await expect(this.locatorLastNameInput).toBeVisible(); 56 | await expect(this.locatorLastNameInput).toHaveAttribute('type', 'text'); 57 | await expect(this.locatorLastNameInput).toHaveAttribute('placeholder', 'Last Name'); 58 | await expect(this.locatorLastNameInput).not.toHaveClass(/\berror\b/); 59 | 60 | await expect(this.locatorZipCodeInput).toBeVisible(); 61 | await expect(this.locatorZipCodeInput).toHaveAttribute('type', 'text'); 62 | await expect(this.locatorZipCodeInput).toHaveAttribute('placeholder', 'Zip/Postal Code'); 63 | await expect(this.locatorZipCodeInput).not.toHaveClass(/\berror\b/); 64 | 65 | return super.validateDefaultLayout(); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /tests/e2e/login.spec.ts: -------------------------------------------------------------------------------- 1 | // import { test } from '@playwright/test'; 2 | // import { LoginPage } from '../pages/LoginPage'; 3 | // import data from '../fixtures/data/users.json'; 4 | // import { InventoryPage } from '../pages/InventoryPage'; 5 | // let loginPage: LoginPage; 6 | 7 | // test.use({ storageState: { cookies: [], origins: [] } }); 8 | 9 | // test.beforeEach(async ({page}) => { 10 | // loginPage = new LoginPage(page); 11 | // await loginPage.visit(); 12 | // await loginPage.toBe(); 13 | // }) 14 | 15 | // test.describe('login feature', () => { 16 | // test('should login', async ({ page }) => { 17 | // await loginPage.formComponent.fill(data.success); 18 | // await loginPage.formComponent.submit() 19 | // await new InventoryPage(page).toBe(); 20 | // }); 21 | 22 | // test('should login with visual user', async ({ page }) => { 23 | // await loginPage.formComponent.fill(data.visual_user); 24 | // await loginPage.formComponent.submit() 25 | // await new InventoryPage(page).toBe(); 26 | // }); 27 | 28 | // test('should login with error user', async ({ page }) => { 29 | // await loginPage.formComponent.fill(data.error_user); 30 | // await loginPage.formComponent.submit() 31 | // await new InventoryPage(page).toBe(); 32 | // }); 33 | 34 | // test('should login with problem user', async ({ page }) => { 35 | // await loginPage.formComponent.fill(data.problem_user); 36 | // await loginPage.formComponent.submit() 37 | // await new InventoryPage(page).toBe(); 38 | // }); 39 | 40 | // test('should login with performance user', async ({ page }) => { 41 | // await loginPage.formComponent.fill(data.performance_user); 42 | // await loginPage.formComponent.submit() 43 | // await new InventoryPage(page).toBe(); 44 | // }); 45 | 46 | // test('should not login with wrong credentials', async({page}) => { 47 | // await loginPage.formComponent.fill(data.bad_credential); 48 | // await loginPage.formComponent.submit() 49 | // await loginPage.validateErrorUX(); 50 | // await loginPage.formComponent.errorComponent.hasMessage('Username and password do not match any user in this service'); 51 | // }) 52 | 53 | // test('should not login with locked user', async({page}) => { 54 | // await loginPage.formComponent.fill(data.locked_user); 55 | // await loginPage.formComponent.submit() 56 | // await loginPage.validateErrorUX(); 57 | // await loginPage.formComponent.errorComponent.hasMessage('Sorry, this user has been locked out.'); 58 | // }) 59 | 60 | // test('should not login with empty user', async({page}) => { 61 | // await loginPage.formComponent.fill(data.empty_user); 62 | // await loginPage.formComponent.submit() 63 | // await loginPage.validateErrorUX(); 64 | // await loginPage.formComponent.errorComponent.hasMessage('Username is required'); 65 | // }) 66 | 67 | // test('should not login with empty password', async({page}) => { 68 | // await loginPage.formComponent.fill(data.empty_password); 69 | // await loginPage.formComponent.submit() 70 | // await loginPage.validateErrorUX(); 71 | // await loginPage.formComponent.errorComponent.hasMessage('Password is required'); 72 | // }) 73 | 74 | // test('should not login with empty credential', async({page}) => { 75 | // await loginPage.formComponent.fill(data.empty_credential); 76 | // await loginPage.formComponent.submit() 77 | // await loginPage.validateErrorUX(); 78 | // await loginPage.formComponent.errorComponent.hasMessage('Username is required'); 79 | // }) 80 | // }) -------------------------------------------------------------------------------- /tests/e2e/checkoutOne.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { CartPage } from "../pages/CartPage"; 3 | import { InventoryPage } from "../pages/InventoryPage"; 4 | import data from '../fixtures/data/cart.json'; 5 | import dataCheckout from '../fixtures/data/checkout.json'; 6 | import { CheckoutStepOnePage } from "../pages/CheckoutStepOnePage"; 7 | import { CheckoutStepTwoPage } from "../pages/CheckoutStepTwoPage"; 8 | import { CheckoutCompletePage } from "../pages/CheckoutCompletePage"; 9 | 10 | let inventoryPage: InventoryPage; 11 | let cartPage: CartPage; 12 | let checkoutOnePage: CheckoutStepOnePage; 13 | let checkoutTwoPage: CheckoutStepTwoPage; 14 | let checkoutCompletePage: CheckoutCompletePage; 15 | 16 | test.beforeEach(async ({page})=> { 17 | inventoryPage = new InventoryPage(page); 18 | checkoutOnePage = new CheckoutStepOnePage(page); 19 | checkoutTwoPage = new CheckoutStepTwoPage(page, [data[1], data[2], data[3]]); 20 | checkoutCompletePage = new CheckoutCompletePage(page); 21 | 22 | cartPage = new CartPage(page, [data[1], data[2], data[3]]); 23 | 24 | await inventoryPage.visit(); 25 | await inventoryPage.items[0].addToCart(); 26 | await inventoryPage.items[2].addToCart(); 27 | await inventoryPage.items[4].addToCart(); 28 | await inventoryPage.header.visitCart(); 29 | await cartPage.checkout(); 30 | }) 31 | 32 | test.describe('Checkout features', () => { 33 | test('Should checkout and go back home', async ({page}) => { 34 | await checkoutOnePage.form.fill(dataCheckout.success); 35 | await checkoutOnePage.form.submit(); 36 | await checkoutTwoPage.toBe(); 37 | 38 | const totalItem = data[1].product.price + data[2].product.price + data[3].product.price; 39 | const taxItem = Math.round((totalItem * 0.08) * 100) / 100; 40 | const total = totalItem + taxItem; 41 | 42 | console.log(totalItem); 43 | console.log(taxItem); 44 | console.log(total); 45 | 46 | await checkoutTwoPage.itemPrice(totalItem.toString()); 47 | await checkoutTwoPage.taxPrice(taxItem.toString()); 48 | await checkoutTwoPage.totalPrice(total.toString()); 49 | 50 | await checkoutTwoPage.finish(); 51 | await checkoutCompletePage.toBe(); 52 | await checkoutCompletePage.backHome(); 53 | await inventoryPage.toBe(); 54 | }); 55 | 56 | test('Should not checkout with empty form', async () => { 57 | await checkoutOnePage.form.fill(dataCheckout.empty); 58 | await checkoutOnePage.form.submit(); 59 | await checkoutOnePage.validateErrorLayout(); 60 | await checkoutOnePage.form.errorComponent.hasMessage('First Name is required'); 61 | }); 62 | 63 | test('Should not checkout without first name', async () => { 64 | await checkoutOnePage.form.fill(dataCheckout.empty_firstName); 65 | await checkoutOnePage.form.submit(); 66 | await checkoutOnePage.validateErrorLayout(); 67 | await checkoutOnePage.form.errorComponent.hasMessage('First Name is required'); 68 | }); 69 | 70 | test('Should not checkout without last name', async () => { 71 | await checkoutOnePage.form.fill(dataCheckout.empty_lastName); 72 | await checkoutOnePage.form.submit(); 73 | await checkoutOnePage.validateErrorLayout(); 74 | await checkoutOnePage.form.errorComponent.hasMessage('Last Name is required'); 75 | }); 76 | 77 | test('Should not checkout without zip code', async () => { 78 | await checkoutOnePage.form.fill(dataCheckout.empty_zipCode); 79 | await checkoutOnePage.form.submit(); 80 | await checkoutOnePage.validateErrorLayout(); 81 | await checkoutOnePage.form.errorComponent.hasMessage('Postal Code is required'); 82 | }); 83 | 84 | test('Should cancel checkout on step one', async () => { 85 | await checkoutOnePage.cancel(); 86 | await cartPage.toBe(); 87 | }) 88 | 89 | test('Should cancel checkout on step two', async ({page}) => { 90 | await checkoutOnePage.form.fill(dataCheckout.success); 91 | await checkoutOnePage.form.submit(); 92 | await checkoutTwoPage.toBe(); 93 | await checkoutTwoPage.cancel(); 94 | await inventoryPage.toBe(); 95 | }); 96 | }) -------------------------------------------------------------------------------- /tests/visual/visual.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from "@playwright/test"; 2 | import { InventoryPage } from "../pages/InventoryPage"; 3 | import { InventoryItemPage } from "../pages/InventoryItemPage"; 4 | import { CartPage } from "../pages/CartPage"; 5 | import { CheckoutStepOnePage } from "../pages/CheckoutStepOnePage"; 6 | import { CheckoutStepTwoPage } from "../pages/CheckoutStepTwoPage"; 7 | import { CheckoutCompletePage } from "../pages/CheckoutCompletePage"; 8 | import { LoginPage } from "../pages/LoginPage"; 9 | 10 | import cart from '../fixtures/data/cart.json'; 11 | import products from '../fixtures/data/products.json'; 12 | import checkout from '../fixtures/data/checkout.json'; 13 | import user from '../fixtures/data/users.json'; 14 | import { ILogin } from "../fixtures/models"; 15 | 16 | let loginPage: LoginPage; 17 | let inventoryPage: InventoryPage; 18 | let inventoryItemPage: InventoryItemPage; 19 | let cartPage: CartPage; 20 | let checkoutOnePage: CheckoutStepOnePage; 21 | let checkoutTwoPage: CheckoutStepTwoPage; 22 | let checkoutCompletePage: CheckoutCompletePage; 23 | 24 | test.beforeEach(async ({page}) => { 25 | loginPage = new LoginPage(page); 26 | inventoryPage = new InventoryPage(page); 27 | inventoryItemPage = new InventoryItemPage(page, products.az[0]); 28 | checkoutOnePage = new CheckoutStepOnePage(page); 29 | checkoutTwoPage = new CheckoutStepTwoPage(page, [cart[1], cart[2], cart[3]]); 30 | checkoutCompletePage = new CheckoutCompletePage(page); 31 | cartPage = new CartPage(page, [cart[1], cart[2], cart[3]]); 32 | }) 33 | 34 | test.describe.serial('visual test', ()=>{ 35 | 36 | test('Should pass with standard user @create-snapshots', async ({page}) => { 37 | await visualTest(page, user.success); 38 | }) 39 | 40 | test('Should fail with visual user', async ({page}) => { 41 | await visualTest(page, user.visual_user); 42 | }) 43 | 44 | }) 45 | 46 | const visualTest = async (page: Page, user: ILogin) => { 47 | await loginPage.visit(); 48 | await expect.soft(page).toHaveScreenshot('loginPage.png', { 49 | fullPage: true 50 | }); 51 | 52 | await loginPage.formComponent.submit(); 53 | 54 | await expect.soft(page).toHaveScreenshot('loginPage-error.png', { 55 | fullPage: true 56 | }); 57 | 58 | await loginPage.formComponent.fill(user); 59 | await loginPage.formComponent.submit(); 60 | 61 | await expect.soft(page).toHaveScreenshot('inventoryPage.png', { 62 | fullPage: true 63 | }); 64 | 65 | await inventoryPage.header.visitCart(); 66 | 67 | await expect.soft(page).toHaveScreenshot('cartPage-empty.png', { 68 | fullPage: true 69 | }); 70 | 71 | await cartPage.continueShopping(); 72 | 73 | await inventoryPage.items[0].addToCart(); 74 | await inventoryPage.items[2].addToCart(); 75 | await inventoryPage.items[4].addToCart(); 76 | 77 | await expect.soft(page).toHaveScreenshot('inventoryPage-added.png', { 78 | fullPage: true 79 | }); 80 | 81 | await inventoryPage.items[0].openDetailsClickingOnTitle(); 82 | 83 | await expect.soft(page).toHaveScreenshot('inventoryItemPage-added.png', { 84 | fullPage: true 85 | }); 86 | 87 | await inventoryItemPage.backToProducts(); 88 | await inventoryPage.items[1].openDetailsClickingOnTitle(); 89 | 90 | await expect.soft(page).toHaveScreenshot('inventoryItemPage.png', { 91 | fullPage: true 92 | }); 93 | 94 | await inventoryPage.header.openMenu(); 95 | 96 | await expect.soft(page).toHaveScreenshot('menu.png', { 97 | fullPage: true 98 | }); 99 | 100 | await inventoryPage.header.menu.close(); 101 | await inventoryPage.header.visitCart(); 102 | 103 | await expect.soft(page).toHaveScreenshot('cartPage.png', { 104 | fullPage: true 105 | }); 106 | 107 | await cartPage.checkout(); 108 | 109 | await expect.soft(page).toHaveScreenshot('checkoutPage-one.png', { 110 | fullPage: true 111 | }); 112 | 113 | await checkoutOnePage.form.fill(checkout.success); 114 | await checkoutOnePage.form.submit(); 115 | 116 | await expect.soft(page).toHaveScreenshot('checkoutPage-two.png', { 117 | fullPage: true 118 | }); 119 | 120 | await checkoutTwoPage.finish(); 121 | 122 | await expect.soft(page).toHaveScreenshot('checkoutPage-complete.png', { 123 | fullPage: true 124 | }); 125 | } -------------------------------------------------------------------------------- /tests/pages/CheckoutStepTwoPage.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "playwright-core"; 2 | import { BasePage } from "./BasePage"; 3 | import {CheckoutItemComponent} from "../components/CheckoutItemComponent"; 4 | import { Locator, expect } from "@playwright/test"; 5 | import { FooterComponent } from "../components/FooterComponent"; 6 | import { HeaderComponent } from "../components/HeaderComponent"; 7 | import { ICartProduct } from "../fixtures/models"; 8 | 9 | export class CheckoutStepTwoPage extends BasePage{ 10 | 11 | readonly products: CheckoutItemComponent[]; 12 | readonly locatorHeaderTitle: Locator; 13 | readonly locatorQty: Locator; 14 | readonly locatorDescription: Locator; 15 | readonly locatorCancelButton: Locator; 16 | readonly locatorFinishButton: Locator; 17 | readonly footer: FooterComponent; 18 | readonly header: HeaderComponent; 19 | readonly locatorPaymentInfoLabel: Locator; 20 | readonly locatorPaymentInfoValue: Locator; 21 | readonly locatorShippingInfoLabel: Locator; 22 | readonly locatorShippingInfoValue: Locator; 23 | readonly locatorPriceLabel: Locator; 24 | readonly locatorPriceItemValue: Locator; 25 | readonly locatorPriceTaxValue: Locator; 26 | readonly locatorTotal: Locator; 27 | 28 | constructor(page: Page, products: ICartProduct[]) { 29 | super(page, "/checkout-step-two"); 30 | 31 | this.footer = new FooterComponent(page); 32 | this.header = new HeaderComponent(page); 33 | 34 | this.products = new Array(); 35 | for(const product of products){ 36 | this.products.push(new CheckoutItemComponent(this.page, product)); 37 | } 38 | 39 | this.locatorHeaderTitle = this.page.locator('#header_container').getByText('Checkout: Overview'); 40 | this.locatorQty = this.page.getByText('Qty'); 41 | this.locatorDescription = this.page.getByText('Description'); 42 | this.locatorPaymentInfoLabel = this.page.getByText('Payment Information', { exact: true}); 43 | this.locatorPaymentInfoValue = this.page.getByText(/\bSauceCard\b\s#\d{5}/); 44 | this.locatorShippingInfoLabel = this.page.getByText('Shipping Information', { exact: true}); 45 | this.locatorShippingInfoValue = this.page.getByText('Free Pony Express Delivery!', { exact: true}); 46 | this.locatorPriceLabel = this.page.getByText('Price Total', { exact: true}); 47 | this.locatorPriceItemValue = this.page.getByText(/\bItem total\b\:\s\$\d*.\d{2}/); 48 | this.locatorPriceTaxValue = this.page.getByText(/\bTax\b\:\s\$\d*.\d{2}/); 49 | this.locatorTotal = this.page.getByText(/\bTotal\b\:\s\$\d*.\d{2}/); 50 | this.locatorCancelButton = this.page.getByRole('button', { name: 'Cancel'}); 51 | this.locatorFinishButton = this.page.getByRole('button', { name: 'Finish'}); 52 | } 53 | 54 | validateDefaultLayout = async () => { 55 | await expect(this.locatorHeaderTitle).toBeVisible(); 56 | await expect(this.locatorQty).toBeVisible(); 57 | await expect(this.locatorDescription).toBeVisible(); 58 | await expect(this.locatorPaymentInfoLabel).toBeVisible(); 59 | await expect(this.locatorPaymentInfoValue).toBeVisible(); 60 | await expect(this.locatorShippingInfoLabel).toBeVisible(); 61 | await expect(this.locatorShippingInfoValue).toBeVisible(); 62 | await expect(this.locatorPriceLabel).toBeVisible(); 63 | await expect(this.locatorPriceItemValue).toBeVisible(); 64 | await expect(this.locatorPriceTaxValue).toBeVisible(); 65 | await expect(this.locatorTotal).toBeVisible(); 66 | await expect(this.locatorCancelButton).toBeVisible(); 67 | await expect(this.locatorFinishButton).toBeVisible(); 68 | 69 | await this.footer.validateDefaultLayout(); 70 | await this.header.validateDefaultLayout(); 71 | 72 | for(const product of this.products){ 73 | await product.validateDefaultUX(); 74 | } 75 | 76 | } 77 | 78 | itemPrice = async (value: string) => { 79 | await expect(this.locatorPriceItemValue).toContainText(value); 80 | } 81 | 82 | taxPrice = async (value: string) => { 83 | await expect(this.locatorPriceTaxValue).toContainText(value); 84 | } 85 | 86 | totalPrice = async (value: string) => { 87 | await expect(this.locatorTotal).toContainText(value); 88 | } 89 | 90 | cancel = async () => { 91 | await this.locatorCancelButton.click(); 92 | await this.page.waitForLoadState('networkidle'); 93 | } 94 | 95 | finish = async () => { 96 | await this.locatorFinishButton.click(); 97 | await this.page.waitForLoadState('networkidle'); 98 | } 99 | } -------------------------------------------------------------------------------- /ui.md: -------------------------------------------------------------------------------- 1 | # User Interface (UI) Tests 2 | 3 | This set of UI tests, located in the `ui/` and `visual/` directories, is designed to thoroughly evaluate various aspects of the user interface, including layout, usability, responsiveness, and visual elements. 4 | 5 | ## UI Test Scenarios 6 | 7 | ### login.spec.ts 8 | 9 | 1. **Viewport should resize correctly** 10 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 11 | 12 | 1. **Should Have Default Layout on Start** 13 | - Description: Confirm that the login page exhibits the expected default layout upon initial loading. 14 | - Steps: Access the login page and verify the layout conforms to the default design. 15 | 16 | 1. **Should Have Error Layout on Invalid Form** 17 | - Description: Validate that the login page switches to an error-specific layout when an invalid form is submitted. 18 | - Steps: Submit a login form with incorrect credentials and ensure the error layout is displayed. 19 | 20 | ### inventory.spec.ts 21 | 22 | 1. **Viewport should resize correctly** 23 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 24 | 25 | 1. **Should Have Default Layout** 26 | - Description: Verify that the inventory page maintains the default layout upon initial access. 27 | - Steps: Navigate to the inventory page and confirm adherence to the default design. 28 | 29 | 1. **Should Alternate Add To Cart and Remove Buttons** 30 | - Description: Confirm that the "Add to Cart" and "Remove" buttons on the inventory page alternate their visibility and layout when items are added or removed from the cart. 31 | - Steps: Add an item to the cart and verify the change in button layout. Then, remove the item and confirm the reversal of button layout. 32 | 33 | 1. **Viewport should resize correctly** 34 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 35 | 36 | ### inventoryItem.spec.ts 37 | 38 | 1. **Viewport should resize correctly** 39 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 40 | 41 | 1. **Should Have Default Layout When Accessing a Product That is Not Added** 42 | - Description: Ensure that the product-specific page displays the default layout when accessing a product that has not been added to the cart. 43 | - Steps: Access the detailed view of a product not in the cart and validate the default layout. 44 | 45 | 1. **Should Have Remove Layout When Accessing a Product That is Added** 46 | - Description: Confirm that the product-specific page displays a layout with the option to remove the product when accessing an item already added to the cart. 47 | - Steps: Add a product to the cart, then access its detailed view and verify the presence of the remove-specific layout. 48 | 49 | 1. **Viewport should resize correctly** 50 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 51 | 52 | ### cart.spec.ts 53 | 54 | 1. **Viewport should resize correctly** 55 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 56 | 57 | 1. **Should Have Default Layout** 58 | - Description: Confirm that the cart page displays the default layout upon navigation. 59 | - Steps: Navigate to the cart and validate the default layout. 60 | 61 | 1. **Viewport should resize correctly** 62 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 63 | 64 | ### checkout.spec.ts 65 | 66 | 1. **Viewport should resize correctly** 67 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 68 | 69 | 1. **Checkout Step One Should Have Default Layout** 70 | - Description: Validate that the first step of the checkout process displays the default layout. 71 | - Steps: Initiate the checkout process and verify the layout on the first step. 72 | 73 | 1. **Checkout Step One Should Have Error Layout on Invalid Form** 74 | - Description: Confirm that the first step of the checkout process switches to an error-specific layout when an invalid form is submitted. 75 | - Steps: Submit an incomplete or invalid form during the first step of checkout and ensure the error layout is displayed. 76 | 77 | 1. **Checkout Step Two Should Have Default Layout** 78 | - Description: Validate that the second step of the checkout process displays the default layout. 79 | - Steps: Progress to the second step of checkout and verify the layout. 80 | 81 | 1. **Checkout Complete Should Have Default Layout** 82 | - Description: Confirm that the completion page of the checkout process exhibits the default layout. 83 | - Steps: Complete the checkout process and validate the default layout on the completion page. 84 | 85 | 1. **Viewport should resize correctly** 86 | - Description: Simulate resizing the browser window and check if the page responds correctly, dynamically adapting to the window size. 87 | 88 | ### menu.spec.ts 89 | 90 | 1. **Menu, Header, Footer Should Have Default Layout** 91 | - Description: Validate that the menu, header, and footer components maintain the default layout. 92 | - Steps: Navigate through menu options and access different sections to ensure the default layout is consistent. 93 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | import path from 'path'; 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | export const STORAGE_STATE_DEFAULT_USER = path.join(__dirname, 'playwright/.auth/user.json'); 10 | export const STORAGE_STATE_PROBLEM_USER = path.join(__dirname, 'playwright/.auth/problemUser.json'); 11 | export const STORAGE_STATE_VISUAL_USER = path.join(__dirname, 'playwright/.auth/visualUser.json'); 12 | export const STORAGE_STATE_ERROR_USER = path.join(__dirname, 'playwright/.auth/errorUser.json'); 13 | export const STORAGE_STATE_PERFORMANCE_USER = path.join(__dirname, 'playwright/.auth/performanceUser.json'); 14 | 15 | /** 16 | * See https://playwright.dev/docs/test-configuration. 17 | */ 18 | export default defineConfig({ 19 | testDir: './tests', 20 | /* Run tests in files in parallel */ 21 | fullyParallel: true, 22 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 23 | forbidOnly: !!process.env.CI, 24 | /* Retry on CI only */ 25 | retries: process.env.CI ? 2 : 0, 26 | /* Opt out of parallel tests on CI. */ 27 | workers: process.env.CI ? 1 : undefined, 28 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 29 | reporter: 'html', 30 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 31 | use: { 32 | /* Base URL to use in actions like `await page.goto('/')`. */ 33 | baseURL: 'https://www.saucedemo.com', 34 | 35 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 36 | trace: 'on', 37 | }, 38 | 39 | /* Configure projects for major browsers */ 40 | projects: [ 41 | { 42 | name: 'setup', 43 | testMatch: '**/*.setup.ts' 44 | }, 45 | { 46 | name: 'e2e login', 47 | testMatch: 'login.spec.ts', 48 | use: { 49 | ...devices['Desktop Chrome'], 50 | } 51 | }, 52 | { 53 | name: 'e2e standard user', 54 | testMatch: '**/e2e/*.spec.ts', 55 | testIgnore: '**/e2e/login.spec.ts', 56 | use: { 57 | ...devices['Desktop Chrome'], 58 | storageState: STORAGE_STATE_DEFAULT_USER 59 | }, 60 | dependencies: ['setup'], 61 | }, 62 | // { 63 | // name: 'e2e problem user', 64 | // testMatch: '**/e2e/*.spec.ts', 65 | // testIgnore: '**/e2e/login.spec.ts', 66 | // use: { 67 | // ...devices['Desktop Chrome'], 68 | // storageState: STORAGE_STATE_PROBLEM_USER 69 | // }, 70 | // dependencies: ['setup'], 71 | // }, 72 | // { 73 | // name: 'e2e visual user', 74 | // testMatch: '**/e2e/*.spec.ts', 75 | // testIgnore: '**/e2e/login.spec.ts', 76 | // use: { 77 | // ...devices['Desktop Chrome'], 78 | // storageState: STORAGE_STATE_VISUAL_USER 79 | // }, 80 | // dependencies: ['setup'], 81 | // }, 82 | { 83 | name: 'e2e error user', 84 | testMatch: '**/e2e/*.spec.ts', 85 | testIgnore: '**/e2e/login.spec.ts', 86 | use: { 87 | ...devices['Desktop Chrome'], 88 | storageState: STORAGE_STATE_ERROR_USER 89 | }, 90 | dependencies: ['setup'], 91 | }, 92 | // { 93 | // name: 'e2e performance user', 94 | // testMatch: '**/e2e/*.spec.ts', 95 | // testIgnore: '**/e2e/login.spec.ts', 96 | // use: { 97 | // ...devices['Desktop Chrome'], 98 | // storageState: STORAGE_STATE_PERFORMANCE_USER 99 | // }, 100 | // dependencies: ['setup'], 101 | // }, 102 | { 103 | name: 'visual on Desktop Chrome', 104 | testMatch: '**/visual/*.spec.ts', 105 | use: { 106 | ...devices['Desktop Chrome'], 107 | } 108 | }, 109 | { 110 | name: 'visual on Pixel 5', 111 | testMatch: '**/visual/*.spec.ts', 112 | use: { 113 | ...devices['Pixel 5'], 114 | } 115 | }, 116 | { 117 | name: 'visual on iPhone 12', 118 | testMatch: '**/visual/*.spec.ts', 119 | use: { 120 | ...devices['iPhone 12'], 121 | } 122 | }, 123 | /* UI Test */ 124 | { 125 | name: 'UI on Desktop Chrome', 126 | testMatch: '**/ui/*.spec.ts', 127 | use: { 128 | ...devices['Desktop Chrome'], 129 | storageState: STORAGE_STATE_DEFAULT_USER 130 | }, 131 | dependencies: ['setup'], 132 | }, 133 | { 134 | name: 'UI on Desktop Firefox', 135 | testMatch: '**/ui/*.spec.ts', 136 | use: { 137 | ...devices['Desktop Firefox'], 138 | storageState: STORAGE_STATE_DEFAULT_USER 139 | }, 140 | dependencies: ['setup'], 141 | }, 142 | { 143 | name: 'UI on Desktop Safari', 144 | testMatch: '**/ui/*.spec.ts', 145 | use: { 146 | ...devices['Desktop Safari'], 147 | storageState: STORAGE_STATE_DEFAULT_USER 148 | }, 149 | dependencies: ['setup'], 150 | }, 151 | { 152 | name: 'UI on Pixel 5', 153 | testMatch: '**/ui/*.spec.ts', 154 | grepInvert: /@responsive/, 155 | use: { 156 | ...devices['Pixel 5'], 157 | storageState: STORAGE_STATE_DEFAULT_USER 158 | }, 159 | dependencies: ['setup'], 160 | }, 161 | { 162 | name: 'UI on Iphone 12', 163 | testMatch: '**/ui/*.spec.ts', 164 | grepInvert: /@responsive/, 165 | use: { 166 | ...devices['iPhone 12'], 167 | storageState: STORAGE_STATE_DEFAULT_USER 168 | }, 169 | dependencies: ['setup'], 170 | } 171 | ], 172 | 173 | /* Run your local dev server before starting the tests */ 174 | // webServer: { 175 | // command: 'npm run start', 176 | // url: 'http://127.0.0.1:3000', 177 | // reuseExistingServer: !process.env.CI, 178 | // }, 179 | }); 180 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Playwright E2E Testing Project for saucedemo.com 2 | 3 | Hello! This is my E2E testing project for [saucedemo.com](www.saucedemo.com) using Playwright and TypeScript! I developed this project as part of my learning journey in test automation. 4 | 5 | The workflow, defined in the `.github/workflows/playwright.yml` file, executes Playwright tests on specified browsers and operating systems. After the test execution, Playwright generates a comprehensive report detailing both passed and failed scenarios. The GitHub Actions workflow is set up to automatically publish this Playwright test report to GitHub Pages. 6 | 7 | This integration ensures that the latest Playwright test results are readily available, providing transparency and visibility into the testing process. 8 | 9 | The generated report, showcasing insights into the results of the Playwright tests, is accessible at [ecureuill.github.io/saucedemo-playwright](http://ecureuill.github.io/saucedemo-playwright). 10 | 11 | ## Technologies Used 12 | 13 | - **Playwright**: A versatile library tailored for browser automation with a focus on reliability. 14 | - **TypeScript**: A statically typed superset of JavaScript. 15 | - **npm**: The package manager for JavaScript. 16 | 17 | ## Project Structure 18 | 19 | The project adopts a structured approach to maintainability and scalability. Here's an overview of the project's directory structure: 20 | 21 | ```bash 22 | saucedemo/ 23 | ├── tests/ 24 | │ ├── components/ 25 | │ │ ├── FooterComponent.ts 26 | │ │ └── ... 27 | │ ├── fixtures/ 28 | │ │ ├── model.ts 29 | │ │ └── data/ 30 | │ │ ├── cart.json 31 | │ │ ├── checkout.json 32 | │ │ ├── products.json 33 | │ │ └── users.json 34 | │ ├── pages/ 35 | │ │ ├── LoginPage.ts 36 | │ │ └── ... 37 | │ ├── e2e/ 38 | │ │ └── login.spec.ts 39 | │ │ └── ... 40 | │ ├── ui/ 41 | │ │ └── login.spec.ts 42 | │ │ └── ... 43 | │ ├── visual/ 44 | │ │ └── visual.spec.ts 45 | │ └── auth.setup.ts 46 | ├── playwright.config.js 47 | ├── node_modules/ 48 | ├── package.json 49 | ├── tsconfig.json 50 | └── ... 51 | ``` 52 | ## Types of Tests 53 | 54 | ### 1. End-to-End (E2E) Tests 55 | 56 | E2E tests, located in `e2e/`, aim to verify the complete functionality of the application. They cover scenarios such as user authentication, navigation, and interactions with different pages. 57 | 58 | Check all scenarios [here](/e2e.md). 59 | 60 | ### 2. Visual Snapshot Tests 61 | 62 | Visual snapshot tests, located in the `visual/` directory, play a crucial role in ensuring the visual consistency of the application across various test runs. Leveraging Playwright's powerful screenshot capabilities, these tests capture valid screenshots while navigating through the site in the context of a `standard_user`. Subsequently, the application's interface is tested against these baseline screenshots, this time with the user logged in as `visual_user`. This comparison process helps identify and verify any changes in the interface, ensuring a consistent and visually appealing user experience. 63 | 64 | ### 2. User Interface (UI) Tests 65 | 66 | The UI tests, located in the `ui/` directory, are designed to scrutinize essential aspects such as usability, layout integrity, responsiveness, and the visual aesthetics of the application. In this initial version, responsiveness is assessed by simulating window resizing, and the integrity of the layout is validated. In upcoming versions, the testing scope will be expanded to delve deeper into these initiated aspects, while also addressing additional facets for a more comprehensive evaluation of the application's user interface. 67 | 68 | Check all scenarios [here](/ui.md). 69 | 70 | 71 | ## Page Objects and Componentization 72 | 73 | The project embraces the Page Object pattern to encapsulate interactions with various pages of the SouceDemo website. Page objects are organized under the `pages/` directory, making the test code more readable, maintainable, and less prone to duplication. 74 | 75 | Example of a Page Object (`LoginPage.ts`): 76 | 77 | ```typescript 78 | export class LoginPage extends BasePage { 79 | // Implementation of page interactions 80 | } 81 | ``` 82 | 83 | The project also incorporates a `components/` directory to house reusable components like `FooterComponent.ts`, promoting a modular and efficient approach to building and maintaining tests. 84 | 85 | ## Fixtures and Data 86 | 87 | Data used in tests is organized under the `fixtures/` directory. This includes a `model.ts` file for defining data models and a `data/` directory housing various JSON files containing test data. 88 | 89 | 90 | ## State Maintenance 91 | 92 | To maintain the logged-in state of the website and access protected pages, the framework utilizes a `.storeState()` function located in `auth.setup.ts`. This function stores the authentication credentials in a secure location, allowing subsequent tests to seamlessly navigate through the application without requiring repetitive logins. 93 | 94 | ## Test Execution 95 | 96 | 1. Navigate to the project directory: 97 | 98 | ```bash 99 | cd saucedemo 100 | ``` 101 | 102 | 1. Install dependencies: 103 | 104 | ```bash 105 | npm install 106 | ``` 107 | 108 | 1. Run the tests: 109 | 110 | ```bash 111 | npx playwright test 112 | ``` 113 | 114 | ## HTML reporter 115 | 116 | HTML reporter produces a self-contained folder that contains report for the test run that can be served as a web page. 117 | 118 | ```bash 119 | npx playwright test --reporter=html 120 | ``` 121 | 122 | By default, HTML report is opened automatically if some of the tests failed. You can control this behavior via the open property in the Playwright config or the `PW_TEST_HTML_REPORT_OPEN` environmental variable. The possible values for that property are `always`, `never` and `on-failure` (default). 123 | 124 | ## Trace viewer 125 | 126 | You can open the saved trace using the Playwright CLI or in your browser on [trace.playwright.dev](trace.playwright.dev). Make sure to add the full path to where your trace.zip file is located. This should include the full path to your trace.zip file. 127 | 128 | ```bash 129 | npx playwright show-trace path/to/trace.zip 130 | ``` 131 | 132 | ## Playwright Configuration 133 | 134 | The `playwright.config.js` file configures the Playwright test environment. It specifies settings such as browsers to use, context options, and additional configurations needed for test execution. 135 | 136 | ## Contribution 137 | 138 | I'm learning, so if you find anything interesting or peculiar, I'd love to discuss! Feel free to open issues or propose improvements. 139 | -------------------------------------------------------------------------------- /tests/fixtures/data/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "az": [ 3 | { 4 | "name": "Sauce Labs Backpack", 5 | "description": "carry.allTheThings() with the sleek, streamlined Sly Pack that melds uncompromising style with unequaled laptop and tablet protection.", 6 | "price": 29.99 7 | }, 8 | { 9 | "name": "Sauce Labs Bike Light", 10 | "description": "A red light isn't the desired state in testing but it sure helps when riding your bike at night. Water-resistant with 3 lighting modes, 1 AAA battery included.", 11 | "price": 9.99 12 | }, 13 | { 14 | "name": "Sauce Labs Bolt T-Shirt", 15 | "description": "Get your testing superhero on with the Sauce Labs bolt T-shirt. From American Apparel, 100% ringspun combed cotton, heather gray with red bolt.", 16 | "price": 15.99 17 | }, 18 | { 19 | "name": "Sauce Labs Fleece Jacket", 20 | "description": "It's not every day that you come across a midweight quarter-zip fleece jacket capable of handling everything from a relaxing day outdoors to a busy day at the office.", 21 | "price": 49.99 22 | }, 23 | { 24 | "name": "Sauce Labs Onesie", 25 | "description": "Rib snap infant onesie for the junior automation engineer in development. Reinforced 3-snap bottom closure, two-needle hemmed sleeved and bottom won't unravel.", 26 | "price": 7.99 27 | }, 28 | { 29 | "name": "Test.allTheThings() T-Shirt (Red)", 30 | "description": "This classic Sauce Labs t-shirt is perfect to wear when cozying up to your keyboard to automate a few tests. Super-soft and comfy ringspun combed cotton.", 31 | "price": 15.99 32 | } 33 | ], 34 | "za": [ 35 | { 36 | "name": "Test.allTheThings() T-Shirt (Red)", 37 | "description": "This classic Sauce Labs t-shirt is perfect to wear when cozying up to your keyboard to automate a few tests. Super-soft and comfy ringspun combed cotton.", 38 | "price": 15.99 39 | }, 40 | { 41 | "name": "Sauce Labs Onesie", 42 | "description": "Rib snap infant onesie for the junior automation engineer in development. Reinforced 3-snap bottom closure, two-needle hemmed sleeved and bottom won't unravel.", 43 | "price": 7.99 44 | }, 45 | { 46 | "name": "Sauce Labs Fleece Jacket", 47 | "description": "It's not every day that you come across a midweight quarter-zip fleece jacket capable of handling everything from a relaxing day outdoors to a busy day at the office.", 48 | "price": 49.99 49 | }, 50 | { 51 | "name": "Sauce Labs Bolt T-Shirt", 52 | "description": "Get your testing superhero on with the Sauce Labs bolt T-shirt. From American Apparel, 100% ringspun combed cotton, heather gray with red bolt.", 53 | "price": 15.99 54 | }, 55 | { 56 | "name": "Sauce Labs Bike Light", 57 | "description": "A red light isn't the desired state in testing but it sure helps when riding your bike at night. Water-resistant with 3 lighting modes, 1 AAA battery included.", 58 | "price": 9.99 59 | }, 60 | { 61 | "name": "Sauce Labs Backpack", 62 | "description": "carry.allTheThings() with the sleek, streamlined Sly Pack that melds uncompromising style with unequaled laptop and tablet protection.", 63 | "price": 29.99 64 | } 65 | ], 66 | "lohi": [ 67 | { 68 | "name": "Sauce Labs Onesie", 69 | "description": "Rib snap infant onesie for the junior automation engineer in development. Reinforced 3-snap bottom closure, two-needle hemmed sleeved and bottom won't unravel.", 70 | "price": 7.99 71 | }, 72 | { 73 | "name": "Sauce Labs Bike Light", 74 | "description": "A red light isn't the desired state in testing but it sure helps when riding your bike at night. Water-resistant with 3 lighting modes, 1 AAA battery included.", 75 | "price": 9.99 76 | }, 77 | { 78 | "name": "Sauce Labs Bolt T-Shirt", 79 | "description": "Get your testing superhero on with the Sauce Labs bolt T-shirt. From American Apparel, 100% ringspun combed cotton, heather gray with red bolt.", 80 | "price": 15.99 81 | }, 82 | { 83 | "name": "Test.allTheThings() T-Shirt (Red)", 84 | "description": "This classic Sauce Labs t-shirt is perfect to wear when cozying up to your keyboard to automate a few tests. Super-soft and comfy ringspun combed cotton.", 85 | "price": 15.99 86 | }, 87 | { 88 | "name": "Sauce Labs Backpack", 89 | "description": "carry.allTheThings() with the sleek, streamlined Sly Pack that melds uncompromising style with unequaled laptop and tablet protection.", 90 | "price": 29.99 91 | }, 92 | { 93 | "name": "Sauce Labs Fleece Jacket", 94 | "description": "It's not every day that you come across a midweight quarter-zip fleece jacket capable of handling everything from a relaxing day outdoors to a busy day at the office.", 95 | "price": 49.99 96 | } 97 | ], 98 | "hilo": [ 99 | { 100 | "name": "Sauce Labs Fleece Jacket", 101 | "description": "It's not every day that you come across a midweight quarter-zip fleece jacket capable of handling everything from a relaxing day outdoors to a busy day at the office.", 102 | "price": 49.99 103 | }, 104 | { 105 | "name": "Sauce Labs Backpack", 106 | "description": "carry.allTheThings() with the sleek, streamlined Sly Pack that melds uncompromising style with unequaled laptop and tablet protection.", 107 | "price": 29.99 108 | }, 109 | { 110 | "name": "Sauce Labs Bolt T-Shirt", 111 | "description": "Get your testing superhero on with the Sauce Labs bolt T-shirt. From American Apparel, 100% ringspun combed cotton, heather gray with red bolt.", 112 | "price": 15.99 113 | }, 114 | { 115 | "name": "Test.allTheThings() T-Shirt (Red)", 116 | "description": "This classic Sauce Labs t-shirt is perfect to wear when cozying up to your keyboard to automate a few tests. Super-soft and comfy ringspun combed cotton.", 117 | "price": 15.99 118 | }, 119 | { 120 | "name": "Sauce Labs Bike Light", 121 | "description": "A red light isn't the desired state in testing but it sure helps when riding your bike at night. Water-resistant with 3 lighting modes, 1 AAA battery included.", 122 | "price": 9.99 123 | }, 124 | { 125 | "name": "Sauce Labs Onesie", 126 | "description": "Rib snap infant onesie for the junior automation engineer in development. Reinforced 3-snap bottom closure, two-needle hemmed sleeved and bottom won't unravel.", 127 | "price": 7.99 128 | } 129 | ] 130 | } -------------------------------------------------------------------------------- /e2e.md: -------------------------------------------------------------------------------- 1 | # End-to-End (E2E) Tests 2 | E2E tests, located in `e2e/`, aim to verify the complete functionality of the application. They cover scenarios such as user authentication, navigation, and interactions with different pages. 3 | 4 | ## E2E scenarios 5 | 6 | ### login.spec.ts 7 | 8 | 1. **Should Login with user "standard_user"** 9 | - Description: Verify that `standard_user` can successfully log in. 10 | - Steps: Navigate to the login page, enter valid credentials for standard_user, and confirm successful login. 11 | 12 | 2. **Should Login with user "visual_user"** 13 | - Description: Verify that `visual_user` can successfully log in. 14 | - Steps: Navigate to the login page, enter valid credentials for a visual user, and ensure successful login. 15 | 16 | 3. **Should Login with user "error_user"** 17 | - Description: Verify that `error_user` can successfully log in. 18 | - Steps: Attempt to log in with the credentials of a user prone to errors and validate the system's response. 19 | 20 | 4. **Should Login with user "problem_user"** 21 | - Description: Verify that `problem_user` can successfully log in. 22 | - Steps: Log in using the credentials of a user known for causing problems and analyze the system's response. 23 | 24 | 5. **Should Login with user "performance_user"** 25 | - Description: Validate the `performance_user`'s behavior when a known problematic user attempts to log in. 26 | - Steps: Log in using the credentials of a user specifically used for performance testing and assess the login speed. 27 | 28 | 6. **Should Not Login with Wrong Credentials** 29 | - Description: Confirm that the system rejects login attempts with incorrect credentials. 30 | - Steps: Attempt to log in with invalid username and password combinations and verify that the system denies access. 31 | 32 | 7. **Should Not Login with Locked User** 33 | - Description: Test the system's response when attempting to log in with a locked user account. 34 | - Steps: Use the credentials of a locked user account and check for the expected system behavior. 35 | 36 | 8. **Should Not Login with Empty User** 37 | - Description: Confirm that login is not possible with an empty username field. 38 | - Steps: Attempt to log in without entering a username and validate the system's response. 39 | 40 | 9. **Should Not Login with Empty Password** 41 | - Description: Verify that login is not allowed when the password field is left empty. 42 | - Steps: Attempt to log in without entering a password and confirm the expected system behavior. 43 | 44 | 10. **Should Not Login with Empty Credential** 45 | - Description: Test the system's response to an attempt to log in without entering any credentials. 46 | - Steps: Try to log in without providing any username or password and ensure that access is denied. 47 | 48 | ### inventory.spec.ts 49 | 50 | 1. **Should List by Name (Z to A)** 51 | - Description: Confirm that the inventory items are displayed in descending order by name. 52 | - Steps: Navigate to the inventory page, select the "Z to A" sorting option, and validate the item order. 53 | 54 | 2. **Should List by Name (A to Z)** 55 | - Description: Ensure that the inventory items are displayed in ascending order by name. 56 | - Steps: Navigate to the inventory page, select the "A to Z" sorting option, and verify the item order. 57 | 58 | 3. **Should List by Price (Low to High)** 59 | - Description: Validate that the inventory items are sorted from low to high based on price. 60 | - Steps: Navigate to the inventory page, select the "Low to High" sorting option, and check the item order. 61 | 62 | 4. **Should List by Price (High to Low)** 63 | - Description: Check that the inventory items are sorted from high to low based on price. 64 | - Steps: Navigate to the inventory page, select the "High to Low" sorting option, and confirm the item order. 65 | 66 | 5. **Should Add to Cart and Then Remove from Cart** 67 | - Description: Test the ability to add an item to the cart and subsequently remove it. 68 | - Steps: Add an item to the cart, remove the added item. 69 | 70 | ### inventoryItem.spec.ts 71 | 72 | 1. **Should Add to Cart and Update Cart Counter** 73 | - Description: Confirm that adding an item to the cart updates the cart counter. 74 | - Steps: Navigate to an item's detailed view, add item to the cart and verify that the cart counter reflects the updated count. 75 | 76 | 2. **Should Remove from Cart and Update Cart Counter** 77 | - Description: Verify that removing an item from the cart updates the cart counter accordingly. 78 | - Steps: Navigate to an item's detailed view, add item to the cart, remove it, and check that the cart counter is decremented. 79 | 80 | 3. **Should Return to Inventory Page** 81 | - Description: Test the functionality to return to the inventory page from an item-specific view. 82 | - Steps: Navigate to an item's detailed view, use the back button, and confirm returning to the inventory page. 83 | 84 | ### cart.spec.ts 85 | 86 | 1. **Should Remove Item and Decrease Cart Counter** 87 | - **Description:** Validate that removing an item from the cart results in a decrease in the cart counter. 88 | - **Steps:** Add an item to the cart, note the initial cart count, remove the item, and confirm the cart counter is decremented. 89 | 90 | 2. **Should Remove All Items** 91 | - **Description:** Test the ability to remove all items from the cart. 92 | - **Steps:** Add multiple items to the cart, initiate the removal of all items, and verify that the cart is empty afterward. 93 | 94 | 3. **Should Continue Shopping** 95 | - **Description:** Confirm the functionality to continue shopping from the cart. 96 | - **Steps:** Navigate to the cart, choose to continue shopping, and ensure a return to the inventory page. 97 | 98 | 4. **Should Checkout** 99 | - **Description:** Validate the checkout process initiated from the cart. 100 | - **Steps:** Add an item to the cart, proceed to checkout, and confirm successful navigation to the checkout process. 101 | 102 | ### checkout.spec.ts 103 | 104 | 1. **Should Checkout and Go Back Home** 105 | - Description: Confirm the successful checkout process, leading back to the home page. 106 | - Steps: Go through the checkout process, complete the purchase, and validate redirection to the home page. 107 | 108 | 2. **Should Not Checkout with Empty Form** 109 | - Description: Validate that attempting to check out with an empty form is not allowed. 110 | - Steps: Initiate the checkout process with an empty form and confirm the expected system behavior. 111 | 112 | 3. **Should Not Checkout Without First Name** 113 | - Description: Confirm that checkout is not possible without entering a first name. 114 | - Steps: Start the checkout process without providing a first name and validate the system's response. 115 | 116 | 4. **Should Not Checkout Without Last Name** 117 | - Description: Verify that checkout requires entering a last name. 118 | - Steps: Attempt to check out without providing a last name and ensure the expected system behavior. 119 | 120 | 5. **Should Not Checkout Without Zip Code** 121 | - Description: Confirm that a zip code is a required field for the checkout process. 122 | - Steps: Start the checkout without entering a zip code and check for the system's expected response. 123 | 124 | 6. **Should Cancel Checkout on Step One** 125 | - Description: Test the ability to cancel the checkout process on the first step. 126 | - Steps: Begin the checkout process and choose to cancel at the first step, verifying a return to the previous state. 127 | 128 | 7. **Should Cancel Checkout on Step Two** 129 | - Description: Confirm that the checkout process can be canceled on the second step. 130 | - Steps: Progress to the second step of checkout and choose to cancel, checking for the expected system behavior. 131 | 132 | ### menu.spec.ts 133 | 134 | 1. **Should Open and Close Menu** 135 | - **Description:** Verify that the menu can be successfully opened and closed. 136 | - **Steps:** Open the menu, confirm its visibility, close the menu, and validate its closure. 137 | 138 | 2. **Should Reset App State** 139 | - **Description:** Test the functionality to reset the application state from the menu. 140 | - **Steps:** Navigate to the menu, choose the option to reset the app state, and confirm a reset. 141 | 142 | 3. **Should Visit About** 143 | - **Description:** Confirm that the "About" section is accessible from the menu. 144 | - **Steps:** Open the menu, select the "About" option, and validate successful navigation to the "About" page. 145 | 146 | 4. **Should Visit All Items** 147 | - **Description:** Validate the ability to visit the "Inventory" page from the menu. 148 | - **Steps:** Open the menu, choose the "All Items" option, and confirm successful navigation to the corresponding page. 149 | 150 | 5. **Should Logout** 151 | - **Description:** Test the logout functionality from the menu. 152 | - **Steps:** Open the menu, select the logout option, and confirm the expected logout behavior. --------------------------------------------------------------------------------