Merge branch 'trunk' into fix/store-alert-button-loading-state

This commit is contained in:
Adrian Duffell 2024-08-10 00:21:18 +08:00 committed by GitHub
commit 0ab41dd5c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3972 changed files with 123656 additions and 139562 deletions

View File

@ -74,7 +74,7 @@ body:
Without this report we won't be able to fully evaluate this issue.
placeholder: |
The System Status Report is found in your WordPress admin under **WooCommerce > Status**.
Please select “Get system report”, then “Copy for support”, and then paste it here.
Please select “Get system report”, then “Copy for GitHub”, and then paste it here.
validations:
required: true
- type: checkboxes

View File

@ -35,8 +35,14 @@ Using the [WooCommerce Testing Instructions Guide](https://github.com/woocommerc
- [ ] Automatically create a changelog entry from the details below.
<!-- If no changelog entry is required for this PR, you can specify that below and provide a comment explaining why. This cannot be used if you selected the option to automatically create a changelog entry above. -->
- [ ] This Pull Request does not require a changelog entry. (Comment required below)
<details>
<summary>Changelog Entry Details</summary>
#### Significance
<!-- Choose only one -->
@ -59,6 +65,12 @@ Using the [WooCommerce Testing Instructions Guide](https://github.com/woocommerc
#### Message <!-- Add a changelog message here -->
#### Comment <!-- If the changes in this pull request don't warrant a changelog entry, you can alternatively supply a comment here. Note that comments are only accepted with a significance of "Patch" -->
</details>
<details>
<summary>Changelog Entry Comment</summary>
#### Comment <!-- If your Pull Request doesn't require a changelog entry, a comment explaining why is required instead -->
</details>

View File

@ -0,0 +1,7 @@
# Report Flaky Tests
A GitHub action to report flaky E2E tests to GitHub issues.
**This package is still experimental and breaking changes could be introduced in future minor versions (`v0.x`). Use it at your own risks.**
<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>

View File

@ -0,0 +1,78 @@
# How to update
## Source code
This action is extracted and bundled version of the following:
Repository: https://github.com/WordPress/gutenberg/tree/trunk/packages/report-flaky-tests
Commit ID: ce803384250671d01fde6c7d6d2aa83075fcc726
## How to build
After checking out the repository, navigate to packages/report-flaky-tests and do some modifications:
### package.json file
Add the following dependency: `"ts-loader": "^9.5.1",`.
### tsconfig.json file
The file context should be updated to following state:
```
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"target": "es6",
"module": "commonjs",
"esModuleInterop": true,
"moduleResolution": "node",
"declarationDir": "build-types",
"rootDir": "src",
"emitDeclarationOnly": false,
},
"include": [ "src/**/*" ],
"exclude": [ "src/__tests__/**/*", "src/__fixtures__/**/*" ]
}
```
### webpack.config.js file
The file should be added with the following content:
```
const path = require( 'path' );
const buildMode = process.env.NODE_ENV || 'production';
module.exports = {
entry: './src/index.ts',
target: 'node',
mode: buildMode,
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
plugins: [],
output: {
filename: 'index.js',
path: path.resolve( __dirname, 'dist' ),
clean: true,
},
};
```
### Build
Run `webpack --config webpack.config.js` (don't forget about `npm install` before that).
Use the generated files under `packages/report-flaky-tests/dist` to update the bundled distribution in this repository.

View File

@ -0,0 +1,17 @@
name: 'Report flaky tests'
description: 'Report flaky tests to GitHub issues'
inputs:
repo-token:
description: 'GitHub token'
required: true
label:
description: 'The flaky-test label name'
required: true
default: 'flaky-test'
artifact-path:
description: 'The path of the downloaded artifact'
required: true
default: 'flaky-tests'
runs:
using: 'node20'
main: 'dist/index.js'

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,41 @@
/*!
* fill-range <https://github.com/jonschlinkert/fill-range>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Licensed under the MIT License.
*/
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/
/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/
/*!
* to-regex-range <https://github.com/micromatch/to-regex-range>
*
* Copyright (c) 2015-present, Jon Schlinkert.
* Released under the MIT License.
*/
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */
/**
* @license React
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -10,36 +10,71 @@ inputs:
build:
description: 'Given a boolean or PNPM filter, runs the build command for monorepo project(s).'
default: false
build-type:
description: 'Given "full" or "backend" will perform optimized build steps.'
default: 'full'
pull-playwright-cache:
description: 'Given a boolean value, invokes Playwright dependencies caching.'
default: false
pull-package-deps:
description: 'Given a string value, will pull the package specific dependencies cache.'
default: false
runs:
using: 'composite'
steps:
- name: 'Read PNPM Version'
id: 'read-pnpm-version'
shell: 'bash'
run: 'echo "version=$(./.github/actions/setup-woocommerce-monorepo/scripts/read-pnpm-version.sh package.json)" >> $GITHUB_OUTPUT'
- name: 'Setup PNPM'
uses: 'pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d'
with:
version: ${{ steps.read-pnpm-version.outputs.version }}
# Next step is rudimentary - fixes a know composite action bug during post-actions:
# Error: Index was out of range. Must be non-negative and less than the size of the collection.
- name: 'Read PNPM version'
id: 'read-pnpm-version'
shell: 'bash'
run: 'echo "version=$(pnpm --version)" >> $GITHUB_OUTPUT'
- name: 'Setup Node'
uses: 'actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65'
with:
node-version-file: '.nvmrc'
# We only want to use the cache if something is being installed.
cache: ${{ inputs.install != 'false' && 'pnpm' || '' }}
# The built-in caching is not fit to per-package caching we are aiming.
cache: ''
- name: 'Setup PHP'
if: ${{ inputs.php-version != 'false' }}
uses: 'shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0'
with:
php-version: '${{ inputs.php-version }}'
coverage: 'none'
- name: 'Cache: identify pnpm caching directory'
if: ${{ inputs.pull-package-deps != 'false' }}
shell: 'bash'
run: |
echo "PNPM_STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: 'Cache: pnpm downloads'
if: ${{ inputs.pull-package-deps != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with:
path: "${{ env.PNPM_STORE_PATH }}"
key: "${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-build:${{ inputs.build-type }}-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
restore-keys: '${{ runner.os }}-pnpm-${{ inputs.pull-package-deps }}-build:${{ inputs.build-type }}-'
- name: 'Cache: node cache'
if: ${{ inputs.pull-package-deps != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with:
path: './node_modules/.cache'
key: "${{ runner.os }}-node-cache-${{ inputs.pull-package-deps }}-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
restore-keys: '${{ runner.os }}-node-cache-${{ inputs.pull-package-deps }}-'
- name: 'Cache Composer Dependencies'
if: ${{ inputs.php-version != 'false' }}
if: ${{ inputs.pull-package-deps != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with:
path: '~/.cache/composer/files'
key: "${{ runner.os }}-composer-${{ hashFiles( '**/composer.lock' ) }}"
restore-keys: '${{ runner.os }}-composer-'
key: "${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-${{ hashFiles( 'packages/*/*/composer.lock', 'plugins/*/composer.lock' ) }}"
restore-keys: '${{ runner.os }}-composer-${{ inputs.pull-package-deps }}-'
- name: 'Cache: playwright downloads'
if: ${{ inputs.pull-playwright-cache != 'false' }}
uses: 'actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319'
with:
path: '~/.cache/ms-playwright/'
key: "${{ runner.os }}-playwright-${{ hashFiles( 'pnpm-lock.yaml' ) }}"
restore-keys: '${{ runner.os }}-playwright-'
- name: 'Parse Project Filters'
id: 'project-filters'
shell: 'bash'
@ -50,9 +85,16 @@ runs:
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
if: ${{ inputs.install == 'true' || steps.project-filters.outputs.install != '' }}
shell: 'bash'
run: 'pnpm install'
# `pnpm install` filtering is broken: https://github.com/pnpm/pnpm/issues/6300
# run: 'pnpm install ${{ steps.project-filters.outputs.install }}'
# The installation command is a bit odd as it's a workaround for know bug - https://github.com/pnpm/pnpm/issues/6300.
run: |
if [[ '${{ inputs.install }}' == '@woocommerce/plugin-woocommerce...' && '${{ inputs.build-type }}' == 'backend' ]]; then
# PHPUnit/REST testing optimized installation of the deps: minimalistic and parallellized between PHP/JS.
# JS deps installation is abit hard-core, but all we need actually is wp-env and playwright - we are good at that regard.
composer install --working-dir=./plugins/woocommerce --quiet &
pnpm install --filter='@woocommerce/plugin-woocommerce' --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts
else
pnpm install ${{ steps.project-filters.outputs.install }} --frozen-lockfile ${{ steps.project-filters.outputs.install != '' && '--config.dedupe-peer-dependents=false' || '' }}
fi
# We want to include an option to build projects using this action so that we can make
# sure that the build cache is always used when building projects.
- name: 'Cache Build Output'
@ -62,5 +104,13 @@ runs:
- name: 'Build'
# Boolean inputs aren't parsed into filters so it'll either be "true" or there will be a filter.
if: ${{ inputs.build == 'true' || steps.project-filters.outputs.build != '' }}
env:
BROWSERSLIST_IGNORE_OLD_DATA: true
shell: 'bash'
run: 'pnpm ${{ steps.project-filters.outputs.build }} build'
run: |
if [[ '${{ inputs.build-type }}' == 'backend' ]]; then
pnpm --filter="@woocommerce/admin-library" build:project:feature-config
fi
if [[ '${{ inputs.build-type }}' == 'full' ]]; then
pnpm ${{ steps.project-filters.outputs.build }} build
fi

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
PACKAGE_FILE=$1
if [[ -z "$PACKAGE_FILE" ]]; then
echo "Usage: $0 <package.json>"
exit 1
fi
awk -F'"' '/"pnpm": ".+"/{ print $4; exit; }' $PACKAGE_FILE

View File

@ -1,50 +0,0 @@
name: Run API tests
description: Runs the WooCommerce Core API tests and generates Allure report.
inputs:
report-name:
description: Name of Allure report to be generated.
required: true
tests:
description: Specific tests to run, separated by single whitespace. See https://playwright.dev/docs/test-cli
playwright-config:
description: Playwright config file to be used
default: playwright.config.js
outputs:
result:
description: Whether the test passed or failed.
value: ${{ steps.run-api-tests.conclusion }}
runs:
using: composite
steps:
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
shell: bash
run: pnpm exec playwright install chromium
- name: Run API tests.
id: run-api-tests
working-directory: plugins/woocommerce
shell: bash
run: |
pnpm exec playwright test \
--config=tests/api-core-tests/${{ inputs.playwright-config }} \
${{ inputs.tests }}
- name: Generate Test report.
if: success() || ( failure() && steps.run-api-tests.conclusion == 'failure' )
working-directory: plugins/woocommerce
shell: bash
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive test report
if: success() || ( failure() && steps.run-api-tests.conclusion == 'failure' )
uses: actions/upload-artifact@v3
with:
name: ${{ inputs.report-name }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
retention-days: 20

View File

@ -1,54 +0,0 @@
name: Run E2E tests
description: Runs the WooCommerce Core E2E tests and generates Allure report.
permissions: {}
inputs:
report-name:
description: Name of Allure report to be generated.
required: true
tests:
description: Specific tests to run, separated by single whitespace. See https://playwright.dev/docs/test-cli
playwright-config:
description: The Playwright configuration file to use.
default: playwright.config.js
outputs:
result:
description: Whether the test passed or failed.
value: ${{ steps.run-e2e-tests.conclusion }}
runs:
using: composite
steps:
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
shell: bash
run: pnpm exec playwright install chromium
- name: Run E2E tests.
id: run-e2e-tests
env:
FORCE_COLOR: 1
USE_WP_ENV: 1
working-directory: plugins/woocommerce
shell: bash
run: |
pnpm exec playwright test \
--config=tests/e2e-pw/${{ inputs.playwright-config }} \
${{ inputs.tests }}
- name: Generate Test report.
if: success() || ( failure() && steps.run-e2e-tests.conclusion == 'failure' )
working-directory: plugins/woocommerce
shell: bash
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive test report
if: success() || ( failure() && steps.run-e2e-tests.conclusion == 'failure' )
uses: actions/upload-artifact@v3
with:
name: ${{ inputs.report-name }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
retention-days: 20

View File

@ -1,17 +0,0 @@
name: Run k6 performance tests
description: Runs the WooCommerce Core k6 performance tests.
permissions: {}
runs:
using: composite
steps:
- name: Install k6
shell: bash
run: |
curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
- name: Run k6 performance tests
id: run-k6-tests
shell: bash
run: |
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-pr-requests.js

View File

@ -1,29 +0,0 @@
name: Setup local test environment
description: Set up a wp-env testing environment
permissions: {}
inputs:
test-type:
required: true
type: choice
options:
- e2e
- api
- k6
runs:
using: composite
steps:
- name: Load docker images and start containers for E2E or API tests
if: ( inputs.test-type == 'e2e' ) || ( inputs.test-type == 'api' )
working-directory: plugins/woocommerce
shell: bash
run: pnpm env:test
- name: Load docker images and start containers for k6 performance tests
if: inputs.test-type == 'k6'
working-directory: plugins/woocommerce
shell: bash
run: |
pnpm --filter=@woocommerce/plugin-woocommerce env:dev
pnpm --filter=@woocommerce/plugin-woocommerce env:performance-init

View File

@ -1,41 +0,0 @@
name: Send Slack alert on PR merge test failure
description: Send a Slack alert when automated tests failed on trunk after PR merge.
permissions: {}
inputs:
slack-bot-token:
required: true
channel-id:
required: true
test-type:
required: true
type: choice
options:
- E2E
- API
- k6
runs:
using: composite
steps:
- name: Compose Slack message
id: compose-slack-message
uses: actions/github-script@v6
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
SHA: ${{ github.event.pull_request.merge_commit_sha }}
TEST_TYPE: ${{ inputs.test-type }}
with:
script: |
const script = require('./.github/actions/tests/slack-alert-on-pr-merge/scripts/compose-slack-message.js')
const slackMessage = script()
core.setOutput('slack-message', slackMessage)
- name: Send Slack alert
uses: slackapi/slack-github-action@v1.23.0
env:
SLACK_BOT_TOKEN: ${{ inputs.slack-bot-token }}
with:
channel-id: ${{ inputs.channel-id }}
payload: ${{ steps.compose-slack-message.outputs.slack-message }}

View File

@ -1,114 +0,0 @@
module.exports = () => {
const {
GITHUB_BASE_REF,
GITHUB_RUN_ID,
PR_NUMBER,
PR_TITLE,
SHA,
TEST_TYPE,
} = process.env;
// Slack message blocks
const blocks = [];
const dividerBlock = {
type: 'divider',
};
const introBlock = {
type: 'section',
text: {
type: 'mrkdwn',
text: `${ TEST_TYPE } tests failed on \`${ GITHUB_BASE_REF }\` after merging PR <https://github.com/woocommerce/woocommerce/pull/${ PR_NUMBER }|#${ PR_NUMBER }>`,
},
};
const prTitleBlock = {
type: 'header',
text: {
type: 'plain_text',
text: PR_TITLE,
emoji: true,
},
};
const prButtonBlock = {
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: 'View pull request :pr-merged:',
emoji: true,
},
value: 'view_pr',
url: `https://github.com/woocommerce/woocommerce/pull/${ PR_NUMBER }`,
action_id: 'view-pr',
},
],
};
const mergeCommitBlock = {
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: `View merge commit ${ SHA.substring(
0,
7
) } :alphabet-yellow-hash:`,
emoji: true,
},
value: 'view_commit',
url: `https://github.com/woocommerce/woocommerce/commit/${ SHA }`,
action_id: 'view-commit',
},
],
};
const githubBlock = {
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: 'View GitHub run log :github:',
emoji: true,
},
value: 'view_github',
url: `https://github.com/woocommerce/woocommerce/actions/runs/${ GITHUB_RUN_ID }`,
action_id: 'view-github',
},
],
};
const reportBlock = {
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: 'View test report :colorful-bar-chart:',
emoji: true,
},
value: 'view_report',
url: `https://woocommerce.github.io/woocommerce-test-reports/pr-merge/${ PR_NUMBER }/${ TEST_TYPE.toLowerCase() }`,
action_id: 'view-report',
},
],
};
// Assemble blocks
blocks.push( dividerBlock );
blocks.push( introBlock );
blocks.push( prTitleBlock );
blocks.push( prButtonBlock );
blocks.push( mergeCommitBlock );
blocks.push( githubBlock );
if ( [ 'e2e', 'api' ].includes( TEST_TYPE.toLowerCase() ) ) {
blocks.push( reportBlock );
}
blocks.push( dividerBlock );
return { blocks };
};

View File

@ -1,175 +0,0 @@
module.exports = async ( { context, core, github } ) => {
const {
API_RESULT,
E2E_RESULT,
k6_RESULT,
PLUGINS_BLOCKS_PATH,
PLUGIN_TESTS_RESULT,
GITHUB_REF_NAME,
GITHUB_RUN_ID,
} = process.env;
const {
selectEmoji,
readContextBlocksFromJsonFiles,
} = require( './utils' );
const URL_GITHUB_RUN_LOG = `https://github.com/woocommerce/woocommerce/actions/runs/${ GITHUB_RUN_ID }`;
const create_blockGroup_header = async () => {
const getRunStartDate = async () => {
const response = await github.rest.actions.getWorkflowRun( {
owner: context.repo.owner,
repo: context.repo.repo,
run_id: GITHUB_RUN_ID,
} );
const runStartedAt = new Date( response.data.run_started_at );
const intlDateTimeFormatOptions = {
dateStyle: 'full',
timeStyle: 'long',
};
const date = new Intl.DateTimeFormat(
'en-US',
intlDateTimeFormatOptions
).format( runStartedAt );
return date;
};
const readableDate = await getRunStartDate();
const blocks = [
{
type: 'header',
text: {
type: 'plain_text',
text: 'Daily test results',
emoji: true,
},
},
{
type: 'divider',
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `*Run started:* ${ readableDate }`,
},
],
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `*Branch:* \`${ GITHUB_REF_NAME }\``,
},
],
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `*GitHub run logs:* <${ URL_GITHUB_RUN_LOG }|${ GITHUB_RUN_ID }>`,
},
],
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: '*Test reports dashboard:* <https://woocommerce.github.io/woocommerce-test-reports/daily/|Daily smoke tests>',
},
],
},
{
type: 'divider',
},
];
return blocks;
};
const create_blockGroup_nightlySite = () => {
const emoji_API = selectEmoji( API_RESULT );
const emoji_E2E = selectEmoji( E2E_RESULT );
const emoji_k6 = selectEmoji( k6_RESULT );
const url_API =
'https://woocommerce.github.io/woocommerce-test-reports/daily/nightly-site/api';
const url_E2E =
'https://woocommerce.github.io/woocommerce-test-reports/daily/nightly-site/e2e';
const url_k6 = URL_GITHUB_RUN_LOG;
const blocks = [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `<${ URL_GITHUB_RUN_LOG }|*Smoke tests on daily build*>`,
},
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `<${ url_API }|API> ${ emoji_API }\t<${ url_E2E }|E2E> ${ emoji_E2E }\t<${ url_k6 }|k6> ${ emoji_k6 }`,
},
],
},
{
type: 'divider',
},
];
return blocks;
};
const create_blockGroups_plugins = () => {
const pluginTestsSkipped = PLUGIN_TESTS_RESULT === 'skipped';
const blocks_pluginTestsSkipped = [
{
type: 'section',
text: {
type: 'mrkdwn',
text: ':warning: *Plugin tests were not run!*',
},
},
{
type: 'context',
elements: [
{
type: 'mrkdwn',
text: `Head over to the <${ URL_GITHUB_RUN_LOG }|GitHub workflow run log> to see what went wrong.`,
},
],
},
{
type: 'divider',
},
];
return pluginTestsSkipped
? blocks_pluginTestsSkipped
: readContextBlocksFromJsonFiles( PLUGINS_BLOCKS_PATH );
};
const blockGroup_header = await create_blockGroup_header();
const blockGroup_nightlySite = create_blockGroup_nightlySite();
const blockGroups_plugins = create_blockGroups_plugins();
const blocks_all = [
...blockGroup_header,
...blockGroup_nightlySite,
...blockGroups_plugins.flat(),
];
const payload = {
text: 'Daily test results',
blocks: blocks_all,
};
const payload_stringified = JSON.stringify( payload );
core.setOutput( 'payload', payload_stringified );
};

View File

@ -1,27 +0,0 @@
module.exports = ( { core } ) => {
const { E2E_RESULT, PLUGIN_NAME, PLUGIN_SLUG } = process.env;
const { selectEmoji } = require( './utils' );
const fs = require( 'fs' );
const emoji_E2E = selectEmoji( E2E_RESULT );
const reportURL = `https://woocommerce.github.io/woocommerce-test-reports/daily/${ PLUGIN_SLUG }/e2e`;
const blockGroup = [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `<${ reportURL }|*${ PLUGIN_NAME }*>: E2E tests ${ emoji_E2E }`,
},
},
{
type: 'divider',
},
];
const blockGroup_stringified = JSON.stringify( blockGroup );
const path = `/tmp/${ PLUGIN_SLUG }.json`;
fs.writeFileSync( path, blockGroup_stringified );
core.setOutput( 'path', path );
};

View File

@ -1,26 +0,0 @@
const fs = require( 'fs' );
const path = require( 'path' );
/**
* @param {string} blocksDir
* @returns {any[][]}
*/
const readContextBlocksFromJsonFiles = ( blocksDir ) => {
const jsonsDir = path.resolve( blocksDir );
const jsons = fs.readdirSync( jsonsDir );
let contextBlocks = [];
for ( const json of jsons ) {
const jsonPath = path.resolve( jsonsDir, json );
const contextBlock = require( jsonPath );
contextBlocks.push( contextBlock );
}
return contextBlocks;
};
module.exports = {
readContextBlocksFromJsonFiles,
};

View File

@ -1,7 +0,0 @@
const { readContextBlocksFromJsonFiles } = require( './get-context-blocks' );
const { selectEmoji } = require( './select-emoji' );
module.exports = {
readContextBlocksFromJsonFiles,
selectEmoji,
};

View File

@ -1,26 +0,0 @@
const emojis = {
PASSED: ':workflow-passed:',
FAILED: ':workflow-failed:',
SKIPPED: ':workflow-skipped:',
CANCELLED: ':workflow-cancelled:',
UNKNOWN: ':grey_question:',
};
const selectEmoji = ( result ) => {
switch ( result ) {
case 'success':
return emojis.PASSED;
case 'failure':
return emojis.FAILED;
case 'skipped':
return emojis.SKIPPED;
case 'cancelled':
return emojis.CANCELLED;
default:
return emojis.UNKNOWN;
}
};
module.exports = {
selectEmoji,
};

View File

@ -1,59 +0,0 @@
name: Compose a Slack block for release tests
description: Create a Slack block that shows the API and E2E test results from one of the release tests, and upload it as an artifact.
permissions: {}
inputs:
test-name:
required: true
api-result:
required: true
type: choice
default: skipped
options:
- success
- failure
- cancelled
- skipped
e2e-result:
required: true
type: choice
default: skipped
options:
- success
- failure
- cancelled
- skipped
env-slug:
required: true
release-version:
required: true
runs:
using: composite
steps:
- name: Create context block as a JSON object
id: generate-json
uses: actions/github-script@v6
with:
script: |
const script = require('./.github/actions/tests/slack-summary-on-release/slack-blocks/scripts/create-result-block');
return script();
env:
API_RESULT: ${{ inputs.api-result }}
E2E_RESULT: ${{ inputs.e2e-result }}
ENV_SLUG: ${{ inputs.env-slug }}
TEST_NAME: ${{ inputs.test-name }}
RELEASE_VERSION: ${{ inputs.release-version }}
- name: Write JSON file
working-directory: /tmp
shell: bash
env:
CONTEXT_JSON: ${{ toJSON(steps.generate-json.outputs.result) }}
run: echo ${{ env.CONTEXT_JSON }} > "${{ inputs.test-name }}.json"
- name: Upload JSON file as artifact
uses: actions/upload-artifact@v3
with:
name: ${{ env.SLACK_BLOCKS_ARTIFACT }}
path: /tmp/${{ inputs.test-name }}.json

View File

@ -1,31 +0,0 @@
module.exports = () => {
const { API_RESULT, E2E_RESULT, ENV_SLUG, TEST_NAME, RELEASE_VERSION } =
process.env;
const { setElementText } = require( './utils' );
const apiLinkText = setElementText( {
testType: 'API',
result: API_RESULT,
envSlug: ENV_SLUG,
releaseVersion: RELEASE_VERSION,
} );
const e2eLinkText = setElementText( {
testType: 'E2E',
result: E2E_RESULT,
envSlug: ENV_SLUG,
releaseVersion: RELEASE_VERSION,
} );
const elementText = `*${ TEST_NAME }*\n ${ apiLinkText } ${ e2eLinkText }`;
const contextBlock = {
type: 'context',
elements: [
{
type: 'mrkdwn',
text: elementText,
},
],
};
return contextBlock;
};

View File

@ -1,5 +0,0 @@
const { setElementText } = require( './set-element-text' );
module.exports = {
setElementText,
};

View File

@ -1,26 +0,0 @@
const emojis = {
PASSED: ':workflow-passed:',
FAILED: ':workflow-failed:',
SKIPPED: ':workflow-skipped:',
CANCELLED: ':workflow-cancelled:',
UNKNOWN: ':grey_question:',
};
const selectEmoji = ( result ) => {
switch ( result ) {
case 'success':
return emojis.PASSED;
case 'failure':
return emojis.FAILED;
case 'skipped':
return emojis.SKIPPED;
case 'cancelled':
return emojis.CANCELLED;
default:
return emojis.UNKNOWN;
}
};
module.exports = {
selectEmoji,
};

View File

@ -1,12 +0,0 @@
const setElementText = ( { testType, result, envSlug, releaseVersion } ) => {
const { selectEmoji } = require( './select-emoji' );
const allureReportURL = `https://woocommerce.github.io/woocommerce-test-reports/release/${ releaseVersion }/${ envSlug }/${ testType.toLowerCase() }`;
const emoji = selectEmoji( result );
const textValue = `<${ allureReportURL }|${ testType.toUpperCase() } ${ emoji }>`;
return textValue;
};
module.exports = {
setElementText,
};

View File

@ -1,27 +0,0 @@
name: Combine all Slack blocks
description: Combine all Slack blocks to construct the payload for the Slack GitHub action
permissions: {}
inputs:
release-version:
required: true
blocks-dir:
require: true
outputs:
payload:
value: ${{ steps.payload.outputs.result }}
runs:
using: composite
steps:
- name: Construct payload from all blocks
id: payload
uses: actions/github-script@v6
env:
RELEASE_VERSION: ${{ inputs.release-version }}
BLOCKS_DIR: ${{ inputs.blocks-dir }}
with:
script: |
const script = require('./.github/actions/tests/slack-summary-on-release/slack-payload/scripts/construct-payload');
return script();

View File

@ -1,36 +0,0 @@
module.exports = () => {
const { RELEASE_VERSION, BLOCKS_DIR } = process.env;
const {
filterContextBlocks,
readContextBlocksFromJsonFiles,
} = require( './utils' );
const headerText = `Test summary for ${ RELEASE_VERSION }`;
const headerBlock = {
type: 'header',
text: {
type: 'plain_text',
text: headerText,
emoji: true,
},
};
const blocks_all = readContextBlocksFromJsonFiles( BLOCKS_DIR );
const blocks_wcUpdate = filterContextBlocks( blocks_all, 'WC Update' );
const blocks_wpVersions = filterContextBlocks( blocks_all, 'WP Latest' );
const blocks_phpVersions = filterContextBlocks( blocks_all, 'PHP' );
const blocks_plugins = filterContextBlocks( blocks_all, 'With' );
const blocksPayload = [ headerBlock ]
.concat( blocks_wcUpdate )
.concat( blocks_wpVersions )
.concat( blocks_phpVersions )
.concat( blocks_plugins );
const payload = {
text: headerText,
blocks: blocksPayload,
};
return payload;
};

View File

@ -1,42 +0,0 @@
const fs = require( 'fs' );
const path = require( 'path' );
const readContextBlocksFromJsonFiles = ( blocksDir ) => {
const jsonsDir = path.resolve( blocksDir );
const jsons = fs.readdirSync( jsonsDir );
let contextBlocks = [];
for ( const json of jsons ) {
const jsonPath = path.resolve( jsonsDir, json );
const contextBlock = require( jsonPath );
contextBlocks.push( contextBlock );
}
return contextBlocks;
};
const filterContextBlocks = ( blocks, testName ) => {
const divider = {
type: 'divider',
};
let filteredBlocks = [];
const matchingBlocks = blocks.filter( ( { elements } ) =>
elements[ 0 ].text.includes( testName )
);
matchingBlocks.forEach( ( block ) => {
filteredBlocks.push( block );
filteredBlocks.push( divider );
} );
return filteredBlocks;
};
module.exports = {
filterContextBlocks,
readContextBlocksFromJsonFiles,
};

View File

@ -1,6 +0,0 @@
const {
filterContextBlocks,
readContextBlocksFromJsonFiles,
} = require( './get-context-blocks' );
module.exports = { filterContextBlocks, readContextBlocksFromJsonFiles };

View File

@ -1,37 +0,0 @@
name: Upload Allure files to bucket
description: Upload Allure files to bucket.
permissions: {}
inputs:
artifact-name:
description: Name of the artifact that contains the allure-report and/or allure-results folders.
required: true
aws-region:
required: true
aws-access-key-id:
required: true
aws-secret-access-key:
required: true
s3-bucket:
required: true
include-allure-results:
dafault: false
runs:
using: composite
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-region: ${{ inputs.aws-region }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
- name: Upload Allure artifact
env:
ARTIFACT_NAME: ${{ inputs.artifact-name }}
S3_BUCKET: ${{ inputs.s3-bucket }}
INCLUDE_ALLURE_RESULTS: ${{ inputs.include-allure-results }}
shell: bash
working-directory: .github/actions/tests/upload-allure-files-to-bucket/scripts
run: bash upload-allure-artifact.sh

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
s3_upload () {
aws s3 cp "$1" "$2" \
--recursive
}
upload_allure_results () {
if [[ $INCLUDE_ALLURE_RESULTS != "true" ]]; then
return
fi
SOURCE="$ALLURE_RESULTS_DIR"
DESTINATION="$S3_BUCKET/artifacts/$GITHUB_RUN_ID/$ARTIFACT_NAME/allure-results"
s3_upload "$SOURCE" "$DESTINATION"
}
upload_allure_report () {
SOURCE="$ALLURE_REPORT_DIR"
DESTINATION="$S3_BUCKET/artifacts/$GITHUB_RUN_ID/$ARTIFACT_NAME/allure-report"
s3_upload "$SOURCE" "$DESTINATION"
}
upload_allure_results
upload_allure_report
EXIT_CODE=$(echo $?)
exit $EXIT_CODE

View File

@ -81,6 +81,13 @@
"plugins/woocommerce/**/*":
- team: proton
"plugins/woocommerce/templates/**/*":
- team: rubik
- team: woo-fse
"plugins/woocommerce/templates/emails/**/*":
- team: proton
"plugins/woocommerce/src/Admin/**/*":
- team: mothra
- team: ghidorah

View File

@ -12,4 +12,4 @@ jobs:
uses: acq688/Request-Reviewer-For-Team-Action@v1.1
with:
config: '.github/automate-team-review-assignment-config.yml'
GITHUB_TOKEN: ${{ secrets.FINE_GRAINED_TOKEN_ACTIONS }}
GITHUB_TOKEN: ${{ secrets.PR_ASSIGN_TOKEN }}

View File

@ -1,72 +0,0 @@
name: Blocks Playwright Tests
on:
pull_request:
paths:
- '.github/workflows/blocks-playwright.yml' # This file
- 'plugins/woocommerce-blocks/**'
- 'plugins/woocommerce/src/Blocks/**'
- 'plugins/woocommerce/templates/**'
- 'plugins/woocommerce/patterns/**'
# Allow manually triggering the workflow.
workflow_dispatch:
env:
FORCE_COLOR: 1
jobs:
blocks-playwright-tests:
name: Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}
timeout-minutes: 60
runs-on: ubuntu-latest
defaults:
run:
working-directory: plugins/woocommerce-blocks
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Install Playwright dependencies
run: pnpm exec playwright install chromium --with-deps
- name: Setup testing environment and start the server
run: pnpm env:start
- name: Run Playwright tests
run: pnpm test:e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Archive debug artifacts (screenshots, traces)
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: failures-artifacts-shard-${{ matrix.shardIndex }}
path: plugins/woocommerce-blocks/tests/e2e/artifacts/test-results
if-no-files-found: ignore
merge-artifacts:
# Merges all artifacts from all shards into a single zip and
# deletes the parts. In case of a rerun, artifacts from the
# previous run will be retained by merging them with the new ones.
name: Merge Artifacts
if: ${{ !cancelled() }}
needs: [blocks-playwright-tests]
runs-on: ubuntu-latest
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
# Don't fail the job if there aren't any artifacts to merge.
continue-on-error: true
with:
name: failures-artifacts
delete-merged: true

View File

@ -17,7 +17,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get current version
id: version
@ -30,16 +30,16 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Prepare plugin zips
id: prepare
env:
CURRENT_VERSION: ${{ steps.version.outputs.version }}
# Build with experimental blocks.
WOOCOMMERCE_BLOCKS_PHASE: 3
run: |
# Current version must compare greather than any previously used current version for this PR.
# Current version must compare greater than any previously used current version for this PR.
# Assume GH run IDs are monotonic.
VERSUFFIX="${GITHUB_RUN_ID}-g$(git rev-parse --short HEAD)"
@ -52,12 +52,12 @@ jobs:
bash bin/build-zip.sh
mkdir "$GITHUB_WORKSPACE/zips"
cp "$GITHUB_WORKSPACE/plugins/woocommerce/woocommerce.zip" "$GITHUB_WORKSPACE/zips/woocommerce.zip"
mv "$GITHUB_WORKSPACE/plugins/woocommerce/woocommerce.zip" "$GITHUB_WORKSPACE/zips/woocommerce.zip"
cd "$GITHUB_WORKSPACE/zips"
unzip woocommerce.zip
unzip -qq woocommerce.zip
rm woocommerce.zip
mv woocommerce woocommerce-dev
zip -q -r "woocommerce-dev.zip" "woocommerce-dev/"
zip -q -r -9 "woocommerce-dev.zip" "woocommerce-dev/"
rm -fR "$GITHUB_WORKSPACE/zips/woocommerce-dev"
# Plugin data is passed as a JSON object.
@ -66,7 +66,7 @@ jobs:
echo "plugin-data=$PLUGIN_DATA" >> $GITHUB_OUTPUT
- name: Create plugins artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: steps.prepare.outputs.plugin-data != '{}'
with:
name: plugins

View File

@ -20,6 +20,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip
working-directory: plugins/woocommerce

View File

@ -23,11 +23,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: 'monorepo-utils...'
build: 'monorepo-utils'
- name: Generate Changelog File
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.PR_CREATE_TOKEN || secrets.GITHUB_TOKEN }}
run: pnpm utils changefile ${{github.event.number || inputs.prNumber}} -o ${{ github.repository_owner }}

View File

@ -5,10 +5,24 @@ on:
branches:
- 'trunk'
- 'release/*'
release:
types: [ published, edited ]
workflow_call:
inputs:
trigger:
description: 'Type of run to trigger. E.g. daily-e2e, release-checks, etc.'
required: true
default: 'default'
type: string
concurrency:
group: '${{ github.workflow }}-${{ github.ref }}'
# Cancel concurrent jobs but not for push event. For push use the run_id to have a unique group.
group: ci-${{ github.event_name == 'push' && github.run_id || github.event_name }}-${{ github.ref }}-${{ inputs.trigger }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
project-jobs:
# Since this is a monorepo, not every pull request or change is going to impact every project.
@ -19,24 +33,26 @@ jobs:
runs-on: 'ubuntu-20.04'
outputs:
lint-jobs: ${{ steps.project-jobs.outputs.lint-jobs }}
default-test-jobs: ${{ steps.project-jobs.outputs.default-test-jobs }}
e2e-test-jobs: ${{ steps.project-jobs.outputs.e2e-test-jobs }}
api-test-jobs: ${{ steps.project-jobs.outputs.api-test-jobs }}
performance-test-jobs: ${{ steps.project-jobs.outputs.performance-test-jobs }}
test-jobs: ${{ steps.project-jobs.outputs.test-jobs }}
report-jobs: ${{ steps.project-jobs.outputs.report-jobs }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
with:
fetch-depth: 0
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
with:
php-version: false # We don't want to waste time installing PHP since we aren't using it in this job.
# If 'base_ref' is available, the 'Build Matrix' step requires non-shallow git-history to identify changed files.
fetch-depth: ${{ ( ( github.base_ref && '0' ) || '1' ) }}
- name: 'Setup PNPM'
uses: 'pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d'
- uses: actions/github-script@v7
name: 'Build Matrix'
id: 'project-jobs'
with:
script: |
// Intended behaviour of the jobs generation:
// - PRs: run CI jobs aiming PRs and filter out jobs based on the content changes
// - Pushes: run CI jobs aiming pushes without filtering based on the content changes
// github.base_ref is only available for pull_request events
let baseRef = ${{ toJson( github.base_ref ) }};
if ( baseRef ) {
baseRef = `--base-ref origin/${ baseRef }`;
@ -44,6 +60,28 @@ jobs:
let githubEvent = ${{ toJson( github.event_name ) }};
const refType = ${{ toJson( github.ref_type ) }};
const refName = ${{ toJson( github.ref_name ) }};
if ( refType === 'tag' && refName !== 'nightly' ) {
githubEvent = 'release-checks';
}
if ( refType === 'tag' && refName === 'nightly' ) {
githubEvent = 'nightly-checks';
}
let trigger = ${{ toJson( inputs.trigger ) }};
if ( trigger ) {
githubEvent = trigger;
}
// `pre-release` should trigger `release-checks`, but without a 'tag' ref.
// This will run all release-checks against the branch the workflow targeted, instead of a release artifact.
if ( trigger === 'pre-release' ) {
githubEvent = 'release-checks';
}
const child_process = require( 'node:child_process' );
child_process.execSync( `pnpm utils ci-jobs ${ baseRef } --event ${ githubEvent }` );
@ -51,7 +89,7 @@ jobs:
name: "Lint - ${{ matrix.projectName }} ${{ matrix.optional && ' (optional)' || ''}}"
runs-on: 'ubuntu-20.04'
needs: 'project-jobs'
if: ${{ needs.project-jobs.outputs.lint-jobs != '[]' }}
if: ${{ needs.project-jobs.outputs.lint-jobs != '[]' && github.event_name == 'pull_request' }}
strategy:
fail-fast: false
matrix:
@ -60,174 +98,158 @@ jobs:
- uses: 'actions/checkout@v4'
name: 'Checkout'
with:
fetch-depth: 0
# the WooCommerce plugin package uses phpcs-changed for linting, which requires non-shallow git-history.
fetch-depth: ${{ ( ( matrix.projectName == '@woocommerce/plugin-woocommerce' && '0' ) || '1' ) }}
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
with:
install: '${{ matrix.projectName }}...'
build: '${{ matrix.projectName }}'
pull-package-deps: '${{ matrix.projectName }}'
- name: 'Lint'
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
project-default-test-jobs:
name: "Test - ${{ matrix.projectName }} - ${{ matrix.name }} ${{ matrix.optional && ' (optional)' || '' || ''}}"
project-test-jobs:
name: "${{ matrix.name }}"
runs-on: 'ubuntu-20.04'
needs: 'project-jobs'
if: ${{ needs.project-jobs.outputs.default-test-jobs != '[]' }}
if: ${{ needs.project-jobs.outputs.test-jobs != '[]' }}
env: ${{ matrix.testEnv.envVars }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON( needs.project-jobs.outputs.default-test-jobs ) }}
include: ${{ fromJSON( needs.project-jobs.outputs.test-jobs ) }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
name: 'Install Monorepo'
id: 'install-monorepo'
with:
install: '${{ matrix.projectName }}...'
build: '${{ matrix.projectName }}'
- name: 'Prepare Test Environment'
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }}
pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }}
pull-package-deps: '${{ matrix.projectName }}'
- name: 'Update wp-env config'
if: ${{ github.ref_type == 'tag' }}
env:
RELEASE_TAG: ${{ github.ref_name }}
ARTIFACT_NAME: ${{ github.ref_name == 'nightly' && 'woocommerce-trunk-nightly.zip' || 'woocommerce.zip' }}
# band-aid to get the path to wp-env.json for blocks e2e tests, until they're migrated to plugins/woocommerce
WP_ENV_CONFIG_PATH: ${{ github.workspace }}/${{ matrix.testEnv.start == 'env:start:blocks' && 'plugins/woocommerce-blocks' || matrix.projectPath }}
run: node .github/workflows/scripts/override-wp-env-plugins.js
- name: 'Start Test Environment'
id: 'prepare-test-environment'
if: ${{ matrix.testEnv.shouldCreate }}
env: ${{ matrix.testEnv.envVars }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}'
- name: 'Test'
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
project-e2e-test-jobs:
name: "E2E - ${{ matrix.name }} ${{ matrix.optional && ' (optional)' || ''}}"
runs-on: 'ubuntu-20.04'
needs: 'project-jobs'
if: ${{ needs.project-jobs.outputs.e2e-test-jobs != '[]' }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON( needs.project-jobs.outputs.e2e-test-jobs ) }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
with:
install: '${{ matrix.projectName }}...'
build: '${{ matrix.projectName }}'
- name: Get commit message
id: get_commit_message
- name: 'Determine BuildKite Analytics Message'
env:
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
COMMIT_MESSAGE=`echo "$HEAD_COMMIT_MESSAGE" | head -1`
MESSAGE=`echo "$HEAD_COMMIT_MESSAGE" | head -1`
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
COMMIT_MESSAGE="$PR_TITLE"
MESSAGE="$PR_TITLE"
else
COMMIT_MESSAGE="${{ github.event_name }}"
MESSAGE="${{ github.event_name }}"
fi
echo "COMMIT_MESSAGE=$COMMIT_MESSAGE" >> "$GITHUB_OUTPUT"
echo "BUILDKITE_ANALYTICS_MESSAGE=$MESSAGE" >> "$GITHUB_OUTPUT"
shell: bash
- name: 'Prepare Test Environment'
id: 'prepare-test-environment'
if: ${{ matrix.testEnv.shouldCreate }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}'
- name: 'Run tests'
- name: 'Resolve artifacts path'
if: ${{ always() && matrix.report.resultsPath != '' }}
# Blocks e2e use a relative path which is not supported by actions/upload-artifact@v4
# https://github.com/actions/upload-artifact/issues/176
env:
ARTIFACTS_PATH: '${{ matrix.projectPath }}/${{ matrix.report.resultsPath }}'
run: |
# first runs will probably not have the directory, so we need to create it so that realpath doesn't fail
mkdir -p $ARTIFACTS_PATH
echo "ARTIFACTS_PATH=$(realpath $ARTIFACTS_PATH)" >> $GITHUB_ENV
- name: 'Download Playwright last run info'
id: 'download-last-run-info'
if: ${{ always() && matrix.report.resultsPath != '' && matrix.testType == 'e2e' }}
uses: actions/download-artifact@v4
with:
pattern: 'last-run__${{ strategy.job-index }}'
- name: 'Run tests (${{ matrix.testType }})'
env:
E2E_ENV_KEY: ${{ secrets.E2E_ENV_KEY }}
BUILDKITE_ANALYTICS_TOKEN: ${{ secrets.BUILDKITE_CORE_E2E_TOKEN }}
BUILDKITE_ANALYTICS_MESSAGE: ${{ steps.get_commit_message.outputs.COMMIT_MESSAGE }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }} # required by Metrics tests
LAST_FAILED_RUN: ${{ vars.LAST_FAILED_RUN }}
run: |
lastRunFile="${{ steps.download-last-run-info.outputs.download-path }}/last-run__${{ strategy.job-index }}/.last-run.json"
lastRunFileDest="$ARTIFACTS_PATH/.last-run.json"
- name: 'Upload artifacts'
if: ${{ always() }}
if [ -f "$lastRunFile" ]; then
echo "Found last run info file: \"$lastRunFile\""
echo "Moving to destination: \"$lastRunFileDest\""
mkdir -p "$ARTIFACTS_PATH"
mv "$lastRunFile" "$lastRunFileDest"
else
echo "No last run info file found. Searched for: \"$lastRunFile\""
fi
lastRunFlag=""
if [ -f "$lastRunFileDest" ]; then
# Playwright last run info is available, parse the file and check if there are failed tests
cat "$lastRunFileDest"
failedTests=$(jq '.failedTests | length' "$lastRunFileDest")
# Only if there are failed tests, we want to use the --last-failed flag.
# The run will fail if we're using the flag and there are no failed tests.
if [ "$failedTests" -gt 0 ]; then
if [ "$LAST_FAILED_RUN" == "1" ]; then
echo "Found failed tests, running only failed tests"
# Add shard 1/1 to override the default shard value. No tests will run for shards > 1.
# The clean way would be to replace the shard flag from the command, but this also works.
lastRunFlag="--last-failed --shard=1/1"
else
echo "Found failed tests, but LAST_FAILED_RUN is switched off. Running all tests."
fi
else
echo "No failed tests found, running all tests"
fi
fi
# Finally, run the tests
pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }} $lastRunFlag
- name: 'Upload Playwright last run info'
# always upload the last run info, even if the test run passed
if: ${{ always() && matrix.report.resultsPath != '' }}
uses: actions/upload-artifact@v4
with:
name: all-blob-e2e-reports-${{ strategy.job-index }}
path: ${{ matrix.projectPath }}/tests/e2e-pw/test-results
retention-days: 1
compression-level: 9
project-api-test-jobs:
name: "API - ${{ matrix.name }} ${{ matrix.optional && ' (optional)' || ''}}"
runs-on: 'ubuntu-20.04'
needs: 'project-jobs'
if: ${{ needs.project-jobs.outputs.api-test-jobs != '[]' }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON( needs.project-jobs.outputs.api-test-jobs ) }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
with:
install: '${{ matrix.projectName }}...'
build: '${{ matrix.projectName }}'
- name: 'Prepare Test Environment'
id: 'prepare-test-environment'
if: ${{ matrix.testEnv.shouldCreate }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}'
- name: 'Run tests'
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
name: 'last-run__${{ strategy.job-index }}'
path: '${{ env.ARTIFACTS_PATH }}/.last-run.json'
if-no-files-found: ignore
overwrite: true
- name: 'Upload artifacts'
if: ${{ always() }}
if: ${{ always() && matrix.report.resultsPath != '' }}
uses: actions/upload-artifact@v4
with:
name: all-blob-api-reports-${{ strategy.job-index }}
path: ${{ matrix.projectPath }}/tests/api-core-tests/test-results/allure-results
retention-days: 1
compression-level: 9
name: '${{ matrix.report.resultsBlobName }}__${{ strategy.job-index }}'
path: ${{ env.ARTIFACTS_PATH }}
project-performance-test-jobs:
name: "Performance - ${{ matrix.name }} ${{ matrix.optional && ' (optional)' || ''}}"
runs-on: 'ubuntu-20.04'
needs: 'project-jobs'
if: ${{ needs.project-jobs.outputs.performance-test-jobs != '[]' }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON( needs.project-jobs.outputs.performance-test-jobs ) }}
env:
WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
- name: 'Upload flaky test reports'
uses: actions/upload-artifact@v4
with:
install: '${{ matrix.projectName }}...'
build: '${{ matrix.projectName }}'
- name: 'Prepare Test Environment'
id: 'prepare-test-environment'
if: ${{ matrix.testEnv.shouldCreate }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.testEnv.start }}'
- name: 'Run tests'
env:
CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }}
run: 'pnpm --filter="${{ matrix.projectName }}" ${{ matrix.command }}'
- name: 'Archive metrics results'
if: ${{ success() && matrix.name == 'Metrics' }}
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: metrics-results
path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json
name: flaky-tests-${{ strategy.job-index }}
path: ${{ env.ARTIFACTS_PATH }}/flaky-tests
if-no-files-found: ignore
evaluate-project-jobs:
# In order to add a required status check we need a consistent job that we can grab onto.
@ -241,12 +263,9 @@ jobs:
[
'project-jobs',
'project-lint-jobs',
'project-default-test-jobs',
'project-e2e-test-jobs',
'project-api-test-jobs',
'project-performance-test-jobs'
'project-test-jobs',
]
if: ${{ always() }}
if: ${{ !cancelled() && github.event_name == 'pull_request' }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
@ -266,136 +285,140 @@ jobs:
node .github/workflows/scripts/evaluate-jobs-conclusions.js
e2e-test-reports:
name: 'Report e2e tests results'
needs: [project-e2e-test-jobs]
if: ${{ ! cancelled() && needs.project-e2e-test-jobs.result != 'skipped' }}
alert-on-failure:
name: 'Report results on Slack'
runs-on: 'ubuntu-20.04'
needs:
[
'project-jobs',
'project-lint-jobs',
'project-test-jobs',
]
if: ${{ !cancelled() && github.event_name != 'pull_request' && github.repository == 'woocommerce/woocommerce' }}
steps:
- uses: 'actions/checkout@v4'
name: 'Checkout'
- name: 'Setup PNPM'
uses: 'pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d'
- name: 'Send messages for failed jobs'
env:
SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}
SLACK_CHANNEL: ${{ secrets.TEST_REPORTS_SLACK_CHANNEL }}
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
INPUT_TRIGGER: ${{ inputs.trigger }}
RUN_TYPE: ${{ github.ref_type == 'tag' && (github.ref_name == 'nightly' && 'nightly-checks' || 'release-checks') || '' }}
run: |
COMMIT_MESSAGE=`echo "$HEAD_COMMIT_MESSAGE" | head -1`
if [[ -n "${INPUT_TRIGGER}" ]]; then
CHECKS_TYPE="${INPUT_TRIGGER}"
else
CHECKS_TYPE="${RUN_TYPE}"
fi
pnpm utils slack-test-report -c "${{ needs.project-jobs.result }}" -r "$CHECKS_TYPE Build jobs matrix" -m "$COMMIT_MESSAGE"
pnpm utils slack-test-report -c "${{ needs.project-lint-jobs.result }}" -r "$CHECKS_TYPE Linting" -m "$COMMIT_MESSAGE"
pnpm utils slack-test-report -c "${{ needs.project-test-jobs.result }}" -r "$CHECKS_TYPE Tests" -m "$COMMIT_MESSAGE"
test-reports:
name: 'Test reports - ${{ matrix.report }}'
needs:
[
'project-jobs',
'project-test-jobs',
]
if: ${{ !cancelled() && needs.project-jobs.outputs.report-jobs != '[]' && github.repository == 'woocommerce/woocommerce' }}
strategy:
fail-fast: false
matrix:
report: ${{ fromJSON( needs.project-jobs.outputs.report-jobs ) }}
runs-on: ubuntu-latest
env:
ARTIFACT_NAME: ${{ matrix.report }}-attempt-${{ github.run_attempt }}
steps:
- uses: actions/checkout@v4
- name: 'Install Allure CLI'
env:
DESTINATION_PATH: ../
run: ./.github/workflows/scripts/install-allure.sh
- name: 'Download blob reports from artifacts'
uses: actions/download-artifact@v4
- name: 'Merge artifacts'
id: merge-artifacts
uses: actions/upload-artifact/merge@v4
continue-on-error: true
with:
path: ./out
pattern: all-blob-e2e-reports-*
run-id: project-e2e-test-jobs
merge-multiple: true
name: ${{ env.ARTIFACT_NAME }}
pattern: ${{ matrix.report }}__*
delete-merged: true
- name: 'Generate Allure report'
id: generate_allure_report
run: allure generate --clean ./out/allure-results --output ./out/allure-report
- name: 'Archive reports'
uses: actions/upload-artifact@v4
with:
name: e2e-test-report
path: ./out
if-no-files-found: ignore
retention-days: 5
- name: 'Send workflow dispatch'
- name: 'Publish report to dashboard'
if: ${{ !! steps.merge-artifacts.outputs.artifact-id }}
env:
GH_TOKEN: ${{ secrets.REPORTS_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_ID: ${{ github.run_id }}
REPORT_NAME: ${{ matrix.report }}
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
PR_TITLE: ${{ github.event.pull_request.title }}
EVENT_NAME: ${{ inputs.trigger == '' && github.event_name || inputs.trigger }}
run: |
if [ "$GITHUB_EVENT_NAME" == pull_request ]; then
gh workflow run publish-test-reports-pr.yml \
-f run_id=$RUN_ID \
-f e2e_artifact=e2e-test-report \
-f pr_number=$PR_NUMBER \
-f commit_sha=$GITHUB_SHA \
-f s3_root=public \
--repo woocommerce/woocommerce-test-reports
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
REPORT_TITLE="$PR_TITLE"
REF_NAME="$GITHUB_HEAD_REF"
elif [[ "${{ github.event_name }}" == "push" ]]; then
REPORT_TITLE=`echo "$HEAD_COMMIT_MESSAGE" | head -1`
REF_NAME="$GITHUB_REF_NAME"
else
gh workflow run publish-test-reports-trunk-merge.yml \
-f run_id=$RUN_ID \
-f artifact=e2e-test-report \
-f pr_number=$PR_NUMBER \
-f commit_sha=$GITHUB_SHA \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
REPORT_TITLE="$EVENT_NAME"
REF_NAME="$GITHUB_REF_NAME"
fi
- name: 'Send Slack notification'
if: github.event_name != 'pull_request'
uses: automattic/action-test-results-to-slack@v0.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
slack_token: ${{ secrets.E2E_SLACK_TOKEN }}
slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
playwright_report_path: ./out/test-results-*.json
playwright_output_dir: ./out/results-data
gh workflow run report.yml \
-f artifact="$ARTIFACT_NAME" \
-f run_id="$GITHUB_RUN_ID" \
-f run_attempt="$GITHUB_RUN_ATTEMPT" \
-f event="$EVENT_NAME" \
-f pr_number="$PR_NUMBER" \
-f ref_name="$REF_NAME" \
-f commit_sha="$GITHUB_SHA" \
-f repository="$GITHUB_REPOSITORY" \
-f suite="$REPORT_NAME" \
-f report_title="$REPORT_TITLE" \
--repo woocommerce/woocommerce-test-reports
api-test-reports:
name: 'Report API tests results'
needs: [project-api-test-jobs]
if: ${{ ! cancelled() && needs.project-api-test-jobs.result != 'skipped'}}
report-flaky-tests:
name: 'Create issues for flaky tests'
if: ${{ !cancelled() && github.repository == 'woocommerce/woocommerce' && needs.project-jobs.outputs.test-jobs != '[]' }}
needs:
[
'project-jobs',
'project-test-jobs',
]
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
- uses: 'actions/checkout@v4'
name: 'Checkout'
- name: 'Install Allure CLI'
env:
DESTINATION_PATH: ../
run: ./.github/workflows/scripts/install-allure.sh
- name: 'Download blob reports from artifacts'
uses: actions/download-artifact@v4
- uses: 'actions/download-artifact@v4'
name: 'Download artifacts'
with:
path: ./out/allure-results
pattern: all-blob-api-reports-*
run-id: project-api-test-jobs
pattern: flaky-tests*
path: flaky-tests
merge-multiple: true
- name: 'Generate Allure report'
id: generate_allure_report
run: allure generate --clean ./out/allure-results --output ./out/allure-report
- name: 'Archive reports'
uses: actions/upload-artifact@v4
with:
name: api-test-report
path: ./out
if-no-files-found: ignore
retention-days: 5
- name: 'Publish reports'
env:
GH_TOKEN: ${{ secrets.REPORTS_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_ID: ${{ github.run_id }}
- name: 'Merge flaky tests reports'
run: |
if [ "$GITHUB_EVENT_NAME" == pull_request ]; then
gh workflow run publish-test-reports-pr.yml \
-f run_id=$RUN_ID \
-f api_artifact=api-test-report \
-f pr_number=$PR_NUMBER \
-f commit_sha=$GITHUB_SHA \
-f s3_root=public \
--repo woocommerce/woocommerce-test-reports
else
gh workflow run publish-test-reports-trunk-merge.yml \
-f run_id=$RUN_ID \
-f artifact=api-test-report \
-f pr_number=$PR_NUMBER \
-f commit_sha=$GITHUB_SHA \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
fi
downloadPath='${{ steps.download-artifact.outputs.download-path || './flaky-tests' }}'
# make dir so that next step doesn't fail if it doesn't exist
mkdir -p $downloadPath
# any output means there are reports
echo "FLAKY_REPORTS=$(ls -A $downloadPath | head -1)" >> $GITHUB_ENV
- name: 'Send Slack notification'
if: github.event_name != 'pull_request'
uses: automattic/action-test-results-to-slack@v0.3.0
- name: 'Report flaky tests'
if: ${{ !!env.FLAKY_REPORTS }}
uses: './.github/actions/report-flaky-tests'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
slack_token: ${{ secrets.E2E_SLACK_TOKEN }}
slack_channel: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
label: 'metric: flaky e2e test'

View File

@ -0,0 +1,146 @@
name: Blocks Playwright Tests
on:
pull_request:
paths:
- '.github/workflows/blocks-playwright.yml' # This file
- 'plugins/woocommerce-blocks/**'
- 'plugins/woocommerce/src/Blocks/**'
- 'plugins/woocommerce/templates/**'
- 'plugins/woocommerce/patterns/**'
# Allow manually triggering the workflow.
workflow_dispatch:
# Run workflow when a PR is merged to trunk branch
# to create github issues for flaky tests.
push:
branches:
- trunk
paths:
- '.github/workflows/blocks-playwright.yml' # This file
- 'plugins/woocommerce-blocks/**'
- 'plugins/woocommerce/src/Blocks/**'
- 'plugins/woocommerce/templates/**'
- 'plugins/woocommerce/patterns/**'
concurrency:
group: '${{ github.workflow }}-${{ github.ref }}'
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
blocks-playwright-tests:
name: Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }}
timeout-minutes: 60
runs-on: ubuntu-latest
defaults:
run:
working-directory: plugins/woocommerce-blocks
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
pull-playwright-cache: true
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Install Playwright dependencies
run: pnpm exec playwright install chromium --with-deps
- name: Setup testing environment and start the server
run: pnpm env:start
- name: Run Playwright tests
run: pnpm test:e2e --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Archive debug artifacts (screenshots, traces)
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: failures-artifacts-shard-${{ matrix.shardIndex }}
path: plugins/woocommerce-blocks/tests/e2e/artifacts/test-results
if-no-files-found: ignore
- name: Archive flaky test reports
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: flaky-tests-report-shard-${{ matrix.shardIndex }}
path: plugins/woocommerce-blocks/flaky-tests
if-no-files-found: ignore
merge-artifacts:
# Merges all artifacts from all shards into a single zip and
# deletes the parts. In case of a rerun, artifacts from the
# previous run will be retained by merging them with the new ones.
name: Merge Artifacts
if: ${{ !cancelled() }}
needs: [blocks-playwright-tests]
runs-on: ubuntu-latest
outputs:
has-flaky-test-report: ${{ !!steps.merge-flaky-tests-reports.outputs.artifact-id }}
steps:
- name: Merge failures artifacts
uses: actions/upload-artifact/merge@v4
# Don't fail the job if there aren't any artifacts to merge.
continue-on-error: true
with:
name: failures-artifacts
pattern: failures-artifacts*
delete-merged: true
- name: Merge flaky tests reports
id: merge-flaky-tests-reports
uses: actions/upload-artifact/merge@v4
continue-on-error: true
with:
name: flaky-tests-report
pattern: flaky-tests-report*
delete-merged: true
create-github-issues-for-flaky-tests:
name: Create GitHub issues for flaky tests
needs: [merge-artifacts]
if: ${{ !cancelled() && needs.merge-artifacts.outputs.has-flaky-test-report == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
# We are using @wordpress/report-flaky-tests package from Gutenberg repo
# to create GitHub issues for flaky tests.
- uses: actions/checkout@v4
with:
repository: WordPress/gutenberg
# We are using commit hash to checkout the exact version of the script.
# This is to avoid any breaking changes.
ref: dbf201449e9736f672b61e422787d47659db327a
- uses: actions/download-artifact@v4
with:
name: flaky-tests-report
path: flaky-tests
- name: Setup Node.js and install dependencies
uses: ./.github/setup-node
- name: Npm build
# TODO: We don't have to build the entire project, just the action itself.
run: npm run build:packages
- name: Report flaky tests
uses: ./packages/report-flaky-tests
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
label: 'metric: flaky e2e test'
artifact-path: flaky-tests

View File

@ -1,61 +0,0 @@
name: Metrics Tracking
on:
pull_request:
push:
branches: [trunk]
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
permissions: {}
jobs:
metrics:
name: Run metrics tests
runs-on: ubuntu-20.04
permissions:
contents: read
env:
WP_ARTIFACTS_PATH: ${{ github.workspace }}/artifacts
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Compare performance with trunk
if: github.event_name == 'pull_request'
run: cd tools/compare-perf && pnpm run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA
- name: Compare performance with base branch
if: github.event_name == 'push'
# The base hash used here need to be a commit that is compatible with the current WP version
# The current one is 19f3d0884617d7ecdcf37664f648a51e2987cada
# it needs to be updated every time it becomes unsupported by the current wp-env (WP version).
# It is used as a base comparison point to avoid fluctuation in the performance metrics.
run: |
WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' plugins/woocommerce/readme.txt)
IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION"
WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}"
cd tools/compare-perf && pnpm run compare perf $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
- name: Archive performance results
if: success()
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: performance-results
path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json
- name: Publish performance results
if: github.event_name == 'push'
env:
CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }}
run: |
COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI")
cd tools/compare-perf && pnpm run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada $COMMITTED_AT

View File

@ -1,169 +0,0 @@
name: Run tests with HPOS disabled
on:
push:
branches:
- 'trunk'
- 'release/*'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
non-hpos-e2e-tests-run:
name: Runs E2E tests with HPOS disabled.
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard:
[
{ number: 1, name: 1/5 },
{ number: 2, name: 2/5 },
{ number: 3, name: 3/5 },
{ number: 4, name: 4/5 },
{ number: 5, name: 5/5 },
]
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers
working-directory: plugins/woocommerce
run: pnpm env:test
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run Playwright E2E tests.
timeout-minutes: 60
id: run_playwright_e2e_tests
env:
USE_WP_ENV: 1
ENABLE_HPOS: 0
FORCE_COLOR: 1
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js --shard ${{ matrix.shard.name }}
- name: Upload reports to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: all-blob-reports-${{ matrix.shard.number }}
path: ${{ env.ALLURE_RESULTS_DIR }}
retention-days: 1
compression-level: 9
merge-reports:
name: Merge e2e test reports
# Merge reports after playwright-tests, even if some shards have failed
if: always()
needs: [non-hpos-e2e-tests-run]
runs-on: ubuntu-latest
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: ${{ env.ALLURE_RESULTS_DIR }}
pattern: all-blob-reports-*
run-id: e2e-tests-run
merge-multiple: true
- name: Generate Playwright E2E Test report.
id: generate_e2e_report
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright E2E test report
if: |
always() &&
steps.generate_e2e_report.conclusion == 'success'
uses: actions/upload-artifact@v4
with:
name: e2e-test-report---pr-${{ github.event.number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
non-hpos-api-tests-run:
name: Runs API tests with HPOS disabled.
runs-on: ubuntu-latest
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers
working-directory: plugins/woocommerce
run: pnpm --filter='@woocommerce/plugin-woocommerce' env:test
- name: Run Playwright API tests.
id: run_playwright_api_tests
working-directory: plugins/woocommerce
env:
BASE_URL: http://localhost:8086
USER_KEY: admin
USER_SECRET: password
ENABLE_HPOS: 0
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
- name: Generate Playwright API Test report.
id: generate_api_report
if: |
always() &&
(
steps.run_playwright_api_tests.conclusion != 'cancelled' ||
steps.run_playwright_api_tests.conclusion != 'skipped'
)
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright API test report
if: |
always() &&
steps.generate_api_report.conclusion == 'success'
uses: actions/upload-artifact@v3
with:
name: api-test-report---pr-${{ github.event.number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5

View File

@ -1,179 +0,0 @@
name: Run daily tests in an environment with HPOS disabled
on:
schedule:
- cron: '30 2 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
non-hpos-e2e-tests-run:
name: Runs E2E tests with HPOS disabled.
runs-on: ubuntu-20.04
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers with HPOS disabled.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 0
run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:no-hpos
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run Playwright E2E tests.
timeout-minutes: 60
id: run_playwright_e2e_tests
env:
USE_WP_ENV: 1
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js
- name: Generate Playwright E2E Test report.
id: generate_e2e_report
if: |
always() &&
(
steps.run_playwright_e2e_tests.conclusion != 'cancelled' ||
steps.run_playwright_e2e_tests.conclusion != 'skipped'
)
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright E2E test report
if: |
always() &&
steps.generate_e2e_report.conclusion == 'success'
uses: actions/upload-artifact@v3
with:
name: e2e-test-report---pr-${{ github.run_number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
non-hpos-api-tests-run:
name: Runs API tests with HPOS disabled.
runs-on: ubuntu-20.04
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers with HPOS disabled.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 0
run: pnpm --filter=@woocommerce/plugin-woocommerce env:test:no-hpos
- name: Run Playwright API tests.
id: run_playwright_api_tests
working-directory: plugins/woocommerce
env:
BASE_URL: http://localhost:8086
USER_KEY: admin
USER_SECRET: password
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
- name: Generate Playwright API Test report.
id: generate_api_report
if: |
always() &&
(
steps.run_playwright_api_tests.conclusion != 'cancelled' ||
steps.run_playwright_api_tests.conclusion != 'skipped'
)
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright API test report
if: |
always() &&
steps.generate_api_report.conclusion == 'success'
uses: actions/upload-artifact@v3
with:
name: api-test-report---pr-${{ github.run_number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
test-summary:
name: Post test results
if: |
always() &&
! github.event.pull_request.head.repo.fork &&
(
contains( needs.*.result, 'success' ) ||
contains( needs.*.result, 'failure' )
)
runs-on: ubuntu-20.04
permissions:
contents: read
needs:
- non-hpos-api-tests-run
- non-hpos-e2e-tests-run
steps:
- name: Create dirs
run: |
mkdir -p repo
mkdir -p artifacts/api
mkdir -p artifacts/e2e
mkdir -p output
- name: Checkout code
uses: actions/checkout@v4
with:
path: repo
- name: Download API test report artifact
uses: actions/download-artifact@v3
with:
name: api-test-report---pr-${{ github.run_number }}
path: artifacts/api
- name: Download Playwright E2E test report artifact
uses: actions/download-artifact@v3
with:
name: e2e-test-report---pr-${{ github.run_number }}
path: artifacts/e2e
- name: Prepare test summary
id: prepare-test-summary
uses: actions/github-script@v6
env:
API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
with:
result-encoding: string
script: |
const script = require( './repo/.github/workflows/scripts/prepare-test-summary-daily.js' )
return await script( { core } )

View File

@ -1,330 +0,0 @@
name: Run tests against PR
on:
workflow_dispatch:
pull_request:
paths-ignore:
- '**/changelog/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
e2e-tests-run:
name: Runs E2E tests in matrix.
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard:
[
{ number: 1, name: 1/5 },
{ number: 2, name: 2/5 },
{ number: 3, name: 3/5 },
{ number: 4, name: 4/5 },
{ number: 5, name: 5/5 },
]
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
outputs:
E2E_GRAND_TOTAL: ${{ steps.count_e2e_total.outputs.E2E_GRAND_TOTAL }}
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
WP_ENV_PHP_VERSION: 7.4
run: pnpm env:test
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Get total number of Playwright E2E tests to be run.
id: count_e2e_total
working-directory: plugins/woocommerce
run: |
TOTAL_STR=$(pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js --list | grep "Total:")
NO_PREFIX=${TOTAL_STR#*"Total: "}
COUNT=${NO_PREFIX%" tests in"*}
echo "E2E_GRAND_TOTAL=$COUNT" >> $GITHUB_OUTPUT
- name: Run Playwright tests
working-directory: plugins/woocommerce
env:
USE_WP_ENV: 1
E2E_MAX_FAILURES: 15
FORCE_COLOR: 1
id: run_playwright_e2e_tests
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js --shard ${{ matrix.shard.name }}
- name: Upload reports to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: all-blob-reports-${{ matrix.shard.number }}
path: ${{ env.ALLURE_RESULTS_DIR }}
retention-days: 1
compression-level: 9
e2e-tests-success:
name: Evaluate e2e tests results
runs-on: ubuntu-latest
needs: e2e-tests-run
if: ${{ always() }}
steps:
- run: |
result="${{ needs.e2e-tests-run.result }}"
if [[ $result != "success" && $result != "skipped" ]]; then
echo "One or more e2e tests have failed!"
exit 1
fi
echo "e2e tests have completed successfully."
merge-reports:
name: Merge e2e test reports
# Merge reports after playwright-tests, even if some shards have failed
if: always()
needs: [e2e-tests-success]
runs-on: ubuntu-latest
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: ${{ env.ALLURE_RESULTS_DIR }}
pattern: all-blob-reports-*
run-id: e2e-tests-run
merge-multiple: true
- name: Generate Playwright E2E Test report.
id: generate_e2e_report
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright E2E test report
if: |
always() &&
steps.generate_e2e_report.conclusion == 'success'
uses: actions/upload-artifact@v4
with:
name: e2e-test-report---pr-${{ github.event.number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
api-tests-run:
name: Runs API tests.
runs-on: ubuntu-latest
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 0
run: pnpm --filter=@woocommerce/plugin-woocommerce env:test
- name: Run Playwright API tests.
id: run_playwright_api_tests
working-directory: plugins/woocommerce
env:
BASE_URL: http://localhost:8086
USER_KEY: admin
USER_SECRET: password
run: pnpm exec playwright test --config=tests/api-core-tests/playwright.config.js
- name: Generate Playwright API Test report.
id: generate_api_report
if: |
always() &&
(
steps.run_playwright_api_tests.conclusion != 'cancelled' ||
steps.run_playwright_api_tests.conclusion != 'skipped'
)
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright API test report
if: |
always() &&
steps.generate_api_report.conclusion == 'success'
uses: actions/upload-artifact@v4
with:
name: api-test-report---pr-${{ github.event.number }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
k6-tests-run:
name: Runs k6 Performance tests
if: github.event.pull_request.user.login != 'github-actions[bot]'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 0
run: |
pnpm --filter=@woocommerce/plugin-woocommerce env:dev
pnpm --filter=@woocommerce/plugin-woocommerce env:performance-init
- name: Install k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
- name: Run k6 tests
run: |
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-pr-requests.js
test-summary:
name: Post test results
if: |
always() &&
! github.event.pull_request.head.repo.fork &&
github.event.pull_request.user.login != 'github-actions[bot]' &&
(
contains( needs.*.result, 'success' ) ||
contains( needs.*.result, 'failure' )
)
runs-on: ubuntu-latest
needs: [api-tests-run, merge-reports]
permissions:
contents: read
issues: write
pull-requests: write
env:
E2E_GRAND_TOTAL: ${{needs.e2e-tests-run.outputs.E2E_GRAND_TOTAL}}
steps:
- name: Create dirs
run: |
mkdir -p repo
mkdir -p artifacts/api
mkdir -p artifacts/e2e
mkdir -p output
- name: Checkout code
uses: actions/checkout@v4
with:
path: repo
- name: Download API test report artifact
uses: actions/download-artifact@v4
with:
name: api-test-report---pr-${{ github.event.number }}
path: artifacts/api
- name: Download Playwright E2E test report artifact
uses: actions/download-artifact@v4
with:
name: e2e-test-report---pr-${{ github.event.number }}
path: artifacts/e2e
- name: Prepare test summary
id: prepare-test-summary
uses: actions/github-script@v7
env:
API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
PR_NUMBER: ${{ github.event.number }}
SHA: ${{ github.event.pull_request.head.sha }}
with:
result-encoding: string
script: |
const script = require( './repo/.github/workflows/scripts/prepare-test-summary.js' )
return await script( { core } )
- name: Find PR comment by github-actions[bot]
uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Test Results Summary
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: ${{ steps.prepare-test-summary.outputs.result }}
edit-mode: replace
publish-test-reports:
name: Publish test reports
if: |
always() &&
! github.event.pull_request.head.repo.fork &&
github.event.pull_request.user.login != 'github-actions[bot]' &&
(
contains( needs.*.result, 'success' ) ||
contains( needs.*.result, 'failure' )
)
runs-on: ubuntu-latest
needs: [api-tests-run, merge-reports, k6-tests-run]
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
PR_NUMBER: ${{ github.event.number }}
RUN_ID: ${{ github.run_id }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
steps:
- name: Publish test reports
env:
API_ARTIFACT: api-test-report---pr-${{ github.event.number }}
E2E_ARTIFACT: e2e-test-report---pr-${{ github.event.number }}
run: |
gh workflow run publish-test-reports-pr.yml \
-f run_id=$RUN_ID \
-f api_artifact=$API_ARTIFACT \
-f e2e_artifact=$E2E_ARTIFACT \
-f pr_number=$PR_NUMBER \
-f commit_sha=$COMMIT_SHA \
-f s3_root=public \
--repo woocommerce/woocommerce-test-reports

View File

@ -1,26 +0,0 @@
name: Check daily smoke test site status.
on:
schedule:
- cron: '25 7 * * *'
permissions: {}
jobs:
ping_site:
runs-on: ubuntu-20.04
name: Check site and notify if not found
steps:
- name: Check site status
id: sitecheck
uses: srt32/uptime@958231f4d95c117f08eb0fc70907e80d0dfedf2b
with:
url-to-hit: "${{ secrets.SMOKE_TEST_URL }}ready/"
expected-statuses: "200,301"
- name: Send message to Slack API
if: failure()
uses: archive/github-actions-slack@deecc2edc496dc642d643de1d7cf3a47f51fb27a
id: notify
with:
slack-bot-user-oauth-access-token: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
slack-channel: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
slack-text: ':warning: <!subteam^${{ secrets.SMOKE_TEST_SLACK_GROUP }}> FYI the URL ${{ secrets.SMOKE_TEST_URL }}ready/ appears to be returning `404 not found` :x:'

View File

@ -1,179 +0,0 @@
name: Run tests against trunk after PR merge
on:
workflow_dispatch:
pull_request:
types:
- closed
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
api:
name: Run API tests
runs-on: ubuntu-latest
if: (github.event.pull_request.merged == true) && (github.event.pull_request.base.ref == 'trunk')
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
ARTIFACT_NAME: api-pr-merge-${{ github.event.pull_request.number }}-run-${{ github.run_number }}
steps:
- name: Checkout merge commit on trunk
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Setup local test environment
uses: ./.github/actions/tests/setup-local-test-environment
with:
test-type: api
- name: Run API tests
id: run-api-composite-action
uses: ./.github/actions/tests/run-api-tests
with:
report-name: ${{ env.ARTIFACT_NAME }}
- name: Upload Allure files to bucket
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.ARTIFACT_NAME }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish Allure report
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
run: |
gh workflow run publish-test-reports-trunk-merge.yml \
-f run_id=${{ github.run_id }} \
-f artifact=${{ env.ARTIFACT_NAME }} \
-f pr_number=${{ github.event.pull_request.number }} \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Send Slack alert on test failure
if: failure() && steps.run-api-composite-action.conclusion == 'failure'
uses: ./.github/actions/tests/slack-alert-on-pr-merge
with:
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
test-type: API
e2e:
name: Run E2E tests
needs: [api]
runs-on: ubuntu-latest
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ARTIFACT_NAME: e2e-pr-merge-${{ github.event.pull_request.number }}-run-${{ github.run_number }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Setup local test environment
uses: ./.github/actions/tests/setup-local-test-environment
with:
test-type: e2e
- name: Run E2E tests
id: run-e2e-composite-action
timeout-minutes: 60
uses: ./.github/actions/tests/run-e2e-tests
env:
E2E_MAX_FAILURES: 15
with:
report-name: ${{ env.ARTIFACT_NAME }}
- name: Upload Allure files to bucket
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.ARTIFACT_NAME }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
include-allure-results: false
- name: Publish Allure report
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
run: |
gh workflow run publish-test-reports-trunk-merge.yml \
-f run_id=${{ github.run_id }} \
-f artifact=${{ env.ARTIFACT_NAME }} \
-f pr_number=${{ github.event.pull_request.number }} \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Send Slack alert on test failure
if: failure() && steps.run-e2e-composite-action.conclusion == 'failure'
uses: ./.github/actions/tests/slack-alert-on-pr-merge
with:
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
test-type: E2E
k6:
name: Runs k6 Performance tests
if: (github.event.pull_request.user.login != 'github-actions[bot]') && (github.event.pull_request.merged == true) && (github.event.pull_request.base.ref == 'trunk')
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 0
run: |
pnpm --filter=@woocommerce/plugin-woocommerce env:dev
pnpm --filter=@woocommerce/plugin-woocommerce env:performance-init
- name: Install k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
- name: Run k6 tests
run: |
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-pr-requests.js
- name: Send Slack alert on test failure
if: failure() && steps.run-k6-composite-action.conclusion == 'failure'
uses: ./.github/actions/tests/slack-alert-on-pr-merge
with:
slack-bot-token: ${{ secrets.E2E_SLACK_TOKEN }}
channel-id: ${{ secrets.E2E_TRUNK_SLACK_CHANNEL }}
test-type: k6

View File

@ -18,6 +18,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip
working-directory: plugins/woocommerce

View File

@ -24,6 +24,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip
working-directory: plugins/woocommerce

View File

@ -1,30 +0,0 @@
name: WP Nightly Tests
on:
schedule:
- cron: '17 4 * * *' # Run at 4:17 AM UTC.
workflow_dispatch:
permissions: {}
jobs:
nightly:
name: Run Tests Against Nightly
runs-on: ubuntu-20.04
steps:
- uses: 'actions/checkout@v3'
name: 'Checkout'
with:
fetch-depth: 0
- uses: './.github/actions/setup-woocommerce-monorepo'
name: 'Setup Monorepo'
id: 'setup-monorepo'
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: 'Prepare Test Environment'
id: 'prepare-test-environment'
env:
WP_ENV_CORE: 'https://wordpress.org/nightly-builds/wordpress-latest.zip'
run: 'pnpm --filter="@woocommerce/plugin-woocommerce" env:test'
- name: 'Test'
run: 'pnpm --filter="@woocommerce/plugin-woocommerce" test:php:env'

View File

@ -27,6 +27,7 @@ jobs:
with:
install: true
build: './tools/package-release'
pull-package-deps: 'tools/package-release'
- name: Clean working directory
run: git checkout pnpm-lock.yaml # in case for whatever reason the lockfile is out of sync, there won't be interference with npm publish.

View File

@ -0,0 +1,57 @@
name: Compressed Size
on:
pull_request:
paths:
- '**.jsx?'
- '**.tsx?'
- '**.css'
- '**.scss'
- '**package*.json'
- '**.eslint*'
- '**.prettier*'
- '**.tsconfig*'
- '**/webpack.config.js'
- '!.github/**'
- '!packages/js/*e2e*/**'
- '!packages/js/*plugin*/**'
- '!packages/js/*internal*/**'
- '!packages/js/*create*/**'
- '!**/*.spec.*'
- '!**/tests/**'
- '!tools/**'
- '!changelog/**'
- '!docs/**'
- '!bin/**'
- '.github/workflows/pr-assess-bundle-size.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
cancel-in-progress: true
jobs:
build:
name: Check Asset Sizes
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
php-version: false
pull-package-deps: '@woocommerce/plugin-woocommerce'
- uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c
env:
BROWSERSLIST_IGNORE_OLD_DATA: true
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks}/{build,build-style}/**/*.{js,css}'
install-script: 'pnpm install --filter="@woocommerce/plugin-woocommerce..." --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts'
build-script: '--filter="@woocommerce/plugin-woocommerce" build'
clean-script: '--if-present buildclean'
minimum-change-threshold: 100
omit-unchanged: true

View File

@ -8,6 +8,8 @@ on:
- '**/changelog/**'
- '**/tests/**'
- '**/*.md'
- '.github/**'
- '!.github/workflows/pr-build-live-branch.yml'
concurrency:
# Cancel concurrent jobs on pull_request but not push, by including the run_id in the concurrency group for the latter.
@ -26,7 +28,7 @@ jobs:
repository-projects: write
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Get current version
id: version
@ -39,13 +41,13 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Prepare plugin zips
id: prepare
env:
CURRENT_VERSION: ${{ steps.version.outputs.version }}
# Build with experimental blocks.
WOOCOMMERCE_BLOCKS_PHASE: 3
run: |
# Current version must compare greater than any previously used current version for this PR.
@ -62,13 +64,13 @@ jobs:
mkdir "$GITHUB_WORKSPACE/zips"
mkdir -p "$GITHUB_WORKSPACE/unzips/woocommerce"
cp "$GITHUB_WORKSPACE/plugins/woocommerce/woocommerce.zip" "$GITHUB_WORKSPACE/zips/woocommerce.zip"
mv "$GITHUB_WORKSPACE/plugins/woocommerce/woocommerce.zip" "$GITHUB_WORKSPACE/zips/woocommerce.zip"
cd "$GITHUB_WORKSPACE/zips"
unzip woocommerce.zip
unzip -qq woocommerce.zip
cp -r woocommerce "$GITHUB_WORKSPACE/unzips/woocommerce/woocommerce"
rm woocommerce.zip
mv woocommerce woocommerce-dev
zip -q -r "woocommerce-dev.zip" "woocommerce-dev/"
zip -q -r -9 "woocommerce-dev.zip" "woocommerce-dev/"
rm -fR "$GITHUB_WORKSPACE/zips/woocommerce-dev"
# Plugin data is passed as a JSON object.
PLUGIN_DATA="{}"
@ -76,7 +78,7 @@ jobs:
echo "plugin-data=$PLUGIN_DATA" >> $GITHUB_OUTPUT
- name: Create plugins artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: steps.prepare.outputs.plugin-data != '{}'
with:
name: plugins
@ -92,8 +94,8 @@ jobs:
path: unzips/woocommerce
retention-days: 30
- name: Inform Beta Download webhook
if: steps.prepare.outputs.plugin-data != '{}'
- name: Inform Beta Download webhook if this is an internal PR
if: steps.prepare.outputs.plugin-data != '{}' && ! github.event.pull_request.head.repo.fork
env:
SECRET: ${{ secrets.WOOBETA_SECRET }}
PLUGIN_DATA: ${{ steps.prepare.outputs.plugin-data }}

View File

@ -3,6 +3,7 @@ on:
pull_request:
paths:
- 'plugins/woocommerce/**'
- '!plugins/woocommerce/templates/templates/**'
jobs:
analyze:
name: 'Analyze Branch Changes'
@ -17,6 +18,8 @@ jobs:
with:
install: 'code-analyzer...'
build: 'code-analyzer'
pull-package-deps: 'code-analyzer'
- name: 'Analyze'
id: 'analyze'
working-directory: 'tools/code-analyzer'
@ -24,7 +27,7 @@ jobs:
GIT_CLONE_PROTECTION_ACTIVE: false
run: |
HEAD_REF=$(git rev-parse HEAD)
exclude="plugins/woocommerce/tests plugins/woocommerce-admin/tests plugins/woocommerce-blocks/tests"
exclude="plugins/woocommerce/tests plugins/woocommerce/templates/templates plugins/woocommerce-admin/tests plugins/woocommerce-blocks/tests"
version=$(pnpm analyzer major-minor "$HEAD_REF" "plugins/woocommerce/woocommerce.php" | tail -n 1)
pnpm analyzer "$HEAD_REF" $version -o "github" -e $exclude
- uses: 'actions/github-script@v6'

View File

@ -21,6 +21,7 @@ jobs:
**/*.md
files_ignore: |
docs/**/*.md
.github/**/*.md
- name: Get docs changed files
id: docs-changed-files
@ -37,9 +38,7 @@ jobs:
docs/docs-manifest.json
- name: Setup PNPM
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: '8.6.7'
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
- name: Setup Node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c

View File

@ -1,105 +0,0 @@
name: Run smoke tests against pull request.
on:
pull_request:
paths-ignore:
- '**/changelog/**'
branches:
- trunk
types:
- labeled
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
prcheck:
name: Smoke test a pull request.
if: "${{ contains(github.event.label.name, 'run: smoke tests') }}"
runs-on: ubuntu-20.04
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Install Jest
run: pnpm install -g jest
- name: Run smoke test.
working-directory: plugins/woocommerce
if: always()
env:
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }}
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
WC_E2E_SCREENSHOTS: 1
E2E_RETEST: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
UPDATE_WC: 1
DEFAULT_TIMEOUT_OVERRIDE: 120000
run: |
pnpm exec wc-e2e test:e2e tests/e2e/specs/smoke-tests/update-woocommerce.js
- name: Post Smoke tests results comment on PR
if: always()
uses: actions/github-script@v5
env:
TITLE: 'Smoke Test Results'
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const script = require( './packages/js/e2e-environment/bin/post-results-to-github-pr.js' )
await script({github, context})
- name: Run E2E tests.
working-directory: plugins/woocommerce
if: always()
env:
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
SMOKE_TEST_ADMIN_USER: ${{ secrets.SMOKE_TEST_ADMIN_USER }}
SMOKE_TEST_ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_ADMIN_PASSWORD }}
SMOKE_TEST_ADMIN_USER_EMAIL: ${{ secrets.SMOKE_TEST_ADMIN_USER_EMAIL }}
SMOKE_TEST_CUSTOMER_USER: ${{ secrets.SMOKE_TEST_CUSTOMER_USER }}
SMOKE_TEST_CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_CUSTOMER_PASSWORD }}
WC_E2E_SCREENSHOTS: 1
E2E_RETEST: 1
E2E_SLACK_TOKEN: ${{ secrets.SMOKE_TEST_SLACK_TOKEN }}
E2E_SLACK_CHANNEL: ${{ secrets.SMOKE_TEST_SLACK_CHANNEL }}
UPDATE_WC: 1
DEFAULT_TIMEOUT_OVERRIDE: 120000
run: |
pnpm exec wc-e2e test:e2e
- name: Post E2E tests results comment on PR
if: always()
uses: actions/github-script@v5
env:
TITLE: 'E2E Test Results'
SMOKE_TEST_URL: ${{ secrets.SMOKE_TEST_URL }}
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const script = require( './packages/js/e2e-environment/bin/post-results-to-github-pr.js' )
await script({github, context})
- name: Remove label from pull request.
if: |
always()
&& contains( github.event.pull_request.labels.*.name, format('run{0} smoke tests', ':'))
uses: actions-ecosystem/action-remove-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: 'run: smoke tests'

View File

@ -26,6 +26,7 @@ jobs:
with:
install: true
build: './tools/package-release'
pull-package-deps: 'tools/package-release'
- name: Execute script
run: ./tools/package-release/bin/dev prepare ${{ github.event.inputs.packages }}

View File

@ -31,7 +31,6 @@ jobs:
issues: write
pull-requests: write
outputs:
pnpmVersion: ${{ steps.read-pnpm-version.outputs.version }}
isTodayAcceleratedFreeze: ${{ steps.get-versions.outputs.isTodayAcceleratedFreeze }}
isTodayMonthlyFreeze: ${{ steps.get-versions.outputs.isTodayMonthlyFreeze }}
acceleratedVersion: ${{ steps.get-versions.outputs.acceleratedVersion }}
@ -47,18 +46,8 @@ jobs:
with:
fetch-depth: 0
- name: Read PNPM Version
id: read-pnpm-version
shell: bash
run: |
version=$(./.github/actions/setup-woocommerce-monorepo/scripts/read-pnpm-version.sh package.json)
echo "version=$version" >> $GITHUB_OUTPUT
echo "PNPM Version: $version"
- name: Setup PNPM
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: ${{ steps.read-pnpm-version.outputs.version }}
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
- name: Setup Node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
@ -149,9 +138,7 @@ jobs:
fetch-depth: 0
- name: Setup PNPM
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: ${{ needs.code-freeze-prep.outputs.pnpmVersion }}
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
- name: Setup Node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
@ -191,6 +178,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip
working-directory: plugins/woocommerce
@ -219,6 +208,8 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
pull-package-deps: '@woocommerce/plugin-woocommerce'
- name: Build zip
working-directory: plugins/woocommerce
@ -271,9 +262,7 @@ jobs:
fetch-depth: 0
- name: Setup PNPM
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: ${{ needs.code-freeze-prep.outputs.pnpmVersion }}
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
- name: Setup Node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c
@ -342,9 +331,7 @@ jobs:
fetch-depth: 0
- name: Setup PNPM
uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
with:
version: ${{ needs.code-freeze-prep.outputs.pnpmVersion }}
uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d
- name: Setup Node
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c

View File

@ -20,11 +20,9 @@ jobs:
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce-beta-tester'
- name: Lint
working-directory: plugins/woocommerce-beta-tester
run: composer run phpcs
install: '@woocommerce/plugin-woocommerce-beta-tester...'
build: '@woocommerce/plugin-woocommerce-beta-tester...'
pull-package-deps: '@woocommerce/plugin-woocommerce-beta-tester'
- name: Build WooCommerce Beta Tester Zip
working-directory: plugins/woocommerce-beta-tester

View File

@ -1,10 +1,14 @@
name: Remind reviewers to also review the testing instructions.
name: Remind reviewers to also review the testing instructions and test coverage
on:
pull_request_target:
types: [review_requested]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
add-testing-instructions-review-comment:
runs-on: ubuntu-20.04
@ -51,7 +55,7 @@ jobs:
comment-author: 'github-actions[bot]'
body-includes: please make sure to review the testing instructions
- name: Create or update PR comment asking for reviewers to review the testing instructions
- name: Create or update PR comment asking for reviewers to review the testing instructions and test coverage
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
@ -59,7 +63,7 @@ jobs:
body: |
Hi ${{ env.REVIEWERS }}, ${{ env.TEAMS }}
Apart from reviewing the code changes, please make sure to review the testing instructions as well.
Apart from reviewing the code changes, please make sure to review the testing instructions and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed.
You can follow this guide to find out what good testing instructions should look like:
https://github.com/woocommerce/woocommerce/wiki/Writing-high-quality-testing-instructions

View File

@ -1,19 +0,0 @@
module.exports = async ( { github, context, core } ) => {
const { ASSET_ID: asset_id } = process.env;
const { owner, repo } = context.repo;
const fs = require( 'fs' );
const path = require( 'path' );
const response = await github.rest.repos.getReleaseAsset( {
owner,
repo,
asset_id,
headers: { accept: 'application/octet-stream' },
} );
const zipPath = path.resolve( 'tmp', 'woocommerce.zip' );
fs.mkdirSync( 'tmp' );
fs.writeFileSync( zipPath, Buffer.from( response.data ) );
core.setOutput( 'zip-path', zipPath );
};

View File

@ -78,5 +78,25 @@
"status": "completed",
"conclusion": "unknown_conclusion",
"name": "Job with unknown conclusion (optional)"
},
{
"status": "in_progress",
"conclusion": "",
"name": "Publish reports in_progress"
},
{
"status": "completed",
"conclusion": "",
"name": "Publish reports - failed"
},
{
"status": "completed",
"conclusion": "",
"name": "Another Publish reports that failed"
},
{
"status": "queued",
"conclusion": "",
"name": "Publish reports job queued"
}
]

View File

@ -1,36 +1,54 @@
/* eslint-disable no-console */
const { REPOSITORY, RUN_ID, GITHUB_TOKEN, TEST_MODE } = process.env;
const IGNORED_JOBS = [
'Evaluate Project Job Statuses',
'Report e2e tests results',
'Report API tests results',
/Evaluate Project Job Statuses/,
/Report results on Slack/,
/Test reports/,
/Create issues for flaky tests/,
];
const isJobRequired = ( job ) => {
return (
! job.name.endsWith( '(optional)' ) &&
! IGNORED_JOBS.includes( job.name )
! IGNORED_JOBS.some( ( ignoredJobRegex ) =>
ignoredJobRegex.test( job.name )
)
);
};
const fetchJobs = async () => {
let url = `https://api.github.com/repos/${ REPOSITORY }/actions/runs/${ RUN_ID }/jobs`;
const nextPattern = /(?<=<)([\S]*)(?=>; rel="Next")/i;
let pagesRemaining = true;
const jobs = [];
while ( pagesRemaining ) {
console.log( 'Fetching:', url );
try {
const response = await fetch(
`https://api.github.com/repos/${ REPOSITORY }/actions/runs/${ RUN_ID }/jobs`,
{
const response = await fetch( url, {
headers: {
'User-Agent': 'node.js',
Authorization: `Bearer ${ GITHUB_TOKEN }`,
},
}
);
} );
const data = await response.json();
return data.jobs;
jobs.push( ...data.jobs );
const linkHeader = response.headers.get( 'link' );
pagesRemaining =
linkHeader && linkHeader.includes( `rel=\"next\"` );
if ( pagesRemaining ) {
url = linkHeader.match( nextPattern )[ 0 ];
}
} catch ( error ) {
console.error( 'Error:', error );
// We want to fail if there is an error getting the jobs conclusions
process.exit( 1 );
}
}
return jobs;
};
const evaluateJobs = async () => {

View File

@ -1,54 +0,0 @@
/**
* A script that fetches the asset id of a given release and sets it as the output for the step that calls it
*/
const https = require('https');
const options = {
hostname: 'api.github.com',
port: 443,
path: `/repos/${process.env.REPO}/releases/${process.env.RELEASE_ID}/assets`,
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${ process.env.GITHUB_TOKEN }`,
'User-Agent': 'WooCommerce Smoke Build'
},
};
/**
*
* @returns {Promise}
*/
const fetchAssetId = () => {
return new Promise( ( resolve, reject ) => {
const request = https.get( options, ( response ) => {
response.setEncoding('utf8');
let responseBody = '';
response.on( 'data', ( chunk ) => {
responseBody += chunk;
} );
response.on( 'end', () => {
const assets = JSON.parse( responseBody );
// use the most recently uploaded asset
resolve( assets[ assets.length - 1 ].id );
} );
} );
request.on('error', ( error ) => {
reject( error );
} );
request.end();
} );
}
module.exports = async ( { github, context, core } ) => {
const id = await fetchAssetId();
// set asset_id as the output
core.setOutput( 'asset_id', id );
}

View File

@ -24,6 +24,16 @@ const generateWordpressPlaygroundBlueprint = ( runId, prNumber ) => {
activate: true,
},
},
{
step: 'installPlugin',
pluginZipFile: {
resource: 'url',
url: `https://github-proxy.com/https://github.com/woocommerce/woocommerce/releases/download/wc-beta-tester-2.3.1/woocommerce-beta-tester.zip`,
},
options: {
activate: true,
},
},
{
step: 'login',
username: 'admin',

View File

@ -0,0 +1,69 @@
/* eslint-disable no-console */
const fs = require( 'fs' );
const { RELEASE_TAG, ARTIFACT_NAME, WP_ENV_CONFIG_PATH } = process.env;
if ( ! RELEASE_TAG ) {
console.error( 'Please set the RELEASE_TAG environment variable!' );
process.exit( 1 );
}
if ( ! ARTIFACT_NAME ) {
console.error( 'Please set the ARTIFACT_NAME environment variable!' );
process.exit( 1 );
}
if ( ! WP_ENV_CONFIG_PATH ) {
console.error( 'Please set the WP_ENV_CONFIG_PATH environment variable!' );
process.exit( 1 );
}
const artifactUrl = `https://github.com/woocommerce/woocommerce/releases/download/${ RELEASE_TAG }/${ ARTIFACT_NAME }`;
const configPath = `${ WP_ENV_CONFIG_PATH }/.wp-env.json`;
console.log( `Reading ${ configPath }` );
const data = fs.readFileSync( configPath, 'utf8' );
const wpEnvConfig = JSON.parse( data );
const overrideConfig = {};
if ( wpEnvConfig.plugins ) {
overrideConfig.plugins = wpEnvConfig.plugins;
}
if ( wpEnvConfig.env?.tests?.plugins ) {
overrideConfig.env = {
tests: {
plugins: wpEnvConfig.env.tests.plugins,
},
};
}
const entriesToReplace = [ '.', '../woocommerce' ];
for ( const entry of entriesToReplace ) {
// Search and replace in root plugins
let found = overrideConfig.plugins.indexOf( entry );
if ( found >= 0 ) {
console.log(
`Replacing ${ entry } with ${ artifactUrl } in root plugins`
);
overrideConfig.plugins[ found ] = artifactUrl;
}
// Search and replace in test env plugins
found = overrideConfig.env?.tests?.plugins?.indexOf( entry );
if ( found >= 0 ) {
console.log(
`Replacing ${ entry } with ${ artifactUrl } in env.tests.plugins`
);
overrideConfig.env.tests.plugins[ found ] = artifactUrl;
}
}
const overrideConfigPath = `${ WP_ENV_CONFIG_PATH }/.wp-env.override.json`;
console.log( `Saving ${ overrideConfigPath }` );
fs.writeFileSync(
overrideConfigPath,
JSON.stringify( overrideConfig, null, 2 )
);

View File

@ -1,154 +0,0 @@
/**
* Script to generate the test results summary.
*/
const { API_SUMMARY_PATH, E2E_PW_SUMMARY_PATH } = process.env;
/**
* Convert the given `duration` from milliseconds to a more user-friendly string.
* For example, if `duration = 323000`, this function would return `5m 23s`.
*
* @param {Number} duration Duration in millisecods, as read from either the `summary.json` file in the Allure report, or from the `test-results.json` file from the Jest-Puppeteer report.
* @returns String in "5m 23s" format.
*/
const getFormattedDuration = ( duration ) => {
const durationMinutes = Math.floor( duration / 1000 / 60 );
const durationSeconds = Math.floor( ( duration / 1000 ) % 60 );
return `${ durationMinutes }m ${ durationSeconds }s`;
};
/**
* Extract the test report statistics (the number of tests that passed, failed, skipped, etc.) from Allure report's `summary.json` file.
*
* @param {string} summaryJSONPath Path to the Allure report's `summary.json` file.
* @returns An object containing relevant statistics from the Allure report.
*/
const getAllureSummaryStats = ( summaryJSONPath ) => {
const summary = require( summaryJSONPath );
const { statistic, time } = summary;
const { passed, failed, skipped, broken, unknown, total } = statistic;
const { duration } = time;
return {
passed,
failed,
skipped,
broken,
unknown,
total,
duration,
};
};
/**
* Construct the array to be used for the API table row.
*
* @returns Array of API test result stats.
*/
const createAPITableRow = () => {
const { passed, failed, skipped, broken, unknown, total, duration } =
getAllureSummaryStats( API_SUMMARY_PATH );
const durationFormatted = getFormattedDuration( duration );
return [
'API Tests',
passed.toString(),
failed.toString(),
broken.toString(),
skipped.toString(),
unknown.toString(),
total.toString(),
durationFormatted,
];
};
/**
* Construct the array to be used for the E2E table row.
*
* @returns Array of E2E test result stats.
*/
const createE2ETableRow = () => {
const { passed, failed, skipped, broken, unknown, total, duration } =
getAllureSummaryStats( E2E_PW_SUMMARY_PATH );
const durationFormatted = getFormattedDuration( duration );
return [
'E2E Tests',
passed.toString(),
failed.toString(),
broken.toString(),
skipped.toString(),
unknown.toString(),
total.toString(),
durationFormatted,
];
};
/**
* Create the heading and test results table.
*
* @param core The GitHub Actions toolkit core object
*/
const addSummaryHeadingAndTable = ( core ) => {
const apiTableRow = createAPITableRow();
const e2eTableRow = createE2ETableRow();
core.summary.addHeading( 'Smoke tests on nightly build' ).addTable( [
[
{ data: 'Test :test_tube:', header: true },
{ data: 'Passed :white_check_mark:', header: true },
{ data: 'Failed :rotating_light:', header: true },
{ data: 'Broken :construction:', header: true },
{ data: 'Skipped :next_track_button:', header: true },
{ data: 'Unknown :grey_question:', header: true },
{ data: 'Total :bar_chart:', header: true },
{ data: 'Duration :stopwatch:', header: true },
],
apiTableRow,
e2eTableRow,
] );
};
/**
* Add the summary footer.
*
* @param core The GitHub Actions toolkit core object
*/
const addSummaryFooter = ( core ) => {
core.summary
.addSeparator()
.addRaw( 'To view the full API test report, click ' )
.addLink(
'here.',
'https://woocommerce.github.io/woocommerce-test-reports/daily/api'
)
.addBreak()
.addRaw( 'To view the full E2E test report, click ' )
.addLink(
'here.',
'https://woocommerce.github.io/woocommerce-test-reports/daily/e2e'
)
.addBreak()
.addRaw( 'To view all test reports, visit the ' )
.addLink(
'WooCommerce Test Reports Dashboard.',
'https://woocommerce.github.io/woocommerce-test-reports/'
);
};
/**
* Generate the contents of the test results summary and post it on the workflow run.
*
* @param {*} params Objects passed from the calling GitHub Action workflow.
* @returns Stringified content of the test results summary.
*/
module.exports = async ( { core } ) => {
addSummaryHeadingAndTable( core );
addSummaryFooter( core );
const summary = core.summary.stringify();
await core.summary.write();
return summary;
};

View File

@ -1,219 +0,0 @@
/**
* Script to generate the test results summary to be posted as a GitHub Job Summary and as a PR comment.
*/
const {
API_SUMMARY_PATH,
E2E_PW_SUMMARY_PATH,
SHA,
PR_NUMBER,
E2E_GRAND_TOTAL,
} = process.env;
/**
* Convert the given `duration` from milliseconds to a more user-friendly string.
* For example, if `duration = 323000`, this function would return `5m 23s`.
*
* @param {Number} duration Duration in millisecods, as read from either the `summary.json` file in the Allure report, or from the `test-results.json` file from the Jest-Puppeteer report.
* @returns String in "5m 23s" format.
*/
const getFormattedDuration = ( duration ) => {
const durationMinutes = Math.floor( duration / 1000 / 60 );
const durationSeconds = Math.floor( ( duration / 1000 ) % 60 );
return `${ durationMinutes }m ${ durationSeconds }s`;
};
/**
* Extract the test report statistics (the number of tests that passed, failed, skipped, etc.) from Allure report's `summary.json` file.
*
* @param {string} summaryJSONPath Path to the Allure report's `summary.json` file.
* @returns An object containing relevant statistics from the Allure report.
*/
const getAllureSummaryStats = ( summaryJSONPath ) => {
const summary = require( summaryJSONPath );
const { statistic, time } = summary;
const { passed, failed, skipped, broken, unknown, total } = statistic;
const { duration } = time;
return {
passed,
failed,
skipped,
broken,
unknown,
total,
duration,
};
};
/**
* Construct the array to be used for the API table row.
*
* @returns Array of API test result stats.
*/
const createAPITableRow = () => {
const { passed, failed, skipped, broken, unknown, total, duration } =
getAllureSummaryStats( API_SUMMARY_PATH );
const durationFormatted = getFormattedDuration( duration );
return [
'API Tests',
passed.toString(),
failed.toString(),
broken.toString(),
skipped.toString(),
unknown.toString(),
total.toString(),
durationFormatted,
];
};
/**
* Construct the array to be used for the E2E table row.
*
* @returns Array of E2E test result stats.
*/
const createE2ETableRow = () => {
const { passed, failed, skipped, broken, unknown, total, duration } =
getAllureSummaryStats( E2E_PW_SUMMARY_PATH );
const durationFormatted = getFormattedDuration( duration );
return [
'E2E Tests',
passed.toString(),
failed.toString(),
broken.toString(),
skipped.toString(),
unknown.toString(),
total.toString(),
durationFormatted,
];
};
/**
* Add a warning when the number of executed Playwright E2E tests were fewer than the total.
*/
const addWarningE2EIncomplete = ( warnings ) => {
const { statistic } = require( E2E_PW_SUMMARY_PATH );
const { total } = statistic;
const expectedTotal = Number( E2E_GRAND_TOTAL );
if ( total < expectedTotal ) {
warnings.push(
`INCOMPLETE E2E TEST RUN. We have a total of ${ expectedTotal } E2E tests, but only ${ total } were executed. Note that in CI, E2E tests automatically end when they encounter too many failures.`
);
}
};
/**
*
* Add a warning when there are failures and broken tests.
*/
const addWarningFailuresBrokenTests = ( warnings ) => {
const { failed: apiFailed, broken: apiBroken } =
getAllureSummaryStats( API_SUMMARY_PATH );
const { failed: e2eFailed, broken: e2eBroken } =
getAllureSummaryStats( E2E_PW_SUMMARY_PATH );
if ( apiFailed || apiBroken || e2eFailed || e2eBroken ) {
warnings.push(
'FAILED/BROKEN TESTS. There were failed and/or broken API and E2E tests.'
);
}
};
/**
* Add warnings to the test summary.
*
* @param core The GitHub Actions toolkit core object
*/
const addSummaryWarnings = ( core ) => {
const warnings = [];
addWarningFailuresBrokenTests( warnings );
addWarningE2EIncomplete( warnings );
if ( warnings.length > 0 ) {
core.summary
.addHeading( ':warning: Warning', 3 )
.addRaw(
'Please address the following issues prior to merging this pull request:'
)
.addList( warnings );
}
};
/**
* Create the heading, commit SHA, and test results table.
*
* @param core The GitHub Actions toolkit core object
*/
const addSummaryHeadingAndTable = ( core ) => {
const apiTableRow = createAPITableRow();
const e2eTableRow = createE2ETableRow();
core.summary
.addHeading( 'Test Results Summary' )
.addRaw( `Commit SHA: ${ SHA }` )
.addBreak()
.addBreak()
.addTable( [
[
{ data: 'Test :test_tube:', header: true },
{ data: 'Passed :white_check_mark:', header: true },
{ data: 'Failed :rotating_light:', header: true },
{ data: 'Broken :construction:', header: true },
{ data: 'Skipped :next_track_button:', header: true },
{ data: 'Unknown :grey_question:', header: true },
{ data: 'Total :bar_chart:', header: true },
{ data: 'Duration :stopwatch:', header: true },
],
apiTableRow,
e2eTableRow,
] );
};
/**
* Add the summary footer.
*
* @param core The GitHub Actions toolkit core object
*/
const addSummaryFooter = ( core ) => {
core.summary
.addSeparator()
.addRaw( 'To view the full API test report, click ' )
.addLink(
'here.',
`https://woocommerce.github.io/woocommerce-test-reports/pr/${ PR_NUMBER }/api/`
)
.addBreak()
.addRaw( 'To view the full E2E test report, click ' )
.addLink(
'here.',
`https://woocommerce.github.io/woocommerce-test-reports/pr/${ PR_NUMBER }/e2e/`
)
.addBreak()
.addRaw( 'To view all test reports, visit the ' )
.addLink(
'WooCommerce Test Reports Dashboard.',
'https://woocommerce.github.io/woocommerce-test-reports/'
);
};
/**
* Generate the contents of the test results summary and post it on the workflow run.
*
* @param {*} params Objects passed from the calling GitHub Action workflow.
* @returns Stringified content of the test results summary.
*/
module.exports = async ( { core } ) => {
addSummaryHeadingAndTable( core );
addSummaryWarnings( core );
addSummaryFooter( core );
const summary = core.summary.stringify();
await core.summary.write();
return summary;
};

View File

@ -7,25 +7,34 @@ if [[ -z "$GITHUB_EVENT_NAME" ]]; then
exit 1
fi
echo "Installing dependencies"
pnpm install --filter="compare-perf"
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
echo "Comparing performance with trunk"
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA
elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
echo "Comparing performance with base branch"
# The base hash used here need to be a commit that is compatible with the current WP version
# The current one is 19f3d0884617d7ecdcf37664f648a51e2987cada
# it needs to be updated every time it becomes unsupported by the current wp-env (WP version).
# It is used as a base comparison point to avoid fluctuation in the performance metrics.
WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt)
# Updating the WP version used for performance jobs means theres a high
# chance that the reference commit used for performance test stability
# becomes incompatible with the WP version. So, every time the "Tested up
# to" flag is updated in the readme.txt, we also have to update the
# reference commit below (BASE_SHA). The new reference needs to meet the
# following requirements:
# - Be compatible with the new WP version used in the “Tested up to” flag.
# - Be tracked on https://www.codevitals.run/project/woo for all existing
# metrics.
BASE_SHA=3d7d7f02017383937f1a4158d433d0e5d44b3dc9
echo "WP_VERSION: $WP_VERSION"
IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION"
WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}"
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
echo "Publish results to CodeVitals"
COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI")
pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada $COMMITTED_AT
pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT
else
echo "Unsupported event: $GITHUB_EVENT_NAME"
fi

View File

@ -1,51 +0,0 @@
module.exports = async ( { github, context, core } ) => {
const { RELEASE_VERSION, GITHUB_EVENT_NAME } = process.env;
async function findRelease() {
const { owner, repo } = context.repo;
const list = await github.rest.repos.listReleases( {
owner,
repo,
per_page: 100,
} );
const match = list.data.find( ( { tag_name, name } ) =>
[ tag_name, name ].includes( RELEASE_VERSION )
);
return match;
}
async function handleWorkflowDispatch() {
const match = await findRelease();
if ( match ) {
return match;
}
throw new Error(
`"${ RELEASE_VERSION }" is not a valid release version!`
);
}
function findWooCommerceZipAsset() {
const match = release.assets.find(
( { name } ) => name === 'woocommerce.zip'
);
if ( ! match ) {
throw new Error(
`Release ${ RELEASE_VERSION } does not contain a woocommerce.zip asset!`
);
}
return match;
}
const release =
GITHUB_EVENT_NAME === 'release'
? await findRelease()
: await handleWorkflowDispatch();
const asset = findWooCommerceZipAsset();
core.setOutput( 'version', RELEASE_VERSION );
core.setOutput( 'created', release.created_at );
core.setOutput( 'asset-id', asset.id );
};

View File

@ -1,482 +0,0 @@
name: Smoke test daily
on:
schedule:
- cron: '25 3 * * *'
workflow_dispatch:
env:
API_ARTIFACT: api-daily--run-${{ github.run_number }}
E2E_ARTIFACT: e2e-daily--run-${{ github.run_number }}
FORCE_COLOR: 1
PLUGIN_SLACK_BLOCKS_ARTIFACT: plugin-blocks
concurrency:
group: '${{ github.workflow }}-${{ github.ref }}'
cancel-in-progress: true
permissions: {}
jobs:
api-tests:
name: API tests on nightly build
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
test-result: ${{ steps.run-api-composite-action.outputs.result }}
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
ENABLE_HPOS: 1
run: pnpm --filter=@woocommerce/plugin-woocommerce env:test
- name: Run API tests
id: run-api-composite-action
uses: ./.github/actions/tests/run-api-tests
with:
report-name: ${{ env.API_ARTIFACT }}
env:
BASE_URL: http://localhost:8086
USER_KEY: admin
USER_SECRET: password
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
- name: Generate Playwright API Test report.
id: generate_api_report
if: |
always() &&
(
steps.run-api-composite-action.conclusion != 'cancelled' ||
steps.run-api-composite-action.conclusion != 'skipped'
)
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright API test report
if: |
always() &&
steps.generate_api_report.conclusion == 'success'
uses: actions/upload-artifact@v3
with:
name: ${{ env.API_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 20
e2e-tests:
name: E2E tests on nightly build
runs-on: ubuntu-latest
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
shard:
[
{ number: 1, name: 1/5 },
{ number: 2, name: 2/5 },
{ number: 3, name: 3/5 },
{ number: 4, name: 4/5 },
{ number: 5, name: 5/5 },
]
permissions:
contents: read
outputs:
test-result: ${{ steps.run_playwright_e2e_tests.outputs.result }}
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Load docker images and start containers.
working-directory: plugins/woocommerce
env:
WP_ENV_PHP_VERSION: 7.4
ENABLE_HPOS: 1
run: pnpm env:test
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Set Buildkite message
id: set_buildkite_message
run: |
BUILDKITE_MESSAGE=`echo "Daily E2E run for $(date '+%Y-%m-%d')"`
echo "BUILDKITE_MESSAGE=$BUILDKITE_MESSAGE" >> "$GITHUB_OUTPUT"
shell: bash
- name: Run E2E tests
timeout-minutes: 90
id: run_playwright_e2e_tests
env:
USE_WP_ENV: 1
E2E_MAX_FAILURES: 90
FORCE_COLOR: 1
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
BUILDKITE_ANALYTICS_TOKEN: ${{ secrets.BUILDKITE_DAILY_E2E_TOKEN }}
BUILDKITE_ANALYTICS_MESSAGE: ${{ steps.set_buildkite_message.outputs.BUILDKITE_MESSAGE }}
working-directory: plugins/woocommerce
run: pnpm exec playwright test --config=tests/e2e-pw/playwright.config.js --shard ${{ matrix.shard.name }}
- name: Upload reports to GitHub Actions Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: all-blob-reports-${{ matrix.shard.number }}
path: ${{ env.ALLURE_RESULTS_DIR }}
retention-days: 1
compression-level: 9
merge-reports:
name: Merge e2e test reports
if: always()
needs: e2e-tests
runs-on: ubuntu-latest
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: ${{ env.ALLURE_RESULTS_DIR }}
pattern: all-blob-reports-*
run-id: e2e-tests-run
merge-multiple: true
- name: Generate Test report.
id: generate_e2e_report
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive Playwright E2E test report
if: |
always() &&
steps.generate_e2e_report.conclusion == 'success'
uses: actions/upload-artifact@v4
with:
name: ${{ env.E2E_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
k6-tests:
name: k6 tests on nightly build
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [api-tests]
if: success() || failure()
outputs:
test-result: ${{ steps.run-k6-tests.conclusion }}
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
- name: Update performance test site with E2E test
id: update-perf-site
continue-on-error: true
uses: ./.github/actions/tests/run-e2e-tests
with:
report-name: k6-daily-update-site--run-${{ github.run_number }}
tests: update-woocommerce.spec.js
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
BASE_URL: ${{ secrets.SMOKE_TEST_PERF_URL }}/
ADMIN_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
ADMIN_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
CUSTOMER_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
CUSTOMER_PASSWORD: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
UPDATE_WC: nightly
- name: Install k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.33.0/k6-v0.33.0-linux-amd64.tar.gz -L | tar xvz --strip-components 1
- name: Run k6 smoke tests
id: run-k6-tests
env:
URL: ${{ secrets.SMOKE_TEST_PERF_URL }}
HOST: ${{ secrets.SMOKE_TEST_PERF_HOST }}
A_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
A_PW: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
C_USER: ${{ secrets.SMOKE_TEST_PERF_ADMIN_USER }}
C_PW: ${{ secrets.SMOKE_TEST_PERF_ADMIN_PASSWORD }}
P_ID: 22733
run: |
./k6 run plugins/woocommerce/tests/performance/tests/gh-action-daily-ext-requests.js
test-plugins:
name: E2E tests with ${{ matrix.plugin }} plugin installed
runs-on: ubuntu-latest
permissions:
contents: read
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
PLUGIN_NAME: ${{ matrix.plugin }}
PLUGIN_SLUG: ${{ matrix.slug }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
strategy:
fail-fast: false
matrix:
include:
- plugin: 'WooPayments'
repo: 'automattic/woocommerce-payments'
slug: woocommerce-payments
- plugin: 'WooCommerce PayPal Payments'
repo: 'woocommerce/woocommerce-paypal-payments'
slug: woocommerce-paypal-payments
- plugin: 'WooCommerce Shipping & Tax'
repo: 'automattic/woocommerce-services'
slug: woocommerce-services
- plugin: 'Gutenberg'
repo: 'WordPress/gutenberg'
slug: gutenberg
- plugin: 'Gutenberg - Nightly'
repo: 'bph/gutenberg'
slug: gutenberg
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Setup local test environment
uses: ./.github/actions/tests/setup-local-test-environment
with:
test-type: e2e
- name: Setup plugin for the main e2e suite
working-directory: ./plugins/woocommerce
run: ./tests/e2e-pw/bin/install-plugin.sh
- name: Run 'Upload plugin' test
id: run-upload-plugin-test
if: ${{ failure() }} # only if the plugin setup failed to check if the plugin really cannot be installed
uses: ./.github/actions/tests/run-e2e-tests
with:
report-name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }})
tests: upload-plugin.spec.js
- name: Run the rest of E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
with:
playwright-config: ignore-plugin-tests.playwright.config.js
report-name: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }})
env:
E2E_MAX_FAILURES: 90
- name: Create context block and save as JSON file
if: success() || failure()
id: create-block-json
uses: actions/github-script@v6
with:
script: |
const script = require( './.github/actions/tests/slack-summary-daily/scripts/create-blocks-plugin-tests.js' )
script( { core } );
env:
UPLOAD_RESULT: ${{ steps.run-upload-plugin-test.outputs.result }}
E2E_RESULT: ${{ steps.run-e2e-composite-action.outputs.result }}
PLUGIN_NAME: ${{ matrix.plugin }}
PLUGIN_SLUG: ${{ matrix.slug }}
- name: Upload JSON file as artifact
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.PLUGIN_SLACK_BLOCKS_ARTIFACT }}
path: ${{ steps.create-block-json.outputs.path }}
trunk-results:
name: Publish report on smoke tests on nightly build
if: |
( success() || failure() ) &&
! github.event.pull_request.head.repo.fork
runs-on: ubuntu-20.04
permissions:
contents: read
needs: [e2e-tests, test-plugins, k6-tests]
steps:
- name: Create dirs
run: |
mkdir -p repo
mkdir -p artifacts/api
mkdir -p artifacts/e2e
mkdir -p output
- name: Checkout code
uses: actions/checkout@v3
with:
path: repo
- name: Download API test report artifact
uses: actions/download-artifact@v3
with:
name: ${{ env.API_ARTIFACT }}
path: artifacts/api
- name: Download E2E test report artifact
uses: actions/download-artifact@v4
with:
name: ${{ env.E2E_ARTIFACT }}
path: artifacts/e2e
- name: Post test summary
uses: actions/github-script@v6
env:
API_SUMMARY_PATH: ${{ github.workspace }}/artifacts/api/allure-report/widgets/summary.json
E2E_PW_SUMMARY_PATH: ${{ github.workspace }}/artifacts/e2e/allure-report/widgets/summary.json
with:
result-encoding: string
script: |
const script = require( './repo/.github/workflows/scripts/prepare-test-summary-daily.js' )
return await script( { core } )
- name: Publish report
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
RUN_ID: ${{ github.run_id }}
run: |
gh workflow run publish-test-reports-daily.yml \
-f run_id=$RUN_ID \
-f api_artifact="$API_ARTIFACT" \
-f e2e_artifact="$E2E_ARTIFACT" \
-f s3_root=public \
--repo woocommerce/woocommerce-test-reports
plugins-results:
name: Publish report on Smoke tests on trunk with plugins
if: |
( success() || failure() ) &&
( needs.test-plugins.result != 'skipped' ) &&
! github.event.pull_request.head.repo.fork
runs-on: ubuntu-20.04
needs: [e2e-tests, test-plugins, k6-tests]
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
RUN_ID: ${{ github.run_id }}
ARTIFACT: Smoke tests on trunk with ${{ matrix.plugin }} plugin installed (run ${{ github.run_number }})
strategy:
fail-fast: false
matrix:
include:
- plugin: 'WooPayments'
slug: woocommerce-payments
- plugin: 'WooCommerce PayPal Payments'
slug: woocommerce-paypal-payments
- plugin: 'WooCommerce Shipping & Tax'
slug: woocommerce-services
- plugin: 'Gutenberg'
slug: gutenberg
- plugin: 'Gutenberg - Nightly'
slug: gutenberg-nightly
steps:
- name: Download test report artifact
uses: actions/download-artifact@v3
with:
name: ${{ env.ARTIFACT }}
- name: Publish reports
run: |
gh workflow run publish-test-reports-daily-plugins.yml \
-f run_id=$RUN_ID \
-f artifact="${{ env.ARTIFACT }}" \
-f plugin="${{ matrix.plugin }}" \
-f slug="${{ matrix.slug }}" \
-f s3_root=public \
--repo woocommerce/woocommerce-test-reports
post-slack-summary:
name: Post Slack summary
runs-on: ubuntu-20.04
permissions:
contents: read
if: |
github.ref_name == 'trunk' &&
success() || (
failure() && contains( needs.*.result, 'failure' )
)
needs:
- api-tests
- e2e-tests
- k6-tests
- test-plugins
steps:
- uses: actions/checkout@v3
- name: Download Slack blocks from plugin tests
if: needs.test-plugins.result != 'skipped'
id: download-plugin-blocks
uses: actions/download-artifact@v3
with:
name: ${{ env.PLUGIN_SLACK_BLOCKS_ARTIFACT }}
path: /tmp/plugin-blocks
- name: Construct Slack payload
id: construct-slack-payload
uses: actions/github-script@v6
with:
script: |
const script = require('./.github/actions/tests/slack-summary-daily/scripts/construct-slack-payload.js');
await script( { context, core, github } );
env:
API_RESULT: ${{ needs.api-tests.outputs.test-result }}
E2E_RESULT: ${{ needs.e2e-tests.outputs.test-result || needs.e2e-tests.result }}
k6_RESULT: ${{ needs.k6-tests.outputs.test-result || needs.k6-tests.result }}
PLUGINS_BLOCKS_PATH: ${{ steps.download-plugin-blocks.outputs.download-path }}
PLUGIN_TESTS_RESULT: ${{ needs.test-plugins.result }}
- name: Send Slack message
id: send-slack-message
uses: slackapi/slack-github-action@v1.23.0
with:
channel-id: ${{ secrets.DAILY_TEST_SLACK_CHANNEL }}
payload: ${{ steps.construct-slack-payload.outputs.payload }}
env:
SLACK_BOT_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}

View File

@ -1,751 +0,0 @@
name: Smoke test release
on:
release:
types: [released, prereleased, published]
workflow_dispatch:
inputs:
tag:
description: 'WooCommerce release version'
required: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.release.tag_name || inputs.tag }}
cancel-in-progress: true
permissions: {}
env:
E2E_WP_LATEST_ARTIFACT: E2E test on release smoke test site with WP Latest (run ${{ github.run_number }})
E2E_UPDATE_WC_ARTIFACT: WooCommerce version update test on release smoke test site (run ${{ github.run_number }})
SLACK_BLOCKS_ARTIFACT: slack-blocks
jobs:
validate-version:
name: Validate release version
permissions:
contents: read
runs-on: ubuntu-20.04
outputs:
version: ${{ steps.validate-version.outputs.version }}
created: ${{ steps.validate-version.outputs.created }}
asset-id: ${{ steps.validate-version.outputs.asset-id }}
steps:
- uses: actions/checkout@v3
- name: Validate release version
id: validate-version
uses: actions/github-script@v6
env:
RELEASE_VERSION: ${{ inputs.tag }}
with:
github-token: ${{ secrets.E2E_GH_TOKEN }}
script: |
const script = require('./.github/workflows/scripts/validate-release-version.js');
await script({ github, context, core });
e2e-update-wc:
name: Test WooCommerce update
runs-on: ubuntu-20.04
needs: [validate-version]
permissions:
contents: read
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
- name: Run E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
with:
report-name: ${{ env.E2E_UPDATE_WC_ARTIFACT }}
tests: update-woocommerce.spec.js
env:
ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }}
CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
UPDATE_WC: ${{ needs.validate-version.outputs.version }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.E2E_WP_LATEST_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish E2E Allure report
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && steps.run-e2e-composite-action.outputs.result == 'failure'
)
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: WC Update test
e2e-result: ${{ steps.run-e2e-composite-action.outputs.result }}
env-slug: wp-latest
release-version: ${{ needs.validate-version.outputs.version }}
api-wp-latest:
name: API on WP Latest
runs-on: ubuntu-20.04
needs: [validate-version, e2e-update-wc]
permissions:
contents: read
outputs:
result: ${{ steps.run-api-composite-action.outputs.result }}
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/api-test-report/allure-results
API_WP_LATEST_ARTIFACT: API test on release smoke test site with WP Latest (run ${{ github.run_number }})
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
- name: Download and install Chromium browser.
working-directory: plugins/woocommerce
run: pnpm exec playwright install chromium
- name: Run API tests
id: run-api-composite-action
uses: ./.github/actions/tests/run-api-tests
with:
report-name: ${{ env.API_WP_LATEST_ARTIFACT }}
tests: hello
playwright-config: ci-release.playwright.config.js
env:
API_BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
USER_KEY: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
USER_SECRET: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
UPDATE_WC: ${{ needs.validate-version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.API_WP_LATEST_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish API Allure report
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.API_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && steps.run-api-composite-action.outputs.result == 'failure'
)
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: WP Latest
api-result: ${{ steps.run-api-composite-action.outputs.result }}
env-slug: wp-latest
release-version: ${{ needs.validate-version.outputs.version }}
e2e-wp-latest:
name: E2E on WP Latest
runs-on: ubuntu-20.04
needs: [validate-version, api-wp-latest]
permissions:
contents: read
env:
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
steps:
- uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
- name: Run E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
with:
report-name: e2e-wp-latest--partial--run-${{ github.run_number }}
playwright-config: ignore-plugin-tests.playwright.config.js
env:
ADMIN_PASSWORD: ${{ secrets.RELEASE_TEST_ADMIN_PASSWORD }}
ADMIN_USER: ${{ secrets.RELEASE_TEST_ADMIN_USER }}
ADMIN_USER_EMAIL: ${{ secrets.RELEASE_TEST_ADMIN_USER_EMAIL }}
BASE_URL: ${{ secrets.RELEASE_TEST_URL }}
CUSTOMER_PASSWORD: ${{ secrets.RELEASE_TEST_CUSTOMER_PASSWORD }}
CUSTOMER_USER: ${{ secrets.RELEASE_TEST_CUSTOMER_USER }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
E2E_MAX_FAILURES: 90
RESET_SITE: true
- name: Download 'e2e-update-wc' artifact
if: success() || failure()
uses: actions/download-artifact@v3
with:
name: ${{ env.E2E_UPDATE_WC_ARTIFACT }}
path: plugins/woocommerce/tmp
- name: Add allure-results from 'e2e-update-wc'
if: success() || failure()
working-directory: plugins/woocommerce
run: cp -r tmp/allure-results tests/e2e-pw/test-results
- name: Generate E2E Test report.
if: success() || failure()
working-directory: plugins/woocommerce
run: pnpm exec allure generate --clean ${{ env.ALLURE_RESULTS_DIR }} --output ${{ env.ALLURE_REPORT_DIR }}
- name: Archive E2E test report
if: success() || failure()
uses: actions/upload-artifact@v3
with:
name: ${{ env.E2E_WP_LATEST_ARTIFACT }}
path: |
${{ env.ALLURE_RESULTS_DIR }}
${{ env.ALLURE_REPORT_DIR }}
if-no-files-found: ignore
retention-days: 5
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.E2E_WP_LATEST_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish E2E Allure report
if: success() || failure()
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.E2E_WP_LATEST_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && steps.run-e2e-composite-action.outputs.result == 'failure'
)
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: WP Latest
api-result: ${{ needs.api-wp-latest.outputs.result }}
e2e-result: ${{ steps.run-e2e-composite-action.outputs.result }}
env-slug: wp-latest
release-version: ${{ needs.validate-version.outputs.version }}
test-wp-latest-1:
name: Test against WP Latest-1
runs-on: ubuntu-20.04
needs: [validate-version]
env:
API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-report
API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/api/allure-results
API_WP_LATEST_X_ARTIFACT: API test on wp-env with WordPress L-1 (run ${{ github.run_number }})
E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-report
E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/e2e/allure-results
E2E_WP_LATEST_X_ARTIFACT: E2E test on wp-env with WordPress L-1 (run ${{ github.run_number }})
permissions:
contents: read
steps:
- name: Checkout WooCommerce repo
uses: actions/checkout@v3
- name: Get WP Latest-1 version number
id: get-wp-latest-1
uses: actions/github-script@v6
with:
script: |
const { getVersionWPLatestMinusOne } = require( './plugins/woocommerce/tests/e2e-pw/utils/wordpress' );
await getVersionWPLatestMinusOne( { core, github } );
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Launch WP Env
working-directory: plugins/woocommerce
run: pnpm env:test
env:
WP_ENV_CORE: WordPress/WordPress#${{ steps.get-wp-latest-1.outputs.version }}
- name: Download release zip
id: download-zip
uses: actions/github-script@v6
env:
ASSET_ID: ${{ needs.validate-version.outputs.asset-id }}
with:
github-token: ${{ secrets.E2E_GH_TOKEN }}
script: |
const script = require('./.github/workflows/scripts/download-release-zip.js');
await script({ github, context, core });
- name: Replace `plugins/woocommerce` with unzipped woocommerce release build
run: unzip -d plugins -o ${{ env.ZIP_PATH }}
env:
ZIP_PATH: ${{ steps.download-zip.outputs.zip-path }}
- name: Run API tests
id: run-api-composite-action
uses: ./.github/actions/tests/run-api-tests
with:
report-name: ${{ env.API_WP_LATEST_X_ARTIFACT }}
tests: hello.test.js
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.API_ALLURE_REPORT_DIR }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.API_ALLURE_REPORT_DIR }}
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.API_WP_LATEST_X_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish API Allure report
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest-1
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.API_WP_LATEST_X_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Run E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
env:
E2E_MAX_FAILURES: 90
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
with:
report-name: ${{ env.E2E_WP_LATEST_X_ARTIFACT }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
env:
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }}
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.E2E_WP_LATEST_X_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish E2E Allure report
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: wp-latest-1
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.E2E_WP_LATEST_X_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && (
steps.run-api-composite-action.outputs.result == 'failure' ||
steps.run-e2e-composite-action.outputs.result == 'failure'
)
)
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: WP Latest-1 (${{ steps.get-wp-latest-1.outputs.version }})
api-result: ${{ steps.run-api-composite-action.outputs.result }}
e2e-result: ${{ steps.run-e2e-composite-action.outputs.result }}
env-slug: wp-latest-1
release-version: ${{ needs.validate-version.outputs.version }}
test-php-versions:
name: Test against PHP ${{ matrix.php_version }}
runs-on: ubuntu-20.04
needs: [validate-version]
strategy:
fail-fast: false
matrix:
php_version: ['7.4', '8.1']
env:
API_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-report
API_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/api-core-tests/test-results/allure-results
API_ARTIFACT: API test on wp-env with PHP ${{ matrix.php_version }} (run ${{ github.run_number }})
E2E_ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-report
E2E_ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/test-results/allure-results
E2E_ARTIFACT: E2E test on wp-env with PHP ${{ matrix.php_version }} (run ${{ github.run_number }})
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Launch WP Env
working-directory: plugins/woocommerce
env:
WP_ENV_PHP_VERSION: ${{ matrix.php_version }}
run: pnpm env:test
- name: Verify PHP version
working-directory: .github/workflows/scripts
env:
EXPECTED_PHP_VERSION: ${{ matrix.php_version }}
run: bash verify-php-version.sh
- name: Download release zip
id: download-zip
uses: actions/github-script@v6
env:
ASSET_ID: ${{ needs.validate-version.outputs.asset-id }}
with:
github-token: ${{ secrets.E2E_GH_TOKEN }}
script: |
const script = require('./.github/workflows/scripts/download-release-zip.js');
await script({ github, context, core });
- name: Replace `plugins/woocommerce` with unzipped woocommerce release build
run: unzip -d plugins -o ${{ env.ZIP_PATH }}
env:
ZIP_PATH: ${{ steps.download-zip.outputs.zip-path }}
- name: Run API tests
id: run-api-composite-action
uses: ./.github/actions/tests/run-api-tests
with:
report-name: ${{ env.API_ARTIFACT }}
tests: hello.test.js
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.API_ALLURE_REPORT_DIR }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
env:
ALLURE_RESULTS_DIR: ${{ env.API_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.API_ALLURE_REPORT_DIR }}
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.API_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish API Allure report
if: success() || ( failure() && steps.run-api-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: php-${{ matrix.php_version }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.API_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="api" \
--repo woocommerce/woocommerce-test-reports
- name: Run E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
env:
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }}
DEFAULT_TIMEOUT_OVERRIDE: 120000
E2E_MAX_FAILURES: 90
with:
report-name: ${{ env.E2E_ARTIFACT }}
- name: Upload Allure artifacts to bucket
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
env:
ALLURE_RESULTS_DIR: ${{ env.E2E_ALLURE_RESULTS_DIR }}
ALLURE_REPORT_DIR: ${{ env.E2E_ALLURE_REPORT_DIR }}
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.E2E_ARTIFACT }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish E2E Allure report
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
ENV_DESCRIPTION: php-${{ matrix.php_version }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.E2E_ARTIFACT }}" \
-f env_description="${{ env.ENV_DESCRIPTION }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && (
steps.run-api-composite-action.outputs.result == 'failure' ||
steps.run-e2e-composite-action.outputs.result == 'failure'
)
)
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: PHP ${{ matrix.php_version }}
api-result: ${{ steps.run-api-composite-action.outputs.result }}
e2e-result: ${{ steps.run-e2e-composite-action.outputs.result }}
env-slug: php-${{ matrix.php_version }}
release-version: ${{ needs.validate-version.outputs.version }}
test-plugins:
name: With ${{ matrix.plugin }}
runs-on: ubuntu-20.04
needs: [validate-version]
env:
ALLURE_RESULTS_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-results
ALLURE_REPORT_DIR: ${{ github.workspace }}/plugins/woocommerce/tests/e2e-pw/allure-report
ARTIFACT_NAME: E2E test on wp-env with ${{ matrix.plugin }} installed (run ${{ github.run_number }})
strategy:
fail-fast: false
matrix:
include:
- plugin: 'WooCommerce Payments'
repo: 'automattic/woocommerce-payments'
env_description: 'woocommerce-payments'
- plugin: 'WooCommerce PayPal Payments'
repo: 'woocommerce/woocommerce-paypal-payments'
env_description: 'woocommerce-paypal-payments'
- plugin: 'WooCommerce Shipping & Tax'
repo: 'automattic/woocommerce-services'
env_description: 'woocommerce-shipping-&-tax'
- plugin: 'Woo Subscriptions'
repo: WC_SUBSCRIPTIONS_REPO
private: true
env_description: 'woocommerce-subscriptions'
- plugin: 'Gutenberg'
repo: 'WordPress/gutenberg'
env_description: 'gutenberg'
- plugin: 'Gutenberg - Nightly'
repo: 'bph/gutenberg'
env_description: 'gutenberg-nightly'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup WooCommerce Monorepo
uses: ./.github/actions/setup-woocommerce-monorepo
with:
install: '@woocommerce/plugin-woocommerce...'
build: '@woocommerce/plugin-woocommerce'
- name: Launch WP Env
working-directory: plugins/woocommerce
run: pnpm env:test
- name: Download release zip
id: download-zip
uses: actions/github-script@v6
env:
ASSET_ID: ${{ needs.validate-version.outputs.asset-id }}
with:
github-token: ${{ secrets.E2E_GH_TOKEN }}
script: |
const script = require('./.github/workflows/scripts/download-release-zip.js');
await script({ github, context, core });
- name: Replace `plugins/woocommerce` with unzipped woocommerce release build
run: unzip -d plugins -o ${{ env.ZIP_PATH }}
env:
ZIP_PATH: ${{ steps.download-zip.outputs.zip-path }}
- name: Run 'Upload plugin' test
id: run-upload-test
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
with:
report-name: ${{ env.ARTIFACT_NAME }}
tests: upload-plugin.spec.js
env:
GITHUB_TOKEN: ${{ secrets.E2E_GH_TOKEN }}
PLUGIN_NAME: ${{ matrix.plugin }}
PLUGIN_REPOSITORY: ${{ matrix.private && secrets[matrix.repo] || matrix.repo }}
- name: Run the rest of E2E tests
id: run-e2e-composite-action
timeout-minutes: 90
uses: ./.github/actions/tests/run-e2e-tests
with:
playwright-config: ignore-plugin-tests.playwright.config.js
report-name: ${{ env.ARTIFACT_NAME }}
env:
E2E_MAX_FAILURES: 90
- name: Upload Allure artifacts to bucket
if: |
success() ||
( failure() &&
( steps.run-upload-test.conclusion == 'failure' || steps.run-e2e-composite-action.conclusion == 'failure' ) )
uses: ./.github/actions/tests/upload-allure-files-to-bucket
with:
aws-access-key-id: ${{ secrets.REPORTS_AWS_ACCESS_KEY_ID }}
aws-region: ${{ secrets.REPORTS_AWS_REGION }}
aws-secret-access-key: ${{ secrets.REPORTS_AWS_SECRET_ACCESS_KEY }}
artifact-name: ${{ env.ARTIFACT_NAME }}
s3-bucket: ${{ secrets.REPORTS_BUCKET }}
- name: Publish E2E Allure report
if: success() || ( failure() && steps.run-e2e-composite-action.conclusion == 'failure' )
env:
GITHUB_TOKEN: ${{ secrets.REPORTS_TOKEN }}
run: |
gh workflow run publish-test-reports-release.yml \
-f created_at="${{ needs.validate-version.outputs.created }}" \
-f run_id=${{ github.run_id }} \
-f run_number=${{ github.run_number }} \
-f release_tag=${{ needs.validate-version.outputs.version }} \
-f artifact="${{ env.ARTIFACT_NAME }}" \
-f env_description="${{ matrix.env_description }}" \
-f test_type="e2e" \
--repo woocommerce/woocommerce-test-reports
- name: Create Slack block
if: |
success() || (
failure() && steps.run-e2e-composite-action.outputs.result == 'failure' )
uses: ./.github/actions/tests/slack-summary-on-release/slack-blocks
with:
test-name: With ${{ matrix.plugin }}
e2e-result: ${{ steps.run-e2e-composite-action.outputs.result }}
env-slug: ${{ matrix.env_description }}
release-version: ${{ needs.validate-version.outputs.version }}
post-slack-summary:
name: Post Slack summary
runs-on: ubuntu-20.04
permissions:
contents: read
if: |
github.ref_name == 'trunk' &&
success() || (
failure() && contains( needs.*.result, 'failure' )
)
needs:
- e2e-wp-latest
- validate-version
- test-php-versions
- test-plugins
- test-wp-latest-1
steps:
- uses: actions/checkout@v3
- name: Download all slack blocks
id: download-slack-blocks
uses: actions/download-artifact@v3
with:
name: ${{ env.SLACK_BLOCKS_ARTIFACT }}
path: /tmp/slack-payload
- name: Construct payload from all blocks
id: run-payload-action
uses: ./.github/actions/tests/slack-summary-on-release/slack-payload
with:
release-version: ${{ needs.validate-version.outputs.version }}
blocks-dir: ${{ steps.download-slack-blocks.outputs.download-path }}
- name: Send Slack message
uses: slackapi/slack-github-action@v1.23.0
with:
channel-id: ${{ secrets.RELEASE_TEST_SLACK_CHANNEL }}
payload: ${{ steps.run-payload-action.outputs.payload }}
env:
SLACK_BOT_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}

14
.github/workflows/tests-daily-run.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: 'Daily tests run'
on:
schedule:
- cron: '25 3 * * *'
workflow_dispatch:
jobs:
run-tests:
name: 'Run tests'
if: github.repository == 'woocommerce/woocommerce'
uses: ./.github/workflows/ci.yml
with:
trigger: 'daily-checks'
secrets: inherit

38
.github/workflows/tests-on-demand.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: 'On demand tests run'
on:
workflow_dispatch:
inputs:
trigger:
type: choice
description: 'Event name: it will be used to filter the jobs to run in ci.yml.'
required: true
options:
- push
- daily-checks
- pre-release
- on-demand
- custom
default: on-demand
custom-trigger:
type: string
description: 'Custom event name: In case the `Event name` choice is `custom`, this field is required.'
required: false
jobs:
validate-input:
runs-on: ubuntu-latest
steps:
- name: 'Validate input'
run: |
if [ "${{ inputs.trigger }}" == "custom" ] && [ -z "${{ inputs.custom-trigger }}" ]; then
echo "Custom event name is required when event name choice `custom`."
exit 1
fi
run-tests:
name: 'Run tests'
uses: ./.github/workflows/ci.yml
with:
trigger: ${{ inputs.trigger == 'custom' && inputs.custom-trigger || inputs.trigger }}
secrets: inherit

View File

@ -1,4 +1,10 @@
#!/bin/sh
#!/usr/bin/env bash
. "$(dirname "$0")/_/husky.sh"
pnpm install
changedManifests=$( ( git diff --name-only HEAD ORIG_HEAD | grep -E '(package.json|pnpm-lock.yaml|pnpm-workspace.yaml|composer.json|composer.lock)$' ) || echo '' )
if [ -n "$changedManifests" ]; then
printf "It was a change in the following file(s) - refreshing dependencies:\n"
printf " %s\n" $changedManifests
pnpm install --frozen-lockfile
fi

View File

@ -41,7 +41,43 @@
"@types/*",
"@typescript-eslint/*",
"@woocommerce/*",
"@wordpress/*",
"@wordpress/api-fetch",
"@wordpress/autop",
"@wordpress/babel-preset-default",
"@wordpress/base-styles",
"@wordpress/block-editor",
"@wordpress/blocks",
"@wordpress/browserslist-config",
"@wordpress/components",
"@wordpress/compose",
"@wordpress/core-data",
"@wordpress/data",
"@wordpress/data-controls",
"@wordpress/date",
"@wordpress/dependency-extraction-webpack-plugin",
"@wordpress/deprecated",
"@wordpress/dom",
"@wordpress/dom-ready",
"@wordpress/e2e-test-utils",
"@wordpress/e2e-test-utils-playwright",
"@wordpress/e2e-tests",
"@wordpress/element",
"@wordpress/html-entities",
"@wordpress/i18n",
"@wordpress/icons",
"@wordpress/is-shallow-equal",
"@wordpress/notices",
"@wordpress/plugins",
"@wordpress/postcss-plugins-preset",
"@wordpress/postcss-themes",
"@wordpress/prettier-config",
"@wordpress/primitives",
"@wordpress/scripts",
"@wordpress/server-side-render",
"@wordpress/style-engine",
"@wordpress/stylelint-config",
"@wordpress/url",
"@wordpress/wordcount",
"babel*",
"eslint*",
"glob*",
@ -136,7 +172,7 @@
"packages": [
"**"
],
"pinVersion": "^1.40.1"
"pinVersion": "^1.45.1"
},
{
"dependencies": [
@ -205,9 +241,10 @@
"@wordpress/env"
],
"packages": [
"@woocommerce/block-library",
"**"
],
"pinVersion": "^9.0.7"
"pinVersion": "^9.7.0"
},
{
"dependencies": [
@ -216,7 +253,7 @@
"packages": [
"**"
],
"pinVersion": "wp-6.4"
"pinVersion": "wp-6.6"
},
{
"dependencies": [

View File

@ -0,0 +1,44 @@
diff --git a/build-module/lock-unlock.js b/build-module/lock-unlock.js
index 2265f933ceec19f65ca6776c24c3f88b368d713f..e9e10980bfd1b584ab0a037c3b72edae29a2a26e 100644
--- a/build-module/lock-unlock.js
+++ b/build-module/lock-unlock.js
@@ -1,9 +1,34 @@
/**
- * WordPress dependencies
+ * External dependencies
*/
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
-export const {
- lock,
- unlock
-} = __dangerousOptInToUnstableAPIsOnlyForCoreModules('I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.', '@wordpress/edit-site');
+
+// Workaround for Gutenberg private API consent string differences between WP 6.3 and 6.4+
+// The modified version checks for the WP version and replaces the consent string with the correct one.
+// This can be removed once we drop support for WP 6.3 in the "Customize Your Store" task.
+// See this PR for details: https://github.com/woocommerce/woocommerce/pull/40884
+
+const wordPressConsentString = {
+ 6.4: 'I know using unstable features means my plugin or theme will inevitably break on the next WordPress release.',
+ 6.5: 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.',
+ 6.6: 'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
+};
+
+function optInToUnstableAPIs() {
+ let error;
+ for ( const optInString of Object.values( wordPressConsentString ) ) {
+ try {
+ return __dangerousOptInToUnstableAPIsOnlyForCoreModules(
+ optInString,
+ '@wordpress/edit-site'
+ );
+ } catch ( anError ) {
+ error = anError;
+ }
+ }
+
+ throw error;
+}
+
+export const { lock, unlock } = optInToUnstableAPIs();
//# sourceMappingURL=lock-unlock.js.map

View File

@ -1,5 +1,509 @@
== Changelog ==
= 9.1.4 2024-07-26 =
**WooCommerce**
* Fix - Revert fixing terms count in tracking PR as it caused product_add_publish to be triggered more than usual. [#49797](https://github.com/woocommerce/woocommerce/pull/49797)
* Fix - Hardening against XSS via the Product Button unescaped attribute. [#50010](https://github.com/woocommerce/woocommerce/pull/50010)
* Fix - Enhance escaping for block attributes. [#50015](https://github.com/woocommerce/woocommerce/pull/50015)
= 9.1.2 2024-07-12 =
**WooCommerce**
* Fix - Revert 46857 to preserve backcompat with earlier WC versions. [#48753](https://github.com/woocommerce/woocommerce/pull/48753)
= 9.1.1 2024-07-11 =
**WooCommerce**
* Tweak - Revert #46262, as that PR would render input values invisible under certain conditions. [49404](https://github.com/woocommerce/woocommerce/pull/49404)
= 9.1.0 2024-07-10 =
**WooCommerce**
* Fix - Prevent HTML tags being rendered on order confirmation and emails [#49370](https://github.com/woocommerce/woocommerce/pull/49370)
* Security - Improve the way we cache information about recent customer activity, to prevent the wrong data being retrieved in some specific conditions involving multisite networks. [#49373](https://github.com/woocommerce/woocommerce/pull/49373)
* Fix - Prevent BatchProcessingController from cleaning up processors after a premature shutdown. [#49243](https://github.com/woocommerce/woocommerce/pull/49243)
* Fix - CYS: fix not template set correctly. [#49113](https://github.com/woocommerce/woocommerce/pull/49113)
* Fix - CYS: Disable readonly mode only when full composability feature flag is enabled. [#48752](https://github.com/woocommerce/woocommerce/pull/48752)
* Fix - CYS: fix crash of CYS on WordPress 6.6 [#48664](https://github.com/woocommerce/woocommerce/pull/48664)
* Fix - Revert "Set stock quantity value as 0 by default (#48448)" #48863 [#48863](https://github.com/woocommerce/woocommerce/pull/48863)
* Fix - Add product id to product_edit_view track in classic product edit screen [#47853](https://github.com/woocommerce/woocommerce/pull/47853)
* Fix - Address responsiveness issues in orders list table. [#47684](https://github.com/woocommerce/woocommerce/pull/47684)
* Fix - Add screen-reader-text styles to e-mails. [#47738](https://github.com/woocommerce/woocommerce/pull/47738)
* Fix - Adds new hook `woocommerce_rest_delete_shipping_zone_method` which will fire after a shipping zone method is deleted via the REST API. [#47862](https://github.com/woocommerce/woocommerce/pull/47862)
* Fix - Allow products with non-integer stock to be created via REST API. [#48541](https://github.com/woocommerce/woocommerce/pull/48541)
* Fix - Calling $product->get_status() after $product->save() on a new product now returns correct status. [#48241](https://github.com/woocommerce/woocommerce/pull/48241)
* Fix - Change the cursor to a pointer when hovering over the mini cart [#46996](https://github.com/woocommerce/woocommerce/pull/46996)
* Fix - CYS - Hovering over the header or footer on the "Design your homepage" section should not make them highlighted. [#48358](https://github.com/woocommerce/woocommerce/pull/48358)
* Fix - CYS - Select the next block after deleting the selected one (instead of the header). [#48316](https://github.com/woocommerce/woocommerce/pull/48316)
* Fix - CYS: apply white color to the heading elements in the core/cover block. [#48447](https://github.com/woocommerce/woocommerce/pull/48447)
* Fix - CYS: Fix crash homepage. [#48205](https://github.com/woocommerce/woocommerce/pull/48205)
* Fix - CYS: Fix CSS header. [#48389](https://github.com/woocommerce/woocommerce/pull/48389)
* Fix - CYS: fix logic to disable mover buttons. [#48502](https://github.com/woocommerce/woocommerce/pull/48502)
* Fix - CYS: fix tooltip position. [#48495](https://github.com/woocommerce/woocommerce/pull/48495)
* Fix - CYS: hide popover when the mouse pointer leaves the site preview and then back. [#48394](https://github.com/woocommerce/woocommerce/pull/48394)
* Fix - Do not create empty webhooks after failure to deliver deleted webhook. [#48480](https://github.com/woocommerce/woocommerce/pull/48480)
* Fix - Ensure attribute slugs with multibyte characters are handled property when outputting attributes in the REST API products endpoint [#48198](https://github.com/woocommerce/woocommerce/pull/48198)
* Fix - Ensure available stock is updated correctly when updating line items in orders via the REST API. [#47784](https://github.com/woocommerce/woocommerce/pull/47784)
* Fix - Ensure data filtered by `woocommerce_logger_log_message` does not carry across multiple log handlers [#48336](https://github.com/woocommerce/woocommerce/pull/48336)
* Fix - Ensure getPreviousDate default behaviour is comparing previous_year [#47951](https://github.com/woocommerce/woocommerce/pull/47951)
* Fix - Ensure permission checks for the customer downloads REST API endpoint use the correct customer ID. [#47854](https://github.com/woocommerce/woocommerce/pull/47854)
* Fix - Ensure that data containing multibyte characters and/or slashes that is appended to log entries gets encoded and rendered correctly [#48341](https://github.com/woocommerce/woocommerce/pull/48341)
* Fix - Fix a bug with the woocommerce_get_default_value_for_{key} filter that was preventing setting a falsey value on a checkbox (i.e. to uncheck it dynamically) [#48031](https://github.com/woocommerce/woocommerce/pull/48031)
* Fix - Fix activation limit for single license subscriptions on woocommerce.com [#47643](https://github.com/woocommerce/woocommerce/pull/47643)
* Fix - Fix a null parameter being passed into strpos in Admin/Orders/PageController.php [#48476](https://github.com/woocommerce/woocommerce/pull/48476)
* Fix - Fix bug where Core Profiler initiates a Jetpack connection even if it was already connected before [#48345](https://github.com/woocommerce/woocommerce/pull/48345)
* Fix - Fix bumped down data when analytics chart current period contains 29th Feb [#45874](https://github.com/woocommerce/woocommerce/pull/45874)
* Fix - Fix coming soon footer banner doesn't display properly on tablet and mobile [#47980](https://github.com/woocommerce/woocommerce/pull/47980)
* Fix - Fix e2e tests about the tabs selection during the product creation experience [#47860](https://github.com/woocommerce/woocommerce/pull/47860)
* Fix - Fix edit variable product test [#48288](https://github.com/woocommerce/woocommerce/pull/48288)
* Fix - Fix FlexSlider thumbnail animation for variable products with default form values on small devices. </details> <details> [#48137](https://github.com/woocommerce/woocommerce/pull/48137)
* Fix - Fix location settings not updated in tax task [#48606](https://github.com/woocommerce/woocommerce/pull/48606)
* Fix - Fix LYS private link URL parameter regardless of permalink settings [#48425](https://github.com/woocommerce/woocommerce/pull/48425)
* Fix - Fix product archive page not hidden behind the coming soon page [#48522](https://github.com/woocommerce/woocommerce/pull/48522)
* Fix - Fix Product Gallery block error on revisiting Single Product template without fully reloading the page. [#47636](https://github.com/woocommerce/woocommerce/pull/47636)
* Fix - Fix product tracks when importing #47857 [#47857](https://github.com/woocommerce/woocommerce/pull/47857)
* Fix - Fix some issues in performance tests #47735 [#47735](https://github.com/woocommerce/woocommerce/pull/47735)
* Fix - Fix the issue that the React-powered admin routing pages added after the filter initialization could not be displayed. [#47696](https://github.com/woocommerce/woocommerce/pull/47696)
* Fix - Fix the terms counts in wcadmin_product_add_publish event. [#48194](https://github.com/woocommerce/woocommerce/pull/48194)
* Fix - Fix two products being added to cart when Geolocate (with page caching support) was enabled and AJAX add to cart buttons disabled [#47761](https://github.com/woocommerce/woocommerce/pull/47761)
* Fix - Fix untranslated strings on CYS and marketplace [#48127](https://github.com/woocommerce/woocommerce/pull/48127)
* Fix - Honor empty "additional content" setting in e-mails. [#47809](https://github.com/woocommerce/woocommerce/pull/47809)
* Fix - Improve consistency of Setting-> Gateway Manage button for WooPayments gateway [#48212](https://github.com/woocommerce/woocommerce/pull/48212)
* Fix - In general, the `last_access` field of a REST API key should only be updated once-per-request.
* Fix - Make coupon metadata read robust against wrongly stored product related metadata [#48362](https://github.com/woocommerce/woocommerce/pull/48362)
* Fix - Moved WooCommerce block categories registration on the server-side, fixing a bug that would show warnings to developers trying to hook new blocks in such categories. [#47836](https://github.com/woocommerce/woocommerce/pull/47836)
* Fix - Possible availability of unpublished coupons on sites with an object cache has been addressed through improved cache management. [#47739](https://github.com/woocommerce/woocommerce/pull/47739)
* Fix - Prefer update URLs over PluginURI in My Subscriptions for plugins without a subscription. [#47950](https://github.com/woocommerce/woocommerce/pull/47950)
* Fix - Prevent on-sale badge from showing on top of the coming soon banner. [#48082](https://github.com/woocommerce/woocommerce/pull/48082)
* Fix - Prevent Product Gallery from being inserted on Posts and Pages. [#48228](https://github.com/woocommerce/woocommerce/pull/48228)
* Fix - Product Collection: prevent throwing warnings in some circumstances when rendering block [#48530](https://github.com/woocommerce/woocommerce/pull/48530)
* Fix - Product Price: Narrow down the ancestors of the block so it's available in inserter only in places where block makes sense [#47802](https://github.com/woocommerce/woocommerce/pull/47802)
* Fix - Re-enable variable product E2E test #48294 [#48294](https://github.com/woocommerce/woocommerce/pull/48294)
* Fix - Related Products: hides unusable options from Inspector Controls [#47845](https://github.com/woocommerce/woocommerce/pull/47845)
* Fix - Run possibly_add_template_id function in woocommerce_rest_prepare_product_variation_object hook [#48325](https://github.com/woocommerce/woocommerce/pull/48325)
* Fix - Scroll to view the templates section on the status page [#48125](https://github.com/woocommerce/woocommerce/pull/48125)
* Fix - Set stock quantity value as 0 by default #48448 [#48448](https://github.com/woocommerce/woocommerce/pull/48448)
* Fix - Update plugin installation error logger to use plugin track key for extension name [#47786](https://github.com/woocommerce/woocommerce/pull/47786)
* Fix - When a product attribute is updated, unchanged values should not be reset to their defaults. [#48120](https://github.com/woocommerce/woocommerce/pull/48120)
* Fix - WooCommerce: fixes the checks when migrating the product form template [#48386](https://github.com/woocommerce/woocommerce/pull/48386)
* Fix - [CYS Full Composability] Ensure that the assembler doesn't crash when the feature flag is enabled, but the site doesn't have the latest version of Gutenberg. [#47546](https://github.com/woocommerce/woocommerce/pull/47546)
* Add - Add CLI tools for the product attributes lookup table [#47311](https://github.com/woocommerce/woocommerce/pull/47311)
* Add - Add 'woocommerce_order_note_deleted' hook for order note deletions. [#47916](https://github.com/woocommerce/woocommerce/pull/47916)
* Add - Add CLI tools to enable and disable HPOS compatibility mode. [#48117](https://github.com/woocommerce/woocommerce/pull/48117)
* Add - Added 'woocommerce_restore_order_item_stock' filter for restored line item stock on canceled orders [#40848](https://github.com/woocommerce/woocommerce/pull/40848)
* Add - Add ErrorBoundary component for handling unexpect errors [#48250](https://github.com/woocommerce/woocommerce/pull/48250)
* Add - Add filter to dynamically exclude a page from Coming soon mode [#47787](https://github.com/woocommerce/woocommerce/pull/47787)
* Add - Add Printful product placement to Add Products task [#48520](https://github.com/woocommerce/woocommerce/pull/48520)
* Add - Add skipped test custom reporter to surface skipped tests in CI runs [#48195](https://github.com/woocommerce/woocommerce/pull/48195)
* Add - Add the ability to test experimental blocks via the Advanced > Features menu of WooCommerce settings. [#47701](https://github.com/woocommerce/woocommerce/pull/47701)
* Add - Add woocommerce_manage_stock option to the default_option_permissions list in the Options rest controller [#48239](https://github.com/woocommerce/woocommerce/pull/48239)
* Add - CYS: add CTA to our Fiverr Logo Maker landing page. [#48486](https://github.com/woocommerce/woocommerce/pull/48486)
* Add - CYS: add pattern category in the block toolbar. [#48501](https://github.com/woocommerce/woocommerce/pull/48501)
* Add - CYS: Add the Delete button to the Block Toolbar. [#48143](https://github.com/woocommerce/woocommerce/pull/48143)
* Add - CYS: Ensure that toolbar appears only when the homepage sidebar is open. [#48115](https://github.com/woocommerce/woocommerce/pull/48115)
* Add - CYS: Show Patterns from PTK. [#48207](https://github.com/woocommerce/woocommerce/pull/48207)
* Add - CYS: Show popover when the user clicks on the pattern [#47583](https://github.com/woocommerce/woocommerce/pull/47583)
* Add - Determine _product_template_id from 'woocommerce_product_editor_determine_product_template' filter [#47762](https://github.com/woocommerce/woocommerce/pull/47762)
* Add - Display an admin notice in Setting and Extension pages when there are expiring subscriptions and connected account doesn't have a payment method. [#47141](https://github.com/woocommerce/woocommerce/pull/47141)
* Add - Enhancements to background batch processing. [#48078](https://github.com/woocommerce/woocommerce/pull/48078)
* Add - Highlight the pattern when the user hovers it. [#47415](https://github.com/woocommerce/woocommerce/pull/47415)
* Add - LYS - Add 'Remove test orders' for WooPayments [#47832](https://github.com/woocommerce/woocommerce/pull/47832)
* Add - PFT: introduce controller and initialize it [#48221](https://github.com/woocommerce/woocommerce/pull/48221)
* Add - REST API: extened shipping_classes namespace with the /suggest-slug endpoint [#47896](https://github.com/woocommerce/woocommerce/pull/47896)
* Add - Updated shipstation copy [#48549](https://github.com/woocommerce/woocommerce/pull/48549)
* Add - WooCommerce: create a new product_form CPT [#48073](https://github.com/woocommerce/woocommerce/pull/48073)
* Add - WooCommerce: introduce `product-editor-template-system` feature flag [#48136](https://github.com/woocommerce/woocommerce/pull/48136)
* Add - WooCommerce: update CPT product_form posts when plugin updates [#48265](https://github.com/woocommerce/woocommerce/pull/48265)
* Add - WooCommerce Blocks: Added a GitHub Action to create issues for flaky E2E tests [#47758](https://github.com/woocommerce/woocommerce/pull/47758)
* Update - Add feature flag for Printful placement [#49104](https://github.com/woocommerce/woocommerce/pull/49104)
* Update - Add a control to enable a separator on the Checkout block's "Checkout Terms" block. This will enable a separator above the block that can be turned off in case the block is moved. [#47565](https://github.com/woocommerce/woocommerce/pull/47565)
* Update - Change the item schemas for Orders and Order Refunds API endpoints to correctly specify that the rate_id property in a tax_line object is an integer, not a string [#47779](https://github.com/woocommerce/woocommerce/pull/47779)
* Update - Clean up theming sections in WooCommerce blocks docs [#48420](https://github.com/woocommerce/woocommerce/pull/48420)
* Update - CYS - Exclude two testimonials patterns from registering since they depend on Jetpack. [#48233](https://github.com/woocommerce/woocommerce/pull/48233)
* Update - CYS - Fix active/inactive patterns for each of the sections in the assembler. [#48458](https://github.com/woocommerce/woocommerce/pull/48458)
* Update - CYS - Install the patterns during the CYS flow if the transient is not set. [#48274](https://github.com/woocommerce/woocommerce/pull/48274)
* Update - CYS - Redirect to the same section after installing fonts or patterns on the assembler. [#48227](https://github.com/woocommerce/woocommerce/pull/48227)
* Update - CYS - Show tooltips on the Shuffle and Delete buttons in the assembler toolbar. [#48465](https://github.com/woocommerce/woocommerce/pull/48465)
* Update - CYS: set new default patterns. [#48467](https://github.com/woocommerce/woocommerce/pull/48467)
* Update - Display return to cart link on mobile devices. [#48103](https://github.com/woocommerce/woocommerce/pull/48103)
* Update - Docs: update documentation regarding Compatibility Layer [#48456](https://github.com/woocommerce/woocommerce/pull/48456)
* Update - Expand block templates documentation [#48247](https://github.com/woocommerce/woocommerce/pull/48247)
* Update - Experimental blocks now have "(Experimental)" suffix [#48071](https://github.com/woocommerce/woocommerce/pull/48071)
* Update - fix: label improvement on my order page template [#48374](https://github.com/woocommerce/woocommerce/pull/48374)
* Update - Improve WooCommerce block template names in the Add New Template screen. [#48106](https://github.com/woocommerce/woocommerce/pull/48106)
* Update - Invalidate cache for SiteGround Speed Optimizer [#48523](https://github.com/woocommerce/woocommerce/pull/48523)
* Update - Optimize the regeneration of the product attributes lookup table [#47700](https://github.com/woocommerce/woocommerce/pull/47700)
* Update - Product Archive templates: Replace the default block from Products (Beta) to Product Collection block [#48112](https://github.com/woocommerce/woocommerce/pull/48112)
* Update - Product Block Editor: disable the `product-editor-template-system` feature flag as default, even for the development environment. [#48378](https://github.com/woocommerce/woocommerce/pull/48378)
* Update - Product Collection: Handpicked Products filter now allows searching from 2 characters and more and updates available results as you type [#48379](https://github.com/woocommerce/woocommerce/pull/48379)
* Update - Product Elements: hide Product Summary from Single Product block and only show Excerpt variation [#48253](https://github.com/woocommerce/woocommerce/pull/48253)
* Update - Product Rating Stars and Product Rating Counter from the inserter [#48229](https://github.com/woocommerce/woocommerce/pull/48229)
* Update - Products (Beta): hide block from inserter in favor of Product Collection block [#48204](https://github.com/woocommerce/woocommerce/pull/48204)
* Update - Product Summary: Increase the length of the description from 55 to 100 words (max supported by core/post-excerpt) [#47651](https://github.com/woocommerce/woocommerce/pull/47651)
* Update - Reduced the number of FlexSlider animation engines from 2 to 1, now always using CSS3 transitions. [#46564](https://github.com/woocommerce/woocommerce/pull/46564)
* Update - Replace the use of options endpoint with the LYS API endpoint to query woocommerce_admin_launch_your_store_survey_completed option. [#47915](https://github.com/woocommerce/woocommerce/pull/47915)
* Update - The archive product title will now be updated to the title of the current shop
page. If the page does not exist, it will fall back to "Shop". [#48255](https://github.com/woocommerce/woocommerce/pull/48255)
* Update - Toggle LYS feature flag on for 9.1 [#48244](https://github.com/woocommerce/woocommerce/pull/48244)
* Update - Update input fields styles of the Checkout block [#46362](https://github.com/woocommerce/woocommerce/pull/46362)
* Update - WooCommerce: store the template description in the `product_form` excerpt property. [#48327](https://github.com/woocommerce/woocommerce/pull/48327)
* Update - Wrap activity panels in error boundary [#48415](https://github.com/woocommerce/woocommerce/pull/48415)
* Update - [CYS] Ensure fetch PTK patterns requests are always done async to improve performance. [#47551](https://github.com/woocommerce/woocommerce/pull/47551)
* Update - [CYS] Refactor the pattern registration and add patterns from the PTK API. [#47306](https://github.com/woocommerce/woocommerce/pull/47306)
* Update - [CYS] Remove the restriction to TT4 and allow users to proceed to the pattern assembler with any block themes. Update intro page design. [#46916](https://github.com/woocommerce/woocommerce/pull/46916)
* Update - [CYS] Show a message when tracking is not allowed in patterns and add the ability for users to opt-in and fetch patterns. </details> <details> [#48095](https://github.com/woocommerce/woocommerce/pull/48095)
* Dev - Improve E2E selector by making it stricter. Wait for text due to AJAX call. [#48471](https://github.com/woocommerce/woocommerce/pull/48471)
* Dev - Added e2e test to check ability to connect to woocommerce.com [#48028](https://github.com/woocommerce/woocommerce/pull/48028)
* Dev - Added test enviornments [#48101](https://github.com/woocommerce/woocommerce/pull/48101)
* Dev - Add previous error class to checkout endpoint response [#47489](https://github.com/woocommerce/woocommerce/pull/47489)
* Dev - Add test for wcpay_connect_account_clicked track [#48347](https://github.com/woocommerce/woocommerce/pull/48347)
* Dev - Add tests for some product editor tracks [#48245](https://github.com/woocommerce/woocommerce/pull/48245)
* Dev - Blocks E2E: Remove confusing utilities in favor of native locator functionality. [#47904](https://github.com/woocommerce/woocommerce/pull/47904)
* Dev - CI: merge test jobs [#48175](https://github.com/woocommerce/woocommerce/pull/48175)
* Dev - Clean up eslint comments after rules update in Blocks E2E tests. [#47875](https://github.com/woocommerce/woocommerce/pull/47875)
* Dev - Clean up tasklist progression headercard experiment [#47983](https://github.com/woocommerce/woocommerce/pull/47983)
* Dev - Clean up welcome modal code [#48346](https://github.com/woocommerce/woocommerce/pull/48346)
* Dev - Do not dismiss the error snackbar automatically, fix E2E test #48192 [#48192](https://github.com/woocommerce/woocommerce/pull/48192)
* Dev - E2E test: Improve analytics data spec by disabling the task list reminder bar [#48357](https://github.com/woocommerce/woocommerce/pull/48357)
* Dev - E2E tests: configure snapshotPathTemplate [#47773](https://github.com/woocommerce/woocommerce/pull/47773)
* Dev - E2E tests: fixing flakiness in checkout block and launch your store tests [#48016](https://github.com/woocommerce/woocommerce/pull/48016)
* Dev - E2E tests: fixing flaky assembler homepage test [#48356](https://github.com/woocommerce/woocommerce/pull/48356)
* Dev - E2E tests: fixing flaky checkout block test [#48527](https://github.com/woocommerce/woocommerce/pull/48527)
* Dev - E2E tests: fixing flaky color palette picker test [#48496](https://github.com/woocommerce/woocommerce/pull/48496)
* Dev - E2E tests: fixing flaky connect to woo test [#48613](https://github.com/woocommerce/woocommerce/pull/48613)
* Dev - E2E tests: fixing flaky customize store transitional test [#48532](https://github.com/woocommerce/woocommerce/pull/48532)
* Dev - E2E tests: fixing flaky logo picker test [#48503](https://github.com/woocommerce/woocommerce/pull/48503)
* Dev - E2E tests: fixing flaky merchant create variable product test [#48276](https://github.com/woocommerce/woocommerce/pull/48276)
* Dev - E2E tests: fixing flaky merchant customer list test [#48463](https://github.com/woocommerce/woocommerce/pull/48463)
* Dev - E2E tests: fixing flaky merchant product attribute test [#48230](https://github.com/woocommerce/woocommerce/pull/48230)
* Dev - E2E tests: fixing flaky merchant user create and logging [#48446](https://github.com/woocommerce/woocommerce/pull/48446)
* Dev - E2E tests: fixing flaky shopper checkout coupons [#48555](https://github.com/woocommerce/woocommerce/pull/48555)
* Dev - E2E tests: fixing flaky shopper search browse products in the shop [#48560](https://github.com/woocommerce/woocommerce/pull/48560)
* Dev - E2E tests: fixing flaky store owner core profiler test [#48430](https://github.com/woocommerce/woocommerce/pull/48430)
* Dev - E2E tests: fixing skipped mini cart test [#47756](https://github.com/woocommerce/woocommerce/pull/47756)
* Dev - E2E tests: fixing skipped tests [#47859](https://github.com/woocommerce/woocommerce/pull/47859)
* Dev - E2E tests: improve existing merchant e2e tests for creating page and post [#48162](https://github.com/woocommerce/woocommerce/pull/48162)
* Dev - E2E tests: improve existing util for inserting blocks via shortcut [#48225](https://github.com/woocommerce/woocommerce/pull/48225)
* Dev - E2E tests: improving cart util and updating relevant tests [#48475](https://github.com/woocommerce/woocommerce/pull/48475)
* Dev - E2E tests: updated the test ignore pattern for Gutenberg tests project [#47764](https://github.com/woocommerce/woocommerce/pull/47764)
* Dev - E2E tests: update tests checking if blocks can be added [#48211](https://github.com/woocommerce/woocommerce/pull/48211)
* Dev - E2E tests: update the report configuration for all core jobs [#48424](https://github.com/woocommerce/woocommerce/pull/48424)
* Dev - Fix a filters block e2e test that was mistakenly merged incorrectly. [#48122](https://github.com/woocommerce/woocommerce/pull/48122)
* Dev - Fixing a flaky core profiler e2e test [#47917](https://github.com/woocommerce/woocommerce/pull/47917)
* Dev - Fix path to test results for api core tests [#48490](https://github.com/woocommerce/woocommerce/pull/48490)
* Dev - Implement unit test for tracks wcadmin_page_view, wcadmin_tasklist_view, wcadmin_tasklist_task_completed, wcadmin_tasklist_click [#47876](https://github.com/woocommerce/woocommerce/pull/47876)
* Dev - Include blocks e2e in ci.yml [#48224](https://github.com/woocommerce/woocommerce/pull/48224)
* Dev - Migrate release smoke workflow to the new CI setup [#48113](https://github.com/woocommerce/woocommerce/pull/48113)
* Dev - Product Editor: Move variation pricing fields to General tab. [#48155](https://github.com/woocommerce/woocommerce/pull/48155)
* Dev - Remove the isFeaturePlugin function, which was used to turn off experimental block styling (but was non functional). Also remove associated code in FeatureGating class. [#47866](https://github.com/woocommerce/woocommerce/pull/47866)
* Dev - Remove WOOCOMMERCE_BLOCKS_PHASE completely from the monorepo, introduce BUNDLE_EXPERIMENTAL_BLOCKS just for the purpose of building/bundling experimental blocks [#47807](https://github.com/woocommerce/woocommerce/pull/47807)
* Dev - Skipped flaky test: test_order_updated_webhook_delivered_once [#48064](https://github.com/woocommerce/woocommerce/pull/48064)
* Dev - Streamline the implementation of the Blocks' E2E utilities. [#47660](https://github.com/woocommerce/woocommerce/pull/47660)
* Dev - Streamline the usage of WP CLI in Blocks E2E tests. [#47869](https://github.com/woocommerce/woocommerce/pull/47869)
* Dev - Tweak the paths that should trigger e2e tests. [#48067](https://github.com/woocommerce/woocommerce/pull/48067)
* Dev - Unskip some tests that have been skipped for flakiness [#47772](https://github.com/woocommerce/woocommerce/pull/47772)
* Dev - Update @wordpress/env version to 9.7.0 [#48443](https://github.com/woocommerce/woocommerce/pull/48443)
* Dev - Updated Core Profilers XState version to V5 [#48135](https://github.com/woocommerce/woocommerce/pull/48135)
* Dev - Update Playwright from 1.41.1 to 1.44.1 (latest) and fixed tests [#48291](https://github.com/woocommerce/woocommerce/pull/48291)
* Dev - Update pnpm-lock with updated React [#47973](https://github.com/woocommerce/woocommerce/pull/47973)
* Dev - Update the React version in the pnpm-lock file [#47993](https://github.com/woocommerce/woocommerce/pull/47993)
* Dev - Update the URLs for order-related e2e tests to use new URLs from HPOS [#46397](https://github.com/woocommerce/woocommerce/pull/46397)
* Dev - [e2e tests] Fix e2e test reports paths [#48320](https://github.com/woocommerce/woocommerce/pull/48320)
* Tweak - Update Printful label [#48778](https://github.com/woocommerce/woocommerce/pull/48778)
* Tweak - Add a close button to dismiss store alerts [#48453](https://github.com/woocommerce/woocommerce/pull/48453)
* Tweak - Adds a defensive check to reduce error log noise when regenerating images. [#47785](https://github.com/woocommerce/woocommerce/pull/47785)
* Tweak - Adds best practice advice to the API key generation screen. [#48483](https://github.com/woocommerce/woocommerce/pull/48483)
* Tweak - CYS - Update the copy for the intro tour. [#48202](https://github.com/woocommerce/woocommerce/pull/48202)
* Tweak - CYS: Refactor routing approach. [#48312](https://github.com/woocommerce/woocommerce/pull/48312)
* Tweak - Include 'original_post_status' in HPOS edit form. [#48196](https://github.com/woocommerce/woocommerce/pull/48196)
* Tweak - Minor improvements to BlockTemplatesController instantiation [#48107](https://github.com/woocommerce/woocommerce/pull/48107)
* Tweak - Only load 'productCount' and 'experimentalBlocksEnabled' settings in admin [#48152](https://github.com/woocommerce/woocommerce/pull/48152)
* Tweak - Product Editor: Skip momentarily the 'can create a variation option and publish the product' E2E test [#47618](https://github.com/woocommerce/woocommerce/pull/47618)
* Tweak - Remove checkstyle.xml file [#47844](https://github.com/woocommerce/woocommerce/pull/47844)
* Tweak - Remove unused woocommerce_task_list_prompt_shown option [#48304](https://github.com/woocommerce/woocommerce/pull/48304)
* Tweak - Update coming soon banner text to use translation function [#47742](https://github.com/woocommerce/woocommerce/pull/47742)
* Tweak - Update LYS survey completion track props [#47985](https://github.com/woocommerce/woocommerce/pull/47985)
* Tweak - Update printful copy. [#48626](https://github.com/woocommerce/woocommerce/pull/48626)
* Tweak - Update WC blocks e2e tests to WordPress 6.6 [#48436](https://github.com/woocommerce/woocommerce/pull/48436)
* Tweak - Verify if the coming soon cache is displayed when launching the store and alerts the user if it is still present. [#48586](https://github.com/woocommerce/woocommerce/pull/48586)
* Performance - Add DISTINCT keyword for smaller response and performance. [#48139](https://github.com/woocommerce/woocommerce/pull/48139)
* Performance - CYS - Optimize the `Choose a professionally designed theme` intro page image. [#48566](https://github.com/woocommerce/woocommerce/pull/48566)
* Performance - Replaced `classnames` package with the faster and smaller `clsx` package. [#47760](https://github.com/woocommerce/woocommerce/pull/47760)
* Performance - Revert changing the title of the edit comments screen when editing a review. [#48485](https://github.com/woocommerce/woocommerce/pull/48485)
* Enhancement - Accessibility enhancement for the whole shop accounts section [#47144](https://github.com/woocommerce/woocommerce/pull/47144)
* Enhancement - Add information about block/shortcode/template usage on Cart and Checkout pages to the WC system report. [#48300](https://github.com/woocommerce/woocommerce/pull/48300)
* Enhancement - CYS: add shuffle feature. [#47356](https://github.com/woocommerce/woocommerce/pull/47356)
* Enhancement - CYS: allow to the user to move the pattern. [#47322](https://github.com/woocommerce/woocommerce/pull/47322)
* Enhancement - Enhancement editor loading speed [#47425](https://github.com/woocommerce/woocommerce/pull/47425)
* Enhancement - Handle core profiler get countries error [#48317](https://github.com/woocommerce/woocommerce/pull/48317)
* Enhancement - If a variable product doesn't have a Product Image but variations do have images, the zoom and flex slider will be initiated as expected [#47714](https://github.com/woocommerce/woocommerce/pull/47714)
* Enhancement - Improve spacing between steps in the Checkout block on mobile and desktop [#47565](https://github.com/woocommerce/woocommerce/pull/47565)
* Enhancement - Increase connection timeout to 30 seconds for the requests in WCCOM connection flow [#47842](https://github.com/woocommerce/woocommerce/pull/47842)
* Enhancement - Limit coming soon options API call to home screen [#48303](https://github.com/woocommerce/woocommerce/pull/48303)
* Enhancement - Modified order status tooltip labels [#47861](https://github.com/woocommerce/woocommerce/pull/47861)
* Enhancement - Optimize text wrapping for wc admin pages [#48131](https://github.com/woocommerce/woocommerce/pull/48131)
* Enhancement - Remove the previous product management experience [#47814](https://github.com/woocommerce/woocommerce/pull/47814)
= 9.0.2 2024-06-24 =
**WooCommerce**
* Fix - Revert 46857 to preserve backcompat with earlier WC versions. [#48753](https://github.com/woocommerce/woocommerce/pull/48753)
= 9.0.1 2024-06-20 =
**WooCommerce**
* Fix - Check WC_Legacy_API instead of WC_API as that seems to be cached by lightspeed servers. [#48593](https://github.com/woocommerce/woocommerce/pull/48593)
= 9.0.0 2024-06-18 =
**WooCommerce**
* Security - Prevent HTML & JS injection attacks on registration and checkout forms when the Order Attribution is enabled. [#48348](https://github.com/woocommerce/woocommerce/pull/48348)
* Update - Toggle LYS feature flag off for 9.0 [#48231](https://github.com/woocommerce/woocommerce/pull/48231)
* Fix - Fix settings-api textarea validation to prevent insertion of iframes in description areas by default [#48432](https://github.com/woocommerce/woocommerce/pull/48432)
* Fix - #47626 changed the classes on the legacy admin settings save button and broke saving standard tax rates [#48201](https://github.com/woocommerce/woocommerce/pull/48201)
* Fix - Revert "Remove customer-effort-score-tracks" feature flag #48235 [#48235](https://github.com/woocommerce/woocommerce/pull/48235)
* Fix - Fix db update notice redirection bug where it redirects without checking for db update action. [#48163](https://github.com/woocommerce/woocommerce/pull/48163)
* Fix - Add missing URL to discover more link in LYS tour [#48109](https://github.com/woocommerce/woocommerce/pull/48109)
* Fix - Fix: "On Sale" collection isn't displaying on Editor side [#47994](https://github.com/woocommerce/woocommerce/pull/47994)
* Fix - Make the plugin autoinstall process more robust [#47798](https://github.com/woocommerce/woocommerce/pull/47798)
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
* Fix - Fix: Product Collection block does not display properly when editing template/post [#47871](https://github.com/woocommerce/woocommerce/pull/47871)
* Fix - Added useRef to ensure confetti animation is only run once [#47838](https://github.com/woocommerce/woocommerce/pull/47838)
* Fix - Fixed a fatal error when programmatically using the WC_Discounts::class in a context where no cart exists. [#47589](https://github.com/woocommerce/woocommerce/pull/47589)
* Fix - Fixed shipping flat price change to free shipping when shipping cost represented using decimal separators and thousands separators. [#46857](https://github.com/woocommerce/woocommerce/pull/46857)
* Fix - Product Collection: fix the incompatibility of Query Pagination block with Product Collection [#47749](https://github.com/woocommerce/woocommerce/pull/47749)
* Fix - Add missing line item data to the REST API refunds schema in versions 2 and 3 [#47254](https://github.com/woocommerce/woocommerce/pull/47254)
* Fix - Allow category and tag taxonomy filters on the Product Collection block to see more than the first 10. [#47155](https://github.com/woocommerce/woocommerce/pull/47155)
* Fix - A number of e2e fixes for flaky tests [#47562](https://github.com/woocommerce/woocommerce/pull/47562)
* Fix - Avoid a warning in PHP 8.3 with some edge case uses of array_sum [#47595](https://github.com/woocommerce/woocommerce/pull/47595)
* Fix - Blocks: Fix JS unit tests [#47516](https://github.com/woocommerce/woocommerce/pull/47516)
* Fix - Catch NotFoundException before woocommerce_get_batch_processor [#46975](https://github.com/woocommerce/woocommerce/pull/46975)
* Fix - Check each pacakge's chosen shipping rate against each valid rate for that package [#47716](https://github.com/woocommerce/woocommerce/pull/47716)
* Fix - Correctly clear out state and postcode when switching countries. [#47369](https://github.com/woocommerce/woocommerce/pull/47369)
* Fix - Correctly load up Cart/Checkout translations on Windows machines. [#47625](https://github.com/woocommerce/woocommerce/pull/47625)
* Fix - CYS: fix color picker E2E test [#47274](https://github.com/woocommerce/woocommerce/pull/47274)
* Fix - Filter by Attribute: fix potential reading from undefined error [#47699](https://github.com/woocommerce/woocommerce/pull/47699)
* Fix - Fix: handle undefined templateSlug in Product Collection tracking utils [#47504](https://github.com/woocommerce/woocommerce/pull/47504)
* Fix - Fix: Product Collection Block Respects 'Out of stock visibility' Setting [#47537](https://github.com/woocommerce/woocommerce/pull/47537)
* Fix - Fix an accessibility error in the add to cart button template. [#46897](https://github.com/woocommerce/woocommerce/pull/46897)
* Fix - Fix an issue in which a warning is emitted when placing an order using Checkout block. [#47633](https://github.com/woocommerce/woocommerce/pull/47633)
* Fix - Fix broken checkout address forms layout [#47131](https://github.com/woocommerce/woocommerce/pull/47131)
* Fix - Fix broken db update button on homescreen [#47608](https://github.com/woocommerce/woocommerce/pull/47608)
* Fix - Fix core profiler email field is not positioned correctly in mobile screens [#47077](https://github.com/woocommerce/woocommerce/pull/47077)
* Fix - Fix coupon rest api returning 500 error on delete. [#47474](https://github.com/woocommerce/woocommerce/pull/47474)
* Fix - Fix dates dropdown in admin list order page where it wasn't filtered by order type. [#47440](https://github.com/woocommerce/woocommerce/pull/47440)
* Fix - FIxed a bug where shop page isn't recognized as a WooCommerce page when WordPress is installed in a subdirectory with permalink set to plain. [#46664](https://github.com/woocommerce/woocommerce/pull/46664)
* Fix - Fixes regarding authentication and non-existing products in the receipts rendering engine [#47300](https://github.com/woocommerce/woocommerce/pull/47300)
* Fix - Fix failed to load coming-soon resources [#47073](https://github.com/woocommerce/woocommerce/pull/47073)
* Fix - Fix flaky Attributes and Variations E2E tests #47471 [#47471](https://github.com/woocommerce/woocommerce/pull/47471)
* Fix - Fix GLA site verification with coming soon mode [#47140](https://github.com/woocommerce/woocommerce/pull/47140)
* Fix - Fix LYS site icon size [#47689](https://github.com/woocommerce/woocommerce/pull/47689)
* Fix - Fix missing shipping-recommendation task [#47117](https://github.com/woocommerce/woocommerce/pull/47117)
* Fix - Fix orders search filter position in admin list table. [#47640](https://github.com/woocommerce/woocommerce/pull/47640)
* Fix - fix PHP 8 warning in Blocks\Installer\create_pages() callback when other filters have removed pages [#47094](https://github.com/woocommerce/woocommerce/pull/47094)
* Fix - Fix registration of plugin on woocommerce.com if plugin is already active on site. [#46780](https://github.com/woocommerce/woocommerce/pull/46780)
* Fix - Fix RIN Display Logic for Banner Alert Notifications [#47167](https://github.com/woocommerce/woocommerce/pull/47167)
* Fix - Fix tasklist_payments_options to record only shown payment gateways [#47713](https://github.com/woocommerce/woocommerce/pull/47713)
* Fix - Fix Tax reports not showing correct totals in analytics [#46248](https://github.com/woocommerce/woocommerce/pull/46248)
* Fix - Fix undefined variable $exlude_paths [#47490](https://github.com/woocommerce/woocommerce/pull/47490)
* Fix - Fix warnings from using fse theme json filter [#47631](https://github.com/woocommerce/woocommerce/pull/47631)
* Fix - Fix width of the button to set the variation prices #47682 [#47682](https://github.com/woocommerce/woocommerce/pull/47682)
* Fix - Fix `admin_url` usage in Task List button links for Customize Store and Launch Your Store tasks. [#47010](https://github.com/woocommerce/woocommerce/pull/47010)
* Fix - Make REST order queries involving 'customer' field compatible with HPOS in v2 API. [#46878](https://github.com/woocommerce/woocommerce/pull/46878)
* Fix - Prevent empty checkboxes added by Additional Checkout Fields API showing in the order confirmation when the order was placed using the shortcode checkout experience. [#47333](https://github.com/woocommerce/woocommerce/pull/47333)
* Fix - Prevent product from being saved prematurely when updated via REST API [#46674](https://github.com/woocommerce/woocommerce/pull/46674)
* Fix - Product Collection - Fix edge cases in Price Range filter around value parsing [#47354](https://github.com/woocommerce/woocommerce/pull/47354)
* Fix - Reenable global styles in coming soon entire site template [#47388](https://github.com/woocommerce/woocommerce/pull/47388)
* Fix - Replace forward slashes in additional fields IDs with hyphens. [#47650](https://github.com/woocommerce/woocommerce/pull/47650)
* Fix - Select the first shipping rate when local pickup is enabled and customer switches to shipping. [#47120](https://github.com/woocommerce/woocommerce/pull/47120)
* Fix - Show Germany state field in Checkout block. [#47319](https://github.com/woocommerce/woocommerce/pull/47319)
* Fix - Switch marketplace promotions from Action Scheduler to transient. [#47262](https://github.com/woocommerce/woocommerce/pull/47262)
* Fix - Update color contrast for Checkout fields. Update color contrast for notice buttons. Return focus when closing mini cart drawer. Return focus when closing shipping calculator. [#47470](https://github.com/woocommerce/woocommerce/pull/47470)
* Fix - Update coupon_usage for failed & trashed orders. [#47125](https://github.com/woocommerce/woocommerce/pull/47125)
* Fix - Update delete item meta query to format supported by wpdb::get_table_from_query() [#46692](https://github.com/woocommerce/woocommerce/pull/46692)
* Fix - Updated the PHP DocBlock for WC_CLI_Runner::register_route_commands to correctly specify the first parameter's type as being a WC_CLI_REST_Command object rather than a string. [#47599](https://github.com/woocommerce/woocommerce/pull/47599)
* Fix - Update the version number of some methods in AbstractTemplateCompatibility [#47118](https://github.com/woocommerce/woocommerce/pull/47118)
* Fix - Use the default ordering of states in the Checkout block [#46656](https://github.com/woocommerce/woocommerce/pull/46656)
* Fix - [CYS]: Fix event name when starting the no-AI flow. [#47181](https://github.com/woocommerce/woocommerce/pull/47181)
* Fix - [CYS] Fix bug making the AI flow fail on the same prompt. [#46872](https://github.com/woocommerce/woocommerce/pull/46872)
* Add - Show notice for expiring subscriptions [#47004](https://github.com/woocommerce/woocommerce/pull/47004)
* Add - Add custom fonts via wp_theme_json_data_theme filter for coming soon pages [#47417](https://github.com/woocommerce/woocommerce/pull/47417)
* Add - Added phone number field to Shipping Address form under My Account [#47062](https://github.com/woocommerce/woocommerce/pull/47062)
* Add - Added the `wc_product_pre_has_unique_sku` filter hook to allow SKU uniqueness to be determined externally [#46763](https://github.com/woocommerce/woocommerce/pull/46763)
* Add - Add filter to convert WooCommerce slug for plugin dependencies [#46707](https://github.com/woocommerce/woocommerce/pull/46707)
* Add - Add hooks to customize the order receipt generation [#46224](https://github.com/woocommerce/woocommerce/pull/46224)
* Add - Adds 3 additional UTM parameters recognized in GA4 documentation. [#47639](https://github.com/woocommerce/woocommerce/pull/47639)
* Add - Adds a wc/v3/refunds REST API endpoint so refunds can be queried collectively, unconnected to their orders [#46895](https://github.com/woocommerce/woocommerce/pull/46895)
* Add - Add tracks events for the LYS badge [#46509](https://github.com/woocommerce/woocommerce/pull/46509)
* Add - Allow HPOS CLI cleanup tool to remove metadata for deleted orders. [#46970](https://github.com/woocommerce/woocommerce/pull/46970)
* Add - Append coming soon tracks prop globally [#47644](https://github.com/woocommerce/woocommerce/pull/47644)
* Add - Comment: Added feature to redirect back to LYS from CYS if the referrer is LYS. [#47654](https://github.com/woocommerce/woocommerce/pull/47654)
* Add - Filter to apply Base64 encoding to order attribution cookies [#47597](https://github.com/woocommerce/woocommerce/pull/47597)
* Add - For shared subscriptions, My Subscriptions now shows "Shared with you" and the email address of the person who shared it with you. [#46229](https://github.com/woocommerce/woocommerce/pull/46229)
* Add - New product editor: Add 'placeholder' attribute to category field in Simple Product Template [#46938](https://github.com/woocommerce/woocommerce/pull/46938)
* Add - Product Collection: Add preview mode on Editor side [#46369](https://github.com/woocommerce/woocommerce/pull/46369)
* Add - Product Collection: collections that don't inherit query from template are non-filterable [#45820](https://github.com/woocommerce/woocommerce/pull/45820)
* Add - REST API: Add product variation type to response when getting variations. [#47377](https://github.com/woocommerce/woocommerce/pull/47377)
* Add - Return HPOS edit links for calls to `get_edit_post_link()` on placeholder posts. [#47149](https://github.com/woocommerce/woocommerce/pull/47149)
* Add - Show an message in the plugin table list for WooCommerce extensions that are either expired or expiring. [#47076](https://github.com/woocommerce/woocommerce/pull/47076)
* Add - Show the label for freemium products in the in-app marketpalce [#45982](https://github.com/woocommerce/woocommerce/pull/45982)
* Update - Add a new update function for WC 9.0 to add woocommerce_show_lys_tour option. [#47634](https://github.com/woocommerce/woocommerce/pull/47634)
* Update - Add aria-label to customer account block link when in icon-only display mode. [#46899](https://github.com/woocommerce/woocommerce/pull/46899)
* Update - Add busy animation for save button in settings screen [#47626](https://github.com/woocommerce/woocommerce/pull/47626)
* Update - Added `padding-left: 15px` to the copy link to provide equal spacing around the button [#47313](https://github.com/woocommerce/woocommerce/pull/47313)
* Update - Add UTM tags to all product links in core profiler Free features step [#47397](https://github.com/woocommerce/woocommerce/pull/47397)
* Update - Always show pickup location address and details and truncate pickup details. [#47173](https://github.com/woocommerce/woocommerce/pull/47173)
* Update - Applies `wp_plugin_dependencies_slug` filter to get the correct plugin name in PluginVersionRuleProcessor. [#47235](https://github.com/woocommerce/woocommerce/pull/47235)
* Update - Avoid writing an empty line to a log file if the log entry is empty. [#47091](https://github.com/woocommerce/woocommerce/pull/47091)
* Update - Changed the Form Step blocks in the Checkout block so that the step nunbers cannot be turned off individually. This is now a global setting on the Checkout Fields block that will affect all child blocks. The FormStep component in the checkout pacakge remains unchanged. [#47479](https://github.com/woocommerce/woocommerce/pull/47479)
* Update - Classic Templates: Renamed blocks representing classic templates from "WooCommerce XYZ Block" to "XYZ (Classic)" [#44931](https://github.com/woocommerce/woocommerce/pull/44931)
* Update - Disable auto zoom when focusing on input for core profile [#47400](https://github.com/woocommerce/woocommerce/pull/47400)
* Update - Display the total price in the place order button. [#47083](https://github.com/woocommerce/woocommerce/pull/47083)
* Update - Display `Import Product` task item text and header copies when merchant indicates they are already selling [#47164](https://github.com/woocommerce/woocommerce/pull/47164)
* Update - Do not display smart app banner on the core profiler, LYS, and CYS pages [#47429](https://github.com/woocommerce/woocommerce/pull/47429)
* Update - Ensure the woocommerce_format_log_entry filter hook still has access to the log source value [#46851](https://github.com/woocommerce/woocommerce/pull/46851)
* Update - Hide progress bar when entering Design With AI from Entrepreneur signup flow. [#47574](https://github.com/woocommerce/woocommerce/pull/47574)
* Update - Improvements to HPOS settings screen. [#47370](https://github.com/woocommerce/woocommerce/pull/47370)
* Update - In blocks migrate `@wordpress/components` Button to Ariakit, replace `__experimentalRadio/RadioGroup` with Ariakit Button. [#45974](https://github.com/woocommerce/woocommerce/pull/45974)
* Update - Inject order attribution checkout fields (only once) on a wider set of checkout form actions. [#46834](https://github.com/woocommerce/woocommerce/pull/46834)
* Update - LYS - Use flow layout for the coming soon template [#47335](https://github.com/woocommerce/woocommerce/pull/47335)
* Update - LYS: disables the "Save changes" button until changes are made. [#47316](https://github.com/woocommerce/woocommerce/pull/47316)
* Update - Make order attribution data globally accessible client side. [#46965](https://github.com/woocommerce/woocommerce/pull/46965)
* Update - Move country to be the first field in Checkout block. [#47375](https://github.com/woocommerce/woocommerce/pull/47375)
* Update - Product Collection: remove Beta label [#47572](https://github.com/woocommerce/woocommerce/pull/47572)
* Update - Redirect old market-place to new market-place pages [#47276](https://github.com/woocommerce/woocommerce/pull/47276)
* Update - Refactor coming soon entire page to wrap under cover block [#46914](https://github.com/woocommerce/woocommerce/pull/46914)
* Update - Remove admin toolbar button hover background [#47314](https://github.com/woocommerce/woocommerce/pull/47314)
* Update - Remove noindex robot call from lys coming soon pages [#47178](https://github.com/woocommerce/woocommerce/pull/47178)
* Update - Remove title from checkout page [#47529](https://github.com/woocommerce/woocommerce/pull/47529)
* Update - Rename and sort filter options in "Add a filter" in Analytics. [#46955](https://github.com/woocommerce/woocommerce/pull/46955)
* Update - Replace div element with main in block templates [#47119](https://github.com/woocommerce/woocommerce/pull/47119)
* Update - Replace Options API usage with LaunchYourStore endpoint for LYS [#47252](https://github.com/woocommerce/woocommerce/pull/47252)
* Update - Replace the Legacy REST API with a stub that always returns an error [#40627](https://github.com/woocommerce/woocommerce/pull/40627)
* Update - Replace the `Testimonials single` default image. [#47535](https://github.com/woocommerce/woocommerce/pull/47535)
* Update - Return users to LYS after completing essential task [#47606](https://github.com/woocommerce/woocommerce/pull/47606)
* Update - Toggle LYS feature flag on for post-8.9 [#46853](https://github.com/woocommerce/woocommerce/pull/46853)
* Update - Update @automattic/tour-kit to 1.1.3 and @automattic/components to 2.1.1 [#47129](https://github.com/woocommerce/woocommerce/pull/47129)
* Update - Update delivery titles & pickup options; refine shipping selector layout & address field visibility in the Checkout block. [#46083](https://github.com/woocommerce/woocommerce/pull/46083)
* Update - Updated the toolbar's store link based on the site's visibility settings. [#47315](https://github.com/woocommerce/woocommerce/pull/47315)
* Update - Update experiment name for the new product editing screen. [#47647](https://github.com/woocommerce/woocommerce/pull/47647)
* Update - Update Octokit from 2.1.0 to 3.1.2 and updated variable names [#42891](https://github.com/woocommerce/woocommerce/pull/42891)
* Update - Update the display logic of company name, address line 2 and phone number fields and allow making the address line 2 field required. [#47160](https://github.com/woocommerce/woocommerce/pull/47160)
* Update - Update the WooCommerce Status page to use the full plugin version to show dev, Beta and RC versions as opposed to only the milestone number [#46906](https://github.com/woocommerce/woocommerce/pull/46906)
* Update - Use the term attribute sort order for displaying the "Filter by attribute" terms. [#47616](https://github.com/woocommerce/woocommerce/pull/47616)
* Update - Use Woo branded colors for LYS confetti [#47334](https://github.com/woocommerce/woocommerce/pull/47334)
* Update - When the total shipping cost is 0 the order summary in the Cart and Checkout blocks shows "FREE" instead of 0.00 [#47553](https://github.com/woocommerce/woocommerce/pull/47553)
* Update - Woocommerce: update code to data TS changes [#46907](https://github.com/woocommerce/woocommerce/pull/46907)
* Update - Wrap LYS private link exclusion logic in an overall check [#47690](https://github.com/woocommerce/woocommerce/pull/47690)
* Dev - Add a feature flag for full composability and the Pattern Toolkit [#47392](https://github.com/woocommerce/woocommerce/pull/47392)
* Dev - Add an API test for the new refunds endpoint [#47340](https://github.com/woocommerce/woocommerce/pull/47340)
* Dev - Add Buildkite test reporting (sanitized) back using production account [#47558](https://github.com/woocommerce/woocommerce/pull/47558)
* Dev - Add help text under "Include downloads" toggle #46752 [#46752](https://github.com/woocommerce/woocommerce/pull/46752)
* Dev - Add new disableHpos test env variable in ci-job utility [#47619](https://github.com/woocommerce/woocommerce/pull/47619)
* Dev - Add Playwright performance tests to Product editor #47590 [#47590](https://github.com/woocommerce/woocommerce/pull/47590)
* Dev - Add the optional property for ci jobs [#47261](https://github.com/woocommerce/woocommerce/pull/47261)
* Dev - Block E2E: Eliminate side effects through improved test isolation [#46125](https://github.com/woocommerce/woocommerce/pull/46125)
* Dev - Blocks E2E: Align ESlint and TS configs with Gutenberg & fix flaky tests. [#47228](https://github.com/woocommerce/woocommerce/pull/47228)
* Dev - Blocks E2E: Fix flaky block insertion tests [#47213](https://github.com/woocommerce/woocommerce/pull/47213)
* Dev - Blocks E2E: Fix flaky Product Collection tests [#47211](https://github.com/woocommerce/woocommerce/pull/47211)
* Dev - Blocks E2E: Refactor Playwright configs and CI workflow [#46409](https://github.com/woocommerce/woocommerce/pull/46409)
* Dev - Blocks E2E: Remove discouraged waitForTimeout from tests [#47214](https://github.com/woocommerce/woocommerce/pull/47214)
* Dev - Blocks E2E: Remove obsolete waitForSiteEditorFinishLoading utility. [#47547](https://github.com/woocommerce/woocommerce/pull/47547)
* Dev - Blocks E2E: Remove the DB snapshot on env reset [#47416](https://github.com/woocommerce/woocommerce/pull/47416)
* Dev - Blocks E2E: Wait for Site Editor canvas loader in the `enterEditMore()` utility. [#47541](https://github.com/woocommerce/woocommerce/pull/47541)
* Dev - Disable pre-publish panel #47430 [#47430](https://github.com/woocommerce/woocommerce/pull/47430)
* Dev - E2E tests: disable broken tests [#47268](https://github.com/woocommerce/woocommerce/pull/47268)
* Dev - E2E tests: disable woocommerce_coming_soon during test environment setup [#47024](https://github.com/woocommerce/woocommerce/pull/47024)
* Dev - E2E tests: don't exit if the consumer token was not cleared in teardown [#47020](https://github.com/woocommerce/woocommerce/pull/47020)
* Dev - E2E tests: fix cleanup of created test pages and migrate to using fixtures [#46944](https://github.com/woocommerce/woocommerce/pull/46944)
* Dev - E2E tests: fixing flaky admin marketing test [#47665](https://github.com/woocommerce/woocommerce/pull/47665)
* Dev - E2E tests: more fixes for tests with Gutenberg active [#46861](https://github.com/woocommerce/woocommerce/pull/46861)
* Dev - Fix for a couple of flaky e2e tests [#47253](https://github.com/woocommerce/woocommerce/pull/47253)
* Dev - Load e2e test helper (child) themes via .wp-json instead of via WP-CLI. [#47080](https://github.com/woocommerce/woocommerce/pull/47080)
* Dev - Monorepo utils: add support for github events in ci-jobs tool [#46922](https://github.com/woocommerce/woocommerce/pull/46922)
* Dev - Move pricing features to General tab #47435 [#47435](https://github.com/woocommerce/woocommerce/pull/47435)
* Dev - Move the PHP tests with WP nightly back into CI [#47568](https://github.com/woocommerce/woocommerce/pull/47568)
* Dev - pnpm-lock.yaml update [#47575](https://github.com/woocommerce/woocommerce/pull/47575)
* Dev - Product Collection: add tracking to Editor filters usage [#46545](https://github.com/woocommerce/woocommerce/pull/46545)
* Dev - Remove BuildKite reporter from e2e tests [#47449](https://github.com/woocommerce/woocommerce/pull/47449)
* Dev - Remove not used feature flags #47150 [#47150](https://github.com/woocommerce/woocommerce/pull/47150)
* Dev - Remove upload plugin test from daily reporting [#47067](https://github.com/woocommerce/woocommerce/pull/47067)
* Dev - Restore the playwright/no-hooks linter rule introduced in #46432 and accidentally removed in #47228. [#47500](https://github.com/woocommerce/woocommerce/pull/47500)
* Dev - This PR fixes a minor typo in the Exposing your data in the Store API doc. [#43488](https://github.com/woocommerce/woocommerce/pull/43488)
* Dev - Try out the BuildKite Test Analytics in CI [#47202](https://github.com/woocommerce/woocommerce/pull/47202)
* Dev - Update cart/checkout usage of the @wordpress/components Slot Fill [#47105](https://github.com/woocommerce/woocommerce/pull/47105)
* Dev - Update docs about blocks styling to clearly state global styles are the recommended approach [#47269](https://github.com/woocommerce/woocommerce/pull/47269)
* Dev - Update events that should trigger the test job(s) [#47612](https://github.com/woocommerce/woocommerce/pull/47612)
* Dev - Update fast-xml-parser from 4.2.4 to 4.2.5 [#41982](https://github.com/woocommerce/woocommerce/pull/41982)
* Dev - Update pnpm to 9.1.0 [#47385](https://github.com/woocommerce/woocommerce/pull/47385)
* Dev - Update the Blocks JS tests to React 18 [#47383](https://github.com/woocommerce/woocommerce/pull/47383)
* Tweak - Add aria-label to account page [#43696](https://github.com/woocommerce/woocommerce/pull/43696)
* Tweak - Correct the close days for the flaky test stalebot workflow [#47484](https://github.com/woocommerce/woocommerce/pull/47484)
* Tweak - Fix coming soon page mobile UI issue [#47491](https://github.com/woocommerce/woocommerce/pull/47491)
* Tweak - Make sure "Change Address" button in Cart block is accessible. [#47460](https://github.com/woocommerce/woocommerce/pull/47460)
* Tweak - Reduce the number of tags in the plugin's readme file, to avoid warnings. [#47688](https://github.com/woocommerce/woocommerce/pull/47688)
* Tweak - Remove "Powered by WooCommerce" footer [#47075](https://github.com/woocommerce/woocommerce/pull/47075)
* Tweak - Removed bold highlight from selected payment method in the Checkout block. This has been replaced with a border highlight. [#47412](https://github.com/woocommerce/woocommerce/pull/47412)
* Tweak - Remove repetitive words [#47158](https://github.com/woocommerce/woocommerce/pull/47158)
* Tweak - Remove unused order type registration property. [#46843](https://github.com/woocommerce/woocommerce/pull/46843)
* Tweak - Respect locale settings when formatting order counts in admin orders page. [#47373](https://github.com/woocommerce/woocommerce/pull/47373)
* Tweak - Update core profiler industry list [#47605](https://github.com/woocommerce/woocommerce/pull/47605)
* Tweak - Update the order summary on Cart & Checkout with some minor visual changes [#45767](https://github.com/woocommerce/woocommerce/pull/45767)
* Tweak - Update XPF currency symbol to XPF and change its default formatting. [#46960](https://github.com/woocommerce/woocommerce/pull/46960)
* Tweak - Use a real em dash character (—) in the coming soon template [#47394](https://github.com/woocommerce/woocommerce/pull/47394)
* Tweak - Use site editor to set fonts, set default fonts to cardo and inter, add type safety for filter [#47613](https://github.com/woocommerce/woocommerce/pull/47613)
* Performance - Add experimental support for FTS indexes in HPOS. Additionally, revert existing HPOS search queries to use post like structure. [#46130](https://github.com/woocommerce/woocommerce/pull/46130)
* Performance - Add index on SKU filed in wc_product_meta_lookup table [#47051](https://github.com/woocommerce/woocommerce/pull/47051)
* Performance - Create a hook to filter the woocommerce blocks that can be registered [#47066](https://github.com/woocommerce/woocommerce/pull/47066)
* Performance - CYS > Ensure get_patterns_ai_data_post is triggered only if AI enabled and improve performance. [#46999](https://github.com/woocommerce/woocommerce/pull/46999)
* Performance - Free some in-memory usage when doing batch processing in HPOS [#47159](https://github.com/woocommerce/woocommerce/pull/47159)
* Performance - HPOS - Made the query for retrieving meta keys more performant [#46985](https://github.com/woocommerce/woocommerce/pull/46985)
* Performance - Remove duplicate css code from any woo scss file [#47122](https://github.com/woocommerce/woocommerce/pull/47122)
* Enhancement - Add Business Services to the Extensions catalogue [#47303](https://github.com/woocommerce/woocommerce/pull/47303)
* Enhancement - Added a "manual_update" parameter to the Orders REST API endpoint that will make it so that, when set to "true", status changes to an order will be attributed to a specific user in the order notes. [#46900](https://github.com/woocommerce/woocommerce/pull/46900)
* Enhancement - Add support for FI and SE postcode validation [#45480](https://github.com/woocommerce/woocommerce/pull/45480)
* Enhancement - Add support for insert_or_update for better concurrency. [#47610](https://github.com/woocommerce/woocommerce/pull/47610)
* Enhancement - Add woocommerce_order_received_verify_known_shoppers filter to Order Confirmation template [#46957](https://github.com/woocommerce/woocommerce/pull/46957)
* Enhancement - Enable guest users to visualize shipping/billing information on the order received page. [#47477](https://github.com/woocommerce/woocommerce/pull/47477)
* Enhancement - Fix: Reset password form missing required indicator [#47229](https://github.com/woocommerce/woocommerce/pull/47229)
* Enhancement - Improved readability and better UX for GitHub bug reports, by optimizing the format of the SSR [#47088](https://github.com/woocommerce/woocommerce/pull/47088)
* Enhancement - Mark the checkout block sidebar as sticky on desktop. [#47376](https://github.com/woocommerce/woocommerce/pull/47376)
* Enhancement - Replace copy: List Price to Regular Price [#47658](https://github.com/woocommerce/woocommerce/pull/47658)
= 8.9.3 2024-06-10 =
**WooCommerce**
* Security - Prevent HTML & JS injection attacks on registration and checkout forms when the Order Attribution is enabled. [#48348](https://github.com/woocommerce/woocommerce/pull/48348)
= 8.9.2 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.9.1 2024-05-21 =
**WooCommerce**
* Fix - Automated installation of the Legacy REST API plugin should only happen once. After that, it must be installed manually (if needed). [#47563](https://github.com/woocommerce/woocommerce/pull/47563)
* Fix - Fixes a crash in the modal block editor when the Add button is clicked with Gutenberg 18.3.0 and later [#47561](https://github.com/woocommerce/woocommerce/pull/47561)
* Fix - Fix warning when loading guest sessions from previous sessions. [#47514](https://github.com/woocommerce/woocommerce/pull/47514)
* Fix - Prevent calling woocommerce.com on empty update-check and update-check-public payload. [#47507](https://github.com/woocommerce/woocommerce/pull/47507)
= 8.9.0 2024-05-14 =
**WooCommerce**
@ -245,6 +749,20 @@
* Enhancement - Various UX improvements in HPOS CLI cleanup tool. [#45322](https://github.com/woocommerce/woocommerce/pull/45322)
= 8.8.5 2024-06-10 =
**WooCommerce**
* Security - Prevent HTML & JS injection attacks on registration and checkout forms when the Order Attribution is enabled. [#48348](https://github.com/woocommerce/woocommerce/pull/48348)
= 8.8.4 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.8.3 2024-04-29 =
* Update - Reverts auto-injecting specific Woo Blocks in every block theme and restores only auto-injecting in themes found in the allow list. [#46935](https://github.com/woocommerce/woocommerce/pull/46935)
@ -453,6 +971,14 @@
* Enhancement - Remove 'List price' and 'Sale price' fields from the General tab [#45495](https://github.com/woocommerce/woocommerce/pull/45495)
* Enhancement - Validate coupons with email restrictions upfront and change user's feedback when a coupon is not valid for the user. [#43872](https://github.com/woocommerce/woocommerce/pull/43872)
= 8.7.1 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.7.0 2024-03-01 =
**WooCommerce**
@ -647,6 +1173,13 @@
* Enhancement - Product Collection: enable "Sync with query" option by default only for the first Product Catalog instance, disable for 2nd and next ones [#44577](https://github.com/woocommerce/woocommerce/pull/44577)
= 8.6.2 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.6.1 2024-02-20 =
**WooCommerce**
@ -826,6 +1359,13 @@
* Enhancement - Update WooPayments task copy [#43365](https://github.com/woocommerce/woocommerce/pull/43365)
= 8.5.3 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.5.2 2024-01-25 =
**WooCommerce**
@ -988,6 +1528,13 @@
* Enhancement - Update the pattern imageSizing to single. [#42767](https://github.com/woocommerce/woocommerce/pull/42767)
= 8.4.1 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.4.0 2023-12-12 =
**WooCommerce**
@ -1136,6 +1683,13 @@
* Enhancement - Check if $data['billing_email'] is set in the create_order function. [#41098](https://github.com/woocommerce/woocommerce/pull/41098)
= 8.3.2 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.3.1 2023-11-21 =
**WooCommerce**
@ -1324,6 +1878,13 @@
* Enhancement - Hide "Preview" icon to other users when order is locked for edits. [#40730](https://github.com/woocommerce/woocommerce/pull/40730)
= 8.2.3 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.2.2 2023-11-08 =
**WooCommerce**
@ -1467,6 +2028,13 @@
* Enhancement - Update Venezuelan currency: Bolívar (Bs.). [#29380](https://github.com/woocommerce/woocommerce/pull/29380)
= 8.1.2 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.1.1 2023-09-18 =
**WooCommerce**
@ -1564,6 +2132,13 @@
* Enhancement - Update the admin's menu remaining tasks bubble CSS class and handling [#39273](https://github.com/woocommerce/woocommerce/pull/39273)
= 8.0.4 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 8.0.3 2023-08-29 =
* Update - Bump WooCommerce Blocks to 10.6.6. [#39853](https://github.com/woocommerce/woocommerce/pull/39853)
@ -1667,6 +2242,13 @@
* Enhancement - Refresh UX to enable HPOS to make it user friendly. [[#38993]](https://github.com/woocommerce/woocommerce/pull/38993)
= 7.9.1 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 7.9.0 2023-07-17 =
**WooCommerce**
@ -1801,6 +2383,13 @@
* Enhancement - Update product editor tour/guide copy and style. [[#38726]](https://github.com/woocommerce/woocommerce/pull/38726)
= 7.8.3 2024-06-04 =
**WooCommerce**
* Fix - Prevent tracking files from being enqueued on the front end. [#47938](https://github.com/woocommerce/woocommerce/pull/47938)
= 7.8.2 2023-07-04 =
**WooCommerce**

View File

@ -1,6 +1,6 @@
---
post_title: How to add a custom field to simple and variable products
menu_title: Add custom fields to products
menu_title: Add Custom Fields to Products
tags: how-to
---

View File

@ -0,0 +1,5 @@
---
category_title: Cart and Checkout Blocks
category_slug: cart-and-checkout-blocks
post_title: Cart and Checkout blocks - Extensibility
---

View File

@ -0,0 +1,725 @@
---
post_title: Cart and Checkout - Additional checkout fields
menu_title: Additional Checkout Fields
tags: reference
---
A common use-case for developers and merchants is to add a new field to the Checkout form to collect additional data about a customer or their order.
This document will outline the steps an extension should take to register some additional checkout fields.
## Available field locations
Additional checkout fields can be registered in three different places:
- Contact information
- Addresses (Shipping **and** Billing)
- Order information
A field can only be shown in one location, it is not possible to render the same field in multiple locations in the same registration.
### Contact information
The contact information section currently renders at the top of the form. It contains the `email` field and any other additional fields.
![Showing the contact information section with two fields rendered, email and an additional checkout field (optional)](https://github.com/woocommerce/woocommerce/assets/5656702/097c2596-c629-4eab-9604-577ee7a14cfe)
Fields rendered here will be saved to the shopper's account. They will be visible and editable render in the shopper's "Account details" section.
### Address
The "Address" section currently contains a form for the shipping address and the billing address. Additional checkout fields can be registered to appear within these forms.
![The shipping address form showing the additional checkout field at the bottom](https://github.com/woocommerce/woocommerce/assets/5656702/746d280f-3354-4d37-a78a-a2518eb0e5de)
Fields registered here will be saved to both the customer _and_ the order, so returning customers won't need to refill those values again.
If a field is registered in the `address` location it will appear in both the shipping **and** the billing address. It is not possible to have the field in only one of the addresses.
You will also end up collecting two values for this field, one for shipping and one for billing.
### Order information
As part of the additional checkout fields feature, the checkout block has a new inner block called the "Order information block".
This block is used to render fields that aren't part of the contact information or address information, for example it may be a "How did you hear about us" field or a "Gift message" field.
Fields rendered here will be saved to the order. They will not be part of the customer's saved address or account information. New orders will not have any previously used values pre-filled.
![The order information section containing an additional checkout field](https://github.com/woocommerce/woocommerce/assets/5656702/295b3048-a22a-4225-96b0-6b0371a7cd5f)
By default, this block will render as the last step in the Checkout form, however it can be moved using the Gutenberg block controls in the editor.
![The order information block in the post editor"](https://github.com/woocommerce/woocommerce/assets/5656702/05a3d7d9-b3af-4445-9318-443ae2c4d7d8)
## Accessing values
Additional fields are saved to individual meta keys in both the customer meta and order meta, you can access them using helper methods, or using the meta keys directly, we recommend using the helper methods, as they're less likely to change, can handle future migrations, and will support future enhancements (e.g. reading from different locations).
For address fields, two values are saved: one for shipping, and one for billing. If the customer has selected 'Use same address for billing` then the values will be the same, but still saved independently of each other.
For contact and order fields, only one value is saved per field.
### Helper methods
`CheckoutFields` provides a function to access values from both customers and orders, it's are `get_field_from_object`.
To access a customer billing and/or shipping value:
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$field_id = 'my-plugin-namespace/my-field';
$customer = wc()->customer; // Or new WC_Customer( $id )
$checkout_fields = Package::container()->get( CheckoutFields::class );
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
$my_customer_shipping_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'shipping' );
```
To access an order field:
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$field_id = 'my-plugin-namespace/my-field';
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$my_order_billing_field = $checkout_fields->get_field_from_object( $field_id, $order, 'billing' );
$my_order_shipping_field = $checkout_fields->get_field_from_object( $field_id, $order, 'shipping' );
```
After an order is placed, the data saved to the customer and the data saved to the order will be the same. Customers can change the values for _future_ orders, or from within their My Account page. If you're looking at a customer value at a specific point in time (i.e. when the order was placed), access it from the order object, if you're looking for the most up to date value regardless, access it from the customer object.
#### Guest customers
When a guest customer places an order with additional fields, those fields will be saved to its session, so as long as the customer still has a valid session going, the values will always be there.
#### Logged-in customers
For logged-in customers, the value is only persisted once they place an order. Accessing a logged-in customer object during the place order lifecycle will return null or stale data.
If you're at a place order hook, doing this will return previous data (not the currently inserted one):
```php
$customer = new WC_Customer( $order->customer_id ); // Or new WC_Customer( 1234 )
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
```
Instead, always access the latest data if you want to run some extra validation/data-moving:
```php
$customer = wc()->customer // This will return the current customer with its session.
$my_customer_billing_field = $checkout_fields->get_field_from_object( $field_id, $customer, 'billing' );
```
#### Accessing all fields
You can use `get_all_fields_from_object` to access all additional fields saved to an order or a customer.
```php
use Automattic\WooCommerce\Blocks\Package;
use Automattic\WooCommerce\Blocks\Domain\Services\CheckoutFields;
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' );
$order_additional_shipping_fields = $checkout_fields->get_all_fields_from_object( $order, 'shipping' );
$order_other_additional_fields = $checkout_fields->get_all_fields_from_object( $order, 'other' ); // Contact and Order are saved in the same place under the additional group.
```
This will return an array of all values, it will only include fields currently registered, if you want to include fields no longer registered, you can pass a third `true` parameter.
```php
$order = wc_get_order( 1234 );
$checkout_fields = Package::container()->get( CheckoutFields::class );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing' ); // array( 'my-plugin-namespace/my-field' => 'my-value' );
$order_additional_billing_fields = $checkout_fields->get_all_fields_from_object( $order, 'billing', true ); // array( 'my-plugin-namespace/my-field' => 'my-value', 'old-namespace/old-key' => 'old-value' );
```
### Accessing values directly
While not recommended, you can use the direct meta key to access certain values, this is useful for external engines or page/email builders who only provide access to meta values.
Values are saved under a predefined prefix, this is needed to able to query fields without knowing which ID the field was registered under, for a field with key `'my-plugin-namespace/my-field'`, it's meta key will be the following if it's an address field:
- `_wc_billing/my-plugin-namespace/my-field`
- `_wc_shipping/my-plugin-namespace/my-field`
Or the following if it's a contact/order field:
- `_wc_other/my-plugin-namespace/my-field`.
Those prefixes are part of `CheckoutFields` class, and can be accessed using the following constants:
```php
echo ( CheckoutFields::BILLING_FIELDS_PREFIX ); // _wc_billing/
echo ( CheckoutFields::SHIPPING_FIELDS_PREFIX ); // _wc_shipping/
echo ( CheckoutFields::OTHER_FIELDS_PREFIX ); // _wc_other/
```
`CheckoutFields` provides a couple of helpers to get the group name or key based on one or the other:
```php
CheckoutFields::get_group_name( "_wc_billing" ); // "billing"
CheckoutFields::get_group_name( "_wc_billing/" ); // "billing"
CheckoutFields::get_group_key( "shipping" ); // "_wc_shipping/"
```
Use cases here would be to build the key name to access the meta directly:
```php
$key = CheckoutFields::get_group_key( "other" ) . 'my-plugin/is-opt-in';
$opted_in = get_user_meta( 123, $key, true ) === "1" ? true : false;
```
#### Checkboxes values
When accessing a checkbox values directly, it will either return `"1"` for true, `"0"` for false, or `""` if the value doesn't exist, only the provided functions will sanitize that to a boolean.
## Supported field types
The following field types are supported:
- `select`
- `text`
- `checkbox`
There are plans to expand this list, but for now these are the types available.
## Using the API
To register additional checkout fields you must use the `woocommerce_register_additional_checkout_field` function.
It is recommended to run this function after the `woocommerce_init` action.
The registration function takes an array of options describing your field. Some field types take additional options.
### Options
#### General options
These options apply to all field types (except in a few circumstances which are noted inline).
| Option name | Description | Required? | Example | Default value |
|---------------------|-------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | The field's ID. This should be a unique identifier for your field. It is composed of a namespace and field name separated by a `/`. | Yes | `plugin-namespace/how-did-you-hear` | No default - this must be provided. |
| `label` | The label shown on your field. This will be the placeholder too. | Yes | `How did you hear about us?` | No default - this must be provided. |
| `optionalLabel` | The label shown on your field if it is optional. This will be the placeholder too. | No | `How did you hear about us? (Optional)` | The default value will be the value of `label` with `(optional)` appended. |
| `location` | The location to render your field. | Yes | `contact`, `address`, or `order` | No default - this must be provided. |
| `type` | The type of field you're rendering. It defaults to `text` and must match one of the supported field types. | No | `text`, `select`, or `checkbox` | `text` |
| `attributes` | An array of additional attributes to render on the field's input element. This is _not_ supported for `select` fields. | No | `[ 'data-custom-data' => 'my-custom-data' ]` | `[]` |
| `sanitize_callback` | A function called to sanitize the customer provided value when posted. | No | See example below | By default the field's value is returned unchanged. |
| `validate_callback` | A function called to validate the customer provided value when posted. This runs _after_ sanitization. | No | See example below | The default validation function will add an error to the response if the field is required and does not have a value. [See the default validation function.](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/Blocks/Domain/Services/CheckoutFields.php#L270-L281) |
##### Example of `sanitize_callback`. This function will remove spaces from the value <!-- omit from toc -->
```php
'sanitize_callback' => function( $field_value ) {
return str_replace( ' ', '', $field_value );
},
```
##### Example of `validate_callback`. This function will check if the value is an email <!-- omit from toc -->
```php
'validate_callback' => function( $field_value ) {
if ( ! is_email( $field_value ) ) {
return new WP_Error( 'invalid_alt_email', 'Please ensure your alternative email matches the correct format.' );
}
},
```
#### Options for `text` fields
As well as the options above, text fields also support a `required` option. If this is `true` then the shopper _must_ provide a value for this field during the checkout process.
| Option name | Description | Required? | Example | Default value |
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------|-----------|----------------------------------------------|---|
| `required` | If this is `true` then the shopper _must_ provide a value for this field during the checkout process. | No | `true` | `false` |
#### Options for `select` fields
As well as the options above, select fields must also be registered with an `options` option. This is used to specify what options the shopper can select.
Select fields will mount with no value selected by default, if the field is required, the user will be required to select a value.
You can set a placeholder to be shown on the select by passing a `placeholder` value when registering the field. This will be the first option in the select and will not be selectable if the field is required.
| Option name | Description | Required? | Example | Default value |
|-----|-----|-----|----------------|--------------|
| `options` | An array of options to show in the select input. Each options must be an array containing a `label` and `value` property. Each entry must have a unique `value`. Any duplicate options will be removed. The `value` is what gets submitted to the server during checkout and the `label` is simply a user-friendly representation of this value. It is not transmitted to the server in any way. | Yes | see below | No default - this must be provided. |
| `required` | If this is `true` then the shopper _must_ provide a value for this field during the checkout process. | No | `true` | `false` |
| `placeholder` | If this value is set, the shopper will see this option in the select. If the select is required, the shopper cannot select this option. | No | `Select a role | Select a $label |
##### Example of `options` value
```php
[
[
'value' => 'store_1',
'label' => 'Our London Store'
],
[
'value' => 'store_2',
'label' => 'Our Paris Store'
],
[
'value' => 'store_3',
'label' => 'Our New York Store'
]
]
```
#### Options for `checkbox` fields
The checkbox field type does not have any specific options, however `required` will always be `false` for a checkbox field. Making a checkbox field required is not supported.
### Attributes
Adding additional attributes to checkbox and text fields is supported. Adding them to select fields is **not possible for now**.
These attributes have a 1:1 mapping to the HTML attributes on `input` elements (except `pattern` on checkbox).
The supported attributes are:
- `data-*` attributes
- `aria-*` attributes
- `autocomplete`
- `autocapitalize`
- `pattern` (not supported on checkbox fields)
- `title`
- `maxLength` (equivalent to `maxlength` HTML attribute)
- `readOnly` (equivalent to `readonly` HTML attribute)
`maxLength` and `readOnly` are in camelCase because the attributes are rendered on a React element which must receive them in this format.
Certain attributes are not passed through to the field intentionally, these are `autofocus` and `disabled`. We are welcome to hear feedback and adjust this behaviour if valid use cases are provided.
## Usage examples
### Rendering a text field
This example demonstrates rendering a text field in the address section:
```php
add_action(
'woocommerce_init',
function() {
woocommerce_register_additional_checkout_field(
array(
'id' => 'namespace/gov-id',
'label' => 'Government ID',
'optionalLabel' => 'Government ID (optional)',
'location' => 'address',
'required' => true,
'attributes' => array(
'autocomplete' => 'government-id',
'aria-describedby' => 'some-element',
'aria-label' => 'custom aria label',
'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers.
'title' => 'Title to show on hover',
'data-custom' => 'custom data',
),
),
);
}
);
```
This results in the following address form (the billing form will be the same):
![The shipping address form with the Government ID field rendered at the bottom](https://github.com/woocommerce/woocommerce/assets/5656702/f6eb3c6f-9178-4978-8e74-e6b2ea353192)
The rendered markup looks like this:
```html
<input type="text" id="shipping-namespace-gov-id" autocapitalize="off"
autocomplete="government-id" aria-label="custom aria label"
aria-describedby="some-element" required="" aria-invalid="true"
title="Title to show on hover" pattern="[A-Z0-9]{5}"
data-custom="custom data" value="" >
```
### Rendering a checkbox field
This example demonstrates rendering a checkbox field in the contact information section:
```php
add_action(
'woocommerce_init',
function() {
woocommerce_register_additional_checkout_field(
array(
'id' => 'namespace/marketing-opt-in',
'label' => 'Do you want to subscribe to our newsletter?',
'location' => 'contact',
'type' => 'checkbox',
)
);
}
);
```
This results in the following contact information section:
![The contact information section with a newsletter subscription checkbox rendered inside it](https://github.com/woocommerce/woocommerce/assets/5656702/7444e41a-97cc-451d-b2c9-4eedfbe05724)
Note that because an `optionalLabel` was not supplied, the string `(optional)` is appended to the label. To remove that an `optionalLabel` property should be supplied to override this.
### Rendering a select field
This example demonstrates rendering a select field in the order information section:
```php
add_action(
'woocommerce_init',
function() {
woocommerce_register_additional_checkout_field(
array(
'id' => 'namespace/how-did-you-hear-about-us',
'label' => 'How did you hear about us?',
'placeholder' => 'Select a source',
'location' => 'order',
'type' => 'select',
'options' => [
[
'value' => 'google',
'label' => 'Google'
],
[
'value' => 'facebook',
'label' => 'Facebook'
],
[
'value' => 'friend',
'label' => 'From a friend'
],
[
'value' => 'other',
'label' => 'Other'
],
]
)
);
}
);
```
This results in the order information section being rendered like so:
### The select input before being focused
![The select input before being focused](https://github.com/woocommerce/woocommerce/assets/5656702/bbe17ad0-7c7d-419a-951d-315f56f8898a)
### The select input when focused
![The select input when focused](https://github.com/woocommerce/woocommerce/assets/5656702/bd943906-621b-404f-aa84-b951323e25d3)
If it is undesirable to force the shopper to select a value, mark the select as optional by setting the `required` option to `false`.
## Validation and sanitization
It is possible to add custom validation and sanitization for additional checkout fields using WordPress action hooks.
These actions happen in two places:
1. Updating and submitting the form during the checkout process and,
2. Updating address/contact information in the "My account" area.
### Sanitization
Sanitization is used to ensure the value of a field is in a specific format. An example is when taking a government ID, you may want to format it so that all letters are capitalized and there are no spaces. At this point, the value should **not** be checked for _validity_. That will come later. This step is only intended to set the field up for validation.
#### Using the `woocommerce_sanitize_additional_field` filter
To run a custom sanitization function for a field you can use the `sanitize_callback` function on registration, or the `woocommerce_sanitize_additional_field` filter.
| Argument | Type | Description |
|--------------|-------------------|-------------------------------------------------------------------------|
| `$field_value` | `boolean\|string` | The value of the field. |
| `$field_key` | `string` | The ID of the field. This is the same ID the field was registered with. |
##### Example of sanitization
This example shows how to remove whitespace and capitalize all letters in the example Government ID field we added above.
```php
add_action(
'woocommerce_sanitize_additional_field',
function ( $field_value, $field_key ) {
if ( 'namespace/gov-id' === $field_key ) {
$field_value = str_replace( ' ', '', $field_key );
$field_value = strtoupper( $field_value );
}
return $field_value;
},
10,
2
);
```
### Validation
There are two phases of validation in the additional checkout fields system. The first is validating a single field based on its key and value.
#### Single field validation
##### Using the `woocommerce_validate_additional_field` action
When the `woocommerce_validate_additional_field` action is fired the callback receives the field's key, the field's value, and a `WP_Error` object.
To add validation errors to the response, use the [`WP_Error::add`](https://developer.wordpress.org/reference/classes/wp_error/add/) method.
| Argument | Type | Description |
|--------------|-------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. |
| `$field_key` | `string` | The id of the field. This is the ID the field was registered with. |
| `$field_value` | `boolean\|string` | The value of the field |
###### The `WP_Error` object
When adding your error to the `WP_Error` object, it should have a unique error code. You may want to prefix the error code with the plugin namespace to reduce the chance of collision. Using codes that are already in use across other plugins may result in the error message being overwritten or showing in a different location.
###### Example of single-field validation
The below example shows how to apply custom validation to the `namespace/gov-id` text field from above. The code here ensures the field is made up of 5 characters, either upper-case letters or numbers. The sanitization function from the example above ensures that all whitespace is removed and all letters are capitalized, so this check is an extra safety net to ensure the input matches the pattern.
```php
add_action(
'woocommerce_validate_additional_field',
function ( WP_Error $errors, $field_key, $field_value ) {
if ( 'namespace/gov-id' === $field_key ) {
$match = preg_match( '/[A-Z0-9]{5}/', $field_value );
if ( 0 === $match || false === $match ) {
$errors->add( 'invalid_gov_id', 'Please ensure your government ID matches the correct format.' );
}
}
},
10,
3
);
```
It is important to note that this action must _add_ errors to the `WP_Error` object it receives. Returning a new `WP_Error` object or any other value will result in the errors not showing.
If no validation errors are encountered the function can just return void.
#### Multiple field validation
There are cases where the validity of a field depends on the value of another field, for example validating the format of a government ID based on what country the shopper is in. In this case, validating only single fields (as above) is not sufficient as the country may be unknown during the `woocommerce_validate_additional_field` action.
To solve this, it is possible to validate a field in the context of the location it renders in. The other fields in that location will be passed to this action.
##### Using the `woocommerce_blocks_validate_location_{location}_fields` action
This action will be fired for each location that additional fields can render in (`address`, `contact`, and `order`). For `address` it fires twice, once for the billing address and once for the shipping address.
The callback receives the keys and values of the other additional fields in the same location.
It is important to note that any fields rendered in other locations will not be passed to this action, however it might be possible to get those values by accessing the customer or order object, however this is not supported and there are no guarantees regarding backward compatibility in future versions.
| Argument | Type | Description |
|----------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `$errors` | `WP_Error` | An error object containing errors that were already encountered while processing the request. If no errors were added yet, it will still be a `WP_Error` object but it will be empty. |
| `$fields` | `array` | The fields rendered in this locations. |
| `$group` | `'billing'\|'shipping'\|'other'` | If the action is for the address location, the type of address will be set here. If it is for contact or order, this will be 'other'. |
There are several places where these hooks are fired.
- When checking out using the Checkout block or Store API.
- `woocommerce_blocks_validate_location_address_fields` (x2)
- `woocommerce_blocks_validate_location_contact_fields`
- `woocommerce_blocks_validate_location_other_fields`
- When updating addresses in the "My account" area
- `woocommerce_blocks_validate_location_address_fields` (**x1** - only the address being edited)
- When updating the "Account details" section in the "My account" area
- `woocommerce_blocks_validate_location_contact_fields`
##### Example of location validation
In this example, assume there is another field registered alongside the `namespace/gov-id` called `namespace/confirm-gov-id`. This field will be a confirmation for the Government ID field.
The example below illustrates how to verify that the value of the confirmation field matches the value of the main field.
```php
add_action(
'woocommerce_blocks_validate_location_address_fields',
function ( \WP_Error $errors, $fields, $group ) {
if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) {
$errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' );
}
},
10,
3
);
```
If these fields were rendered in the "contact" location instead, the code would be the same except the hook used would be: `woocommerce_blocks_validate_location_contact_fields`.
## Backward compatibility
Due to technical reasons, it's not yet possible to specify the meta key for fields, as we want them to be prefixed and managed. Plugins with existing fields in shortcode Checkout can be compatible and react to reading and saving fields using hooks.
Assuming 2 fields, named `my-plugin-namespace/address-field` in the address step and `my-plugin-namespace/my-other-field` in the order step, you can:
### React to to saving fields
You can react to those fields being saved by hooking into `woocommerce_set_additional_field_value` action.
```php
add_action(
'woocommerce_set_additional_field_value',
function ( $key, $value, $group, $wc_object ) {
if ( 'my-plugin-namespace/address-field' !== $key ) {
return;
}
if ( 'billing' === $group ) {
$my_plugin_address_key = 'existing_billing_address_field_key';
} else {
$my_plugin_address_key = 'existing_shipping_address_field_key';
}
$wc_object->update_meta_data( $my_plugin_address_key, $value, true );
},
10,
4
);
add_action(
'woocommerce_set_additional_field_value',
function ( $key, $value, $group, $wc_object ) {
if ( 'my-plugin-namespace/my-other-field' !== $key ) {
return;
}
$my_plugin_key = 'existing_order_field_key';
$wc_object->update_meta_data( $my_plugin_key, $value, true );
},
10,
4
);
```
This way, you can ensure existing systems will continue working and your integration will continue to work. However, ideally, you should migrate your existing data and systems to use the new meta fields.
### React to reading fields
You can use the `woocommerce_get_default_value_for_{$key}` filters to provide a different default value (a value coming from another meta field for example):
```php
add_filter(
"woocommerce_blocks_get_default_value_for_my-plugin-namespace/address-field",
function ( $value, $group, $wc_object ) {
if ( 'billing' === $group ) {
$my_plugin_key = 'existing_billing_address_field_key';
} else {
$my_plugin_key = 'existing_shipping_address_field_key';
}
return $wc_object->get_meta( $my_plugin_key );
},
10,
3
);
add_filter(
"woocommerce_blocks_get_default_value_for_my-plugin-namespace/my-other-field",
function ( $value, $group, $wc_object ) {
$my_plugin_key = 'existing_order_field_key';
return $wc_object->get_meta( $my_plugin_key );
},
10,
3
);
```
## A full example
In this full example we will register the Government ID text field and verify that it conforms to a specific pattern.
This example is just a combined version of the examples shared above.
```php
add_action(
'woocommerce_init',
function() {
woocommerce_register_additional_checkout_field(
array(
'id' => 'namespace/gov-id',
'label' => 'Government ID',
'location' => 'address',
'required' => true,
'attributes' => array(
'autocomplete' => 'government-id',
'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers.
'title' => 'Your 5-digit Government ID',
),
),
);
woocommerce_register_additional_checkout_field(
array(
'id' => 'namespace/confirm-gov-id',
'label' => 'Confirm government ID',
'location' => 'address',
'required' => true,
'attributes' => array(
'autocomplete' => 'government-id',
'pattern' => '[A-Z0-9]{5}', // A 5-character string of capital letters and numbers.
'title' => 'Confirm your 5-digit Government ID',
),
),
);
add_action(
'woocommerce_sanitize_additional_field',
function ( $field_value, $field_key ) {
if ( 'namespace/gov-id' === $field_key || 'namespace/confirm-gov-id' === $field_key ) {
$field_value = str_replace( ' ', '', $field_key );
$field_value = strtoupper( $field_value );
}
return $field_value;
},
10,
2
);
add_action(
'woocommerce_validate_additional_field',
function ( WP_Error $errors, $field_key, $field_value ) {
if ( 'namespace/gov-id' === $field_key ) {
$match = preg_match( '/[A-Z0-9]{5}/', $field_value );
if ( 0 === $match || false === $match ) {
$errors->add( 'invalid_gov_id', 'Please ensure your government ID matches the correct format.' );
}
}
return $error;
},
10,
3
);
}
);
add_action(
'woocommerce_blocks_validate_location_address_fields',
function ( \WP_Error $errors, $fields, $group ) {
if ( $fields['namespace/gov-id'] !== $fields['namespace/confirm-gov-id'] ) {
$errors->add( 'gov_id_mismatch', 'Please ensure your government ID matches the confirmation.' );
}
},
10,
3
);
```

View File

@ -0,0 +1,118 @@
---
category_title: Available Filters
category_slug: cart-and-checkout-available-filters
post_title: Cart and Checkout - Available Filters
---
This document lists the filters that are currently available to extensions and offers usage information for each one of them. Information on registering filters can be found on the [Checkout - Filter Registry](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/packages/checkout/filter-registry/README.md) page.
## Cart Line Items filters
The following [Cart Line Items filters](./cart-line-items.md) are available:
- `cartItemClass`
- `cartItemPrice`
- `itemName`
- `saleBadgePriceFormat`
- `showRemoveItemLink`
- `subtotalPriceFormat`
The following screenshot shows which parts the individual filters affect:
![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png)
## Order Summary Items filters
The following [Order Summary Items filters](./order-summary-items.md) are available:
- `cartItemClass`
- `cartItemPrice`
- `itemName`
- `subtotalPriceFormat`
The following screenshot shows which parts the individual filters affect:
![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png)
## Totals Footer Item filter
The following [Totals Footer Item filter](./totals-footer-item.md) is available:
- `totalLabel`
- `totalValue`
## Checkout and place order button filters
The following [Checkout and place order button filters](./checkout-and-place-order-button.md) are available:
- `proceedToCheckoutButtonLabel`
- `proceedToCheckoutButtonLink`
- `placeOrderButtonLabel`
## Coupon filters
The following [Coupon filters](./coupons.md) are available:
- `coupons`
- `showApplyCouponNotice`
- `showRemoveCouponNotice`
## Additional Cart and Checkout inner block types filter
The following [Additional Cart and Checkout inner block types filter](./additional-cart-checkout-inner-block-types.md) is available:
- `additionalCartCheckoutInnerBlockTypes`
## Combined filters
Filters can also be combined. The following example shows how to combine some of the available filters.
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const isOrderSummaryContext = ( args ) => args?.context === 'summary';
const modifyCartItemClass = ( defaultValue, extensions, args ) => {
if ( isOrderSummaryContext( args ) ) {
return 'my-custom-class';
}
return defaultValue;
};
const modifyCartItemPrice = ( defaultValue, extensions, args ) => {
if ( isOrderSummaryContext( args ) ) {
return '<price/> for all items';
}
return defaultValue;
};
const modifyItemName = ( defaultValue, extensions, args ) => {
if ( isOrderSummaryContext( args ) ) {
return `${ defaultValue }`;
}
return defaultValue;
};
const modifySubtotalPriceFormat = ( defaultValue, extensions, args ) => {
if ( isOrderSummaryContext( args ) ) {
return '<price/> per item';
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
cartItemClass: modifyCartItemClass,
cartItemPrice: modifyCartItemPrice,
itemName: modifyItemName,
subtotalPriceFormat: modifySubtotalPriceFormat,
} );
```
## Troubleshooting
If you are logged in to the store as an administrator, you should be shown an error like this if your filter is not
working correctly. The error will also be shown in your console.
![Troubleshooting](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-30-at-10.52.53.png)

View File

@ -0,0 +1,75 @@
---
post_title: Cart and Checkout Filters - Inner block types
menu_title: Inner Block Types
tags: reference
---
The following Additional Cart and Checkout inner block types filter is available:
- `additionalCartCheckoutInnerBlockTypes`
## `additionalCartCheckoutInnerBlockTypes`
### Description <!-- omit in toc -->
The Cart and Checkout blocks are made up of inner blocks. These inner blocks areas allow certain block types to be added as children. By default, only `core/paragraph`, `core/image`, and `core/separator` are available to add.
By using the `additionalCartCheckoutInnerBlockTypes` filter it is possible to add items to this array to control what the editor can into an inner block.
This filter is called once for each inner block area, so it is possible to be very granular when determining what blocks can be added where.
### Parameters <!-- omit in toc -->
- _defaultValue_ `array` (default: `[]`) - The default value of the filter.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following key:
- _block_ `string` - The block name of the inner block area, e.g. `woocommerce/checkout-shipping-address-block`.
- _validation_ `boolean` or `Error` - Checks if the returned value is an arry of strings. If an error occurs, it will be thrown.
### Returns <!-- omit in toc -->
- `array` - The modified array with allowed block types for the corresponding inner block area.
### Code example <!-- omit in toc -->
Let's suppose we want to allow the editor to add some blocks in specific places in the Cart and Checkout blocks.
1. Allow `core/quote` to be inserted in every block area in the Cart and Checkout blocks.
2. Allow `core/table` to be inserted in the Shipping Address block in the Checkout.
In our extension we could register a filter satisfy both of these conditions like so:
```tsx
document.addEventListener( 'DOMContentLoaded', function () {
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyAdditionalInnerBlockTypes = (
defaultValue,
extensions,
args,
validation
) => {
defaultValue.push( 'core/quote' );
if ( args?.block === 'woocommerce/checkout-shipping-address-block' ) {
defaultValue.push( 'core/table' );
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
additionalCartCheckoutInnerBlockTypes: modifyAdditionalInnerBlockTypes,
} );
} );
```
To call this filter within the editor, wrap the filter registration in a `DOMContentLoaded` event listener and ensure the code runs in the admin panel.
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Additional Cart and Checkout inner block types filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/0d4560c8-c2b1-4ed8-8aee-469b248ccb08) |![After applying the Additional Cart and Checkout inner block types filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/d38cd568-6c8c-4158-9269-d8dffdf66988) |

View File

@ -0,0 +1,626 @@
---
post_title: Cart and Checkout Filters - Cart line items
menu_title: Cart Line Items
tags: reference
---
<!-- markdownlint-disable MD024 -->
The following Cart Line Items filters are available:
- `cartItemClass`
- `cartItemPrice`
- `itemName`
- `saleBadgePriceFormat`
- `showRemoveItemLink`
- `subtotalPriceFormat`
The following objects are shared between the filters:
- Cart object
- Cart Item object
The following screenshot shows which parts the individual filters affect:
![Cart Line Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-13.12.33.png)
## `cartItemClass`
### Description <!-- omit in toc -->
The `cartItemClass` filter allows to change the cart item class.
### Parameters <!-- omit in toc -->
- _defaultValue_ `object` (default: `''`) - The default cart item class.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `string` - The modified cart item class, or an empty string.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemClass = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return 'my-custom-class';
};
registerCheckoutFilters( 'example-extension', {
cartItemClass: modifyCartItemClass,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemClass = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return 'cool-class';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return 'hot-class';
}
return 'my-custom-class';
};
registerCheckoutFilters( 'example-extension', {
cartItemClass: modifyCartItemClass,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a587a6ce-d051-4ed0-bba5-815b5d72179d) |![After applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/9b25eeae-6d81-4e28-b177-32f942e1d0c2) |
## `cartItemPrice`
### Description <!-- omit in toc -->
The `cartItemPrice` filter allows to format the cart item price.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `&lt;price/&gt;`) - The default cart item price.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
- _validation_ `boolean` - Checks if the return value contains the substring `&lt;price/&gt;`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the cart item price, which must contain the substring `&lt;price/&gt;`, or the original price format.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return '&lt;price/&gt; for all items';
};
registerCheckoutFilters( 'example-extension', {
cartItemPrice: modifyCartItemPrice,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return '&lt;price/&gt; to keep you warm';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return '&lt;price/&gt; to keep you cool';
}
return '&lt;price/&gt; for all items';
};
registerCheckoutFilters( 'example-extension', {
cartItemPrice: modifyCartItemPrice,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/bbaeb68a-492e-41e7-87b7-4b8b05ca3709) |![After applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/bbaeb68a-492e-41e7-87b7-4b8b05ca3709) |
## `itemName`
### Description <!-- omit in toc -->
The `itemName` filter allows to change the cart item name.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` - The default cart item name.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `string` - The original or modified cart item name.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyItemName = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return `🪴 ${ defaultValue } 🪴`;
};
registerCheckoutFilters( 'example-extension', {
itemName: modifyItemName,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyItemName = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return `⛷️ ${ defaultValue } ⛷️`;
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return `🏄‍♂️ ${ defaultValue } 🏄‍♂️`;
}
return `🪴 ${ defaultValue } 🪴`;
};
registerCheckoutFilters( 'example-extension', {
itemName: modifyItemName,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/97d0f501-138e-4448-93df-a4d865b524e6) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/69381932-d064-4e8f-b378-c2477fef56ae) |
## `saleBadgePriceFormat`
### Description <!-- omit in toc -->
The `saleBadgePriceFormat` filter allows to format the cart item sale badge price.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `&lt;price/&gt;`) - The default cart item sale badge price.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
- _validation_ `boolean` - Checks if the return value contains the substring `&lt;price/&gt;`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the cart item sale badge price, which must contain the substring `&lt;price/&gt;`, or the original price format.
### Code examples <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySaleBadgePriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return '&lt;price/&gt; per item';
};
registerCheckoutFilters( 'example-extension', {
saleBadgePriceFormat: modifySaleBadgePriceFormat,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySaleBadgePriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return '&lt;price/&gt; per item while keeping warm';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return '&lt;price/&gt; per item while looking cool';
}
return '&lt;price/&gt; per item';
};
registerCheckoutFilters( 'example-extension', {
saleBadgePriceFormat: modifySaleBadgePriceFormat,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Sale Badge Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/d2aeb206-e620-44e0-93c1-31484cfcdca6) |![After applying the Sale Badge Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/6b929695-5d89-433b-8694-b9201a7c0519) |
## `showRemoveItemLink`
### Description <!-- omit in toc -->
The `showRemoveItemLink` is used to show or hide the cart item remove link.
### Parameters <!-- omit in toc -->
- _defaultValue_ (type: `boolean`, default: `true`) - The default value of the remove link.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `boolean` - `true` if the cart item remove link should be shown, `false` otherwise.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowRemoveItemLink = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return false;
};
registerCheckoutFilters( 'example-extension', {
showRemoveItemLink: modifyShowRemoveItemLink,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowRemoveItemLink = ( defaultValue, extensions, args ) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return false;
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return false;
}
return true;
};
registerCheckoutFilters( 'example-extension', {
showRemoveItemLink: modifyShowRemoveItemLink,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Show Remove Item Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a4254f3b-f056-47ad-b34a-d5f6d5500e56) |![After applying the Show Remove Item Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/32c55dc7-ef65-4f35-ab90-9533bc79d362) |
## `subtotalPriceFormat`
### Description <!-- omit in toc -->
The `subtotalPriceFormat` filter allows to format the cart item subtotal price.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `&lt;price/&gt;`) - The default cart item subtotal price.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see Cart object.
- _cartItem_ `object` - The cart item object from `wc/store/cart`, see Cart Item object.
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
- _validation_ `boolean` - Checks if the return value contains the substring `&lt;price/&gt;`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the cart item subtotal price, which must contain the substring `&lt;price/&gt;`, or the original price format.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySubtotalPriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
return '&lt;price/&gt; per item';
};
registerCheckoutFilters( 'example-extension', {
subtotalPriceFormat: modifySubtotalPriceFormat,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySubtotalPriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isCartContext = args?.context === 'cart';
if ( ! isCartContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return '&lt;price/&gt; per warm beanie';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return '&lt;price/&gt; per cool sunglasses';
}
return '&lt;price/&gt; per item';
};
registerCheckoutFilters( 'example-extension', {
subtotalPriceFormat: modifySubtotalPriceFormat,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a392cb24-4c40-4e25-8396-bf4971830e22) |![After applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/af69b26f-662a-4ef9-a288-3713b6e46373) |
## Cart object
The Cart object of the filters above has the following keys:
- _billingAddress_ `object` - The billing address object with the following keys:
- _address_1_ `string` - The first line of the address.
- _address_2_ `string` - The second line of the address.
- _city_ `string` - The city of the address.
- _company_ `string` - The company of the address.
- _country_ `string` - The country of the address.
- _email_ `string` - The email of the address.
- _first_name_ `string` - The first name of the address.
- _last_name_ `string` - The last name of the address.
- _phone_ `string` - The phone of the address.
- _postcode_ `string` - The postcode of the address.
- _state_ `string` - The state of the address.
- ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object.
- _cartCoupons_ `array` - The cart coupons array.
- _cartErrors_ `array` - The cart errors array.
- _cartFees_ `array` - The cart fees array.
- _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping.
- _cartIsLoading_ `boolean` - Whether the cart is loading.
- _cartItemErrors_ `array` - The cart item errors array.
- _cartItems_ `array` - The cart items array with cart item objects, see Cart Item object.
- _cartItemsCount_ `number` - The cart items count.
- _cartItemsWeight_ `number` - The cart items weight.
- _cartNeedsPayment_ `boolean` - Whether the cart needs payment.
- _cartNeedsShipping_ `boolean` - Whether the cart needs shipping.
- _cartTotals_ `object` - The cart totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _tax_lines_ `array` - The tax lines array with tax line objects with the following keys:
- _name_ `string` - The name of the tax line.
- _price_ `number` - The price of the tax line.
- _rate_ `string` - The rate ID of the tax line.
- _total_discount_ `string` - The total discount.
- _total_discount_tax_ `string` - The total discount tax.
- _total_fees_ `string` - The total fees.
- _total_fees_tax_ `string` - The total fees tax.
- _total_items_ `string` - The total items.
- _total_items_tax_ `string` - The total items tax.
- _total_price_ `string` - The total price.
- _total_shipping_ `string` - The total shipping.
- _total_shipping_tax_ `string` - The total shipping tax.
- _total_tax_ `string` - The total tax.
- _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _isLoadingRates_ `boolean` - Whether the cart is loading rates.
- _paymentRequirements_ `array` - The payment requirements array.
- _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object.
- _shippingRates_ `array` - The shipping rates array.
## Cart Item object
The Cart Item object of the filters above has the following keys:
- _backorders_allowed_ `boolean` - Whether backorders are allowed.
- _catalog_visibility_ `string` - The catalog visibility.
- _decsription_ `string` - The cart item description.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _id_ `number` - The item ID.
- _images_ `array` - The item images array.
- _item_data_ `array` - The item data array.
- _key_ `string` - The item key.
- _low_stock_remaining_ `number` - The low stock remaining.
- _name_ `string` - The item name.
- _permalink_ `string` - The item permalink.
- _prices_ `object` - The item prices object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _price_ `string` - The price.
- _price_range_ `string` - The price range.
- _raw_prices_ `object` - The raw prices object with the following keys:
- _precision_ `number` - The precision.
- _price_ `number` - The price.
- _regular_price_ `number` - The regular price.
- _sale_price_ `number` - The sale price.
- _regular_price_ `string` - The regular price.
- _sale_price_ `string` - The sale price.
- _quantity_ `number` - The item quantity.
- _quantity_limits_ `object` - The item quantity limits object with the following keys:
- _editable_ `boolean` - Whether the quantity is editable.
- _maximum_ `number` - The maximum quantity.
- _minimum_ `number` - The minimum quantity.
- _multiple_of_ `number` - The multiple of quantity.
- _short_description_ `string` - The item short description.
- _show_backorder_badge_ `boolean` - Whether to show the backorder badge.
- _sku_ `string` - The item SKU.
- _sold_individually_ `boolean` - Whether the item is sold individually.
- _totals_ `object` - The item totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _line_subtotal_ `string` - The line subtotal.
- _line_subtotal_tax_ `string` - The line subtotal tax.
- _line_total_ `string` - The line total.
- _line_total_tax_ `string` - The line total tax.
- _type_ `string` - The item type.
- _variation_ `array` - The item variation array.

View File

@ -0,0 +1,325 @@
---
post_title: Cart and Checkout Filters - Checkout and place order button
menu_title: Checkout and Place Order Button
tags: reference
---
<!-- markdownlint-disable MD024 -->
The following Checkout and place order button filters are available:
- `proceedToCheckoutButtonLabel`
- `proceedToCheckoutButtonLink`
- `placeOrderButtonLabel`
The following objects are shared between the filters:
- Cart object
- Cart Item object
## `proceedToCheckoutButtonLabel`
### Description <!-- omit in toc -->
The `proceedToCheckoutButtonLabel` filter allows change the label of the "Proceed to checkout" button.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `Proceed to Checkout`) - The label of the "Proceed to checkout" button.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
### Returns <!-- omit in toc -->
- `string` - The label of the "Proceed to checkout" button.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyProceedToCheckoutButtonLabel = (
defaultValue,
extensions,
args
) => {
if ( ! args?.cart.items ) {
return defaultValue;
}
return 'Go to checkout';
};
registerCheckoutFilters( 'example-extension', {
proceedToCheckoutButtonLabel: modifyProceedToCheckoutButtonLabel,
} );
```
#### Advanced example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyProceedToCheckoutButtonLabel = (
defaultValue,
extensions,
args
) => {
if ( ! args?.cart.items ) {
return defaultValue;
}
const isSunglassesInCart = args?.cart.items.some(
( item ) => item.name === 'Sunglasses'
);
if ( isSunglassesInCart ) {
return '😎 Proceed to checkout 😎';
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
proceedToCheckoutButtonLabel: modifyProceedToCheckoutButtonLabel,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Proceed To Checkout Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/fb0216c1-a091-4d58-b443-f49ccff98ed8) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/ef15b6df-fbd7-43e7-a359-b4adfbba961a) |
## `proceedToCheckoutButtonLink`
### Description <!-- omit in toc -->
The `proceedToCheckoutButtonLink` filter allows change the link of the "Proceed to checkout" button.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `/checkout`) - The link of the "Proceed to checkout" button.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](./category/cart-and-checkout-blocks/available-filters/).
### Returns <!-- omit in toc -->
- `string` - The link of the "Proceed to checkout" button.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyProceedToCheckoutButtonLink = (
defaultValue,
extensions,
args
) => {
if ( ! args?.cart.items ) {
return defaultValue;
}
return '/custom-checkout';
};
registerCheckoutFilters( 'example-extension', {
proceedToCheckoutButtonLink: modifyProceedToCheckoutButtonLink,
} );
```
#### Advanced example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyProceedToCheckoutButtonLink = (
defaultValue,
extensions,
args
) => {
if ( ! args?.cart.items ) {
return defaultValue;
}
const isSunglassesInCart = args?.cart.items.some(
( item ) => item.name === 'Sunglasses'
);
if ( isSunglassesInCart ) {
return '/custom-checkout';
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
proceedToCheckoutButtonLink: modifyProceedToCheckoutButtonLink,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Proceed To Checkout Button Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3f657e0f-4fcc-4746-a554-64221e071b2e) |![After applying the Proceed To Checkout Button Link filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/064df213-439e-4d8f-b29c-55962604cb97) |
## `placeOrderButtonLabel`
### Description <!-- omit in toc -->
The `placeOrderButtonLabel` filter allows change the label of the "Place order" button.
### Parameters <!-- omit in toc -->
- _defaultValue_ (type: `string`, default: `Place order`) - The label of the "Place order" button.
- _extensions_ `object` (default: `{}`) - The extensions object.
### Returns <!-- omit in toc -->
- `string` - The label of the "Place order" button.
### Code example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyPlaceOrderButtonLabel = ( defaultValue, extensions ) => {
return '😎 Pay now 😎';
};
registerCheckoutFilters( 'example-extension', {
placeOrderButtonLabel: modifyPlaceOrderButtonLabel,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Place Order Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/aa6d9b65-4d56-45f7-8162-a6bbfe171250) |![After applying the Place Order Button Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a5cc2572-16e7-4781-a5ab-5d6cdced2ff6) |
<!-- FEEDBACK -->
## Cart object
The Cart object of the filters above has the following keys:
- _billingAddress_ `object` - The billing address object with the following keys:
- _address_1_ `string` - The first line of the address.
- _address_2_ `string` - The second line of the address.
- _city_ `string` - The city of the address.
- _company_ `string` - The company of the address.
- _country_ `string` - The country of the address.
- _email_ `string` - The email of the address.
- _first_name_ `string` - The first name of the address.
- _last_name_ `string` - The last name of the address.
- _phone_ `string` - The phone of the address.
- _postcode_ `string` - The postcode of the address.
- _state_ `string` - The state of the address.
- _coupons_ `array` - The coupons array.
- _crossSells_ `array` - The cross sell items array.
- _errors_ `array` - The errors array.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _fees_ `array` - The fees array.
- _hasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping.
- _items_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object).
- _itemsCount_ `number` - The number of items in the cart.
- _itemsWeight_ `number` - The total weight of the cart items.
- _needsPayment_ `boolean` - Whether the cart needs payment.
- _needsShipping_ `boolean` - Whether the cart needs shipping.
- _paymentMethods_ `array` - The payment methods array.
- _paymentRequirements_ `array` - The payment requirements array.
- _shippingAddress_ `object` - The shipping address object with the same keys as the billing address object.
- _shippingRates_ `array` - The shipping rates array.
- _totals_ `object` - The totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _tax_lines_ `array` - The tax lines array of objects with the following keys:
- _name_ `string` - The tax name.
- _price_ `string` - The tax price.
- _rate_ `string` - The tax rate.
- _total_discount_ `string` - The total discount.
- _total_discount_tax_ `string` - The total discount tax.
- _total_fee_ `string` - The total fee.
- _total_fee_tax_ `string` - The total fee tax.
- _total_items_ `string` - The total items.
- _total_items_tax_ `string` - The total items tax.
- _total_price_ `string` - The total price.
- _total_shipping_ `string` - The total shipping.
- _total_shipping_tax_ `string` - The total shipping tax.
- _total_tax_ `string` - The total tax.
## Cart Item object
The Cart Item object of the filters above has the following keys:
- _backorders_allowed_ `boolean` - Whether backorders are allowed.
- _catalog_visibility_ `string` - The catalog visibility.
- _decsription_ `string` - The cart item description.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _id_ `number` - The item ID.
- _images_ `array` - The item images array.
- _item_data_ `array` - The item data array.
- _key_ `string` - The item key.
- _low_stock_remaining_ `number` - The low stock remaining.
- _name_ `string` - The item name.
- _permalink_ `string` - The item permalink.
- _prices_ `object` - The item prices object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _price_ `string` - The price.
- _price_range_ `string` - The price range.
- _raw_prices_ `object` - The raw prices object with the following keys:
- _precision_ `number` - The precision.
- _price_ `number` - The price.
- _regular_price_ `number` - The regular price.
- _sale_price_ `number` - The sale price.
- _regular_price_ `string` - The regular price.
- _sale_price_ `string` - The sale price.
- _quantity_ `number` - The item quantity.
- _quantity_limits_ `object` - The item quantity limits object with the following keys:
- _editable_ `boolean` - Whether the quantity is editable.
- _maximum_ `number` - The maximum quantity.
- _minimum_ `number` - The minimum quantity.
- _multiple_of_ `number` - The multiple of quantity.
- _short_description_ `string` - The item short description.
- _show_backorder_badge_ `boolean` - Whether to show the backorder badge.
- _sku_ `string` - The item SKU.
- _sold_individually_ `boolean` - Whether the item is sold individually.
- _totals_ `object` - The item totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _line_subtotal_ `string` - The line subtotal.
- _line_subtotal_tax_ `string` - The line subtotal tax.
- _line_total_ `string` - The line total.
- _line_total_tax_ `string` - The line total tax.
- _type_ `string` - The item type.
- _variation_ `array` - The item variation array.

View File

@ -0,0 +1,189 @@
---
post_title: Cart and Checkout Filters - Coupons
menu_title: Coupons
tags: reference
---
<!-- markdownlint-disable MD024 -->
The following Coupon filters are available:
- `coupons`
- `showApplyCouponNotice`
- `showRemoveCouponNotice`
## `coupons`
### Description <!-- omit in toc -->
The current functionality is to display the coupon codes in the Cart and Checkout sidebars. This could be undesirable if you dynamically generate a coupon code that is not user-friendly. It may, therefore, be desirable to change the way this code is displayed. To achieve this, the filter `coupons` exists. This filter could also be used to show or hide coupons. This filter must _not_ be used to alter the value/totals of a coupon. This will not carry through to the Cart totals.
### Parameters <!-- omit in toc -->
- _coupons_ `object` - The coupons object with the following keys:
- _code_ `string` - The coupon code.
- _discount_type_ `string` - The type of discount. Can be `percent` or `fixed_cart`.
- _totals_ `object` - The totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _total_discount_ `string` - The total discount.
- _total_discount_tax_ `string` - The total discount tax.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following key:
- _context_ `string` (default: `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `array` - The coupons array of objects with the same keys as above.
### Code example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCoupons = ( coupons, extensions, args ) => {
return coupons.map( ( coupon ) => {
if ( ! coupon.label.match( /autocoupon(?:_\d+)+/ ) ) {
return coupon;
}
return {
...coupon,
label: 'Automatic coupon',
};
} );
};
registerCheckoutFilters( 'example-extension', {
coupons: modifyCoupons,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Coupons filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/6cab1aff-e4b9-4909-b81c-5726c6a20c40) |![After applying the Coupons filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/a5cc2572-16e7-4781-a5ab-5d6cdced2ff6) |
## `showApplyCouponNotice`
### Description <!-- omit in toc -->
### Parameters <!-- omit in toc -->
- _value_ `boolean` (default: `true`) - Weather to show the apply coupon notice.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _context_ `string` (allowed values: `wc/cart` and `wc/checkout`) - The context of the coupon notice.
- _code_ `string` - The coupon code.
### Returns <!-- omit in toc -->
- `boolean` - Weather to show the apply coupon notice.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowApplyCouponNotice = ( defaultValue, extensions, args ) => {
return false;
};
registerCheckoutFilters( 'example-extension', {
showApplyCouponNotice: modifyShowApplyCouponNotice,
} );
```
#### Advanced example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowApplyCouponNotice = ( defaultValue, extensions, args ) => {
if ( args?.couponCode === '10off' ) {
return false;
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
showApplyCouponNotice: modifyShowApplyCouponNotice,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Show Apply Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/374d4899-61f3-49b2-ae04-5541d4c130c2) |![After applying the Show Apply Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/c35dbd9b-eee4-4afe-9a29-9c554d467729) |
## `showRemoveCouponNotice`
### Description <!-- omit in toc -->
### Parameters <!-- omit in toc -->
- _value_ `boolean` (default: `true`) - Weather to show the remove coupon notice.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _context_ `string` (allowed values: `wc/cart` and `wc/checkout`) - The context of the coupon notice.
- _code_ `string` - The coupon code.
### Returns <!-- omit in toc -->
- `boolean` - Weather to show the apply coupon notice.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowRemoveCouponNotice = ( defaultValue, extensions, args ) => {
return false;
};
registerCheckoutFilters( 'example-extension', {
showRemoveCouponNotice: modifyShowRemoveCouponNotice,
} );
```
#### Advanced example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyShowRemoveCouponNotice = ( defaultValue, extensions, args ) => {
if ( args?.couponCode === '10off' ) {
return false;
}
return defaultValue;
};
registerCheckoutFilters( 'example-extension', {
showRemoveCouponNotice: modifyShowRemoveCouponNotice,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Show Remove Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/9d8607fa-ab20-4181-b70b-7954e7aa49cb) |![After applying the Show Remove Coupon Notice filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/83d5f65f-c4f3-4707-a250-077952514931) |

View File

@ -0,0 +1,456 @@
---
post_title: Cart and Checkout Filters - Order summary items
menu_title: Order Summary Items
tags: reference
---
<!-- markdownlint-disable MD024 -->
The following Order Summary Items filters are available:
- `cartItemClass`
- `cartItemPrice`
- `itemName`
- `subtotalPriceFormat`
The following objects are shared between the filters:
- Cart object
- Cart Item object
The following screenshot shows which parts the individual filters affect:
![Order Summary Items](https://woocommerce.com/wp-content/uploads/2023/10/Screenshot-2023-10-26-at-16.29.45.png)
## `cartItemClass`
### Description <!-- omit in toc -->
The `cartItemClass` filter allows to change the order summary item class.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `''`) - The default order summary item class.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
- _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object).
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `string` - The modified order summary item class, or an empty string.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemClass = ( defaultValue, extensions, args ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
return 'my-custom-class';
};
registerCheckoutFilters( 'example-extension', {
cartItemClass: modifyCartItemClass,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemClass = ( defaultValue, extensions, args ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return 'cool-class';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return 'hot-class';
}
return 'my-custom-class';
};
registerCheckoutFilters( 'example-extension', {
cartItemClass: modifyCartItemClass,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/ff555a84-8d07-4889-97e1-8f7d50d47350) |![After applying the Cart Item Class filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/183809d8-03dc-466d-a415-d8d2062d880f) |
## `cartItemPrice`
### Description <!-- omit in toc -->
The `cartItemPrice` filter allows to format the order summary item price.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `<price/>`) - The default order summary item price.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
- _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object).
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the order summary item price, which must contain the substring `<price/>`, or the original price format.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
return '<price/> for all items';
};
registerCheckoutFilters( 'example-extension', {
cartItemPrice: modifyCartItemPrice,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyCartItemPrice = ( defaultValue, extensions, args, validation ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return '<price/> to keep you ☀️';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return '<price/> to keep you ❄️';
}
return '<price/> for all items';
};
registerCheckoutFilters( 'example-extension', {
cartItemPrice: modifyCartItemPrice,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/58137fc4-884d-4783-9275-5f78abec1473) |![After applying the Cart Item Price filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/fb502b74-6447-49a8-8d35-241e738f089d) |
## `itemName`
### Description <!-- omit in toc -->
The `itemName` filter allows to change the order summary item name.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` - The default order summary item name.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
- _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object).
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
### Returns <!-- omit in toc -->
- `string` - The original or modified order summary item name.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyItemName = ( defaultValue, extensions, args ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
return `🪴 ${ defaultValue } 🪴`;
};
registerCheckoutFilters( 'example-extension', {
itemName: modifyItemName,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyItemName = ( defaultValue, extensions, args ) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return `⛷️ ${ defaultValue } ⛷️`;
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return `🏄‍♂️ ${ defaultValue } 🏄‍♂️`;
}
return `🪴 ${ defaultValue } 🪴`;
};
registerCheckoutFilters( 'example-extension', {
itemName: modifyItemName,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3dc0bda7-fccf-4f35-a2e2-aa04e616563a) |![After applying the Item Name filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/c96b8394-03a7-45f6-813b-5335f4bf83b5) |
## `subtotalPriceFormat`
### Description <!-- omit in toc -->
The `subtotalPriceFormat` filter allows to format the order summary item subtotal price.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `<price/>`) - The default order summary item subtotal price.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
- _cartItem_ `object` - The order summary item object from `wc/store/cart`, see [order summary item object](#cart-item-object).
- _context_ `string` (allowed values: `cart` or `summary`) - The context of the item.
- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the order summary item subtotal price, which must contain the substring `<price/>`, or the original price format.
### Code examples <!-- omit in toc -->
#### Basic example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySubtotalPriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
return '<price/> per item';
};
registerCheckoutFilters( 'example-extension', {
subtotalPriceFormat: modifySubtotalPriceFormat,
} );
```
#### Advanced example <!-- omit in toc -->
```tsx
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifySubtotalPriceFormat = (
defaultValue,
extensions,
args,
validation
) => {
const isOrderSummaryContext = args?.context === 'summary';
if ( ! isOrderSummaryContext ) {
return defaultValue;
}
if ( args?.cartItem?.name === 'Beanie with Logo' ) {
return '<price/> per warm beanie';
}
if ( args?.cartItem?.name === 'Sunglasses' ) {
return '<price/> per cool sunglasses';
}
return '<price/> per item';
};
registerCheckoutFilters( 'example-extension', {
subtotalPriceFormat: modifySubtotalPriceFormat,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/3574e7ae-9857-4651-ac9e-e6b597e3a589) |![After applying the Subtotal Price Format filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/94e18439-6d6b-44a4-ade1-8302c5984641) |
## Cart object
The Cart object of the filters above has the following keys:
- _billingAddress_ `object` - The billing address object with the following keys:
- _address_1_ `string` - The first line of the address.
- _address_2_ `string` - The second line of the address.
- _city_ `string` - The city of the address.
- _company_ `string` - The company of the address.
- _country_ `string` - The country of the address.
- _email_ `string` - The email of the address.
- _first_name_ `string` - The first name of the address.
- _last_name_ `string` - The last name of the address.
- _phone_ `string` - The phone of the address.
- _postcode_ `string` - The postcode of the address.
- _state_ `string` - The state of the address.
- ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object.
- _cartCoupons_ `array` - The cart coupons array.
- _cartErrors_ `array` - The cart errors array.
- _cartFees_ `array` - The cart fees array.
- _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping.
- _cartIsLoading_ `boolean` - Whether the cart is loading.
- _cartItemErrors_ `array` - The cart item errors array.
- _cartItems_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object).
- _cartItemsCount_ `number` - The cart items count.
- _cartItemsWeight_ `number` - The cart items weight.
- _cartNeedsPayment_ `boolean` - Whether the cart needs payment.
- _cartNeedsShipping_ `boolean` - Whether the cart needs shipping.
- _cartTotals_ `object` - The cart totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _tax_lines_ `array` - The tax lines array with tax line objects with the following keys:
- _name_ `string` - The name of the tax line.
- _price_ `number` - The price of the tax line.
- _rate_ `string` - The rate ID of the tax line.
- _total_discount_ `string` - The total discount.
- _total_discount_tax_ `string` - The total discount tax.
- _total_fees_ `string` - The total fees.
- _total_fees_tax_ `string` - The total fees tax.
- _total_items_ `string` - The total items.
- _total_items_tax_ `string` - The total items tax.
- _total_price_ `string` - The total price.
- _total_shipping_ `string` - The total shipping.
- _total_shipping_tax_ `string` - The total shipping tax.
- _total_tax_ `string` - The total tax.
- _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _isLoadingRates_ `boolean` - Whether the cart is loading rates.
- _paymentRequirements_ `array` - The payment requirements array.
- _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object.
- _shippingRates_ `array` - The shipping rates array.
## Cart Item object
The Cart Item object of the filters above has the following keys:
- _backorders_allowed_ `boolean` - Whether backorders are allowed.
- _catalog_visibility_ `string` - The catalog visibility.
- _decsription_ `string` - The cart item description.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _id_ `number` - The item ID.
- _images_ `array` - The item images array.
- _item_data_ `array` - The item data array.
- _key_ `string` - The item key.
- _low_stock_remaining_ `number` - The low stock remaining.
- _name_ `string` - The item name.
- _permalink_ `string` - The item permalink.
- _prices_ `object` - The item prices object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _price_ `string` - The price.
- _price_range_ `string` - The price range.
- _raw_prices_ `object` - The raw prices object with the following keys:
- _precision_ `number` - The precision.
- _price_ `number` - The price.
- _regular_price_ `number` - The regular price.
- _sale_price_ `number` - The sale price.
- _regular_price_ `string` - The regular price.
- _sale_price_ `string` - The sale price.
- _quantity_ `number` - The item quantity.
- _quantity_limits_ `object` - The item quantity limits object with the following keys:
- _editable_ `boolean` - Whether the quantity is editable.
- _maximum_ `number` - The maximum quantity.
- _minimum_ `number` - The minimum quantity.
- _multiple_of_ `number` - The multiple of quantity.
- _short_description_ `string` - The item short description.
- _show_backorder_badge_ `boolean` - Whether to show the backorder badge.
- _sku_ `string` - The item SKU.
- _sold_individually_ `boolean` - Whether the item is sold individually.
- _totals_ `object` - The item totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _line_subtotal_ `string` - The line subtotal.
- _line_subtotal_tax_ `string` - The line subtotal tax.
- _line_total_ `string` - The line total.
- _line_total_tax_ `string` - The line total tax.
- _type_ `string` - The item type.
- _variation_ `array` - The item variation array.

View File

@ -0,0 +1,215 @@
---
post_title: Cart and Checkout Filters - Totals footer item
menu_title: Totals Footer Item
tags: reference
---
<!-- markdownlint-disable MD024 -->
The following Totals Footer Item filter are available:
- `totalLabel`
- `totalValue`
## `totalLabel`
The following object is used in the filter:
- [Cart object](#cart-object)
### Description <!-- omit in toc -->
The `totalLabel` filter allows to change the label of the total item in the footer of the Cart and Checkout blocks.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `Total`) - The total label.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
### Returns <!-- omit in toc -->
- `string` - The updated total label.
### Code example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyTotalLabel = ( defaultValue, extensions, args ) => {
return 'Deposit due today';
};
registerCheckoutFilters( 'example-extension', {
totalLabel: modifyTotalLabel,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Total Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/5b2fb8ab-db84-4ed0-a676-d5203edc84d2) |![After applying the Total Label filter](https://github.com/woocommerce/woocommerce-blocks/assets/3323310/07955eea-cb17-48e9-9cb5-6548dd6a3b24) |
## `totalValue`
The following object is used in the filter:
- [Cart object](#cart-object)
### Description <!-- omit in toc -->
The `totalValue` filter allows to format the total price in the footer of the Cart and Checkout blocks.
### Parameters <!-- omit in toc -->
- _defaultValue_ `string` (default: `Total`) - The total label.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _args_ `object` - The arguments object with the following keys:
- _cart_ `object` - The cart object from `wc/store/cart`, see [Cart object](#cart-object).
- _validation_ `boolean` - Checks if the return value contains the substring `<price/>`.
### Returns <!-- omit in toc -->
- `string` - The modified format of the total price, which must contain the substring `<price/>`, or the original price format.
### Code example <!-- omit in toc -->
```ts
const { registerCheckoutFilters } = window.wc.blocksCheckout;
const modifyTotalsPrice = ( defaultValue, extensions, args, validation ) => {
return 'Pay <price/> now';
};
registerCheckoutFilters( 'my-extension', {
totalValue: modifyTotalsPrice,
} );
```
> Filters can be also combined. See [Combined filters](./category/cart-and-checkout-blocks/available-filters/) for an example.
### Screenshots <!-- omit in toc -->
| Before | After |
|:---------------------------------------------------------------------:|:---------------------------------------------------------------------:|
|![Before applying the Total Value filter](https://github.com/woocommerce/woocommerce/assets/3323310/4b788bdd-6fbd-406c-a9ad-4fb13f901c23) |![After applying the Total Value filter](https://github.com/woocommerce/woocommerce/assets/3323310/1b1b5f72-7f2f-4ee5-b2a4-1d8eb2208deb) |
## Cart object
The Cart object of the filters above has the following keys:
- _billingAddress_ `object` - The billing address object with the following keys:
- _address_1_ `string` - The first line of the address.
- _address_2_ `string` - The second line of the address.
- _city_ `string` - The city of the address.
- _company_ `string` - The company of the address.
- _country_ `string` - The country of the address.
- _email_ `string` - The email of the address.
- _first_name_ `string` - The first name of the address.
- _last_name_ `string` - The last name of the address.
- _phone_ `string` - The phone of the address.
- _postcode_ `string` - The postcode of the address.
- _state_ `string` - The state of the address.
- ~~_billingData_~~ `object` - The billing data object with the same keys as the `billingAddress` object.
- _cartCoupons_ `array` - The cart coupons array.
- _cartErrors_ `array` - The cart errors array.
- _cartFees_ `array` - The cart fees array.
- _cartHasCalculatedShipping_ `boolean` - Whether the cart has calculated shipping.
- _cartIsLoading_ `boolean` - Whether the cart is loading.
- _cartItemErrors_ `array` - The cart item errors array.
- _cartItems_ `array` - The cart items array with cart item objects, see [Cart Item object](#cart-item-object).
- _cartItemsCount_ `number` - The cart items count.
- _cartItemsWeight_ `number` - The cart items weight.
- _cartNeedsPayment_ `boolean` - Whether the cart needs payment.
- _cartNeedsShipping_ `boolean` - Whether the cart needs shipping.
- _cartTotals_ `object` - The cart totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _tax_lines_ `array` - The tax lines array with tax line objects with the following keys:
- _name_ `string` - The name of the tax line.
- _price_ `number` - The price of the tax line.
- _rate_ `string` - The rate ID of the tax line.
- _total_discount_ `string` - The total discount.
- _total_discount_tax_ `string` - The total discount tax.
- _total_fees_ `string` - The total fees.
- _total_fees_tax_ `string` - The total fees tax.
- _total_items_ `string` - The total items.
- _total_items_tax_ `string` - The total items tax.
- _total_price_ `string` - The total price.
- _total_shipping_ `string` - The total shipping.
- _total_shipping_tax_ `string` - The total shipping tax.
- _total_tax_ `string` - The total tax.
- _crossSellsProducts_ `array` - The cross sells products array with cross sells product objects.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _isLoadingRates_ `boolean` - Whether the cart is loading rates.
- _paymentRequirements_ `array` - The payment requirements array.
- _shippingAddress_ `object` - The shipping address object with the same keys as the `billingAddress` object.
- _shippingRates_ `array` - The shipping rates array.
## Cart Item object
The Cart Item object of the filters above has the following keys:
- _backorders_allowed_ `boolean` - Whether backorders are allowed.
- _catalog_visibility_ `string` - The catalog visibility.
- _decsription_ `string` - The cart item description.
- _extensions_ `object` (default: `{}`) - The extensions object.
- _id_ `number` - The item ID.
- _images_ `array` - The item images array.
- _item_data_ `array` - The item data array.
- _key_ `string` - The item key.
- _low_stock_remaining_ `number` - The low stock remaining.
- _name_ `string` - The item name.
- _permalink_ `string` - The item permalink.
- _prices_ `object` - The item prices object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _price_ `string` - The price.
- _price_range_ `string` - The price range.
- _raw_prices_ `object` - The raw prices object with the following keys:
- _precision_ `number` - The precision.
- _price_ `number` - The price.
- _regular_price_ `number` - The regular price.
- _sale_price_ `number` - The sale price.
- _regular_price_ `string` - The regular price.
- _sale_price_ `string` - The sale price.
- _quantity_ `number` - The item quantity.
- _quantity_limits_ `object` - The item quantity limits object with the following keys:
- _editable_ `boolean` - Whether the quantity is editable.
- _maximum_ `number` - The maximum quantity.
- _minimum_ `number` - The minimum quantity.
- _multiple_of_ `number` - The multiple of quantity.
- _short_description_ `string` - The item short description.
- _show_backorder_badge_ `boolean` - Whether to show the backorder badge.
- _sku_ `string` - The item SKU.
- _sold_individually_ `boolean` - Whether the item is sold individually.
- _totals_ `object` - The item totals object with the following keys:
- _currency_code_ `string` - The currency code.
- _currency_decimal_separator_ `string` - The currency decimal separator.
- _currency_minor_unit_ `number` - The currency minor unit.
- _currency_prefix_ `string` - The currency prefix.
- _currency_suffix_ `string` - The currency suffix.
- _currency_symbol_ `string` - The currency symbol.
- _currency_thousand_separator_ `string` - The currency thousand separator.
- _line_subtotal_ `string` - The line subtotal.
- _line_subtotal_tax_ `string` - The line subtotal tax.
- _line_total_ `string` - The line total.
- _line_total_tax_ `string` - The line total tax.
- _type_ `string` - The item type.
- _variation_ `array` - The item variation array.

View File

@ -0,0 +1,172 @@
---
post_title: Cart and Checkout - Available slots
menu_title: Available Slots
tags: reference
---
<!-- markdownlint-disable MD024 -->
This document presents the list of available Slots that you can use for adding your custom content (Fill).
If you want to add a new SlotFill component, check the [Checkout - Slot Fill document](https://github.com/woocommerce/woocommerce/blob/1675c63bba94c59703f57c7ef06e7deff8fd6bba/plugins/woocommerce-blocks/packages/checkout/slot/README.md). To read more about Slot and Fill, check the [Slot and Fill document](./cart-and-checkout-slot-and-fill/).
**Note About Naming:** Slots that are prefixed with `Experimental` are experimental and subject to change or remove. Once they graduate from the experimental stage, the naming would change and the `Experimental` prefix would be dropped. Check the [Feature Gating document](https://github.com/woocommerce/woocommerce/blob/1675c63bba94c59703f57c7ef06e7deff8fd6bba/plugins/woocommerce-blocks/docs/internal-developers/blocks/feature-flags-and-experimental-interfaces.md) from more information.
## ExperimentalOrderMeta
This Slot renders below the Checkout summary section and above the "Proceed to Checkout" button in the Cart.
```ts
const { __ } = window.wp.i18n;
const { registerPlugin } = window.wp.plugins;
const { ExperimentalOrderMeta } = window.wc.blocksCheckout;
const render = () => {
return (
<ExperimentalOrderMeta>
<div class="wc-block-components-totals-wrapper">
{ __( 'Yearly recurring total ...', 'YOUR-TEXTDOMAIN' ) }
</div>
</ExperimentalOrderMeta>
);
};
registerPlugin( 'slot-and-fill-examples', {
render,
scope: 'woocommerce-checkout',
} );
```
Cart:
![Example of ExperimentalOrderMeta in the Cart block](https://user-images.githubusercontent.com/1628454/154517779-117bb4e4-568e-413c-904c-855fc3450dfa.png)
Checkout:
![Example of ExperimentalOrderMeta in the Checkout block](https://user-images.githubusercontent.com/1628454/154697224-de245182-6783-4914-81ba-1dbcf77292eb.png)
### Passed parameters
- `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188)
- `extensions`: external data registered by third-party developers using `ExtendSchema`. If you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here.
- `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout`
## ExperimentalOrderShippingPackages
This slot renders inside the shipping step of Checkout and inside the shipping options in Cart.
```ts
const { __ } = window.wp.i18n;
const { registerPlugin } = window.wp.plugins;
const { ExperimentalOrderShippingPackages } = window.wc.blocksCheckout;
const render = () => {
return (
<ExperimentalOrderShippingPackages>
<div>{ __( 'Express Shipping', 'YOUR-TEXTDOMAIN' ) }</div>
</ExperimentalOrderShippingPackages>
);
};
registerPlugin( 'slot-and-fill-examples', {
render,
scope: 'woocommerce-checkout',
} );
```
Cart:
![Example of ExperimentalOrderShippingPackages in the Cart block](https://user-images.githubusercontent.com/6165348/118399054-2b4dec80-b653-11eb-94a0-989e2e6e362a.png)
Checkout:
![Example of ExperimentalOrderShippingPackages in the Checkout block](https://user-images.githubusercontent.com/6165348/118399133-90094700-b653-11eb-8ff0-c917947c199f.png)
### Passed parameters
- `collapsible`: `Boolean|undefined` If a shipping package panel should be collapsible or not, this is false in Checkout and undefined in Cart.
- `collapse`: `Boolean` If a panel should be collapsed by default, this is true if if panels are collapsible.
- `showItems`: `Boolean|undefined` If we should show the content of each package, this is undefined in Cart and Checkout and is left to the actual package logic to decide.
- `noResultsMessage`: A React element that you can render if there are no shipping options.
- `renderOption`: a render function that takes a rate object and returns a render option.
- `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188)
- `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here.
- `components`: an object containing components you can use to render your own shipping rates, it contains `ShippingRatesControlPackage`.
- `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout`
## ExperimentalOrderLocalPickupPackages
This slot renders inside the Checkout Pickup Options block in the Checkout block. It does not render in the Cart block.
```ts
const { __ } = window.wp.i18n;
const { registerPlugin } = window.wp.plugins;
const { ExperimentalOrderLocalPickupPackages } = window.wc.blocksCheckout;
const render = () => {
return (
<ExperimentalOrderLocalPickupPackages>
<div>
{ __(
'By using our convenient local pickup option, you can come to our store and pick up your order. We will send you and email when your order is ready for pickup.',
'YOUR-TEXTDOMAIN'
) }
</div>
</ExperimentalOrderLocalPickupPackages>
);
};
registerPlugin( 'slot-and-fill-examples', {
render,
scope: 'woocommerce-checkout',
} );
```
Checkout:
![Example of ExperimentalOrderLocalPickupPackages in the Checkout block](https://user-images.githubusercontent.com/5656702/222814945-a449d016-0621-4a70-b0f4-2ae1ce6487f1.png)
### Passed parameters
- `renderPickupLocation`: a render function that renders the address details of a local pickup option.
- `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188)
- `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here.
- `components`: an object containing components you can use to render your own pickup rates, it contains `ShippingRatesControlPackage` and `RadioControl`.
## ExperimentalDiscountsMeta
This slot renders below the `CouponCode` input.
```ts
const { __ } = window.wp.i18n;
const { registerPlugin } = window.wp.plugins;
const { ExperimentalDiscountsMeta } = window.wc.blocksCheckout;
const render = () => {
return (
<ExperimentalDiscountsMeta>
<div class="wc-block-components-totals-wrapper">
{ __( 'You have 98683 coins to spend ...', 'YOUR-TEXTDOMAIN' ) }
</div>
</ExperimentalDiscountsMeta>
);
};
registerPlugin( 'slot-and-fill-examples', {
render,
scope: 'woocommerce-checkout',
} );
```
Cart:
![Cart showing ExperimentalDiscountsMeta location](https://user-images.githubusercontent.com/5656702/122774218-ea27a880-d2a0-11eb-9450-11f119567f26.png)
Checkout:
![Checkout showing ExperimentalDiscountsMeta location](https://user-images.githubusercontent.com/5656702/122779606-efd3bd00-d2a5-11eb-8c84-6525eca5d704.png)
### Passed parameters
- `cart`: `wc/store/cart` data but in `camelCase` instead of `snake_case`. [Object breakdown.](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c00da597efe4c16fcf5481c213d8052ec5df3766/assets/js/type-defs/cart.ts#L172-L188)
- `extensions`: external data registered by third-party developers using `ExtendSchema`, if you used `ExtendSchema` on `wc/store/cart` you would find your data under your namespace here.
- `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout`

View File

@ -0,0 +1,8 @@
---
category_title: Payment Methods
category_slug: checkout-payment-methods
post_title: Checkout payment methods
---

View File

@ -0,0 +1,469 @@
---
post_title: Cart and Checkout - Checkout flow and events
menu_title: Checkout Flow and Events
tags: reference
---
This document gives an overview of the flow for the checkout in the WooCommerce checkout block, and some general architectural overviews.
The architecture of the Checkout Block is derived from the following principles:
- A single source of truth for data within the checkout flow.
- Provide a consistent interface for extension integrations (eg Payment methods). This interface protects the integrity of the checkout process and isolates extension logic from checkout logic. The checkout block handles _all_ communication with the server for processing the order. Extensions are able to react to and communicate with the checkout block via the provided interface.
- Checkout flow state is tracked by checkout status.
- Extensions are able to interact with the checkout flow via subscribing to emitted events.
Here's a high level overview of the flow:
![checkout flow diagram](https://user-images.githubusercontent.com/1628454/113739726-f8c9df00-96f7-11eb-80f1-78e25ccc88cb.png)
## General Concepts
### Tracking flow through status
At any point in the checkout lifecycle, components should be able to accurately detect the state of the checkout flow. This includes things like:
- Is something loading? What is loading?
- Is there an error? What is the error?
- is the checkout calculating totals?
Using simple booleans can be fine in some cases, but in others it can lead to complicated conditionals and bug prone code (especially for logic behaviour that reacts to various flow state).
To surface the flow state, the block uses statuses that are tracked in the various contexts. _As much as possible_ these statuses are set internally in reaction to various actions so there's no implementation needed in children components (components just have to _consume_ the status not set status).
The following statuses exist in the Checkout.
#### Checkout Data Store Status
There are various statuses that are exposed on the Checkout data store via selectors. All the selectors are detailed below and in the [Checkout API docs](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/internal-developers/block-client-apis/checkout/checkout-api.md).
You can use them in your component like so
```jsx
const { useSelect } = window.wp.data;
const { CHECKOUT_STORE_KEY } = window.wc.wcBlocksData;
const MyComponent = ( props ) => {
const isComplete = useSelect( ( select ) =>
select( CHECKOUT_STORE_KEY ).isComplete()
);
// do something with isComplete
};
```
The following boolean flags available related to status are:
**isIdle**: When the checkout status is `IDLE` this flag is true. Checkout will be this status after any change to checkout state after the block is loaded. It will also be this status when retrying a purchase is possible after processing happens with an error.
**isBeforeProcessing**: When the checkout status is `BEFORE_PROCESSING` this flag is true. Checkout will be this status when the user submits checkout for processing.
**isProcessing**: When the checkout status is `PROCESSING` this flag is true. Checkout will be this status when all the observers on the event emitted with the `BEFORE_PROCESSING` status are completed without error. It is during this status that the block will be sending a request to the server on the checkout endpoint for processing the order. **Note:** there are some checkout payment status changes that happen during this state as well (outlined in the `PaymentProvider` exposed statuses section).
**isAfterProcessing**: When the checkout status is `AFTER_PROCESSING` this flag is true. Checkout will have this status after the block receives the response from the server side processing request.
**isComplete**: When the checkout status is `COMPLETE` this flag is true. Checkout will have this status after all observers on the events emitted during the `AFTER_PROCESSING` status are completed successfully. When checkout is at this status, the shopper's browser will be redirected to the value of `redirectUrl` at that point (usually the `order-received` route).
#### Special States
The following are booleans exposed via the checkout provider that are independent from each other and checkout statuses but can be used in combination to react to various state in the checkout.
**isCalculating:** This is true when the total is being re-calculated for the order. There are numerous things that might trigger a recalculation of the total: coupons being added or removed, shipping rates updated, shipping rate selected etc. This flag consolidates all activity that might be occurring (including requests to the server that potentially affect calculation of totals). So instead of having to check each of those individual states you can reliably just check if this boolean is true (calculating) or false (not calculating).
**hasError:** This is true when anything in the checkout has created an error condition state. This might be validation errors, request errors, coupon application errors, payment processing errors etc.
### `ShippingProvider` Exposed Statuses
The shipping context provider exposes everything related to shipping in the checkout. Included in this are a set of error statuses that inform what error state the shipping context is in and the error state is affected by requests to the server on address changes, rate retrieval and selection.
Currently the error status may be one of `NONE`, `INVALID_ADDRESS` or `UNKNOWN` (note, this may change in the future).
The status is exposed on the `currentErrorStatus` object provided by the `useShippingDataContext` hook. This object has the following properties on it:
- `isPristine` and `isValid`: Both of these booleans are connected to the same error status. When the status is `NONE` the values for these booleans will be `true`. It basically means there is no shipping error.
- `hasInvalidAddress`: When the address provided for shipping is invalid, this will be true.
- `hasError`: This is `true` when the error status for shipping is either `UNKNOWN` or `hasInvalidAddress`.
### Payment Method Data Store Status
The status of the payment lives in the payment data store. You can query the status with the following selectors:
```jsx
const { select } = window.wp.data;
const { PAYMENT_STORE_KEY } = window.wc.wcBlocksData;
const MyComponent = ( props ) => {
const isPaymentIdle = select( PAYMENT_STORE_KEY ).isPaymentIdle();
const isExpressPaymentStarted =
select( PAYMENT_STORE_KEY ).isExpressPaymentStarted();
const isPaymentProcessing =
select( PAYMENT_STORE_KEY ).isPaymentProcessing();
const isPaymentReady = select( PAYMENT_STORE_KEY ).isPaymentReady();
const hasPaymentError = select( PAYMENT_STORE_KEY ).hasPaymentError();
// do something with the boolean values
};
```
The status here will help inform the current state of _client side_ processing for the payment and are updated via the store actions at different points throughout the checkout processing cycle. _Client side_ means the state of processing any payments by registered and active payment methods when the checkout form is submitted via those payment methods registered client side components. It's still possible that payment methods might have additional server side processing when the order is being processed but that is not reflected by these statuses (more in the [payment method integration doc](./payment-method-integration.md)).
The possible _internal_ statuses that may be set are:
- `IDLE`: This is the status when checkout is initialized and there are payment methods that are not doing anything. This status is also set whenever the checkout status is changed to `IDLE`.
- `EXPRESS_STARTED`: **Express Payment Methods Only** - This status is used when an express payment method has been triggered by the user clicking it's button. This flow happens before processing, usually in a modal window.
- `PROCESSING`: This status is set when the checkout status is `PROCESSING`, checkout `hasError` is false, checkout is not calculating, and the current payment status is not `FINISHED`. When this status is set, it will trigger the payment processing event emitter.
- `READY`: This status is set after all the observers hooked into the payment processing event have completed successfully. The `CheckoutProcessor` component uses this along with the checkout `PROCESSING` status to signal things are ready to send the order to the server with data for processing and to take payment
- `ERROR`: This status is set after an observer hooked into the payment processing event returns an error response. This in turn will end up causing the checkout `hasError` flag to be set to true.
### Emitting Events
Another tricky thing for extensibility, is providing opinionated, yet flexible interfaces for extensions to act and react to specific events in the flow. For stability, it's important that the core checkout flow _controls_ all communication to and from the server specific to checkout/order processing and leave extension specific requirements for the extension to handle. This allows for extensions to predictably interact with the checkout data and flow as needed without impacting other extensions hooking into it.
One of the most reliable ways to implement this type of extensibility is via the usage of an events system. Thus the various context providers:
- expose subscriber APIs for extensions to subscribe _observers_ to the events they want to react to.
- emit events at specific points of the checkout flow that in turn will feed data to the registered observers and, in some cases, react accordingly to the responses from observers.
One _**very important rule**_ when it comes to observers registered to any event emitter in this system is that they _cannot_ update context state. Updating state local to a specific component is okay but not any context or global state. The reason for this is that the observer callbacks are run sequentially at a specific point and thus subsequent observers registered to the same event will not react to any change in global/context state in earlier executed observers.
```jsx
const unsubscribe = emitter( myCallback );
```
You could substitute in whatever emitter you are registering for the `emitter` function. So for example if you are registering for the `onCheckoutValidation` event emitter, you'd have something like:
```jsx
const unsubscribe = onCheckoutValidation( myCallback );
```
You can also indicate what priority you want your observer to execute at. Lower priority is run before higher priority, so you can affect when your observer will run in the stack of observers registered to an emitter. You indicate priority via an number on the second argument:
```jsx
const unsubscribe = onCheckoutValidation( myCallback, 10 );
```
In the examples, `myCallback`, is your subscriber function. The subscriber function could receive data from the event emitter (described in the emitter details below) and may be expected to return a response in a specific shape (also described in the specific emitter details). The subscriber function can be a `Promise` and when the event emitter cycles through the registered observers it will await for any registered Promise to resolve.
Finally, the return value of the call to the emitter function is an unsubscribe function that can be used to unregister your observer. This is especially useful in a React component context where you need to make sure you unsubscribe the observer on component unmount. An example is usage in a `useEffect` hook:
```jsx
const MyComponent = ( { onCheckoutValidation } ) => {
useEffect( () => {
const unsubscribe = onCheckoutValidation( () => true );
return unsubscribe;
}, [ onCheckoutValidation ] );
return null;
};
```
**`Event Emitter Utilities`**
There are a bunch of utility methods that can be used related to events. These are available in `assets/js/base/context/event-emit/utils.ts` and can be imported as follows:
```jsx
import {
isSuccessResponse,
isErrorResponse,
isFailResponse,
noticeContexts,
responseTypes,
shouldRetry,
} from '@woocommerce/base-context';
};
```
The helper functions are described below:
- `isSuccessResponse`, `isErrorResponse` and `isFailResponse`: These are helper functions that receive a value and report via boolean whether the object is a type of response expected. For event emitters that receive responses from registered observers, a `type` property on the returned object from the observer indicates what type of response it is and event emitters will react according to that type. So for instance if an observer returned `{ type: 'success' }` the emitter could feed that to `isSuccessResponse` and it would return `true`. You can see an example of this being implemented for the payment processing emitted event [here](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/assets/js/base/context/cart-checkout/payment-methods/payment-method-data-context.js#L281-L307).
- `noticeContexts`: This is an object containing properties referencing areas where notices can be targeted in the checkout. The object has the following properties:
- `PAYMENTS`: This is a reference to the notice area in the payment methods step.
- `EXPRESS_PAYMENTS`: This is a reference to the notice area in the express payment methods step.
- `responseTypes`: This is an object containing properties referencing the various response types that can be returned by observers for some event emitters. It makes it easier for autocompleting the types and avoiding typos due to human error. The types are `SUCCESS`, `FAIL`, `ERROR`. The values for these types also correspond to the [payment status types](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/Payments/PaymentResult.php#L21) from the [checkout endpoint response from the server](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L113).
- `shouldRetry`: This is a function containing the logic whether the checkout flow should allow the user to retry the payment after a previous payment failed. It receives the `response` object and by default checks whether the `retry` property is true/undefined or false. Refer to the [`onCheckoutSuccess`](#oncheckoutsuccess) documentation for more details.
Note: `noticeContexts` and `responseTypes` are exposed to payment methods via the `emitResponse` prop given to their component:
```jsx
const MyPaymentMethodComponent = ( { emitResponse } ) => {
const { noticeContexts, responseTypes } = emitResponse;
// other logic for payment method...
};
```
The following event emitters are available to extensions to register observers to:
### `onCheckoutValidation`
Observers registered to this event emitter will receive nothing as an argument. Also, all observers will be executed before the checkout handles the responses from the emitters. Observers registered to this emitter can return `true` if they have nothing to communicate back to checkout, `false` if they want checkout to go back to `IDLE` status state, or an object with any of the following properties:
- `errorMessage`: This will be added as an error notice on the checkout context.
- `validationErrors`: This will be set as inline validation errors on checkout fields. If your observer wants to trigger validation errors it can use the following shape for the errors:
- This is an object where keys are the property names the validation error is for (that correspond to a checkout field, eg `country` or `coupon`) and values are the error message describing the validation problem.
This event is emitted when the checkout status is `BEFORE_PROCESSING` (which happens at validation time, after the checkout form submission is triggered by the user - or Express Payment methods).
If all observers return `true` for this event, then the checkout status will be changed to `PROCESSING`.
This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component:
_For internal development:_
```jsx
import { useCheckoutContext } from '@woocommerce/base-contexts';
import { useEffect } from '@wordpress/element';
const Component = () => {
const { onCheckoutValidation } = useCheckoutContext();
useEffect( () => {
const unsubscribe = onCheckoutValidation( () => true );
return unsubscribe;
}, [ onCheckoutValidation ] );
return null;
};
```
_For registered payment method components:_
```jsx
const { useEffect } = window.wp.element;
const PaymentMethodComponent = ( { eventRegistration } ) => {
const { onCheckoutValidation } = eventRegistration;
useEffect( () => {
const unsubscribe = onCheckoutValidation( () => true );
return unsubscribe;
}, [ onCheckoutValidation ] );
};
```
### ~~`onPaymentProcessing`~~
This is now deprecated and replaced by the `onPaymentSetup` event emitter.
### `onPaymentSetup`
This event emitter was fired when the payment method context status is `PROCESSING` and that status is set when the checkout status is `PROCESSING`, checkout `hasError` is false, checkout is not calculating, and the current payment status is not `FINISHED`.
This event emitter will execute through each registered observer (passing in nothing as an argument) _until_ an observer returns a non-truthy value at which point it will _abort_ further execution of registered observers.
When a payment method returns a non-truthy value, if it returns a valid response type the event emitter will update various internal statuses according to the response. Here's the possible response types that will get handled by the emitter:
#### Success
A successful response should be given when the user's entered data is correct and the payment checks are successful. A response is considered successful if, at a minimum, it is an object with this shape:
```js
const successResponse = { type: 'success' };
```
When a success response is returned, the payment method context status will be changed to `SUCCESS`. In addition, including any of the additional properties will result in extra actions:
- `paymentMethodData`: The contents of this object will be included as the value for `payment_data` when checkout sends a request to the checkout endpoint for processing the order. This is useful if a payment method does additional server side processing.
- `billingAddress`: This allows payment methods to update any billing data information in the checkout (typically used by Express payment methods) so it's included in the checkout processing request to the server. This data should be in the [shape outlined here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts).
- `shippingAddress`: This allows payment methods to update any shipping data information for the order (typically used by Express payment methods) so it's included in the checkout processing request to the server. This data should be in the [shape outlined here](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts).
If `billingAddress` or `shippingAddress` properties aren't in the response object, then the state for the data is left alone.
#### Fail
A fail response should be given when there is an error with the payment processing. A response is considered a fail response when it is an object with this shape:
```js
const failResponse = { type: 'failure' };
```
When a fail response is returned by an observer, the payment method context status will be changed to `FAIL`. In addition, including any of the following properties will result in extra actions:
- `message`: The string provided here will be set as an error notice in the checkout.
- `messageContext`: If provided, this will target the given area for the error notice (this is where `noticeContexts` mentioned earlier come in to play). Otherwise the notice will be added to the `noticeContexts.PAYMENTS` area.
- `paymentMethodData`: (same as for success responses).
- `billingAddress`: (same as for success responses).
#### Error
An error response should be given when there is an error with the user input on the checkout form. A response is considered an error response when it is an object with this shape:
```js
const errorResponse = { type: 'error' };
```
When an error response is returned by an observer, the payment method context status will be changed to `ERROR`. In addition, including any of the following properties will result in extra actions:
- `message`: The string provided here will be set as an error notice.
- `messageContext`: If provided, this will target the given area for the error notice (this is where `noticeContexts` mentioned earlier come in to play). Otherwise, the notice will be added to the `noticeContexts.PAYMENTS` area.
- `validationErrors`: This will be set as inline validation errors on checkout fields. If your observer wants to trigger validation errors it can use the following shape for the errors:
- This is an object where keys are the property names the validation error is for (that correspond to a checkout field, eg `country` or `coupon`) and values are the error message describing the validation problem.
If the response object doesn't match any of the above conditions, then the fallback is to set the payment status as `SUCCESS`.
When the payment status is set to `SUCCESS` and the checkout status is `PROCESSING`, the `CheckoutProcessor` component will trigger the request to the server for processing the order.
This event emitter subscriber can be obtained via the checkout context using the `usePaymentEventsContext` hook or to payment method extensions as a prop on their registered component:
_For internal development:_
```jsx
import { usePaymentEventsContext } from '@woocommerce/base-contexts';
import { useEffect } from '@wordpress/element';
const Component = () => {
const { onPaymentSetup } = usePaymentEventsContext();
useEffect( () => {
const unsubscribe = onPaymentSetup( () => true );
return unsubscribe;
}, [ onPaymentSetup ] );
return null;
};
```
_For registered payment method components:_
```jsx
const { useEffect } = window.wp.element;
const PaymentMethodComponent = ( { eventRegistration } ) => {
const { onPaymentSetup } = eventRegistration;
useEffect( () => {
const unsubscribe = onPaymentSetup( () => true );
return unsubscribe;
}, [ onPaymentSetup ] );
};
```
### `onCheckoutSuccess`
This event emitter is fired when the checkout status is `AFTER_PROCESSING` and the checkout `hasError` state is false. The `AFTER_PROCESSING` status is set by the `CheckoutProcessor` component after receiving a response from the server for the checkout processing request.
Observers registered to this event emitter will receive the following object as an argument:
```js
const onCheckoutProcessingData = {
redirectUrl,
orderId,
customerId,
orderNotes,
paymentResult,
};
```
The properties are:
- `redirectUrl`: This is a string that is the url the checkout will redirect to as returned by the processing on the server.
- `orderId`: Is the id of the current order being processed.
- `customerId`: Is the id for the customer making the purchase (that is attached to the order).
- `orderNotes`: This will be any custom note the customer left on the order.
- `paymentResult`: This is the value of [`payment_result` from the /checkout StoreApi response](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L138). The data exposed on this object is (via the object properties):
- `paymentStatus`: Whatever the status is for the payment after it was processed server side. Will be one of `success`, `failure`, `pending`, `error`.
- `paymentDetails`: This will be an arbitrary object that contains any data the payment method processing server side sends back to the client in the checkout processing response. Payment methods are able to hook in on the processing server side and set this data for returning.
This event emitter will invoke each registered observer until a response from any of the registered observers does not equal `true`. At that point any remaining non-invoked observers will be skipped and the response from the observer triggering the abort will be processed.
This emitter will handle a `success` response type (`{ type: success }`) by setting the checkout status to `COMPLETE`. Along with that, if the response includes `redirectUrl` then the checkout will redirect to the given address.
This emitter will also handle a `failure` response type or an `error` response type and if no valid type is detected it will treat it as an `error` response type.
In all cases, if there are the following properties in the response, additional actions will happen:
- `message`: This string will be added as an error notice.
- `messageContext`: If present, the notice will be configured to show in the designated notice area (otherwise it will just be a general notice for the checkout block).
- `retry`: If this is `true` or not defined, then the checkout status will be set to `IDLE`. This basically means that the error is recoverable (for example try a different payment method) and so checkout will be reset to `IDLE` for another attempt by the shopper. If this is `false`, then the checkout status is set to `COMPLETE` and the checkout will redirect to whatever is currently set as the `redirectUrl`.
- `redirectUrl`: If this is present, then the checkout will redirect to this url when the status is `COMPLETE`.
If all observers return `true`, then the checkout status will just be set to `COMPLETE`.
This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component:
_For internal development:_
```jsx
import { useCheckoutContext } from '@woocommerce/base-contexts';
import { useEffect } from '@wordpress/element';
const Component = () => {
const { onCheckoutSuccess } = useCheckoutContext();
useEffect( () => {
const unsubscribe = onCheckoutSuccess( () => true );
return unsubscribe;
}, [ onCheckoutSuccess ] );
return null;
};
```
_For registered payment method components:_
```jsx
const { useEffect } = window.wp.element;
const PaymentMethodComponent = ( { eventRegistration } ) => {
const { onCheckoutSuccess } = eventRegistration;
useEffect( () => {
const unsubscribe = onCheckoutSuccess( () => true );
return unsubscribe;
}, [ onCheckoutSuccess ] );
};
```
### `onCheckoutFail`
This event emitter is fired when the checkout status is `AFTER_PROCESSING` and the checkout `hasError` state is `true`. The `AFTER_PROCESSING` status is set by the `CheckoutProcessor` component after receiving a response from the server for the checkout processing request.
Observers registered to this emitter will receive the same data package as those registered to `onCheckoutSuccess`.
The response from the first observer returning a value that does not `===` true will be handled similarly as the `onCheckoutSuccess` except it only handles when the type is `error` or `failure`.
If all observers return `true`, then the checkout status will just be set to `IDLE` and a default error notice will be shown in the checkout context.
This event emitter subscriber can be obtained via the checkout context using the `useCheckoutContext` hook or to payment method extensions as a prop on their registered component:
_For internal development:_
```jsx
import { useCheckoutContext } from '@woocommerce/base-contexts';
import { useEffect } from '@wordpress/element';
const Component = () => {
const { onCheckoutFail } = useCheckoutContext();
useEffect( () => {
const unsubscribe = onCheckoutFail( () => true );
return unsubscribe;
}, [ onCheckoutFail ] );
return null;
};
```
_For registered payment method components:_
```jsx
const { useEffect } = window.wp.element;
const PaymentMethodComponent = ( { eventRegistration } ) => {
const { onCheckoutFail } = eventRegistration;
useEffect( () => {
const unsubscribe = onCheckoutFail( () => true );
return unsubscribe;
}, [ onCheckoutFail ] );
};
```
### `onShippingRateSuccess`
This event emitter is fired when shipping rates are not loading and the shipping data context error state is `NONE` and there are shipping rates available.
This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current shipping rates retrieved from the server.
### `onShippingRateFail`
This event emitter is fired when shipping rates are not loading and the shipping data context error state is `UNKNOWN` or `INVALID_ADDRESS`.
This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current error status in the context.
### `onShippingRateSelectSuccess`
This event emitter is fired when a shipping rate selection is not being persisted to the server and there are selected rates available and the current error status in the context is `NONE`.
This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current selected rates.
### `onShippingRateSelectFail`
This event emitter is fired when a shipping rate selection is not being persisted to the server and the shipping data context error state is `UNKNOWN` or `INVALID_ADDRESS`.
This event emitter doesn't care about any registered observer response and will simply execute all registered observers passing them the current error status in the context.

View File

@ -0,0 +1,156 @@
---
post_title: Cart and Checkout - Filtering payment methods in the Checkout block
menu_title: Filtering Payment Methods
tags: how-to
---
<!-- markdownlint-disable MD024 -->
## The problem
You're an extension developer, and your extension is conditionally hiding payment gateways on the checkout step. You need to be able to hide payment gateways on the Checkout block using a front-end extensibility point.
### The solution
WooCommerce Blocks provides a function called `registerPaymentMethodExtensionCallbacks` which allows extensions to register callbacks for specific payment methods to determine if they can make payments.
### Importing
#### Aliased import
```js
import { registerPaymentMethodExtensionCallbacks } from '@woocommerce/blocks-registry';
```
#### `wc global`
```js
const { registerPaymentMethodExtensionCallbacks } = window.wc.wcBlocksRegistry;
```
### Signature
| Parameter | Description | Type |
| ----------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| `namespace` | Unique string to identify your extension. Choose something that eliminates a name collision with another extension. | `string` |
| `callbacks` | An object containing callbacks registered for different payment methods | Record< string, CanMakePaymentExtensionCallback > |
Read more below about [callbacks](#callbacks-registered-for-payment-methods).
#### Extension namespace collision
When trying to register callbacks under an extension namespace already used with `registerPaymentMethodExtensionCallbacks`, the registration will be aborted and you will be notified that you are not using a unique namespace. This will be shown in the JavaScript console.
### Usage example
```js
registerPaymentMethodExtensionCallbacks( 'my-hypothetical-extension', {
cod: ( arg ) => {
return arg.shippingAddress.city === 'Berlin';
},
cheque: ( arg ) => {
return false;
},
} );
```
### Callbacks registered for payment methods
Extensions can register only one callback per payment method:
```text
payment_method_name: ( arg ) => {...}
```
`payment_method_name` is the value of the [name](payment-method-integration.md#name-required) property used when the payment method was registered with WooCommerce Blocks.
The registered callbacks are used to determine whether the corresponding payment method should be available as an option for the shopper. The function will be passed an object containing data about the current order.
```ts
type CanMakePaymentExtensionCallback = (
cartData: CanMakePaymentArgument
) => boolean;
```
Each callback will have access to the information bellow
```ts
interface CanMakePaymentArgument {
cart: Cart;
cartTotals: CartTotals;
cartNeedsShipping: boolean;
billingAddress: CartResponseBillingAddress;
shippingAddress: CartResponseShippingAddress;
selectedShippingMethods: Record< string, unknown >;
paymentRequirements: Array< string >;
}
```
If you need data that is not available in the parameter received by the callback you can consider [exposing your data in the Store API](https://github.com/woocommerce/woocommerce/blob/1675c63bba94c59703f57c7ef06e7deff8fd6bba/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/rest-api/extend-rest-api-add-data.md).
## Filtering payment methods using requirements
### The problem
Your extension has added functionality to your store in such a way that only specific payment gateways can process orders that contain certain products.
Using the example of `Bookings` if the shopper adds a `Bookable` product to their cart, for example a stay in a hotel, and you, the merchant, want to confirm all bookings before taking payment. You would still need to capture the customer's checkout details but not their payment method at that point.
### The solution
To allow the shopper to check out without entering payment details, but still require them to fill in the other checkout details it is possible to create a new payment method which will handle carts containing a `Bookable` item.
Using the `supports` configuration of payment methods it is possible to prevent other payment methods (such as credit card, PayPal etc.) from being used to check out, and only allow the one your extension has added to appear in the Checkout block.
For more information on how to register a payment method with WooCommerce Blocks, please refer to the [Payment method integration](./payment-method-integration.md) documentation.
### Basic usage
Following the documentation for registering payment methods linked above, you should register your payment method with a unique `supports` feature, for example `booking_availability`. This will be used to isolate it and prevent other methods from displaying.
First you will need to create a function that will perform the checks on the cart to determine what the specific payment requirements of the cart are. Below is an example of doing this for our `Bookable` products.
Then you will need to use the `register_payment_requirements` on the `ExtendSchema` class to tell the Checkout block to execute a callback to check for requirements.
### Putting it all together
This code example assumes there is some class called `Pseudo_Booking_Class` that has the `cart_contains_bookable_product` method available. The implementation of this method is not relevant here.
```php
/**
* Check the content of the cart and add required payment methods.
*
*
* @return array list of features required by cart items.
*/
function inject_payment_feature_requirements_for_cart_api() {
// Cart contains a bookable product, so return an array containing our requirement of booking_availability.
if ( Pseudo_Booking_Class::cart_contains_bookable_product() ) {
return array( 'booking_availability' );
}
// No bookable products in the cart, no need to add anything.
return array();
}
```
To summarise the above: if there's a bookable product in the cart then this function will return an array containing `booking_availability`, otherwise it will return an empty array.
The next step will tell the `ExtendSchema` class to execute this callback when checking which payment methods to display.
To do this you could use the following code:
```php
add_action('woocommerce_blocks_loaded', function() {
woocommerce_store_api_register_payment_requirements(
array(
'data_callback' => 'inject_payment_feature_requirements_for_cart_api',
)
);
});
```
It is important to note the comment in this code block, you must not instantiate your own version of `ExtendSchema`.
If you've added your payment method correctly with the correct `supports` values then when you reach the checkout page with a `Bookable` item in your cart, any method that does not `supports` the `booking_availability` requirement should not display, while yours, the one that _does_ support this requirement _will_ display.

View File

@ -0,0 +1,237 @@
---
post_title: Cart and Checkout - Payment method integration for the Checkout block
menu_title: Payment Method Integration
tags: reference
---
## Client Side integration
The client side integration consists of an API for registering both _express_ payment methods (those that consist of a one-button payment process initiated by the shopper such as Stripe, ApplePay, or GooglePay), and payment methods such as _cheque_, PayPal Standard, or Stripe Credit Card.
In both cases, the client side integration is done using registration methods exposed on the `blocks-registry` API. You can access this via the `wc` global in a WooCommerce environment (`wc.wcBlocksRegistry`).
> Note: In your build process, you could do something similar to what is done in the blocks repository which [aliases this API as an external on `@woocommerce/blocks-registry`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/bin/webpack-helpers.js#L16-L35).
### Express payment methods - `registerExpressPaymentMethod( options )`
![Express Payment Area](https://user-images.githubusercontent.com/1429108/79565636-17fed500-807f-11ea-8e5d-9af32e43b71d.png)
To register an express payment method, you use the `registerExpressPaymentMethod` function from the blocks registry. An example of importing this for use in your JavaScript file is:
#### `registerExpressPaymentMethod` Aliased import
```js
import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
```
#### `registerExpressPaymentMethod` on the `wc` global
```js
const { registerExpressPaymentMethod } = window.wc.wcBlocksRegistry;
```
#### The `registerExpressPaymentMethod` registration options
The registry function expects a JavaScript object with options specific to the payment method:
```js
registerExpressPaymentMethod( options );
```
The options you feed the configuration instance should be an object in this shape (see `ExpressPaymentMethodConfiguration` typedef):
```js
const options = {
name: 'my_payment_method',
content: <div>A React node</div>,
edit: <div>A React node</div>,
canMakePayment: () => true,
paymentMethodId: 'new_payment_method',
supports: {
features: [],
},
};
```
Here's some more details on the configuration options:
#### `name` (required)
This should be a unique string (wise to try to pick something unique for your gateway that wouldn't be used by another implementation) that is used as the identifier for the gateway client side. If `paymentMethodId` is not provided, `name` is used for `paymentMethodId` as well.
#### `content` (required)
This should be a React node that will output in the express payment method area when the block is rendered in the frontend. It will be cloned in the rendering process. When cloned, this React node will receive props passed in from the checkout payment method interface that will allow your component to interact with checkout data (more on [these props later](#props-fed-to-payment-method-nodes)).
#### `edit` (required)
This should be a React node that will be output in the express payment method area when the block is rendered in the editor. It will be cloned in the rendering process. When cloned, this React node will receive props from the payment method interface to checkout (but they will contain preview data).
#### `canMakePayment` (required)
A callback to determine whether the payment method should be available as an option for the shopper. The function will be passed an object containing data about the current order.
```ts
canMakePayment( {
cart: Cart,
cartTotals: CartTotals,
cartNeedsShipping: boolean,
shippingAddress: CartShippingAddress,
billingAddress: CartBillingAddress,
selectedShippingMethods: Record<string,unknown>,
paymentRequirements: string[],
} )
```
Returns a boolean value - true if payment method is available for use. If your gateway needs to perform async initialization to determine availability, you can return a promise (resolving to boolean). This allows a payment method to be hidden based on the cart, e.g. if the cart has physical/shippable products (example: [`Cash on delivery`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/assets/js/payment-method-extensions/payment-methods/cod/index.js#L48-L70)); or for payment methods to control whether they are available depending on other conditions.
`canMakePayment` only runs on the frontend of the Store. In editor context, rather than use `canMakePayment`, the editor will assume the payment method is available (true) so that the defined `edit` component is shown to the merchant.
**Keep in mind this function could be invoked multiple times in the lifecycle of the checkout and thus any expensive logic in the callback provided on this property should be memoized.**
#### `paymentMethodId`
This is the only optional configuration object. The value of this property is what will accompany the checkout processing request to the server and is used to identify what payment method gateway class to load for processing the payment (if the shopper selected the gateway). So for instance if this is `stripe`, then `WC_Gateway_Stripe::process_payment` will be invoked for processing the payment.
#### `supports:features`
This is an array of payment features supported by the gateway. It is used to crosscheck if the payment method can be used for the content of the cart. By default payment methods should support at least `products` feature. If no value is provided then this assumes that `['products']` are supported.
---
### Payment Methods - `registerPaymentMethod( options )`
![Image 2021-02-24 at 4 24 05 PM](https://user-images.githubusercontent.com/1429108/109067640-c7073680-76bc-11eb-98e5-f04d35ddef99.jpg)
To register a payment method, you use the `registerPaymentMethod` function from the blocks registry. An example of importing this for use in your JavaScript file is:
#### `registerPaymentMethod` Aliased import
```js
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
```
#### `registerPaymentMethod` on the `wc` global
```js
const { registerPaymentMethod } = window.wc.wcBlocksRegistry;
```
#### The `registerPaymentMethod` registration options
The registry function expects a JavaScript object with options specific to the payment method (see `PaymentMethodRegistrationOptions` typedef):
```js
registerPaymentMethod( options );
```
The options you feed the configuration instance are the same as those for express payment methods with the following additions:
- `savedTokenComponent`: This should be a React node that contains logic handling any processing your payment method has to do with saved payment methods if your payment method supports them. This component will be rendered whenever a customer's saved token using your payment method for processing is selected for making the purchase.
- `label`: This should be a React node that will be used to output the label for the option where the payment methods are. For example it might be `<strong>Credit/Debit Cart</strong>` or you might output images.
- `ariaLabel`: This is the label that will be read out via screen-readers when the payment method is selected.
- `placeOrderButtonLabel`: This is an optional label which will change the default "Place Order" button text to something else when the payment method is selected. As an example, the PayPal Standard payment method [changes the text of the button to "Proceed to PayPal"](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/assets/js/payment-method-extensions/payment-methods/paypal/index.js#L37-L40) when it is selected as the payment method for checkout because the payment method takes the shopper offsite to PayPal for completing the payment.
- `supports`: This is an object containing information about what features your payment method supports. The following keys are valid here:
- `showSavedCards`: This value will determine whether saved cards associated with your payment method are shown to the customer.
- `showSaveOption`: This value will control whether to show the checkbox which allows customers to save their payment method for future payments.
### Props Fed to Payment Method Nodes
A big part of the payment method integration is the interface that is exposed for payment methods to use via props when the node provided is cloned and rendered on block mount. While all the props are listed below, you can find more details about what the props reference, their types etc via the [typedefs described in this file](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/assets/js/types/type-defs/payment-method-interface.ts).
| Property | Type | Description | Values |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `activePaymentMethod` | String | The slug of the current active payment method in the checkout. | - |
| `billing` | Object | Contains everything related to billing. | `billingAddress`, `cartTotal`, `currency`, `cartTotalItems`, `displayPricesIncludingTax`, `appliedCoupons`, `customerId` |
| `cartData` | Object | Data exposed from the cart including items, fees, and any registered extension data. Note that this data should be treated as immutable (should not be modified/mutated) or it will result in errors in your application. | `cartItems`, `cartFees`, `extensions` |
| `checkoutStatus` | Object | The current checkout status exposed as various boolean state. | `isCalculating`, `isComplete`, `isIdle`, `isProcessing` |
| `components` | Object | It exposes React components that can be implemented by your payment method for various common interface elements used by payment methods. | <!-- markdownlint-disable-line no-inline-html --><ul><li>`ValidationInputError`: a container for holding validation errors which typically you'll include after any inputs.</li><li>[`PaymentMethodLabel`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/assets/js/payment-method-extensions/payment-methods/paypal/index.js#L37-L40): use this component for the payment method label, including an optional icon.</li><li>`PaymentMethodIcons`: a React component used for displaying payment method icons.</li><li>- `LoadingMask`: a wrapper component that handles displaying a loading state when the isLoading prop is true. Exposes the [LoadingMask component](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c9074a4941919987dbad16a80f358b960336a09d/assets/js/base/components/loading-mask/index.js)</li></ul> |
| `emitResponse` | Object | Contains some constants that can be helpful when using the event emitter. Read the _[Emitting Events](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e267cd96a4329a4eeef816b2ef627e113ebb72a5/docs/extensibility/checkout-flow-and-events.md#emitting-events)_ section for more details. | <!-- markdownlint-disable-line no-inline-html --><ul><li>`noticeContexts`: This is an object containing properties referencing areas where notices can be targeted in the checkout. The object has the following properties: <ul><li>`PAYMENTS`: This is a reference to the notice area in the payment methods step.</li><li>`EXPRESS_PAYMENTS`: This is a reference to the notice area in the express payment methods step.</li></ul></li><li>`responseTypes`: This is an object containing properties referencing the various response types that can be returned by observers for some event emitters. It makes it easier for autocompleting the types and avoiding typos due to human error. The types are `SUCCESS`, `FAIL`, `ERROR`. The values for these types also correspond to the [payment status types](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/Payments/PaymentResult.php#L21) from the [checkout endpoint response from the server](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L113).</li></ul> |
| `eventRegistration` | object | Contains all the checkout event emitter registration functions. These are functions the payment method can register observers on to interact with various points in the checkout flow (see [this doc](./checkout-flow-and-events.md) for more info). | `onCheckoutValidation`, `onCheckoutSuccess`, `onCheckoutFail`, `onPaymentSetup`, `onShippingRateSuccess`, `onShippingRateFail`, `onShippingRateSelectSuccess`, `onShippingRateSelectFail` |
| `onClick` | Function | **Provided to express payment methods** that should be triggered when the payment method button is clicked (which will signal to checkout the payment method has taken over payment processing) | - |
| `onClose` | Function | **Provided to express payment methods** that should be triggered when the express payment method modal closes and control is returned to checkout. | - |
| `onSubmit` | Function | Submits the checkout and begins processing | - |
| `paymentStatus` | Object | Various payment status helpers. Note, your payment method does not have to handle setting this status client side. Checkout will handle this via the responses your payment method gives from observers registered to [checkout event emitters](./checkout-flow-and-events.md). | `isPristine`, `isStarted`, `isProcessing`, `isFinished`, `hasError`, `hasFailed`, `isSuccessful` (see below for explanation) |
| `setExpressPaymentError` | Function | Receives a string and allows express payment methods to set an error notice for the express payment area on demand. This can be necessary because some express payment method processing might happen outside of checkout events. | - |
| `shippingData` | Object | Contains all shipping related data (outside of the shipping status). | `shippingRates`, `shippingRatesLoading`, `selectedRates`, `setSelectedRates`, `isSelectingRate`, `shippingAddress`, `setShippingAddress`, and `needsShipping` |
| `shippingStatus` | Object | Various shipping status helpers. | <!-- markdownlint-disable-line no-inline-html --><ul><li>`shippingErrorStatus`: an object with various error statuses that might exist for shipping</li><li>`shippingErrorTypes`: an object containing all the possible types for shipping error status</li></ul> |
| `shouldSavePayment` | Boolean | Indicates whether or not the shopper has selected to save their payment method details (for payment methods that support saved payments). True if selected, false otherwise. Defaults to false. | - |
- `isPristine`: This is true when the current payment status is `PRISTINE`.
- `isStarted`: This is true when the current payment status is `EXPRESS_STARTED`.
- `isProcessing`: This is true when the current payment status is `PROCESSING`.
- `isFinished`: This is true when the current payment status is one of `ERROR`, `FAILED`, or`SUCCESS`.
- `hasError`: This is true when the current payment status is `ERROR`.
- `hasFailed`: This is true when the current payment status is `FAILED`.
- `isSuccessful`: This is true when the current payment status is `SUCCESS`
Any registered `savedTokenComponent` node will also receive a `token` prop which includes the id for the selected saved token in case your payment method needs to use it for some internal logic. However, keep in mind, this is just the id representing this token in the database (and the value of the radio input the shopper checked), not the actual customer payment token (since processing using that usually happens on the server for security).
## Server Side Integration
### Processing Payment
The checkout block currently has legacy handling for payment processing. It converts incoming `payment_data` provided by the client-side payment method to `$_POST` and calls the payment gateway's `process_payment` function. If you already have a WooCommerce Payment method extension integrated with the existing shortcode checkout flow, the checkout block's legacy handling will take care of processing your payment for you on the server side. However, If your payment method hooks into the core checkout `process_checkout` function in any way, you will need to account for this behavior and make appropriate adjustments. (See the section below about hooking into the checkout process via the Store API.)
See an example of [Passing a value from the client through to server side payment processing](https://github.com/woocommerce/woocommerce-blocks/blob/62243e1731a0773f51b81fb8406ebc2e8b180b40/docs/internal-developers/block-client-apis/checkout/checkout-api.md#passing-a-value-from-the-client-through-to-server-side-payment-processing)
### Registering Assets
Implementing the correct loading of your client side asset registration is tricky for the blocks integration. This is because there are some dependencies on the _loading order_ of dependent assets in the request. To remove the complexity of this for extension consumers here, the server side API interface helps with ensuring you can register any assets and data to pass along to your client side payment method from the server and handles the correct loading order of those assets.
First, you create a class that extends `Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType` (or you can implement the `Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface`, but you get some functionality for free via the abstract class).
In your class:
- Define a `name` property (This property is a string used to reference your payment method. It is important to use the same name as in your client-side JavaScript payment method registration, see `name: 'my_payment_method'` in the options object above).
- Define an `initialize` function. This function will get called during the server side initialization process and is a good place to put any settings population etc. Basically anything you need to do to initialize your gateway. **Note, this will be called on every request so don't put anything expensive here.**
- Define an `is_active` function. This should return whether the payment method is active or not.
- Define a `get_payment_method_script_handles` function. In this function you should register your payment method scripts (using `wp_register_script`) and then return the script handles you registered with. This will be used to add your payment method as a dependency of the checkout script and thus take sure of loading it correctly. **Note:** You should still make sure any other asset dependencies your script has are registered properly here, if you're using Webpack to build your assets, you may want to use the [WooCommerce Webpack Dependency Extraction Plugin](https://www.npmjs.com/package/@woocommerce/dependency-extraction-webpack-plugin) to make this easier for you.
- Define a `get_payment_method_script_handles_for_admin` function. Include this if your payment method has a script you _only_ want to load in the editor context for the checkout block. Include here any script from `get_payment_method_script_handles` that is also needed in the admin.
- Define a `get_payment_method_data` function. You can return from this function an associative array of data you want to be exposed for your payment method client side. This data will be available client side via `wc.wcSettings.getSetting`. So for instance if you assigned `stripe` as the value of the `name` property for this class, client side you can access any data via: `wc.wcSettings.getSetting( 'stripe_data' )`. That would return an object matching the shape of the associative array you returned from this function.
### Hooking into the Checkout processing by the Store API
There may be some cases where the fallback legacy processing of Checkout requests from the StoreAPI mentioned earlier doesn't work for your existing payment method integration. For these cases, there is also an [action hook you can implement to hook into the server side processing of the order](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/308e968c700028180cab391f2223eb0a43dd2d4d/src/RestApi/StoreApi/Routes/Checkout.php#L350-L361). **Note:** a good place to register your callback on this hook is in the `initialize` method of the payment method class you created from the above instructions.
This hook is the _preferred_ place to hook in your payment processing and if you set a status on the provided `PaymentResult` object, then the legacy processing will be ignored for your payment method. Hook callbacks will receive:
[`Automattic\WooCommerce\StoreApi\Payments\PaymentContext`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/StoreApi/Payments/PaymentContext.php)
This contains various details about the payment extracted from the checkout processing request. Notably is the `payment_data` property that will contain an associative array of data your payment method client side provided to checkout. It also contains a string value for `payment_method` which contains the `paymentMethodId` value for the active payment method used during checkout processing. So you can use this to determine whether your payment method processes this data or not.
[`Automattic\WooCommerce\StoreApi\Payments\PaymentResult`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/StoreApi/Payments/PaymentResult.php)
This contains various details about the payment result returned to the client and exposed on the `onAfterCheckoutProcessingWithSucces/WithError` event. Server side, your payment method can use this to:
- set the status to return for the payment method (one of `success`, `failure`, `pending`, `error`).
- set a redirect url.
- set any additional payment details (in case you need to return something for your client to further process with).
### Putting it all together
So you've created a class extending `Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType`, but you still need to _register_ this with the server side handling of payment methods. In order to do this you need to register a callback on the `woocommerce_blocks_payment_method_type_registration` action. Your callback will receive an instance of `Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry` which has a `register` method for registering an instance of the class you created. It's also recommended that you register your callback on this action within the context of a callback on the `woocommerce_blocks_loaded` action.
> Note: With Cart and Checkout Blocks currently only available in the WooCommerce Blocks Feature plugin, you will want to make sure you check for the availability of the `Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType` class before registering your payment method integration server side.
So for example, assuming your class that extends `Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType` is named `MyPaymentMethod`. You would have this somewhere in your extension's bootstrap:
```php
use MyPlugin\MyPaymentMethod;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
add_action( 'woocommerce_blocks_loaded', 'my_extension_woocommerce_blocks_support' );
function my_extension_woocommerce_blocks_support() {
if ( class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) {
add_action(
'woocommerce_blocks_payment_method_type_registration',
function( PaymentMethodRegistry $payment_method_registry ) {
$payment_method_registry->register( new MyPaymentMethod );
}
);
}
}
```
As an example, you can see how the Stripe extension adds it's integration in this [pull request](https://github.com/woocommerce/woocommerce-gateway-stripe/pull/1467/files).

View File

@ -0,0 +1,37 @@
---
post_title: Cart and Checkout - DOM events
menu_title: DOM Events
tags: reference
---
Some blocks need to react to certain events in order to display the most up to date data or behave in a certain way. That's the case of the Cart block, for example, that must listen to 'add to cart' events in order to update the cart contents; or the Mini-Cart block, that gets opened every time a product is added to the cart.
## WooCommerce core events in WooCommerce Blocks
WooCommerce core uses jQuery events to trigger and listen to certain events, like when a product is added or removed from the cart. In WooCommerce Blocks, we moved away from using jQuery, but we still need to listen to those events. To achieve that, we have a utility named [`translatejQueryEventToNative()`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/3f7c3e517d7bf13008a22d0c2eb89434a9c35ae7/assets/js/base/utils/legacy-events.ts#L79-L106) that listens to those jQuery events, and every time one is triggered, it triggers an associated DOM native event (with the `wc-blocks_` prefix).
## WooCommerce Blocks events
### `wc-blocks_adding_to_cart`
This event is the equivalent to the jQuery event `adding_to_cart` triggered by WooCommerce core. It indicates that the process of adding a product to the cart was sent to the server, but there is still no indication on whether the product was successfully added or not.
_Example usage in WC Blocks:_ Mini-Cart block listens to this event to append its dependencies.
### `wc-blocks_added_to_cart`
This event is the equivalent to the jQuery event `added_to_cart` triggered by WooCommerce core. It indicates that the process of adding a product to the cart has finished with success.
_Example usage in WC Blocks:_ Cart and Mini-Cart blocks (via the `useStoreCart()` hook) listen to this event to know if they need to update their contents.
#### `detail` parameters
| Parameter | Type | Default value | Description |
| ------------------ | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preserveCartData` | boolean | `false` | Whether Cart data in the store should be preserved. By default, it's `false` so any `wc-blocks_added_to_cart` event will invalidate cart data and blocks listening to it will refetch cart data again. However, if the code triggering the event already updates the store (ie: All Products block), it can set `preserveCartData: true` to avoid the other blocks refetching the data again. |
### `wc-blocks_removed_from_cart`
This event is the equivalent to the jQuery event `removed_from_cart` triggered by WooCommerce core. It indicates that a product has been removed from the cart.
_Example usage in WC Blocks:_ Cart and Mini-Cart blocks (via the `useStoreCart()` hook) listen to this event to know if they need to update their contents.

View File

@ -0,0 +1,8 @@
---
category_title: Hooks
category_slug: cart-and-checkout-hooks
post_title: Cart and Checkout - Hooks
---

View File

@ -0,0 +1,50 @@
---
post_title: Cart and Checkout - Legacy hooks
menu_title: Legacy Hooks
tags: reference
---
Below are the hooks that exist in WooCommerce core and that were brough over to WooCommerce Blocks.
Please note that the actions and filters here run on the server side. The client-side blocks won't necessarily change based on a callback added to a server side hook. [Please see our documentation relating to APIs for manipulating the blocks on the client-side](./an-introduction-to-extensiblity-in-woocommerce-blocks/).
## Legacy Filters
- [loop_shop_per_page](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#loop_shop_per_page)
- [wc_session_expiration](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#wc_session_expiration)
- [woocommerce_add_cart_item](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item)
- [woocommerce_add_cart_item_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_cart_item_data)
- [woocommerce_add_to_cart_quantity](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_quantity)
- [woocommerce_add_to_cart_sold_individually_quantity](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_sold_individually_quantity)
- [woocommerce_add_to_cart_validation](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_add_to_cart_validation)
- [woocommerce_adjust_non_base_location_prices](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_adjust_non_base_location_prices)
- [woocommerce_apply_base_tax_for_local_pickup](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_base_tax_for_local_pickup)
- [woocommerce_apply_individual_use_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_individual_use_coupon)
- [woocommerce_apply_with_individual_use_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_apply_with_individual_use_coupon)
- [woocommerce_cart_contents_changed](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_contents_changed)
- [woocommerce_cart_item_permalink](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_cart_item_permalink)
- [woocommerce_get_item_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_get_item_data)
- [woocommerce_loop_add_to_cart_args](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_args)
- [woocommerce_loop_add_to_cart_link](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_loop_add_to_cart_link)
- [woocommerce_new_customer_data](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_new_customer_data)
- [woocommerce_pay_order_product_has_enough_stock](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_has_enough_stock)
- [woocommerce_pay_order_product_in_stock](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_pay_order_product_in_stock)
- [woocommerce_registration_errors](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_registration_errors)
- [woocommerce_shipping_package_name](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_shipping_package_name)
- [woocommerce_show_page_title](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_show_page_title)
- [woocommerce_single_product_image_thumbnail_html](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/filters.md#woocommerce_single_product_image_thumbnail_html)
## Legacy Actions
- [woocommerce_add_to_cart](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_add_to_cart)
- [woocommerce_after_main_content](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_main_content)
- [woocommerce_after_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_after_shop_loop)
- [woocommerce_applied_coupon](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_applied_coupon)
- [woocommerce_archive_description](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_archive_description)
- [woocommerce_before_main_content](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_main_content)
- [woocommerce_before_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_before_shop_loop)
- [woocommerce_check_cart_items](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_check_cart_items)
- [woocommerce_created_customer](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_created_customer)
- [woocommerce_no_products_found](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_no_products_found)
- [woocommerce_register_post](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_register_post)
- [woocommerce_shop_loop](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/hooks/actions.md#woocommerce_shop_loop)

View File

@ -0,0 +1,142 @@
---
post_title: Cart and Checkout - How the checkout block processes an order
menu_title: Processing an Order
tags: reference
---
This document will shed some light on the inner workings of the Checkout flow. More specifically, what happens after a user hits the "Place Order" button.
## Structure
The following areas are associated with processing the checkout for a user.
### The Payment Registry [(file)](https://href.li/?https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/blocks-registry/payment-methods/registry.ts#L1)
The payment registry stores all the configuration information for each payment method. We can register a new payment method here with the `registerPaymentMethod` and `registerExpressPaymentMethod `functions, also available to other plugins.
### Data Stores
Data stores are used to keep track of data that is likely to change during a user's session, such as the active payment method, whether the checkout has an error, etc. We split these data stores by areas of concern, so we have 2 data stores related to the checkout: `wc/store/checkout` [(file)](https://href.li/?https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/index.ts#L1) and `wc/store/payment` [(file)](https://href.li/?https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/payment-methods/index.ts#L1) . Data stores live in the `assets/js/data` folder.
### Contexts
Contexts are used to make data available to the Checkout block. Each of these provide data and functions related to a specific area of concern, via the use of a hook. For example, if we wanted to use the `onPaymentSetup` handler from the `PaymentEventsContext` context, we can do it like this:
```js
const { onPaymentSetup } = usePaymentEventsContext();
```
The other job of contexts is to run side effects for our Checkout block. What typically happens is that the `CheckoutEvents` and `PaymentEvents` will listen for changes in the checkout and payment data stores, and dispatch actions on those stores based on some logic.
For example, in the `CheckoutEvents` context, we dispatch the `emitValidateEvent` action when the checkout status is `before_processing`. There is a lot of similar logic that reacts to changes in status and other state data from these two stores.
The Checkout contexts are:
| Context | Description |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| [CheckoutEvents](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx#L4) | Provides some checkout related event handlers |
| [ PaymentEvents ](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-events-context.tsx#L3) | Provides event handlers related to payments |
| [ CustomerData ](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/customer/index.tsx#L1) | Provides data related to the current customer |
| [ ShippingData ](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/shipping/index.js#L1) | Provides data and actions related to shipping errors |
### The Checkout Processor (checkout-processor.js)
The checkout processor component subscribes to changes in the checkout or payment data stores, packages up some of this data and calls the StoreApi `/checkout` endpoint when the conditions are right.
## The Checkout Provider
The [checkout provider](https://github.com/woocommerce/woocommerce-blocks/blob/trunk/assets/js/base/context/providers/cart-checkout/checkout-provider.js), wraps all the contexts mentioned above around the `CheckoutProcessor` component.
---
## Checkout User Flow
Below is the complete checkout flow
### 1\. Click the "Place Order" button
The checkout process starts when a user clicks the button
### 2\. Checkout status is set to `before_processing` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx#L167)
As soon as the user clicks the "Place Order" button, we change the checkout status to _"before_processing"_. This is where we handle the validation of the checkout information.
### 3\. Emit the `checkout_validation_before_processing` event [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx#L113)
This is where the WooCommerce Blocks plugin and other plugins can register event listeners for dealing with validation. The event listeners for this event will run and if there are errors, we set checkout status to `idle` and display the errors to the user.
If there are no errors, we move to step 4 below.
### 4\. Checkout status is set to `processing` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/thunks.ts#L76)
The processing status is used by step 5 below to change the payment status
### 5\. Payment status is set to `processing` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-events-context.tsx#L94)
Once all the checkout processing is done and if there are no errors, the payment processing begins
### 6\. Emit the `payment_processing` event [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/payment-methods/thunks.ts#L42)
The `payment_processing` event is emitted. Otherplugins can register event listeners for this event and run their own code.
For example, the Stripe plugin checks the address and payment details here, and uses the stripe APIs to create a customer and payment reference within Stripe.
**Important note: The actual payment is not taken here**. **It acts like a pre-payment hook to run any payment plugin code before the actual payment is attempted.**
### 7\. Execute the `payment_processing` event listeners and set the payment and checkout states accordingly [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/payment-methods/thunks.ts#L54-L132)
If the registered event listeners return errors, we will display this to the user.
If the event listeners are considered successful, we sync up the address of the checkout with the payment address, store the `paymentMethodData` in the payment store, and set the payment status property `{ isProcessing: true }`.
### 8\. POST to `/wc/store/v1/checkout` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/checkout-processor.js#L234)
The `/checkout` StoreApi endpoint is called if there are no payment errors. This will take the final customer addresses and chosen payment method, and any additional payment data, then attempts payment and returns the result.
**Important: payment is attempted through the StoreApi, NOT through the `payment_processing` event that is sent from the client**
### 9\. The `processCheckoutResponse` action is triggered on the checkout store [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/thunks.ts#L33)
Once the fetch to the StoreApi `/checkout` endpoint returns a response, we pass this to the `processCheckoutResponse` action on the `checkout` data store.
It will perform the following actions:
- It sets the redirect url to redirect to once the order is complete
- It stores the payment result in the `checkout` data store.
- It changes the checkout status to `after_processing` (step 10)
### 10\. Checkout status is set to `after_processing` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/thunks.ts#L42)
The `after_processing` status indicates that we've finished the main checkout processing step. In this step, we run cleanup actions and display any errors that have been triggered during the last step.
### 11\. Emit the `checkout_after_processing_with_success` event [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/thunks.ts#L118-L128)
If there are no errors, the `checkout_after_processing_with_success` event is triggered. This is where other plugins can run some code after the checkout process has been successful.
Any event listeners registered on the `checkout_after_processing_with_success` event will be executed. If there are no errors from the event listeners, `setComplete` action is called on the `checkout` data store to set the status to `complete` (step 13). An event listener can also return an error here, which will be displayed to the user.
### 12\. Emit the `checkout_after_processing_with_error` event [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/thunks.ts#L104-L116)
If there has been an error from step 5, the `checkout_after_processing_with_error` event will be emitted. Other plugins can register event listeners to run here to display an error to the user. The event listeners are processed and display any errors to the user.
### 13\. Set checkout status to `complete` [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/utils.ts#L146)
If there are no errors, the `status` property changes to `complete` in the checkout data store. This indicates that the checkout process is complete.
### 14\. Redirect [(file)](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/base/context/providers/cart-checkout/checkout-processor.js#L193-L197)
Once the checkout status is set to `complete`, if there is a `redirectUrl` in the checkout data store, then we redirect the user to the URL.
## Other notable things
### Checking payment methods
A payment method is registered with a [configuration object](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/types/type-defs/payments.ts#L60-L83). It must include a function named `canMakePayment`. This function should return true if the payment method can be used to pay for the current cart. The current cart (items, addresses, shipping methods etc.) is passed to this function, and each payment method is responsible for reporting whether it can be used.
The `checkPaymentMethodsCanPay()` [function](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/payment-methods/check-payment-methods.ts#L26) goes through all the registered payment methods, checks if they can pay, and if so, adds them to the `availablePaymentMethods` property on the payment data store.
The `checkPaymentMethodsCanPay()` [function](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/payment-methods/check-payment-methods.ts#L26) must be called in a few places in order to validate the payment methods before they can be displayed to the user as viable options.
- [Here](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/cart/index.ts#L46-L57), once the cart loads, we want to be able to display express payment methods, so we need to validate them first.
- [Here](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/cart/index.ts#L42-L43), once the cart changes, we may want to enable/disable certain payment methods
- [Here](https://github.com/woocommerce/woocommerce-blocks/blob/4af2c0916a936369be8a4f0044683b90b3af4f0d/assets/js/data/checkout/index.ts#L44-L49), once the checkout loads, we want to verify all registered payment methods

View File

@ -0,0 +1,196 @@
---
post_title: Cart and Checkout - Handling scripts, styles, and data
menu_title: Script, Styles, and Data Handling
tags: how-to
---
## The problem
You are an extension developer, and to allow users to interact with your extension on the client-side, you have written some CSS and JavaScript that you would like to be included on the page. Your JavaScript also relies on some server-side data, and you'd like this to be available to your scripts.
## The solution
You may use the `IntegrationRegistry` to register an `IntegrationInterface` this will be a class that will handle the enqueuing of scripts, styles, and data. You may have a different `IntegrationInterface` for each block (Mini-Cart, Cart and Checkout), or you may use the same one, it is entirely dependent on your use case.
You should use the hooks: `woocommerce_blocks_mini-cart_block_registration`. `woocommerce_blocks_cart_block_registration` and `woocommerce_blocks_checkout_block_registration`. These hooks pass an instance of [`IntegrationRegistry`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/Integrations/IntegrationRegistry.php) to the callback.
You may then use the `register` method on this object to register your `IntegrationInterface`.
## `IntegrationInterface` methods
To begin, we'll need to create our integration class, our `IntegrationInterface`. This will be a class that implements WooCommerce Blocks' interface named [`IntegrationInterface`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/src/Integrations/IntegrationInterface.php).
In this section, we will step through the interface's members and discuss what they are used for.
### `get_name()`
This is the `IntegrationInterface`'s way of namespacing your integration. The name chosen here should be unique to your extension. This method should return a string.
### `initialize()`
This is where any setup, or initialization for your integration should be placed. For example, you could register the scripts and styles your extension needs here. This method should not return anything.
### `get_script_handles()`
This is where the handles of any scripts you want to be enqueued on the client-side in the frontend context should be placed. This method should return an array of strings.
### `get_editor_script_handles()`
This is where the handles of any scripts you want to be enqueued on the client-side in the editor context should be placed. This method should return an array of strings.
### `get_script_data()`
This is where you can set values you want to be available to your scripts on the frontend. This method should return an associative array, the keys of which will be used to get the data using the JavaScript function `getSetting`.
The keys and values of this array should all be serializable.
## Usage example
Let's suppose we're the author of an extension: `WooCommerce Example Plugin`. We want to enqueue scripts, styles, and data on the frontend when either the Mini-Cart, Cart or Checkout blocks are being used.
We also want some data from a server-side function to be available to our front-end scripts.
You will notice that in the example below, we reference the `/build/index.asset.php` file. This is created by the [`DependencyExtractionWebpackPlugin`](https://www.npmjs.com/package/@wordpress/dependency-extraction-webpack-plugin) which creates a PHP file mapping the dependencies of your client-side scripts, so that they can be added in the `dependencies` array of `wp_register_script`.
Let's create our `IntegrationInterface`.
```php
<?php
use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;
/**
* Class for integrating with WooCommerce Blocks
*/
class WooCommerce_Example_Plugin_Integration implements IntegrationInterface {
/**
* The name of the integration.
*
* @return string
*/
public function get_name() {
return 'woocommerce-example-plugin';
}
/**
* When called invokes any initialization/setup for the integration.
*/
public function initialize() {
$script_path = '/build/index.js';
$style_path = '/build/style-index.css';
/**
* The assets linked below should be a path to a file, for the sake of brevity
* we will assume \WooCommerce_Example_Plugin_Assets::$plugin_file is a valid file path
*/
$script_url = plugins_url( $script_path, \WooCommerce_Example_Plugin_Assets::$plugin_file );
$style_url = plugins_url( $style_path, \WooCommerce_Example_Plugin_Assets::$plugin_file );
$script_asset_path = dirname( \WooCommerce_Example_Plugin_Assets::$plugin_file ) . '/build/index.asset.php';
$script_asset = file_exists( $script_asset_path )
? require $script_asset_path
: array(
'dependencies' => array(),
'version' => $this->get_file_version( $script_path ),
);
wp_enqueue_style(
'wc-blocks-integration',
$style_url,
[],
$this->get_file_version( $style_path )
);
wp_register_script(
'wc-blocks-integration',
$script_url,
$script_asset['dependencies'],
$script_asset['version'],
true
);
wp_set_script_translations(
'wc-blocks-integration',
'woocommerce-example-plugin',
dirname( \WooCommerce_Example_Plugin_Assets::$plugin_file ) . '/languages'
);
}
/**
* Returns an array of script handles to enqueue in the frontend context.
*
* @return string[]
*/
public function get_script_handles() {
return array( 'wc-blocks-integration' );
}
/**
* Returns an array of script handles to enqueue in the editor context.
*
* @return string[]
*/
public function get_editor_script_handles() {
return array( 'wc-blocks-integration' );
}
/**
* An array of key, value pairs of data made available to the block on the client side.
*
* @return array
*/
public function get_script_data() {
$woocommerce_example_plugin_data = some_expensive_serverside_function();
return [
'expensive_data_calculation' => $woocommerce_example_plugin_data
];
}
/**
* Get the file modified time as a cache buster if we're in dev mode.
*
* @param string $file Local path to the file.
* @return string The cache buster value to use for the given file.
*/
protected function get_file_version( $file ) {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG && file_exists( $file ) ) {
return filemtime( $file );
}
// As above, let's assume that WooCommerce_Example_Plugin_Assets::VERSION resolves to some versioning number our
// extension uses.
return \WooCommerce_Example_Plugin_Assets::VERSION;
}
}
```
As mentioned, we will need register our `IntegrationInterface` with WooCommerce Blocks, as we want our scripts to be included when either the Mini-Cart, Cart or Checkout is used, we need to register callbacks for three actions.
```php
add_action(
'woocommerce_blocks_mini-cart_block_registration',
function( $integration_registry ) {
$integration_registry->register( new WooCommerce_Example_Plugin_Integration() );
}
);
add_action(
'woocommerce_blocks_cart_block_registration',
function( $integration_registry ) {
$integration_registry->register( new WooCommerce_Example_Plugin_Integration() );
}
);
add_action(
'woocommerce_blocks_checkout_block_registration',
function( $integration_registry ) {
$integration_registry->register( new WooCommerce_Example_Plugin_Integration() );
}
);
```
Now, when we load a page containing either block, we should see the scripts we registered in `initialize` being loaded!
### Getting data added in `get_script_data`
We associated some data with the extension in the `get_script_data` method of our interface, we need to know how to get this!
In the `@woocommerce/settings` package there is a method you can import called `getSetting`. This method accepts a string. The name of the setting containing the data added in `get_script_data` is the name of your integration (i.e. the value returned by `get_name`) suffixed with `_data`. In our example it would be: `woocommerce-example-plugin_data`.
The value returned here is a plain old JavaScript object, keyed by the keys of the array returned by `get_script_data`, the values will serialized.

View File

@ -0,0 +1,60 @@
---
post_title: Cart and Checkout - Slot and fill
menu_title: Slot and Fill
tags: reference
---
## The problem
You added custom data to the [Store API](https://github.com/woocommerce/woocommerce/blob/1675c63bba94c59703f57c7ef06e7deff8fd6bba/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/rest-api/extend-rest-api-add-data.md). You changed several strings using [Checkout filters](./category/cart-and-checkout-blocks/available-filters/). Now you want to render your own components in specific places in the Cart and Checkout.
## Solution
Slot and Fill are a pair of components that add the possibility to render your own HTML in pre-defined places in the Cart and Checkout. Your component will get access to contextual data and will get re-rendered when needed.
A _Slot_ is a place in the Cart and Checkout that can render an indefinite number of external components.
A _Fill_ is the component provided by third-party developers to render inside a _Slot_.
Slot and Fill use WordPress' API, and you can learn more about how they work in [the Slot and Fill documentation.](https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/slot-fill).
## Basic Usage
`ExperimentalOrderMeta` is a fill that will render in a slot below the Order summary section in the Cart and Checkout blocks.
The `ExperimentalOrderMeta` will automatically pass props to its top level child:
- `cart` which contains cart data
- `extensions` which contains data registered with `ExtendSchema::class` in `wc/store/cart` endpoint
- `context`, equal to the name of the Block in which the fill is rendered: `woocommerce/cart` or `woocommerce/checkout`
```jsx
const { registerPlugin } = wp.plugins;
const { ExperimentalOrderMeta } = wc.blocksCheckout;
const MyCustomComponent = ( { cart, extensions } ) => {
return <div className="my-component">Hello WooCommerce</div>;
};
const render = () => {
return (
<ExperimentalOrderMeta>
<MyCustomComponent />
</ExperimentalOrderMeta>
);
};
registerPlugin( 'my-plugin-namespace', {
render,
scope: 'woocommerce-checkout',
} );
```
## registerPlugin
In the above example, we're using `registerPlugin`. This plugin will take our component and render it, but it won't make it visible. The SlotFill part is the one responsible for actually having it show up in the correct place.
You use `registerPlugin` to feed in your plugin namespace, your component `render`, and the scope of your `registerPlugin`. The value of scope should always be `woocommerce-checkout`.
## Requirements
For this to work, your script must be enqueued after Cart and Checkout. You can follow the [IntegrationInterface](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/50f9b3e8d012f425d318908cc13d9c601d97bd68/docs/extensibility/integration-interface.md) documentation for enqueueing your script.

View File

@ -0,0 +1,44 @@
---
post_title: Check if a Payment Method Support Refunds, Subscriptions or Pre-orders
menu_title: Payment method support for refunds, subscriptions, pre-orders
tags: payment-methods
current wccom url: https://woocommerce.com/document/check-if-payment-gateway-supports-refunds-subscriptions-preorders/
---
# Check if a Payment Method Support Refunds, Subscriptions or Pre-orders
If a payment method's documentation doesnt clearly outline the supported features, you can often find what features are supported by looking at payment methods code.
Payment methods can add support for certain features from WooCommerce and its extensions. For example, a payment method can support refunds, subscriptions or pre-orders functionality.
## Simplify Commerce example
Taking the Simplify Commerce payment method as an example, open the plugin files in your favorite editor and search for `$this->supports`. You'll find the supported features:
```php
class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway {
/** * Constructor */
public function __construct() {
$this->id
= 'simplify_commerce';
$this->method_title
= __( 'Simplify Commerce', 'woocommerce' );
$this->method_description = __( 'Take payments via Simplify Commerce - uses simplify.js to create card tokens and the Simplify Commerce SDK. Requires SSL when sandbox is disabled.', 'woocommerce' );
$this->has_fields = true;
$this->supports = array(
'subscriptions',
'products',
'subscription_cancellation',
'subscription_reactivation',
'subscription_suspension',
'subscription_amount_changes',
'subscription_payment_method_change',
'subscription_date_changes',
'default_credit_card_form',
'refunds',
'pre-orders'
);
```
If you dont find `$this->supports` in the plugin files, that may mean that the payment method isnt correctly declaring support for refunds, subscripts or pre-orders.

View File

@ -0,0 +1,81 @@
---
post_title: Code snippets for configuring special tax scenarios
menu_title: Configuring special tax scenarios
tags: code-snippet, tax
current wccom url: https://woocommerce.com/document/setting-up-taxes-in-woocommerce/configuring-specific-tax-setups-in-woocommerce/#configuring-special-tax-setups
---
# Code snippets for configuring special tax scenarios
## Scenario A: Charge the same price regardless of location and taxes
Scenario A: Charge the same price regardless of location and taxes
If a store enters product prices including taxes, but levies various location-based tax rates, the prices will appear to change depending on which tax rate is applied. In reality, the base price remains the same, but the taxes influence the total. [Follow this link for a detailed explanation](https://woocommerce.com/document/how-taxes-work-in-woocommerce/#cross-border-taxes).
Some merchants prefer to dynamically change product base prices to account for the changes in taxes and so keep the total price consistent regardless of tax rate. Enable that functionality by adding the following snippet to your child themes functions.php file or via a code snippet plugin.
```php
<?php
add_filter( 'woocommerce_adjust_non_base_location_prices', '__return_false' );
```
## Scenario B: Charge tax based on the subtotal amount
The following snippet is useful in case where a store only ads taxes when the subtotal reaches a specified minimum. In the code snippet below that minimum is 110 of the stores currency. Adjust the snippet according to your requirements.
```php
<?php
add_filter( 'woocommerce_product_get_tax_class', 'big_apple_get_tax_class', 1, 2 );
function big_apple_get_tax_class( $tax_class, $product ) {
if ( WC()->cart->subtotal <= 110 )
$tax_class = 'Zero Rate';
return $tax_class;
}
```
## Scenario C: Apply different tax rates based on the customer role
Some merchants may require different tax rates to be applied based on a customer role to accommodate for wholesale status or tax exemption.
To enable this functionality, add the following snippet to your child themes functions.php file or via a code snippet plugin. In this snippet, users with “administrator” capabilities will be assigned the **Zero rate tax class**. Adjust it according to your requirements.
```php
<?php
/**
* Apply a different tax rate based on the user role.
*/
function wc_diff_rate_for_user( $tax_class, $product ) {
if ( is_user_logged_in() && current_user_can( 'administrator' ) ) {
$tax_class = 'Zero Rate';
}
return $tax_class;
}
add_filter( 'woocommerce_product_get_tax_class', 'wc_diff_rate_for_user', 1, 2 );
add_filter( 'woocommerce_product_variation_get_tax_class', 'wc_diff_rate_for_user', 1, 2 );
```
## Scenario D: Show 0 value taxes
Taxes that have 0-value are hidden by default. To show them regardless, add the following snippet to your themes functions.php file or via a code snippet plugins:
```php
add_filter( 'woocommerce_order_hide_zero_taxes', '__return_false' );
```
## Scenario E: Suffixes on the main variable product
One of the tax settings for WooCommerce enables the use of suffixes to add additional information to product prices. Its available for use with the variations of a variable product, but is disabled at the main variation level as it can impact website performance when there are many variations.
The method responsible for the related price output can be customized via filter hooks if needed for variable products. This will require customization that can be implemented via this filter:
```php
add_filter( 'woocommerce_show_variation_price', '__return_true' );
```

View File

@ -9,7 +9,7 @@ Custom code should be copied into your child theme's **functions.php** file.
## Note
Some parts of this document only applies to the shortcode Checkout, for adding fields to the Checkout block, [consult this document.](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md)
Some parts of this document only applies to the shortcode Checkout, for adding fields to the Checkout block, consult [the additional checkout fields documentation](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce-blocks/docs/third-party-developers/extensibility/checkout-block/additional-checkout-fields.md).
## How Are Checkout Fields Loaded to WooCommerce?

Some files were not shown because too many files have changed in this diff Show More