mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-04-27 16:04:31 +00:00
Build/Test Tools: Migrate Puppeteer tests to Playwright.
As per the migration plan shared last year, this migrates all browser-based tests in WordPress core to use Playwright. This includes end-to-end, performance, and visual regression tests. Props swissspidy, mamaduka, kevin940726, bartkalisz, desrosj, adamsilverstein. Fixes #59517. git-svn-id: https://develop.svn.wordpress.org/trunk@56926 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
2
.github/workflows/coding-standards.yml
vendored
2
.github/workflows/coding-standards.yml
vendored
@@ -142,8 +142,6 @@ jobs:
|
||||
contents: read
|
||||
timeout-minutes: 20
|
||||
if: ${{ github.repository == 'WordPress/wordpress-develop' || github.event_name == 'pull_request' }}
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: ${{ true }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
27
.github/workflows/end-to-end-tests.yml
vendored
27
.github/workflows/end-to-end-tests.yml
vendored
@@ -42,11 +42,13 @@ jobs:
|
||||
# - Sets up Node.js.
|
||||
# - Logs debug information about the GitHub Action runner.
|
||||
# - Installs npm dependencies.
|
||||
# - Install Playwright browsers.
|
||||
# - Builds WordPress to run from the `build` directory.
|
||||
# - Starts the WordPress Docker container.
|
||||
# - Logs the running Docker containers.
|
||||
# - Logs Docker debug information (about both the Docker installation within the runner and the WordPress container).
|
||||
# - Install WordPress within the Docker container.
|
||||
# - Install Gutenberg.
|
||||
# - Run the E2E tests.
|
||||
# - Ensures version-controlled files are not modified or deleted.
|
||||
e2e-tests:
|
||||
@@ -90,6 +92,9 @@ jobs:
|
||||
- name: Install npm Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Build WordPress
|
||||
run: npm run build
|
||||
|
||||
@@ -115,6 +120,9 @@ jobs:
|
||||
LOCAL_SCRIPT_DEBUG: ${{ matrix.LOCAL_SCRIPT_DEBUG }}
|
||||
run: npm run env:install
|
||||
|
||||
- name: Install Gutenberg
|
||||
run: npm run env:cli -- plugin install gutenberg --path=/var/www/${{ env.LOCAL_DIR }}
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npm run test:e2e
|
||||
|
||||
@@ -129,6 +137,22 @@ jobs:
|
||||
- name: Ensure version-controlled files are not modified or deleted
|
||||
run: git diff --exit-code
|
||||
|
||||
slack-notifications:
|
||||
name: Slack Notifications
|
||||
uses: WordPress/wordpress-develop/.github/workflows/slack-notifications.yml@trunk
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
needs: [ e2e-tests ]
|
||||
if: ${{ github.repository == 'WordPress/wordpress-develop' && github.event_name != 'pull_request' && always() }}
|
||||
with:
|
||||
calling_status: ${{ contains( needs.*.result, 'cancelled' ) && 'cancelled' || contains( needs.*.result, 'failure' ) && 'failure' || 'success' }}
|
||||
secrets:
|
||||
SLACK_GHA_SUCCESS_WEBHOOK: ${{ secrets.SLACK_GHA_SUCCESS_WEBHOOK }}
|
||||
SLACK_GHA_CANCELLED_WEBHOOK: ${{ secrets.SLACK_GHA_CANCELLED_WEBHOOK }}
|
||||
SLACK_GHA_FIXED_WEBHOOK: ${{ secrets.SLACK_GHA_FIXED_WEBHOOK }}
|
||||
SLACK_GHA_FAILURE_WEBHOOK: ${{ secrets.SLACK_GHA_FAILURE_WEBHOOK }}
|
||||
|
||||
failed-workflow:
|
||||
name: Failed workflow tasks
|
||||
runs-on: ubuntu-latest
|
||||
@@ -141,7 +165,8 @@ jobs:
|
||||
github.event_name != 'pull_request' &&
|
||||
github.run_attempt < 2 &&
|
||||
(
|
||||
needs.e2e-tests.result == 'cancelled' || needs.e2e-tests.result == 'failure'
|
||||
contains( needs.*.result, 'cancelled' ) ||
|
||||
contains( needs.*.result, 'failure' )
|
||||
)
|
||||
steps:
|
||||
- name: Dispatch workflow run
|
||||
|
||||
32
.github/workflows/performance.yml
vendored
32
.github/workflows/performance.yml
vendored
@@ -31,10 +31,10 @@ permissions: {}
|
||||
|
||||
env:
|
||||
# Performance testing should be performed in an environment reflecting a standard production environment.
|
||||
WP_DEBUG: false
|
||||
SCRIPT_DEBUG: false
|
||||
SAVEQUERIES : false
|
||||
WP_DEVELOPMENT_MODE: ''
|
||||
LOCAL_WP_DEBUG: false
|
||||
LOCAL_SCRIPT_DEBUG: false
|
||||
LOCAL_SAVEQUERIES: false
|
||||
LOCAL_WP_DEVELOPMENT_MODE: "''"
|
||||
|
||||
# This workflow takes two sets of measurements — one for the current commit,
|
||||
# and another against a consistent version that is used as a baseline measurement.
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
# - Set up Node.js.
|
||||
# - Log debug information.
|
||||
# - Install npm dependencies.
|
||||
# - Install Playwright browsers.
|
||||
# - Build WordPress.
|
||||
# - Start Docker environment.
|
||||
# - Log running Docker containers.
|
||||
@@ -73,6 +74,7 @@ jobs:
|
||||
# - Run performance tests (previous/target commit).
|
||||
# - Print target performance tests results.
|
||||
# - Reset to original commit.
|
||||
# - Install npm dependencies.
|
||||
# - Set the environment to the baseline version.
|
||||
# - Run baseline performance tests.
|
||||
# - Print baseline performance tests results.
|
||||
@@ -119,6 +121,9 @@ jobs:
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Build WordPress
|
||||
run: npm run build
|
||||
|
||||
@@ -182,24 +187,35 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Run target performance tests (base/previous commit)
|
||||
run: npm run test:performance -- --prefix=before
|
||||
env:
|
||||
TEST_RESULTS_PREFIX: before
|
||||
run: npm run test:performance
|
||||
|
||||
- name: Print target performance tests results
|
||||
run: node ./tests/performance/results.js --prefix=before
|
||||
env:
|
||||
TEST_RESULTS_PREFIX: before
|
||||
run: node ./tests/performance/results.js
|
||||
|
||||
- name: Reset to original commit
|
||||
run: git reset --hard $GITHUB_SHA
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Set the environment to the baseline version
|
||||
run: |
|
||||
npm run env:cli -- core update --version=${{ env.BASE_TAG }} --force --path=/var/www/${{ env.LOCAL_DIR }}
|
||||
npm run env:cli -- core version --path=/var/www/${{ env.LOCAL_DIR }}
|
||||
|
||||
- name: Run baseline performance tests
|
||||
run: npm run test:performance -- --prefix=base
|
||||
env:
|
||||
TEST_RESULTS_PREFIX: base
|
||||
run: npm run test:performance
|
||||
|
||||
- name: Print baseline performance tests results
|
||||
run: node ./tests/performance/results.js --prefix=base
|
||||
env:
|
||||
TEST_RESULTS_PREFIX: base
|
||||
run: node ./tests/performance/results.js
|
||||
|
||||
- name: Compare results with base
|
||||
run: node ./tests/performance/compare-results.js ${{ runner.temp }}/summary.md
|
||||
|
||||
1
.github/workflows/phpunit-tests-run.yml
vendored
1
.github/workflows/phpunit-tests-run.yml
vendored
@@ -51,7 +51,6 @@ env:
|
||||
LOCAL_DB_VERSION: ${{ inputs.db-version }}
|
||||
LOCAL_PHP_MEMCACHED: ${{ inputs.memcached }}
|
||||
PHPUNIT_CONFIG: ${{ inputs.phpunit-config }}
|
||||
PUPPETEER_SKIP_DOWNLOAD: ${{ true }}
|
||||
|
||||
jobs:
|
||||
# Runs the PHPUnit tests for WordPress.
|
||||
|
||||
1
.github/workflows/test-coverage.yml
vendored
1
.github/workflows/test-coverage.yml
vendored
@@ -29,7 +29,6 @@ on:
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: ${{ true }}
|
||||
LOCAL_PHP: '7.4-fpm'
|
||||
LOCAL_PHP_XDEBUG: true
|
||||
LOCAL_PHP_XDEBUG_MODE: 'coverage'
|
||||
|
||||
3
.github/workflows/test-npm.yml
vendored
3
.github/workflows/test-npm.yml
vendored
@@ -37,9 +37,6 @@ concurrency:
|
||||
# Any needed permissions should be configured at the job level.
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: ${{ true }}
|
||||
|
||||
jobs:
|
||||
# Verifies that installing npm dependencies and building WordPress works as expected.
|
||||
#
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -100,4 +100,4 @@ wp-tests-config.php
|
||||
/docker-compose.override.yml
|
||||
|
||||
# Visual regression test diffs
|
||||
tests/visual-regression/specs/__image_snapshots__
|
||||
tests/visual-regression/specs/__snapshots__
|
||||
|
||||
@@ -80,7 +80,7 @@ module.exports = function(grunt) {
|
||||
}
|
||||
|
||||
// First do `npm install` if package.json has changed.
|
||||
installChanged.watchPackage();
|
||||
// installChanged.watchPackage();
|
||||
|
||||
// Load tasks.
|
||||
require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
|
||||
|
||||
265
package-lock.json
generated
265
package-lock.json
generated
@@ -106,10 +106,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodder/grunt-postcss": "^3.1.1",
|
||||
"@playwright/test": "1.32.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
|
||||
"@wordpress/babel-preset-default": "7.26.6",
|
||||
"@wordpress/dependency-extraction-webpack-plugin": "4.25.6",
|
||||
"@wordpress/e2e-test-utils": "10.13.6",
|
||||
"@wordpress/e2e-test-utils-playwright": "0.11.0",
|
||||
"@wordpress/scripts": "26.13.6",
|
||||
"autoprefixer": "10.4.16",
|
||||
"chalk": "5.3.0",
|
||||
@@ -3743,6 +3745,25 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
|
||||
"integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.32.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz",
|
||||
@@ -7011,14 +7032,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright": {
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.6.tgz",
|
||||
"integrity": "sha512-qUIcQTB4lFG6BUVCPtzs4gDeO/9Pzz1Knq3Uvt1QIYojy9Yr6G6c3f3Mudql+HFfiXoj3B3BxGbA4oLSb7bI6w==",
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.11.0.tgz",
|
||||
"integrity": "sha512-UxDkVvm24FJdi4nkn5+n9XirYxdJ1QDZgnHotdrgGRel8NOvlEOlhmT/xpuAPQrVwo+yynxEKeb1Y2AT6jX9og==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wordpress/api-fetch": "^6.39.6",
|
||||
"@wordpress/keycodes": "^3.42.6",
|
||||
"@wordpress/url": "^3.43.6",
|
||||
"@wordpress/api-fetch": "^6.40.0",
|
||||
"@wordpress/keycodes": "^3.43.0",
|
||||
"@wordpress/url": "^3.44.0",
|
||||
"change-case": "^4.1.2",
|
||||
"form-data": "^4.0.0",
|
||||
"get-port": "^5.1.1",
|
||||
@@ -7032,6 +7053,79 @@
|
||||
"@playwright/test": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/api-fetch": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.40.0.tgz",
|
||||
"integrity": "sha512-sNk6vZW02ldci1EpNIjmm61323x/0n2Ra/cDHuehZf8avOH/OV0zF0dXxttT8M9Fncz+XZDSIHopm76dU3Phug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/i18n": "^4.43.0",
|
||||
"@wordpress/url": "^3.44.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/hooks": {
|
||||
"version": "3.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.43.0.tgz",
|
||||
"integrity": "sha512-SHSiyFUEsggihl0pDvY1l72q+fHMDyFHtIR3GCt0uV2ifctvoa/PIYdVwrxpGQaGdNEV25XCZ4kNldqJmfTddw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/i18n": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.43.0.tgz",
|
||||
"integrity": "sha512-XHU/vGgI+pgjJU9WzWDHke1u948z8i3OPpKUNdxc/gMcTkKaKM4D8DW1+VMSQHyU6pneP8+ph7EF+1RIehP3lQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/hooks": "^3.43.0",
|
||||
"gettext-parser": "^1.3.1",
|
||||
"memize": "^2.1.0",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tannin": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"pot-to-php": "tools/pot-to-php.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/keycodes": {
|
||||
"version": "3.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.43.0.tgz",
|
||||
"integrity": "sha512-B6rYPiKFdQTlnJfm93R+usQnjEODUX/K4+hMvY5ZZOinvxe7KyU/xyFGz7gRrS8WmIEYcJowqSmAlGgVs4XwKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/i18n": "^4.43.0",
|
||||
"change-case": "^4.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/e2e-test-utils-playwright/node_modules/@wordpress/url": {
|
||||
"version": "3.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.44.0.tgz",
|
||||
"integrity": "sha512-QNtTPFg/cGHTJLOvOtQCvCgn5quFQgJml8A88I05o4dyUH/tc92rb8LNXi0qcVz/z4JPrx2g3+Ki8heYellP4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"remove-accents": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -7956,6 +8050,28 @@
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/scripts/node_modules/@wordpress/e2e-test-utils-playwright": {
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.6.tgz",
|
||||
"integrity": "sha512-qUIcQTB4lFG6BUVCPtzs4gDeO/9Pzz1Knq3Uvt1QIYojy9Yr6G6c3f3Mudql+HFfiXoj3B3BxGbA4oLSb7bI6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@wordpress/api-fetch": "^6.39.6",
|
||||
"@wordpress/keycodes": "^3.42.6",
|
||||
"@wordpress/url": "^3.43.6",
|
||||
"change-case": "^4.1.2",
|
||||
"form-data": "^4.0.0",
|
||||
"get-port": "^5.1.1",
|
||||
"lighthouse": "^10.4.0",
|
||||
"mime": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/scripts/node_modules/ajv": {
|
||||
"version": "8.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||
@@ -8099,6 +8215,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/scripts/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/scripts/node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -8158,6 +8288,18 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@wordpress/scripts/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/scripts/node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
@@ -36729,6 +36871,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.32.0.tgz",
|
||||
"integrity": "sha512-zOdGloaF0jeec7hqoLqM5S3L2rR4WxMJs6lgiAeR70JlH7Ml54ZPoIIf3X7cvnKde3Q9jJ/gaxkFh8fYI9s1rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.32.0"
|
||||
}
|
||||
},
|
||||
"@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.5.tgz",
|
||||
@@ -39263,14 +39416,14 @@
|
||||
}
|
||||
},
|
||||
"@wordpress/e2e-test-utils-playwright": {
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.6.tgz",
|
||||
"integrity": "sha512-qUIcQTB4lFG6BUVCPtzs4gDeO/9Pzz1Knq3Uvt1QIYojy9Yr6G6c3f3Mudql+HFfiXoj3B3BxGbA4oLSb7bI6w==",
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.11.0.tgz",
|
||||
"integrity": "sha512-UxDkVvm24FJdi4nkn5+n9XirYxdJ1QDZgnHotdrgGRel8NOvlEOlhmT/xpuAPQrVwo+yynxEKeb1Y2AT6jX9og==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@wordpress/api-fetch": "^6.39.6",
|
||||
"@wordpress/keycodes": "^3.42.6",
|
||||
"@wordpress/url": "^3.43.6",
|
||||
"@wordpress/api-fetch": "^6.40.0",
|
||||
"@wordpress/keycodes": "^3.43.0",
|
||||
"@wordpress/url": "^3.44.0",
|
||||
"change-case": "^4.1.2",
|
||||
"form-data": "^4.0.0",
|
||||
"get-port": "^5.1.1",
|
||||
@@ -39278,6 +39431,61 @@
|
||||
"mime": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wordpress/api-fetch": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.40.0.tgz",
|
||||
"integrity": "sha512-sNk6vZW02ldci1EpNIjmm61323x/0n2Ra/cDHuehZf8avOH/OV0zF0dXxttT8M9Fncz+XZDSIHopm76dU3Phug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/i18n": "^4.43.0",
|
||||
"@wordpress/url": "^3.44.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/hooks": {
|
||||
"version": "3.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.43.0.tgz",
|
||||
"integrity": "sha512-SHSiyFUEsggihl0pDvY1l72q+fHMDyFHtIR3GCt0uV2ifctvoa/PIYdVwrxpGQaGdNEV25XCZ4kNldqJmfTddw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/i18n": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.43.0.tgz",
|
||||
"integrity": "sha512-XHU/vGgI+pgjJU9WzWDHke1u948z8i3OPpKUNdxc/gMcTkKaKM4D8DW1+VMSQHyU6pneP8+ph7EF+1RIehP3lQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/hooks": "^3.43.0",
|
||||
"gettext-parser": "^1.3.1",
|
||||
"memize": "^2.1.0",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tannin": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/keycodes": {
|
||||
"version": "3.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.43.0.tgz",
|
||||
"integrity": "sha512-B6rYPiKFdQTlnJfm93R+usQnjEODUX/K4+hMvY5ZZOinvxe7KyU/xyFGz7gRrS8WmIEYcJowqSmAlGgVs4XwKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/i18n": "^4.43.0",
|
||||
"change-case": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"@wordpress/url": {
|
||||
"version": "3.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.44.0.tgz",
|
||||
"integrity": "sha512-QNtTPFg/cGHTJLOvOtQCvCgn5quFQgJml8A88I05o4dyUH/tc92rb8LNXi0qcVz/z4JPrx2g3+Ki8heYellP4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"remove-accents": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
@@ -39957,6 +40165,22 @@
|
||||
"webpack-dev-server": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wordpress/e2e-test-utils-playwright": {
|
||||
"version": "0.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-0.10.6.tgz",
|
||||
"integrity": "sha512-qUIcQTB4lFG6BUVCPtzs4gDeO/9Pzz1Knq3Uvt1QIYojy9Yr6G6c3f3Mudql+HFfiXoj3B3BxGbA4oLSb7bI6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@wordpress/api-fetch": "^6.39.6",
|
||||
"@wordpress/keycodes": "^3.42.6",
|
||||
"@wordpress/url": "^3.43.6",
|
||||
"change-case": "^4.1.2",
|
||||
"form-data": "^4.0.0",
|
||||
"get-port": "^5.1.1",
|
||||
"lighthouse": "^10.4.0",
|
||||
"mime": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "8.12.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
|
||||
@@ -40053,6 +40277,17 @@
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -40097,6 +40332,12 @@
|
||||
"p-locate": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"dev": true
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@lodder/grunt-postcss": "^3.1.1",
|
||||
"@playwright/test": "1.32.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
|
||||
"@wordpress/babel-preset-default": "7.26.6",
|
||||
"@wordpress/dependency-extraction-webpack-plugin": "4.25.6",
|
||||
"@wordpress/e2e-test-utils": "10.13.6",
|
||||
"@wordpress/e2e-test-utils-playwright": "0.11.0",
|
||||
"@wordpress/scripts": "26.13.6",
|
||||
"autoprefixer": "10.4.16",
|
||||
"chalk": "5.3.0",
|
||||
@@ -189,10 +191,10 @@
|
||||
"env:cli": "node ./tools/local-env/scripts/docker.js run cli",
|
||||
"env:logs": "node ./tools/local-env/scripts/docker.js logs",
|
||||
"env:pull": "node ./tools/local-env/scripts/docker.js pull",
|
||||
"test:performance": "node ./tests/performance/run-tests.js",
|
||||
"test:performance": "wp-scripts test-playwright --config tests/performance/playwright.config.js",
|
||||
"test:php": "node ./tools/local-env/scripts/docker.js run -T php composer update -W && node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit",
|
||||
"test:e2e": "node ./tests/e2e/run-tests.js",
|
||||
"test:visual": "node ./tests/visual-regression/run-tests.js",
|
||||
"test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js",
|
||||
"test:visual": "wp-scripts test-playwright --config tests/visual-regression/playwright.config.js",
|
||||
"sync-gutenberg-packages": "grunt sync-gutenberg-packages",
|
||||
"postsync-gutenberg-packages": "grunt wp-packages:sync-stable-blocks && grunt build --dev && grunt build"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
# E2E Tests
|
||||
# E2E Tests
|
||||
145
tests/e2e/config/bootstrap.js
vendored
145
tests/e2e/config/bootstrap.js
vendored
@@ -1,145 +0,0 @@
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
clearLocalStorage,
|
||||
enablePageDialogAccept,
|
||||
setBrowserViewport,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
|
||||
/**
|
||||
* Environment variables
|
||||
*/
|
||||
const { PUPPETEER_TIMEOUT } = process.env;
|
||||
|
||||
/**
|
||||
* Set of console logging types observed to protect against unexpected yet
|
||||
* handled (i.e. not catastrophic) errors or warnings. Each key corresponds
|
||||
* to the Puppeteer ConsoleMessage type, its value the corresponding function
|
||||
* on the console global object.
|
||||
*
|
||||
* @type {Object<string,string>}
|
||||
*/
|
||||
const OBSERVED_CONSOLE_MESSAGE_TYPES = {
|
||||
warning: 'warn',
|
||||
error: 'error',
|
||||
};
|
||||
|
||||
/**
|
||||
* Array of page event tuples of [ eventName, handler ].
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
const pageEvents = [];
|
||||
|
||||
// The Jest timeout is increased because these tests are a bit slow
|
||||
jest.setTimeout( PUPPETEER_TIMEOUT || 100000 );
|
||||
|
||||
|
||||
/**
|
||||
* Adds an event listener to the page to handle additions of page event
|
||||
* handlers, to assure that they are removed at test teardown.
|
||||
*/
|
||||
function capturePageEventsForTearDown() {
|
||||
page.on( 'newListener', ( eventName, listener ) => {
|
||||
pageEvents.push( [ eventName, listener ] );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all bound page event handlers.
|
||||
*/
|
||||
function removePageEvents() {
|
||||
pageEvents.forEach( ( [ eventName, handler ] ) => {
|
||||
page.removeListener( eventName, handler );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a page event handler to emit uncaught exception to process if one of
|
||||
* the observed console logging types is encountered.
|
||||
*/
|
||||
function observeConsoleLogging() {
|
||||
page.on( 'console', ( message ) => {
|
||||
const type = message.type();
|
||||
if ( ! OBSERVED_CONSOLE_MESSAGE_TYPES.hasOwnProperty( type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let 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;
|
||||
}
|
||||
|
||||
// An exception is made for jQuery migrate console warnings output by
|
||||
// the unminified script loaded in development environments.
|
||||
if ( text.includes( 'JQMIGRATE' ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
// A bug present in WordPress 5.2 will produce console warnings when
|
||||
// loading the Dashicons font. These can be safely ignored, as they do
|
||||
// not otherwise regress on application behavior. This logic should be
|
||||
// removed once the associated ticket has been closed.
|
||||
//
|
||||
// See: https://core.trac.wordpress.org/ticket/47183
|
||||
if (
|
||||
text.startsWith( 'Failed to decode downloaded font:' ) ||
|
||||
text.startsWith( 'OTS parsing error:' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ];
|
||||
|
||||
// As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of
|
||||
// type JSHandle for error logging, instead of the expected string.
|
||||
//
|
||||
// See: https://github.com/GoogleChrome/puppeteer/issues/3397
|
||||
//
|
||||
// The recommendation there to asynchronously resolve the error value
|
||||
// upon a console event may be prone to a race condition with the test
|
||||
// completion, leaving a possibility of an error not being surfaced
|
||||
// correctly. Instead, the logic here synchronously inspects the
|
||||
// internal object shape of the JSHandle to find the error text. If it
|
||||
// cannot be found, the default text value is used instead.
|
||||
text = get( message.args(), [ 0, '_remoteObject', 'description' ], text );
|
||||
|
||||
// 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 );
|
||||
} );
|
||||
}
|
||||
|
||||
// Before every test suite run, delete all content created by the test. This ensures
|
||||
// other posts/comments/etc. aren't dirtying tests and tests don't depend on
|
||||
// each other's side-effects.
|
||||
beforeAll( async () => {
|
||||
capturePageEventsForTearDown();
|
||||
enablePageDialogAccept();
|
||||
observeConsoleLogging();
|
||||
await page.emulateMediaFeatures( [
|
||||
{ name: 'prefers-reduced-motion', value: 'reduce' },
|
||||
] );
|
||||
await setBrowserViewport( 'large' );
|
||||
} );
|
||||
|
||||
afterEach( async () => {
|
||||
await clearLocalStorage();
|
||||
await setBrowserViewport( 'large' );
|
||||
} );
|
||||
|
||||
afterAll( () => {
|
||||
removePageEvents();
|
||||
} );
|
||||
43
tests/e2e/config/global-setup.js
Normal file
43
tests/e2e/config/global-setup.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { request } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('@playwright/test').FullConfig} config
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function globalSetup( config ) {
|
||||
const { storageState, baseURL } = config.projects[ 0 ].use;
|
||||
const storageStatePath =
|
||||
typeof storageState === 'string' ? storageState : undefined;
|
||||
|
||||
const requestContext = await request.newContext( {
|
||||
baseURL,
|
||||
} );
|
||||
|
||||
const requestUtils = new RequestUtils( requestContext, {
|
||||
storageStatePath,
|
||||
} );
|
||||
|
||||
// Authenticate and save the storageState to disk.
|
||||
await requestUtils.setupRest();
|
||||
|
||||
// Reset the test environment before running the tests.
|
||||
await Promise.all( [
|
||||
requestUtils.activateTheme( 'twentytwentyone' ),
|
||||
requestUtils.deleteAllPosts(),
|
||||
requestUtils.deleteAllBlocks(),
|
||||
requestUtils.resetPreferences(),
|
||||
] );
|
||||
|
||||
await requestContext.dispose();
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
@@ -1,10 +0,0 @@
|
||||
const config = require( '@wordpress/scripts/config/jest-e2e.config' );
|
||||
|
||||
const jestE2EConfig = {
|
||||
...config,
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/config/bootstrap.js',
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = jestE2EConfig;
|
||||
27
tests/e2e/playwright.config.js
Normal file
27
tests/e2e/playwright.config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const baseConfig = require( '@wordpress/scripts/config/playwright.config' );
|
||||
|
||||
process.env.WP_ARTIFACTS_PATH ??= path.join( process.cwd(), 'artifacts' );
|
||||
process.env.STORAGE_STATE_PATH ??= path.join(
|
||||
process.env.WP_ARTIFACTS_PATH,
|
||||
'storage-states/admin.json'
|
||||
);
|
||||
|
||||
const config = defineConfig( {
|
||||
...baseConfig,
|
||||
globalSetup: require.resolve( './config/global-setup.js' ),
|
||||
webServer: {
|
||||
...baseConfig.webServer,
|
||||
command: 'npm run env:start',
|
||||
},
|
||||
} );
|
||||
|
||||
export default config;
|
||||
@@ -1,13 +0,0 @@
|
||||
const dotenv = require( 'dotenv' );
|
||||
const dotenv_expand = require( 'dotenv-expand' );
|
||||
const { execSync } = require( 'child_process' );
|
||||
|
||||
// WP_BASE_URL interpolates LOCAL_PORT, so needs to be parsed by dotenv_expand().
|
||||
dotenv_expand.expand( dotenv.config() );
|
||||
|
||||
// Run the tests, passing additional arguments through to the test script.
|
||||
execSync(
|
||||
'wp-scripts test-e2e --config tests/e2e/jest.config.js ' +
|
||||
process.argv.slice( 2 ).join( ' ' ),
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
@@ -1,38 +1,46 @@
|
||||
import {
|
||||
visitAdminPage,
|
||||
createNewPost,
|
||||
publishPost,
|
||||
trashAllPosts,
|
||||
createURL,
|
||||
logout,
|
||||
} from "@wordpress/e2e-test-utils";
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
describe( 'Cache Control header directives', () => {
|
||||
test.describe( 'Cache Control header directives', () => {
|
||||
test.beforeAll( async ( { requestUtils } ) => {
|
||||
await requestUtils.deleteAllPosts();
|
||||
});
|
||||
|
||||
beforeEach( async () => {
|
||||
await trashAllPosts();
|
||||
} );
|
||||
test(
|
||||
'No private directive present in cache control when user not logged in.',
|
||||
async ( { browser, admin, editor}
|
||||
) => {
|
||||
await admin.createNewPost( { title: 'Hello World' } );
|
||||
await editor.publishPost();
|
||||
|
||||
it( 'No private directive present in cache control when user not logged in.', async () => {
|
||||
await createNewPost( { title: 'Hello World' } );
|
||||
await publishPost();
|
||||
await logout();
|
||||
await admin.visitAdminPage( '/' );
|
||||
|
||||
const response = await page.goto( createURL( '/hello-world/' ) );
|
||||
// Create a new incognito browser context to simulate logged-out state.
|
||||
const context = await browser.newContext();
|
||||
const loggedOutPage = await context.newPage();
|
||||
|
||||
const response = await loggedOutPage.goto( '/hello-world/' );
|
||||
const responseHeaders = response.headers();
|
||||
|
||||
// Dispose context once it's no longer needed.
|
||||
await context.close();
|
||||
|
||||
expect( responseHeaders ).toEqual( expect.not.objectContaining( { "cache-control": "no-store" } ) );
|
||||
expect( responseHeaders ).toEqual( expect.not.objectContaining( { "cache-control": "private" } ) );
|
||||
} );
|
||||
|
||||
it( 'Private directive header present in cache control when logged in.', async () => {
|
||||
await visitAdminPage( '/' );
|
||||
test(
|
||||
'Private directive header present in cache control when logged in.',
|
||||
async ( { page, admin }
|
||||
) => {
|
||||
await admin.visitAdminPage( '/' );
|
||||
|
||||
const response = await page.goto( createURL( '/wp-admin' ) );
|
||||
const response = await page.goto( '/wp-admin' );
|
||||
const responseHeaders = response.headers();
|
||||
|
||||
expect( responseHeaders[ 'cache-control' ] ).toContain( 'no-store' );
|
||||
expect( responseHeaders[ 'cache-control' ] ).toContain( 'private' );
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import {
|
||||
pressKeyTimes,
|
||||
trashAllPosts,
|
||||
visitAdminPage,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
describe( 'Quick Draft', () => {
|
||||
beforeEach( async () => {
|
||||
await trashAllPosts();
|
||||
test.describe( 'Quick Draft', () => {
|
||||
test.beforeEach( async ({ requestUtils }) => {
|
||||
await requestUtils.deleteAllPosts();
|
||||
} );
|
||||
|
||||
it( 'Allows draft to be created with Title and Content', async () => {
|
||||
await visitAdminPage( '/' );
|
||||
test( 'Allows draft to be created with Title and Content', async ( {
|
||||
admin,
|
||||
page
|
||||
} ) => {
|
||||
await admin.visitAdminPage( '/' );
|
||||
|
||||
// Wait for Quick Draft title field to appear and focus it
|
||||
const draftTitleField = await page.waitForSelector(
|
||||
'#quick-press #title'
|
||||
);
|
||||
await draftTitleField.focus();
|
||||
// Wait for Quick Draft title field to appear.
|
||||
const draftTitleField = page.locator(
|
||||
'#quick-press'
|
||||
).getByRole( 'textbox', { name: 'Title' } );
|
||||
|
||||
// Type in a title.
|
||||
await page.keyboard.type( 'Test Draft Title' );
|
||||
await expect( draftTitleField ).toBeVisible();
|
||||
|
||||
// Focus and fill in a title.
|
||||
await draftTitleField.fill( 'Test Draft Title' );
|
||||
|
||||
// Navigate to content field and type in some content
|
||||
await page.keyboard.press( 'Tab' );
|
||||
@@ -30,47 +33,42 @@ describe( 'Quick Draft', () => {
|
||||
await page.keyboard.press( 'Enter' );
|
||||
|
||||
// Check that new draft appears in Your Recent Drafts section
|
||||
const newDraft = await page.waitForSelector( '.drafts .draft-title' );
|
||||
|
||||
expect(
|
||||
await newDraft.evaluate( ( element ) => element.innerText )
|
||||
).toContain( 'Test Draft Title' );
|
||||
await expect(
|
||||
page.locator( '.drafts .draft-title' ).first().getByRole( 'link' )
|
||||
).toHaveText( 'Test Draft Title' );
|
||||
|
||||
// Check that new draft appears in Posts page
|
||||
await visitAdminPage( '/edit.php' );
|
||||
const postsListDraft = await page.waitForSelector(
|
||||
'.type-post.status-draft .title'
|
||||
);
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
expect(
|
||||
await postsListDraft.evaluate( ( element ) => element.innerText )
|
||||
).toContain( 'Test Draft Title' );
|
||||
await expect(
|
||||
page.locator( '.type-post.status-draft .title' ).first()
|
||||
).toContainText( 'Test Draft Title' );
|
||||
} );
|
||||
|
||||
it( 'Allows draft to be created without Title or Content', async () => {
|
||||
await visitAdminPage( '/' );
|
||||
test( 'Allows draft to be created without Title or Content', async ( {
|
||||
admin,
|
||||
page
|
||||
} ) => {
|
||||
await admin.visitAdminPage( '/' );
|
||||
|
||||
// Wait for Save Draft button to appear and click it
|
||||
const saveDraftButton = await page.waitForSelector(
|
||||
'#quick-press #save-post'
|
||||
);
|
||||
const saveDraftButton = page.locator(
|
||||
'#quick-press'
|
||||
).getByRole( 'button', { name: 'Save Draft' } );
|
||||
|
||||
await expect( saveDraftButton ).toBeVisible();
|
||||
await saveDraftButton.click();
|
||||
|
||||
// Check that new draft appears in Your Recent Drafts section
|
||||
const newDraft = await page.waitForSelector( '.drafts .draft-title a' );
|
||||
|
||||
expect(
|
||||
await newDraft.evaluate( ( element ) => element.innerText )
|
||||
).toContain( '(no title)' );
|
||||
await expect(
|
||||
page.locator( '.drafts .draft-title' ).first().getByRole( 'link' )
|
||||
).toHaveText( 'Untitled' );
|
||||
|
||||
// Check that new draft appears in Posts page
|
||||
await visitAdminPage( '/edit.php' );
|
||||
const postsListDraft = await page.waitForSelector(
|
||||
'.type-post.status-draft .title a'
|
||||
);
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
expect(
|
||||
await postsListDraft.evaluate( ( element ) => element.innerText )
|
||||
).toContain( '(no title)' );
|
||||
await expect(
|
||||
page.locator( '.type-post.status-draft .title' ).first()
|
||||
).toContainText( 'Untitled' );
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -1,137 +1,135 @@
|
||||
import {
|
||||
createNewPost,
|
||||
pressKeyTimes,
|
||||
publishPost,
|
||||
trashAllPosts,
|
||||
visitAdminPage,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
describe( 'Edit Posts', () => {
|
||||
beforeEach( async () => {
|
||||
await trashAllPosts();
|
||||
test.describe( 'Edit Posts', () => {
|
||||
test.beforeEach( async ( { requestUtils }) => {
|
||||
await requestUtils.deleteAllPosts();
|
||||
} );
|
||||
|
||||
it( 'displays a message in the posts table when no posts are present', async () => {
|
||||
await visitAdminPage( '/edit.php' );
|
||||
const noPostsMessage = await page.$x(
|
||||
'//td[text()="No posts found."]'
|
||||
);
|
||||
expect( noPostsMessage.length ).toBe( 1 );
|
||||
test( 'displays a message in the posts table when no posts are present',async ( {
|
||||
admin,
|
||||
page,
|
||||
} ) => {
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
await expect(
|
||||
page.getByRole( 'cell', { name: 'No posts found.' } )
|
||||
).toBeVisible();
|
||||
} );
|
||||
|
||||
it( 'shows a single post after one is published with the correct title', async () => {
|
||||
test( 'shows a single post after one is published with the correct title',async ( {
|
||||
admin,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
const title = 'Test Title';
|
||||
await createNewPost( { title } );
|
||||
await publishPost();
|
||||
await visitAdminPage( '/edit.php' );
|
||||
await admin.createNewPost( { title } );
|
||||
await editor.publishPost();
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
await page.waitForSelector( '#the-list .type-post' );
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Expect there to be one row in the post list.
|
||||
const posts = await page.$$( '#the-list .type-post' );
|
||||
expect( posts.length ).toBe( 1 );
|
||||
|
||||
const [ firstPost ] = posts;
|
||||
const posts = listTable.locator( '.row-title' );
|
||||
await expect( posts ).toHaveCount( 1 );
|
||||
|
||||
// Expect the title of the post to be correct.
|
||||
const postTitle = await firstPost.$x(
|
||||
`//a[contains(@class, "row-title")][contains(text(), "${ title }")]`
|
||||
);
|
||||
expect( postTitle.length ).toBe( 1 );
|
||||
expect( posts.first() ).toHaveText( title );
|
||||
} );
|
||||
|
||||
it( 'allows an existing post to be edited using the Edit button', async () => {
|
||||
test( 'allows an existing post to be edited using the Edit button', async ( {
|
||||
admin,
|
||||
editor,
|
||||
page,
|
||||
} ) => {
|
||||
const title = 'Test Title';
|
||||
await createNewPost( { title } );
|
||||
await publishPost();
|
||||
await visitAdminPage( '/edit.php' );
|
||||
await admin.createNewPost( { title } );
|
||||
await editor.publishPost();
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
await page.waitForSelector( '#the-list .type-post' );
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Click the post title (edit) link
|
||||
const [ editLink ] = await page.$x(
|
||||
`//a[contains(@class, "row-title")][contains(text(), "${ title }")]`
|
||||
);
|
||||
await editLink.click();
|
||||
await listTable.getByRole( 'link', { name: `“${ title }” (Edit)` } ).click();
|
||||
|
||||
// Wait for the editor iframe to load, and switch to it as the active content frame.
|
||||
const editorFrame = await page.waitForSelector( 'iframe[name="editor-canvas"]' );
|
||||
await page
|
||||
.frameLocator( '[name=editor-canvas]' )
|
||||
.locator( 'body > *' )
|
||||
.first()
|
||||
.waitFor();
|
||||
|
||||
const innerFrame = await editorFrame.contentFrame();
|
||||
const editorPostTitle = editor.canvas.getByRole( 'textbox', { name: 'Add title' } );
|
||||
|
||||
// Wait for title field to render onscreen.
|
||||
await innerFrame.waitForSelector( '.editor-post-title__input' );
|
||||
|
||||
// Expect to now be in the editor with the correct post title shown.
|
||||
const editorPostTitleInput = await innerFrame.$x(
|
||||
`//h1[contains(@class, "editor-post-title__input")][contains(text(), "${ title }")]`
|
||||
);
|
||||
expect( editorPostTitleInput.length ).toBe( 1 );
|
||||
// Expect title field to be in the editor with correct title shown.
|
||||
await expect( editorPostTitle ).toBeVisible();
|
||||
await expect( editorPostTitle ).toHaveText( title );
|
||||
} );
|
||||
|
||||
it( 'allows an existing post to be quick edited using the Quick Edit button', async () => {
|
||||
test( 'allows an existing post to be quick edited using the Quick Edit button', async ( {
|
||||
admin,
|
||||
editor,
|
||||
page,
|
||||
pageUtils
|
||||
} ) => {
|
||||
const title = 'Test Title';
|
||||
await createNewPost( { title } );
|
||||
await publishPost();
|
||||
await visitAdminPage( '/edit.php' );
|
||||
await admin.createNewPost( { title } );
|
||||
await editor.publishPost();
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
await page.waitForSelector( '#the-list .type-post' );
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Focus on the post title link.
|
||||
const [ editLink ] = await page.$x(
|
||||
`//a[contains(@class, "row-title")][contains(text(), "${ title }")]`
|
||||
);
|
||||
await editLink.focus();
|
||||
// // Focus on the post title link.
|
||||
await listTable.getByRole( 'link', { name: `“${ title }” (Edit)` } ).focus();
|
||||
|
||||
// Tab to the Quick Edit button and press Enter to quick edit.
|
||||
await pressKeyTimes( 'Tab', 2 );
|
||||
await pageUtils.pressKeys( 'Tab', { times: 2 } )
|
||||
await page.keyboard.press( 'Enter' );
|
||||
|
||||
// Type in the currently focused (title) field to modify the title, testing that focus is moved to the input.
|
||||
await page.keyboard.type( ' Edited' );
|
||||
|
||||
// Update the post.
|
||||
await page.click( '.button.save' );
|
||||
await page.getByRole( 'button', { name: 'Update' } ).click();
|
||||
|
||||
// Wait for the quick edit button to reappear.
|
||||
await page.waitForSelector( 'button.editinline', { visible: true } );
|
||||
await expect( page.getByRole( 'button', { name: 'Quick Edit' } ) ).toBeVisible();
|
||||
|
||||
// Expect there to be one row in the post list.
|
||||
const posts = await page.$$( '#the-list tr.type-post' );
|
||||
expect( posts.length ).toBe( 1 );
|
||||
|
||||
const [ firstPost ] = posts;
|
||||
const posts = listTable.locator( '.row-title' );
|
||||
await expect( posts ).toHaveCount( 1 );
|
||||
|
||||
// Expect the title of the post to be correct.
|
||||
const postTitle = await firstPost.$x(
|
||||
`//a[contains(@class, "row-title")][contains(text(), "${ title } Edited")]`
|
||||
);
|
||||
expect( postTitle.length ).toBe( 1 );
|
||||
expect( posts.first() ).toHaveText( `${ title } Edited` );
|
||||
} );
|
||||
it( 'allows an existing post to be deleted using the Trash button', async () => {
|
||||
const title = 'Test Title';
|
||||
await createNewPost( { title } );
|
||||
await publishPost();
|
||||
await visitAdminPage( '/edit.php' );
|
||||
|
||||
await page.waitForSelector( '#the-list .type-post' );
|
||||
test( 'allows an existing post to be deleted using the Trash button', async ( {
|
||||
admin,
|
||||
editor,
|
||||
page,
|
||||
pageUtils
|
||||
} ) => {
|
||||
const title = 'Test Title';
|
||||
await admin.createNewPost( { title } );
|
||||
await editor.publishPost();
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Focus on the post title link.
|
||||
const [ editLink ] = await page.$x(
|
||||
`//a[contains(@class, "row-title")][contains(text(), "${ title }")]`
|
||||
);
|
||||
await editLink.focus();
|
||||
await listTable.getByRole( 'link', { name: `“${ title }” (Edit)` } ).focus();
|
||||
|
||||
// Tab to the Trash button and press Enter to delete the post.
|
||||
await pressKeyTimes( 'Tab', 3 );
|
||||
await pageUtils.pressKeys( 'Tab', { times: 3 } )
|
||||
await page.keyboard.press( 'Enter' );
|
||||
|
||||
const noPostsMessage = await page.waitForSelector(
|
||||
'#the-list .no-items td'
|
||||
);
|
||||
|
||||
expect(
|
||||
await noPostsMessage.evaluate( ( element ) => element.innerText )
|
||||
).toBe( 'No posts found.' );
|
||||
await expect(
|
||||
page.getByRole( 'cell', { name: 'No posts found.' } )
|
||||
).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -1,72 +1,55 @@
|
||||
import {
|
||||
visitAdminPage,
|
||||
createNewPost,
|
||||
trashAllPosts,
|
||||
publishPost,
|
||||
} from "@wordpress/e2e-test-utils";
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
const POST_TITLE = "Test Title";
|
||||
const POST_TITLE = 'Test Title';
|
||||
|
||||
describe("Empty Trash", () => {
|
||||
async function createPost(title) {
|
||||
// Create a Post
|
||||
await createNewPost({ title });
|
||||
await publishPost();
|
||||
}
|
||||
test.describe( 'Empty Trash', () => {
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.deleteAllPosts();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await trashAllPosts();
|
||||
});
|
||||
test('Empty Trash', async ({ admin, editor, page }) => {
|
||||
await admin.createNewPost( { title: POST_TITLE } );
|
||||
await editor.publishPost();
|
||||
|
||||
it("Empty Trash", async () => {
|
||||
await createPost(POST_TITLE);
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
await visitAdminPage("/edit.php");
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Move post to trash
|
||||
await page.hover(`[aria-label^="“${POST_TITLE}”"]`);
|
||||
await page.click(`[aria-label='Move “${POST_TITLE}” to the Trash']`);
|
||||
// Move post to trash
|
||||
await listTable.getByRole( 'link', { name: `“${ POST_TITLE }” (Edit)` } ).hover();
|
||||
await listTable.getByRole( 'link', { name: `Move “${POST_TITLE}” to the Trash` } ).click();
|
||||
|
||||
// Empty trash
|
||||
const trashTab = await page.waitForXPath('//h2[text()="Filter posts list"]/following-sibling::ul//a[contains(text(), "Trash")]');
|
||||
await Promise.all([
|
||||
trashTab.click(),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
const deleteAllButton = await page.waitForSelector('input[value="Empty Trash"]');
|
||||
await Promise.all([
|
||||
deleteAllButton.click(),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
// Empty trash
|
||||
await page.getByRole( 'link', { name: 'Trash' } ).click();
|
||||
await page.getByRole( 'button', { name: 'Empty Trash' } ).first().click();
|
||||
|
||||
const messageElement = await page.waitForSelector("#message");
|
||||
const message = await messageElement.evaluate((node) => node.innerText);
|
||||
// Until we have `deleteAllPosts`, the number of posts being deleted could be dynamic.
|
||||
expect(message).toMatch(/\d+ posts? permanently deleted\./);
|
||||
});
|
||||
await expect( page.locator( '#message' ) ).toContainText( '1 post permanently deleted.' );
|
||||
} );
|
||||
|
||||
it("Restore trash post", async () => {
|
||||
await createPost(POST_TITLE);
|
||||
test('Restore trash post', async ( { admin, editor, page }) => {
|
||||
await admin.createNewPost( { title: POST_TITLE } );
|
||||
await editor.publishPost();
|
||||
|
||||
await visitAdminPage("/edit.php");
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
|
||||
// Move one post to trash.
|
||||
await page.hover(`[aria-label^="“${POST_TITLE}”"]`);
|
||||
await page.click(`[aria-label='Move “${POST_TITLE}” to the Trash']`);
|
||||
const listTable = page.getByRole( 'table', { name: 'Table ordered by' } );
|
||||
await expect( listTable ).toBeVisible();
|
||||
|
||||
// Remove post from trash.
|
||||
const trashTab = await page.waitForXPath('//h2[text()="Filter posts list"]/following-sibling::ul//a[contains(text(), "Trash")]');
|
||||
await Promise.all([
|
||||
trashTab.click(),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
const [postTitle] = await page.$x(`//*[text()="${POST_TITLE}"]`);
|
||||
await postTitle.hover();
|
||||
await page.click(`[aria-label="Restore “${POST_TITLE}” from the Trash"]`);
|
||||
// Move post to trash
|
||||
await listTable.getByRole( 'link', { name: `“${ POST_TITLE }” (Edit)` } ).hover();
|
||||
await listTable.getByRole( 'link', { name: `Move “${POST_TITLE}” to the Trash` } ).click();
|
||||
|
||||
// Expect for success message for trashed post.
|
||||
const messageElement = await page.waitForSelector("#message");
|
||||
const message = await messageElement.evaluate((element) => element.innerText);
|
||||
expect(message).toContain("1 post restored from the Trash.");
|
||||
});
|
||||
});
|
||||
await page.getByRole( 'link', { name: 'Trash' } ).click();
|
||||
|
||||
// Remove post from trash.
|
||||
await listTable.getByRole( 'cell' ).filter( { hasText: POST_TITLE } ).hover();
|
||||
await listTable.getByRole( 'link', { name: `Restore “${POST_TITLE}” from the Trash` } ).click();
|
||||
|
||||
// Expect for success message for restored post.
|
||||
await expect( page.locator( '#message' ) ).toContainText( '1 post restored from the Trash.' );
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
import {
|
||||
activatePlugin,
|
||||
deactivatePlugin,
|
||||
installPlugin,
|
||||
uninstallPlugin,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
describe( 'Gutenberg plugin', () => {
|
||||
beforeAll( async () => {
|
||||
await installPlugin( 'gutenberg' );
|
||||
test.describe( 'Gutenberg plugin', () => {
|
||||
// Increasing timeout to 5 minutes because potential plugin install could take longer.
|
||||
test.setTimeout( 300_000 );
|
||||
|
||||
test.beforeAll( async ( { requestUtils } ) => {
|
||||
// Install Gutenberg plugin if it's not yet installed.
|
||||
const pluginsMap = await requestUtils.getPluginsMap();
|
||||
if ( ! pluginsMap.gutenberg ) {
|
||||
await requestUtils.rest( {
|
||||
method: 'POST',
|
||||
path: 'wp/v2/plugins?slug=gutenberg',
|
||||
} );
|
||||
}
|
||||
|
||||
// Refetch installed plugin details. It avoids stale values when the test installs the plugin.
|
||||
await requestUtils.getPluginsMap( /* forceRefetch */ true );
|
||||
await requestUtils.deactivatePlugin( 'gutenberg' );
|
||||
} );
|
||||
|
||||
afterAll( async () => {
|
||||
await uninstallPlugin( 'gutenberg' );
|
||||
} );
|
||||
test( 'should activate', async ( { requestUtils }) => {
|
||||
let plugin = await requestUtils.rest( {
|
||||
path: 'wp/v2/plugins/gutenberg/gutenberg',
|
||||
} );
|
||||
|
||||
it( 'should activate', async () => {
|
||||
await activatePlugin( 'gutenberg' );
|
||||
/*
|
||||
* If plugin activation fails, it will time out and throw an error,
|
||||
* since the activatePlugin helper is looking for a `.deactivate` link
|
||||
* which is only there if activation succeeds.
|
||||
*/
|
||||
await deactivatePlugin( 'gutenberg' );
|
||||
expect( plugin.status ).toBe( 'inactive' );
|
||||
|
||||
await requestUtils.activatePlugin( 'gutenberg' );
|
||||
|
||||
plugin = await requestUtils.rest( {
|
||||
path: 'wp/v2/plugins/gutenberg/gutenberg',
|
||||
} );
|
||||
|
||||
expect( plugin.status ).toBe( 'active' );
|
||||
|
||||
await requestUtils.deactivatePlugin( 'gutenberg' );
|
||||
|
||||
plugin = await requestUtils.rest( {
|
||||
path: 'wp/v2/plugins/gutenberg/gutenberg',
|
||||
} );
|
||||
|
||||
expect( plugin.status ).toBe( 'inactive' );
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { visitAdminPage } from '@wordpress/e2e-test-utils';
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
describe( 'Hello World', () => {
|
||||
it( 'Should load properly', async () => {
|
||||
await visitAdminPage( '/' );
|
||||
const nodes = await page.$x(
|
||||
'//h2[contains(text(), "Welcome to WordPress!")]'
|
||||
);
|
||||
expect( nodes.length ).not.toEqual( 0 );
|
||||
test.describe( 'Hello World', () => {
|
||||
test( 'Should load properly', async ( { admin, page }) => {
|
||||
await admin.visitAdminPage( '/' );
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Welcome to WordPress', level: 2 })
|
||||
).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -1,138 +1,133 @@
|
||||
import {
|
||||
visitAdminPage,
|
||||
__experimentalRest as rest,
|
||||
} from "@wordpress/e2e-test-utils";
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
async function getResponseForApplicationPassword() {
|
||||
return await rest({
|
||||
method: "GET",
|
||||
path: "/wp/v2/users/me/application-passwords",
|
||||
});
|
||||
}
|
||||
const TEST_APPLICATION_NAME = 'Test Application';
|
||||
|
||||
async function createApplicationPassword(applicationName) {
|
||||
await visitAdminPage("profile.php");
|
||||
await page.waitForSelector("#new_application_password_name");
|
||||
await page.type("#new_application_password_name", applicationName);
|
||||
await page.click("#do_new_application_password");
|
||||
|
||||
await page.waitForSelector("#application-passwords-section .notice");
|
||||
}
|
||||
|
||||
async function createApplicationPasswordWithApi(applicationName) {
|
||||
await rest({
|
||||
method: "POST",
|
||||
path: "/wp/v2/users/me/application-passwords",
|
||||
data: {
|
||||
name: applicationName,
|
||||
test.describe( 'Manage applications passwords', () => {
|
||||
test.use( {
|
||||
applicationPasswords: async ( { requestUtils, admin, page }, use ) => {
|
||||
await use( new ApplicationPasswords( { requestUtils, admin, page } ) );
|
||||
},
|
||||
});
|
||||
}
|
||||
} );
|
||||
|
||||
async function revokeAllApplicationPasswordsWithApi() {
|
||||
await rest({
|
||||
method: "DELETE",
|
||||
path: `/wp/v2/users/me/application-passwords`,
|
||||
});
|
||||
}
|
||||
test.beforeEach(async ( { applicationPasswords } ) => {
|
||||
await applicationPasswords.delete();
|
||||
} );
|
||||
|
||||
describe("Manage applications passwords", () => {
|
||||
const TEST_APPLICATION_NAME = "Test Application";
|
||||
test('should correctly create a new application password', async ( {
|
||||
page,
|
||||
applicationPasswords
|
||||
} ) => {
|
||||
await applicationPasswords.create();
|
||||
|
||||
beforeEach(async () => {
|
||||
await revokeAllApplicationPasswordsWithApi();
|
||||
});
|
||||
const [ app ] = await applicationPasswords.get();
|
||||
expect( app['name']).toBe( TEST_APPLICATION_NAME );
|
||||
|
||||
it("should correctly create a new application password", async () => {
|
||||
await createApplicationPassword(TEST_APPLICATION_NAME);
|
||||
const successMessage = page.getByRole( 'alert' );
|
||||
|
||||
const response = await getResponseForApplicationPassword();
|
||||
expect(response[0]["name"]).toBe(TEST_APPLICATION_NAME);
|
||||
|
||||
const successMessage = await page.waitForSelector(
|
||||
"#application-passwords-section .notice-success"
|
||||
);
|
||||
expect(
|
||||
await successMessage.evaluate((element) => element.innerText)
|
||||
).toContain(
|
||||
await expect( successMessage ).toHaveClass( /notice-success/ );
|
||||
await expect(
|
||||
successMessage
|
||||
).toContainText(
|
||||
`Your new password for ${TEST_APPLICATION_NAME} is: \n\nBe sure to save this in a safe location. You will not be able to retrieve it.`
|
||||
);
|
||||
} );
|
||||
|
||||
test('should not allow to create two applications passwords with the same name', async ( {
|
||||
page,
|
||||
applicationPasswords
|
||||
} ) => {
|
||||
await applicationPasswords.create();
|
||||
await applicationPasswords.create();
|
||||
|
||||
const errorMessage = page.getByRole( 'alert' );
|
||||
|
||||
await expect( errorMessage ).toHaveClass( /notice-error/ );
|
||||
await expect(
|
||||
errorMessage
|
||||
).toContainText(
|
||||
'Each application name should be unique.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should not allow to create two applications passwords with the same name", async () => {
|
||||
await createApplicationPassword(TEST_APPLICATION_NAME);
|
||||
await createApplicationPassword(TEST_APPLICATION_NAME);
|
||||
test( 'should correctly revoke a single application password', async ( {
|
||||
page,
|
||||
applicationPasswords
|
||||
} ) => {
|
||||
await applicationPasswords.create();
|
||||
|
||||
const errorMessage = await page.waitForSelector(
|
||||
"#application-passwords-section .notice-error"
|
||||
const revokeButton = page.getByRole( 'button', { name: `Revoke "${ TEST_APPLICATION_NAME }"` } );
|
||||
await expect( revokeButton ).toBeVisible();
|
||||
|
||||
// Revoke password.
|
||||
page.on( 'dialog', ( dialog ) => dialog.accept() );
|
||||
await revokeButton.click();
|
||||
|
||||
await expect(
|
||||
page.getByRole( 'alert' )
|
||||
).toContainText(
|
||||
'Application password revoked.'
|
||||
);
|
||||
|
||||
expect(
|
||||
await errorMessage.evaluate((element) => element.textContent)
|
||||
).toContain("Each application name should be unique.");
|
||||
});
|
||||
const response = await applicationPasswords.get();
|
||||
expect( response ).toEqual([]);
|
||||
} );
|
||||
|
||||
it("should correctly revoke a single application password", async () => {
|
||||
await createApplicationPassword(TEST_APPLICATION_NAME);
|
||||
test( 'should correctly revoke all the application passwords', async ( {
|
||||
page,
|
||||
applicationPasswords
|
||||
} ) => {
|
||||
await applicationPasswords.create();
|
||||
|
||||
const revokeApplicationButton = await page.waitForSelector(
|
||||
".application-passwords-user tr button.delete"
|
||||
);
|
||||
|
||||
const revocationDialogPromise = new Promise((resolve) => {
|
||||
page.once("dialog", resolve);
|
||||
});
|
||||
const revokeAllButton = page.getByRole( 'button', { name: 'Revoke all application passwords' } );
|
||||
await expect( revokeAllButton ).toBeVisible();
|
||||
|
||||
await Promise.all([
|
||||
revocationDialogPromise,
|
||||
revokeApplicationButton.click(),
|
||||
]);
|
||||
// Confirms revoking action.
|
||||
page.on( 'dialog', ( dialog ) => dialog.accept() );
|
||||
await revokeAllButton.click();
|
||||
|
||||
const successMessage = await page.waitForSelector(
|
||||
"#application-passwords-section .notice-success"
|
||||
);
|
||||
expect(
|
||||
await successMessage.evaluate((element) => element.textContent)
|
||||
).toContain("Application password revoked.");
|
||||
|
||||
const response = await getResponseForApplicationPassword();
|
||||
expect(response).toEqual([]);
|
||||
});
|
||||
|
||||
it("should correctly revoke all the application passwords", async () => {
|
||||
await createApplicationPassword(TEST_APPLICATION_NAME);
|
||||
|
||||
const revokeAllApplicationPasswordsButton = await page.waitForSelector(
|
||||
"#revoke-all-application-passwords"
|
||||
await expect(
|
||||
page.getByRole( 'alert' )
|
||||
).toContainText(
|
||||
'All application passwords revoked.'
|
||||
);
|
||||
|
||||
const revocationDialogPromise = new Promise((resolve) => {
|
||||
page.once("dialog", resolve);
|
||||
});
|
||||
const response = await applicationPasswords.get();
|
||||
expect( response ).toEqual([]);
|
||||
} );
|
||||
} );
|
||||
|
||||
await Promise.all([
|
||||
revocationDialogPromise,
|
||||
revokeAllApplicationPasswordsButton.click(),
|
||||
]);
|
||||
class ApplicationPasswords {
|
||||
constructor( { requestUtils, page, admin }) {
|
||||
this.requestUtils = requestUtils;
|
||||
this.page = page;
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is commented out because we're using enablePageDialogAccept
|
||||
* which is overly aggressive and no way to temporary disable it either.
|
||||
*/
|
||||
// await dialog.accept();
|
||||
async create(applicationName = TEST_APPLICATION_NAME) {
|
||||
await this.admin.visitAdminPage( '/profile.php' );
|
||||
|
||||
await page.waitForSelector(
|
||||
"#application-passwords-section .notice-success"
|
||||
);
|
||||
const newPasswordField = this.page.getByRole( 'textbox', { name: 'New Application Password Name' } );
|
||||
await expect( newPasswordField ).toBeVisible();
|
||||
await newPasswordField.fill( applicationName );
|
||||
|
||||
const successMessage = await page.waitForSelector(
|
||||
"#application-passwords-section .notice-success"
|
||||
);
|
||||
expect(
|
||||
await successMessage.evaluate((element) => element.textContent)
|
||||
).toContain("All application passwords revoked.");
|
||||
await this.page.getByRole( 'button', { name: 'Add New Application Password' } ).click();
|
||||
await expect( this.page.getByRole( 'alert' ) ).toBeVisible();
|
||||
}
|
||||
|
||||
const response = await getResponseForApplicationPassword();
|
||||
expect(response).toEqual([]);
|
||||
});
|
||||
});
|
||||
async get() {
|
||||
return this.requestUtils.rest( {
|
||||
method: 'GET',
|
||||
path: '/wp/v2/users/me/application-passwords',
|
||||
} );
|
||||
}
|
||||
|
||||
async delete() {
|
||||
await this.requestUtils.rest( {
|
||||
method: 'DELETE',
|
||||
path: '/wp/v2/users/me/application-passwords',
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
/**
|
||||
* External dependencies.
|
||||
*/
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const fs = require( 'node:fs' );
|
||||
const path = require( 'node:path' );
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { median } = require( './utils' );
|
||||
|
||||
/**
|
||||
@@ -23,18 +27,16 @@ const testSuites = [ 'home-block-theme', 'home-classic-theme' ];
|
||||
|
||||
// The current commit's results.
|
||||
const testResults = Object.fromEntries(
|
||||
testSuites.map( ( key ) => [
|
||||
key,
|
||||
parseFile( `${ key }.test.results.json` ),
|
||||
] )
|
||||
testSuites
|
||||
.filter( ( key ) => fs.existsSync( `${ key }.test.results.json` ) )
|
||||
.map( ( key ) => [ key, parseFile( `${ key }.test.results.json` ) ] )
|
||||
);
|
||||
|
||||
// The previous commit's results.
|
||||
const prevResults = Object.fromEntries(
|
||||
testSuites.map( ( key ) => [
|
||||
key,
|
||||
parseFile( `before-${ key }.test.results.json` ),
|
||||
] )
|
||||
testSuites
|
||||
.filter( ( key ) => fs.existsSync( `before-${ key }.test.results.json` ) )
|
||||
.map( ( key ) => [ key, parseFile( `before-${ key }.test.results.json` ) ] )
|
||||
);
|
||||
|
||||
const args = process.argv.slice( 2 );
|
||||
@@ -127,8 +129,8 @@ console.log( 'Performance Test Results\n' );
|
||||
console.log( 'Note: Due to the nature of how GitHub Actions work, some variance in the results is expected.\n' );
|
||||
|
||||
for ( const key of testSuites ) {
|
||||
const current = testResults[ key ];
|
||||
const prev = prevResults[ key ];
|
||||
const current = testResults[ key ] || {};
|
||||
const prev = prevResults[ key ] || {};
|
||||
|
||||
const title = ( key.charAt( 0 ).toUpperCase() + key.slice( 1 ) ).replace(
|
||||
/-+/g,
|
||||
@@ -152,14 +154,18 @@ for ( const key of testSuites ) {
|
||||
} );
|
||||
}
|
||||
|
||||
summaryMarkdown += `## ${ title }\n\n`;
|
||||
summaryMarkdown += `${ formatAsMarkdownTable( rows ) }\n`;
|
||||
if ( rows.length > 0 ) {
|
||||
summaryMarkdown += `## ${ title }\n\n`;
|
||||
summaryMarkdown += `${ formatAsMarkdownTable( rows ) }\n`;
|
||||
|
||||
console.log( title );
|
||||
console.table( rows );
|
||||
console.log( title );
|
||||
console.table( rows );
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
summaryFile,
|
||||
summaryMarkdown
|
||||
);
|
||||
if ( summaryFile ) {
|
||||
fs.writeFileSync(
|
||||
summaryFile,
|
||||
summaryMarkdown
|
||||
);
|
||||
}
|
||||
|
||||
41
tests/performance/config/bootstrap.js
vendored
41
tests/performance/config/bootstrap.js
vendored
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* WordPress dependencies.
|
||||
*/
|
||||
import {
|
||||
clearLocalStorage,
|
||||
enablePageDialogAccept,
|
||||
setBrowserViewport,
|
||||
} from '@wordpress/e2e-test-utils';
|
||||
|
||||
/**
|
||||
* Timeout, in seconds, that the test should be allowed to run.
|
||||
*
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
const PUPPETEER_TIMEOUT = process.env.PUPPETEER_TIMEOUT;
|
||||
|
||||
// The Jest timeout is increased because these tests are a bit slow.
|
||||
jest.setTimeout( PUPPETEER_TIMEOUT || 100000 );
|
||||
|
||||
async function setupBrowser() {
|
||||
await clearLocalStorage();
|
||||
await setBrowserViewport( 'large' );
|
||||
}
|
||||
|
||||
/*
|
||||
* Before every test suite run, delete all content created by the test. This ensures
|
||||
* other posts/comments/etc. aren't dirtying tests and tests don't depend on
|
||||
* each other's side-effects.
|
||||
*/
|
||||
beforeAll( async () => {
|
||||
enablePageDialogAccept();
|
||||
|
||||
await setBrowserViewport( 'large' );
|
||||
await page.emulateMediaFeatures( [
|
||||
{ name: 'prefers-reduced-motion', value: 'reduce' },
|
||||
] );
|
||||
} );
|
||||
|
||||
afterEach( async () => {
|
||||
await setupBrowser();
|
||||
} );
|
||||
40
tests/performance/config/global-setup.js
Normal file
40
tests/performance/config/global-setup.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { request } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('@playwright/test').FullConfig} config
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function globalSetup( config ) {
|
||||
const { storageState, baseURL } = config.projects[ 0 ].use;
|
||||
const storageStatePath =
|
||||
typeof storageState === 'string' ? storageState : undefined;
|
||||
|
||||
const requestContext = await request.newContext( {
|
||||
baseURL,
|
||||
} );
|
||||
|
||||
const requestUtils = new RequestUtils( requestContext, {
|
||||
storageStatePath,
|
||||
} );
|
||||
|
||||
// Authenticate and save the storageState to disk.
|
||||
await requestUtils.setupRest();
|
||||
|
||||
// Reset the test environment before running the tests.
|
||||
await Promise.all( [
|
||||
requestUtils.activateTheme( 'twentytwentyone' ),
|
||||
] );
|
||||
|
||||
await requestContext.dispose();
|
||||
}
|
||||
|
||||
export default globalSetup;
|
||||
38
tests/performance/config/performance-reporter.js
Normal file
38
tests/performance/config/performance-reporter.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { join, dirname, basename } from 'node:path';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getResultsFilename } from '../utils';
|
||||
|
||||
/**
|
||||
* @implements {import('@playwright/test/reporter').Reporter}
|
||||
*/
|
||||
class PerformanceReporter {
|
||||
/**
|
||||
*
|
||||
* @param {import('@playwright/test/reporter').TestCase} test
|
||||
* @param {import('@playwright/test/reporter').TestResult} result
|
||||
*/
|
||||
onTestEnd( test, result ) {
|
||||
const performanceResults = result.attachments.find(
|
||||
( attachment ) => attachment.name === 'results'
|
||||
);
|
||||
|
||||
if ( performanceResults?.body ) {
|
||||
writeFileSync(
|
||||
join(
|
||||
dirname( test.location.file ),
|
||||
getResultsFilename( basename( test.location.file, '.js' ) )
|
||||
),
|
||||
performanceResults.body.toString( 'utf-8' )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PerformanceReporter;
|
||||
@@ -1,14 +0,0 @@
|
||||
const config = require( '@wordpress/scripts/config/jest-e2e.config' );
|
||||
|
||||
const jestE2EConfig = {
|
||||
...config,
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/config/bootstrap.js',
|
||||
],
|
||||
globals: {
|
||||
// Number of requests to run per test.
|
||||
TEST_RUNS: 20,
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = jestE2EConfig;
|
||||
42
tests/performance/playwright.config.js
Normal file
42
tests/performance/playwright.config.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import baseConfig from '@wordpress/scripts/config/playwright.config';
|
||||
|
||||
process.env.WP_ARTIFACTS_PATH ??= path.join( process.cwd(), 'artifacts' );
|
||||
process.env.STORAGE_STATE_PATH ??= path.join(
|
||||
process.env.WP_ARTIFACTS_PATH,
|
||||
'storage-states/admin.json'
|
||||
);
|
||||
process.env.TEST_RUNS ??= '20';
|
||||
|
||||
const config = defineConfig( {
|
||||
...baseConfig,
|
||||
globalSetup: require.resolve( './config/global-setup.js' ),
|
||||
reporter: process.env.CI
|
||||
? './config/performance-reporter.js'
|
||||
: [ [ 'list' ], [ './config/performance-reporter.js' ] ],
|
||||
forbidOnly: !! process.env.CI,
|
||||
workers: 1,
|
||||
retries: 0,
|
||||
timeout: parseInt( process.env.TIMEOUT || '', 10 ) || 600_000, // Defaults to 10 minutes.
|
||||
// Don't report slow test "files", as we will be running our tests in serial.
|
||||
reportSlowTests: null,
|
||||
webServer: {
|
||||
...baseConfig.webServer,
|
||||
command: 'npm run env:start',
|
||||
},
|
||||
use: {
|
||||
...baseConfig.use,
|
||||
video: 'off',
|
||||
},
|
||||
} );
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
/**
|
||||
* External dependencies.
|
||||
*/
|
||||
const fs = require( 'fs' );
|
||||
const { join } = require( 'path' );
|
||||
const fs = require( 'node:fs' );
|
||||
const { join } = require( 'node:path' );
|
||||
const { median, getResultsFilename } = require( './utils' );
|
||||
|
||||
const testSuites = [
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* External dependencies.
|
||||
*/
|
||||
const dotenv = require( 'dotenv' );
|
||||
const dotenv_expand = require( 'dotenv-expand' );
|
||||
const { execSync } = require( 'child_process' );
|
||||
|
||||
// WP_BASE_URL interpolates LOCAL_PORT, so needs to be parsed by dotenv_expand().
|
||||
dotenv_expand.expand( dotenv.config() );
|
||||
|
||||
// Run the tests, passing additional arguments through to the test script.
|
||||
execSync(
|
||||
'wp-scripts test-e2e --config tests/performance/jest.config.js ' +
|
||||
process.argv.slice( 2 ).join( ' ' ),
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
@@ -1,67 +1,57 @@
|
||||
/**
|
||||
* External dependencies.
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const { basename, join } = require( 'path' );
|
||||
const { writeFileSync } = require( 'fs' );
|
||||
const {
|
||||
getResultsFilename,
|
||||
getTimeToFirstByte,
|
||||
getLargestContentfulPaint,
|
||||
} = require( './../utils' );
|
||||
import { test } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
/**
|
||||
* WordPress dependencies.
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { activateTheme, createURL } from '@wordpress/e2e-test-utils';
|
||||
import { camelCaseDashes } from '../utils';
|
||||
|
||||
describe( 'Server Timing - Twenty Twenty Three', () => {
|
||||
const results = {
|
||||
wpBeforeTemplate: [],
|
||||
wpTemplate: [],
|
||||
wpTotal: [],
|
||||
timeToFirstByte: [],
|
||||
largestContentfulPaint: [],
|
||||
lcpMinusTtfb: [],
|
||||
};
|
||||
const results = {
|
||||
timeToFirstByte: [],
|
||||
largestContentfulPaint: [],
|
||||
lcpMinusTtfb: [],
|
||||
};
|
||||
|
||||
beforeAll( async () => {
|
||||
await activateTheme( 'twentytwentythree' );
|
||||
test.describe( 'Front End - Twenty Twenty Three', () => {
|
||||
test.use( {
|
||||
storageState: {}, // User will be logged out.
|
||||
} );
|
||||
|
||||
afterAll( async () => {
|
||||
const resultsFilename = getResultsFilename(
|
||||
basename( __filename, '.js' )
|
||||
);
|
||||
writeFileSync(
|
||||
join( __dirname, resultsFilename ),
|
||||
JSON.stringify( results, null, 2 )
|
||||
);
|
||||
test.beforeAll( async ( { requestUtils } ) => {
|
||||
await requestUtils.activateTheme( 'twentytwentythree' );
|
||||
} );
|
||||
|
||||
it( 'Server Timing Metrics', async () => {
|
||||
let i = TEST_RUNS;
|
||||
while ( i-- ) {
|
||||
await page.goto( createURL( '/' ) );
|
||||
const navigationTimingJson = await page.evaluate( () =>
|
||||
JSON.stringify( performance.getEntriesByType( 'navigation' ) )
|
||||
);
|
||||
test.afterAll( async ( { requestUtils }, testInfo ) => {
|
||||
await testInfo.attach( 'results', {
|
||||
body: JSON.stringify( results, null, 2 ),
|
||||
contentType: 'application/json',
|
||||
} );
|
||||
await requestUtils.activateTheme( 'twentytwentyone' );
|
||||
} );
|
||||
|
||||
const [ navigationTiming ] = JSON.parse( navigationTimingJson );
|
||||
const iterations = Number( process.env.TEST_RUNS );
|
||||
for ( let i = 1; i <= iterations; i++ ) {
|
||||
test( `Measure load time metrics (${ i } of ${ iterations })`, async ( {
|
||||
page,
|
||||
metrics,
|
||||
} ) => {
|
||||
await page.goto( '/' );
|
||||
|
||||
results.wpBeforeTemplate.push(
|
||||
navigationTiming.serverTiming[ 0 ].duration
|
||||
);
|
||||
results.wpTemplate.push(
|
||||
navigationTiming.serverTiming[ 1 ].duration
|
||||
);
|
||||
results.wpTotal.push( navigationTiming.serverTiming[ 2 ].duration );
|
||||
const serverTiming = await metrics.getServerTiming();
|
||||
|
||||
const ttfb = await getTimeToFirstByte();
|
||||
const lcp = await getLargestContentfulPaint();
|
||||
for ( const [key, value] of Object.entries( serverTiming ) ) {
|
||||
results[ camelCaseDashes( key ) ] ??= [];
|
||||
results[ camelCaseDashes( key ) ].push( value );
|
||||
}
|
||||
|
||||
const ttfb = await metrics.getTimeToFirstByte();
|
||||
const lcp = await metrics.getLargestContentfulPaint();
|
||||
|
||||
results.timeToFirstByte.push( ttfb );
|
||||
results.largestContentfulPaint.push( lcp );
|
||||
results.timeToFirstByte.push( ttfb );
|
||||
results.lcpMinusTtfb.push( lcp - ttfb );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -1,71 +1,56 @@
|
||||
/**
|
||||
* External dependencies.
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const { basename, join } = require( 'path' );
|
||||
const { writeFileSync } = require( 'fs' );
|
||||
const { exec } = require( 'child_process' );
|
||||
const {
|
||||
getResultsFilename,
|
||||
getTimeToFirstByte,
|
||||
getLargestContentfulPaint,
|
||||
} = require( './../utils' );
|
||||
import { test } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
/**
|
||||
* WordPress dependencies.
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { activateTheme, createURL } from '@wordpress/e2e-test-utils';
|
||||
import { camelCaseDashes } from '../utils';
|
||||
|
||||
describe( 'Server Timing - Twenty Twenty One', () => {
|
||||
const results = {
|
||||
wpBeforeTemplate: [],
|
||||
wpTemplate: [],
|
||||
wpTotal: [],
|
||||
timeToFirstByte: [],
|
||||
largestContentfulPaint: [],
|
||||
lcpMinusTtfb: [],
|
||||
};
|
||||
const results = {
|
||||
timeToFirstByte: [],
|
||||
largestContentfulPaint: [],
|
||||
lcpMinusTtfb: [],
|
||||
};
|
||||
|
||||
beforeAll( async () => {
|
||||
await activateTheme( 'twentytwentyone' );
|
||||
await exec(
|
||||
'npm run env:cli -- menu location assign all-pages primary'
|
||||
);
|
||||
test.describe( 'Front End - Twenty Twenty One', () => {
|
||||
test.use( {
|
||||
storageState: {}, // User will be logged out.
|
||||
} );
|
||||
|
||||
afterAll( async () => {
|
||||
const resultsFilename = getResultsFilename(
|
||||
basename( __filename, '.js' )
|
||||
);
|
||||
writeFileSync(
|
||||
join( __dirname, resultsFilename ),
|
||||
JSON.stringify( results, null, 2 )
|
||||
);
|
||||
test.beforeAll( async ( { requestUtils } ) => {
|
||||
await requestUtils.activateTheme( 'twentytwentyone' );
|
||||
} );
|
||||
|
||||
it( 'Server Timing Metrics', async () => {
|
||||
let i = TEST_RUNS;
|
||||
while ( i-- ) {
|
||||
await page.goto( createURL( '/' ) );
|
||||
const navigationTimingJson = await page.evaluate( () =>
|
||||
JSON.stringify( performance.getEntriesByType( 'navigation' ) )
|
||||
);
|
||||
test.afterAll( async ( {}, testInfo ) => {
|
||||
await testInfo.attach( 'results', {
|
||||
body: JSON.stringify( results, null, 2 ),
|
||||
contentType: 'application/json',
|
||||
} );
|
||||
} );
|
||||
|
||||
const [ navigationTiming ] = JSON.parse( navigationTimingJson );
|
||||
const iterations = Number( process.env.TEST_RUNS );
|
||||
for ( let i = 1; i <= iterations; i++ ) {
|
||||
test( `Measure load time metrics (${ i } of ${ iterations })`, async ( {
|
||||
page,
|
||||
metrics,
|
||||
} ) => {
|
||||
await page.goto( '/' );
|
||||
|
||||
results.wpBeforeTemplate.push(
|
||||
navigationTiming.serverTiming[ 0 ].duration
|
||||
);
|
||||
results.wpTemplate.push(
|
||||
navigationTiming.serverTiming[ 1 ].duration
|
||||
);
|
||||
results.wpTotal.push( navigationTiming.serverTiming[ 2 ].duration );
|
||||
const serverTiming = await metrics.getServerTiming();
|
||||
|
||||
const ttfb = await getTimeToFirstByte();
|
||||
const lcp = await getLargestContentfulPaint();
|
||||
for (const [key, value] of Object.entries( serverTiming ) ) {
|
||||
results[ camelCaseDashes( key ) ] ??= [];
|
||||
results[ camelCaseDashes( key ) ].push( value );
|
||||
}
|
||||
|
||||
const ttfb = await metrics.getTimeToFirstByte();
|
||||
const lcp = await metrics.getLargestContentfulPaint();
|
||||
|
||||
results.timeToFirstByte.push( ttfb );
|
||||
results.largestContentfulPaint.push( lcp );
|
||||
results.timeToFirstByte.push( ttfb );
|
||||
results.lcpMinusTtfb.push( lcp - ttfb );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
@@ -16,63 +16,24 @@ function median( array ) {
|
||||
/**
|
||||
* Gets the result file name.
|
||||
*
|
||||
* @param {string} File name.
|
||||
* @param {string} fileName File name.
|
||||
*
|
||||
* @return {string} Result file name.
|
||||
*/
|
||||
function getResultsFilename( fileName ) {
|
||||
const prefixArg = process.argv.find( ( arg ) =>
|
||||
arg.startsWith( '--prefix' )
|
||||
);
|
||||
const fileNamePrefix = prefixArg ? `${ prefixArg.split( '=' )[ 1 ] }-` : '';
|
||||
const resultsFilename = fileNamePrefix + fileName + '.results.json';
|
||||
return resultsFilename;
|
||||
const prefix = process.env.TEST_RESULTS_PREFIX;
|
||||
const fileNamePrefix = prefix ? `${ prefix.split( '=' )[ 1 ] }-` : '';
|
||||
return `${fileNamePrefix + fileName}.results.json`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns time to first byte (TTFB) using the Navigation Timing API.
|
||||
*
|
||||
* @see https://web.dev/ttfb/#measure-ttfb-in-javascript
|
||||
*
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
async function getTimeToFirstByte() {
|
||||
return page.evaluate( () => {
|
||||
const { responseStart, startTime } =
|
||||
performance.getEntriesByType( 'navigation' )[ 0 ];
|
||||
return responseStart - startTime;
|
||||
function camelCaseDashes( str ) {
|
||||
return str.replace( /-([a-z])/g, function( g ) {
|
||||
return g[ 1 ].toUpperCase();
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Largest Contentful Paint (LCP) value using the dedicated API.
|
||||
*
|
||||
* @see https://w3c.github.io/largest-contentful-paint/
|
||||
* @see https://web.dev/lcp/#measure-lcp-in-javascript
|
||||
*
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
async function getLargestContentfulPaint() {
|
||||
return page.evaluate(
|
||||
() =>
|
||||
new Promise( ( resolve ) => {
|
||||
new PerformanceObserver( ( entryList ) => {
|
||||
const entries = entryList.getEntries();
|
||||
// The last entry is the largest contentful paint.
|
||||
const largestPaintEntry = entries.at( -1 );
|
||||
|
||||
resolve( largestPaintEntry?.startTime || 0 );
|
||||
} ).observe( {
|
||||
type: 'largest-contentful-paint',
|
||||
buffered: true,
|
||||
} );
|
||||
} )
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
median,
|
||||
getResultsFilename,
|
||||
getTimeToFirstByte,
|
||||
getLargestContentfulPaint,
|
||||
camelCaseDashes,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Visual Regression Tests in WordPress Core
|
||||
|
||||
These tests make use of Jest and Puppeteer, with a setup very similar to that of the e2e tests, together with [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) for generating the visual diffs.
|
||||
These tests make use of Playwright, with a setup very similar to that of the e2e tests.
|
||||
|
||||
## How to Run the Tests Locally
|
||||
|
||||
1. Check out trunk.
|
||||
2. Run `npm run test:visual` to generate some base snapshots.
|
||||
3. Check out the feature branch to be tested.
|
||||
4. Run `npm run test:visual` again. If any tests fail, the diff images can be found in `tests/visual-regression/specs/__image_snapshots__/__diff_output__`.
|
||||
4. Run `npm run test:visual` again. If any tests fail, the diff images can be found in `artifacts/`
|
||||
|
||||
|
||||
10
tests/visual-regression/config/bootstrap.js
vendored
10
tests/visual-regression/config/bootstrap.js
vendored
@@ -1,10 +0,0 @@
|
||||
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
|
||||
|
||||
// All available options: https://github.com/americanexpress/jest-image-snapshot#%EF%B8%8F-api
|
||||
const toMatchImageSnapshot = configureToMatchImageSnapshot( {
|
||||
// Maximum diff to allow in px.
|
||||
failureThreshold: 1,
|
||||
} );
|
||||
|
||||
// Extend Jest's "expect" with image snapshot functionality.
|
||||
expect.extend( { toMatchImageSnapshot } );
|
||||
@@ -1,8 +0,0 @@
|
||||
const config = require( '@wordpress/scripts/config/jest-e2e.config' );
|
||||
|
||||
const jestVisualRegressionConfig = {
|
||||
...config,
|
||||
setupFilesAfterEnv: [ '<rootDir>/config/bootstrap.js' ],
|
||||
};
|
||||
|
||||
module.exports = jestVisualRegressionConfig;
|
||||
27
tests/visual-regression/playwright.config.js
Normal file
27
tests/visual-regression/playwright.config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import path from 'node:path';
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
const baseConfig = require( '@wordpress/scripts/config/playwright.config' );
|
||||
|
||||
process.env.WP_ARTIFACTS_PATH ??= path.join( process.cwd(), 'artifacts' );
|
||||
process.env.STORAGE_STATE_PATH ??= path.join(
|
||||
process.env.WP_ARTIFACTS_PATH,
|
||||
'storage-states/admin.json'
|
||||
);
|
||||
|
||||
const config = defineConfig( {
|
||||
...baseConfig,
|
||||
globalSetup: undefined,
|
||||
webServer: {
|
||||
...baseConfig.webServer,
|
||||
command: 'npm run env:start',
|
||||
},
|
||||
} );
|
||||
|
||||
export default config;
|
||||
@@ -1,13 +0,0 @@
|
||||
const dotenv = require( 'dotenv' );
|
||||
const dotenv_expand = require( 'dotenv-expand' );
|
||||
const { execSync } = require( 'child_process' );
|
||||
|
||||
// WP_BASE_URL interpolates LOCAL_PORT, so needs to be parsed by dotenv_expand().
|
||||
dotenv_expand.expand( dotenv.config() );
|
||||
|
||||
// Run the tests, passing additional arguments through to the test script.
|
||||
execSync(
|
||||
'wp-scripts test-e2e --config tests/visual-regression/jest.config.js ' +
|
||||
process.argv.slice( 2 ).join( ' ' ),
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
@@ -1,222 +1,166 @@
|
||||
import { visitAdminPage } from '@wordpress/e2e-test-utils';
|
||||
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
// See https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pagescreenshotoptions for more available options.
|
||||
const screenshotOptions = {
|
||||
fullPage: true,
|
||||
};
|
||||
const elementsToHide = [
|
||||
'#footer-upgrade',
|
||||
'#wp-admin-bar-root-default',
|
||||
'#toplevel_page_gutenberg'
|
||||
];
|
||||
|
||||
async function hideElementVisibility( elements ) {
|
||||
for ( let i = 0; i < elements.length; i++ ) {
|
||||
const elementOnPage = await page.$( elements[ i ] );
|
||||
if ( elementOnPage ) {
|
||||
await elementOnPage.evaluate( ( el ) => {
|
||||
el.style.visibility = 'hidden';
|
||||
} );
|
||||
}
|
||||
}
|
||||
await page.waitFor( 1000 );
|
||||
}
|
||||
|
||||
async function removeElementFromLayout( elements ) {
|
||||
for ( let i = 0; i < elements.length; i++ ) {
|
||||
const elementOnPage = await page.$( elements[ i ] );
|
||||
if ( elementOnPage ) {
|
||||
await elementOnPage.evaluate( ( el ) => {
|
||||
el.style.visibility = 'hidden';
|
||||
} );
|
||||
}
|
||||
}
|
||||
await page.waitFor( 1000 );
|
||||
}
|
||||
|
||||
const elementsToHide = [ '#footer-upgrade', '#wp-admin-bar-root-default' ];
|
||||
|
||||
const elementsToRemove = [ '#toplevel_page_gutenberg' ];
|
||||
|
||||
describe( 'Admin Visual Snapshots', () => {
|
||||
beforeAll( async () => {
|
||||
await page.setViewport( {
|
||||
width: 1000,
|
||||
height: 750,
|
||||
} );
|
||||
test.describe( 'Admin Visual Snapshots', () => {
|
||||
test( 'All Posts', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/edit.php' );
|
||||
await expect( page ).toHaveScreenshot( 'All Posts.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'All Posts', async () => {
|
||||
await visitAdminPage( '/edit.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Categories', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/edit-tags.php', 'taxonomy=category' );
|
||||
await expect( page ).toHaveScreenshot( 'Categories.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Categories', async () => {
|
||||
await visitAdminPage( '/edit-tags.php', 'taxonomy=category' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Tags', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/edit-tags.php', 'taxonomy=post_tag' );
|
||||
await expect( page ).toHaveScreenshot( 'Tags.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Tags', async () => {
|
||||
await visitAdminPage( '/edit-tags.php', 'taxonomy=post_tag' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Media Library', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/upload.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Media Library.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Media Library', async () => {
|
||||
await visitAdminPage( '/upload.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Add New Media', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/media-new.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Add New Media.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Add New Media', async () => {
|
||||
await visitAdminPage( '/media-new.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'All Pages', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/edit.php', 'post_type=page' );
|
||||
await expect( page ).toHaveScreenshot( 'All Pages.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'All Pages', async () => {
|
||||
await visitAdminPage( '/edit.php', 'post_type=page' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Comments', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/edit-comments.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Comments.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Comments', async () => {
|
||||
await visitAdminPage( '/edit-comments.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Widgets', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/widgets.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Widgets.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Widgets', async () => {
|
||||
await visitAdminPage( '/widgets.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Menus', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/nav-menus.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Menus.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Menus', async () => {
|
||||
await visitAdminPage( '/nav-menus.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Plugins', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/plugins.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Plugins.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Plugins', async () => {
|
||||
await visitAdminPage( '/plugins.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'All Users', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/users.php' );
|
||||
await expect( page ).toHaveScreenshot( 'All Users.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'All Users', async () => {
|
||||
await visitAdminPage( '/users.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Add New User', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/user-new.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Add New User.png', {
|
||||
mask: [
|
||||
...elementsToHide,
|
||||
'.password-input-wrapper'
|
||||
].map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Add New User', async () => {
|
||||
await visitAdminPage( '/user-new.php' );
|
||||
await hideElementVisibility( [
|
||||
...elementsToHide,
|
||||
'.password-input-wrapper',
|
||||
] );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Your Profile', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/profile.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Your Profile.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Your Profile', async () => {
|
||||
await visitAdminPage( '/profile.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Available Tools', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/tools.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Available Tools.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Available Tools', async () => {
|
||||
await visitAdminPage( '/tools.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Import', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/import.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Import.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Import', async () => {
|
||||
await visitAdminPage( '/import.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Export', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/export.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Export.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Export', async () => {
|
||||
await visitAdminPage( '/export.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Export Personal Data', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/export-personal-data.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Export Personal Data.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Export Personal Data', async () => {
|
||||
await visitAdminPage( '/export-personal-data.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Erase Personal Data', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/erase-personal-data.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Erase Personal Data.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Erase Personal Data', async () => {
|
||||
await visitAdminPage( '/erase-personal-data.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Reading Settings', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/options-reading.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Reading Settings.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Reading Settings', async () => {
|
||||
await visitAdminPage( '/options-reading.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Discussion Settings', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/options-discussion.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Discussion Settings.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Discussion Settings', async () => {
|
||||
await visitAdminPage( '/options-discussion.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Media Settings', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/options-media.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Media Settings.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
|
||||
it( 'Media Settings', async () => {
|
||||
await visitAdminPage( '/options-media.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
} );
|
||||
|
||||
it( 'Privacy Settings', async () => {
|
||||
await visitAdminPage( '/options-privacy.php' );
|
||||
await hideElementVisibility( elementsToHide );
|
||||
await removeElementFromLayout( elementsToRemove );
|
||||
const image = await page.screenshot( screenshotOptions );
|
||||
expect( image ).toMatchImageSnapshot();
|
||||
test( 'Privacy Settings', async ({ admin, page }) => {
|
||||
await admin.visitAdminPage( '/options-privacy.php' );
|
||||
await expect( page ).toHaveScreenshot( 'Privacy Settings.png', {
|
||||
mask: elementsToHide.map( ( selector ) => page.locator( selector ) ),
|
||||
});
|
||||
} );
|
||||
} );
|
||||
|
||||
Reference in New Issue
Block a user