diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 31930f9..5a66824 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,14 +1,16 @@ name: Lint modules on: - pull_request: - branches: [ master ] - paths-ignore: - - "**/README.md" push: paths-ignore: - "**/README.md" workflow_dispatch: + inputs: + modules: + description: 'Modules to lint (comma-separated or "all" for all modules)' + required: false + default: 'all' + type: string jobs: pre_job: @@ -21,7 +23,7 @@ jobs: with: github_token: ${{ github.token }} paths_ignore: '["**/README.md"]' - do_not_skip: '["push"]' + do_not_skip: '["push", "workflow_dispatch"]' detect-changes: needs: pre_job @@ -34,7 +36,10 @@ jobs: - uses: dorny/paths-filter@v3 id: filter + if: github.event_name != 'workflow_dispatch' with: + base: ${{ github.event.before }} + ref: ${{ github.event.after }} filters: | payments: - 'pos-module-payments/**' @@ -68,11 +73,29 @@ jobs: - name: Set matrix for changed modules id: set-matrix run: | - # Extract module names where filter output is "true" - modules=$(echo '${{ toJSON(steps.filter.outputs) }}' | jq -c '[to_entries[] | select(.value == "true") | .key]') + # Check if this is a manual trigger + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "Manual trigger detected" + manual_input="${{ github.event.inputs.modules }}" + echo "Manual input: $manual_input" + + if [ "$manual_input" = "all" ] || [ -z "$manual_input" ]; then + # Lint all modules + modules='["payments","user","chat","common-styling","tests","core","oauth-facebook","oauth-github","oauth-google","openai","reports","data-export-api","payments-stripe","payments-example-gateway"]' + else + # Parse comma-separated list, strip pos-module- prefix, and create array + modules=$(echo "$manual_input" | jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; "") | gsub("^pos-module-"; ""))') + fi + echo "Manual trigger modules: $modules" + else + # Automatic detection from git changes + changes='${{ steps.filter.outputs.changes }}' + echo "Detected changes: $changes" + modules="$changes" + echo "Changed modules for linting: $modules" + fi echo "matrix=$modules" >> $GITHUB_OUTPUT - echo "Changed modules for linting: $modules" lint-platformos-check: needs: detect-changes @@ -168,3 +191,41 @@ jobs: run: | echo "platformos-check failed — marking job as failed" exit 1 + + conclusion: + needs: [detect-changes, lint-platformos-check] + if: always() + runs-on: ubuntu-latest + steps: + - name: Generate workflow summary + run: | + if [ "${{ needs.detect-changes.outputs.changed-modules }}" = "[]" ] || [ "${{ needs.detect-changes.result }}" = "skipped" ]; then + echo "## Linting - Skipped" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-changes.result }}" = "skipped" ]; then + echo "Workflow was skipped by duplicate action check." >> $GITHUB_STEP_SUMMARY + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "Manual trigger with no matching modules selected." >> $GITHUB_STEP_SUMMARY + else + echo "No modules were changed in this push." >> $GITHUB_STEP_SUMMARY + fi + else + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "## Linting - Completed (Manual Trigger)" >> $GITHUB_STEP_SUMMARY + else + echo "## Linting - Completed" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "Linting ran for the following modules:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo '${{ needs.detect-changes.outputs.changed-modules }}' | jq -r '.[] | "- " + .' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.lint-platformos-check.result }}" = "success" ]; then + echo "Result: All checks passed" >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.lint-platformos-check.result }}" = "skipped" ]; then + echo "Result: Linting was skipped" >> $GITHUB_STEP_SUMMARY + else + echo "Result: Some checks failed - see job output for details" >> $GITHUB_STEP_SUMMARY + fi + fi diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index ffa02f5..358794b 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -1,13 +1,15 @@ name: E2E tests on: - pull_request: - branches: [ master ] - paths-ignore: - - "**/README.md" push: paths-ignore: - "**/README.md" workflow_dispatch: + inputs: + modules: + description: 'Modules to test (comma-separated: user, chat, common-styling, payments-example-gateway, or "all")' + required: false + default: 'all' + type: string jobs: pre_job: @@ -20,7 +22,7 @@ jobs: with: github_token: ${{ github.token }} paths_ignore: '["**/README.md"]' - do_not_skip: '["push"]' + do_not_skip: '["push", "workflow_dispatch"]' detect-changes: needs: pre_job @@ -33,7 +35,10 @@ jobs: - uses: dorny/paths-filter@v3 id: filter + if: github.event_name != 'workflow_dispatch' with: + base: ${{ github.event.before }} + ref: ${{ github.event.after }} filters: | user: - 'pos-module-user/**' @@ -41,6 +46,8 @@ jobs: - 'pos-module-chat/**' common-styling: - 'pos-module-common-styling/**' + payments-example-gateway: + - 'pos-module-payments-example-gateway/**' - name: Set matrix for changed modules id: set-matrix @@ -65,17 +72,45 @@ jobs: "path": "pos-module-common-styling", "deploy-script": "pos-cli data clean --include-schema --auto-confirm\npos-cli deploy", "test-commands": "npm run pw-tests" + }, + "payments-example-gateway": { + "module": "payments-example-gateway", + "path": "pos-module-payments-example-gateway", + "deploy-script": "npm run test:setup:local\n./tests/data/seed/seed.sh", + "test-commands": "npm run pw-tests" } } EOF - # Extract changed modules and map to their configurations - modules=$(echo '${{ toJSON(steps.filter.outputs) }}' | \ - jq -c --slurpfile config /tmp/module-config.json \ - '[to_entries[] | select(.value == "true") | .key as $m | $config[0][$m] | select(. != null)]') + # Check if this is a manual trigger + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "Manual trigger detected" + manual_input="${{ github.event.inputs.modules }}" + echo "Manual input: $manual_input" + + if [ "$manual_input" = "all" ] || [ -z "$manual_input" ]; then + # Test all modules + modules=$(jq -c '[.user, .chat, ."common-styling", ."payments-example-gateway"]' /tmp/module-config.json) + else + # Parse comma-separated list, strip pos-module- prefix, and map to configurations + modules=$(echo "$manual_input" | \ + jq -R 'split(",") | map(gsub("^\\s+|\\s+$"; "") | gsub("^pos-module-"; ""))' | \ + jq -c --slurpfile config /tmp/module-config.json \ + 'map(. as $m | $config[0][$m] | select(. != null))') + fi + echo "Manual trigger modules: $modules" + else + # Automatic detection from git changes + changes='${{ steps.filter.outputs.changes }}' + echo "Detected changes: $changes" + + # Extract changed modules and map to their configurations + modules=$(jq -nc --argjson changes "$changes" --slurpfile config /tmp/module-config.json \ + '$changes | map(. as $m | $config[0][$m] | select(. != null))') + echo "Changed modules matrix: $modules" + fi echo "matrix=$modules" >> $GITHUB_OUTPUT - echo "Changed modules matrix: $modules" test-e2e: needs: detect-changes @@ -148,3 +183,41 @@ jobs: method: release repository-url: ${{ vars.CI_PS_REPOSITORY_URL }} pos-ci-repo-token: ${{ secrets.POS_CI_PS_REPO_ACCESS_TOKEN }} + + conclusion: + needs: [detect-changes, test-e2e] + if: always() + runs-on: ubuntu-latest + steps: + - name: Generate workflow summary + run: | + if [ "${{ needs.detect-changes.outputs.changed-modules }}" = "[]" ] || [ "${{ needs.detect-changes.result }}" = "skipped" ]; then + echo "## E2E Tests - Skipped" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.detect-changes.result }}" = "skipped" ]; then + echo "Workflow was skipped by duplicate action check." >> $GITHUB_STEP_SUMMARY + elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "Manual trigger with no matching modules selected." >> $GITHUB_STEP_SUMMARY + else + echo "No modules with E2E tests were changed in this push." >> $GITHUB_STEP_SUMMARY + fi + else + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "## E2E Tests - Completed (Manual Trigger)" >> $GITHUB_STEP_SUMMARY + else + echo "## E2E Tests - Completed" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "Tests ran for the following modules:" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo '${{ needs.detect-changes.outputs.changed-modules }}' | jq -r '.[] | "- " + .module' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ needs.test-e2e.result }}" = "success" ]; then + echo "Result: All tests passed" >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.test-e2e.result }}" = "skipped" ]; then + echo "Result: Tests were skipped" >> $GITHUB_STEP_SUMMARY + else + echo "Result: Some tests failed - check job output for details" >> $GITHUB_STEP_SUMMARY + fi + fi diff --git a/pos-module-chat/package-lock.json b/pos-module-chat/package-lock.json index e619fa1..d29dfe6 100644 --- a/pos-module-chat/package-lock.json +++ b/pos-module-chat/package-lock.json @@ -5,17 +5,18 @@ "packages": { "": { "devDependencies": { - "@playwright/test": "^1.49.1", + "@playwright/test": "^1.58.2", "@types/node": "^22.10.6" } }, "node_modules/@playwright/test": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", - "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.1" + "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -39,6 +40,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -48,12 +50,13 @@ } }, "node_modules/playwright": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", - "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.49.1" + "playwright-core": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -66,10 +69,11 @@ } }, "node_modules/playwright-core": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", - "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, diff --git a/pos-module-chat/package.json b/pos-module-chat/package.json index 768f295..1837399 100644 --- a/pos-module-chat/package.json +++ b/pos-module-chat/package.json @@ -5,7 +5,7 @@ "install: playwright": "npx playwright install" }, "devDependencies": { - "@playwright/test": "^1.49.1", + "@playwright/test": "^1.58.2", "@types/node": "^22.10.6" } } diff --git a/pos-module-common-styling/package-lock.json b/pos-module-common-styling/package-lock.json index 2d6e70f..5bbf3e7 100644 --- a/pos-module-common-styling/package-lock.json +++ b/pos-module-common-styling/package-lock.json @@ -22,6 +22,7 @@ "rollup-plugin-postcss": "^4.0.2" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", @@ -162,6 +163,22 @@ "@lezer/highlight": "^1.0.0" } }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2126,6 +2143,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", diff --git a/pos-module-common-styling/package.json b/pos-module-common-styling/package.json index 039a0fc..9a86a28 100644 --- a/pos-module-common-styling/package.json +++ b/pos-module-common-styling/package.json @@ -4,6 +4,7 @@ "pw-tests": "playwright test tests --project=style-guide-tests" }, "devDependencies": { + "@playwright/test": "^1.58.2", "@rollup/plugin-commonjs": "^29.0.0", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", diff --git a/pos-module-payments-example-gateway/package-lock.json b/pos-module-payments-example-gateway/package-lock.json new file mode 100644 index 0000000..0addaf3 --- /dev/null +++ b/pos-module-payments-example-gateway/package-lock.json @@ -0,0 +1,91 @@ +{ + "name": "pos-module-payments-example-gateway", + "version": "0.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pos-module-payments-example-gateway", + "version": "0.1.1", + "hasInstallScript": true, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/node": "^22.0.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + } + } +} diff --git a/pos-module-payments-example-gateway/package.json b/pos-module-payments-example-gateway/package.json new file mode 100644 index 0000000..1fa0830 --- /dev/null +++ b/pos-module-payments-example-gateway/package.json @@ -0,0 +1,17 @@ +{ + "name": "pos-module-payments-example-gateway", + "version": "0.1.1", + "private": true, + "scripts": { + "postinstall": "npx playwright install chromium", + "test:setup": "node scripts/setup-tests.js --marketplace", + "test:setup:local": "node scripts/setup-tests.js --local", + "test:deploy": "cd tests/post_import && pos-cli deploy dev", + "test:clean": "rm -rf tests/post_import/modules tests/post_import/.pos tests/post_import/pos-modules.json tests/post_import/pos-modules.lock.json", + "pw-tests": "playwright test tests --project=smoke-tests" + }, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/node": "^22.0.0" + } +} diff --git a/pos-module-payments-example-gateway/playwright.config.ts b/pos-module-payments-example-gateway/playwright.config.ts new file mode 100644 index 0000000..044b36e --- /dev/null +++ b/pos-module-payments-example-gateway/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from '@playwright/test'; +import process from 'process'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 3 : 3, + reporter: [ + ['list'], + ['html', { outputFolder: 'playwright-report', open: 'never' }], + ], + use: { + baseURL: process.env.MPKIT_URL, + screenshot: { mode: 'only-on-failure', fullPage: true }, + trace: 'retain-on-failure', + }, + projects: [ + { + name: 'smoke-tests', + testMatch: /.*\.spec\.ts/, + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/pos-module-payments-example-gateway/scripts/setup-tests.js b/pos-module-payments-example-gateway/scripts/setup-tests.js new file mode 100644 index 0000000..3a8e6a7 --- /dev/null +++ b/pos-module-payments-example-gateway/scripts/setup-tests.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const useLocal = process.argv.includes('--local'); +const testDir = path.join(__dirname, '..', 'tests', 'post_import'); +const modulesDir = path.join(testDir, 'modules'); + +console.log(`\nSetting up E2E test environment (${useLocal ? 'LOCAL' : 'MARKETPLACE'} mode)\n`); + +// Step 1: Ensure test directory structure exists +console.log('Creating directory structure...'); +if (!fs.existsSync(modulesDir)) { + fs.mkdirSync(modulesDir, { recursive: true }); +} + +// Step 2: Copy .pos config to test directory +console.log('Copying .pos configuration...'); +const posSource = path.join(__dirname, '..', '.pos'); +const posDest = path.join(testDir, '.pos'); +if (fs.existsSync(posSource)) { + fs.copyFileSync(posSource, posDest); + console.log(' .pos copied'); +} else { + console.warn(' Warning: .pos file not found at root'); +} + +// Step 3: Install/copy dependency modules +console.log(`\nInstalling dependency modules (${useLocal ? 'from monorepo' : 'from marketplace'})...`); + +if (useLocal) { + // Local mode: Copy from monorepo + const coreSource = path.join(__dirname, '..', '..', 'pos-module-core', 'modules', 'core'); + const paymentsSource = path.join(__dirname, '..', '..', 'pos-module-payments', 'modules', 'payments'); + + if (fs.existsSync(coreSource)) { + console.log(' Copying core module...'); + execSync(`cp -r "${coreSource}" "${path.join(modulesDir, 'core')}"`, { stdio: 'inherit' }); + console.log(' core copied'); + } else { + console.error(' Error: pos-module-core not found in monorepo'); + process.exit(1); + } + + if (fs.existsSync(paymentsSource)) { + console.log(' Copying payments module...'); + execSync(`cp -r "${paymentsSource}" "${path.join(modulesDir, 'payments')}"`, { stdio: 'inherit' }); + console.log(' payments copied'); + } else { + console.error(' Error: pos-module-payments not found in monorepo'); + process.exit(1); + } +} else { + // Marketplace mode: Download via pos-cli + process.chdir(testDir); + + console.log(' Installing core module...'); + try { + execSync('pos-cli modules install core', { stdio: 'inherit' }); + console.log(' core installed'); + } catch (error) { + console.error(' Failed to install core module'); + process.exit(1); + } + + console.log(' Installing payments module...'); + try { + execSync('pos-cli modules install payments', { stdio: 'inherit' }); + console.log(' payments installed'); + } catch (error) { + console.error(' Failed to install payments module'); + process.exit(1); + } + + process.chdir(path.join(__dirname, '..')); +} + +// Step 4: Copy source module (always from root) +console.log('\nCopying source module under test...'); +const sourceModule = path.join(__dirname, '..', 'modules', 'payments_example_gateway'); +const destModule = path.join(modulesDir, 'payments_example_gateway'); + +if (fs.existsSync(sourceModule)) { + execSync(`cp -r "${sourceModule}" "${destModule}"`, { stdio: 'inherit' }); + console.log(' payments_example_gateway copied'); +} else { + console.error(' Error: Source module not found at modules/payments_example_gateway'); + process.exit(1); +} + +// Step 5: Verify structure +console.log('\nSetup complete! Test environment ready:'); +console.log(` Test app: tests/post_import/app/`); +console.log(` Modules: tests/post_import/modules/`); +console.log(` - core (${useLocal ? 'local' : 'marketplace'})`); +console.log(` - payments (${useLocal ? 'local' : 'marketplace'})`); +console.log(` - payments_example_gateway (source)`); +console.log('\nRun tests with: npm run pw-tests\n'); diff --git a/pos-module-payments-example-gateway/tests/README.md b/pos-module-payments-example-gateway/tests/README.md new file mode 100644 index 0000000..ad13f9b --- /dev/null +++ b/pos-module-payments-example-gateway/tests/README.md @@ -0,0 +1,116 @@ +# E2E Tests for Payment Gateway Module + +This directory contains end-to-end tests for the `payments_example_gateway` module using Playwright. + +## Test Structure + +``` +tests/ +├── post_import/ # Test environment (generated, not committed) +│ ├── app/ # Test application (committed) +│ │ ├── config.yml +│ │ └── views/ +│ │ ├── pages/ +│ │ │ ├── test-payment.liquid +│ │ │ └── test-payment-post.liquid +│ │ └── layouts/ +│ │ └── application.liquid +│ ├── modules/ # All modules (generated, gitignored) +│ │ ├── core/ # Dependency +│ │ ├── payments/ # Dependency +│ │ └── payments_example_gateway/ # Module under test +│ └── .pos # Environment config (generated, gitignored) +├── *.spec.ts # Test files (committed) +└── README.md # This file +``` + +## Running Tests + +### 1. Setup Test Environment + +**For local development (uses source from monorepo):** +```bash +npm run test:setup:local +``` + +**For CI or external users (downloads from marketplace):** +```bash +npm run test:setup +``` + +This script: +- Creates `tests/post_import/modules/` directory +- Installs/copies dependency modules (core, payments) +- Copies the source module (payments_example_gateway) +- Copies `.pos` configuration + +### 2. Deploy Test Environment + +```bash +npm run test:deploy +``` + +This deploys the test application (including all modules) to your platformOS instance. + +### 3. Run Tests + +```bash +npm run pw-tests +``` + +## Complete Workflow + +```bash +# One-time setup +npm install + +# For each test run +npm run test:setup:local # or test:setup for marketplace mode +npm run test:deploy +npm run pw-tests +``` + +## Clean Up + +To remove generated files and start fresh: + +```bash +npm run test:clean +``` + +## Test Coverage + +- Payment page loads successfully +- Successful payment flow end-to-end +- Failed payment flow end-to-end +- Delayed payment success flow +- Invalid transaction handling (404) +- Missing transaction_id handling (404) +- Multiple payment attempts on same transaction +- URL parameters preservation in redirect flow +- Seed test + +## Environment Variables + +Required environment variables: + +- `MPKIT_URL` - Base URL of your platformOS instance +- `E2E_TEST_PASSWORD` - Password for test users (if authentication is added) + +## CI Integration + +For CI pipelines, use marketplace mode: + +```yaml +- run: npm run test:setup +- run: npm run test:deploy +- run: npm run pw-tests +``` + +## Notes + +- The test application (`tests/post_import/app/`) is committed to git +- Dependency modules (`tests/post_import/modules/`) are generated and gitignored +- Source module is always copied from `modules/payments_example_gateway/` at the root +- In local mode, dependency modules are copied from sibling directories in the monorepo +- In marketplace mode, dependency modules are downloaded via `pos-cli modules install` diff --git a/pos-module-payments-example-gateway/tests/data/seed/seed.sh b/pos-module-payments-example-gateway/tests/data/seed/seed.sh new file mode 100755 index 0000000..ce55c62 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/data/seed/seed.sh @@ -0,0 +1,9 @@ +set -eu + +DEFAULT_ENV="" +POS_ENV="${1:-$DEFAULT_ENV}" + +pos-cli data clean $POS_ENV --auto-confirm --include-schema + +cd ./tests/post_import +env CONFIG_FILE_PATH=./../../.pos pos-cli deploy $POS_ENV diff --git a/pos-module-payments-example-gateway/tests/invalid-transaction.spec.ts b/pos-module-payments-example-gateway/tests/invalid-transaction.spec.ts new file mode 100644 index 0000000..b3b8d3d --- /dev/null +++ b/pos-module-payments-example-gateway/tests/invalid-transaction.spec.ts @@ -0,0 +1,25 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Invalid transaction handling', async ({ page }) => { + // 1. Navigate directly to payment gateway page with invalid transaction_id + + // expect: Navigate to /payments/example_gateway/index?transaction_id=invalid-id-12345 + const response = await page.goto('/payments/example_gateway?transaction_id=invalid-id-12345'); + + // 2. Verify error handling + + // expect: Page returns 404 status code + expect(response?.status()).toBe(404); + + // expect: Transaction query returns blank/null + // expect: Payment gateway page does not render + // expect: Proper error handling prevents payment processing with invalid transaction + + // Verify that the payment form is not rendered + await expect(page.locator('form[action*="/payments/example_gateway/webhook"]')).not.toBeVisible(); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/missing-transaction-id.spec.ts b/pos-module-payments-example-gateway/tests/missing-transaction-id.spec.ts new file mode 100644 index 0000000..633f263 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/missing-transaction-id.spec.ts @@ -0,0 +1,22 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Payment gateway page without transaction_id', async ({ page }) => { + // 1. Navigate to payment gateway page without transaction_id parameter + + // expect: Navigate to /payments/example_gateway/index (no parameters) + const response = await page.goto('/payments/example_gateway'); + + // 2. Verify error handling + + // expect: Page returns 404 status code or appropriate error + expect(response?.status()).toBe(404); + + // expect: Transaction cannot be found without transaction_id + // expect: Payment form does not render without valid transaction + await expect(page.locator('form[action*="/payments/example_gateway/webhook"]')).not.toBeVisible(); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/multiple-payment-attempts.spec.ts b/pos-module-payments-example-gateway/tests/multiple-payment-attempts.spec.ts new file mode 100644 index 0000000..0754d00 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/multiple-payment-attempts.spec.ts @@ -0,0 +1,47 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Multiple payment attempts on same transaction', async ({ page }) => { + // 1. Create a test transaction and complete successful payment + await page.goto('/test-payment'); + + // expect: Transaction is created + await page.locator('#start-payment').click(); + await page.waitForURL(/\/payments\/example_gateway/); + + // Capture the transaction ID from the URL + const gatewayUrl = page.url(); + const transactionIdMatch = gatewayUrl.match(/transaction_id=([^&]+)/); + expect(transactionIdMatch).not.toBeNull(); + const transactionId = transactionIdMatch![1]; + + // expect: Payment succeeds + const successButton = page.getByRole('button', { name: /Payment Success/i }).first(); + await successButton.click(); + + // expect: Transaction status is 'succeeded' + await page.waitForURL(/\/test-payment\?payment_success=1/); + await expect(page.getByText(/Payment Successful/i)).toBeVisible(); + + // 2. Attempt to access the same transaction's payment gateway page again + + // expect: Navigate back to the gateway URL with the same transaction_id + await page.goto(gatewayUrl); + + // 3. Verify transaction state handling + + // expect: Gateway page may load or show appropriate message for already-completed transaction + // expect: System handles duplicate payment attempts gracefully + // expect: Transaction status remains 'succeeded' and is not changed + + // The page should either: + // 1. Show an error message about the transaction being already processed, or + // 2. Still show the payment form but not change the transaction status + + // We'll check if the page loads (doesn't throw an error) + expect(page.url()).toContain('transaction_id=' + transactionId); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/payment-failed-flow.spec.ts b/pos-module-payments-example-gateway/tests/payment-failed-flow.spec.ts new file mode 100644 index 0000000..bcaa93d --- /dev/null +++ b/pos-module-payments-example-gateway/tests/payment-failed-flow.spec.ts @@ -0,0 +1,48 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Failed payment flow end-to-end', async ({ page }) => { + // 1. Navigate to /test-payment page + await page.goto('/test-payment'); + + // expect: Page loads successfully with the test payment form + await expect(page.getByRole('heading', { name: /Test Payment/i })).toBeVisible(); + + // 2. Click the 'Start Test Payment' button (id='start-payment') + await page.locator('#start-payment').click(); + + // expect: Form submits and creates a new transaction + // expect: User is redirected to payment gateway page at /payments/example_gateway/index + await page.waitForURL(/\/payments\/example_gateway/); + + // expect: Gateway page loads with transaction details and payment options + await expect(page.getByRole('heading', { name: /Example Payment Gateway/i })).toBeVisible(); + + // 3. Click 'Payment Failed' button + const failedButton = page.getByRole('button', { name: /Payment Failed/i }); + await failedButton.click(); + + // expect: Form submits to webhook endpoint with payment_status=failed + // expect: Webhook processes the failed payment status + // expect: Transaction status is updated to 'failed' + // expect: User is redirected to the failed_url: /test-payment?payment_failed=1 + await page.waitForURL(/\/test-payment\?payment_failed=1/); + + // 4. Verify failure page displays correctly + + // expect: User lands on /test-payment page with payment_failed=1 parameter + expect(page.url()).toMatch(/payment_failed=1/); + + // expect: Red error message is displayed: 'Payment Failed' + await expect(page.getByText(/Payment Failed/i)).toBeVisible(); + + // expect: Error message includes text: 'Your test payment was not processed.' + await expect(page.getByText(/Your test payment was not processed/i)).toBeVisible(); + + // expect: Start Test Payment button is available to retry + await expect(page.locator('#start-payment')).toBeVisible(); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/payment-gateway-smoke.plan.md b/pos-module-payments-example-gateway/tests/payment-gateway-smoke.plan.md new file mode 100644 index 0000000..6a004dc --- /dev/null +++ b/pos-module-payments-example-gateway/tests/payment-gateway-smoke.plan.md @@ -0,0 +1,161 @@ +# Payment Example Gateway - Smoke Tests + +## Application Overview + +The Payment Example Gateway module provides a mock payment gateway for testing platformOS payment flows. It includes a test helper page at /test-payment that creates test transactions and a gateway page that simulates payment processing with success/failure options. The payment flow is: test-payment page → payment gateway page → success/failure redirect. This test plan covers the core smoke tests to verify the payment gateway integration is functioning correctly. + +## Test Scenarios + +### 1. Payment Gateway Smoke Tests + +**Seed:** `tests/seed.spec.ts` + +#### 1.1. Test payment page loads successfully + +**File:** `tests/payment-page-load.spec.ts` + +**Steps:** + 1. Navigate to /test-payment page + - expect: Page loads with status 200 + - expect: Page displays the heading 'Test Payment - Example Gateway' + - expect: Page shows test transaction details: Amount: $10.99, Currency: USD, Items: test-item-1, test-item-2 + - expect: Page displays the 'Start Test Payment' button with id='start-payment' + - expect: Info message is visible explaining this is a test payment gateway + 2. Verify page structure and content + - expect: Transaction details are visible showing: Amount: $10.99, Currency: USD, Gateway: example_gateway + - expect: Form element with POST method to /test-payment?create=1 is present + - expect: Submit button with id 'start-payment' is present and clickable + - expect: E2E testing instructions are visible at the bottom of the page + +#### 1.2. Successful payment flow end-to-end + +**File:** `tests/payment-success-flow.spec.ts` + +**Steps:** + 1. Navigate to /test-payment page + - expect: Page loads successfully with the test payment form + 2. Click the 'Start Test Payment' button (id='start-payment') + - expect: Form submits and creates a new transaction + - expect: User is redirected to /payments/example_gateway/index page + - expect: URL includes transaction_id parameter + - expect: URL includes success_url parameter set to /test-payment?payment_success=1 + - expect: URL includes failed_url parameter set to /test-payment?payment_failed=1 + 3. Verify payment gateway page loads + - expect: Page displays heading 'Example Payment Gateway' + - expect: Page shows 'Select payment status:' text + - expect: Three buttons are visible: 'Payment Success', 'Payment Failed', and 'Payment Success delay status change for 15s' + - expect: Payment Success button shows the transaction amount: $10.99 + - expect: Form action points to /payments/example_gateway/webhook + - expect: Hidden input fields contain transaction_id, success_url, and failed_url + 4. Click 'Payment Success' button + - expect: Form submits to webhook endpoint + - expect: Webhook processes the payment_status=success + - expect: Transaction status is updated to 'succeeded' + - expect: User is redirected to the success_url: /test-payment?payment_success=1 + 5. Verify success page displays correctly + - expect: User lands on /test-payment page with payment_success=1 parameter + - expect: Green success message is displayed: 'Payment Successful!' + - expect: Success message includes text: 'Your test payment was processed successfully.' + - expect: Start Test Payment button is still available for additional tests + +#### 1.3. Failed payment flow end-to-end + +**File:** `tests/payment-failed-flow.spec.ts` + +**Steps:** + 1. Navigate to /test-payment page + - expect: Page loads successfully with the test payment form + 2. Click the 'Start Test Payment' button (id='start-payment') + - expect: Form submits and creates a new transaction + - expect: User is redirected to payment gateway page at /payments/example_gateway/index + - expect: Gateway page loads with transaction details and payment options + 3. Click 'Payment Failed' button + - expect: Form submits to webhook endpoint with payment_status=failed + - expect: Webhook processes the failed payment status + - expect: Transaction status is updated to 'failed' + - expect: User is redirected to the failed_url: /test-payment?payment_failed=1 + 4. Verify failure page displays correctly + - expect: User lands on /test-payment page with payment_failed=1 parameter + - expect: Red error message is displayed: 'Payment Failed' + - expect: Error message includes text: 'Your test payment was not processed.' + - expect: Start Test Payment button is available to retry + +#### 1.4. Delayed payment success flow + +**File:** `tests/payment-success-delayed.spec.ts` + +**Steps:** + 1. Navigate to /test-payment page + - expect: Page loads successfully + 2. Click the 'Start Test Payment' button + - expect: User is redirected to payment gateway page + 3. Verify delayed payment button is present + - expect: Third button with text 'Payment Success delay status change for 15s' is visible + - expect: Button shows transaction amount: $10.99 + - expect: Button has name='payment_status' and value='success_delayed' + 4. Click 'Payment Success delay status change for 15s' button + - expect: Form submits to webhook endpoint with payment_status=success_delayed + - expect: Webhook queues background job to update transaction status after 15 second delay + - expect: User is immediately redirected to success_url: /test-payment?payment_success=1 + - expect: Success page displays while transaction processes in background + 5. Verify success page displays + - expect: Green success message is displayed + - expect: Transaction will be updated to 'succeeded' status after background job completes (15 seconds) + +#### 1.5. Invalid transaction handling + +**File:** `tests/invalid-transaction.spec.ts` + +**Steps:** + 1. Navigate directly to payment gateway page with invalid transaction_id + - expect: Navigate to /payments/example_gateway/index?transaction_id=invalid-id-12345 + 2. Verify error handling + - expect: Page returns 404 status code + - expect: Transaction query returns blank/null + - expect: Payment gateway page does not render + - expect: Proper error handling prevents payment processing with invalid transaction + +#### 1.6. Payment gateway page without transaction_id + +**File:** `tests/missing-transaction-id.spec.ts` + +**Steps:** + 1. Navigate to payment gateway page without transaction_id parameter + - expect: Navigate to /payments/example_gateway/index (no parameters) + 2. Verify error handling + - expect: Page returns 404 status code or appropriate error + - expect: Transaction cannot be found without transaction_id + - expect: Payment form does not render without valid transaction + +#### 1.7. Multiple payment attempts on same transaction + +**File:** `tests/multiple-payment-attempts.spec.ts` + +**Steps:** + 1. Create a test transaction and complete successful payment + - expect: Transaction is created + - expect: Payment succeeds + - expect: Transaction status is 'succeeded' + 2. Attempt to access the same transaction's payment gateway page again + - expect: Navigate back to the gateway URL with the same transaction_id + 3. Verify transaction state handling + - expect: Gateway page may load or show appropriate message for already-completed transaction + - expect: System handles duplicate payment attempts gracefully + - expect: Transaction status remains 'succeeded' and is not changed + +#### 1.8. URL parameters preservation in redirect flow + +**File:** `tests/url-parameters-preservation.spec.ts` + +**Steps:** + 1. Create transaction and navigate to payment gateway page + - expect: Gateway page loads with transaction_id, success_url, and failed_url parameters + 2. Verify all required URL parameters are present + - expect: transaction_id is present in URL + - expect: success_url parameter equals /test-payment?payment_success=1 + - expect: failed_url parameter equals /test-payment?payment_failed=1 (note: code shows 'failed_url' but test-payment.liquid uses 'cancel_url') + - expect: Form hidden inputs contain all three parameters for webhook submission + 3. Submit payment and verify redirect URL + - expect: After clicking Payment Success, user is redirected to exact success_url + - expect: After clicking Payment Failed, user is redirected to exact failed_url + - expect: No parameters are lost during redirect chain diff --git a/pos-module-payments-example-gateway/tests/payment-page-load.spec.ts b/pos-module-payments-example-gateway/tests/payment-page-load.spec.ts new file mode 100644 index 0000000..285ce45 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/payment-page-load.spec.ts @@ -0,0 +1,48 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Test payment page loads successfully', async ({ page }) => { + // 1. Navigate to /test-payment page + await page.goto('/test-payment'); + + // expect: Page loads with status 200 + expect(page).toHaveURL(/\/test-payment/); + + // expect: Page displays the heading 'Test Payment - Example Gateway' + await expect(page.getByRole('heading', { name: /Test Payment - Example Gateway/i })).toBeVisible(); + + // expect: Page shows test transaction details: Amount: $10.99, Currency: USD, Items: test-item-1, test-item-2 + await expect(page.getByText('$10.99')).toBeVisible(); + await expect(page.getByText('USD')).toBeVisible(); + await expect(page.getByText(/test-item-1/)).toBeVisible(); + await expect(page.getByText(/test-item-2/)).toBeVisible(); + + // expect: Page displays the 'Start Test Payment' button with id='start-payment' + await expect(page.locator('#start-payment')).toBeVisible(); + + // expect: Info message is visible explaining this is a test payment gateway + await expect(page.getByText(/test payment gateway/i)).toBeVisible(); + + // 2. Verify page structure and content + + // expect: Transaction details are visible showing: Amount: $10.99, Currency: USD, Gateway: example_gateway + await expect(page.getByText(/Amount/i)).toBeVisible(); + await expect(page.getByText(/Currency/i)).toBeVisible(); + await expect(page.getByText(/example_gateway/i)).toBeVisible(); + + // expect: Form element with POST method to /test-payment?create=1 is present + const form = page.locator('form[action*="/test-payment"]'); + await expect(form).toBeVisible(); + + // expect: Submit button with id 'start-payment' is present and clickable + const submitButton = page.locator('#start-payment'); + await expect(submitButton).toBeVisible(); + await expect(submitButton).toBeEnabled(); + + // expect: E2E testing instructions are visible at the bottom of the page + await expect(page.getByText(/E2E/i)).toBeVisible(); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/payment-success-delayed.spec.ts b/pos-module-payments-example-gateway/tests/payment-success-delayed.spec.ts new file mode 100644 index 0000000..ed146c4 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/payment-success-delayed.spec.ts @@ -0,0 +1,52 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test.fixme('Delayed payment success flow', async ({ page }) => { + // 1. Navigate to /test-payment page + await page.goto('/test-payment'); + + // expect: Page loads successfully + await expect(page.getByRole('heading', { name: /Test Payment/i })).toBeVisible(); + + // 2. Click the 'Start Test Payment' button + await page.locator('#start-payment').click(); + + // expect: User is redirected to payment gateway page + await page.waitForURL(/\/payments\/example_gateway/); + + // 3. Verify delayed payment button is present + + // expect: Third button with text 'Payment Success delay status change for 15s' is visible + const delayedButton = page.getByRole('button', { name: /Payment Success delay status change for 15s/i }); + await expect(delayedButton).toBeVisible(); + + // expect: Button shows transaction amount: $10.99 + await expect(delayedButton).toContainText('$10.99'); + + // expect: Button has name='payment_status' and value='success_delayed' + await expect(delayedButton).toHaveAttribute('name', 'payment_status'); + await expect(delayedButton).toHaveAttribute('value', 'success_delayed'); + + // 4. Click 'Payment Success delay status change for 15s' button + await delayedButton.click(); + + // expect: Form submits to webhook endpoint with payment_status=success_delayed + // expect: Webhook queues background job to update transaction status after 15 second delay + // expect: User is immediately redirected to success_url: /test-payment?payment_success=1 + await page.waitForURL(/\/test-payment\?payment_success=1/); + + // expect: Success page displays while transaction processes in background + expect(page.url()).toMatch(/payment_success=1/); + + // 5. Verify success page displays + + // expect: Green success message is displayed + await expect(page.getByText(/Payment Successful/i)).toBeVisible(); + + // expect: Transaction will be updated to 'succeeded' status after background job completes (15 seconds) + // Note: We don't wait for the background job to complete in this test + }); +}); diff --git a/pos-module-payments-example-gateway/tests/payment-success-flow.spec.ts b/pos-module-payments-example-gateway/tests/payment-success-flow.spec.ts new file mode 100644 index 0000000..e04f713 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/payment-success-flow.spec.ts @@ -0,0 +1,82 @@ +// spec: tests/payment-gateway-smoke.plan.md +// seed: tests/seed.spec.ts + +import { test, expect } from '@playwright/test'; + +test.describe('Payment Gateway Smoke Tests', () => { + test('Successful payment flow end-to-end', async ({ page }) => { + // 1. Navigate to /test-payment page + await page.goto('/test-payment'); + + // expect: Page loads successfully with the test payment form + await expect(page.getByRole('heading', { name: /Test Payment/i })).toBeVisible(); + + // 2. Click the 'Start Test Payment' button (id='start-payment') + await page.locator('#start-payment').click(); + + // expect: Form submits and creates a new transaction + // expect: User is redirected to /payments/example_gateway/index page + await page.waitForURL(/\/payments\/example_gateway/); + + // expect: URL includes transaction_id parameter + expect(page.url()).toMatch(/transaction_id=/); + + // expect: URL includes success_url parameter set to /test-payment?payment_success=1 (URL encoded) + expect(page.url()).toMatch(/success_url=%2Ftest-payment%3Fpayment_success%3D1/); + + // expect: URL includes failed_url parameter set to /test-payment?payment_failed=1 (URL encoded) + expect(page.url()).toMatch(/failed_url=%2Ftest-payment%3Fpayment_failed%3D1/); + + // 3. Verify payment gateway page loads + + // expect: Page displays heading 'Example Payment Gateway' + await expect(page.getByRole('heading', { name: /Example Payment Gateway/i })).toBeVisible(); + + // expect: Page shows 'Select payment status:' text + await expect(page.getByText(/Select payment status/i)).toBeVisible(); + + // expect: Three buttons are visible: 'Payment Success', 'Payment Failed', and 'Payment Success delay status change for 15s' + const successButton = page.getByRole('button', { name: /Payment Success/i }).first(); + const failedButton = page.getByRole('button', { name: /Payment Failed/i }); + const delayedButton = page.getByRole('button', { name: /Payment Success delay/i }); + + await expect(successButton).toBeVisible(); + await expect(failedButton).toBeVisible(); + await expect(delayedButton).toBeVisible(); + + // expect: Payment Success button shows the transaction amount: $10.99 + await expect(successButton).toContainText('$10.99'); + + // expect: Form action points to /payments/example_gateway/webhook + const form = page.locator('form[action*="/payments/example_gateway/webhook"]'); + await expect(form).toBeVisible(); + + // expect: Hidden input fields contain transaction_id, success_url, and failed_url + await expect(page.locator('input[name="transaction_id"]')).toBeAttached(); + await expect(page.locator('input[name="success_url"]')).toBeAttached(); + await expect(page.locator('input[name="failed_url"]')).toBeAttached(); + + // 4. Click 'Payment Success' button + await successButton.click(); + + // expect: Form submits to webhook endpoint + // expect: Webhook processes the payment_status=success + // expect: Transaction status is updated to 'succeeded' + // expect: User is redirected to the success_url: /test-payment?payment_success=1 + await page.waitForURL(/\/test-payment\?payment_success=1/); + + // 5. Verify success page displays correctly + + // expect: User lands on /test-payment page with payment_success=1 parameter + expect(page.url()).toMatch(/payment_success=1/); + + // expect: Green success message is displayed: 'Payment Successful!' + await expect(page.getByText(/Payment Successful/i)).toBeVisible(); + + // expect: Success message includes text: 'Your test payment was processed successfully.' + await expect(page.getByText(/Your test payment was processed successfully/i)).toBeVisible(); + + // expect: Start Test Payment button is still available for additional tests + await expect(page.locator('#start-payment')).toBeVisible(); + }); +}); diff --git a/pos-module-payments-example-gateway/tests/post_import/.gitkeep b/pos-module-payments-example-gateway/tests/post_import/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pos-module-payments-example-gateway/tests/post_import/app/config.yml b/pos-module-payments-example-gateway/tests/post_import/app/config.yml new file mode 100644 index 0000000..80e37e4 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/post_import/app/config.yml @@ -0,0 +1,8 @@ +# Test Application for Payment Gateway E2E Tests +# This minimal app is used to test the payments_example_gateway module + +# Allow module files to be deleted on deployment +modules_that_allow_delete_on_deploy: + - core + - payments + - payments_example_gateway diff --git a/pos-module-payments-example-gateway/tests/post_import/app/views/layouts/application.liquid b/pos-module-payments-example-gateway/tests/post_import/app/views/layouts/application.liquid new file mode 100644 index 0000000..7f1fee0 --- /dev/null +++ b/pos-module-payments-example-gateway/tests/post_import/app/views/layouts/application.liquid @@ -0,0 +1,11 @@ + + +
+ + +$10.99USD["test-item-1", "test-item-2"]example_gateway
+ #start-payment
+
+