Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate performance tests to Playwright #51084

Merged
merged 53 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
cdc5b1a
Create performance config
WunderBart May 30, 2023
58c9171
Define WP_ARTIFACTS_PATH in global setup
WunderBart May 30, 2023
35c35ef
Move perf utils and reporter
WunderBart May 30, 2023
b251340
Migrate Site Editor perf spec
WunderBart May 30, 2023
9837b4a
Try running in CI
WunderBart May 30, 2023
117ea63
Refactor reporter to Playwright
WunderBart May 30, 2023
c91913c
Ignore performance tests from the base E2E config
WunderBart May 30, 2023
cb1f49f
Delete the obsolete reporter
WunderBart May 30, 2023
33ea9c1
Migrate Post Editor perf specs
WunderBart May 30, 2023
4f05e97
Migrate the classic theme perf spec
WunderBart Jun 7, 2023
96344ac
Migrate the block theme perf spec
WunderBart Jun 7, 2023
fdf9eeb
Move WP_ASSETS_PATH env var declaration to the base config
WunderBart Jun 7, 2023
d9db7fe
Move the sample loop inside
WunderBart Jun 7, 2023
7d9f84e
Make the test independent from preceding steps
WunderBart Jun 7, 2023
c36eacc
Push missing util
WunderBart Jun 7, 2023
2f163a3
Small cleanup
WunderBart Jun 7, 2023
788548c
Make the perf config independent from the e2e one
WunderBart Jun 9, 2023
4bf1c77
Try tracing warnings to see what's wrong with CI
WunderBart Jun 9, 2023
7718268
Merge remote-tracking branch 'origin' into refactor/playwright-perfor…
WunderBart Jun 13, 2023
262d5be
Undo changes to the e2e config
WunderBart Jun 13, 2023
a0e8e4c
Rename perf reporter class
WunderBart Jun 13, 2023
ce36f27
Make tests as 1:1 to current ones as possible
WunderBart Jun 14, 2023
9556478
Add linefeed at the end of the custom report
WunderBart Jun 14, 2023
3649b25
Isolate performance tests from e2e tests
WunderBart Jun 14, 2023
c107fda
Fix esm/cjs mixup
WunderBart Jun 14, 2023
74ba986
Build packages correctly
WunderBart Jun 14, 2023
baef167
Merge branch 'trunk' into refactor/playwright-performance-tests
WunderBart Jun 14, 2023
cc06c99
Make site editor loading test take sample from a new page as before
WunderBart Jun 15, 2023
94dd81a
Try running a different test
WunderBart Jun 15, 2023
9424aeb
Try printing from the child process
WunderBart Jun 15, 2023
c7ac136
Make sure Playwright is installed
WunderBart Jun 15, 2023
07afec7
Revert "Try printing from the child process"
WunderBart Jun 15, 2023
40ddc30
Revert "Try running a different test"
WunderBart Jun 15, 2023
f53273d
Leave only chromium for perf testing
WunderBart Jun 15, 2023
a3679e8
Try stdout again
WunderBart Jun 15, 2023
71e673d
Do not run retries
WunderBart Jun 15, 2023
2596967
Create new page for loading spec as before
WunderBart Jun 15, 2023
7885bf3
Make naming consistent
WunderBart Jun 15, 2023
07e4d34
Remove warning tracing
WunderBart Jun 15, 2023
1ebe284
Revert "Try stdout again"
WunderBart Jun 15, 2023
5412032
Update config according to review comments
WunderBart Jun 16, 2023
875a4f1
tmp: Compare Puppeteer vs. Playwright in CI
WunderBart Jun 16, 2023
d9f2672
Revert "Revert "Try stdout again""
WunderBart Jun 16, 2023
740360a
Increase global timeout to 10 minutes
WunderBart Jun 19, 2023
5934795
Remove unused dep
WunderBart Jun 19, 2023
c8e6639
Extend more timeouts to address flaky CI
WunderBart Jun 19, 2023
d0aee00
Revert "Revert "Revert "Try stdout again"""
WunderBart Jun 19, 2023
115d731
Revert "tmp: Compare Puppeteer vs. Playwright in CI"
WunderBart Jun 20, 2023
ffcd340
Add Playwright script, but keep Puppeteer as a default runner
WunderBart Jun 20, 2023
e77d0b7
tmp: Run Puppeteer vs. Playwright comparison 100 times
WunderBart Jun 22, 2023
6bb53d8
Revert "tmp: Run Puppeteer vs. Playwright comparison 100 times"
WunderBart Jun 26, 2023
637035a
Revert "Make sure Playwright is installed"
WunderBart Jun 27, 2023
c5266ba
Revert "Build packages correctly"
WunderBart Jun 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ module.exports = {
},
{
files: [ 'packages/jest*/**/*.js', '**/test/**/*.js' ],
excludedFiles: [ 'test/e2e/**/*.js' ],
excludedFiles: [ 'test/e2e/**/*.js', 'test/performance/**/*.js' ],
extends: [ 'plugin:@wordpress/eslint-plugin/test-unit' ],
},
{
Expand All @@ -376,6 +376,7 @@ module.exports = {
'packages/react-native-*/**/*.[tj]s?(x)',
'test/native/**/*.[tj]s?(x)',
'test/e2e/**/*.[tj]s?(x)',
'test/performance/**/*.[tj]s?(x)',
'test/storybook-playwright/**/*.[tj]s?(x)',
],
extends: [
Expand All @@ -395,6 +396,7 @@ module.exports = {
{
files: [
'test/e2e/**/*.[tj]s',
'test/performance/**/*.[tj]s',
'packages/e2e-test-utils-playwright/**/*.[tj]s',
],
extends: [
Expand All @@ -405,6 +407,7 @@ module.exports = {
tsconfigRootDir: __dirname,
project: [
'./test/e2e/tsconfig.json',
'./test/performance/tsconfig.json',
'./packages/e2e-test-utils-playwright/tsconfig.json',
],
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@
"test:e2e:watch": "npm run test:e2e -- --watch",
"test:performance": "wp-scripts test-e2e --config packages/e2e-tests/jest.performance.config.js",
"test:performance:debug": "wp-scripts --inspect-brk test-e2e --runInBand --no-cache --verbose --config packages/e2e-tests/jest.performance.config.js --puppeteer-devtools",
"test:performance:playwright": "playwright test --config test/performance/playwright.config.ts",
"test:php": "npm-run-all lint:php test:unit:php",
"test:php:watch": "wp-env run --env-cwd='wp-content/plugins/gutenberg' tests-cli composer run-script test:watch",
"test:unit": "wp-scripts test-unit-js --config test/unit/jest.config.js",
Expand Down
5,553 changes: 5,553 additions & 0 deletions test/performance/assets/large-post.html

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions test/performance/assets/small-post-with-containers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!-- wp:columns {"align":"full"} -->
<div class="wp-block-columns alignfull"><!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Heading</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>one</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>two</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>three</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Heading</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>one</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>two</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>three</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list --></div>
<!-- /wp:column -->

<!-- wp:column -->
<div class="wp-block-column"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading">Heading</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>one</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>two</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>three</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list --></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->

<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->
31 changes: 31 additions & 0 deletions test/performance/config/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { request } from '@playwright/test';
import type { FullConfig } from '@playwright/test';

/**
* WordPress dependencies
*/
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';

async function globalSetup( config: FullConfig ) {
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();

await requestContext.dispose();
}

export default globalSetup;
174 changes: 174 additions & 0 deletions test/performance/config/performance-reporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* External dependencies
*/
import path from 'path';
import chalk from 'chalk';
import { readFileSync, existsSync } from 'fs';
import type { Reporter, TestCase } from '@playwright/test/reporter';

/**
* Internal dependencies
*/
import { average, round } from '../utils';

const title = chalk.bold;
const success = chalk.bold.green;

class PerformanceReporter implements Reporter {
onTestEnd( test: TestCase ) {
const basename = path.basename( test.location.file, '.js' );
const filepath = path.join(
process.env.WP_ARTIFACTS_PATH as string,
basename + '.performance-results.json'
);

if ( ! existsSync( filepath ) ) {
return;
}

const results = readFileSync( filepath, 'utf8' );
const {
serverResponse,
firstPaint,
domContentLoaded,
loaded,
firstContentfulPaint,
firstBlock,
type,
typeContainer,
focus,
listViewOpen,
inserterOpen,
inserterHover,
inserterSearch,
} = JSON.parse( results );

if ( serverResponse && serverResponse.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Loading Time:' ) }
Average time to server response (subtracted from client side metrics): ${ success(
round( average( serverResponse ) ) + 'ms'
) }
Average time to first paint: ${ success(
round( average( firstPaint ) ) + 'ms'
) }
Average time to DOM content load: ${ success(
round( average( domContentLoaded ) ) + 'ms'
) }
Average time to load: ${ success( round( average( loaded ) ) + 'ms' ) }
Average time to first contentful paint: ${ success(
round( average( firstContentfulPaint ) ) + 'ms'
) }
Average time to first block: ${ success(
round( average( firstBlock ) ) + 'ms'
) }` );
}

if ( type && type.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Typing:' ) }
Average time to type character: ${ success( round( average( type ) ) + 'ms' ) }
Slowest time to type character: ${ success(
round( Math.max( ...type ) ) + 'ms'
) }
Fastest time to type character: ${ success(
round( Math.min( ...type ) ) + 'ms'
) }` );
}

if ( typeContainer && typeContainer.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Typing within a container:' ) }
Average time to type within a container: ${ success(
round( average( typeContainer ) ) + 'ms'
) }
Slowest time to type within a container: ${ success(
round( Math.max( ...typeContainer ) ) + 'ms'
) }
Fastest time to type within a container: ${ success(
round( Math.min( ...typeContainer ) ) + 'ms'
) }` );
}

if ( focus && focus.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Block Selection:' ) }
Average time to select a block: ${ success( round( average( focus ) ) + 'ms' ) }
Slowest time to select a block: ${ success(
round( Math.max( ...focus ) ) + 'ms'
) }
Fastest time to select a block: ${ success(
round( Math.min( ...focus ) ) + 'ms'
) }` );
}

if ( listViewOpen && listViewOpen.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Opening List View:' ) }
Average time to open list view: ${ success(
round( average( listViewOpen ) ) + 'ms'
) }
Slowest time to open list view: ${ success(
round( Math.max( ...listViewOpen ) ) + 'ms'
) }
Fastest time to open list view: ${ success(
round( Math.min( ...listViewOpen ) ) + 'ms'
) }` );
}

if ( inserterOpen && inserterOpen.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Opening Global Inserter:' ) }
Average time to open global inserter: ${ success(
round( average( inserterOpen ) ) + 'ms'
) }
Slowest time to open global inserter: ${ success(
round( Math.max( ...inserterOpen ) ) + 'ms'
) }
Fastest time to open global inserter: ${ success(
round( Math.min( ...inserterOpen ) ) + 'ms'
) }` );
}

if ( inserterSearch && inserterSearch.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Inserter Search:' ) }
Average time to type the inserter search input: ${ success(
round( average( inserterSearch ) ) + 'ms'
) }
Slowest time to type the inserter search input: ${ success(
round( Math.max( ...inserterSearch ) ) + 'ms'
) }
Fastest time to type the inserter search input: ${ success(
round( Math.min( ...inserterSearch ) ) + 'ms'
) }` );
}

if ( inserterHover && inserterHover.length ) {
// eslint-disable-next-line no-console
console.log( `
${ title( 'Inserter Block Item Hover:' ) }
Average time to move mouse between two block item in the inserter: ${ success(
round( average( inserterHover ) ) + 'ms'
) }
Slowest time to move mouse between two block item in the inserter: ${ success(
round( Math.max( ...inserterHover ) ) + 'ms'
) }
Fastest time to move mouse between two block item in the inserter: ${ success(
round( Math.min( ...inserterHover ) ) + 'ms'
) }` );
}

// eslint-disable-next-line no-console
console.log( '' );
}
}

export default PerformanceReporter;
64 changes: 64 additions & 0 deletions test/performance/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* External dependencies
*/
import path from 'path';
import { fileURLToPath } from 'url';
import { defineConfig, devices } from '@playwright/test';

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.ASSETS_PATH = path.join( __dirname, 'assets' );

const config = defineConfig( {
reporter: process.env.CI
? undefined // We're using another reporter in CI.
: [ [ 'list' ], [ './config/performance-reporter.ts' ] ],
forbidOnly: !! process.env.CI,
fullyParallel: false,
workers: 1,
retries: 0,
timeout: parseInt( process.env.TIMEOUT || '', 10 ) || 600_000, // Defaults to 10 minutes.
testDir: fileURLToPath( new URL( './specs', 'file:' + __filename ).href ),
outputDir: path.join( process.env.WP_ARTIFACTS_PATH, 'test-results' ),
snapshotPathTemplate:
'{testDir}/{testFileDir}/__snapshots__/{arg}-{projectName}{ext}',
globalSetup: fileURLToPath(
new URL( './config/global-setup.ts', 'file:' + __filename ).href
),
use: {
baseURL: process.env.WP_BASE_URL || 'http://localhost:8889',
headless: true,
viewport: {
width: 960,
height: 700,
},
ignoreHTTPSErrors: true,
locale: 'en-US',
contextOptions: {
reducedMotion: 'reduce',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this has any performance impact? I'm also not sure if the original test is using this or not 😅 .

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might have, though we do also use it with Puppeteer. I'd leave it as is for the sake of consistency and explore the impact in a follow-up PR.

strictSelectors: true,
},
storageState: process.env.STORAGE_STATE_PATH,
actionTimeout: 10_000, // 10 seconds.
trace: 'retain-on-failure',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have some minor performance impact. Probably we don't need to enable it in performance test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we mostly care about changes in performance, so the impact itself might not be that important if we keep it switched on. The question is whether we need the trace or not. It did help me with some debugging just recently!

screenshot: 'only-on-failure',
video: 'off',
},
webServer: {
command: 'npm run wp-env start',
port: 8889,
timeout: 120_000, // 120 seconds.
reuseExistingServer: true,
},
projects: [
{
name: 'chromium',
use: { ...devices[ 'Desktop Chrome' ] },
},
],
} );

export default config;
Loading