Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
/.gitattributes export-ignore
/.phpcs.xml export-ignore
/.wp-env.json export-ignore
/blueprint.json export-ignore
/composer.lock export-ignore
/global-setup.js export-ignore
/global-teardown.js export-ignore
/playwright.config.js export-ignore
/package.json export-ignore
/package-lock.json export-ignore
/CONTRIBUTING.md export-ignore
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/playwright-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Playwright Tests

on:
push:
branches:
- main
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test PHP ${{ matrix.php }} / WP ${{ matrix.wordpress }}
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

strategy:
fail-fast: false
matrix:
php: [ '8.2', '8.3' ]
wordpress: [ '6.8', 'latest' ]

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: 'npm'

- name: Install dependencies
run: npm ci

# Tests run against the built assets, not src/.
- name: Build plugin
run: npm run build

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Run Playwright tests
id: tests
continue-on-error: true
run: npm run test:e2e
env:
CI: true
WP_PLAYGROUND_PHP: ${{ matrix.php }}
WP_PLAYGROUND_WP: ${{ matrix.wordpress }}
WP_PLAYGROUND_PORT: 9400

- name: Upload test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: playwright-report-php${{ matrix.php }}-wp${{ matrix.wordpress }}
path: |
test-results/
playwright-report/
retention-days: 7
if-no-files-found: ignore

- name: Comment PR with test results
uses: daun/playwright-report-comment@31ed947ec7a623d18a66cfd20ef546dc66ef8063 # v4.1.0
if: always() && github.event_name == 'pull_request'
with:
report-file: test-results/results.json
comment-title: 'Playwright — PHP ${{ matrix.php }} / WP ${{ matrix.wordpress }}'

- name: Fail if tests failed
if: steps.tests.outcome == 'failure'
run: exit 1
33 changes: 33 additions & 0 deletions .github/workflows/pr-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: PR Cleanup

# Deletes the `pr-<n>-built` branch created by pr-playground-preview.yml once
# the pull request is closed or merged.

on:
pull_request:
types: [ closed ]

permissions:
contents: write

jobs:
cleanup:
name: Delete the preview branch
runs-on: ubuntu-latest

steps:
- name: Delete PR preview branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
BRANCH_NAME="pr-${PR_NUMBER}-built"
REMOTE="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git"

if git ls-remote --heads "$REMOTE" "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "Deleting $BRANCH_NAME"
git push "$REMOTE" --delete "$BRANCH_NAME"
else
echo "Branch $BRANCH_NAME does not exist. Nothing to clean up."
fi
136 changes: 136 additions & 0 deletions .github/workflows/pr-playground-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: PR Playground Preview

# Builds the PR's assets, pushes them to a throwaway `pr-<n>-built` branch, and
# adds a "Preview in WordPress Playground" button to the PR description that
# boots a WordPress instance with that build installed and demo content seeded.
#
# `build/` is gitignored, so the preview needs a branch that carries the
# compiled assets — Playground installs the plugin from that branch's zip.
# `pr-cleanup.yml` deletes the branch when the PR closes.

on:
pull_request:
types: [ opened, synchronize, reopened, edited ]

permissions:
contents: write
pull-requests: write

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
build-and-preview:
name: Build and publish preview
runs-on: ubuntu-latest
# Fork PRs get a read-only token, so the branch push would fail. Skip
# rather than failing the check.
if: github.event.pull_request.head.repo.full_name == github.repository

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Label the build with the PR number
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# PR_NUMBER is numeric, so this is safe to interpolate. Never
# interpolate the PR title — it is attacker controlled.
sed -i "s|^ \* Version:.*| * Version: 0.0.0-pr${PR_NUMBER}|" query-filter.php
grep -n "Version:" query-filter.php

- name: Build plugin
run: npm run build

- name: Push built files to a preview branch
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"

BRANCH_NAME="pr-${PR_NUMBER}-built"
git checkout -B "$BRANCH_NAME"

# build/ is gitignored in source; force-add it so the branch zip is
# a self-contained, installable plugin.
git add -f build
git add -A

if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "Build assets for PR #${PR_NUMBER} (commit: ${HEAD_SHA})"
git push origin "$BRANCH_NAME" --force
fi

