Improve the E2E testing process with Playwright (https://github.com/woocommerce/woocommerce-blocks/pull/9148)

* Add Playwright infrastucture

* improve documentation

* improve type

* remove puppeteer tests

* fix wp-env.json

* add link on how run E2E tests

* chore on playwright.yml

* remove unnecessary flush command

* improve stability E2E test

* remove build:e2e-test command

* Update .github/workflows/playwright.yml

Co-authored-by: Niels Lange <info@nielslange.de>

* Update .github/workflows/playwright.yml

Co-authored-by: Niels Lange <info@nielslange.de>

* Update docs/contributors/contributing/e2e-guidelines.md

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

* Update docs/contributors/contributing/e2e-guidelines.md

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

* Update docs/contributors/contributing/e2e-guidelines.md

Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>

* remove emptyline and fix JSDoc warning

* add link about E2E guidelines

* fix theme name

* improve style

* improve markdown

---------

Co-authored-by: Niels Lange <info@nielslange.de>
Co-authored-by: Albert Juhé Lluveras <contact@albertjuhe.com>
This commit is contained in:
Luigi Teschio 2023-05-05 13:03:36 +02:00 committed by GitHub
parent 349ae658bb
commit 739fa60fea
37 changed files with 850 additions and 344 deletions

View File

@ -37,7 +37,7 @@ jobs:
run: npm ci --no-optional
- name: Build Assets
run: FORCE_REDUCED_MOTION=true npm run build:e2e-test
run: FORCE_REDUCED_MOTION=true npm run build
- name: blocks.ini setup
run: |
@ -124,7 +124,7 @@ jobs:
npm install --no-optional --no-audit
- name: Build Assets
run: FORCE_REDUCED_MOTION=true npm run build:e2e-test
run: FORCE_REDUCED_MOTION=true npm run build
- name: blocks.ini setup
run: |

View File

@ -1,5 +1,4 @@
name: Playwright Tests
on:
push:
branches: [ trunk ]
@ -7,27 +6,12 @@ on:
jobs:
PlaywrightE2ETests:
if: false
name: Playwright E2E tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: trunk
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: node_modules
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Setup node version and npm cache
uses: actions/setup-node@v3
@ -36,27 +20,45 @@ jobs:
cache: 'npm'
- name: Install Node dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm install --no-optional --no-audit
run: npm ci
- name: Load wp-env
run: npm run env:start --update
- name: Build Assets
run: FORCE_REDUCED_MOTION=true npm run build
- name: Fix permissions # We need to figure this out https://github.com/WordPress/gutenberg/issues/22515#issuecomment-1308346256
- name: blocks.ini setup
run: |
WP_ENV_DIR=$(npm run wp-env install-path --silent 2>&1 | head -1)
cd $WP_ENV_DIR
mkdir -p tests-WordPress/wp-content/languages tests-WordPress/wp-content/upgrade
chmod -R 767 tests-WordPress/wp-content/languages tests-WordPress/wp-content/upgrade
cd -
echo -e 'woocommerce_blocks_phase = 3\nwoocommerce_blocks_env = tests' > blocks.ini
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install Playwright Browsers
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
coverage: none
tools: composer
- name: Composer install
run: composer install
- name: Install Playwright
run: npx playwright install --with-deps
- name: Load wp-env
run: npm run env:start
- name: Run Playwright tests
run: npx playwright test --config=tests/e2e-pw/playwright.config.ts
run: npm run test:e2e-pw
- uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 30
path: ./tests/e2e-pw/artifacts/test-results

View File

@ -37,7 +37,7 @@ jobs:
- name: Npm install and build
run: |
npm ci --no-optional
FORCE_REDUCED_MOTION=true npm run build:e2e-test
FORCE_REDUCED_MOTION=true npm run build
- name: blocks.ini setup
run: echo -e 'woocommerce_blocks_phase = 3\nwoocommerce_blocks_env = test' > blocks.ini

View File

@ -44,6 +44,8 @@ tests/cli/vendor
# E2E tests
/tests/e2e-tests/config/local-*.json
/tests/e2e-pw/test-results/
/tests/e2e-pw/artifacts/
/artifacts/
# Logs
/logs

View File

@ -3,11 +3,23 @@
"plugins": [
"https://downloads.wordpress.org/plugin/woocommerce.latest-stable.zip",
"https://github.com/WP-API/Basic-Auth/archive/master.zip",
"./tests/mocks/woo-test-helper",
"."
"https://downloads.wordpress.org/plugin/wordpress-importer.0.8.zip",
"./tests/mocks/woo-test-helper"
],
"env": {
"tests": {
"mappings": {
"wp-content/mu-plugins": "./node_modules/@wordpress/e2e-tests/mu-plugins",
"wp-content/plugins/gutenberg-test-plugins": "./node_modules/@wordpress/e2e-tests/plugins",
"wp-content/plugins/woocommerce-blocks": ".",
"wp-cli.yml": "./wp-cli.yml"
}
}
},
"themes": [
"https://downloads.wordpress.org/theme/storefront.latest-stable.zip",
"https://downloads.wordpress.org/theme/twentytwentyone.latest-stable.zip",
"https://downloads.wordpress.org/theme/twentytwentythree.latest-stable.zip",
"./tests/mocks/emptytheme",
"./tests/mocks/theme-with-woo-templates"
],

View File

@ -13,8 +13,7 @@ else
fi
## set permalinks for easier wp-json
wp rewrite structure '/%postname%/'
wp rewrite flush
wp rewrite structure '/%postname%/' --hard
wp core version --extra
wp plugin list
wp theme activate storefront
@ -22,6 +21,7 @@ wp wc customer update 1 --user=1 --billing='{"first_name":"John","last_name":"Do
## Prepare translation for the test suite
wp language core install nl_NL
wp language plugin install woocommerce nl_NL
wp plugin activate woocommerce-blocks
## We download a full version of .po (that has translation for js files as well).
curl https://translate.wordpress.org/projects/wp-plugins/woo-gutenberg-products-block/stable/nl/default/export-translations/ --output ./wp-content/languages/plugins/woo-gutenberg-products-block-nl_NL.po
sleep 5

View File

@ -7,6 +7,7 @@ This folder contains documentation for developers and contributors looking to ge
| [Getting Started](contributing/getting-started.md) | This doc covers tooling and creating builds during development. |
| [Coding Guidelines](contributing/coding-guidelines.md) | This doc covers development best practices. |
| [JavaScript Testing](contributing/javascript-testing.md) | This doc explains how to run automated JavaScript tests. |
| [E2E Testing Guidelines](contributing/e2e-guidelines.md) | This doc covers development best practices about E2E tests. |
| [Developing Components (& Storybook)](components.md) | This doc outlines where our reusable components live, and how to test them in Storybook. |
| [Block Script Assets](contributing/block-assets.md) | This doc explains how Block Script Assets are loaded and used. |
| [JS build system](contributing/javascript-build-system.md) | This doc explains how JavaScript files are built. |

View File

@ -3,15 +3,17 @@
This folder contains documentation for developers and contributors looking to get started with WooCommerce Block Development.
| Document | Description |
| ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|----------------------------------------------------------|------------------------------------------------------------------------------------------|
| [Getting Started](getting-started.md) | This doc covers tooling and creating builds during development. |
| [Coding Guidelines](coding-guidelines.md) | This doc covers development best practices. |
| [Block Script Assets](block-assets.md) | This doc explains how Block Script Assets are loaded and used. |
| [CSS Build System](css-build-system.md) | This doc explains how CSS files are built. |
| [JavaScript Build System](javascript-build-system.md) | This doc explains how JavaScript files are built. |
| [JavaScript Testing](javascript-testing.md) | This doc explains how to run automated JavaScript tests. |
| [E2E Testing Guidelines](e2e-guidelines.md) | This doc covers development best practices about E2E tests. |
| [Storybook & Components](storybook-and-components.md) | This doc outlines where our reusable components live, and how to test them in Storybook. |
<!-- FEEDBACK -->
---

View File

@ -0,0 +1,55 @@
# E2E Guidelines <!-- omit in toc -->
## Table of contents <!-- omit in toc -->
- [Structure](#structure)
- [Playwright](#playwright)
- [Structure](#structure-1)
This living document serves to prescribe coding guidelines specific to the WooCommerce Blocks project E2E tests. For more information on how to run Playwright end-to-end (E2E) tests, please refer to the [dedicated resource](../../../tests/e2e-pw/README.md).
## Structure
There are two folders dedicated to E2E tests.
The first folder is named "e2e" and it contains all the E2E tests that were created with the deprecated infrastructure Jest + Puppetter. The "e2e-pw" folder contains all the E2E tests that were created with the current infrastructure: Playwright. These tests are actively maintained and should be used for all new E2E testing.
### Playwright
#### Structure
There are two Playwright projects configuration:
- blockTheme
- classicTheme
The blockTheme project runs the tests with the suffix *block_theme*. In this case, the theme is a block theme. The block theme is the default WordPress theme. Currently, it is Twenty-Twenty Three. You should use this configuration if you want test the block with the Site Editor.
The classicTheme project runs the tests with the suffix *classic_theme*. In this case, the theme is a Twenty Twenty-One. You should use this configuration if you want test the block with a classic theme.
Each block should have a dedicated folder with a scoped util file if you want share some logic related to the block.
#### Code Guidelines
##### Make tests as isolated as possible - Avoid side effects
Each test should be completely isolated from another test and should run independently with its own local storage, session storage, data, cookies etc. Test isolation improves reproducibility, makes debugging easier and prevents cascading test failures.
In order to avoid repetition for a particular part of your test you can use before and after hooks. Within your test file add a before hook to run a part of your test before each test such as going to a particular URL or logging in to a part of your app. This keeps your tests isolated as no test relies on another. However it is also ok to have a little duplication when tests are simple enough especially if it keeps your tests clearer and easier to read and maintain. Avoid using functions that impact other tests, such as the `deleteAllTemplates` function, which restores all templates and can break other tests since E2E tests run in parallel. After running a suite of tests for a specific block, it is important to clean up any changes made during the tests to ensure a clean slate for subsequent test runs.
For more detail see [Make Tests as Isolated as Possible](https://playwright.dev/docs/best-practices#make-tests-as-isolated-as-possible).
##### Use Locators
In order to write end to end tests we need to first find elements on the webpage. We can do this by using Playwright's built in locators. Locators come with auto waiting and retry-ability. Auto waiting means that Playwright performs a range of actionability checks on the elements, such as ensuring the element is visible and enabled before it performs the click. To make tests resilient, we recommend prioritizing user-facing attributes and explicit contracts. For more detail see [Use Locators](https://playwright.dev/docs/best-practices#use-locators).
##### Avoid Using Relative Imports
In order to make the codebase cleaner, you should import the function from the packages:
- "@woocommerce/e2e-utils": Contains generic utils for interactive with the page.
- "@woocommerce/e2e-types": Contains generic types.
- "@woocommerce/e2e-playwright-utils": Contains utils for playwright for example custom hooks.
By using these packages, you can make your code more modular and easier to maintain.

View File

@ -36,7 +36,7 @@ Additionally,
- `test:update` updates the snapshot tests for components, used if you change a component that has tests attached.
- `test:watch` keeps watch of files and automatically re-runs tests when things change.
## How to run end-to-end tests
## How to run end-to-end tests with deprecated infrastructure
End-to-end tests are implemented in `tests/e2e-tests/specs/`.
@ -44,7 +44,7 @@ Since these drive the user interface, they need to run against a test environmen
To set up to run e2e tests:
- `npm run build:e2e-test` builds the assets (js/css), you can exclude this step if you've already got built files to test with.
- `npm run build` builds the assets (js/css), you can exclude this step if you've already got built files to test with.
- `npm run wp-env start` to start the test environment
Then, to run the tests:
@ -60,6 +60,10 @@ When you're done, you may want to shut down the test environment:
**Note:** There are a number of other useful `wp-env` commands. You can find out more in the [wp-env docs](https://github.com/WordPress/gutenberg/blob/master/packages/env/README.md).
## How to run end-to-end tests
Visit the [dedicated documentation](../../../tests/e2e-pw/README.md).
### Debugging e2e tests using generated reports
When e2e test suites are run in a GitHub automation, a report is generated automatically for every suite that failed. This can be a useful tool to debug failing tests, as it provides a visual way to inspect the tests that failed and, additionally, it includes some screenshots.

View File

@ -111,7 +111,8 @@
"@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
"@wordpress/dom": "3.27.0",
"@wordpress/e2e-test-utils": "10.1.0",
"@wordpress/e2e-tests": "4.6.0",
"@wordpress/e2e-test-utils-playwright": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz",
"@wordpress/e2e-tests": "^4.6.0",
"@wordpress/element": "4.20.0",
"@wordpress/env": "5.16.0",
"@wordpress/html-entities": "3.24.0",
@ -14131,6 +14132,65 @@
"puppeteer-core": ">=11"
}
},
"node_modules/@wordpress/e2e-test-utils-playwright": {
"version": "0.0.0",
"resolved": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz",
"integrity": "sha512-BLtq8Vcsyxbac1d8I+klE2Zrhbso3DJ2YTRl3ZmZ6KMiIiYWCvPLLIbtYAWiIDy6NU2jiT1wLlJ4G4jyV3DhIQ==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/keycodes": "file:../keycodes",
"@wordpress/url": "file:../url",
"change-case": "^4.1.2",
"form-data": "^4.0.0",
"mime": "^3.0.0"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"@playwright/test": ">=1"
}
},
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/api-fetch": {
"resolved": "node_modules/@wordpress/api-fetch",
"link": true
},
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/keycodes": {
"resolved": "node_modules/@wordpress/keycodes",
"link": true
},
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/url": {
"resolved": "node_modules/@wordpress/url",
"link": true
},
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@wordpress/e2e-test-utils/node_modules/@wordpress/api-fetch": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.27.0.tgz",
@ -61979,6 +62039,87 @@
}
}
},
"@wordpress/e2e-test-utils-playwright": {
"version": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz",
"integrity": "sha512-BLtq8Vcsyxbac1d8I+klE2Zrhbso3DJ2YTRl3ZmZ6KMiIiYWCvPLLIbtYAWiIDy6NU2jiT1wLlJ4G4jyV3DhIQ==",
"dev": true,
"requires": {
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/keycodes": "file:../keycodes",
"@wordpress/url": "file:../url",
"change-case": "^4.1.2",
"form-data": "^4.0.0",
"mime": "^3.0.0"
},
"dependencies": {
"@wordpress/api-fetch": {
"version": "file:node_modules/@wordpress/api-fetch",
"requires": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.24.0",
"@wordpress/url": "^3.25.0"
},
"dependencies": {
"@wordpress/url": {
"version": "3.25.0",
"resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.25.0.tgz",
"integrity": "sha512-2W4CP3Tyj7IRrPTb2XzUUvbckkimcW31v1g9Ly8ud1K0qSO4PvBrVxHfkEemkD9jI/KSvm3iPku++bhKY502wg==",
"requires": {
"@babel/runtime": "^7.16.0",
"remove-accents": "^0.4.2"
}
}
}
},
"@wordpress/keycodes": {
"version": "file:node_modules/@wordpress/keycodes",
"requires": {
"@babel/runtime": "^7.16.0",
"@wordpress/i18n": "^4.30.0",
"change-case": "^4.1.2"
},
"dependencies": {
"@wordpress/i18n": {
"version": "4.30.0",
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.30.0.tgz",
"integrity": "sha512-vIntwrTBSU2MXOBlpyFntPgimHP+RW+k7/Y00BMPL+xoxPr7J7sXX/GNoYlH1BNsAo7XOi5AY5FrUnQ7ZIYdtQ==",
"requires": {
"@babel/runtime": "^7.16.0",
"@wordpress/hooks": "^3.30.0",
"gettext-parser": "^1.3.1",
"memize": "^1.1.0",
"sprintf-js": "^1.1.1",
"tannin": "^1.2.0"
}
}
}
},
"@wordpress/url": {
"version": "file:node_modules/@wordpress/url",
"requires": {
"@babel/runtime": "^7.16.0",
"remove-accents": "^0.4.2"
}
},
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true
}
}
},
"@wordpress/e2e-tests": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@wordpress/e2e-tests/-/e2e-tests-4.6.0.tgz",

