diff --git a/.github/workflows/create-plugin-zip.yml b/.github/workflows/create-plugin-zip.yml index 6257f5a9..3dfe427f 100644 --- a/.github/workflows/create-plugin-zip.yml +++ b/.github/workflows/create-plugin-zip.yml @@ -1,6 +1,10 @@ name: Create Plugin Zip on: + push: + branches: [main, master, 'release/*'] + pull_request: + branches: [main, master] workflow_dispatch: workflow_call: inputs: diff --git a/.github/workflows/e2e-free.yml b/.github/workflows/e2e-free.yml new file mode 100644 index 00000000..7c345f39 --- /dev/null +++ b/.github/workflows/e2e-free.yml @@ -0,0 +1,282 @@ +name: NotificationX Free — E2E Tests + +on: + workflow_dispatch: + inputs: + nx_branch: + description: "NotificationX branch to test" + required: true + default: "master" + type: string + wp_version: + description: "WordPress version" + required: false + default: "latest" + type: string + workflow_call: + inputs: + artifact-name: + required: true + type: string + description: "Pre-built plugin artifact name from create-plugin-zip" + wp_version: + required: false + type: string + default: "latest" + +permissions: + contents: read + +concurrency: + group: "nx-free-e2e-${{ github.ref }}" + cancel-in-progress: true + +env: + WP_DIR: /tmp/wordpress + WP_PORT: 8080 + DB_NAME: wordpress_test + DB_USER: root + DB_PASS: root + DB_HOST: 127.0.0.1 + ADMIN_USER: admin + ADMIN_PASS: admin + ADMIN_EMAIL: admin@test.local + +jobs: + # Build plugin from source (only when no pre-built artifact is provided) + build-plugin: + if: ${{ inputs.artifact-name == '' }} + uses: ./.github/workflows/create-plugin-zip.yml + with: + ref: ${{ inputs.nx_branch }} + + e2e-test: + name: "E2E Tests (WP ${{ inputs.wp_version || 'latest' }})" + needs: [build-plugin] + # Run even if build-plugin was skipped (workflow_call provides artifact directly) + if: ${{ !cancelled() && !failure() }} + runs-on: ubuntu-latest + timeout-minutes: 60 + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: wordpress_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping --silent" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + - name: Download plugin artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name || 'notificationx' }} + path: notificationx-plugin + + - name: Checkout E2E test suite + uses: actions/checkout@v4 + with: + repository: ShahrearMSf/nx-free-e2e + ref: main + path: e2e-suite + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + extensions: mysqli, mbstring, xml, curl, zip, intl, gd + tools: wp-cli + + - name: Download & configure WordPress + run: | + wp core download --path="$WP_DIR" --version="${{ inputs.wp_version || 'latest' }}" + + wp config create \ + --path="$WP_DIR" \ + --dbname="$DB_NAME" \ + --dbuser="$DB_USER" \ + --dbpass="$DB_PASS" \ + --dbhost="$DB_HOST" \ + --extra-php <<'PHP' + define('WP_DEBUG', true); + define('WP_DEBUG_LOG', true); + define('WP_DEBUG_DISPLAY', false); + define('DISABLE_WP_CRON', true); + PHP + + wp core install \ + --path="$WP_DIR" \ + --url="http://localhost:${WP_PORT}" \ + --title="NX E2E Test Site" \ + --admin_user="$ADMIN_USER" \ + --admin_password="$ADMIN_PASS" \ + --admin_email="$ADMIN_EMAIL" \ + --skip-email + + wp rewrite structure '/%postname%/' --path="$WP_DIR" + wp rewrite flush --path="$WP_DIR" + + - name: Install plugins (WooCommerce + NotificationX) + run: | + # Install WooCommerce from wp.org + wp plugin install woocommerce --activate --path="$WP_DIR" + + # Copy built NotificationX + mkdir -p "$WP_DIR/wp-content/plugins/notificationx" + cp -r notificationx-plugin/* "$WP_DIR/wp-content/plugins/notificationx/" + wp plugin activate notificationx --path="$WP_DIR" + + echo "--- Active plugins ---" + wp plugin list --status=active --path="$WP_DIR" + + - name: Setup WooCommerce (pages, products, COD) + run: | + WP="wp --path=$WP_DIR" + + # Create WooCommerce pages + $WP wc tool run install_pages --user=1 2>/dev/null || { + SHOP_ID=$($WP post create --post_type=page --post_title="Shop" --post_status=publish --post_content="[woocommerce_shop]" --porcelain) + CART_ID=$($WP post create --post_type=page --post_title="Cart" --post_status=publish --post_content="[woocommerce_cart]" --porcelain) + CHECKOUT_ID=$($WP post create --post_type=page --post_title="Checkout" --post_status=publish --post_content="[woocommerce_checkout]" --porcelain) + ACCOUNT_ID=$($WP post create --post_type=page --post_title="My Account" --post_status=publish --post_content="[woocommerce_my_account]" --porcelain) + $WP option update woocommerce_shop_page_id "$SHOP_ID" + $WP option update woocommerce_cart_page_id "$CART_ID" + $WP option update woocommerce_checkout_page_id "$CHECKOUT_ID" + $WP option update woocommerce_myaccount_page_id "$ACCOUNT_ID" + } + + # WooCommerce settings + $WP option update woocommerce_currency "USD" + $WP option update woocommerce_store_address "123 Test St" + $WP option update woocommerce_store_city "Test City" + $WP option update woocommerce_default_country "US:CA" + $WP option update woocommerce_store_postcode "90210" + $WP option update woocommerce_calc_taxes "no" + $WP option update woocommerce_onboarding_profile '{"completed":true}' --format=json + $WP option update woocommerce_task_list_hidden "yes" 2>/dev/null || true + $WP option update woocommerce_coming_soon "no" 2>/dev/null || true + + # Enable Cash on Delivery + $WP option update woocommerce_cod_settings '{"enabled":"yes","title":"Cash on Delivery","description":"Pay with cash upon delivery.","instructions":"Pay with cash upon delivery.","enable_for_methods":[],"enable_for_virtual":"yes"}' --format=json + + # Product 1: T-Shirt (in stock) + PRODUCT1=$($WP wc product create --user=1 \ + --name="Classic T-Shirt" --type=simple --regular_price="29.99" \ + --short_description="Classic cotton t-shirt" \ + --manage_stock=true --stock_quantity=50 --status=publish \ + --porcelain) + echo "Created T-Shirt (ID: $PRODUCT1)" + + # Product 2: Coffee Mug (low stock) + PRODUCT2=$($WP wc product create --user=1 \ + --name="Coffee Mug" --type=simple --regular_price="14.99" \ + --short_description="Premium ceramic mug" \ + --manage_stock=true --stock_quantity=3 --status=publish \ + --porcelain) + # Set low stock threshold separately (param may vary by WC version) + $WP post meta update "$PRODUCT2" _low_stock_amount 5 2>/dev/null || true + echo "Created Coffee Mug (ID: $PRODUCT2)" + + # Product 3: Wireless Headphones (on sale) + PRODUCT3=$($WP wc product create --user=1 \ + --name="Wireless Headphones" --type=simple --regular_price="79.99" --sale_price="59.99" \ + --short_description="Wireless noise-cancelling headphones" \ + --manage_stock=true --stock_quantity=25 --status=publish \ + --porcelain) + echo "Created Wireless Headphones (ID: $PRODUCT3)" + + echo "--- Products ---" + $WP wc product list --user=1 --fields=id,name,price,stock_quantity + + # Create a sample completed order (for Sales Notification data) + $WP wc shop_order create --user=1 \ + --status=completed --payment_method=cod \ + --billing='{"first_name":"John","last_name":"Doe","address_1":"123 Test St","city":"Test City","state":"CA","postcode":"90210","country":"US","email":"john@test.local"}' \ + --line_items="[{\"product_id\":${PRODUCT1},\"quantity\":2}]" 2>/dev/null || echo "Order created (or fallback needed)" + + # Dismiss admin nags + $WP option update fresh_site 0 + $WP transient delete --all + $WP user meta update 1 admin_email_lifespan 9999999999 + + - name: Start PHP server + run: | + cd "$WP_DIR" + php -S 0.0.0.0:${WP_PORT} & + for i in $(seq 1 30); do + curl -s -o /dev/null "http://localhost:${WP_PORT}" && break + sleep 1 + done + echo "WordPress ready at http://localhost:${WP_PORT}" + + - name: Install E2E dependencies + working-directory: e2e-suite + run: | + npm ci + npx playwright install --with-deps chromium + + - name: Verify WordPress is accessible + run: | + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${WP_PORT}/wp-login.php") + echo "WP login page returned: $HTTP_CODE" + if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 400 ]; then + echo "WordPress is not accessible!" + exit 1 + fi + + - name: Prepare test directories + working-directory: e2e-suite + run: mkdir -p snapshots playwright/.auth + + - name: Run Playwright E2E tests + id: test-run + continue-on-error: true + working-directory: e2e-suite + run: npx playwright test --project=setup --project=chromium + env: + BASE_URL: "http://localhost:8080" + ADMIN_USER: admin + ADMIN_PASS: admin + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: e2e-suite/playwright-report/ + retention-days: 30 + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: e2e-suite/test-results/ + retention-days: 14 + + - name: Upload WP debug log + uses: actions/upload-artifact@v4 + if: failure() + with: + name: wp-debug-log + path: /tmp/wordpress/wp-content/debug.log + retention-days: 7 + + - name: Set job status + if: always() + run: | + if [ "${{ steps.test-run.outcome }}" == "failure" ]; then + echo "E2E tests failed" + exit 1 + fi diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 2e96aa01..f22bd064 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -1,10 +1,6 @@ name: QA on: - push: - branches: [main, master, 'release/*'] - pull_request: - branches: [main, master] workflow_dispatch: inputs: plugin_repo: @@ -52,5 +48,8 @@ jobs: with: artifact-name: ${{ needs.build.outputs.artifact-name }} - - + e2e-free: + needs: [build] + uses: ./.github/workflows/e2e-free.yml + with: + artifact-name: ${{ needs.build.outputs.artifact-name }}