- name: Add the Playground preview button to the PR
uses: WordPress/action-wp-playground-pr-preview@43fc435558bc6cee69f5b214d6b8ca4f9f80c31d # v3
with:
mode: 'append-to-description'
github-token: ${{ secrets.GITHUB_TOKEN }}
blueprint: |
{
"landingPage": "/query-filter-demo/",
"preferredVersions": {
"php": "8.2",
"wp": "6.8"
},
"features": { "networking": true },
"extraLibraries": [ "wp-cli" ],
"steps": [
{
"step": "login",
"username": "admin",
"password": "password"
},
{
"step": "installTheme",
"themeData": {
"resource": "wordpress.org/themes",
"slug": "twentytwentyfive"
},
"options": { "activate": true }
},
{
"step": "installPlugin",
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "advanced-query-loop"
},
"options": { "activate": true }
},
{
"step": "installPlugin",
"pluginData": {
"resource": "url",
"url": "https://github.com/${{ github.repository }}/archive/refs/heads/pr-${{ github.event.pull_request.number }}-built.zip"
},
"options": { "activate": true }
},
{
"step": "runPHP",
"code": "<?php require '/wordpress/wp-load.php'; foreach ( [ 'Alpha' => 'alpha', 'Beta' => 'beta', 'Gamma' => 'gamma' ] as $name => $slug ) { wp_insert_term( $name, 'category', [ 'slug' => $slug ] ); } $i = 0; foreach ( [ 'alpha', 'alpha', 'beta', 'beta', 'gamma', '' ] as $slug ) { $i++; $id = wp_insert_post( [ 'post_title' => 'Demo Post ' . $i, 'post_name' => 'demo-post-' . $i, 'post_status' => 'publish', 'post_type' => 'post', 'post_author' => 1, 'post_content' => 'Demo content for post ' . $i . '.' ] ); if ( $slug ) { wp_set_object_terms( $id, [ $slug ], 'category' ); } } wp_insert_post( [ 'post_title' => 'Query Filter Demo', 'post_name' => 'query-filter-demo', 'post_status' => 'publish', 'post_type' => 'page', 'post_author' => 1, 'post_content' => '<!-- wp:query {\"queryId\":1,\"query\":{\"perPage\":10,\"pages\":0,\"offset\":0,\"postType\":\"post\",\"order\":\"asc\",\"orderBy\":\"title\",\"author\":\"\",\"search\":\"\",\"exclude\":[],\"sticky\":\"\",\"inherit\":false}} --><div class=\"wp-block-query\"><!-- wp:query-filter/taxonomy {\"taxonomy\":\"category\"} /--><!-- wp:query-filter/post-type /--><!-- wp:search {\"buttonText\":\"Search\"} /--><!-- wp:post-template --><!-- wp:post-title {\"isLink\":true} /--><!-- /wp:post-template --></div><!-- /wp:query -->' ] ); echo 'Seeded demo content.';"
},
{
"step": "wp-cli",
"command": "wp rewrite structure '/%postname%/'"
},
{
"step": "wp-cli",
"command": "wp rewrite flush"
}
]
}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,9 @@ node_modules

/build/
/vendor/

# Playwright / Playground test artefacts
/.auth/
/dist/
/playwright-report/
/test-results/
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ Command | Purpose

<sup>&ddagger;</sup> This command deliberately filters out GET/OPTIONS/HEAD/POST/PUT access log entries

## Testing

End-to-end tests run against [WordPress Playground](https://wordpress.org/playground/), which boots in process — there is no environment to start or stop:

```bash
npm ci
npm run build # tests run against build/, not src/
npm run test:e2e
```

Command | Purpose
---- | ----
`npm run test:e2e` | Run the Playwright suite
`npm run test:e2e:debug` | Step through tests in the Playwright inspector
`npm run test:e2e:watch` | Open the Playwright UI, rerunning on change
`npm run playground:start` | Boot the same environment for manual testing, without running tests

The environment is described by [blueprint.json](./blueprint.json) and is shared between the test run and manual use. See [tests/e2e/README.md](./tests/e2e/README.md) for the fixture content and how to write tests.

Every pull request also gets a **Preview in WordPress Playground** button added to its description, which boots that PR's build with demo content already in place — no local checkout needed to try a change.

## Release Process

Releases are cut by a manually-dispatched ["Release" GH Actions workflow](https://github.com/humanmade/query-filter/actions/workflows/release.yml), which builds the plugin, stamps the version into `query-filter.php`, and creates an immutable `vX.Y.Z` tag pointing at the built, versioned code. That tag is published to [Packagist](https://packagist.org/packages/humanmade/query-filter) and attached to a GitHub release as a downloadable ZIP.
Expand Down
71 changes: 71 additions & 0 deletions blueprint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"landingPage": "/taxonomy-filter/",
"preferredVersions": {
"php": "8.2",
"wp": "6.8"
},
"features": {
"networking": true
},
"extraLibraries": [ "wp-cli" ],
"steps": [
{
"step": "login"
},
{
"step": "installTheme",
"themeData": {
"resource": "wordpress.org/themes",
"slug": "twentytwentyfive"
},
"options": {
"activate": true
}
},
{
"step": "installPlugin",
"pluginData": {
"resource": "wordpress.org/plugins",
"slug": "advanced-query-loop"
},
"options": {
"activate": true
}
},
{
"step": "defineWpConfigConsts",
"consts": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": false,
"SCRIPT_DEBUG": true
}
},
{
"step": "wp-cli",
"command": "wp plugin activate query-filter"
},
{
"step": "mkdir",
"path": "/wordpress/wp-content/mu-plugins"
},
{
"step": "cp",
"fromPath": "/wordpress/wp-content/plugins/query-filter/tests/mu-plugins/register-test-content.php",
"toPath": "/wordpress/wp-content/mu-plugins/register-test-content.php"
},
{
"step": "runPHP",
"code": "<?php require '/wordpress/wp-load.php'; require '/wordpress/wp-content/plugins/query-filter/tests/seed.php';"
},
{
"step": "wp-cli",
"command": "wp rewrite structure '/%postname%/'"
},
{
"step": "wp-cli",
"command": "wp rewrite flush"
}
]
}
Loading
Loading