diff --git a/apps/wpcom-block-editor/src/wpcom/features/tracking.js b/apps/wpcom-block-editor/src/wpcom/features/tracking.js index 5feff65e4c304..b068b36373624 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/tracking.js +++ b/apps/wpcom-block-editor/src/wpcom/features/tracking.js @@ -223,11 +223,29 @@ const getBlocksTracker = ( eventName ) => ( blockIds ) => { */ const maybeTrackPatternInsertion = ( actionData ) => { const meta = find( actionData, ( item ) => item?.patternName ); - const patternName = meta?.patternName; + let patternName = meta?.patternName; + + // Quick block inserter doesn't use an object to store the patternName + // in the metadata. The pattern name is just directly used as a string. + if ( ! patternName ) { + const patterns = select( 'core/block-editor' ).getSettings().__experimentalBlockPatterns; + const actionDataToCheck = Object.values( actionData ).filter( + ( data ) => typeof data === 'string' + ); + const foundPattern = patterns.find( ( pattern ) => actionDataToCheck.includes( pattern.name ) ); + if ( foundPattern ) { + patternName = foundPattern.name; + } + } if ( patternName ) { + const patternCategory = + // Pattern category dropdown in global inserter + document.querySelector( '.block-editor-inserter__panel-header-patterns select' )?.value; + tracksRecordEvent( 'wpcom_pattern_inserted', { pattern_name: patternName, + pattern_category: patternCategory, blocks_replaced: actionData?.blocks_replaced, } ); } diff --git a/apps/wpcom-block-editor/src/wpcom/features/tracking/track-record-event.js b/apps/wpcom-block-editor/src/wpcom/features/tracking/track-record-event.js index d6fa285c73189..2721e40da1a5e 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/tracking/track-record-event.js +++ b/apps/wpcom-block-editor/src/wpcom/features/tracking/track-record-event.js @@ -22,7 +22,7 @@ if ( typeof window !== 'undefined' ) { // Enable a events stack for e2e testing purposes // on e2e test environments only. // see https://github.com/Automattic/wp-calypso/pull/41329. -const E2E_STACK_SIZE = 20; +const E2E_STACK_SIZE = 100; if ( isE2ETest() ) { e2ETracksDebug( 'E2E env' ); window._e2eEventsStack = []; diff --git a/test/e2e/lib/components/site-editor-component.js b/test/e2e/lib/components/site-editor-component.js index 13017867ebb44..b16dfe67f321b 100644 --- a/test/e2e/lib/components/site-editor-component.js +++ b/test/e2e/lib/components/site-editor-component.js @@ -8,6 +8,7 @@ import { By } from 'selenium-webdriver'; * Internal dependencies */ import * as driverHelper from '../driver-helper'; +import * as driverManager from '../driver-manager'; import AsyncBaseContainer from '../async-base-container'; import GutenbergEditorComponent from '../gutenberg/gutenberg-editor-component'; @@ -59,6 +60,23 @@ export default class SiteEditorComponent extends AsyncBaseContainer { } ); } + async isBlockInserterOpen() { + const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + return await driverHelper.isElementLocated( this.driver, inserterMenuLocator ); + } + + async openBlockInserter() { + const inserterToggleLocator = By.css( + '.edit-site-header .edit-site-header-toolbar__inserter-toggle' + ); + if ( ! ( await this.isBlockInserterOpen() ) ) { + await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); + } + + const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + await driverHelper.waitUntilElementLocatedAndVisible( this.driver, inserterMenuLocator ); + } + async openBlockInserterAndSearch( searchTerm ) { await this.runInCanvas( async () => { await driverHelper.scrollIntoView( @@ -67,23 +85,43 @@ export default class SiteEditorComponent extends AsyncBaseContainer { 'start' ); } ); - const inserterToggleLocator = By.css( - '.edit-site-header .edit-site-header-toolbar__inserter-toggle' - ); - const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + + await this.openBlockInserter(); const inserterSearchInputLocator = By.css( 'input.block-editor-inserter__search-input' ); + await driverHelper.setWhenSettable( this.driver, inserterSearchInputLocator, searchTerm ); + } - if ( await driverHelper.isElementNotLocated( this.driver, inserterMenuLocator ) ) { - await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); - // "Click" twice - the first click seems to trigger a tooltip, the second opens the menu - // See https://github.com/Automattic/wp-calypso/issues/43179 - if ( await driverHelper.isElementNotLocated( this.driver, inserterMenuLocator ) ) { - await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); - } + async insertPattern( category, name ) { + await this.openBlockInserter(); - await driverHelper.waitUntilElementLocatedAndVisible( this.driver, inserterMenuLocator ); - } - await driverHelper.setWhenSettable( this.driver, inserterSearchInputLocator, searchTerm ); + const patternTabLocator = By.css( + '.block-editor-inserter__tabs .components-tab-panel__tabs-item[id$="patterns"]' + ); + const patternCategoryDropdownLocator = By.css( + '.components-tab-panel__tab-content .components-select-control__input' + ); + const patternCategoryDropdownOptionLocator = By.css( + `.components-tab-panel__tab-content .components-select-control__input option[value="${ category }"]` + ); + const patternItemLocator = By.css( + `.block-editor-block-patterns-list__list-item[aria-label="${ name }"]` + ); + await driverHelper.clickWhenClickable( this.driver, patternTabLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownOptionLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownLocator ); + await driverHelper.clickWhenClickable( this.driver, patternItemLocator ); + } + + async closeBlockInserter() { + const inserterCloseLocator = By.css( + driverManager.currentScreenSize() === 'mobile' + ? '.edit-site-editor__inserter-panel-header .components-button' + : '.edit-site-header-toolbar__inserter-toggle' + ); + const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + await driverHelper.clickWhenClickable( this.driver, inserterCloseLocator ); + await driverHelper.waitUntilElementNotLocated( this.driver, inserterMenuLocator ); } async addBlock( title ) { @@ -179,4 +217,37 @@ export default class SiteEditorComponent extends AsyncBaseContainer { ); } } + + async dismissNotices() { + const snackbarNoticeLocator = By.css( + '.components-snackbar[aria-label="Dismiss this notice"]' + ); + + const notices = await this.driver.findElements( snackbarNoticeLocator ); + for ( const notice of notices ) { + await driverHelper.clickWhenClickable( this.driver, () => notice ); + } + + await driverHelper.waitUntilElementNotLocated( this.driver, snackbarNoticeLocator ); + } + + async insertBlockOrPatternViaBlockAppender( name, container = 'Group' ) { + const containerBlockId = await this.addBlock( container ); + await this.runInCanvas( async () => { + const blockAppenderLocator = By.css( + `#${ containerBlockId } .block-editor-button-block-appender` + ); + await driverHelper.clickWhenClickable( this.driver, blockAppenderLocator ); + } ); + + const quickInserterSearchInputLocator = By.css( + '.block-editor-inserter__quick-inserter .block-editor-inserter__search-input' + ); + const patternItemLocator = By.css( + '.block-editor-inserter__quick-inserter .block-editor-block-types-list__item, .block-editor-inserter__quick-inserter .block-editor-block-patterns-list__item' + ); + + await driverHelper.setWhenSettable( this.driver, quickInserterSearchInputLocator, name ); + await driverHelper.clickWhenClickable( this.driver, patternItemLocator ); + } } diff --git a/test/e2e/lib/gutenberg/gutenberg-editor-component.js b/test/e2e/lib/gutenberg/gutenberg-editor-component.js index 0adf832948a2c..4cac5c3fbcad3 100644 --- a/test/e2e/lib/gutenberg/gutenberg-editor-component.js +++ b/test/e2e/lib/gutenberg/gutenberg-editor-component.js @@ -230,31 +230,58 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer { return await driverHelper.isElementLocated( this.driver, By.css( '.block-editor-warning' ) ); } + async isBlockInserterOpen() { + const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + return await driverHelper.isElementLocated( this.driver, inserterMenuLocator ); + } + + async openBlockInserter() { + const inserterToggleLocator = By.css( + '.edit-post-header .edit-post-header-toolbar__inserter-toggle' + ); + if ( ! ( await this.isBlockInserterOpen() ) ) { + await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); + } + + const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); + await driverHelper.waitUntilElementLocatedAndVisible( this.driver, inserterMenuLocator ); + } + async openBlockInserterAndSearch( searchTerm ) { await driverHelper.scrollIntoView( this.driver, By.css( '.block-editor-writing-flow' ), 'start' ); - const inserterToggleLocator = By.css( - '.edit-post-header .edit-post-header-toolbar__inserter-toggle' - ); - const inserterMenuLocator = By.css( '.block-editor-inserter__menu' ); - const inserterSearchInputLocator = By.css( 'input.block-editor-inserter__search-input' ); - if ( await driverHelper.isElementNotLocated( this.driver, inserterMenuLocator ) ) { - await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); - // "Click" twice - the first click seems to trigger a tooltip, the second opens the menu - // See https://github.com/Automattic/wp-calypso/issues/43179 - if ( await driverHelper.isElementNotLocated( this.driver, inserterMenuLocator ) ) { - await driverHelper.clickWhenClickable( this.driver, inserterToggleLocator ); - } + await this.openBlockInserter(); + const inserterSearchInputLocator = By.css( 'input.block-editor-inserter__search-input' ); - await driverHelper.waitUntilElementLocatedAndVisible( this.driver, inserterMenuLocator ); - } await driverHelper.setWhenSettable( this.driver, inserterSearchInputLocator, searchTerm ); } + async insertPattern( category, name ) { + await this.openBlockInserter(); + + const patternTabLocator = By.css( + '.block-editor-inserter__tabs .components-tab-panel__tabs-item[id$="patterns"]' + ); + const patternCategoryDropdownLocator = By.css( + '.components-tab-panel__tab-content .components-select-control__input' + ); + const patternCategoryDropdownOptionLocator = By.css( + `.components-tab-panel__tab-content .components-select-control__input option[value="${ category }"]` + ); + const patternItemLocator = By.css( + `.block-editor-block-patterns-list__list-item[aria-label="${ name }"]` + ); + await driverHelper.clickWhenClickable( this.driver, patternTabLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownOptionLocator ); + await driverHelper.clickWhenClickable( this.driver, patternCategoryDropdownLocator ); + await driverHelper.clickWhenClickable( this.driver, patternItemLocator ); + } + // @TODO: Remove `.block-editor-inserter__results .components-panel__body-title` selector in favor of the `.block-editor-inserter__block-list .block-editor-inserter__panel-title` selector when Gutenberg 8.0.0 is deployed. async isBlockCategoryPresent( name ) { const categoryLocator = @@ -676,4 +703,29 @@ export default class GutenbergEditorComponent extends AsyncBaseContainer { By.css( '.edit-post-header .table-of-contents button' ) ); } + + async dismissNotices() { + const locator = By.css( '.components-snackbar[aria-label="Dismiss this notice"]' ); + const notices = await this.driver.findElements( locator ); + await Promise.all( notices.map( ( notice ) => notice.click() ) ); + await driverHelper.waitUntilElementNotLocated( this.driver, locator ); + } + + async insertBlockOrPatternViaBlockAppender( name, container = 'Group' ) { + const containerBlockId = await this.addBlock( container ); + const blockAppenderLocator = By.css( + `#${ containerBlockId } .block-editor-button-block-appender` + ); + await driverHelper.clickWhenClickable( this.driver, blockAppenderLocator ); + + const quickInserterSearchInputLocator = By.css( + '.block-editor-inserter__quick-inserter .block-editor-inserter__search-input' + ); + const patternItemLocator = By.css( + '.block-editor-inserter__quick-inserter .block-editor-block-types-list__item, .block-editor-inserter__quick-inserter .block-editor-block-patterns-list__item' + ); + + await driverHelper.setWhenSettable( this.driver, quickInserterSearchInputLocator, name ); + await driverHelper.clickWhenClickable( this.driver, patternItemLocator ); + } } diff --git a/test/e2e/lib/gutenberg/tracking/general-tests.js b/test/e2e/lib/gutenberg/tracking/general-tests.js index ebfecbea6a70a..a35b1a8c645c6 100644 --- a/test/e2e/lib/gutenberg/tracking/general-tests.js +++ b/test/e2e/lib/gutenberg/tracking/general-tests.js @@ -9,7 +9,7 @@ import { By } from 'selenium-webdriver'; */ import GutenbergEditorComponent from '../gutenberg-editor-component'; import SiteEditorComponent from '../../components/site-editor-component'; -import { getEventsStack, getTotalEventsFiredForBlock } from './utils'; +import { clearEventsStack, getEventsStack, getTotalEventsFiredForBlock } from './utils'; import * as driverHelper from '../../driver-helper'; export function createGeneralTests( { it, editorType, postType } ) { @@ -173,6 +173,99 @@ export function createGeneralTests( { it, editorType, postType } ) { ); } ); + it( 'Tracks "wpcom_pattern_inserted"', async function () { + const editor = await EditorComponent.Expect( this.driver, gutenbergEditorType ); + + await editor.insertPattern( 'list', 'List with Image' ); + const eventsStackList = await getEventsStack( this.driver ); + await clearEventsStack( this.driver ); + + await editor.insertPattern( 'gallery', 'Heading and Three Images' ); + // We need to save the eventsStack after each insertion to make sure we + // aren't running out of the E2E queue size. + const eventsStackGallery = await getEventsStack( this.driver ); + if ( await editor.isBlockInserterOpen() ) { + await editor.closeBlockInserter(); + } + await editor.dismissNotices(); + + const patternInsertedEvents = [ ...eventsStackGallery, ...eventsStackList ].filter( + ( [ eventName ] ) => eventName === 'wpcom_pattern_inserted' + ); + assert.strictEqual( + patternInsertedEvents.length, + 2, + '"wpcom_pattern_inserted" editor tracking event failed to fire for both patterns' + ); + const [ , eventDataGallery ] = patternInsertedEvents[ 0 ]; + const [ , eventDataList ] = patternInsertedEvents[ 1 ]; + assert.strictEqual( + eventDataGallery.pattern_name, + 'a8c/heading-and-three-images', + '"wpcom_pattern_inserted" editor tracking event pattern name property is incorrect' + ); + assert.strictEqual( + eventDataGallery.pattern_category, + 'gallery', + '"wpcom_pattern_inserted" editor tracking event pattern category property is incorrect' + ); + assert.strictEqual( + eventDataList.pattern_name, + 'a8c/list-with-image', + '"wpcom_pattern_inserted" editor tracking event pattern name property is incorrect' + ); + assert.strictEqual( + eventDataList.pattern_category, + 'list', + '"wpcom_pattern_inserted" editor tracking event pattern category property is incorrect' + ); + } ); + + it( 'Tracks "wpcom_pattern_inserted"', async function () { + const editor = await EditorComponent.Expect( this.driver, gutenbergEditorType ); + + await editor.insertBlockOrPatternViaBlockAppender( 'List with Image' ); + const eventsStackList = await getEventsStack( this.driver ); + await clearEventsStack( this.driver ); + + await editor.insertBlockOrPatternViaBlockAppender( 'Heading and Three Images' ); + // We need to save the eventsStack after each insertion to make sure we + // aren't running out of the E2E queue size. + const eventsStackGallery = await getEventsStack( this.driver ); + await editor.dismissNotices(); + + const patternInsertedEvents = [ ...eventsStackGallery, ...eventsStackList ].filter( + ( [ eventName ] ) => eventName === 'wpcom_pattern_inserted' + ); + assert.strictEqual( + patternInsertedEvents.length, + 2, + '"wpcom_pattern_inserted" editor tracking event failed to fire for both patterns' + ); + const [ , eventDataGallery ] = patternInsertedEvents[ 0 ]; + const [ , eventDataList ] = patternInsertedEvents[ 1 ]; + assert.strictEqual( + eventDataGallery.pattern_name, + 'a8c/heading-and-three-images', + '"wpcom_pattern_inserted" editor tracking event pattern name property is incorrect' + ); + assert.strictEqual( + typeof eventDataGallery.pattern_category, + 'undefined', + '"wpcom_pattern_inserted" editor tracking event pattern category property should not be present' + ); + assert.strictEqual( + eventDataList.pattern_name, + 'a8c/list-with-image', + '"wpcom_pattern_inserted" editor tracking event pattern name property is incorrect' + ); + assert.strictEqual( + typeof eventDataGallery.pattern_category, + 'undefined', + '"wpcom_pattern_inserted" editor tracking event pattern category property should not be present' + ); + } ); + if ( editorType === 'post' ) { it( 'Tracks "wpcom_block_editor_details_open" event', async function () { const editor = await EditorComponent.Expect( this.driver, gutenbergEditorType ); diff --git a/test/e2e/specs/specs-wpcom/wp-calypso-gutenberg-site-editor-tracking-spec.js b/test/e2e/specs/specs-wpcom/wp-calypso-gutenberg-site-editor-tracking-spec.js index c986a44f3a35b..72c7203a85450 100644 --- a/test/e2e/specs/specs-wpcom/wp-calypso-gutenberg-site-editor-tracking-spec.js +++ b/test/e2e/specs/specs-wpcom/wp-calypso-gutenberg-site-editor-tracking-spec.js @@ -547,6 +547,7 @@ describe( `[${ host }] Calypso Gutenberg Site Editor Tracking: (${ screenSize }) await editor.addBlock( 'Template Part' ); await clearEventsStack( this.driver ); + await editor.dismissNotices(); await editor.runInCanvas( async () => { await driverHelper.clickWhenClickable( @@ -583,6 +584,7 @@ describe( `[${ host }] Calypso Gutenberg Site Editor Tracking: (${ screenSize }) // so the insert event won't intefere with our asserts. const blockId = await editor.addBlock( 'Template Part' ); await clearEventsStack( this.driver ); + await editor.dismissNotices(); // Add a template part block and select an existing template part. // Make sure the template part is loaded before moving on.