View File

@ -39,7 +39,6 @@
"build": "rimraf build/* && cross-env BABEL_ENV=default NODE_ENV=production webpack",
"build:check-assets": "rimraf build/* && cross-env ASSET_CHECK=true BABEL_ENV=default NODE_ENV=production webpack",
"build:deploy": "rimraf vendor/* && cross-env WOOCOMMERCE_BLOCKS_PHASE=2 composer install --no-dev && cross-env WOOCOMMERCE_BLOCKS_PHASE=2 npm run build --loglevel error",
"build:e2e-test": "npm run build",
"prebuild:docs": "rimraf docs/extensibility/actions.md & rimraf docs/extensibility/filters.md",
"build:docs": "./vendor/bin/wp-hooks-generator --input=src --output=bin/hook-docs/data && node ./bin/hook-docs",
"postbuild:docs": "./bin/add-doc-footer.sh",
@ -159,7 +158,8 @@
"@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
"@wordpress/dom": "3.27.0",
"@wordpress/e2e-test-utils": "10.1.0",
"@wordpress/e2e-tests": "4.6.0",
"@wordpress/e2e-test-utils-playwright": "https://github.com/woocommerce/woocommerce-blocks/files/11286971/wordpress-e2e-test-utils-playwright-0.0.0.tgz",
"@wordpress/e2e-tests": "^4.6.0",
"@wordpress/element": "4.20.0",
"@wordpress/env": "5.16.0",
"@wordpress/html-entities": "3.24.0",

