-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Improve Site Editor performance tests #48138
Changes from 18 commits
184bf0d
01bc9e2
3dbc1fe
1a1298c
835f3c6
f4a1a9f
f2f52a1
0ab0c95
dee6e71
fbe4439
f84146c
edd93b6
4a549fc
91bdc33
3781e85
10d7c04
8486b48
6dcc933
a5899a4
cc09c6f
5623f43
a0f60cb
3da31c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,9 @@ import { | |
|
||
jest.setTimeout( 1000000 ); | ||
|
||
const sequence = ( start, length ) => | ||
Array.from( { length }, ( _, i ) => i + start ); | ||
|
||
const results = { | ||
serverResponse: [], | ||
firstPaint: [], | ||
|
@@ -46,7 +49,7 @@ const results = { | |
listViewOpen: [], | ||
}; | ||
|
||
let id; | ||
let postId; | ||
|
||
describe( 'Site Editor Performance', () => { | ||
beforeAll( async () => { | ||
|
@@ -59,6 +62,7 @@ describe( 'Site Editor Performance', () => { | |
); | ||
|
||
await createNewPost( { postType: 'page' } ); | ||
|
||
await page.evaluate( ( _html ) => { | ||
const { parse } = window.wp.blocks; | ||
const { dispatch } = window.wp.data; | ||
|
@@ -75,112 +79,115 @@ describe( 'Site Editor Performance', () => { | |
}, html ); | ||
await saveDraft(); | ||
|
||
id = await page.evaluate( () => | ||
postId = await page.evaluate( () => | ||
new URL( document.location ).searchParams.get( 'post' ) | ||
); | ||
} ); | ||
|
||
afterAll( async () => { | ||
const resultsFilename = basename( __filename, '.js' ) + '.results.json'; | ||
writeFileSync( | ||
join( __dirname, resultsFilename ), | ||
JSON.stringify( results, null, 2 ) | ||
); | ||
|
||
await deleteAllTemplates( 'wp_template' ); | ||
await deleteAllTemplates( 'wp_template_part' ); | ||
await activateTheme( 'twentytwentyone' ); | ||
} ); | ||
|
||
beforeEach( async () => { | ||
await visitSiteEditor( { | ||
postId: id, | ||
postType: 'page', | ||
} ); | ||
} ); | ||
|
||
it( 'Loading', async () => { | ||
const editorURL = await page.url(); | ||
|
||
// Number of sample measurements to take. | ||
describe( 'Loading', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have sub describes and iterations within the same "test"? I find this a bit confusing personally. What benefits do we have from this change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a strong opinion here, but it sometimes helps separate the concerns better. Like with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a strong opinion either, but feel like whatever we do, it should be the same for all of our tests and there's already other suites using a single parent describe (post editor, classic theme, block theme) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point; consistency is king. I'll switch it to a single parent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// Number of measurements to take. | ||
const samples = 3; | ||
// Number of throwaway measurements to perform before recording samples. | ||
// Having at least one helps ensure that caching quirks don't manifest in | ||
// the results. | ||
// Having at least one helps ensure that caching quirks don't manifest | ||
// in the results. | ||
const throwaway = 1; | ||
const iterations = sequence( 1, samples + throwaway ); | ||
|
||
it.each( iterations )( | ||
`trace large post loading durations (%i of ${ iterations.length })`, | ||
async ( i ) => { | ||
// Open the test page in Site Editor. | ||
await visitSiteEditor( { | ||
postId, | ||
postType: 'page', | ||
} ); | ||
|
||
// Wait for the first content block to render. | ||
await canvas().waitForSelector( | ||
'[data-type="core/post-content"] .wp-block' | ||
youknowriad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
|
||
if ( i > throwaway ) { | ||
const { | ||
serverResponse, | ||
firstPaint, | ||
domContentLoaded, | ||
loaded, | ||
firstContentfulPaint, | ||
firstBlock, | ||
} = await getLoadingDurations(); | ||
|
||
results.serverResponse.push( serverResponse ); | ||
results.firstPaint.push( firstPaint ); | ||
results.domContentLoaded.push( domContentLoaded ); | ||
results.loaded.push( loaded ); | ||
results.firstContentfulPaint.push( firstContentfulPaint ); | ||
results.firstBlock.push( firstBlock ); | ||
} | ||
|
||
let i = throwaway + samples; | ||
|
||
// Measuring loading time. | ||
while ( i-- ) { | ||
await page.close(); | ||
page = await browser.newPage(); | ||
|
||
await page.goto( editorURL ); | ||
await page.waitForSelector( '.edit-site-visual-editor', { | ||
timeout: 120000, | ||
} ); | ||
await canvas().waitForSelector( '.wp-block', { timeout: 120000 } ); | ||
WunderBart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if ( i < samples ) { | ||
const { | ||
serverResponse, | ||
firstPaint, | ||
domContentLoaded, | ||
loaded, | ||
firstContentfulPaint, | ||
firstBlock, | ||
} = await getLoadingDurations(); | ||
|
||
results.serverResponse.push( serverResponse ); | ||
results.firstPaint.push( firstPaint ); | ||
results.domContentLoaded.push( domContentLoaded ); | ||
results.loaded.push( loaded ); | ||
results.firstContentfulPaint.push( firstContentfulPaint ); | ||
results.firstBlock.push( firstBlock ); | ||
expect( true ).toBe( true ); | ||
} | ||
} | ||
); | ||
} ); | ||
|
||
it( 'Typing', async () => { | ||
await page.waitForSelector( '.edit-site-visual-editor', { | ||
timeout: 120000, | ||
} ); | ||
await canvas().waitForSelector( '.wp-block', { timeout: 120000 } ); | ||
describe( 'Typing', () => { | ||
it( 'trace 200 characters typing sequence inside a large post', async () => { | ||
// Open the test page in Site Editor. | ||
await visitSiteEditor( { | ||
postId, | ||
postType: 'page', | ||
} ); | ||
|
||
// Measuring typing performance inside the post content. | ||
await canvas().waitForSelector( | ||
'[data-type="core/post-content"] [data-type="core/paragraph"]' | ||
); | ||
await enterEditMode(); | ||
await canvas().focus( | ||
'[data-type="core/post-content"] [data-type="core/paragraph"]' | ||
); | ||
await insertBlock( 'Paragraph' ); | ||
let i = 200; | ||
const traceFile = __dirname + '/trace.json'; | ||
await page.tracing.start( { | ||
path: traceFile, | ||
screenshots: false, | ||
categories: [ 'devtools.timeline' ], | ||
} ); | ||
while ( i-- ) { | ||
await page.keyboard.type( 'x' ); | ||
} | ||
await page.tracing.stop(); | ||
const traceResults = JSON.parse( readFile( traceFile ) ); | ||
const [ keyDownEvents, keyPressEvents, keyUpEvents ] = | ||
getTypingEventDurations( traceResults ); | ||
|
||
for ( let j = 0; j < keyDownEvents.length; j++ ) { | ||
results.type.push( | ||
keyDownEvents[ j ] + keyPressEvents[ j ] + keyUpEvents[ j ] | ||
// Wait for the first paragraph to be ready. | ||
const firstParagraph = await canvas().waitForXPath( | ||
'//p[contains(text(), "Lorem ipsum dolor sit amet")]' | ||
); | ||
} | ||
|
||
const resultsFilename = basename( __filename, '.js' ) + '.results.json'; | ||
// Get inside the post content. | ||
await enterEditMode(); | ||
|
||
writeFileSync( | ||
join( __dirname, resultsFilename ), | ||
JSON.stringify( results, null, 2 ) | ||
); | ||
// Insert a new paragraph right under the first one. | ||
await firstParagraph.focus(); | ||
await insertBlock( 'Paragraph' ); | ||
|
||
// Start tracing. | ||
const traceFile = __dirname + '/trace.json'; | ||
await page.tracing.start( { | ||
path: traceFile, | ||
screenshots: false, | ||
categories: [ 'devtools.timeline' ], | ||
} ); | ||
|
||
// Type "x" 200 times. | ||
await page.keyboard.type( new Array( 200 ).fill( 'x' ).join( '' ) ); | ||
|
||
// Stop tracing and save results. | ||
await page.tracing.stop(); | ||
const traceResults = JSON.parse( readFile( traceFile ) ); | ||
const [ keyDownEvents, keyPressEvents, keyUpEvents ] = | ||
getTypingEventDurations( traceResults ); | ||
for ( let i = 0; i < keyDownEvents.length; i++ ) { | ||
results.type.push( | ||
keyDownEvents[ i ] + keyPressEvents[ i ] + keyUpEvents[ i ] | ||
); | ||
} | ||
|
||
deleteFile( traceFile ); | ||
// Delete the original trace file. | ||
deleteFile( traceFile ); | ||
|
||
expect( true ).toBe( true ); | ||
expect( true ).toBe( true ); | ||
} ); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
results
object contains data for bothLoading
andTyping
tests, so let's write the file in theafterAll
hook instead of the end of theTyping
test.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is storing this file alongside the sourcecode the optimal choice here? I know I was confused when first storing results files as artifacts because of the directory structure. would we want to move these instead to our relatively-new
__test-results
directory?we could consider some kind of
TEST_RESULTS_BASE_DIR
ENV
to hold the base path if we wanted to avoid doing path-math everywhere: running in CI vs. locally in Docker vs. locally without Docker…There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, we should be able to pass the
__test-results
path as an env variable and save directly there. Will give it a try.edit:
OTOH, let's leave that for a follow-up PR. It touches all the other perf tests and I wanted to make this PR about the Site Editor ones only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've drafted the refactor in #48684, but I'm not sure how to handle the "locally without docker" situation. I think it still makes the most sense (as it's most convenient) to store alongside the sourcecode when running e.g. via
npm run test:performance -- site-editor
.Let me know if that makes sense to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the main thing about colocating with sourcecode is that it leaves its mess distributed around the project. at least when creating a project-root-level
__test-results
directory it's easy to wipe that out in one command.see for example challenges with the distributed
build
,build-styles
,build-modules
, andbuild-types
directories in each package directory.I'll add further remarks in the linked PR.
thanks! ✋