diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 180af99..fdaae25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -304,6 +304,7 @@ # .github/workflows/playwright-daily.yml # Runs Playwright test shards with TestDino (staging) + merges reports + name: Run Playwright tests on: @@ -344,6 +345,7 @@ jobs: echo "STATE=${{ secrets.STATE }}" >> .env echo "COUNTRY=${{ secrets.COUNTRY }}" >> .env echo "ZIP_CODE=${{ secrets.ZIP_CODE }}" >> .env + # ✅ Cache npm dependencies - name: Cache npm dependencies uses: actions/cache@v3 @@ -352,65 +354,27 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- + # ✅ Install dependencies + TestDino (.tgz) + Playwright browsers - name: Install dependencies & browsers run: | - npm ci - npx playwright install --with-deps chromium firefox webkit - # ✅ Run Playwright shard with TestDino (STAGING) + npm install + npm install --save-dev @testdino/playwright@latest + npx playwright install --with-deps chromium webkit firefox + + # ✅ Run Playwright shard with TestDino - name: Run Playwright shard with TestDino env: - TESTDINO_TOKEN: ${{ secrets.TESTDINO_TOKEN }} - TESTDINO_SERVER_URL: https://staging-api.testdino.com + TESTDINO_TOKEN: ${{ secrets.TESTDINO_TOKEN }} run: | npx playwright test \ --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} \ - --grep "@chromium|@firefox|@webkit|@android|@ios|@api" \ - # ✅ Upload blob report (needed for merge) + --grep="@chromium|@firefox|@webkit|@android|@ios|@api" \ + - name: Upload blob report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shardIndex }} path: ./blob-report - retention-days: 1 - - merge-reports: - name: Merge Reports - needs: run-tests - if: always() - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Setup Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: '20' - - # ✅ Install deps (TestDino not required here) - - name: Install dependencies - run: | - npm ci - npx playwright install --with-deps - # ✅ Download all shard blob reports - - name: Download all blob reports - uses: actions/download-artifact@v4 - with: - path: ./all-blob-reports - pattern: blob-report-* - merge-multiple: true - - # ✅ Merge Playwright HTML report - - name: Merge Playwright HTML report - run: | - npx playwright merge-reports ./all-blob-reports - # ✅ Upload merged Playwright report - - name: Upload combined Playwright report - uses: actions/upload-artifact@v4 - with: - name: Playwright Test Report - path: ./playwright-report - retention-days: 14 \ No newline at end of file + retention-days: 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 668361f..34c823f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ node_modules/ # Playwright /playwright/.auth/ + +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 9db44c0..7ad0a4f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Test Health](https://staging-api.testdino.com/api/badge/project_6994031bf797fc2987c032d8.svg)](https://staging.testdino.com/org_698309c81aa085a66fc43343/projects/project_6994031bf797fc2987c032d8) [![Flaky](https://staging-api.testdino.com/api/badge/project_6994031bf797fc2987c032d8.svg?type=flaky)](https://staging.testdino.com/org_698309c81aa085a66fc43343/projects/project_6994031bf797fc2987c032d8) [![Tests](https://staging-api.testdino.com/api/badge/project_6994031bf797fc2987c032d8.svg?type=tests)](https://staging.testdino.com/org_698309c81aa085a66fc43343/projects/project_6994031bf797fc2987c032d8) # Ecommerce demo store - Playwright (javascript) tests Automated end-to-end tests for Ecommerce demo store using [Playwright](https://playwright.dev/). diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7517762..0000000 --- a/package-lock.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "name": "playwright-js-framework", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "playwright-js-framework", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "dotenv": "^17.2.1", - "playwright": "^1.55.0" - }, - "devDependencies": { - "@playwright/test": "^1.57.0", - "@types/node": "^24.1.0" - } - }, - "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@types/node": { - "version": "24.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", - "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.57.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/package.json b/package.json index 9c91365..17ccf1f 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@types/node": "^24.1.0" }, "dependencies": { + "@testdino/playwright": "^1.0.10", "dotenv": "^17.2.1", "playwright": "^1.55.0" } diff --git a/playwright.config.js b/playwright.config.js index b24d7ee..f6726ea 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -21,6 +21,11 @@ export default defineConfig({ }], ['blob', { outputDir: 'blob-report' }], // Blob reporter for merging ['json', { outputFile: './playwright-report/report.json' }], + + ['@testdino/playwright', { + token: process.env.TESTDINO_TOKEN, + serverUrl: 'https://staging-api.testdino.com', + }], ], use: { diff --git a/tests/cart_checkout.spec.js b/tests/cart_checkout.spec.js index 78fb717..be3501c 100644 --- a/tests/cart_checkout.spec.js +++ b/tests/cart_checkout.spec.js @@ -36,7 +36,15 @@ test.describe('Application E2E Tests', () => { ========================================= */ test.describe('Delete Product from Cart', () => { - test('User can delete selected product from cart',{tag: '@ios'}, async () => { + test('User can delete selected product from cart', { + tag: ['@ios', '@regression', '@cart'], + annotation: [ + { type: 'testdino:priority', description: 'P1' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'cart' }, + { type: 'testdino:context', description: 'Cart item removal flow — search product, add to cart, then delete. Verifies empty cart state.' }, + ], + }, async () => { const productName = 'GoPro HERO10 Black'; const startTime = Date.now(); @@ -77,7 +85,15 @@ test.describe('Application E2E Tests', () => { ========================================= */ test.describe('Single Order and Cancellation', () => { - test('New user can place and cancel order @chromium', {tag: '@chromium'}, async () => { + test('New user can place and cancel order', { + tag: ['@chromium', '@smoke', '@e2e'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'checkout' }, + { type: 'testdino:context', description: 'Full E2E: register → login → browse → add to cart → checkout → place order → cancel. Critical path for new user conversion.' }, + ], + }, async () => { const email = `test+${Date.now()}@test.com`; const startTime = Date.now(); let registrationTime, checkoutTime; @@ -171,7 +187,15 @@ test.describe('Application E2E Tests', () => { ========================================= */ test.describe('Update Personal Information', () => { - test('User can update personal info @firefox', {tag: '@firefox'}, async () => { + test('User can update personal info', { + tag: ['@firefox', '@regression', '@profile'], + annotation: [ + { type: 'testdino:priority', description: 'P2' }, + { type: 'testdino:owner', description: 'team-frontend' }, + { type: 'testdino:feature', description: 'profile' }, + { type: 'testdino:context', description: 'User profile update flow. Verifies personal info can be modified after login.' }, + ], + }, async () => { const startTime = Date.now(); await login(); await allPages.userPage.clickOnUserProfileIcon(); diff --git a/tests/login.spec.js b/tests/login.spec.js index 614d905..c31c37f 100644 --- a/tests/login.spec.js +++ b/tests/login.spec.js @@ -22,7 +22,15 @@ async function logout() { test.describe('Login', () => { test.describe('Authentication', () => { - test('Verify that user can login and logout successfully ', {tag: '@chromium'}, async () => { + test('Verify that user can login and logout successfully ', { + tag: ['@chromium', '@smoke', '@auth'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-auth' }, + { type: 'testdino:feature', description: 'auth' }, + { type: 'testdino:context', description: 'Core login/logout flow. Validates authentication against storedemo. Failure blocks all authenticated tests.' }, + ], + }, async () => { const loginStart = Date.now(); await login(); const loginTime = Date.now() - loginStart; diff --git a/tests/navigation.spec.js b/tests/navigation.spec.js index f7e712e..0e97c56 100644 --- a/tests/navigation.spec.js +++ b/tests/navigation.spec.js @@ -43,7 +43,15 @@ test.describe('Navigation', () => { ========================================= */ test.describe('Verify Navbar Links', () => { - test('Verify that all the navbar links work correctly', {tag: '@firefox'}, async () => { + test('Verify that all the navbar links work correctly', { + tag: ['@firefox', '@smoke', '@navigation'], + annotation: [ + { type: 'testdino:priority', description: 'P1' }, + { type: 'testdino:owner', description: 'team-frontend' }, + { type: 'testdino:feature', description: 'navigation' }, + { type: 'testdino:context', description: 'Validates all top-level navbar links — Home, All Products, Contact Us, About Us. Core navigation smoke test.' }, + ], + }, async () => { const startTime = Date.now(); let navigationCount = 0; @@ -107,7 +115,15 @@ test.describe('Navigation', () => { ========================================= */ test.describe('Submit Contact Us Form', () => { - test('Verify that user can submit Contact Us form successfully', {tag: '@firefox'}, async () => { + test('Verify that user can submit Contact Us form successfully', { + tag: ['@firefox', '@regression', '@contact'], + annotation: [ + { type: 'testdino:priority', description: 'P2' }, + { type: 'testdino:owner', description: 'team-frontend' }, + { type: 'testdino:feature', description: 'contact' }, + { type: 'testdino:context', description: 'Contact Us form submission with authenticated user. Validates form fill and success message.' }, + ], + }, async () => { const startTime = Date.now(); await login(); diff --git a/tests/orders.spec.js b/tests/orders.spec.js index 7766103..52c3430 100644 --- a/tests/orders.spec.js +++ b/tests/orders.spec.js @@ -44,7 +44,15 @@ test.describe('Orders', () => { test.describe('Existing User Address CRUD', () => { test( - 'Verify that user can add, edit, and delete addresses after login', {tag: '@ios'}, async () => { + 'Verify that user can add, edit, and delete addresses after login', { + tag: ['@ios', '@regression', '@address'], + annotation: [ + { type: 'testdino:priority', description: 'P1' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'address' }, + { type: 'testdino:context', description: 'Full address CRUD cycle — add, edit, delete. Tests address management for existing authenticated users.' }, + ], + }, async () => { const startTime = Date.now(); let addTime, editTime, deleteTime; @@ -125,7 +133,15 @@ test.describe('Orders', () => { test.describe('New User Address Creation', () => { test( - 'Verify that new user can add address from Address section', {tag: '@android'}, async () => { + 'Verify that new user can add address from Address section', { + tag: ['@android', '@regression', '@address'], + annotation: [ + { type: 'testdino:priority', description: 'P2' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'address' }, + { type: 'testdino:context', description: 'New user address creation from the Address section. Validates the add-new-address menu and form.' }, + ], + }, async () => { const startTime = Date.now(); await login(); @@ -166,7 +182,15 @@ test.describe('Orders', () => { test.describe('Multiple Quantity Purchase', () => { test( - 'Verify that user can purchase multiple quantities in a single order', {tag: '@android'}, async () => { + 'Verify that user can purchase multiple quantities in a single order', { + tag: ['@android', '@smoke', '@orders'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'orders' }, + { type: 'testdino:context', description: 'Multi-quantity order placement. Searches product, increases cart quantity to 3, completes Cash on Delivery checkout.' }, + ], + }, async () => { const productName = 'GoPro HERO10 Black'; const startTime = Date.now(); const quantity = 3; diff --git a/tests/product.spec.js b/tests/product.spec.js index af208ed..787a95a 100644 --- a/tests/product.spec.js +++ b/tests/product.spec.js @@ -28,7 +28,15 @@ test.describe('Application E2E Tests', () => { ============================================= */ test.describe('Product Reviews', () => { - test('User can submit a product review', {tag: '@firefox'}, async () => { + test('User can submit a product review', { + tag: ['@firefox', '@regression', '@reviews'], + annotation: [ + { type: 'testdino:priority', description: 'P2' }, + { type: 'testdino:owner', description: 'team-product' }, + { type: 'testdino:feature', description: 'reviews' }, + { type: 'testdino:context', description: 'Tests review submission flow on product detail page. Requires authenticated user.' }, + ], + }, async () => { const startTime = Date.now(); await test.step('Login and open product', async () => { @@ -72,7 +80,15 @@ test.describe('Application E2E Tests', () => { ); }); - test('User can edit and delete a product review', {tag: '@firefox'}, async () => { + test('User can edit and delete a product review', { + tag: ['@firefox', '@regression', '@reviews'], + annotation: [ + { type: 'testdino:priority', description: 'P2' }, + { type: 'testdino:owner', description: 'team-product' }, + { type: 'testdino:feature', description: 'reviews' }, + { type: 'testdino:context', description: 'Full CRUD cycle for reviews — submit, edit, then delete. Depends on review creation succeeding first.' }, + ], + }, async () => { const startTime = Date.now(); let editTime; @@ -130,7 +146,15 @@ test.describe('Application E2E Tests', () => { test.describe('Product Filtering', () => { /* ------------ Test ------------ */ - test('User can filter products by price range', {tag: '@webkit'}, async () => { + test('User can filter products by price range', { + tag: ['@webkit', '@regression', '@search'], + annotation: [ + { type: 'testdino:priority', description: 'P1' }, + { type: 'testdino:owner', description: 'team-product' }, + { type: 'testdino:feature', description: 'search' }, + { type: 'testdino:context', description: 'Validates price-range slider filter. Uses range 10000-20000.' }, + ], + }, async () => { const startTime = Date.now(); await login(); await allPages.homePage.clickOnShopNowButton(); @@ -166,7 +190,15 @@ test.describe('Application E2E Tests', () => { test.describe('Wishlist to Checkout', () => { /* ------------ Test ------------ */ - test('User can wishlist product and checkout', {tag: '@webkit'}, async () => { + test('User can wishlist product and checkout', { + tag: ['@webkit', '@smoke', '@checkout'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'checkout' }, + { type: 'testdino:context', description: 'End-to-end wishlist-to-purchase flow. Covers wishlist add, cart transfer, and Cash on Delivery checkout.' }, + ], + }, async () => { const startTime = Date.now(); let wishlistTime, checkoutTime; @@ -238,7 +270,15 @@ test.describe('Application E2E Tests', () => { test.describe('Order Journey', () => { /* ------------ Test ------------ */ - test('User can place and cancel order', {tag: '@webkit'}, async () => { + test('User can place and cancel order', { + tag: ['@webkit', '@regression', '@orders'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'orders' }, + { type: 'testdino:context', description: 'Order placement via product search then immediate cancellation. Validates full order lifecycle.' }, + ], + }, async () => { const productName = 'GoPro HERO10 Black'; const startTime = Date.now(); let orderPlacementTime, cancellationTime; @@ -297,7 +337,15 @@ test.describe('Application E2E Tests', () => { test.describe('Registration to Order', () => { /* ------------ Test ------------ */ - test('New user can register and place order', {tag: '@chromium'}, async () => { + test('New user can register and place order', { + tag: ['@chromium', '@smoke', '@e2e'], + annotation: [ + { type: 'testdino:priority', description: 'P0' }, + { type: 'testdino:owner', description: 'team-commerce' }, + { type: 'testdino:feature', description: 'onboarding' }, + { type: 'testdino:context', description: 'Full new-user journey: registration → login → browse → add to cart → checkout. Critical conversion funnel test.' }, + ], + }, async () => { const email = `test+${Date.now()}@test.com`; const startTime = Date.now(); diff --git a/tests/visual.spec.js b/tests/visual.spec.js index 80217d2..900dc10 100644 --- a/tests/visual.spec.js +++ b/tests/visual.spec.js @@ -11,7 +11,15 @@ test.beforeEach(async ({ page }) => { test.describe('Visual', () => { test.describe('Screenshot Tests', () => { - test('test', {tag: '@chromium'}, async ({ page }) => { + test('test', { + tag: ['@chromium', '@regression', '@visual'], + annotation: [ + { type: 'testdino:priority', description: 'P3' }, + { type: 'testdino:owner', description: 'team-frontend' }, + { type: 'testdino:feature', description: 'visual' }, + { type: 'testdino:context', description: 'Visual regression test against GitHub login page. Captures initial and filled-state screenshots for comparison.' }, + ], + }, async ({ page }) => { const startTime = Date.now(); await page.goto('https://github.com/login'); await expect(page).toHaveScreenshot('github-login-initial.png');