View File

@ -6,12 +6,6 @@
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
###################################################################################################
# Install the WordPress Importer plugin and activate it
###################################################################################################
wp-env run tests-cli "wp plugin install wordpress-importer --activate"
###################################################################################################
# Empty site to prevent conflicts with existing data
###################################################################################################
@ -342,9 +336,10 @@ wp-env run tests-cli "wp wc tax create \
###################################################################################################
# Adjust and flush rewrite rules
###################################################################################################
wp-env run tests-cli "wp rewrite structure /%postname%/"
wp-env run tests-cli "wp rewrite flush"
# Currently, the rewrite rules don't work properly in the test environment: https://github.com/WordPress/gutenberg/issues/28201
wp-env run tests-wordpress "chmod -c ugo+w /var/www/html"
wp-env run tests-cli "wp rewrite structure /%postname%/ --hard"
wp-env run tests-cli "wp rewrite flush --hard"
###################################################################################################
# Create a customer

View File

@ -0,0 +1,6 @@
/**
* External dependencies
*/
import { BLOCK_THEME_SLUG, cli } from '@woocommerce/e2e-utils';
cli( `npm run wp-env run tests-cli "wp theme activate ${ BLOCK_THEME_SLUG }` );

View File

@ -0,0 +1,8 @@
/**
* External dependencies
*/
import { CLASSIC_THEME_SLUG, cli } from '@woocommerce/e2e-utils';
cli(
`npm run wp-env run tests-cli "wp theme activate ${ CLASSIC_THEME_SLUG }`
);

