diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47f397f..cb2edc9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,44 +1,42 @@ -# .github/workflows/playwright-daily.yml -# Runs Playwright test shards every day at **11 : 42 AM IST** (06 : 12 UTC) -# plus anytime you trigger it manually from the Actions tab. - name: Run Playwright tests on: + push: + pull_request: schedule: - - cron: '30 3 * * 1-5' + - cron: '30 3 * * *' # 9:00 AM IST everyday # 11:00 AM IST workflow_dispatch: jobs: run-tests: - name: Run Playwright shards + name: Run Playwright tests ${{ matrix.shardIndex }}/5 runs-on: ubuntu-latest strategy: fail-fast: false matrix: - shardIndex: [1,2,3] - shardTotal: [2] + shardIndex: [1, 2, 3, 4, 5] + shardTotal: [5] steps: - uses: actions/checkout@v4 - - name: Setup Node.js 18.x + - name: Setup Node.js 20.x uses: actions/setup-node@v3 with: - node-version: '18' + node-version: "20" + - name: Create .env file run: | - echo "USERNAME=${{ secrets.USERNAME }}" >> .env - echo "USERNAME1=${{ secrets.USERNAME1 }}" >> .env - echo "PASSWORD=${{ secrets.PASSWORD }}" >> .env - echo "NEW_PASSWORD=${{ secrets.NEW_PASSWORD }}" >> .env - echo "FIRST_NAME=${{ secrets.FIRST_NAME }}" >> .env - echo "STREET_NAME=${{ secrets.STREET_NAME }}" >> .env - echo "CITY=${{ secrets.CITY }}" >> .env - echo "STATE=${{ secrets.STATE }}" >> .env - echo "COUNTRY=${{ secrets.COUNTRY }}" >> .env - echo "ZIP_CODE=${{ secrets.ZIP_CODE }}" >> .env + echo "USERNAME=${{ secrets.USERNAME }}" >> .env + echo "PASSWORD=${{ secrets.PASSWORD }}" >> .env + echo "NEW_PASSWORD=${{ secrets.NEW_PASSWORD }}" >> .env + echo "FIRST_NAME=${{ secrets.FIRST_NAME }}" >> .env + echo "STREET_NAME=${{ secrets.STREET_NAME }}" >> .env + echo "CITY=${{ secrets.CITY }}" >> .env + echo "STATE=${{ secrets.STATE }}" >> .env + echo "COUNTRY=${{ secrets.COUNTRY }}" >> .env + echo "ZIP_CODE=${{ secrets.ZIP_CODE }}" >> .env - name: Cache npm dependencies uses: actions/cache@v3 @@ -48,13 +46,16 @@ jobs: restore-keys: | ${{ runner.os }}-node- - - name: Install deps + browsers + - name: Install dependencies run: | npm ci npx playwright install --with-deps - - name: Run shard ${{ matrix.shardIndex }} - run: npx playwright test --project=chromium --reporter=blob --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + - name: Run Playwright Tests + run: | + npx playwright test \ + --grep="@chromium|@firefox|@webkit|@api|@andriod|@ios" \ + --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - name: Upload blob report if: ${{ !cancelled() }} @@ -67,26 +68,18 @@ jobs: merge-reports: name: Merge Reports needs: run-tests - if: always() # run even if some shards fail + if: always() runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup Node.js 18.x + - name: Setup Node.js 20.x uses: actions/setup-node@v3 with: - node-version: '18' - - - name: Cache npm dependencies - uses: actions/cache@v3 - with: - path: ~/.npm - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- + node-version: "20" - - name: Install deps + browsers + - name: Install dependencies run: | npm ci npx playwright install --with-deps @@ -99,18 +92,23 @@ jobs: merge-multiple: true - name: Merge HTML & JSON reports - run: npx playwright merge-reports --config=playwright.config.js ./all-blob-reports + run: | + npx playwright merge-reports \ + --config=playwright.config.js \ + ./all-blob-reports - - name: Upload combined report + - name: Upload combined Playwright report uses: actions/upload-artifact@v4 with: name: Playwright Test Report path: ./playwright-report retention-days: 14 - - name: Send TestDino report + - name: Upload report to TestDino + env: + TESTDINO_TOKEN: ${{ secrets.TESTDINO_TOKEN }} run: | - npx --yes tdpw ./playwright-report \ - --token="trx_production_75deca4b6fbb32963853ca189506496d60835761c32e54fd9f6787b00c86158f" \ + npx tdpw upload ./playwright-report \ + --upload-traces \ --upload-html \ - --verbose \ No newline at end of file + --token="$TESTDINO_TOKEN" diff --git a/README.md b/README.md index e3adf5e..95c8154 100644 --- a/README.md +++ b/README.md @@ -1 +1,109 @@ -# alphabin-demo-test-playwright \ No newline at end of file +# Ecommerce demo store - Playwright (javascript) tests + +Automated end-to-end tests for Ecommerce demo store using [Playwright](https://playwright.dev/). + +--- + +## Project Structure + +- `pages/` — Page Object Models +- `tests/` — Test specifications +- `playwright.config.js` — Playwright configuration +- `playwright-report/` — HTML test reports +- `.github/workflows/test.yml` — CI/CD pipeline + +--- + +## Prerequisites + +- [Node.js](https://nodejs.org/) v16+ +- [npm](https://www.npmjs.com/) + +--- + +## Installation + +```sh +npm install +``` + +--- + +## Local Test Execution + +Run all tests: +```sh +npx playwright test +``` + +View the HTML report: +```sh +npx playwright show-report +``` + +--- + +## Testdino Integration + +[Testdino](https://testdino.com/) enables cloud-based Playwright reporting. + +> **Important:** +> Make sure your `playwright.config.js` includes both the HTML and JSON reporters. +> The HTML report and JSON report must be available for Testdino to process your test results. + +Example configuration: +```js +reporter: [ + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ['json', { outputFile: './playwright-report/report.json' }], +] +``` + +### Local Execution + +After your tests complete and the report is generated in `playwright-report`, upload it to Testdino: + +```sh +npx --yes tdpw ./playwright-report --token="YOUR_TESTDINO_API_KEY" --upload-html +``` + +Replace the token above with your own Testdino API key. + +See all available commands: +```sh +npx tdpw --help +``` + +--- + +## CI/CD Pipeline Integration + +### GitHub Actions + +Add the following step to your workflow after tests and report generation: + +```yaml +- name: Send Testdino report + run: | + npx --yes tdpw ./playwright-report --token="YOUR_TESTDINO_API_KEY" --upload-html +``` + +Ensure your API key is correctly placed in the command. + +--- + +## Continuous Integration + +Automated test runs and report merging are configured in `.github/workflows/test.yml`. + +--- + +## Contributing + +Pull requests and issues are welcome! + +--- + +## License + +MIT diff --git a/pages/AllPages.js b/pages/AllPages.js index f9f7c91..c4c22f0 100644 --- a/pages/AllPages.js +++ b/pages/AllPages.js @@ -9,6 +9,7 @@ import CheckoutPage from "./CheckoutPage"; import OrderPage from "./OrderPage"; // Import OrderPage import UserPage from "./UserPage"; // Import UserPage import OrderDetailsPage from "./OrderDetailsPage"; +import ContactUsPage from "./ContactUsPage"; class AllPages { constructor(page) { @@ -24,6 +25,8 @@ class AllPages { this.orderPage = new OrderPage(page); // Instantiate OrderPage this.userPage = new UserPage(page); // Instantiate UserPage this.orderDetailsPage = new OrderDetailsPage(page); + this.contactUsPage = new ContactUsPage(page); + } } diff --git a/pages/CartPage.js b/pages/CartPage.js index 9de37c5..5b667b8 100644 --- a/pages/CartPage.js +++ b/pages/CartPage.js @@ -1,3 +1,4 @@ +import { start } from 'repl'; import BasePage from './BasePage.js'; import { expect } from '@playwright/test'; @@ -29,6 +30,9 @@ class CartPage extends BasePage{ checkoutButton: '[data-testid="checkout-button"]', viewCartButton: '[data-testid="view-cart-button"]', shoppingCartIcon: `[data-testid="header-cart-icon"]`, + deleteItemButton: '[aria-label="Remove item"]', + cartEmpty: `[data-testid="empty-cart"]`, + startShoppingButton: `[data-testid="continue-shopping-btn"]` } async assertYourCartTitle() { @@ -124,6 +128,27 @@ class CartPage extends BasePage{ await this.page.waitForTimeout(2000); await this.page.locator(this.locators.checkoutButton).click({ force: true }); } + + async verifyIncreasedQuantity(expectedQuantity) { + await expect(this.page.locator(this.locators.cartItemQuantity)).toHaveText(expectedQuantity); + } + + async clickOnDeleteProductIcon() { + await this.page.locator(this.locators.deleteItemButton).click(); + } + + async verifyCartItemDeleted() { + await expect(this.page.locator(this.locators.cartItemName)).toHaveCount(0); + } + + async verifyEmptyCartMessage() { + await expect(this.page.locator(this.locators.cartEmpty)).toBeVisible(); + } + + async clickOnStartShoppingButton() { + await this.page.locator(this.locators.startShoppingButton).click(); + } + } export default CartPage; \ No newline at end of file diff --git a/pages/ContactUsPage.js b/pages/ContactUsPage.js new file mode 100644 index 0000000..5053db5 --- /dev/null +++ b/pages/ContactUsPage.js @@ -0,0 +1,40 @@ +import BasePage from './BasePage.js'; +import { expect } from '@playwright/test'; + +class ContactUsPage extends BasePage{ + + /** + * @param {import('@playwright/test').Page} page + */ + constructor(page) { + super(page); + this.page = page; + } + + locators = { + contactUsBtn: `[data-testid="header-menu-contact-us"]`, + contactUsTitle: `[data-testid="contact-us-heading"]`, + firstNameInput: `[data-testid="contact-us-first-name-input"]`, + lastNameInput: `[data-testid="contact-us-last-name-input"]`, + subjectInput: `[data-testid="contact-us-subject-input"]`, + messageInput: `[data-testid="contact-us-message-input"]`, + sendMessageBtn: `//button[@data-testid="contact-us-submit-button"]`, + successMessage: `[data-testid="contact-us-success-message"]` + } + + async assertContactUsTitle() { + await expect(this.page.locator(this.locators.contactUsTitle)).toHaveText('Contact Us'); + } + + async fillContactUsForm() { + await this.page.fill(this.locators.firstNameInput, 'John'); + await this.page.fill(this.locators.lastNameInput, 'Doe'); + await this.page.fill(this.locators.subjectInput, 'Test Subject'); + await this.page.fill(this.locators.messageInput, 'This is a test message.'); + await this.page.click(this.locators.sendMessageBtn); + } + async verifySuccessContactUsFormSubmission() { + await expect(this.page.locator(this.locators.successMessage)).toBeVisible(); + } +} +export default ContactUsPage; \ No newline at end of file diff --git a/pages/HomePage.js b/pages/HomePage.js index 72c55b3..593898a 100644 --- a/pages/HomePage.js +++ b/pages/HomePage.js @@ -23,7 +23,8 @@ class HomePage extends BasePage{ AddCartNotification: `div[role="status"][aria-live="polite"]:has-text("Added to the cart")`, priceRangeSlider2 : `[data-testid="all-products-price-range-input-1"]`, priceRangeSlider1 : `[data-testid="all-products-price-range-input-0"]`, - filterButton : `[data-testid="all-products-filter-toggle"]` + filterButton : `[data-testid="all-products-filter-toggle"]`, + aboutUsTitle: `[data-testid="about-us-title"]`, } } @@ -88,6 +89,25 @@ class HomePage extends BasePage{ return this.page.locator(this.locators.navbar.showNowButton); } + async clickOnContactUsLink() { + await this.getContactUsNav().click(); + } + + async clickBackToHomeButton() { + await this.getHomeNav().click(); + } + + async assertHomePage() { + await expect(this.page.locator(this.locators.navbar.homeNav)).toBeVisible({ timeout: 10000 }); + } + + async clickAboutUsNav() { + await this.getAboutUsNav().click(); + } + + async assertAboutUsTitle() { + await expect(this.page.locator(this.locators.navbar.aboutUsTitle)).toBeVisible({ timeout: 10000 }); + } } export default HomePage; \ No newline at end of file diff --git a/pages/ProductDetailsPage.js b/pages/ProductDetailsPage.js index 1256cbf..748b3ef 100644 --- a/pages/ProductDetailsPage.js +++ b/pages/ProductDetailsPage.js @@ -17,7 +17,16 @@ class ProductDetailsPage extends BasePage{ addToCartButton: 'ADD TO CART', headerCartIcon: 'header-cart-icon', productAdditionalInfoTab : `[data-testid="additional-info-tab"]`, - productReviewsTab : `[data-testid="reviews-tab"]` + productReviewsTab : `[data-testid="reviews-tab"]`, + writeReviewBtn: `//button[@data-testid="write-review-button"]`, + yourNameInput: `[data-testid="review-form-name-input"]`, + emailInput: `[data-testid="review-form-email-input"]`, + ratingStars: `[data-testid="review-form-rating-4"]`, + reviewTitleInput: `[data-testid="review-form-title-input"]`, + giveYourOpinionInput: `[data-testid="review-form-review-input"]`, + submitBtn: `[data-testid="review-form-submit-button"]`, + editReviewBtn: `[data-testid="edit-review-button"]`, + deleteReviewBtn: `[data-testid="delete-review-button"]` } @@ -85,6 +94,47 @@ class ProductDetailsPage extends BasePage{ async clickCartIcon() { await this.getCartIcon().click(); } + + async clickOnWriteAReviewBtn() { + await this.page.locator(this.locators.writeReviewBtn).click(); + } + + async fillReviewForm() { + await this.page.fill(this.locators.yourNameInput, 'John Doe'); + await this.page.fill(this.locators.emailInput, 'testing@gmail.com'); + await this.page.click(this.locators.ratingStars); + await this.page.fill(this.locators.reviewTitleInput, 'Great Product'); + await this.page.fill(this.locators.giveYourOpinionInput, 'This product exceeded my expectations. Highly recommend!'); + await this.page.click(this.locators.submitBtn); + } + async assertSubmittedReview({ name, title, opinion }) { + await this.page.waitForTimeout(3000); // Wait for 1 second to ensure the form is ready + await expect(this.page.locator(`text=${name}`)).toBeVisible(); + await expect(this.page.locator(`text=${title}`)).toBeVisible(); + await expect(this.page.locator(`text=${opinion}`)).toBeVisible(); + } + + async clickOnEditReviewBtn() { + await this.page.locator(this.locators.editReviewBtn).click(); + } + + async updateReviewForm() { + await this.page.waitForTimeout(3000); // Wait for 1 second to ensure the form is ready + await this.page.fill(this.locators.reviewTitleInput, 'Updated Review Title'); + await this.page.fill(this.locators.giveYourOpinionInput, 'This is an updated review opinion.'); + await this.page.click(this.locators.submitBtn); + } + + async assertUpdatedReview({ title, opinion }) { + await this.page.waitForTimeout(3000); // Wait for 1 second to ensure the form is ready + await expect(this.page.locator(`text=${title}`)).toBeVisible(); + await expect(this.page.locator(`text=${opinion}`)).toBeVisible(); + } + + async clickOnDeleteReviewBtn() { + await this.page.locator(this.locators.deleteReviewBtn).click(); + await this.page.keyboard.press('Enter'); + } } export default ProductDetailsPage; \ No newline at end of file diff --git a/playwright.config.js b/playwright.config.js index c3dd637..deac8db 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,39 +1,69 @@ // @ts-check import { defineConfig, devices } from '@playwright/test'; +import * as dotenv from 'dotenv'; + +dotenv.config({ quiet: true }); const isCI = !!process.env.CI; export default defineConfig({ testDir: './tests', + fullyParallel: true, forbidOnly: isCI, - retries: isCI ? 1 : 0, + + // ✅ Retries set to 2 + retries: 2, + workers: isCI ? 1 : 1, - timeout: 60 * 1000, // ⏱️ each test fails after 1 min - // In CI we only show a list reporter. The workflow sets --reporter=blob. - // Locally you also get HTML and JSON. + timeout: 30 * 1000, + reporter: [ - ['html', { - outputFolder: 'playwright-report', - open: 'never' - }], - ['blob', { outputDir: 'blob-report' }], // Use blob reporter + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ['blob', { outputDir: 'blob-report' }], // Blob reporter for merging ['json', { outputFile: './playwright-report/report.json' }], ], use: { - baseURL: 'https://demo.alphabin.co/', - headless: false, - trace: 'on-first-retry', - screenshot: 'only-on-failure', - video: 'retain-on-failure', + baseURL: 'https://storedemo.testdino.com', + headless: true, + + // ✅ Always capture debugging artifacts + trace: 'on', + screenshot: 'on', + video: 'on', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, + grep: /@chromium/, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + grep: /@firefox/, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + grep: /@webkit/, + }, + { + name: 'android', + use: { ...devices['Pixel 5'] }, + grep: /@android/, + }, + { + name: 'ios', + use: { ...devices['iPhone 12'] }, + grep: /@ios/, + }, + { + name: 'api', + grep: /@api/, }, ], -}); \ No newline at end of file +}); diff --git a/tests/cart_checkout.spec.js b/tests/cart_checkout.spec.js new file mode 100644 index 0000000..123f880 --- /dev/null +++ b/tests/cart_checkout.spec.js @@ -0,0 +1,177 @@ +// @ts-check +import { expect, test } from '@playwright/test'; +import AllPages from '../pages/AllPages.js'; + +let allPages; + +test.beforeEach(async ({ page }) => { + allPages = new AllPages(page); + await page.goto('/'); +}); + +test.describe('Cart Module', () => { + test.describe('Product Removal', () => { + test('Verify that user is able to delete selected product from cart ',{tag: '@ios'}, async () => { + const productName = 'GoPro HERO10 Black'; + await allPages.inventoryPage.clickOnAllProductsLink(); + await allPages.inventoryPage.searchProduct(productName); + await allPages.inventoryPage.verifyProductTitleVisible(productName); + await allPages.inventoryPage.clickOnAddToCartIcon(); + + await allPages.cartPage.clickOnCartIcon(); + await allPages.cartPage.verifyCartItemVisible(productName); + await allPages.cartPage.clickOnDeleteProductIcon(); + await allPages.cartPage.verifyCartItemDeleted(productName); + + }); + }); +}); + +test.describe('Orders Module', () => { + test.describe('Order Cancellation', () => { + test('Verify new user views and cancels an order in my orders ',{tag: '@chromium'}, async () => { + const email = `test+${Date.now()}@test.com`; + const firstName = 'Test'; + const lastName = 'User'; + let productName = `Rode NT1-A Condenser Mic`; + + await test.step('Verify that user can register successfully', async () => { + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); + }); + + await test.step('Navigate to All Products and add view details of a random product', async () => { + await allPages.homePage.clickAllProductsNav(); + productName = await allPages.allProductsPage.getNthProductName(1); + await allPages.allProductsPage.clickNthProduct(1); + await allPages.productDetailsPage.clickAddToCartButton(); + }); + + await test.step('Add product to cart, add new address and checkout', async () => { + await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.assertYourCartTitle(); + // await expect(allPages.cartPage.getCartItemName()).toContainText(productName, { timeout: 10000 }); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.fillShippingAddress( + // firstName, email, 'New York', 'New York', '123 Main St', '10001', 'United States' + // ); + // await allPages.checkoutPage.clickSaveAddressButton(); + // await allPages.checkoutPage.assertAddressAddedToast(); + // }); + + // await test.step('Complete order and verify in my orders', async () => { + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + // await allPages.inventoryPage.clickOnContinueShopping(); + + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.orderPage.clickOnMyOrdersTab(); + // await allPages.orderPage.clickCancelOrderButton(); + // await allPages.orderPage.confirmCancellation(); + }); + }); + }); +}); + +test.describe('User Journey', () => { + test.describe('Multiple Order Placement', () => { + test('Verify That a New User Can Successfully Complete the Journey from Registration to a Multiple Order Placement ',{tag: '@chromium'}, async () => { + const email = `test+${Date.now()}@test.com`; + const firstName = 'Test'; + const lastName = 'User'; + + await test.step('Verify that user can register successfully', async () => { + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); + }); + + await test.step('Navigate product details and validate tabs', async () => { + await allPages.homePage.clickOnShopNowButton(); + // await allPages.allProductsPage.assertAllProductsTitle(); + // await allPages.allProductsPage.clickNthProduct(1); + // await allPages.productDetailsPage.clickOnReviewsTab(); + // await allPages.productDetailsPage.assertReviewsTab(); + // await allPages.productDetailsPage.clickOnAdditionalInfoTab(); + // await allPages.productDetailsPage.assertAdditionalInfoTab(); + }); + + await test.step('Place first order', async () => { + // await allPages.productDetailsPage.clickAddToCartButton(); + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.clickIncreaseQuantityButton(); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.fillShippingAddress( + // process.env.SFIRST_NAME, + // email, + // process.env.SCITY, + // process.env.SSTATE, + // process.env.SSTREET_ADD, + // process.env.SZIP_CODE, + // process.env.SCOUNTRY + // ); + // await allPages.checkoutPage.clickSaveAddressButton(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + // await allPages.checkoutPage.clickOnContinueShoppingButton(); + }); + + await test.step('Place second order using existing address', async () => { + // await allPages.homePage.clickOnShopNowButton(); + // await allPages.allProductsPage.assertAllProductsTitle(); + // await allPages.allProductsPage.clickNthProduct(1); + // await allPages.productDetailsPage.clickAddToCartButton(); + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + }); + }); + }); +}); + +test.describe('Authentication', () => { + test.describe('Signup & Login', () => { + test('Verify that the new user is able to Sign Up, Log In, and Navigate to the Home Page Successfully ',{tag: '@chromium'}, async () => { + const email = `test+${Date.now()}@test.com`; + const firstName = 'Test'; + const lastName = 'User'; + + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); + + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.login(email, process.env.PASSWORD); + // await allPages.loginPage.verifySuccessSignIn(); + // await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); + }); + }); +}); + +test.describe('User Profile', () => { + test.describe('Personal Information', () => { + test('Verify that user can update personal information ',{tag: '@firefox'}, async () => { + await allPages.userPage.clickOnUserProfileIcon(); + // await allPages.userPage.updatePersonalInfo(); + // await allPages.userPage.verifyPersonalInfoUpdated(); + }); + }); +}); diff --git a/tests/delete-api.spec.js b/tests/delete-api.spec.js new file mode 100644 index 0000000..e85a3da --- /dev/null +++ b/tests/delete-api.spec.js @@ -0,0 +1,47 @@ +// @ts-check +import { expect, test } from '@playwright/test'; + +// Base API URL - adjust this to match your actual API endpoint +const API_BASE_URL = process.env.API_BASE_URL || 'https://dummyjson.com'; +const USERS_ENDPOINT = '/users'; + +test.describe('DELETE User API', () => { + + test('Remove user 1', { tag: '@api' }, async ({ request }) => { + const userId = 1; + const response = await request.delete(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('id', userId); + expect(body).toHaveProperty('isDeleted', true); + }); + + test.skip('Remove user twice', { tag: '@api' }, async ({ request }) => { + const userId = 2; + + // First deletion + const response1 = await request.delete(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`); + expect(response1.status()).toBe(200); + const body1 = await response1.json(); + expect(body1).toHaveProperty('id', userId); + + const response2 = await request.delete(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`); + expect(response2.status()).toBe(200); + const body2 = await response2.json(); + expect(body2).toHaveProperty('id', userId); + }); + + test.skip('Validate body is returned', { tag: '@api' }, async ({ request }) => { + const userId = 3; + const response = await request.delete(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + + // Validate response body structure + expect(body).toBeInstanceOf(Object); + expect(body).toHaveProperty('id'); + expect(typeof body.id).toBe('number'); + }); +}); diff --git a/tests/example.spec.js b/tests/example.spec.js index cf0e23f..96da862 100644 --- a/tests/example.spec.js +++ b/tests/example.spec.js @@ -28,154 +28,178 @@ async function logout() { await allPages.loginPage.clickOnLogoutButton(); } -test('Verify that user can login and logout successfully', async () => { +test('Verify that user can log in and log out successfully', { + tag: '@android', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Login' }, + { type: 'testdino:link', description: 'https://jira.example.com/LOGIN-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Login and logout on Android' } + ] +}, async () => { + const start = Date.now(); await login(); await logout(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify that user can update personal information', async () => { - await login(); - await allPages.userPage.clickOnUserProfileIcon(); - await allPages.userPage.updatePersonalInfo(); - await allPages.userPage.verifyPersonalInfoUpdated(); +test('Verify that all navbar links work properly', { + tag: '@webkit', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Navbar' }, + { type: 'testdino:link', description: 'https://jira.example.com/NAVBAR-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Navbar functionality on WebKit' } + ] +}, async () => { + const start = Date.now(); + // await login(); + await allPages.homePage.clickBackToHomeButton(); + // await allPages.homePage.assertHomePage(); + await allPages.homePage.clickAllProductsNav(); + await allPages.allProductsPage.assertAllProductsTitle(); + await allPages.homePage.clickOnContactUsLink(); + await allPages.contactUsPage.assertContactUsTitle(); + await allPages.homePage.clickAboutUsNav(); + await allPages.homePage.assertAboutUsTitle(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify that User Can Add, Edit, and Delete Addresses after Logging In', async () => { - await login(); +test('Verify that user can edit and delete a product review', { + tag: '@firefox', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Review' }, + { type: 'testdino:link', description: 'https://jira.example.com/REVIEW-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Edit and delete product review on Firefox' } + ] +}, async () => { + const start = Date.now(); + await test.step('Login as existing user and navigate to a product', async () => { + // await login(); + }) - await test.step('Verify that user is able to add address successfully', async () => { - await allPages.userPage.clickOnUserProfileIcon(); - await allPages.userPage.clickOnAddressTab(); - await allPages.userPage.clickOnAddAddressButton(); - await allPages.userPage.fillAddressForm(); - await allPages.userPage.verifytheAddressIsAdded(); - }); + await test.step('Navigate to all product section and select a product', async () => { + await allPages.homePage.clickOnShopNowButton(); + await allPages.allProductsPage.assertAllProductsTitle(); + await allPages.allProductsPage.clickNthProduct(1); + }) - await test.step('Verify that user is able to edit address successfully', async () => { - await allPages.userPage.clickOnEditAddressButton(); - await allPages.userPage.updateAddressForm(); - await allPages.userPage.verifytheUpdatedAddressIsAdded(); + await test.step('Submit a product review and verify submission', async () => { + await allPages.productDetailsPage.clickOnReviewsTab(); + await allPages.productDetailsPage.assertReviewsTab(); + + await allPages.productDetailsPage.clickOnWriteAReviewBtn(); + await allPages.productDetailsPage.fillReviewForm(); + await allPages.productDetailsPage.assertSubmittedReview({ + name: 'John Doe', + title: 'Great Product', + opinion: 'This product exceeded my expectations. Highly recommend!' + }); }) - await test.step('Verify that user is able to delete address successfully', async () => { - await allPages.userPage.clickOnDeleteAddressButton(); - }); -}); + await test.step('Edit the submitted review and verify changes', async () => { + await allPages.productDetailsPage.clickOnEditReviewBtn(); + await allPages.productDetailsPage.updateReviewForm(); + await allPages.productDetailsPage.assertUpdatedReview({ + title: 'Updated Review Title', + opinion: 'This is an updated review opinion.' + }) + }); -test('Verify that user can change password successfully', async () => { - await test.step('Login with existing password', async () => { - await login1(); + await test.step('Delete the submitted review and verify deletion', async () => { + await allPages.productDetailsPage.clickOnDeleteReviewBtn(); }); - await test.step('Change password and verify login with new password', async () => { - await allPages.userPage.clickOnUserProfileIcon(); - await allPages.userPage.clickOnSecurityButton(); - await allPages.userPage.enterNewPassword(); - await allPages.userPage.enterConfirmNewPassword(); - await allPages.userPage.clickOnUpdatePasswordButton(); - await allPages.userPage.getUpdatePasswordNotification(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), }); - await test.step('Verify login with new password and revert back to original password', async () => { - // Re-login with new password - await logout(); - await allPages.loginPage.login(process.env.USERNAME1, process.env.NEW_PASSWORD); - - // Revert back - await allPages.userPage.clickOnUserProfileIcon(); - await allPages.userPage.clickOnSecurityButton(); - await allPages.userPage.revertPasswordBackToOriginal(); - await allPages.userPage.getUpdatePasswordNotification(); - }) -}); - -test('Verify that the New User is able to add Addresses in the Address section', async () => { - await login(); - await allPages.userPage.clickOnUserProfileIcon(); - await allPages.userPage.clickOnAddressTab(); - await allPages.userPage.clickOnAddAddressButton(); - await allPages.userPage.checkAddNewAddressMenu(); - await allPages.userPage.fillAddressForm(); }); -test('Verify that User Can Complete the Journey from Login to Order Placement', async () => { +test('Verify that user can complete the journey from login to order placement', { + tag: '@ios', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Order' }, + { type: 'testdino:link', description: 'https://jira.example.com/ORDER-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Login to order placement journey on iOS' } + ] +}, async () => { + const start = Date.now(); const productName = 'GoPro HERO10 Black'; - await login(); + // await login(); await allPages.inventoryPage.clickOnShopNowButton(); await allPages.inventoryPage.clickOnAllProductsLink(); await allPages.inventoryPage.searchProduct(productName); await allPages.inventoryPage.verifyProductTitleVisible(productName); await allPages.inventoryPage.clickOnAddToCartIcon(); - await allPages.cartPage.clickOnCartIcon(); - await allPages.cartPage.verifyCartItemVisible(productName); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.verifyProductInCheckout(productName); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); -}); - -test('Verify user can place and cancel an order', async () => { - const productName = 'GoPro HERO10 Black'; - const productPriceAndQuantity = '₹49,999 × 1'; - const productQuantity = '1'; - const orderStatusProcessing = 'Processing'; - const orderStatusCanceled = 'Canceled'; - - await test.step('Verify that user can login successfully', async () => { - await login(); - await allPages.inventoryPage.clickOnAllProductsLink(); - await allPages.inventoryPage.searchProduct(productName); - await allPages.inventoryPage.verifyProductTitleVisible(productName); - await allPages.inventoryPage.clickOnAddToCartIcon(); - }) - - await test.step('Add product to cart and checkout', async () => { - await allPages.cartPage.clickOnCartIcon(); - await allPages.cartPage.verifyCartItemVisible(productName); - await allPages.cartPage.clickOnCheckoutButton(); - }) - - await test.step('Place order and click on continue shopping', async () => { - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.verifyProductInCheckout(productName); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); - await allPages.checkoutPage.verifyOrderItemName(productName); - await allPages.inventoryPage.clickOnContinueShopping(); - }) - - await test.step('Verify order in My Orders', async () => { - await allPages.loginPage.clickOnUserProfileIcon(); - await allPages.orderPage.clickOnMyOrdersTab(); - await allPages.orderPage.verifyMyOrdersTitle(); - await allPages.orderPage.clickOnPaginationButton(2); - await allPages.orderPage.verifyProductInOrderList(productName); - await allPages.orderPage.verifyPriceAndQuantityInOrderList(productPriceAndQuantity); - await allPages.orderPage.verifyOrderStatusInList(orderStatusProcessing, productName); - await allPages.orderPage.clickOnPaginationButton(1); - await allPages.orderPage.clickViewDetailsButton(1); - await allPages.orderPage.verifyOrderDetailsTitle(); - await allPages.orderPage.verifyOrderSummary(productName, productQuantity, '₹49,999', orderStatusProcessing); - }) - - await test.step('Cancel order and verify status is updated to Canceled', async () => { - await allPages.orderPage.clickCancelOrderButton(2); - await allPages.orderPage.confirmCancellation(); - await allPages.orderPage.verifyCancellationConfirmationMessage(); - await allPages.orderPage.verifyMyOrdersCount(); - await allPages.orderPage.clickOnMyOrdersTab(); - await allPages.orderPage.verifyMyOrdersTitle(); - await allPages.orderPage.clickOnPaginationButton(2); - await allPages.orderPage.verifyOrderStatusInList(orderStatusCanceled, productName); - }) + // await allPages.cartPage.clickOnCartIcon(); + // await allPages.cartPage.verifyCartItemVisible(productName); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.verifyProductInCheckout(productName); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCashOnDeliverySelected(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify that a New User Can Successfully Complete the Journey from Registration to a Single Order Placement', async () => { +test('Verify that a new user can complete the journey from registration to a single order placement', { + tag: '@android', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Registration' }, + { type: 'testdino:link', description: 'https://jira.example.com/REG-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Registration to single order on Android' } + ] +}, async () => { + const start = Date.now(); // fresh test data const email = `test+${Date.now()}@test.com`; const firstName = 'Test'; @@ -186,19 +210,19 @@ test('Verify that a New User Can Successfully Complete the Journey from Registra let productReviewCount; await test.step('Verify that user can register successfully', async () => { - await allPages.loginPage.clickOnUserProfileIcon(); - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.clickOnSignupLink(); - await allPages.signupPage.assertSignupPage(); - await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); - await allPages.signupPage.verifySuccessSignUp(); + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); }) await test.step('Verify that user can login successfully', async () => { - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.login(email, process.env.PASSWORD); - await allPages.loginPage.verifySuccessSignIn(); - await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.login(email, process.env.PASSWORD); + // await allPages.loginPage.verifySuccessSignIn(); + // await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); }) await test.step('Navigate to all product and add to wishlist section', async () => { @@ -222,101 +246,165 @@ test('Verify that a New User Can Successfully Complete the Journey from Registra await test.step('Add product to cart, add new address and checkout', async () => { await allPages.productDetailsPage.clickAddToCartButton(); - await allPages.productDetailsPage.clickCartIcon(); - await allPages.cartPage.assertYourCartTitle(); - await expect(allPages.cartPage.getCartItemName()).toContainText(productName, { timeout: 10000 }); - await expect(allPages.cartPage.getCartItemPrice()).toContainText(productPrice); - await expect(allPages.cartPage.getCartItemQuantity()).toContainText('1'); - await allPages.cartPage.clickIncreaseQuantityButton(); - await expect(allPages.cartPage.getCartItemQuantity()).toContainText('2'); - - const cleanPrice = productPrice.replace(/[₹,]/g, ''); - const priceValue = parseFloat(cleanPrice) * 2; - await expect(allPages.cartPage.getTotalValue()).toContainText( - priceValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') - ); - await allPages.cartPage.clickOnCheckoutButton(); - - // Fill shipping address and save - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.fillShippingAddress( - firstName, email, 'New York', 'New York', '123 Main St', '10001', 'United States' - ); - await allPages.checkoutPage.clickSaveAddressButton(); - await allPages.checkoutPage.assertAddressAddedToast(); - - // COD, verify summary, place order - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.assertOrderSummaryTitle(); - await expect(allPages.checkoutPage.getOrderSummaryImage()).toBeVisible(); - await expect(allPages.checkoutPage.getOrderSummaryProductName()).toContainText(productName); - await allPages.checkoutPage.verifyProductInCheckout(productName); - await expect(allPages.checkoutPage.getOrderSummaryProductQuantity()).toContainText('2'); - await expect(allPages.checkoutPage.getOrderSummaryProductPrice()).toContainText(productPrice); - - const subtotalValue = parseFloat(cleanPrice) * 2; - const formattedSubtotal = subtotalValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); - await expect(await allPages.checkoutPage.getOrderSummarySubtotalValue()).toContain(formattedSubtotal); - await expect(allPages.checkoutPage.getOrderSummaryShippingValue()).toContainText('Free'); - await allPages.checkoutPage.clickOnPlaceOrder(); - - // Order details and return to home - await allPages.orderDetailsPage.assertOrderDetailsTitle(); - await allPages.orderDetailsPage.assertOrderPlacedName(); - await allPages.orderDetailsPage.assertOrderPlacedMessage(); - await allPages.orderDetailsPage.assertOrderPlacedDate(); - await allPages.orderDetailsPage.assertOrderInformationTitle(); - await allPages.orderDetailsPage.assertOrderConfirmedTitle(); - await allPages.orderDetailsPage.assertOrderConfirmedMessage(); - await allPages.orderDetailsPage.assertShippingDetailsTitle(); - await allPages.orderDetailsPage.assertShippingEmailValue(email); - await allPages.orderDetailsPage.assertPaymentMethodAmount(formattedSubtotal); - await allPages.orderDetailsPage.assertDeliveryAddressLabel(); - await allPages.orderDetailsPage.assertDeliveryAddressValue(); - await allPages.orderDetailsPage.assertContinueShoppingButton(); - - await allPages.orderDetailsPage.assertOrderSummaryTitle(); - await allPages.orderDetailsPage.assertOrderSummaryProductName(productName); - await allPages.orderDetailsPage.assertOrderSummaryProductQuantity('2'); - await allPages.orderDetailsPage.assertOrderSummaryProductPrice(productPrice); - await allPages.orderDetailsPage.assertOrderSummarySubtotalValue(formattedSubtotal); - await allPages.orderDetailsPage.assertOrderSummaryShippingValue('Free'); - await allPages.orderDetailsPage.assertOrderSummaryTotalValue(formattedSubtotal); - await allPages.orderDetailsPage.clickBackToHomeButton(); + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.assertYourCartTitle(); + // await expect(allPages.cartPage.getCartItemName()).toContainText(productName, { timeout: 10000 }); + // await expect(allPages.cartPage.getCartItemPrice()).toContainText(productPrice); + // await expect(allPages.cartPage.getCartItemQuantity()).toContainText('1'); + // await allPages.cartPage.clickIncreaseQuantityButton(); + // await expect(allPages.cartPage.getCartItemQuantity()).toContainText('2'); + + // const cleanPrice = productPrice.replace(/[₹,]/g, ''); + // const priceValue = parseFloat(cleanPrice) * 2; + // await expect(allPages.cartPage.getTotalValue()).toContainText( + // priceValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + // ); + // await allPages.cartPage.clickOnCheckoutButton(); + + // // Fill shipping address and save + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.fillShippingAddress( + // firstName, email, 'New York', 'New York', '123 Main St', '10001', 'United States' + // ); + // await allPages.checkoutPage.clickSaveAddressButton(); + // await allPages.checkoutPage.assertAddressAddedToast(); + + // // COD, verify summary, place order + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.assertOrderSummaryTitle(); + // await expect(allPages.checkoutPage.getOrderSummaryImage()).toBeVisible(); + // await expect(allPages.checkoutPage.getOrderSummaryProductName()).toContainText(productName); + // await allPages.checkoutPage.verifyProductInCheckout(productName); + // await expect(allPages.checkoutPage.getOrderSummaryProductQuantity()).toContainText('2'); + // await expect(allPages.checkoutPage.getOrderSummaryProductPrice()).toContainText(productPrice); + + // const subtotalValue = parseFloat(cleanPrice) * 2; + // const formattedSubtotal = subtotalValue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + // await expect(await allPages.checkoutPage.getOrderSummarySubtotalValue()).toContain(formattedSubtotal); + // await expect(allPages.checkoutPage.getOrderSummaryShippingValue()).toContainText('Free'); + // await allPages.checkoutPage.clickOnPlaceOrder(); + + // // Order details and return to home + // await allPages.orderDetailsPage.assertOrderDetailsTitle(); + // await allPages.orderDetailsPage.assertOrderPlacedName(); + // await allPages.orderDetailsPage.assertOrderPlacedMessage(); + // await allPages.orderDetailsPage.assertOrderPlacedDate(); + // await allPages.orderDetailsPage.assertOrderInformationTitle(); + // await allPages.orderDetailsPage.assertOrderConfirmedTitle(); + // await allPages.orderDetailsPage.assertOrderConfirmedMessage(); + // await allPages.orderDetailsPage.assertShippingDetailsTitle(); + // await allPages.orderDetailsPage.assertShippingEmailValue(email); + // await allPages.orderDetailsPage.assertPaymentMethodAmount(formattedSubtotal); + // await allPages.orderDetailsPage.assertDeliveryAddressLabel(); + // await allPages.orderDetailsPage.assertDeliveryAddressValue(); + // await allPages.orderDetailsPage.assertContinueShoppingButton(); + + // await allPages.orderDetailsPage.assertOrderSummaryTitle(); + // await allPages.orderDetailsPage.assertOrderSummaryProductName(productName); + // await allPages.orderDetailsPage.assertOrderSummaryProductQuantity('2'); + // await allPages.orderDetailsPage.assertOrderSummaryProductPrice(productPrice); + // await allPages.orderDetailsPage.assertOrderSummarySubtotalValue(formattedSubtotal); + // await allPages.orderDetailsPage.assertOrderSummaryShippingValue('Free'); + // await allPages.orderDetailsPage.assertOrderSummaryTotalValue(formattedSubtotal); + // await allPages.orderDetailsPage.clickBackToHomeButton(); + }); + + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), }); }); -test('Verify that user add product to cart before logging in and then complete order after logging in', async () => { +test('Verify that user can add product to cart before logging in and complete order after logging in', { + tag: '@webkit', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Cart' }, + { type: 'testdino:link', description: 'https://jira.example.com/CART-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Add to cart before login, order after login on WebKit' } + ] +}, async () => { + const start = Date.now(); await test.step('Navigate and add product to cart before logging in', async () => { await allPages.homePage.clickOnShopNowButton(); await allPages.homePage.clickProductImage(); await allPages.homePage.clickAddToCartButton(); await allPages.homePage.validateAddCartNotification(); - await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.clickOnUserProfileIcon(); }) - await test.step('Login and complete order', async () => { - await login(); - await allPages.cartPage.clickOnCartIcon(); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); -}) + // await test.step('Login and complete order', async () => { +// await login(); +// await allPages.cartPage.clickOnCartIcon(); +// await allPages.cartPage.clickOnCheckoutButton(); +// await allPages.checkoutPage.verifyCheckoutTitle(); +// await allPages.checkoutPage.selectCashOnDelivery(); +// await allPages.checkoutPage.verifyCashOnDeliverySelected(); +// await allPages.checkoutPage.clickOnPlaceOrder(); +// await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); +// }) + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify that user can filter products by price range', async () => { +test('Verify that user can filter products by price range', { + tag: '@filter', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Filter' }, + { type: 'testdino:link', description: 'https://jira.example.com/FILTER-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Filter products by price range' } + ] +}, async () => { + const start = Date.now(); await login(); await allPages.homePage.clickOnShopNowButton(); await allPages.homePage.clickOnFilterButton(); await allPages.homePage.AdjustPriceRangeSlider('10000', '20000'); await allPages.homePage.clickOnFilterButton(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify if user can add product to wishlist, moves it to card and then checks out', async () => { - await login(); +test('Verify that user can add product to wishlist, move it to cart, and checkout', { + tag: '@wishlist', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Wishlist' }, + { type: 'testdino:link', description: 'https://jira.example.com/WISHLIST-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Wishlist to cart and checkout' } + ] +}, async () => { + const start = Date.now(); + // await login(); await test.step('Add product to wishlistand then add to cart', async () => { await allPages.homePage.clickOnShopNowButton(); @@ -329,38 +417,61 @@ test('Verify if user can add product to wishlist, moves it to card and then chec await test.step('Checkout product added to cart', async () => { await allPages.cartPage.clickOnCartIcon(); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); - }) - + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCashOnDeliverySelected(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + }); + + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); }); -test('Verify new user views and cancels an order in my orders', async () => { +test.describe('Orders Module', () => { + test.describe('Order Cancellation', () => { + test('Verify that new user can view and cancel an order in My Orders', { + tag: '@chromium', + annotation: [ + { type: 'testdino:priority', description: 'p0' }, + { type: 'testdino:feature', description: 'Orders' }, + { type: 'testdino:link', description: 'https://jira.example.com/ORDER-002' }, + { type: 'testdino:owner', description: '@Kriti Verma' }, + { type: 'testdino:notify-slack', description: '@Kriti Verma' }, + { type: 'testdino:context', description: 'Critical order cancellation flow for new users' } + ] + }, async () => { + const start = Date.now(); const email = `test+${Date.now()}@test.com`; const firstName = 'Test'; const lastName = 'User'; let productName= `Rode NT1-A Condenser Mic`; - await test.step('Verify that user can register successfully', async () => { - await allPages.loginPage.clickOnUserProfileIcon(); - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.clickOnSignupLink(); - await allPages.signupPage.assertSignupPage(); - await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); - await allPages.signupPage.verifySuccessSignUp(); - }) - - await test.step('Verify that user can login successfully', async () => { - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.login(email, process.env.PASSWORD); - await allPages.loginPage.verifySuccessSignIn(); - await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); - }) + // await test.step('Verify that user can register successfully', async () => { + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); + // }) + + // await test.step('Verify that user can login successfully', async () => { + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.login(email, process.env.PASSWORD); + // await allPages.loginPage.verifySuccessSignIn(); + // await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); + // }) await test.step('Navigate to All Products and add view details of a random product', async () => { await allPages.homePage.clickAllProductsNav(); @@ -368,60 +479,66 @@ test('Verify new user views and cancels an order in my orders', async () => { productName = await allPages.allProductsPage.getNthProductName(1); await allPages.allProductsPage.clickNthProduct(1); await allPages.productDetailsPage.clickAddToCartButton(); - }) + }); - await test.step('Add product to cart, add new address and checkout', async () => { - await allPages.productDetailsPage.clickCartIcon(); - await allPages.cartPage.assertYourCartTitle(); - await expect(allPages.cartPage.getCartItemName()).toContainText(productName, { timeout: 10000 }); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.fillShippingAddress( - firstName, email, 'New York', 'New York', '123 Main St', '10001', 'United States' - ); - await allPages.checkoutPage.clickSaveAddressButton(); - await allPages.checkoutPage.assertAddressAddedToast(); - }) + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'order-flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); - await test.step('Complete order and verify in my orders', async () => { - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); - await allPages.inventoryPage.clickOnContinueShopping(); - - await allPages.loginPage.clickOnUserProfileIcon(); - await allPages.orderPage.clickOnMyOrdersTab(); - await allPages.orderPage.clickCancelOrderButton(); - await allPages.orderPage.confirmCancellation(); + // await test.step('Add product to cart, add new address and checkout', async () => { + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.assertYourCartTitle(); + // await expect(allPages.cartPage.getCartItemName()).toContainText(productName, { timeout: 10000 }); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.fillShippingAddress( + // firstName, email, 'New York', 'New York', '123 Main St', '10001', 'United States' + // ); + // await allPages.checkoutPage.clickSaveAddressButton(); + // await allPages.checkoutPage.assertAddressAddedToast(); + // }) + + // await test.step('Complete order and verify in my orders', async () => { + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + // await allPages.inventoryPage.clickOnContinueShopping(); + + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.orderPage.clickOnMyOrdersTab(); + // await allPages.orderPage.clickCancelOrderButton(); + // await allPages.orderPage.confirmCancellation(); + // }); + }); }); }); -test('Verify That a New User Can Successfully Complete the Journey from Registration to a Multiple Order Placement', async () => { +test('Verify that a new user can complete the journey from registration to multiple order placements', { + tag: '@firefox', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Order' }, + { type: 'testdino:link', description: 'https://jira.example.com/ORDER-003' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Registration to multiple order placement on Firefox' } + ] +}, async () => { + const start = Date.now(); const email = `test+${Date.now()}@test.com`; const firstName = 'Test'; const lastName = 'User'; let productName= `Rode NT1-A Condenser Mic`; - await test.step('Verify that user can register successfully', async () => { - // Signup - await allPages.loginPage.clickOnUserProfileIcon(); - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.clickOnSignupLink(); - await allPages.signupPage.assertSignupPage(); - await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); - await allPages.signupPage.verifySuccessSignUp(); - }) - - await test.step('Verify that user can login successfully', async () => { - // Login as new user - await allPages.loginPage.validateSignInPage(); - await allPages.loginPage.login(email, process.env.PASSWORD); - await allPages.loginPage.verifySuccessSignIn(); - await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); - }) - await test.step('Navigate to All Products and add view details of a random product', async () => { await allPages.homePage.clickOnShopNowButton(); await allPages.allProductsPage.assertAllProductsTitle(); @@ -433,32 +550,228 @@ test('Verify That a New User Can Successfully Complete the Journey from Registra }) await test.step('Add product to cart, change quantity, add new address and checkout', async () => { - await allPages.productDetailsPage.clickAddToCartButton(); - await allPages.productDetailsPage.clickCartIcon(); - await allPages.cartPage.clickIncreaseQuantityButton(); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.fillShippingAddress(process.env.SFIRST_NAME, email, process.env.SCITY, process.env.SSTATE, process.env.SSTREET_ADD, process.env.SZIP_CODE, process.env.SCOUNTRY); - await allPages.checkoutPage.clickSaveAddressButton(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); - await allPages.checkoutPage.verifyOrderConfirmedTitle(); - await allPages.checkoutPage.clickOnContinueShoppingButton(); + // await allPages.productDetailsPage.clickAddToCartButton(); + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.clickIncreaseQuantityButton(); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCashOnDeliverySelected(); + // await allPages.checkoutPage.fillShippingAddress(process.env.SFIRST_NAME, email, process.env.SCITY, process.env.SSTATE, process.env.SSTREET_ADD, process.env.SZIP_CODE, process.env.SCOUNTRY); + // await allPages.checkoutPage.clickSaveAddressButton(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + // await allPages.checkoutPage.verifyOrderConfirmedTitle(); + // await allPages.checkoutPage.clickOnContinueShoppingButton(); }) await test.step('Add another product to cart, select existing address and checkout', async () => { + // await allPages.homePage.clickOnShopNowButton(); + // await allPages.allProductsPage.assertAllProductsTitle(); + // await allPages.allProductsPage.clickNthProduct(1); + // await allPages.productDetailsPage.clickAddToCartButton(); + // await allPages.productDetailsPage.clickCartIcon(); + // await allPages.cartPage.clickOnCheckoutButton(); + // await allPages.checkoutPage.verifyCheckoutTitle(); + // await allPages.checkoutPage.selectCashOnDelivery(); + // await allPages.checkoutPage.verifyCashOnDeliverySelected(); + // await allPages.checkoutPage.clickOnPlaceOrder(); + // await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); + }); + + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}); + +test('Verify that a new user can sign up, log in, and navigate to the home page successfully', { + tag: '@ios', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Registration' }, + { type: 'testdino:link', description: 'https://jira.example.com/REG-002' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Sign up, login and navigate home on iOS' } + ] +}, async () => { + const start = Date.now(); + const email = `test+${Date.now()}@test.com`; + const firstName = 'Test'; + const lastName = 'User'; + + await test.step('Verify that user can register successfully', async () => { + // await allPages.loginPage.clickOnUserProfileIcon(); + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.clickOnSignupLink(); + // await allPages.signupPage.assertSignupPage(); + // await allPages.signupPage.signup(firstName, lastName, email, process.env.PASSWORD); + // await allPages.signupPage.verifySuccessSignUp(); + }) + + // await test.step('Verify that user can login successfully', async () => { + // await allPages.loginPage.validateSignInPage(); + // await allPages.loginPage.login(email, process.env.PASSWORD); + // await allPages.loginPage.verifySuccessSignIn(); + // await expect(allPages.homePage.getHomeNav()).toBeVisible({ timeout: 30000 }); + // }) + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}) + +test('Verify that user can fill and submit the Contact Us form successfully', { + tag: '@chromium', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Contact' }, + { type: 'testdino:link', description: 'https://jira.example.com/CONTACT-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Contact Us form submission on Chromium' } + ] +}, async () => { + const start = Date.now(); + await login(); + await allPages.homePage.clickOnContactUsLink(); + await allPages.contactUsPage.assertContactUsTitle(); + await allPages.contactUsPage.fillContactUsForm(); + await allPages.contactUsPage.verifySuccessContactUsFormSubmission(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}); + +test('Verify that user can submit a product review', { + tag: '@firefox', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Review' }, + { type: 'testdino:link', description: 'https://jira.example.com/REVIEW-002' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Submit product review on Firefox' } + ] +}, async () => { + const start = Date.now(); + await test.step('Login as existing user and navigate to a product', async () => { + // await login(); + }) + + await test.step('Navigate to all product section and select a product', async () => { await allPages.homePage.clickOnShopNowButton(); await allPages.allProductsPage.assertAllProductsTitle(); await allPages.allProductsPage.clickNthProduct(1); - await allPages.productDetailsPage.clickAddToCartButton(); - await allPages.productDetailsPage.clickCartIcon(); - await allPages.cartPage.clickOnCheckoutButton(); - await allPages.checkoutPage.verifyCheckoutTitle(); - await allPages.checkoutPage.selectCashOnDelivery(); - await allPages.checkoutPage.verifyCashOnDeliverySelected(); - await allPages.checkoutPage.clickOnPlaceOrder(); - await allPages.checkoutPage.verifyOrderPlacedSuccessfully(); }) -}); \ No newline at end of file + + await test.step('Submit a product review and verify submission', async () => { + await allPages.productDetailsPage.clickOnReviewsTab(); + await allPages.productDetailsPage.assertReviewsTab(); + + await allPages.productDetailsPage.clickOnWriteAReviewBtn(); + await allPages.productDetailsPage.fillReviewForm(); + await allPages.productDetailsPage.assertSubmittedReview({ + name: 'John Doe', + title: 'Great Product', + opinion: 'This product exceeded my expectations. Highly recommend!' + }); + }); + + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}); + +test('Verify that user can update personal information in profile', { + tag: '@webkit', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Profile' }, + { type: 'testdino:link', description: 'https://jira.example.com/PROFILE-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Update personal information on WebKit' } + ] +}, async () => { + const start = Date.now(); + await allPages.userPage.clickOnUserProfileIcon(); +// await allPages.userPage.updatePersonalInfo(); +// await allPages.userPage.verifyPersonalInfoUpdated(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}); + +test('Verify that user can delete a selected product from cart', { + tag: '@android', + annotation: [ + { type: 'testdino:priority', description: 'p1' }, + { type: 'testdino:feature', description: 'Cart' }, + { type: 'testdino:link', description: 'https://jira.example.com/CART-001' }, + { type: 'testdino:owner', description: 'qa-team' }, + { type: 'testdino:notify-slack', description: '#e2e-alerts' }, + { type: 'testdino:context', description: 'Delete selected product from cart on Android' } + ] +}, async () => { + const start = Date.now(); + const productName = 'GoPro HERO10 Black'; + await login(); + await allPages.inventoryPage.clickOnShopNowButton(); + await allPages.inventoryPage.clickOnAllProductsLink(); + await allPages.inventoryPage.searchProduct(productName); + await allPages.inventoryPage.verifyProductTitleVisible(productName); + await allPages.inventoryPage.clickOnAddToCartIcon(); + + await allPages.cartPage.clickOnCartIcon(); + await allPages.cartPage.verifyCartItemVisible(productName); + await allPages.cartPage.clickOnDeleteProductIcon(); + await allPages.cartPage.verifyCartItemDeleted(productName); + await allPages.cartPage.verifyEmptyCartMessage(); + await allPages.cartPage.clickOnStartShoppingButton(); + await allPages.allProductsPage.assertAllProductsTitle(); + const flowTime = Date.now() - start; + test.info().annotations.push({ + type: 'testdino:metric', + description: JSON.stringify({ + name: 'flow-time', + value: flowTime, + unit: 'ms', + threshold: 5000, + }), + }); +}); diff --git a/tests/get-users.spec.js b/tests/get-users.spec.js new file mode 100644 index 0000000..1ab0a3d --- /dev/null +++ b/tests/get-users.spec.js @@ -0,0 +1,164 @@ +// @ts-check +import { expect, test } from '@playwright/test'; + +// Base API URL - adjust this to match your actual API endpoint +const API_BASE_URL = process.env.API_BASE_URL || 'https://dummyjson.com'; +const USERS_ENDPOINT = '/users'; + +test.describe('GET Users API', () => { + + test('Fetch all users', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('users'); + expect(Array.isArray(body.users)).toBe(true); + }); + + test('Fetch user by ID = 1', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/1`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('id', 1); + expect(body).toHaveProperty('firstName'); + expect(body).toHaveProperty('lastName'); + }); + + test('Validate total users > 0', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('total'); + expect(body.total).toBeGreaterThan(0); + }); + + test('Validate user image exists', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/1`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('image'); + expect(body.image).toBeTruthy(); + expect(typeof body.image).toBe('string'); + }); + + test('Validate user 1 has firstName field', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/1`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('firstName'); + expect(typeof body.firstName).toBe('string'); + expect(body.firstName.length).toBeGreaterThan(0); + }); + + test('Invalid user ID returns 404', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/999999`); + + expect(response.status()).toBe(404); + }); + + test('default users (no query) returns data object/array', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + // Should have either 'users' array or be an array itself + expect(body.users || Array.isArray(body)).toBeTruthy(); + }); + + test('limit param returns limited results', { tag: '@api' }, async ({ request }) => { + const limit = 5; + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}?limit=${limit}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + const users = body.users || body; + const usersArray = Array.isArray(users) ? users : []; + expect(usersArray.length).toBeLessThanOrEqual(limit); + }); + + test('skip param shifts results', { tag: '@api' }, async ({ request }) => { + const skip = 5; + const limit = 10; + + // Get first page + const response1 = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}?limit=${limit}&skip=0`); + const body1 = await response1.json(); + const users1 = body1.users || body1; + const firstUser1 = Array.isArray(users1) ? users1[0] : null; + + // Get second page with skip + const response2 = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}?limit=${limit}&skip=${skip}`); + const body2 = await response2.json(); + const users2 = body2.users || body2; + const firstUser2 = Array.isArray(users2) ? users2[0] : null; + + expect(response1.status()).toBe(200); + expect(response2.status()).toBe(200); + + // If both have users, they should be different (unless skip doesn't work) + if (firstUser1 && firstUser2) { + expect(firstUser1.id).not.toBe(firstUser2.id); + } + }); + + test('sorting / search query (if supported) returns filtered results', { tag: '@api' }, async ({ request }) => { + // Try search query parameter (common patterns: q, search, query) + const searchTerm = 'john'; + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/search?q=${searchTerm}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + const users = body.users || body; + const usersArray = Array.isArray(users) ? users : []; + + if (usersArray.length > 0) { + // At least verify the response structure is valid + expect(Array.isArray(usersArray)).toBe(true); + } + }); + + test('delayed response (3s) should return 200', { tag: '@api' }, async ({ request }) => { + const isRetry = test.info().retry > 0; + if (!isRetry) { + expect(true).toBe(false); + } + + const delay = 3; + const startTime = Date.now(); + + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}?delay=${delay}`, { + timeout: 10000 // 10 second timeout + }); + + const endTime = Date.now(); + const duration = (endTime - startTime) / 1000; + + expect(response.status()).toBe(200); + expect(duration).toBeGreaterThanOrEqual(delay - 0.5); + + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + }); + + test('enforce timeout (expect to fail if too slow) — set short timeout', { tag: '@api' }, async ({ request }) => { + const delay = 5; + const shortTimeout = 2000; + + try { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}?delay=${delay}`, { + timeout: shortTimeout + }); + + + expect(response.status()).toBe(200); + } catch (error) { + expect(error.message).toMatch(/timeout|Timeout/i); + } + }); +}); diff --git a/tests/post-api.spec.js b/tests/post-api.spec.js new file mode 100644 index 0000000..38059eb --- /dev/null +++ b/tests/post-api.spec.js @@ -0,0 +1,98 @@ +// @ts-check +import { expect, test } from '@playwright/test'; + +// Base API URL - adjust this to match your actual API endpoint +const API_BASE_URL = process.env.API_BASE_URL || 'https://dummyjson.com'; +const USERS_ENDPOINT = '/users'; +const ADD_ENDPOINT = '/users/add'; + +test.describe('POST Create User API', () => { + + test('Bad endpoint returns 404', { tag: '@api' }, async ({ request }) => { + const userData = { + firstName: 'Test', + lastName: 'User' + }; + + const response = await request.post(`${API_BASE_URL}${USERS_ENDPOINT}/invalid-endpoint`, { + data: userData + }); + + expect(response.status()).toBe(404); + }); + + test('Invalid JSON payload handling', { tag: '@api' }, async ({ request }) => { + const response = await request.post(`${API_BASE_URL}${ADD_ENDPOINT}`, { + data: 'invalid json string', + headers: { + 'Content-Type': 'application/json' + } + }); + + // Should return 400 Bad Request for invalid JSON + expect([400, 422]).toContain(response.status()); + }); + + test('Too large ID param should return 404', { tag: '@api' }, async ({ request }) => { + const tooLargeId = 999999999; + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/${tooLargeId}`); + + expect(response.status()).toBe(404); + }); + + test('Deleting invalid id returns 200/response but not crash', { tag: '@api' }, async ({ request }) => { + const invalidId = 999999; + const response = await request.delete(`${API_BASE_URL}${USERS_ENDPOINT}/${invalidId}`); + + expect([200, 404]).toContain(response.status()); + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + }); + + test('PUT: Invalid method usage returns appropriate response (no 500)', { tag: '@api' }, async ({ request }) => { + const userId = 1; + const updateData = { + firstName: 'Updated' + }; + + // Try PUT on an endpoint that might not support it properly + const response = await request.put(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}/invalid`, { + data: updateData + }); + + // Should return appropriate error (400, 404, 405) but not 500 + expect([400, 404, 405, 200]).toContain(response.status()); + expect(response.status()).not.toBe(500); + }); + + test('user schema contains expected keys', { tag: '@api' }, async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}/1`); + + expect(response.status()).toBe(200); + const body = await response.json(); + + // Validate expected keys in user schema + const expectedKeys = ['id', 'firstName', 'lastName']; + expectedKeys.forEach(key => { + expect(body).toHaveProperty(key); + }); + }); + + test('users list contains objects with id and email', { tag: '@api' },async ({ request }) => { + const response = await request.get(`${API_BASE_URL}${USERS_ENDPOINT}`); + + expect(response.status()).toBe(200); + const body = await response.json(); + const users = body.users || body; + const usersArray = Array.isArray(users) ? users : []; + + if (usersArray.length > 0) { + // Check first user has id and email + const firstUser = usersArray[0]; + expect(firstUser).toHaveProperty('id'); + if (firstUser.email !== undefined) { + expect(typeof firstUser.email).toBe('string'); + } + } + }); +}); diff --git a/tests/updateUser.spec.js b/tests/updateUser.spec.js new file mode 100644 index 0000000..ae4e06a --- /dev/null +++ b/tests/updateUser.spec.js @@ -0,0 +1,134 @@ +// @ts-check +import { expect, test } from '@playwright/test'; + +// Base API URL - adjust this to match your actual API endpoint +const API_BASE_URL = process.env.API_BASE_URL || 'https://dummyjson.com'; +const USERS_ENDPOINT = '/users'; +const AUTH_ENDPOINT = '/auth/login'; + +test.describe('PUT / PATCH Update User API', () => { + + test('Update user details', { tag: '@api' }, async ({ request }) => { + const userId = 1; + const updateData = { + firstName: 'John', + lastName: 'Doe', + age: 30 + }; + + // Try PUT first, fallback to PATCH if needed + const response = await request.put(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`, { + data: updateData + }); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('id', userId); + expect(body).toHaveProperty('firstName', updateData.firstName); + expect(body).toHaveProperty('lastName', updateData.lastName); + }); + + test('Update user with empty payload', { tag: '@api' }, async ({ request }) => { + const userId = 2; + const response = await request.put(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`, { + data: {} + }); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + expect(body).toHaveProperty('id', userId); + }); + + test('Update only one field', { tag: '@api' }, async ({ request }) => { + const userId = 3; + const updateData = { + firstName: 'UpdatedFirstName' + }; + + // Use PATCH for partial update + const response = await request.patch(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`, { + data: updateData + }); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('id', userId); + expect(body).toHaveProperty('firstName', updateData.firstName); + }); + + test('Validate returned name field', { tag: '@api' }, async ({ request }) => { + const userId = 4; + const updateData = { + firstName: 'Jane', + lastName: 'Smith' + }; + + const response = await request.put(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`, { + data: updateData + }); + + expect(response.status()).toBe(200); + const body = await response.json(); + expect(body).toHaveProperty('firstName'); + expect(body).toHaveProperty('lastName'); + expect(typeof body.firstName).toBe('string'); + expect(typeof body.lastName).toBe('string'); + expect(body.firstName).toBe(updateData.firstName); + expect(body.lastName).toBe(updateData.lastName); + }); + + test('Update and validate response contains updatedAt simulation', { tag: '@api' }, async ({ request }) => { + const userId = 5; + const updateData = { + firstName: 'Updated', + lastName: 'User' + }; + + const response = await request.put(`${API_BASE_URL}${USERS_ENDPOINT}/${userId}`, { + data: updateData + }); + + expect(response.status()).toBe(200); + const body = await response.json(); + + // Check for updatedAt or similar timestamp field + const hasTimestamp = body.hasOwnProperty('updatedAt') || + body.hasOwnProperty('updated') || + body.hasOwnProperty('modifiedAt'); + + // At minimum, validate the response structure + expect(body).toBeInstanceOf(Object); + expect(body).toHaveProperty('id', userId); + }); + + test('Login failure (invalid creds)', { tag: '@api' }, async ({ request }) => { + const loginData = { + username: 'invaliduser', + password: 'wrongpassword' + }; + + const response = await request.post(`${API_BASE_URL}${AUTH_ENDPOINT}`, { + data: loginData + }); + + // Should return error status (400 or 401) + expect([400, 401, 403]).toContain(response.status()); + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + }); + + test('Login missing fields returns 400', { tag: '@api' }, async ({ request }) => { + const loginData = { + username: 'kminchelle' + }; + + const response = await request.post(`${API_BASE_URL}${AUTH_ENDPOINT}`, { + data: loginData + }); + + expect(response.status()).toBe(400); + const body = await response.json(); + expect(body).toBeInstanceOf(Object); + }); +}); diff --git a/tests/visual.spec.js b/tests/visual.spec.js new file mode 100644 index 0000000..a410be9 --- /dev/null +++ b/tests/visual.spec.js @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; +import AllPages from '../pages/AllPages.js'; + +let allPages; + +test.beforeEach(async ({ page }) => { + allPages = new AllPages(page); + await page.goto('https://github.com/login'); +}); + +test.describe('Visual Comparison', () => { + + test.describe('GitHub Login Page', () => { + test('visual comparison demo test', { tag: ['@visual', '@chromium'] }, async ({ page }) => { + await page.goto('https://github.com/login'); + await expect(page).toHaveScreenshot('github-login.png'); + + await page.getByRole('textbox', { name: 'Username or email address' }).click(); + await page.getByRole('textbox', { name: 'Username or email address' }).fill('test'); + await expect(page).toHaveScreenshot('github-login-changed.png'); + }); + }); +});