diff --git a/.changeset/loud-baboons-bake.md b/.changeset/loud-baboons-bake.md new file mode 100644 index 00000000..25b65f56 --- /dev/null +++ b/.changeset/loud-baboons-bake.md @@ -0,0 +1,5 @@ +--- +"@wpengine/hwp-previews-wordpress-plugin": patch +--- + +chore: Updated action and filter docs. diff --git a/.github/scripts/get-plugin-slug.sh b/.github/scripts/get-plugin-slug.sh new file mode 100644 index 00000000..dd2c98ee --- /dev/null +++ b/.github/scripts/get-plugin-slug.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +set -e + +BASE_SHA="$1" +HEAD_SHA="$2" + +git fetch --prune --unshallow + +# Get changed files in plugins subdirectories +CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" | grep '^plugins/[^/]\+/' || true) + +if [ -z "$CHANGED_FILES" ]; then + echo "No plugin files changed" + exit 1 +fi + +# Extract plugin names from both old and new paths +PLUGINS=() +for file in $CHANGED_FILES; do + plugin=$(echo $file | cut -d/ -f2) + PLUGINS+=("$plugin") +done + +# Get unique plugin names +UNIQUE_PLUGINS=($(printf '%s\n' "${PLUGINS[@]}" | sort -u)) + +# Find the first plugin that actually exists +PLUGIN_SLUG="" +for plugin in "${UNIQUE_PLUGINS[@]}"; do + if [ -d "plugins/$plugin" ]; then + PLUGIN_SLUG="$plugin" + echo "Found existing plugin directory: $PLUGIN_SLUG" + break + fi +done + +if [ -z "$PLUGIN_SLUG" ]; then + echo "No valid plugin directory found" + exit 1 +fi + +echo "slug=$PLUGIN_SLUG" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2e0f07bc..48e486cf 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -28,51 +28,31 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Get changed plugin directory + id: plugin + run: | + bash .github/scripts/get-plugin-slug.sh ${{ github.event.pull_request.base.sha }} ${{ github.sha }} + - name: Detect changed plugins with quality config id: detect run: | - git fetch --prune --unshallow - CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^plugins/' || true) - - if [ -z "$CHANGED_FILES" ]; then - echo "No plugin files changed" + if [ -z "${{ steps.plugin.outputs.slug }}" ]; then + echo "No plugin slug detected" echo "plugins=[]" >> $GITHUB_OUTPUT echo "has-plugins=false" >> $GITHUB_OUTPUT exit 0 fi - # Extract unique plugin names and check for phpcs.xml - PLUGINS_WITH_CONFIG=() - CHECKED_PLUGINS=() - - for file in $CHANGED_FILES; do - plugin=$(echo $file | cut -d/ -f2) - - # Skip if we already checked this plugin - if [[ " ${CHECKED_PLUGINS[@]} " =~ " $plugin " ]]; then - continue - fi - - CHECKED_PLUGINS+=("$plugin") - - if [ -f "plugins/$plugin/phpcs.xml" ]; then - PLUGINS_WITH_CONFIG+=("$plugin") - echo "✅ Found phpcs.xml for plugin: $plugin" - else - echo "ℹ️ No phpcs.xml found for plugin: $plugin, skipping quality checks" - fi - done + PLUGIN="${{ steps.plugin.outputs.slug }}" - # Convert to JSON array - if [ ${#PLUGINS_WITH_CONFIG[@]} -gt 0 ]; then - PLUGINS_JSON=$(printf '%s\n' "${PLUGINS_WITH_CONFIG[@]}" | jq -R -s -c 'split("\n")[:-1]') - echo "plugins=${PLUGINS_JSON}" >> $GITHUB_OUTPUT + if [ -f "plugins/$PLUGIN/phpcs.xml" ]; then + echo "plugins=[\"$PLUGIN\"]" >> $GITHUB_OUTPUT echo "has-plugins=true" >> $GITHUB_OUTPUT - echo "Found ${#PLUGINS_WITH_CONFIG[@]} plugin(s) with quality config: ${PLUGINS_WITH_CONFIG[*]}" + echo "✅ Found phpcs.xml for plugin: $PLUGIN" else echo "plugins=[]" >> $GITHUB_OUTPUT echo "has-plugins=false" >> $GITHUB_OUTPUT - echo "No plugins found with quality configuration" + echo "ℹ️ No phpcs.xml found for plugin: $PLUGIN, skipping quality checks" fi quality-checks: needs: detect-plugins diff --git a/.github/workflows/codeception.yml b/.github/workflows/codeception.yml index dce9983f..c91c0a3f 100644 --- a/.github/workflows/codeception.yml +++ b/.github/workflows/codeception.yml @@ -35,64 +35,31 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Detect changed plugins with test config - id: detect + - name: Get changed plugin directory + id: plugin run: | - git fetch --prune --unshallow + bash .github/scripts/get-plugin-slug.sh ${{ github.event.pull_request.base.sha }} ${{ github.sha }} - # Get changed files based on event type - if [ "${{ github.event_name }}" = "pull_request" ]; then - CHANGED_FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^plugins/' || true) - else - CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD | grep '^plugins/' || true) - fi - - if [ -z "$CHANGED_FILES" ]; then - echo "No plugin files changed" + - name: Detect changed plugins with quality config + id: detect + run: | + if [ -z "${{ steps.plugin.outputs.slug }}" ]; then + echo "No plugin slug detected" echo "plugins=[]" >> $GITHUB_OUTPUT echo "has-plugins=false" >> $GITHUB_OUTPUT exit 0 fi - # Extract unique plugin names and check for test config - PLUGINS_WITH_CONFIG=() - CHECKED_PLUGINS=() - - for file in $CHANGED_FILES; do - plugin=$(echo $file | cut -d/ -f2) - - # Skip if we already checked this plugin - if [[ " ${CHECKED_PLUGINS[@]} " =~ " $plugin " ]]; then - continue - fi - - CHECKED_PLUGINS+=("$plugin") - - # Check for both codeception.dist.yml and composer.json - if [ -f "plugins/$plugin/codeception.dist.yml" ] && [ -f "plugins/$plugin/composer.json" ]; then - PLUGINS_WITH_CONFIG+=("$plugin") - echo "✅ Found test config for plugin: $plugin (codeception.dist.yml + composer.json)" - else - echo "ℹ️ Missing test config for plugin: $plugin, skipping tests" - if [ ! -f "plugins/$plugin/codeception.dist.yml" ]; then - echo " - Missing: codeception.dist.yml" - fi - if [ ! -f "plugins/$plugin/composer.json" ]; then - echo " - Missing: composer.json" - fi - fi - done + PLUGIN="${{ steps.plugin.outputs.slug }}" - # Convert to JSON array - if [ ${#PLUGINS_WITH_CONFIG[@]} -gt 0 ]; then - PLUGINS_JSON=$(printf '%s\n' "${PLUGINS_WITH_CONFIG[@]}" | jq -R -s -c 'split("\n")[:-1]') - echo "plugins=${PLUGINS_JSON}" >> $GITHUB_OUTPUT + if [ -f "plugins/$PLUGIN/codeception.dist.yml" ]; then + echo "plugins=[\"$PLUGIN\"]" >> $GITHUB_OUTPUT echo "has-plugins=true" >> $GITHUB_OUTPUT - echo "Found ${#PLUGINS_WITH_CONFIG[@]} plugin(s) with test config: ${PLUGINS_WITH_CONFIG[*]}" + echo "✅ Found codeception.dist.yml for plugin: $PLUGIN" else echo "plugins=[]" >> $GITHUB_OUTPUT echo "has-plugins=false" >> $GITHUB_OUTPUT - echo "No plugins found with test configuration" + echo "ℹ️ No codeception.dist.yml found for plugin: $PLUGIN, skipping automated tests" fi continuous_integration: diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index adc21778..ca038807 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -1,3 +1,11 @@ +# This does the following: +# 1. Detects modified plugins that have .wp-env.json configuration +# 2. Runs Playwright E2E tests on those plugins using the custom action +# 3. Creates a matrix job for each plugin that has a quality configuration +# Bonus: This means you can have plugin specific badges e.g. +# [![E2E Te](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=hwp-previews%20Playwright%20E2E%20Tests&label=End-to-End%20Tests)](https://github.com/wpengine/hwptoolkit/actions) + + name: End-to-End Tests on: @@ -5,20 +13,62 @@ on: branches: - main paths: - - 'plugins/hwp-previews/**.php' - - 'plugins/hwp-previews/**.js' - - 'plugins/hwp-previews/**.css' - - 'plugins/hwp-previews/**.json' - + - 'plugins/**.php' + - 'plugins/**.js' + - 'plugins/**.css' + - 'plugins/**.json' pull_request: branches: - main paths: - - "plugins/hwp-previews/**" + - "plugins/**" jobs: + detect-plugin: + runs-on: ubuntu-latest + name: Detect plugin with E2E tests + outputs: + plugin: ${{ steps.detect.outputs.plugin }} + has-plugin: ${{ steps.detect.outputs.has-plugin }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Get changed plugin directory + id: plugin + run: | + bash .github/scripts/get-plugin-slug.sh ${{ github.event.pull_request.base.sha }} ${{ github.sha }} + + - name: Detect changed plugin with E2E config + id: detect + run: | + if [ -z "${{ steps.plugin.outputs.slug }}" ]; then + echo "No plugin slug detected" + echo "plugin=" >> $GITHUB_OUTPUT + echo "has-plugin=false" >> $GITHUB_OUTPUT + exit 0 + fi + + PLUGIN="${{ steps.plugin.outputs.slug }}" + + # Check for .wp-env.json file in the plugin directory + if [ -f "plugins/$PLUGIN/.wp-env.json" ]; then + echo "plugin=$PLUGIN" >> $GITHUB_OUTPUT + echo "has-plugin=true" >> $GITHUB_OUTPUT + echo "✅ Found .wp-env.json for plugin: $PLUGIN" + else + echo "plugin=" >> $GITHUB_OUTPUT + echo "has-plugin=false" >> $GITHUB_OUTPUT + echo "ℹ️ No .wp-env.json found for plugin: $PLUGIN, skipping E2E tests" + fi + playwright-e2e-tests: + needs: detect-plugin + if: needs.detect-plugin.outputs.has-plugin == 'true' runs-on: ubuntu-24.04 + name: ${{ needs.detect-plugin.outputs.plugin }} Playwright E2E Tests + env: + PLUGIN: ${{ needs.detect-plugin.outputs.plugin }} steps: - name: Checkout code @@ -37,22 +87,22 @@ jobs: uses: ./.github/actions/setup-php-composer with: php-version: 8.2 - working-directory: plugins/hwp-previews + working-directory: plugins/${{ env.PLUGIN }} composer-options: '--no-progress --optimize-autoloader --no-dev' - name: Install playwright browsers run: npx playwright install --with-deps - working-directory: plugins/hwp-previews + working-directory: plugins/${{ env.PLUGIN }} - name: Start wp-env run: | npm run wp-env start - working-directory: plugins/hwp-previews + working-directory: plugins/${{ env.PLUGIN }} - name: Run Playwright tests run: npm run test:e2e - working-directory: plugins/hwp-previews + working-directory: plugins/${{ env.PLUGIN }} - name: Stop wp-env run: npm run wp-env stop - working-directory: plugins/hwp-previews + working-directory: plugins/${{ env.PLUGIN }} diff --git a/.github/workflows/plugin-artifact-for-pr.yml b/.github/workflows/plugin-artifact-for-pr.yml index a9305b01..f22025bb 100644 --- a/.github/workflows/plugin-artifact-for-pr.yml +++ b/.github/workflows/plugin-artifact-for-pr.yml @@ -20,9 +20,7 @@ jobs: - name: Get changed plugin directory id: plugin run: | - git fetch --prune --unshallow - plugin=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^plugins/' | head -1 | cut -d/ -f2) - echo "slug=$plugin" >> $GITHUB_OUTPUT + bash .github/scripts/get-plugin-slug.sh ${{ github.event.pull_request.base.sha }} ${{ github.sha }} - name: Create plugin artifact uses: ./.github/actions/create-plugin-artifact @@ -44,20 +42,20 @@ jobs: const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; const slug = process.env.PLUGIN_SLUG; const body = `ℹ️ [Download the latest ${slug} plugin zip from this PR](${artifactUrl})\n(See the 'Artifacts' section at the bottom)`; - + // Find existing comment from this bot const comments = await github.rest.issues.listComments({ issue_number: pr.number, owner: context.repo.owner, repo: context.repo.repo }); - - const botComment = comments.data.find(comment => - comment.user.type === 'Bot' && + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && comment.user.login === 'github-actions[bot]' && comment.body.includes(`ℹ️ [Download the latest ${slug} plugin zip from this PR]`) ); - + if (botComment) { // Update existing comment core.info(`Updating existing comment with ID: ${botComment.id}`); diff --git a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md index 33642bcb..c7c5651f 100644 --- a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md +++ b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md @@ -1,43 +1,54 @@ # Actions & Filters -## PHP Actions +## Table of Contents -- `hwp_previews_activate` - Plugin activation hook -- `hwp_previews_deactivate` - Plugin deactivation hook -- `hwp_previews_init` - Plugin initialization hook +- [PHP Actions](#php-actions) +- [PHP Filters](#php-filters) +- [Examples](#examples) + - [Actions](#actions) + - [Filters](#filters) +- [Contributing](#contributing) -## PHP Filters +--- -## Admin +This document lists the available PHP actions and filters provided by the HWP Previews plugin, along with explanations and usage examples. These hooks allow you to customize plugin behavior, settings, and integration with other plugins or your theme. -- `hwp_previews_settings_init` - Allows a user to modify the `Settings` instance -- `hwp_previews_settings_form_manager_init` - Allows a user to modify the `Settings_Form_Manager` instance and update fields and post types -- `hwp_previews_settings_fields` - Allows a user to register, modify, or remove settings fields for the settings page -- `hwp_previews_settings_group_option_key` - Filter to modify the settings group option key. Default is HWP_PREVIEWS_SETTINGS_KEY -- `hwp_previews_settings_group_settings_group` - Filter to modify the settings group name. Default is HWP_PREVIEWS_SETTINGS_GROUP +--- +## PHP Actions +| Action Name | Description | +|-----------------------------------------------|-----------------------------------------------------------------------------------------------| +| `hwp_previews_init` | Fired after the plugin is initialized. | +| `hwp_previews_activate` | Fired on plugin activation. | +| `hwp_previews_deactivate` | Fired on plugin deactivation. | +| `hwp_previews_settings_init` | Fired after the settings page is initialized. | +| `hwp_previews_settings_form_manager_init` | Fired after the settings form manager is initialized. | -- `hwp_previews_register_parameters` - Allows modification of the URL parameters used for previews for the class `Preview_Parameter_Registry` -- `hwp_previews_template_path` - To use our own template for iframe previews -- `hwp_previews_core` - Register or unregister URL parameters, and adjust types/statuses -- `hwp_previews_filter_available_post_types` - Filter to modify the available post types for Previews. -- `hwp_previews_filter_available_post_statuses` - Filter for post statuses for previews for Previews -- `hwp_previews_filter_available_parent_post_statuses` - Filter for parent post statuses for Previews -- `hwp_previews_settings_group_settings_config` - Filter to modify the settings array. See `Settings_Group` -- `hwp_previews_settings_group_cache_groups` - Filter to modify cache groups for `Settings_Group` -- `hwp_previews_get_post_types_config` - Filter for generating the instance of `Post_Types_Config_Interface` -- `hwp_previews_hooks_post_status_config` - Filter for post status config service for the Hook class -- `hwp_previews_hooks_preview_link_service` - Filter for preview link service for the Hook class +--- -## Usage Examples +## PHP Filters -@TODO - Redo +| Filter Name | Description | +|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| `hwp_previews_settings_fields` | Modify or add settings fields for the settings page. | +| `hwp_previews_settings_group_option_key` | Change the settings group option key (default: `HWP_PREVIEWS_SETTINGS_KEY`). | +| `hwp_previews_settings_group_settings_group` | Change the settings group name (default: `HWP_PREVIEWS_SETTINGS_GROUP`). | +| `hwp_previews_register_parameters` | Allows users to modify or register parameters. | +| `hwp_previews_template_path` | Change the template file path for iframe previews. | +| `hwp_previews_filter_available_post_types` | Filter the available post types for previews (affects settings UI and preview logic). | +| `hwp_previews_filter_available_post_statuses` | Filter the available post statuses for previews. | +--- + +## Examples + +Below is some examples of usage for these actions/filters. -### Filter: Post Types List -Modify which post types appear in the settings UI: +#### `hwp_previews_filter_available_post_types` +**Description:** +Filter the available post types for previews (affects settings UI and preview logic). ```php // Removes attachment post type from the settings page configuration. @@ -51,51 +62,32 @@ function hwp_previews_filter_post_type_setting_callback( $post_types ) { } ``` -### Action: Core Registry -Register or unregister URL parameters, and adjust types/statuses: +#### `hwp_previews_template_path` +**Description:** +Change the template file path for iframe previews. ```php -add_action( 'hwp_previews_core', 'modify_preview_url_parameters' ); -function modify_preview_url_parameters( - \HWP\Previews\Preview\Parameter\Preview_Parameter_Registry $registry -) { - // Remove default parameter - $registry->unregister( 'author_ID' ); - - // Add custom parameter - $registry->register( new \HWP\Previews\Preview\Parameter\Preview_Parameter( - 'current_time', - static fn( \WP_Post $post ) => (string) time(), - __( 'Current Unix timestamp', 'your-domain' ) - ) ); -} -``` - -Modify post types and statuses: - -```php -add_action( 'hwp_previews_core', 'modify_post_types_and_statuses_configs', 10, 3 ); -function modify_post_types_and_statuses_configs( - \HWP\Previews\Preview\Parameter\Preview_Parameter_Registry $registry, - \HWP\Previews\Post\Type\Post_Types_Config $types, - \HWP\Previews\Post\Status\Post_Statuses_Config $statuses -) { - // Limit to pages only - $types->set_post_types( [ 'page' ] ); - // Only include drafts - $statuses->set_post_statuses( [ 'draft' ] ); -} +add_filter( 'hwp_previews_template_path', function( $default_path ) { + return get_stylesheet_directory() . '/my-preview-template.php'; +}); ``` -### Filter: Iframe Template Path - -Use your own template for iframe previews: +#### `hwp_previews_settings_group_option_key` and `hwp_previews_settings_group_settings_group` +**Description:** +Change the settings group option key or group name. ```php -add_filter( 'hwp_previews_template_path', function( $default_path ) { - return get_stylesheet_directory() . '/my-preview-template.php'; +add_filter( 'hwp_previews_settings_group_option_key', function( $default_key ) { + return 'my_custom_option_key'; +}); +add_filter( 'hwp_previews_settings_group_settings_group', function( $default_group ) { + return 'my_custom_settings_group'; }); ``` --- + +## Contributing + +If you feel like something is missing or you want to add tests or testing documentation, we encourage you to contribute! Please check out our [Contributing Guide](https://github.com/wpengine/hwptoolkit/blob/main/CONTRIBUTING.md) for more details. diff --git a/plugins/hwp-previews/README.md b/plugins/hwp-previews/README.md index 8ccd559e..d2820c6e 100644 --- a/plugins/hwp-previews/README.md +++ b/plugins/hwp-previews/README.md @@ -13,8 +13,10 @@ ![GitHub forks](https://img.shields.io/github/forks/wpengine/hwptoolkit?style=social) ![GitHub stars](https://img.shields.io/github/stars/wpengine/hwptoolkit?style=social) [![Testing Integration](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=hwp-previews%20codeception%20tests&label=Automated%20Tests)](https://github.com/wpengine/hwptoolkit/actions) +[![Code Coverage](https://img.shields.io/badge/coverage-%3E95%25-brightgreen?label=Code%20Coverage)](https://github.com/wpengine/hwptoolkit/actions) [![Code Quality](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=hwp-previews%20php%20code%20quality%20checks&label=Code%20Quality%20Checks)](https://github.com/wpengine/hwptoolkit/actions) -[![End-to-End Tests](https://github.com/wpengine/hwptoolkit/workflows/End-to-End%20Tests/badge.svg)](https://github.com/wpengine/hwptoolkit/actions?query=workflow%3A%22End-to-End+Tests%22) +[![End-to-End Tests](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=hwp-previews%20Playwright%20E2E%20Tests&label=End-to-End%20Tests)](https://github.com/wpengine/hwptoolkit/actions) + ----- diff --git a/plugins/hwp-previews/TESTING.md b/plugins/hwp-previews/TESTING.md index 5c8b9fe7..d65b6006 100644 --- a/plugins/hwp-previews/TESTING.md +++ b/plugins/hwp-previews/TESTING.md @@ -1,155 +1,235 @@ # Testing HWP Previews -This plugin uses [Codeception](https://codeception.com/) with [WPBrowser](https://wpbrowser.wptestkit.dev/) for automated testing. -Tests are organized into suites for integration (wpunit), functional, and acceptance testing. +## Table of Contents + +- [Overview](#overview) + - [Directory Structure](#directory-structure) + - [Technologies](#technologies) +- [Usage](#usage) + - [Running Tests](#running-tests) + - [GitHub Actions](#github-actions) +- [Setup Tests Locally](#setup-tests-locally) + - [Prerequisites](#prerequisites) + - [Docker Setup](#docker-setup) + - [What the Setup Script Does](#what-the-setup-script-does) + - [Running Tests Locally](#running-tests-locally) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) --- -## Test Suites +## Overview -- **wpunit**: Unit/integration tests with WordPress loaded. -- **functional**: Simulates web requests, runs WordPress in a test environment. -- **acceptance**: Browser-based tests (WPBrowser/WPWebDriver). +HWP Previews comes with automated tests for unit, integration, and acceptance (E2E) scenarios to ensure code quality and functionality. -Configuration files for each suite are in the `tests/` directory (e.g., `wpunit.suite.dist.yml`). +### Directory Structure ---- +A list of related files and directories for testing: -## Local Test Environment +```text +bin/ +├── install-test-env.sh # Set up test WP environment +├── run-codeception.sh # Run Codeception tests +├── run-e2e.sh # Run E2E (Playwright) tests +├── run-coverage.sh # Generate coverage reports +└── local/ + ├── setup-docker-env.sh # Setup Docker environment + ├── run-unit-tests.sh # Run unit tests in Docker with Codeception + ├── run-e2e-tests.sh # Run e2e tests in Docker with Playwright + ├── run-qa.sh # Run php code quality checks with PHPStan, Psalm and PHPCS + ├── run-wpunit.sh # Run WPUnit tests in Docker + └── run-functional.sh # Run functional tests in Docker -The plugin provides scripts to set up a local WordPress environment for testing, using Docker and environment variables defined in `.env.dist`. +tests/ +├── _data/ # Test data (e.g. DB dumps) +├── _envs/ # Environment configs +├── _output/ # Test output (logs, coverage) +├── _support/ # Helper classes, modules +├── wpunit/ # WPUnit (WordPress-aware unit/integration) test cases +├── wpunit.suite.dist.yml +└── wpunit/ + └── bootstrap.php # Bootstrap for WPUnit tests -### Prerequisites +.env.dist # Example environment variables for testing +codeception.dist.yml # Main Codeception config +``` + +### Technologies + +We use the following technologies to run our tests: -- Docker (for local environment) -- Composer -- Node.js (for building assets, if needed) +- [Codeception](https://codeception.com/) - PHP testing framework +- [WPBrowser](https://wpbrowser.wptestkit.dev/) - WordPress-specific testing tools +- [WPUnit](https://github.com/lipemat/wp-unit) - WordPress unit testing +- [Docker](https://www.docker.com/) - Containerized testing environment +- [Composer](https://getcomposer.org/) - PHP dependency management +- [Playwright](https://playwright.dev/) - End-to-end testing framework +- [npm](https://www.npmjs.com/) - JavaScript package manager --- -## Setup +## Usage -1. **Copy and configure environment variables:** +The plugin includes the following test suites: - ```bash - @TODO - cp .env.dist .env - # Edit .env as needed for your local setup - ``` +1. **WP Unit Tests** – Unit and Integration Tests +2. **E2E Tests** – Acceptance tests using Playwright +### Running Tests -2. **Set up the test WordPress environment:** +| Command | Description | +|------------------------------------------|----------------------------------------------------------| +| `composer run test:unit:coverage` | Run WPUnit (unit/integration) tests with coverage report | +| `composer run test:unit:coverage-html` | Generate an HTML code coverage report | +| `composer run test:e2e` | Run end-to-end (E2E) acceptance tests | +| `composer run test` | Run all available test suites | - ```bash - bin/install-test-env.sh - ``` +### GitHub Actions - This script will: - - Create the test database (unless `SKIP_DB_CREATE=true`) - - Download and install WordPress in the directory specified by `WORDPRESS_ROOT_DIR` - - Symlink the plugin into the WordPress plugins directory - - Activate the plugin and set up test data +Automated testing runs on every pull request via GitHub Actions for a modified plugin: + +| Workflow | Description | Status | +|-------------------------|---------------------------------------------|--------| +| **Code Quality** | Runs static analysis and linting checks | [View Workflow](../../actions/workflows/code-quality.yml) | +| **E2E Tests** | Runs Playwright end-to-end acceptance tests | [View Workflow](../../actions/workflows/e2e.yml) | +| **Codeception (WPUnit)** | Runs unit and integration tests | [View Workflow](../../actions/workflows/codeception.yml) | + + +>[!IMPORTANT] +> Test coverage for WP Unit Tests is **95%**. Any new code will require tests to be added in order to pass CI checks. This is set in [text](codeception.dist.yml) in the parameter `min_coverage`. --- -## Running Tests +## Setup Tests Locally + +### Prerequisites -Currently the plugin has the following suite of tests +- Docker and Docker Compose installed and running +- Composer installed +- Node.js and npm installed (for E2E tests) +- Terminal/command line access -1. WP Unit Tests - (Unit and Integration Tests) -2. E2E Tests - Playright tests +### Docker Setup -### WPUnit (WordPress-aware Unit/Integration) Tests +>[!NOTE] +> You need Docker running locally before setting up tests. Alternatively, you can copy `.env.dist` to `.env` and update the database details to point to your local database. However, this will make database changes, so we recommend using the Docker setup instead. -You can also run WPUnit tests using Composer scripts: +To set up your local Docker environment, run: -```bash -composer run test:unit +```shell +sh bin/local/setup-docker-env.sh ``` -To generate coverage reports: +This script will automatically handle the complete Docker environment setup process. -```bash -composer run test:unit:coverage -``` +### What the Setup Script Does -To generate an HTML coverage report: +The setup script performs the following operations: -```bash -composer run test:unit:coverage-html -``` -> [!NOTE] -> HTML code coverage can be found here [tests/_output/coverage/index.html](tests/_output/coverage/index.html) +#### 1. Environment Verification +- ✅ Checks that Docker is running +- ✅ Verifies required files exist +#### 2. Configuration Setup +- 📁 Copies `bin/local/.env.local` to `.env` + - Uses local development configuration (different from `.env.dist`) + - Sets appropriate database credentials and WordPress settings -### E2WTests +#### 3. Docker Container Management +- 🐳 Runs `composer run docker:build` + - Executes `sh bin/build-docker.sh` to create the Docker container + - Builds WordPress environment with PHP 8.2 +- 🚀 Runs `docker compose up -d` to start the container in detached mode + - Creates container named `hwp-previews-wordpress-1` + - Sets up WordPress with test database -Run browser-based acceptance tests: +#### 4. Code Coverage Setup +- 🔧 Installs and configures PCOV extension (preferred for performance) +- 🔄 Falls back to XDebug if PCOV installation fails +- ⚙️ Configures coverage settings automatically +- 🔄 Restarts container to ensure extensions are loaded -```bash -sh bin/local/run-e2e-tests.sh coverage -``` +#### 5. WordPress Installation +- 📝 Installs WordPress if not already present +- 🔌 Activates the plugin automatically +- ✅ Verifies the installation is working correctly + +### Running Tests Locally -### All Tests +Once setup is complete, you can run tests using Composer: -To run all suites: +```shell +# Run unit tests with coverage +composer run test:unit:coverage -```bash +# Run all tests composer run test -# or -vendor/bin/codecept run + +# Run E2E tests +composer run test:e2e ``` +For a full list of available test commands, see the [Usage](#usage) section above. + --- -## Code Coverage +## Troubleshooting -To generate code coverage reports (requires Xdebug or PCOV): +### Container Issues -```bash -# Example for wpunit suite -SUITES=wpunit COVERAGE=1 bin/run-codeception.sh -``` +```shell +# Check container status +docker ps | grep hwp-previews -Coverage output will be in `tests/_output/` or as specified by `COVERAGE_OUTPUT`. +# Restart containers if needed +docker compose restart ---- +# View container logs +docker compose logs hwp-previews-wordpress-1 +``` -## Useful Scripts +### Permission Issues -- `bin/install-test-env.sh` — Sets up the WordPress test environment. -- `bin/run-codeception.sh` — Runs Codeception tests inside the plugin directory. -- `bin/local/run-unit-tests.sh` — Runs unit tests in Docker. -- `bin/local/run-qa.sh` — Runs code quality checks (PHPStan, PHPCS, Psalm). +```shell +# Fix test output permissions +docker exec hwp-previews-wordpress-1 chmod 777 -R tests/_output +``` ---- +### Coverage Driver Issues -## Notes +```shell +# Check which coverage driver is available +docker exec hwp-previews-wordpress-1 php -m | grep -E "(pcov|xdebug)" -- The test database will be reset during setup. **Do not use a database with important data.** -- You can customize which suites to run by setting the `SUITES` environment variable. -- See `.env.dist` for all available environment variables and their descriptions. +# Re-run setup if coverage isn't working +sh bin/local/setup-docker-env.sh +``` ---- +### WordPress Database Issues + +```shell +# Reinstall WordPress +docker exec hwp-previews-wordpress-1 wp core install \ + --url=http://localhost \ + --title="Test Site" \ + --admin_user=admin \ + --admin_password=admin \ + --admin_email=admin@example.com \ + --allow-root +``` -```text -tests/ -├── _data/ # Test data (e.g. DB dumps) -├── _envs/ # Environment configs -├── _output/ # Test output (logs, coverage) -├── _support/ # Helper classes, modules -├── wpunit/ # WPUnit (WordPress-aware unit/integration) test cases -├── wpunit.suite.dist.yml -└── wpunit/ - └── bootstrap.php # Bootstrap for WPUnit tests +### Clean Up Environment -bin/ -├── install-test-env.sh # Script to set up test WP environment -├── run-codeception.sh # Script to run Codeception tests -└── local/ - ├── run-unit-tests.sh # Run unit tests in Docker - └── run-qa.sh # Run code quality checks +```shell +# Stop containers +docker compose down -.env.dist # Example environment variables for testing -codeception.dist.yml # Main Codeception config +# Remove containers and volumes (complete cleanup) +docker compose down -v ``` + +--- + +## Contributing + +If you feel like something is missing or you want to add tests or testing documentation, we encourage you to contribute! Please check out our [Contributing Guide](https://github.com/wpengine/hwptoolkit/blob/main/CONTRIBUTING.md) for more details. diff --git a/plugins/hwp-previews/bin/local/setup-docker-env.sh b/plugins/hwp-previews/bin/local/setup-docker-env.sh new file mode 100644 index 00000000..9e6bb32a --- /dev/null +++ b/plugins/hwp-previews/bin/local/setup-docker-env.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash + +set -e # Exit on any error + +echo "Setting up Docker environment..." + +# Copy environment file +if [ -f "bin/local/.env.local" ]; then + cp bin/local/.env.local .env + echo "✓ Environment file copied" +else + echo "❌ Error: bin/local/.env.local not found" + exit 1 +fi + +# Build and start containers +echo "Building and starting Docker containers..." +composer run docker:build +docker compose up -d + +# Wait for containers to be ready +echo "Waiting for containers to be ready..." +sleep 10 + +# Check if container is running +if ! docker ps | grep -q hwp-previews-wordpress-1; then + echo "❌ Error: Container hwp-previews-wordpress-1 is not running" + exit 1 +fi + +# Function to check if PHP extension is installed +check_extension() { + local extension=$1 + docker exec hwp-previews-wordpress-1 php -m | grep -q "$extension" +} + +# Install coverage driver (prefer PCOV over XDebug for performance) +echo "Setting up code coverage driver..." + +if check_extension "pcov"; then + echo "✓ PCOV already installed and loaded" +elif check_extension "xdebug"; then + echo "✓ XDebug already installed and loaded" +else + echo "Checking if PCOV is installed but not enabled..." + + # Check if PCOV is installed via PECL but not enabled + if docker exec hwp-previews-wordpress-1 pecl list | grep -q "pcov"; then + echo "PCOV is installed via PECL but not enabled. Enabling..." + docker exec hwp-previews-wordpress-1 bash -c " + docker-php-ext-enable pcov && + echo 'pcov.enabled=1' >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini + " + + # Restart container to load the extension + echo "Restarting container to load PCOV..." + docker compose restart + sleep 10 + + if check_extension "pcov"; then + echo "✓ PCOV enabled successfully" + else + echo "❌ Failed to enable PCOV" + fi + else + echo "Installing PCOV for code coverage..." + docker exec hwp-previews-wordpress-1 bash -c " + pecl install --force pcov 2>/dev/null || true && + docker-php-ext-enable pcov && + echo 'pcov.enabled=1' >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini + " + + # Restart container to load the extension + echo "Restarting container to load PCOV..." + docker compose restart + sleep 10 + + if check_extension "pcov"; then + echo "✓ PCOV installed and enabled successfully" + else + echo "⚠️ PCOV setup failed, trying XDebug..." + docker exec hwp-previews-wordpress-1 bash -c " + pecl install --force xdebug 2>/dev/null || true && + docker-php-ext-enable xdebug && + echo 'xdebug.mode=coverage' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + " + + # Restart container to load the extension + echo "Restarting container to load XDebug..." + docker compose restart + sleep 10 + + if check_extension "xdebug"; then + echo "✓ XDebug installed and enabled successfully" + else + echo "❌ Failed to install both PCOV and XDebug" + exit 1 + fi + fi + fi +fi + +# Verify WordPress installation +echo "Checking WordPress installation..." +if docker exec hwp-previews-wordpress-1 wp core is-installed --allow-root 2>/dev/null; then + echo "✓ WordPress is installed" +else + echo "Installing WordPress..." + docker exec hwp-previews-wordpress-1 wp core install \ + --url=http://localhost \ + --title="Test Site" \ + --admin_user=admin \ + --admin_password=admin \ + --admin_email=admin@example.com \ + --allow-root + + if [ $? -eq 0 ]; then + echo "✓ WordPress installed successfully" + else + echo "❌ WordPress installation failed" + exit 1 + fi +fi + +# Install and activate the plugin if needed +echo "Checking plugin activation..." +if docker exec hwp-previews-wordpress-1 wp plugin is-active hwp-previews --allow-root 2>/dev/null; then + echo "✓ Plugin is active" +else + echo "Activating plugin..." + docker exec hwp-previews-wordpress-1 wp plugin activate hwp-previews --allow-root +fi + +# Verify coverage driver is working +echo "Verifying code coverage setup..." +if docker exec hwp-previews-wordpress-1 php -r " + if (extension_loaded('pcov')) { + echo 'PCOV is available'; + exit(0); + } elseif (extension_loaded('xdebug')) { + echo 'XDebug is available'; + exit(0); + } else { + echo 'No coverage driver available'; + exit(1); + } +"; then + echo "✓ Code coverage driver is ready" +else + echo "❌ No code coverage driver available" + exit 1 +fi + +echo "" +echo "🎉 Docker environment setup complete!" +echo "" +echo "You can now run tests with:" +echo " docker exec -e COVERAGE=1 -e SUITES=wpunit -w /var/www/html/wp-content/plugins/hwp-previews hwp-previews-wordpress-1 bin/run-codeception.sh" +echo "" +echo "Or without coverage:" +echo " docker exec -e SUITES=wpunit -w /var/www/html/wp-content/plugins/hwp-previews hwp-previews-wordpress-1 bin/run-codeception.sh" diff --git a/plugins/hwp-previews/bin/run-codeception.sh b/plugins/hwp-previews/bin/run-codeception.sh index b2838a3f..b28eb7fe 100755 --- a/plugins/hwp-previews/bin/run-codeception.sh +++ b/plugins/hwp-previews/bin/run-codeception.sh @@ -21,31 +21,6 @@ setup_before() { curl -L 'https://raw.github.com/Codeception/c3/2.0/c3.php' > "c3.php" fi - # Enable XDebug or PCOV for code coverage. - if [[ "$COVERAGE" == '1' ]]; then - if [[ "$USING_XDEBUG" == '1' ]]; then - echo "Enabling XDebug 3" - cp /usr/local/etc/php/conf.d/disabled/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/ - echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - else - echo "Using pcov/clobber for code coverage" - docker-php-ext-enable pcov - echo "pcov.enabled=1" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini - echo "pcov.directory=${PROJECT_DIR}" >> /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini - COMPOSER_MEMORY_LIMIT=-1 composer require pcov/clobber --dev - vendor/bin/pcov clobber - fi - elif [[ -f /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini ]]; then - echo "Disabling XDebug" - rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - fi - - # Install the PHP dev-dependencies. - if [ ! -d "vendor" ]; then - echo "Running composer install" - COMPOSER_MEMORY_LIMIT=-1 composer install - fi - # Set output permission echo "Setting Codeception output directory permissions" chmod 777 -R tests/_output @@ -59,12 +34,6 @@ run_tests() { local debug="--debug" fi - local suites=$1 - if [[ -z "$SUITES" ]]; then - echo "No test suites specified. Must specify variable SUITES." - exit 1 - fi - if [[ -n "$COVERAGE" ]]; then if [[ -n "$COVERAGE_OUTPUT" ]]; then local coverage="--coverage --coverage-xml $COVERAGE_OUTPUT" @@ -79,25 +48,61 @@ run_tests() { wp maintenance-mode deactivate --allow-root fi - - # Suites is the comma separated list of suites/tests to run. - echo "Running Test Suite $suites" + echo "Running Unit and Integration tests" cd "$PROJECT_DIR" # IMPORTANT: Build Codeception classes before running tests - echo "Building Codeception test classes" - vendor/bin/codecept build -c codeception.dist.yml + echo "Building Codeception test classes" + vendor/bin/codecept build -c codeception.dist.yml - if [ $? -ne 0 ]; then - echo "Error: Codeception build failed" - exit 1 - fi + if [ $? -ne 0 ]; then + echo "Error: Codeception build failed" + exit 1 + fi - XDEBUG_MODE=coverage vendor/bin/codecept run -c codeception.dist.yml ${suites} ${coverage:-} ${debug:-} --no-exit + XDEBUG_MODE=coverage vendor/bin/codecept run -c codeception.dist.yml ${suites} ${coverage:-} ${debug:-} ${debug:-} if [ $? -ne 0 ]; then echo "Error: Codeception tests failed with exit code $?" exit 1 fi + + # Check code coverage if coverage was requested + if [[ -n "$COVERAGE" ]]; then + + if [[ -n "$COVERAGE_OUTPUT" ]]; then + coverage_percent=$(grep -oP '(\d+\.\d+)%' "tests/_output/coverage/index.html" | head -1 | tr -d '%') + else + coverage_percent=$(grep -oP 'line-rate="(\d+\.\d+)"' "tests/_output/coverage.xml" | head -1 | grep -oP '\d+\.\d+') + # Convert to percent + if [[ -n "$coverage_percent" ]]; then + coverage_percent=$(awk "BEGIN { printf \"%.2f\", $coverage_percent * 100 }") + fi + fi + if [[ -z "$coverage_percent" ]]; then + echo "Warning: Could not determine code coverage percentage." + exit 1 + fi + + echo "Code coverage percentage found: $coverage_percent" + + + required_coverage=$(grep 'min_coverage:' codeception.dist.yml | awk '{print $2}') + + if [[ -z "$required_coverage" ]]; then + echo "No min_coverage found in codeception.dist.yml. Defaulting to 80%" + required_coverage=80 + fi + + coverage_int=${coverage_percent%.*} + if (( coverage_int < required_coverage )); then + echo -e "\033[0;31mError: Code coverage is ${coverage_percent}%, which is below the required ${required_coverage}%.\033[0m" + exit 1 + else + echo -e "\033[0;32mCode coverage is ${coverage_percent}% (required: ${required_coverage}%)\033[0m" + fi + + fi + } ## @@ -117,12 +122,11 @@ cleanup_after() { if [[ "$USING_XDEBUG" == '1' ]]; then echo "Disabling XDebug 3" rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini - else + else98 echo "Disabling pcov/clobber" docker-php-ext-disable pcov sed -i '/pcov.enabled=1/d' /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini sed -i '/pcov.directory=${PROJECT_DIR}/d' /usr/local/etc/php/conf.d/docker-php-ext-pcov.ini - COMPOSER_MEMORY_LIMIT=-1 composer remove pcov/clobber --dev fi fi @@ -135,9 +139,7 @@ cleanup_after() { echo "Setting up for Codeception tests" setup_before - -# Run the tests -run_tests $SUITES +run_tests # Clean up after running tests. echo "Cleaning up after Codeception tests" @@ -145,8 +147,8 @@ cleanup_after # Check results and exit accordingly. if [ -f "tests/_output/failed" ]; then - echo "Uh oh, Codeception tests failed." + echo "Codeception tests failed." exit 1 else - echo "Woohoo! Codeception tests completed succesfully!" + echo "Codeception tests completed successfully!" fi diff --git a/plugins/hwp-previews/codeception.dist.yml b/plugins/hwp-previews/codeception.dist.yml index 002a5ff9..f2decb50 100644 --- a/plugins/hwp-previews/codeception.dist.yml +++ b/plugins/hwp-previews/codeception.dist.yml @@ -50,7 +50,8 @@ coverage: - /tests/* - /vendor/* - /src/Templates/* - show_only_summary: false + show_only_summary: true + min_coverage: 95 modules: config: REST: diff --git a/plugins/hwp-previews/composer.json b/plugins/hwp-previews/composer.json index efcb0de3..fd79adbb 100644 --- a/plugins/hwp-previews/composer.json +++ b/plugins/hwp-previews/composer.json @@ -143,10 +143,9 @@ "php:psalm:fix": "psalm --alter", "qa": "sh bin/local/run-qa.sh", "test": [ - "sh bin/local/run-unit-tests.sh", + "sh bin/local/run-unit-tests.sh coverage", "sh bin/local/run-e2e-tests.sh" ], - "test:unit": "sh bin/local/run-unit-tests.sh", "test:unit:coverage": "sh bin/local/run-unit-tests.sh coverage", "test:unit:coverage-html": "sh bin/local/run-unit-tests.sh coverage --coverage-html", "test:e2e": "sh bin/local/run-e2e-tests.sh" diff --git a/plugins/hwp-previews/src/Plugin.php b/plugins/hwp-previews/src/Plugin.php index 7b25e59f..c760ce6f 100644 --- a/plugins/hwp-previews/src/Plugin.php +++ b/plugins/hwp-previews/src/Plugin.php @@ -13,8 +13,6 @@ * * This class serves as the main entry point for the plugin, handling initialization, action and filter hooks. * - * @link https://github.com/wpengine/hwptoolkit/tree/main/plugins/hwp-previews - * * @package HWP\Previews */ final class Plugin {