View File

@ -1,37 +1,23 @@
/* eslint-disable no-console */
/**
* External dependencies
*/
import { chromium, expect } from '@playwright/test';
import { FullConfig, chromium, request } from '@playwright/test';
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
import fs from 'fs';
/**
* Internal dependencies
*/
import { admin, customer } from './test-data/data/data';
import { customer } from './test-data/data/data';
/* eslint-disable no-console */
module.exports = async ( config ) => {
const loginAsCustomer = async ( config: FullConfig ) => {
const { stateDir, baseURL, userAgent } = config.projects[ 0 ].use;
console.log( `State Dir: ${ stateDir }` );
console.log( `Base URL: ${ baseURL }` );
// used throughout tests for authentication
process.env.ADMINSTATE = `${ stateDir }adminState.json`;
process.env.CUSTOMERSTATE = `${ stateDir }customerState.json`;
// Clear out the previous save states
try {
fs.unlinkSync( process.env.ADMINSTATE );
console.log( 'Admin state file deleted successfully.' );
} catch ( err ) {
if ( err.code === 'ENOENT' ) {
console.log( 'Admin state file does not exist.' );
} else {
console.log( 'Admin state file could not be deleted: ' + err );
}
}
try {
fs.unlinkSync( process.env.CUSTOMERSTATE );
console.log( 'Customer state file deleted successfully.' );
@ -43,8 +29,6 @@ module.exports = async ( config ) => {
}
}
// Pre-requisites
let adminLoggedIn = false;
let customerLoggedIn = false;
// Specify user agent when running against an external test site to avoid getting HTTP 406 NOT ACCEPTABLE errors.
@ -52,70 +36,19 @@ module.exports = async ( config ) => {
// Create browser, browserContext, and page for customer and admin users
const browser = await chromium.launch();
const adminContext = await browser.newContext( contextOptions );
const customerContext = await browser.newContext( contextOptions );
const adminPage = await adminContext.newPage();
const customerPage = await customerContext.newPage();
// Sign in as admin user and save state
const adminRetries = 5;
for ( let i = 0; i < adminRetries; i++ ) {
try {
console.log( 'Trying to log-in as admin...' );
await adminPage.goto( `/wp-admin` );
await adminPage.fill( 'input[name="log"]', admin.username );
await adminPage.fill( 'input[name="pwd"]', admin.password );
await adminPage.click( 'text=Log In' );
await adminPage.waitForLoadState( 'networkidle' );
await adminPage.goto( `/wp-admin` );
await adminPage.waitForLoadState( 'domcontentloaded' );
await expect( adminPage.locator( 'div.wrap > h1' ) ).toHaveText(
'Dashboard'
);
await adminPage
.context()
.storageState( { path: process.env.ADMINSTATE } );
console.log( 'Logged-in as admin successfully.' );
adminLoggedIn = true;
break;
} catch ( e ) {
console.log(
`Admin log-in failed, Retrying... ${ i }/${ adminRetries }`
);
console.log( e );
}
}
if ( ! adminLoggedIn ) {
console.error(
'Cannot proceed e2e test, as admin login failed. Please check if the test site has been setup correctly.'
);
process.exit( 1 );
}
// Sign in as customer user and save state
const customerRetries = 5;
for ( let i = 0; i < customerRetries; i++ ) {
try {
console.log( 'Trying to log-in as customer...' );
await customerPage.goto( `/wp-admin` );
await customerPage.fill( 'input[name="log"]', customer.username );
await customerPage.fill( 'input[name="pwd"]', customer.password );
await customerPage.click( 'text=Log In' );
await customerPage.goto( `/my-account` );
await expect(
customerPage.locator(
'.woocommerce-MyAccount-navigation-link--customer-logout'
)
).toBeVisible();
await expect(
customerPage.locator(
'div.woocommerce-MyAccount-content > p >> nth=0'
)
).toContainText( 'Hello' );
await customerPage
.context()
@ -138,7 +71,27 @@ module.exports = async ( config ) => {
process.exit( 1 );
}
await adminContext.close();
await customerContext.close();
await browser.close();
};
async function globalSetup( config: FullConfig ) {
const { storageState, baseURL } = config.projects[ 0 ].use;
const storageStatePath =
typeof storageState === 'string' ? storageState : '';
const requestContext = await request.newContext( {
baseURL: baseURL ?? '',
} );
const requestUtils = new RequestUtils( requestContext, {
storageStatePath,
} );
await requestUtils.setupRest();
await requestContext.dispose();
await loginAsCustomer( config );
}
export default globalSetup;

View File

@ -0,0 +1 @@
export * from './test';

View File

@ -0,0 +1,145 @@
/**
* External dependencies
*/
import { test as base, expect, request } from '@playwright/test';
import type { ConsoleMessage } from '@playwright/test';
import {
Admin,
Editor,
PageUtils,
RequestUtils,
} from '@wordpress/e2e-test-utils-playwright';
import { TemplateApiUtils, STORAGE_STATE_PATH } from '@woocommerce/e2e-utils';
/**
* Set of console logging types observed to protect against unexpected yet
* handled (i.e. not catastrophic) errors or warnings. Each key corresponds
* to the Playwright ConsoleMessage type, its value the corresponding function
* on the console global object.
*/
const OBSERVED_CONSOLE_MESSAGE_TYPES = [ 'warn', 'error' ] as const;
/**
* Adds a page event handler to emit uncaught exception to process if one of
* the observed console logging types is encountered.
*
* @param message The console message.
*/
function observeConsoleLogging( message: ConsoleMessage ) {
const type = message.type();
if (
! OBSERVED_CONSOLE_MESSAGE_TYPES.includes(
type as typeof OBSERVED_CONSOLE_MESSAGE_TYPES[ number ]
)
) {
return;
}
const text = message.text();
// An exception is made for _blanket_ deprecation warnings: Those
// which log regardless of whether a deprecated feature is in use.
if ( text.includes( 'This is a global warning' ) ) {
return;
}
// A chrome advisory warning about SameSite cookies is informational
// about future changes, tracked separately for improvement in core.
//
// See: https://core.trac.wordpress.org/ticket/37000
// See: https://www.chromestatus.com/feature/5088147346030592
// See: https://www.chromestatus.com/feature/5633521622188032
if ( text.includes( 'A cookie associated with a cross-site resource' ) ) {
return;
}
// Viewing posts on the front end can result in this error, which
// has nothing to do with Gutenberg.
if ( text.includes( 'net::ERR_UNKNOWN_URL_SCHEME' ) ) {
return;
}
// Not implemented yet.
// Network errors are ignored only if we are intentionally testing
// offline mode.
// if (
// text.includes( 'net::ERR_INTERNET_DISCONNECTED' ) &&
// isOfflineMode()
// ) {
// return;
// }
// As of WordPress 5.3.2 in Chrome 79, navigating to the block editor
// (Posts > Add New) will display a console warning about
// non - unique IDs.
// See: https://core.trac.wordpress.org/ticket/23165
if ( text.includes( 'elements with non-unique id #_wpnonce' ) ) {
return;
}
// Ignore all JQMIGRATE (jQuery migrate) deprecation warnings.
if ( text.includes( 'JQMIGRATE' ) ) {
return;
}
const logFunction = type as typeof OBSERVED_CONSOLE_MESSAGE_TYPES[ number ];
// Disable reason: We intentionally bubble up the console message
// which, unless the test explicitly anticipates the logging via
// @wordpress/jest-console matchers, will cause the intended test
// failure.
// eslint-disable-next-line no-console
console[ logFunction ]( text );
}
const test = base.extend<
{
admin: Admin;
editor: Editor;
pageUtils: PageUtils;
templateApiUtils: TemplateApiUtils;
snapshotConfig: void;
},
{
requestUtils: RequestUtils;
}
>( {
admin: async ( { page, pageUtils }, use ) => {
await use( new Admin( { page, pageUtils } ) );
},
editor: async ( { page }, use ) => {
await use( new Editor( { page } ) );
},
page: async ( { page }, use ) => {
page.on( 'console', observeConsoleLogging );
await use( page );
// Clear local storage after each test.
await page.evaluate( () => {
window.localStorage.clear();
} );
await page.close();
},
pageUtils: async ( { page }, use ) => {
await use( new PageUtils( { page } ) );
},
templateApiUtils: async ( {}, use ) =>
await use( new TemplateApiUtils( request ) ),
requestUtils: [
async ( {}, use, workerInfo ) => {
const requestUtils = await RequestUtils.setup( {
baseURL: workerInfo.project.use.baseURL,
storageStatePath: STORAGE_STATE_PATH,
} );
await use( requestUtils );
},
{ scope: 'worker', auto: true },
],
} );
export { test, expect };

View File

@ -1,7 +1,11 @@
/**
* External dependencies
*/
import { defineConfig, devices, PlaywrightTestConfig } from '@playwright/test';
import { defineConfig, PlaywrightTestConfig } from '@playwright/test';
import { BASE_URL, STORAGE_STATE_PATH } from '@woocommerce/e2e-utils';
import path from 'path';
import { fileURLToPath } from 'url';
interface ExtendedPlaywrightTestConfig extends PlaywrightTestConfig {
use: {
@ -9,52 +13,52 @@ interface ExtendedPlaywrightTestConfig extends PlaywrightTestConfig {
} & PlaywrightTestConfig[ 'use' ];
}
const {
BASE_URL,
CI,
DEFAULT_TIMEOUT_OVERRIDE,
E2E_MAX_FAILURES,
PLAYWRIGHT_HTML_REPORT,
} = process.env;
const { CI, DEFAULT_TIMEOUT_OVERRIDE, E2E_MAX_FAILURES } = process.env;
const config: ExtendedPlaywrightTestConfig = {
timeout: DEFAULT_TIMEOUT_OVERRIDE
? Number( DEFAULT_TIMEOUT_OVERRIDE )
: 90 * 1000,
expect: { timeout: 20 * 1000 },
outputDir: './test-results/report',
globalSetup: require.resolve( './global-setup' ),
outputDir: path.join( process.cwd(), 'artifacts/test-results' ),
globalSetup: fileURLToPath(
new URL( 'global-setup.ts', 'file:' + __filename ).href
),
globalTeardown: require.resolve( './global-teardown' ),
testDir: 'tests',
retries: CI ? 4 : 0,
retries: CI ? 2 : 0,
workers: 4,
fullyParallel: true,
reporter: [
[ 'list' ],
[
'html',
{
outputFolder:
PLAYWRIGHT_HTML_REPORT ??
'./test-results/playwright-report',
open: CI ? 'never' : 'always',
},
],
[ 'json', { outputFile: './test-results/test-results.json' } ],
],
reporter: process.env.CI ? [ [ 'github' ], [ 'list' ] ] : 'list',
maxFailures: E2E_MAX_FAILURES ? Number( E2E_MAX_FAILURES ) : 0,
use: {
baseURL: BASE_URL ?? 'http://localhost:8889',
baseURL: BASE_URL,
screenshot: 'only-on-failure',
stateDir: './tests/e2e-pw/test-results/storage/',
trace: 'retain-on-failure',
video: 'on-first-retry',
viewport: { width: 1280, height: 720 },
storageState: STORAGE_STATE_PATH,
},
projects: [
{
name: 'Chrome',
use: { ...devices[ 'Desktop Chrome' ] },
name: 'blockThemeConfiguration',
testMatch: /block-theme.setup.ts/,
},
{
name: 'blockTheme',
testMatch: /.*.block_theme.spec.ts/,
dependencies: [ 'blockThemeConfiguration' ],
},
{
name: 'classicThemeConfiguration',
testMatch: /block-theme.setup.ts/,
dependencies: [ 'blockTheme' ],
},
{
name: 'classicTheme',
testMatch: /.*.classic_theme.spec.ts/,
dependencies: [ 'classicThemeConfiguration' ],
},
],
};

View File

@ -1,23 +1,26 @@
/**
* External dependencies
*/
import { test, expect } from '@playwright/test';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
/**
* Internal dependencies
*/
test.describe(
'A basic set of tests to ensure WP, wp-admin and my-account load',
() => {
async () => {
test( 'Load the home page', async ( { page } ) => {
await page.goto( '/' );
const title = page.locator( 'header .wp-block-site-title a' );
const title = page
.locator( 'header' )
.locator( '.wp-block-site-title' );
await expect( title ).toHaveText(
'WooCommerce Blocks E2E Test Suite'
);
} );
test.describe( 'Sign in as admin', () => {
test.use( {
storageState: process.env.ADMINSTATE,
} );
test( 'Load wp-admin', async ( { page } ) => {
await page.goto( '/wp-admin' );
const title = page.locator( 'div.wrap > h1' );

View File

@ -0,0 +1,181 @@
/**
* External dependencies
*/
import { BlockData } from '@woocommerce/e2e-types';
import { test, expect } from '@woocommerce/e2e-playwright-utils';
import { BASE_URL, getBlockByName } from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { getMinMaxPriceInputs } from './utils';
const blockData: BlockData< {
urlSearchParamWhenFilterIsApplied: string;
endpointAPI: string;
placeholderUrl: string;
} > = {
name: 'woocommerce/price-filter',
mainClass: '.wc-block-price-filter',
selectors: {
frontend: {},
editor: {},
},
urlSearchParamWhenFilterIsApplied: '?max_price=10',
endpointAPI: 'max_price=1000',
placeholderUrl: `${ BASE_URL }/wp-content/plugins/woocommerce/assets/images/placeholder.png`,
};
test.describe( `${ blockData.name } Block - with All products Block`, () => {
test.beforeEach( async ( { admin, page, editor } ) => {
await admin.createNewPost();
await editor.insertBlock( { name: 'woocommerce/all-products' } );
await editor.insertBlock( {
name: 'woocommerce/filter-wrapper',
attributes: {
filterType: 'price-filter',
heading: 'Filter By Price',
},
} );
await editor.publishPost();
await page.waitForLoadState( 'networkidle' );
const url = new URL( page.url() );
const postId = url.searchParams.get( 'post' );
await page.goto( `/?p=${ postId }`, { waitUntil: 'networkidle' } );
} );
test( 'should show all products', async ( { page } ) => {
const allProductsBlock = await getBlockByName( {
page,
name: 'woocommerce/all-products',
} );
await page.waitForLoadState( 'networkidle' );
const img = await allProductsBlock.locator( 'img' ).first();
await expect( img ).not.toHaveAttribute(
'src',
blockData.placeholderUrl
);
const products = await allProductsBlock.getByRole( 'listitem' ).all();
expect( products ).toHaveLength( 9 );
} );
test( 'should show only products that match the filter', async ( {
page,
pageUtils,
} ) => {
const { maxPriceInput } = await getMinMaxPriceInputs( {
page,
blockName: 'woocommerce/filter-wrapper',
} );
await maxPriceInput.selectText();
await maxPriceInput.type( '10' );
await pageUtils.pressKeys( 'Tab' );
await page.waitForResponse( ( response ) =>
response.url().includes( blockData.endpointAPI )
);
await page.waitForLoadState( 'networkidle' );
const allProductsBlock = await getBlockByName( {
page,
name: 'woocommerce/all-products',
} );
await page.waitForLoadState( 'networkidle' );
const img = await allProductsBlock.locator( 'img' ).first();
await expect( img ).not.toHaveAttribute(
'src',
blockData.placeholderUrl
);
const products = await allProductsBlock.getByRole( 'listitem' ).all();
expect( products ).toHaveLength( 1 );
expect( page.url() ).toContain(
blockData.urlSearchParamWhenFilterIsApplied
);
} );
} );
test.describe( `${ blockData.name } Block - with PHP classic template`, () => {
test.beforeEach( async ( { admin, page, editor } ) => {
await admin.visitSiteEditor( {
postId: 'woocommerce/woocommerce//archive-product',
postType: 'wp_template',
} );
await editor.canvas.click( 'body' );
await editor.insertBlock( {
name: 'woocommerce/filter-wrapper',
attributes: {
filterType: 'price-filter',
heading: 'Filter By Price',
},
} );
await editor.saveSiteEditorEntities();
await page.goto( `/shop`, { waitUntil: 'networkidle' } );
} );
test.afterEach( async ( { templateApiUtils } ) => {
await templateApiUtils.revertTemplate(
'woocommerce/woocommerce//archive-product'
);
} );
test( 'should show all products', async ( { page } ) => {
const legacyTemplate = await getBlockByName( {
page,
name: 'woocommerce/legacy-template',
} );
await page.waitForLoadState( 'networkidle' );
const products = await legacyTemplate
.getByRole( 'list' )
.locator( '.product' )
.all();
expect( products ).toHaveLength( 16 );
} );
test( 'should show only products that match the filter', async ( {
page,
pageUtils,
} ) => {
const { maxPriceInput } = await getMinMaxPriceInputs( {
page,
blockName: 'woocommerce/filter-wrapper',
} );
await maxPriceInput.selectText();
await maxPriceInput.type( '10' );
await pageUtils.pressKeys( 'Tab', {
delay: 100,
} );
await page.waitForURL( ( url ) =>
url
.toString()
.includes( blockData.urlSearchParamWhenFilterIsApplied )
);
const legacyTemplate = await getBlockByName( {
page,
name: 'woocommerce/legacy-template',
} );
const products = await legacyTemplate
.getByRole( 'list' )
.locator( '.product' )
.all();
expect( products ).toHaveLength( 1 );
} );
} );

View File

@ -0,0 +1,28 @@
/**
* External dependencies
*/
import { Page } from '@playwright/test';
import { getBlockByName } from '@woocommerce/e2e-utils';
export const getMinMaxPriceInputs = async ( {
page,
blockName,
}: {
page: Page;
blockName: string;
} ) => {
const priceFilterBlock = await getBlockByName( {
page,
name: blockName,
} );
const maxPriceInput = await priceFilterBlock.locator(
'.wc-block-price-filter__amount--max'
);
const minPriceInput = await priceFilterBlock.locator(
'.wc-block-price-filter__amount--min'
);
return { maxPriceInput, minPriceInput };
};

View File

@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.base.json",
}

View File

@ -0,0 +1,5 @@
export type BlockData< T = undefined > = {
name: string;
mainClass: string;
selectors: Record< 'editor' | 'frontend', Record< string, unknown > >;
} & ( T extends undefined ? Record< string, never > : T );

View File

@ -0,0 +1 @@
export * from './block-data';

View File

@ -0,0 +1,53 @@
/**
* External dependencies
*/
import { request as req } from '@playwright/test';
import fs from 'fs/promises';
/**
* Internal dependencies
*/
import { BASE_URL, STORAGE_STATE_PATH } from '../constants';
export class TemplateApiUtils {
request: typeof req;
constructor( request: typeof req ) {
this.request = request;
}
async revertTemplate( slug: string ) {
const storageState = JSON.parse(
await fs.readFile( STORAGE_STATE_PATH, 'utf-8' )
);
const requestUtils = await this.request.newContext( {
baseURL: BASE_URL,
storageState: storageState && {
cookies: storageState.cookies,
origins: [],
},
} );
const response = await requestUtils.get(
`/wp-json/wp/v2/templates/${ slug }?context=edit&source=theme&_locale=user`,
{
headers: {
'X-WP-Nonce': storageState.nonce,
},
}
);
const { content } = await response.json();
await requestUtils.post(
`wp-json/wp/v2/templates/${ slug }?_locale=user`,
{
data: {
id: slug,
content: content.raw,
source: 'theme',
},
headers: {
'X-WP-Nonce': storageState.nonce,
},
}
);
}
}

View File

@ -0,0 +1 @@
export * from './TemplateApiUtils';

View File

@ -0,0 +1,17 @@
/**
* External dependencies
*/
import { exec } from 'child_process';
export function cli( cmd, args = [] ) {
return new Promise( ( resolve ) => {
exec( `${ cmd } ${ args.join( ' ' ) }`, ( error, stdout, stderr ) => {
resolve( {
code: error && error.code ? error.code : 0,
error,
stdout,
stderr,
} );
} );
} );
}

View File

@ -0,0 +1,13 @@
/**
* External dependencies
*/
import path from 'path';
export const BLOCK_THEME_SLUG = 'twentytwentythree';
export const CLASSIC_THEME_SLUG = 'twentytwentyone';
export const MIN_TIMEOUT = 10000;
export const BASE_URL = 'http://localhost:8889';
export const STORAGE_STATE_PATH = path.join(
process.cwd(),
'artifacts/storage-states/admin.json'
);

View File

@ -0,0 +1,13 @@
/**
* External dependencies
*/
import { Page } from '@playwright/test';
export const getBlockByName = ( {
page,
name,
}: {
page: Page;
name: string;
} ) => page.locator( `[data-block-name="${ name }"]` );

View File

@ -0,0 +1 @@
export * from './get-block-by-name';

View File

@ -0,0 +1,5 @@
export const goToShop = () => {
page.goto( BASE_URL + '/shop', {
waitUntil: 'networkidle0',
} );
};

View File

@ -0,0 +1,5 @@
export * from './frontend';
export * from './constants';
export * from './use-block-theme';
export * from './cli';
export * from './api';

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { test as Test } from '@wordpress/e2e-test-utils-playwright';
/**
* Internal dependencies
*/
import { BLOCK_THEME_SLUG } from './constants';
export const useBlockTheme = ( test: typeof Test ) => {
test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( BLOCK_THEME_SLUG );
} );
};

View File

@ -3,7 +3,6 @@
*/
import {
createNewPost,
deleteAllTemplates,
insertBlock,
switchUserToAdmin,
publishPost,
@ -13,16 +12,7 @@ import { selectBlockByName } from '@woocommerce/blocks-test-utils';
/**
* Internal dependencies
*/
import {
BASE_URL,
enableApplyFiltersButton,
goToTemplateEditor,
insertAllProductsBlock,
saveTemplate,
useTheme,
waitForCanvas,
waitForAllProductsBlockLoaded,
} from '../../utils';
import { enableApplyFiltersButton, waitForCanvas } from '../../utils';
import { clickLink, saveOrPublish } from '../../../utils';
const block = {
@ -44,11 +34,6 @@ const block = {
const { selectors } = block;
const goToShopPage = () =>
page.goto( BASE_URL + '/shop', {
waitUntil: 'networkidle0',
} );
const setMaxPrice = async () => {
await page.waitForSelector( selectors.frontend.priceMaxAmount );
await page.focus( selectors.frontend.priceMaxAmount );
@ -60,166 +45,6 @@ const setMaxPrice = async () => {
};
describe( `${ block.name } Block`, () => {
describe( 'with All Products Block', () => {
beforeAll( async () => {
await switchUserToAdmin();
await createNewPost( {
postType: 'post',
title: block.name,
} );
await insertBlock( block.name );
await insertAllProductsBlock();
await insertBlock( 'Active Filters' );
await publishPost();
const link = await page.evaluate( () =>
wp.data.select( 'core/editor' ).getPermalink()
);
await page.goto( link );
} );
it( 'should render products', async () => {
await waitForAllProductsBlockLoaded();
const products = await page.$$( selectors.frontend.productsList );
expect( products ).toHaveLength( 5 );
} );
it( 'should show only products that match the filter', async () => {
const isRefreshed = jest.fn( () => void 0 );
page.on( 'load', isRefreshed );
await setMaxPrice();
await waitForAllProductsBlockLoaded();
await expect( page ).toMatchElement( '.wc-blocks-filter-wrapper', {
text: 'Active filters',
} );
const products = await page.$$( selectors.frontend.productsList );
expect( isRefreshed ).not.toBeCalled();
expect( products ).toHaveLength( 1 );
await expect( page ).toMatch( block.foundProduct );
} );
} );
describe( 'with PHP classic template', () => {
const productCatalogTemplateId =
'woocommerce/woocommerce//archive-product';
useTheme( 'emptytheme' );
beforeAll( async () => {
await deleteAllTemplates( 'wp_template' );
await deleteAllTemplates( 'wp_template_part' );
await goToTemplateEditor( {
postId: productCatalogTemplateId,
} );
await insertBlock( block.name );
await saveTemplate();
await goToShopPage();
} );
beforeEach( async () => {
await goToShopPage();
} );
afterAll( async () => {
await deleteAllTemplates( 'wp_template' );
await deleteAllTemplates( 'wp_template_part' );
} );
it( 'should render products', async () => {
const products = await page.$$(
selectors.frontend.classicProductsList
);
expect( products ).toHaveLength( 5 );
} );
it( 'should show only products that match the filter', async () => {
const isRefreshed = jest.fn( () => void 0 );
page.on( 'load', isRefreshed );
await page.waitForSelector( block.class + '.is-loading', {
hidden: true,
} );
await expect( page ).toMatch( block.foundProduct );
expect( isRefreshed ).not.toBeCalled();
await Promise.all( [ page.waitForNavigation(), setMaxPrice() ] );
await page.waitForSelector(
selectors.frontend.classicProductsList
);
const products = await page.$$(
selectors.frontend.classicProductsList
);
const pageURL = page.url();
const parsedURL = new URL( pageURL );
expect( isRefreshed ).toBeCalledTimes( 1 );
expect( products ).toHaveLength( 1 );
expect( parsedURL.search ).toEqual(
block.urlSearchParamWhenFilterIsApplied
);
await expect( page ).toMatch( block.foundProduct );
} );
it( 'should refresh the page only if the user clicks on button', async () => {
await goToTemplateEditor( {
postId: productCatalogTemplateId,
} );
await waitForCanvas();
await selectBlockByName( block.slug );
await enableApplyFiltersButton();
await saveTemplate();
await goToShopPage();
const isRefreshed = jest.fn( () => void 0 );
page.on( 'load', isRefreshed );
await page.waitForSelector( block.class + '.is-loading', {
hidden: true,
} );
expect( isRefreshed ).not.toBeCalled();
await setMaxPrice();
expect( isRefreshed ).not.toBeCalled();
await clickLink( selectors.frontend.submitButton );
await page.waitForSelector(
selectors.frontend.classicProductsList
);
const products = await page.$$(
selectors.frontend.classicProductsList
);
const pageURL = page.url();
const parsedURL = new URL( pageURL );
expect( isRefreshed ).toBeCalledTimes( 1 );
expect( products ).toHaveLength( 1 );
await expect( page ).toMatch( block.foundProduct );
expect( parsedURL.search ).toEqual(
block.urlSearchParamWhenFilterIsApplied
);
} );
} );
describe( 'with Product Query Block', () => {
let editorPageUrl = '';
let frontedPageUrl = '';

View File

@ -61,7 +61,11 @@
"@woocommerce/type-defs/*": [ "assets/js/types/type-defs/*" ],
"@woocommerce/types": [ "assets/js/types" ],
"@woocommerce/storybook-controls": [ "storybook/custom-controls" ],
"@woocommerce/utils": [ "assets/js/utils" ]
"@woocommerce/utils": [ "assets/js/utils" ],
"@woocommerce/e2e-utils": [ "tests/e2e-pw/utils" ],
"@woocommerce/e2e-types": [ "tests/e2e-pw/types" ],
"@woocommerce/e2e-playwright-utils": [ "tests/e2e-pw/playwright-utils" ],
}
}
}

View File

@ -0,0 +1,2 @@
apache_modules:
- mod_rewrite