@wordpress/e2e-test-utils-playwright 1.48.0 → 1.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/admin/create-new-post.js +31 -0
- package/build/admin/create-new-post.js.map +1 -0
- package/build/admin/edit-post.js +20 -0
- package/build/admin/edit-post.js.map +1 -0
- package/{src/admin/get-page-error.ts → build/admin/get-page-error.js} +9 -12
- package/build/admin/get-page-error.js.map +1 -0
- package/build/admin/index.js +40 -0
- package/build/admin/index.js.map +1 -0
- package/build/admin/visit-admin-page.js +32 -0
- package/build/admin/visit-admin-page.js.map +1 -0
- package/build/admin/visit-site-editor.js +45 -0
- package/build/admin/visit-site-editor.js.map +1 -0
- package/build/admin/wait-for-site-editor.js +41 -0
- package/build/admin/wait-for-site-editor.js.map +1 -0
- package/build/config.js +13 -0
- package/build/config.js.map +1 -0
- package/build/editor/click-block-options-menu-item.js +17 -0
- package/build/editor/click-block-options-menu-item.js.map +1 -0
- package/build/editor/click-block-toolbar-button.js +16 -0
- package/build/editor/click-block-toolbar-button.js.map +1 -0
- package/build/editor/get-blocks.js +45 -0
- package/build/editor/get-blocks.js.map +1 -0
- package/build/editor/get-edited-post-content.js +15 -0
- package/build/editor/get-edited-post-content.js.map +1 -0
- package/build/editor/index.js +75 -0
- package/build/editor/index.js.map +1 -0
- package/build/editor/insert-block.js +23 -0
- package/build/editor/insert-block.js.map +1 -0
- package/build/editor/open-document-settings-sidebar.js +27 -0
- package/build/editor/open-document-settings-sidebar.js.map +1 -0
- package/build/editor/preview.js +21 -0
- package/build/editor/preview.js.map +1 -0
- package/build/editor/publish-post.js +45 -0
- package/build/editor/publish-post.js.map +1 -0
- package/build/editor/save-draft.js +18 -0
- package/build/editor/save-draft.js.map +1 -0
- package/build/editor/select-blocks.js +28 -0
- package/build/editor/select-blocks.js.map +1 -0
- package/build/editor/set-content.js +17 -0
- package/build/editor/set-content.js.map +1 -0
- package/build/editor/set-is-fixed-toolbar.js +18 -0
- package/build/editor/set-is-fixed-toolbar.js.map +1 -0
- package/build/editor/set-preferences.js +21 -0
- package/build/editor/set-preferences.js.map +1 -0
- package/build/editor/show-block-toolbar.js +19 -0
- package/build/editor/show-block-toolbar.js.map +1 -0
- package/build/editor/site-editor.js +44 -0
- package/build/editor/site-editor.js.map +1 -0
- package/build/editor/switch-editor-tool.js +33 -0
- package/build/editor/switch-editor-tool.js.map +1 -0
- package/build/editor/switch-to-legacy-canvas.js +18 -0
- package/build/editor/switch-to-legacy-canvas.js.map +1 -0
- package/build/editor/transform-block-to.js +24 -0
- package/build/editor/transform-block-to.js.map +1 -0
- package/build/index.js +19 -0
- package/build/index.js.map +1 -0
- package/build/lighthouse/index.js +61 -0
- package/build/lighthouse/index.js.map +1 -0
- package/build/metrics/index.js +314 -0
- package/build/metrics/index.js.map +1 -0
- package/build/page-utils/drag-files.js +119 -0
- package/build/page-utils/drag-files.js.map +1 -0
- package/build/page-utils/emulate-network-conditions.js +71 -0
- package/build/page-utils/emulate-network-conditions.js.map +1 -0
- package/build/page-utils/index.js +37 -0
- package/build/page-utils/index.js.map +1 -0
- package/build/page-utils/is-current-url.js +21 -0
- package/build/page-utils/is-current-url.js.map +1 -0
- package/build/page-utils/keycodes.js +41 -0
- package/build/page-utils/keycodes.js.map +1 -0
- package/build/page-utils/press-keys.js +141 -0
- package/build/page-utils/press-keys.js.map +1 -0
- package/{src → build}/page-utils/set-browser-viewport.js +12 -14
- package/build/page-utils/set-browser-viewport.js.map +1 -0
- package/build/request-utils/blocks.js +40 -0
- package/build/request-utils/blocks.js.map +1 -0
- package/build/request-utils/comments.js +52 -0
- package/build/request-utils/comments.js.map +1 -0
- package/build/request-utils/gutenberg-experiments.js +47 -0
- package/build/request-utils/gutenberg-experiments.js.map +1 -0
- package/build/request-utils/index.js +196 -0
- package/build/request-utils/index.js.map +1 -0
- package/build/request-utils/login.js +21 -0
- package/build/request-utils/login.js.map +1 -0
- package/build/request-utils/media.js +106 -0
- package/build/request-utils/media.js.map +1 -0
- package/build/request-utils/menus.js +116 -0
- package/build/request-utils/menus.js.map +1 -0
- package/build/request-utils/pages.js +53 -0
- package/build/request-utils/pages.js.map +1 -0
- package/build/request-utils/patterns.js +27 -0
- package/build/request-utils/patterns.js.map +1 -0
- package/build/request-utils/plugins.js +85 -0
- package/build/request-utils/plugins.js.map +1 -0
- package/build/request-utils/posts.js +42 -0
- package/build/request-utils/posts.js.map +1 -0
- package/build/request-utils/preferences.js +54 -0
- package/build/request-utils/preferences.js.map +1 -0
- package/{src/request-utils/records.ts → build/request-utils/records.js} +10 -15
- package/build/request-utils/records.js.map +1 -0
- package/build/request-utils/rest.js +181 -0
- package/build/request-utils/rest.js.map +1 -0
- package/build/request-utils/site-settings.js +33 -0
- package/build/request-utils/site-settings.js.map +1 -0
- package/build/request-utils/templates.js +54 -0
- package/build/request-utils/templates.js.map +1 -0
- package/build/request-utils/themes.js +69 -0
- package/build/request-utils/themes.js.map +1 -0
- package/build/request-utils/users.js +81 -0
- package/build/request-utils/users.js.map +1 -0
- package/build/request-utils/widgets.js +61 -0
- package/build/request-utils/widgets.js.map +1 -0
- package/build/test.js +189 -0
- package/build/test.js.map +1 -0
- package/build/types.js +3 -0
- package/build/types.js.map +1 -0
- package/build-types/admin/index.d.ts.map +1 -1
- package/build-types/config.d.ts.map +1 -1
- package/build-types/editor/index.d.ts.map +1 -1
- package/build-types/lighthouse/index.d.ts.map +1 -1
- package/build-types/metrics/index.d.ts.map +1 -1
- package/build-types/page-utils/index.d.ts.map +1 -1
- package/build-types/page-utils/press-keys.d.ts +1 -1
- package/build-types/page-utils/set-browser-viewport.d.ts +22 -12
- package/build-types/page-utils/set-browser-viewport.d.ts.map +1 -1
- package/build-types/request-utils/index.d.ts +2 -2
- package/build-types/request-utils/index.d.ts.map +1 -1
- package/build-types/request-utils/pages.d.ts +1 -1
- package/build-types/request-utils/pages.d.ts.map +1 -1
- package/build-types/request-utils/widgets.d.ts +2 -2
- package/build-types/request-utils/widgets.d.ts.map +1 -1
- package/build-types/types.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/admin/create-new-post.ts +0 -47
- package/src/admin/edit-post.ts +0 -24
- package/src/admin/index.ts +0 -52
- package/src/admin/visit-admin-page.ts +0 -43
- package/src/admin/visit-site-editor.ts +0 -62
- package/src/admin/wait-for-site-editor.ts +0 -52
- package/src/config.ts +0 -12
- package/src/editor/click-block-options-menu-item.ts +0 -18
- package/src/editor/click-block-toolbar-button.ts +0 -21
- package/src/editor/get-blocks.ts +0 -75
- package/src/editor/get-edited-post-content.ts +0 -19
- package/src/editor/index.ts +0 -95
- package/src/editor/insert-block.ts +0 -54
- package/src/editor/open-document-settings-sidebar.ts +0 -31
- package/src/editor/preview.ts +0 -32
- package/src/editor/publish-post.ts +0 -51
- package/src/editor/save-draft.ts +0 -20
- package/src/editor/select-blocks.ts +0 -48
- package/src/editor/set-content.ts +0 -24
- package/src/editor/set-is-fixed-toolbar.ts +0 -20
- package/src/editor/set-preferences.ts +0 -37
- package/src/editor/show-block-toolbar.ts +0 -20
- package/src/editor/site-editor.ts +0 -55
- package/src/editor/switch-editor-tool.ts +0 -34
- package/src/editor/switch-to-legacy-canvas.ts +0 -20
- package/src/editor/transform-block-to.ts +0 -34
- package/src/index.ts +0 -8
- package/src/lighthouse/index.ts +0 -75
- package/src/metrics/index.ts +0 -441
- package/src/page-utils/drag-files.ts +0 -173
- package/src/page-utils/emulate-network-conditions.ts +0 -101
- package/src/page-utils/index.ts +0 -54
- package/src/page-utils/is-current-url.ts +0 -20
- package/src/page-utils/keycodes.ts +0 -48
- package/src/page-utils/press-keys.ts +0 -195
- package/src/request-utils/blocks.ts +0 -58
- package/src/request-utils/comments.ts +0 -80
- package/src/request-utils/gutenberg-experiments.ts +0 -62
- package/src/request-utils/index.ts +0 -222
- package/src/request-utils/login.ts +0 -34
- package/src/request-utils/media.ts +0 -102
- package/src/request-utils/menus.ts +0 -145
- package/src/request-utils/pages.ts +0 -75
- package/src/request-utils/patterns.ts +0 -31
- package/src/request-utils/plugins.ts +0 -103
- package/src/request-utils/posts.ts +0 -69
- package/src/request-utils/preferences.ts +0 -63
- package/src/request-utils/rest.ts +0 -222
- package/src/request-utils/site-settings.ts +0 -58
- package/src/request-utils/templates.ts +0 -83
- package/src/request-utils/themes.ts +0 -110
- package/src/request-utils/users.ts +0 -125
- package/src/request-utils/widgets.js +0 -68
- package/src/test.ts +0 -204
- package/src/types.ts +0 -13
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Editor } from './index';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The block toolbar is not always visible while typing.
|
|
8
|
-
* Call this function to reveal it.
|
|
9
|
-
*
|
|
10
|
-
* @param this
|
|
11
|
-
*/
|
|
12
|
-
export async function showBlockToolbar( this: Editor ) {
|
|
13
|
-
// Move the mouse to disable the isTyping mode. We need at least three
|
|
14
|
-
// mousemove events for it to work across windows (iframe). With three
|
|
15
|
-
// moves, it's a guarantee that at least two will be in the same window.
|
|
16
|
-
// Two events are required for the flag to be unset.
|
|
17
|
-
await this.page.mouse.move( 50, 50 );
|
|
18
|
-
await this.page.mouse.move( 75, 75 );
|
|
19
|
-
await this.page.mouse.move( 100, 100 );
|
|
20
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Editor } from './index';
|
|
5
|
-
|
|
6
|
-
interface Options {
|
|
7
|
-
// If the only dirty entity is the current one, skip opening the save panel.
|
|
8
|
-
isOnlyCurrentEntityDirty?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Save entities in the site editor. Assumes the editor is in a dirty state.
|
|
13
|
-
*
|
|
14
|
-
* @param this
|
|
15
|
-
* @param options
|
|
16
|
-
*/
|
|
17
|
-
export async function saveSiteEditorEntities(
|
|
18
|
-
this: Editor,
|
|
19
|
-
options: Options = {}
|
|
20
|
-
) {
|
|
21
|
-
const editorTopBar = this.page.getByRole( 'region', {
|
|
22
|
-
name: 'Editor top bar',
|
|
23
|
-
} );
|
|
24
|
-
|
|
25
|
-
// If we have changes in a single entity which can be published the label is `Publish`.
|
|
26
|
-
const saveButton = editorTopBar.getByRole( 'button', {
|
|
27
|
-
name: 'Save',
|
|
28
|
-
exact: true,
|
|
29
|
-
} );
|
|
30
|
-
const publishButton = editorTopBar.getByRole( 'button', {
|
|
31
|
-
name: 'Publish',
|
|
32
|
-
} );
|
|
33
|
-
const publishButtonIsVisible = ! ( await saveButton.isVisible() );
|
|
34
|
-
// First Save button in the top bar.
|
|
35
|
-
const buttonToClick = publishButtonIsVisible ? publishButton : saveButton;
|
|
36
|
-
await buttonToClick.click();
|
|
37
|
-
|
|
38
|
-
if ( ! options.isOnlyCurrentEntityDirty ) {
|
|
39
|
-
// Second Save button in the entities panel.
|
|
40
|
-
await this.page
|
|
41
|
-
.getByRole( 'region', {
|
|
42
|
-
name: /(Editor publish|Save panel)/,
|
|
43
|
-
} )
|
|
44
|
-
.getByRole( 'button', { name: 'Save', exact: true } )
|
|
45
|
-
.click();
|
|
46
|
-
}
|
|
47
|
-
// The text in the notice can be different based on the edited entity, whether
|
|
48
|
-
// we are saving multiple entities and whether we publish or update. So for now,
|
|
49
|
-
// we locate it based on the last part.
|
|
50
|
-
await this.page
|
|
51
|
-
.getByRole( 'button', { name: 'Dismiss this notice' } )
|
|
52
|
-
.getByText( /(updated|published)\./ )
|
|
53
|
-
.first()
|
|
54
|
-
.waitFor();
|
|
55
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Editor } from './index';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Switch the editor tool being used.
|
|
8
|
-
*
|
|
9
|
-
* @param this
|
|
10
|
-
* @param label The text string of the button label.
|
|
11
|
-
*/
|
|
12
|
-
export async function switchEditorTool( this: Editor, label: string ) {
|
|
13
|
-
const toolsToolbar = this.page.getByRole( 'toolbar', {
|
|
14
|
-
name: 'Document tools',
|
|
15
|
-
} );
|
|
16
|
-
await toolsToolbar
|
|
17
|
-
.getByRole( 'button', {
|
|
18
|
-
name: 'Tools',
|
|
19
|
-
} )
|
|
20
|
-
.click();
|
|
21
|
-
const menu = this.page.getByRole( 'menu', {
|
|
22
|
-
name: 'Tools',
|
|
23
|
-
} );
|
|
24
|
-
await menu
|
|
25
|
-
.getByRole( 'menuitemradio', {
|
|
26
|
-
name: label,
|
|
27
|
-
} )
|
|
28
|
-
.click();
|
|
29
|
-
await toolsToolbar
|
|
30
|
-
.getByRole( 'button', {
|
|
31
|
-
name: 'Tools',
|
|
32
|
-
} )
|
|
33
|
-
.click();
|
|
34
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Editor } from './index';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Switches to legacy (non-iframed) canvas.
|
|
8
|
-
*
|
|
9
|
-
* @param this
|
|
10
|
-
*/
|
|
11
|
-
export async function switchToLegacyCanvas( this: Editor ) {
|
|
12
|
-
await this.page.waitForFunction( () => window?.wp?.blocks );
|
|
13
|
-
|
|
14
|
-
await this.page.evaluate( () => {
|
|
15
|
-
window.wp.blocks.registerBlockType( 'test/v2', {
|
|
16
|
-
apiVersion: '2',
|
|
17
|
-
title: 'test',
|
|
18
|
-
} );
|
|
19
|
-
} );
|
|
20
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Editor } from './index';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Clicks the default block appender.
|
|
8
|
-
*
|
|
9
|
-
* @param this
|
|
10
|
-
* @param name Block name.
|
|
11
|
-
*/
|
|
12
|
-
export async function transformBlockTo( this: Editor, name: string ) {
|
|
13
|
-
await this.page.waitForFunction(
|
|
14
|
-
() => window?.wp?.blocks && window?.wp?.data
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
await this.page.evaluate(
|
|
18
|
-
( [ blockName ] ) => {
|
|
19
|
-
const clientIds = window.wp.data
|
|
20
|
-
.select( 'core/block-editor' )
|
|
21
|
-
.getSelectedBlockClientIds();
|
|
22
|
-
const blocks = window.wp.data
|
|
23
|
-
.select( 'core/block-editor' )
|
|
24
|
-
.getBlocksByClientId( clientIds );
|
|
25
|
-
window.wp.data
|
|
26
|
-
.dispatch( 'core/block-editor' )
|
|
27
|
-
.replaceBlocks(
|
|
28
|
-
clientIds,
|
|
29
|
-
window.wp.blocks.switchToBlockType( blocks, blockName )
|
|
30
|
-
);
|
|
31
|
-
},
|
|
32
|
-
[ name ]
|
|
33
|
-
);
|
|
34
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export type * from './types';
|
|
2
|
-
export { Admin } from './admin';
|
|
3
|
-
export { Editor } from './editor';
|
|
4
|
-
export { PageUtils } from './page-utils';
|
|
5
|
-
export { RequestUtils } from './request-utils';
|
|
6
|
-
export { Metrics } from './metrics';
|
|
7
|
-
export { Lighthouse } from './lighthouse';
|
|
8
|
-
export { test, expect } from './test';
|
package/src/lighthouse/index.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import type { Page } from '@playwright/test';
|
|
5
|
-
import lighthouse from 'lighthouse/core/index.cjs';
|
|
6
|
-
|
|
7
|
-
type LighthouseConstructorProps = {
|
|
8
|
-
page: Page;
|
|
9
|
-
port: number;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export class Lighthouse {
|
|
13
|
-
page: Page;
|
|
14
|
-
port: number;
|
|
15
|
-
|
|
16
|
-
constructor( { page, port }: LighthouseConstructorProps ) {
|
|
17
|
-
this.page = page;
|
|
18
|
-
this.port = port;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Returns the Lighthouse report for the current URL.
|
|
23
|
-
*
|
|
24
|
-
* Runs several Lighthouse audits in a separate browser window and returns
|
|
25
|
-
* the summary.
|
|
26
|
-
*/
|
|
27
|
-
async getReport() {
|
|
28
|
-
// From https://github.com/GoogleChrome/lighthouse/blob/36cac182a6c637b1671c57326d7c0241633d0076/core/config/default-config.js#L381-L446
|
|
29
|
-
const audits = {
|
|
30
|
-
'largest-contentful-paint': 'LCP',
|
|
31
|
-
'total-blocking-time': 'TBT',
|
|
32
|
-
interactive: 'TTI',
|
|
33
|
-
'cumulative-layout-shift': 'CLS',
|
|
34
|
-
'interaction-to-next-paint': 'INP',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const report = await lighthouse(
|
|
38
|
-
this.page.url(),
|
|
39
|
-
{ port: this.port },
|
|
40
|
-
{
|
|
41
|
-
extends: 'lighthouse:default',
|
|
42
|
-
settings: {
|
|
43
|
-
// "provided" means no throttling.
|
|
44
|
-
// TODO: Make configurable.
|
|
45
|
-
throttlingMethod: 'provided',
|
|
46
|
-
// Default is "mobile".
|
|
47
|
-
// See https://github.com/GoogleChrome/lighthouse/blob/main/docs/emulation.md
|
|
48
|
-
// TODO: Make configurable.
|
|
49
|
-
formFactor: 'desktop',
|
|
50
|
-
screenEmulation: {
|
|
51
|
-
disabled: true,
|
|
52
|
-
},
|
|
53
|
-
// Speeds up the report.
|
|
54
|
-
disableFullPageScreenshot: true,
|
|
55
|
-
// Only run certain audits to speed things up.
|
|
56
|
-
onlyAudits: Object.keys( audits ),
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const result: Record< string, number > = {};
|
|
62
|
-
|
|
63
|
-
if ( ! report ) {
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const { lhr } = report;
|
|
68
|
-
|
|
69
|
-
for ( const [ audit, acronym ] of Object.entries( audits ) ) {
|
|
70
|
-
result[ acronym ] = lhr.audits[ audit ]?.numericValue || 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/metrics/index.ts
DELETED
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import type { Page, Browser } from '@playwright/test';
|
|
7
|
-
// resolution-mode support in TypeScript 5.3 will resolve this.
|
|
8
|
-
// See https://devblogs.microsoft.com/typescript/announcing-typescript-5-3-beta/
|
|
9
|
-
// @ts-expect-error
|
|
10
|
-
import type { Metric } from 'web-vitals';
|
|
11
|
-
|
|
12
|
-
type EventType =
|
|
13
|
-
| 'click'
|
|
14
|
-
| 'focus'
|
|
15
|
-
| 'focusin'
|
|
16
|
-
| 'keydown'
|
|
17
|
-
| 'keypress'
|
|
18
|
-
| 'keyup'
|
|
19
|
-
| 'mouseout'
|
|
20
|
-
| 'mouseover';
|
|
21
|
-
|
|
22
|
-
interface TraceEvent {
|
|
23
|
-
cat: string;
|
|
24
|
-
name: string;
|
|
25
|
-
dur?: number;
|
|
26
|
-
args: {
|
|
27
|
-
data?: {
|
|
28
|
-
type: EventType;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface Trace {
|
|
34
|
-
traceEvents: TraceEvent[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type MetricsConstructorProps = {
|
|
38
|
-
page: Page;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
interface WebVitalsMeasurements {
|
|
42
|
-
CLS?: number;
|
|
43
|
-
FCP?: number;
|
|
44
|
-
FID?: number;
|
|
45
|
-
INP?: number;
|
|
46
|
-
LCP?: number;
|
|
47
|
-
TTFB?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class Metrics {
|
|
51
|
-
browser: Browser;
|
|
52
|
-
page: Page;
|
|
53
|
-
trace: Trace;
|
|
54
|
-
|
|
55
|
-
webVitals: WebVitalsMeasurements = {};
|
|
56
|
-
|
|
57
|
-
constructor( { page }: MetricsConstructorProps ) {
|
|
58
|
-
this.page = page;
|
|
59
|
-
this.browser = page.context().browser()!;
|
|
60
|
-
this.trace = { traceEvents: [] };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Returns durations from the Server-Timing header.
|
|
65
|
-
*
|
|
66
|
-
* @param fields Optional fields to filter.
|
|
67
|
-
*/
|
|
68
|
-
async getServerTiming( fields: string[] = [] ) {
|
|
69
|
-
return this.page.evaluate< Record< string, number >, string[] >(
|
|
70
|
-
( f: string[] ) =>
|
|
71
|
-
(
|
|
72
|
-
performance.getEntriesByType(
|
|
73
|
-
'navigation'
|
|
74
|
-
) as PerformanceNavigationTiming[]
|
|
75
|
-
)[ 0 ].serverTiming.reduce(
|
|
76
|
-
( acc, entry ) => {
|
|
77
|
-
if ( f.length === 0 || f.includes( entry.name ) ) {
|
|
78
|
-
acc[ entry.name ] = entry.duration;
|
|
79
|
-
}
|
|
80
|
-
return acc;
|
|
81
|
-
},
|
|
82
|
-
{} as Record< string, number >
|
|
83
|
-
),
|
|
84
|
-
fields
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Returns time to first byte (TTFB) using the Navigation Timing API.
|
|
90
|
-
*
|
|
91
|
-
* @see https://web.dev/ttfb/#measure-ttfb-in-javascript
|
|
92
|
-
*
|
|
93
|
-
* @return TTFB value.
|
|
94
|
-
*/
|
|
95
|
-
async getTimeToFirstByte() {
|
|
96
|
-
return await this.page.evaluate< number >( () => {
|
|
97
|
-
const { responseStart, startTime } = (
|
|
98
|
-
performance.getEntriesByType(
|
|
99
|
-
'navigation'
|
|
100
|
-
) as PerformanceNavigationTiming[]
|
|
101
|
-
)[ 0 ];
|
|
102
|
-
return responseStart - startTime;
|
|
103
|
-
} );
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Returns the Largest Contentful Paint (LCP) value using the dedicated API.
|
|
108
|
-
*
|
|
109
|
-
* @see https://w3c.github.io/largest-contentful-paint/
|
|
110
|
-
* @see https://web.dev/lcp/#measure-lcp-in-javascript
|
|
111
|
-
*
|
|
112
|
-
* @return LCP value.
|
|
113
|
-
*/
|
|
114
|
-
async getLargestContentfulPaint() {
|
|
115
|
-
return await this.page.evaluate< number >(
|
|
116
|
-
() =>
|
|
117
|
-
new Promise( ( resolve ) => {
|
|
118
|
-
new PerformanceObserver( ( entryList ) => {
|
|
119
|
-
const entries = entryList.getEntries();
|
|
120
|
-
// The last entry is the largest contentful paint.
|
|
121
|
-
const largestPaintEntry = entries.at( -1 );
|
|
122
|
-
|
|
123
|
-
resolve( largestPaintEntry?.startTime || 0 );
|
|
124
|
-
} ).observe( {
|
|
125
|
-
type: 'largest-contentful-paint',
|
|
126
|
-
buffered: true,
|
|
127
|
-
} );
|
|
128
|
-
} )
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Returns the Cumulative Layout Shift (CLS) value using the dedicated API.
|
|
134
|
-
*
|
|
135
|
-
* @see https://github.com/WICG/layout-instability
|
|
136
|
-
* @see https://web.dev/cls/#measure-layout-shifts-in-javascript
|
|
137
|
-
*
|
|
138
|
-
* @return CLS value.
|
|
139
|
-
*/
|
|
140
|
-
async getCumulativeLayoutShift() {
|
|
141
|
-
return await this.page.evaluate< number >(
|
|
142
|
-
() =>
|
|
143
|
-
new Promise( ( resolve ) => {
|
|
144
|
-
let CLS = 0;
|
|
145
|
-
|
|
146
|
-
new PerformanceObserver( ( l ) => {
|
|
147
|
-
const entries = l.getEntries() as LayoutShift[];
|
|
148
|
-
|
|
149
|
-
entries.forEach( ( entry ) => {
|
|
150
|
-
if ( ! entry.hadRecentInput ) {
|
|
151
|
-
CLS += entry.value;
|
|
152
|
-
}
|
|
153
|
-
} );
|
|
154
|
-
|
|
155
|
-
resolve( CLS );
|
|
156
|
-
} ).observe( {
|
|
157
|
-
type: 'layout-shift',
|
|
158
|
-
buffered: true,
|
|
159
|
-
} );
|
|
160
|
-
} )
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Returns the loading durations using the Navigation Timing API. All the
|
|
166
|
-
* durations exclude the server response time.
|
|
167
|
-
*
|
|
168
|
-
* @return Object with loading metrics durations.
|
|
169
|
-
*/
|
|
170
|
-
async getLoadingDurations() {
|
|
171
|
-
return await this.page.evaluate( () => {
|
|
172
|
-
const [
|
|
173
|
-
{
|
|
174
|
-
requestStart,
|
|
175
|
-
responseStart,
|
|
176
|
-
responseEnd,
|
|
177
|
-
domContentLoadedEventEnd,
|
|
178
|
-
loadEventEnd,
|
|
179
|
-
},
|
|
180
|
-
] = performance.getEntriesByType(
|
|
181
|
-
'navigation'
|
|
182
|
-
) as PerformanceNavigationTiming[];
|
|
183
|
-
const paintTimings = performance.getEntriesByType(
|
|
184
|
-
'paint'
|
|
185
|
-
) as PerformancePaintTiming[];
|
|
186
|
-
|
|
187
|
-
const firstPaintStartTime = paintTimings.find(
|
|
188
|
-
( { name } ) => name === 'first-paint'
|
|
189
|
-
)!.startTime;
|
|
190
|
-
|
|
191
|
-
const firstContentfulPaintStartTime = paintTimings.find(
|
|
192
|
-
( { name } ) => name === 'first-contentful-paint'
|
|
193
|
-
)!.startTime;
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
// Server side metric.
|
|
197
|
-
serverResponse: responseStart - requestStart,
|
|
198
|
-
// For client side metrics, consider the end of the response (the
|
|
199
|
-
// browser receives the HTML) as the start time (0).
|
|
200
|
-
firstPaint: firstPaintStartTime - responseEnd,
|
|
201
|
-
domContentLoaded: domContentLoadedEventEnd - responseEnd,
|
|
202
|
-
loaded: loadEventEnd - responseEnd,
|
|
203
|
-
firstContentfulPaint:
|
|
204
|
-
firstContentfulPaintStartTime - responseEnd,
|
|
205
|
-
timeSinceResponseEnd: performance.now() - responseEnd,
|
|
206
|
-
};
|
|
207
|
-
} );
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Starts Chromium tracing with predefined options for performance testing.
|
|
212
|
-
*
|
|
213
|
-
* The category set mirrors what Chrome DevTools enables when recording in
|
|
214
|
-
* the Performance panel: `devtools.timeline` provides the top-level event
|
|
215
|
-
* tree, and the `disabled-by-default-v8.cpu_profiler` + companion
|
|
216
|
-
* `devtools.timeline.stack` categories enable the V8 sampler that
|
|
217
|
-
* populates JavaScript call stacks. Without the latter, the saved trace
|
|
218
|
-
* shows only opaque "Function call" blocks with no JS frames inside.
|
|
219
|
-
*
|
|
220
|
-
* @param options Options to pass to `browser.startTracing()`.
|
|
221
|
-
*/
|
|
222
|
-
async startTracing( options = {} ) {
|
|
223
|
-
return await this.browser.startTracing( this.page, {
|
|
224
|
-
screenshots: false,
|
|
225
|
-
categories: [
|
|
226
|
-
'devtools.timeline',
|
|
227
|
-
'disabled-by-default-devtools.timeline',
|
|
228
|
-
'disabled-by-default-devtools.timeline.stack',
|
|
229
|
-
'disabled-by-default-v8.cpu_profiler',
|
|
230
|
-
'v8.execute',
|
|
231
|
-
],
|
|
232
|
-
...options,
|
|
233
|
-
} );
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Stops Chromium tracing.
|
|
238
|
-
*
|
|
239
|
-
* When `name` is a non-empty string and the `WP_ARTIFACTS_PATH` environment
|
|
240
|
-
* variable is set, the raw trace is written to
|
|
241
|
-
* `${WP_ARTIFACTS_PATH}/traces/<name>.trace.json`. The resulting file can
|
|
242
|
-
* be opened in Chrome DevTools (Performance panel → "Load profile…") to
|
|
243
|
-
* inspect the flame graph. Pass a falsy value (or omit the argument) to
|
|
244
|
-
* just parse the trace into `this.trace` without writing — this is the
|
|
245
|
-
* default for iteration loops, where you typically want to save only one
|
|
246
|
-
* representative sample. Callers pick which iteration that is, e.g.
|
|
247
|
-
* `i === Math.floor( iterations / 2 ) && 'post-editor-first-block'`.
|
|
248
|
-
*
|
|
249
|
-
* @param name File name (without extension) identifying the scenario, or
|
|
250
|
-
* `false`/`undefined` to skip writing.
|
|
251
|
-
*/
|
|
252
|
-
async stopTracing( name?: string | false ) {
|
|
253
|
-
const traceBuffer = await this.browser.stopTracing();
|
|
254
|
-
const traceJSON = JSON.parse( traceBuffer.toString() );
|
|
255
|
-
|
|
256
|
-
this.trace = traceJSON;
|
|
257
|
-
|
|
258
|
-
if ( ! name ) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const artifactsPath = process.env.WP_ARTIFACTS_PATH;
|
|
263
|
-
if ( ! artifactsPath ) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// The perf comparison flow runs the same suite against multiple
|
|
268
|
-
// branches into a single artifacts directory; the comparison branches
|
|
269
|
-
// set WP_PERF_NO_TRACE so the head branch's traces aren't overwritten.
|
|
270
|
-
if ( process.env.WP_PERF_NO_TRACE ) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Traces are saved minified. Run the following against a downloaded trace
|
|
275
|
-
// + matching `build/` directory to rewrite minified `functionName`s back
|
|
276
|
-
// to their source identifiers:
|
|
277
|
-
//
|
|
278
|
-
// node tools/build-scripts/packages/resolve-trace-source-maps.cjs
|
|
279
|
-
//
|
|
280
|
-
// Or via the workspace script:
|
|
281
|
-
//
|
|
282
|
-
// npm run --workspace @wordpress/build-scripts resolve-trace-source-maps -- <trace.json> [--build-dir <dir>]
|
|
283
|
-
const tracesDir = join( artifactsPath, 'traces' );
|
|
284
|
-
const filePath = join( tracesDir, `${ name }.trace.json` );
|
|
285
|
-
await mkdir( tracesDir, { recursive: true } );
|
|
286
|
-
await writeFile( filePath, JSON.stringify( traceJSON ) );
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @return Durations of all traced `keydown`, `keypress`, and `keyup`
|
|
291
|
-
* events.
|
|
292
|
-
*/
|
|
293
|
-
getTypingEventDurations() {
|
|
294
|
-
return [
|
|
295
|
-
this.getEventDurations( 'keydown' ),
|
|
296
|
-
this.getEventDurations( 'keypress' ),
|
|
297
|
-
this.getEventDurations( 'keyup' ),
|
|
298
|
-
];
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* @return Durations of all traced `focus` and `focusin` events.
|
|
303
|
-
*/
|
|
304
|
-
getSelectionEventDurations() {
|
|
305
|
-
return [
|
|
306
|
-
this.getEventDurations( 'focus' ),
|
|
307
|
-
this.getEventDurations( 'focusin' ),
|
|
308
|
-
];
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* @return Durations of all traced `click` events.
|
|
313
|
-
*/
|
|
314
|
-
getClickEventDurations() {
|
|
315
|
-
return [ this.getEventDurations( 'click' ) ];
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @return Durations of all traced `mouseover` and `mouseout` events.
|
|
320
|
-
*/
|
|
321
|
-
getHoverEventDurations() {
|
|
322
|
-
return [
|
|
323
|
-
this.getEventDurations( 'mouseover' ),
|
|
324
|
-
this.getEventDurations( 'mouseout' ),
|
|
325
|
-
];
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* @param eventType Type of event to filter.
|
|
330
|
-
* @return Durations of all events of a given type.
|
|
331
|
-
*/
|
|
332
|
-
getEventDurations( eventType: EventType ) {
|
|
333
|
-
if ( this.trace.traceEvents.length === 0 ) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
'No trace events found. Did you forget to call stopTracing()?'
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return this.trace.traceEvents
|
|
340
|
-
.filter(
|
|
341
|
-
( item: TraceEvent ): boolean =>
|
|
342
|
-
item.cat === 'devtools.timeline' &&
|
|
343
|
-
item.name === 'EventDispatch' &&
|
|
344
|
-
item?.args?.data?.type === eventType &&
|
|
345
|
-
!! item.dur
|
|
346
|
-
)
|
|
347
|
-
.map( ( item ) => ( item.dur ? item.dur / 1000 : 0 ) );
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Initializes the web-vitals library upon next page navigation.
|
|
352
|
-
*
|
|
353
|
-
* Defaults to automatically triggering the navigation,
|
|
354
|
-
* but it can also be done manually.
|
|
355
|
-
*
|
|
356
|
-
* @example
|
|
357
|
-
* ```js
|
|
358
|
-
* await metrics.initWebVitals();
|
|
359
|
-
* console.log( await metrics.getWebVitals() );
|
|
360
|
-
* ```
|
|
361
|
-
*
|
|
362
|
-
* @example
|
|
363
|
-
* ```js
|
|
364
|
-
* await metrics.initWebVitals( false );
|
|
365
|
-
* await page.goto( '/some-other-page' );
|
|
366
|
-
* console.log( await metrics.getWebVitals() );
|
|
367
|
-
* ```
|
|
368
|
-
*
|
|
369
|
-
* @param reload Whether to force navigation by reloading the current page.
|
|
370
|
-
*/
|
|
371
|
-
async initWebVitals( reload = true ) {
|
|
372
|
-
await this.page.addInitScript( {
|
|
373
|
-
path: join(
|
|
374
|
-
__dirname,
|
|
375
|
-
'../../../../node_modules/web-vitals/dist/web-vitals.umd.cjs'
|
|
376
|
-
),
|
|
377
|
-
} );
|
|
378
|
-
|
|
379
|
-
await this.page.exposeFunction(
|
|
380
|
-
'__reportVitals__',
|
|
381
|
-
( data: string ) => {
|
|
382
|
-
const measurement: Metric = JSON.parse( data );
|
|
383
|
-
this.webVitals[ measurement.name ] = measurement.value;
|
|
384
|
-
}
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
await this.page.addInitScript( () => {
|
|
388
|
-
const reportVitals = ( measurement: unknown ) =>
|
|
389
|
-
window.__reportVitals__( JSON.stringify( measurement ) );
|
|
390
|
-
|
|
391
|
-
window.addEventListener( 'DOMContentLoaded', () => {
|
|
392
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
393
|
-
window.webVitals.onCLS( reportVitals );
|
|
394
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
395
|
-
window.webVitals.onFCP( reportVitals );
|
|
396
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
397
|
-
window.webVitals.onFID( reportVitals );
|
|
398
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
399
|
-
window.webVitals.onINP( reportVitals );
|
|
400
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
401
|
-
window.webVitals.onLCP( reportVitals );
|
|
402
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
403
|
-
window.webVitals.onTTFB( reportVitals );
|
|
404
|
-
} );
|
|
405
|
-
} );
|
|
406
|
-
|
|
407
|
-
if ( reload ) {
|
|
408
|
-
// By reloading the page the script will be applied.
|
|
409
|
-
await this.page.reload();
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Returns web vitals as collected by the web-vitals library.
|
|
415
|
-
*
|
|
416
|
-
* If the web-vitals library hasn't been loaded on the current page yet,
|
|
417
|
-
* it will be initialized with a page reload.
|
|
418
|
-
*
|
|
419
|
-
* Reloads the page to force web-vitals to report all collected metrics.
|
|
420
|
-
*
|
|
421
|
-
* @return {WebVitalsMeasurements} Web vitals measurements.
|
|
422
|
-
*/
|
|
423
|
-
async getWebVitals() {
|
|
424
|
-
// Reset values.
|
|
425
|
-
this.webVitals = {};
|
|
426
|
-
|
|
427
|
-
const hasScript = await this.page.evaluate(
|
|
428
|
-
// @ts-expect-error This is valid but web-vitals does not register the global types.
|
|
429
|
-
() => typeof window.webVitals !== 'undefined'
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
if ( ! hasScript ) {
|
|
433
|
-
await this.initWebVitals();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Trigger navigation so the web-vitals library reports values on unload.
|
|
437
|
-
await this.page.reload();
|
|
438
|
-
|
|
439
|
-
return this.webVitals;
|
|
440
|
-
}
|
|
441
|
-
}
|